diff --git a/CHANGES.txt b/CHANGES.txt index e90c5e127b..047db6472b 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -4,481 +4,26 @@ Next release Features -------- +- Python 3.2 compatibility (except for Paste scaffolding and paster commands, + which do not work, because Paste has not been ported to Python 3 yet). + - Lone instance methods can now be treated as view callables (see https://github.com/Pylons/pyramid/pull/283). -Dependencies ------------- - -- Pyramid no longer depends on the zope.component package, except as a - testing dependency. - -- Pyramid now depends on a ``zope.interface`` version greater than or equal - to 3.8.0. - -1.2 (2011-09-12) -================ - -Features --------- - -- Route pattern replacement marker names can now begin with an underscore. - See https://github.com/Pylons/pyramid/issues/276. - -1.2b3 (2011-09-11) -================== - -Bug Fixes ---------- - -- The route prefix was not taken into account when a static view was added in - an "include". See https://github.com/Pylons/pyramid/issues/266 . - -1.2b2 (2011-09-08) -================== - -Bug Fixes ---------- - -- The 1.2b1 tarball was a brownbag (particularly for Windows users) because - it contained filenames with stray quotation marks in inappropriate places. - We depend on ``setuptools-git`` to produce release tarballs, and when it - was run to produce the 1.2b1 tarball, it didn't yet cope well with files - present in git repositories with high-order characters in their filenames. - -Documentation -------------- - -- Minor tweaks to the "Introduction" narrative chapter example app and - wording. - -1.2b1 (2011-09-08) -================== - -Bug Fixes ---------- - -- Sometimes falling back from territory translations (``de_DE``) to language - translations (``de``) would not work properly when using a localizer. See - https://github.com/Pylons/pyramid/issues/263 - -- The static file serving machinery could not serve files that started with a - ``.`` (dot) character. - -- Static files with high-order (super-ASCII) characters in their names could - not be served by a static view. The static file serving machinery - inappropriately URL-quoted path segments in filenames when asking for files - from the filesystem. - -- Within ``pyramid.traversal.traversal_path`` , canonicalize URL segments - from UTF-8 to Unicode before checking whether a segment matches literally - one of ``.``, the empty string, or ``..`` in case there's some sneaky way - someone might tunnel those strings via UTF-8 that don't match the literals - before decoded. - -Documentation -------------- - -- Added a "What Makes Pyramid Unique" section to the Introduction narrative - chapter. - -1.2a6 (2011-09-06) -================== - -Bug Fixes ---------- - -- AuthTktAuthenticationPolicy with a ``reissue_time`` interfered with logout. - See https://github.com/Pylons/pyramid/issues/262. - -Internal --------- - -- Internalize code previously depended upon as imports from the - ``paste.auth`` module (futureproof). - -- Replaced use of ``paste.urlparser.StaticURLParser`` with a derivative of - Chris Rossi's "happy" static file serving code (futureproof). - -- Fixed test suite; on some systems tests would fail due to indeterminate - test run ordering and a double-push-single-pop of a shared test variable. - -Behavior Differences --------------------- - -- An ETag header is no longer set when serving a static file. A - Last-Modified header is set instead. - -- Static file serving no longer supports the ``wsgi.file_wrapper`` extension. - -- Instead of returning a ``403 Forbidden`` error when a static file is served - that cannot be accessed by the Pyramid process' user due to file - permissions, an IOError (or similar) will be raised. - -Scaffolds ---------- - -- All scaffolds now send the ``cache_max_age`` parameter to the - ``add_static_view`` method. - -1.2a5 (2011-09-04) -================== - -Bug Fixes ---------- - -- The ``route_prefix`` of a configurator was not properly taken into account - when registering routes in certain circumstances. See - https://github.com/Pylons/pyramid/issues/260 - -Dependencies ------------- - -- The ``zope.configuration`` package is no longer a dependency. - -1.2a4 (2011-09-02) -================== - -Features --------- - -- Support an ``onerror`` keyword argument to - ``pyramid.config.Configurator.scan()``. This onerror keyword argument is - passed to ``venusian.Scanner.scan()`` to influence error behavior when - an exception is raised during scanning. - -- The ``request_method`` predicate argument to - ``pyramid.config.Configurator.add_view`` and - ``pyramid.config.Configurator.add_route`` is now permitted to be a tuple of - HTTP method names. Previously it was restricted to being a string - representing a single HTTP method name. - -- Undeprecated ``pyramid.traversal.find_model``, - ``pyramid.traversal.model_path``, ``pyramid.traversal.model_path_tuple``, - and ``pyramid.url.model_url``, which were all deprecated in Pyramid 1.0. - There's just not much cost to keeping them around forever as aliases to - their renamed ``resource_*`` prefixed functions. - -- Undeprecated ``pyramid.view.bfg_view``, which was deprecated in Pyramid - 1.0. This is a low-cost alias to ``pyramid.view.view_config`` which we'll - just keep around forever. - -Dependencies ------------- - -- Pyramid now requires Venusian 1.0a1 or better to support the ``onerror`` - keyword argument to ``pyramid.config.Configurator.scan``. - -1.2a3 (2011-08-29) -================== - -Bug Fixes ---------- - -- Pyramid did not properly generate static URLs using - ``pyramid.url.static_url`` when passed a caller-package relative path due - to a refactoring done in 1.2a1. - -- The ``settings`` object emitted a deprecation warning any time - ``__getattr__`` was called upon it. However, there are legitimate - situations in which ``__getattr__`` is called on arbitrary objects - (e.g. ``hasattr``). Now, the ``settings`` object only emits the warning - upon successful lookup. - -Internal --------- - -- Use ``config.with_package`` in view_config decorator rather than - manufacturing a new renderer helper (cleanup). - -1.2a2 (2011-08-27) -================== - -Bug Fixes ---------- - -- When a ``renderers=`` argument is not specified to the Configurator - constructor, eagerly register and commit the default renderer set. This - permits the overriding of the default renderers, which was broken in 1.2a1 - without a commit directly after Configurator construction. - -- Mako rendering exceptions had the wrong value for an error message. - -- An include could not set a root factory successfully because the - Configurator constructor unconditionally registered one that would be - treated as if it were "the word of the user". - -Features --------- - -- A session factory can now be passed in using the dotted name syntax. - -1.2a1 (2011-08-24) -================== - -Features --------- - -- The ``[pshell]`` section in an ini configuration file now treats a - ``setup`` key as a dotted name that points to a callable that is passed the - bootstrap environment. It can mutate the environment as necessary for - great justice. - -- A new configuration setting named ``pyramid.includes`` is now available. - It is described in the "Environment Variables and ``.ini`` Files Settings" - narrative documentation chapter. - -- Added a ``route_prefix`` argument to the - ``pyramid.config.Configurator.include`` method. This argument allows you - to compose URL dispatch applications together. See the section entitled - "Using a Route Prefix to Compose Applications" in the "URL Dispatch" - narrative documentation chapter. - -- Added a ``pyramid.security.NO_PERMISSION_REQUIRED`` constant for use in - ``permission=`` statements to view configuration. This constant has a - value of the string ``__no_permission_required__``. This string value was - previously referred to in documentation; now the documentation uses the - constant. - -- Added a decorator-based way to configure a response adapter: - ``pyramid.response.response_adapter``. This decorator has the same use as - ``pyramid.config.Configurator.add_response_adapter`` but it's declarative. - -- The ``pyramid.events.BeforeRender`` event now has an attribute named - ``rendering_val``. This can be used to introspect the value returned by a - view in a BeforeRender subscriber. - -- New configurator directive: ``pyramid.config.Configurator.add_tween``. - This directive adds a "tween". A "tween" is used to wrap the Pyramid - router's primary request handling function. This is a feature may be used - by Pyramid framework extensions, to provide, for example, view timing - support and as a convenient place to hang bookkeeping code. - - Tweens are further described in the narrative docs section in the Hooks - chapter, named "Registering Tweens". - -- New paster command ``paster ptweens``, which prints the current "tween" - configuration for an application. See the section entitled "Displaying - Tweens" in the Command-Line Pyramid chapter of the narrative documentation - for more info. - -- The Pyramid debug logger now uses the standard logging configuration - (usually set up by Paste as part of startup). This means that output from - e.g. ``debug_notfound``, ``debug_authorization``, etc. will go to the - normal logging channels. The logger name of the debug logger will be the - package name of the *caller* of the Configurator's constructor. - -- A new attribute is available on request objects: ``exc_info``. Its value - will be ``None`` until an exception is caught by the Pyramid router, after - which it will be the result of ``sys.exc_info()``. - -- ``pyramid.testing.DummyRequest`` now implements the - ``add_finished_callback`` and ``add_response_callback`` methods. - -- New methods of the ``pyramid.config.Configurator`` class: - ``set_authentication_policy`` and ``set_authorization_policy``. These are - meant to be consumed mostly by add-on authors. - -- New Configurator method: ``set_root_factory``. - -- Pyramid no longer eagerly commits some default configuration statements at - Configurator construction time, which permits values passed in as - constructor arguments (e.g. ``authentication_policy`` and - ``authorization_policy``) to override the same settings obtained via an - "include". - -- Better Mako rendering exceptions via - ``pyramid.mako_templating.MakoRenderingException`` - -- New request methods: ``current_route_url``, ``current_route_path``, and - ``static_path``. - -- New functions in ``pyramid.url``: ``current_route_path`` and - ``static_path``. - -- The ``pyramid.request.Request.static_url`` API (and its brethren - ``pyramid.request.Request.static_path``, ``pyramid.url.static_url``, and - ``pyramid.url.static_path``) now accept an asbolute filename as a "path" - argument. This will generate a URL to an asset as long as the filename is - in a directory which was previously registered as a static view. - Previously, trying to generate a URL to an asset using an absolute file - path would raise a ValueError. - -- The ``RemoteUserAuthenticationPolicy ``, ``AuthTktAuthenticationPolicy``, - and ``SessionAuthenticationPolicy`` constructors now accept an additional - keyword argument named ``debug``. By default, this keyword argument is - ``False``. When it is ``True``, debug information will be sent to the - Pyramid debug logger (usually on stderr) when the ``authenticated_userid`` - or ``effective_principals`` method is called on any of these policies. The - output produced can be useful when trying to diagnose - authentication-related problems. - -- New view predicate: ``match_param``. Example: a view added via - ``config.add_view(aview, match_param='action=edit')`` will be called only - when the ``request.matchdict`` has a value inside it named ``action`` with - a value of ``edit``. - -Internal --------- - -- The Pyramid "exception view" machinery is now implemented as a "tween" - (``pyramid.tweens.excview_tween_factory``). - -- WSGIHTTPException (HTTPFound, HTTPNotFound, etc) now has a new API named - "prepare" which renders the body and content type when it is provided with - a WSGI environ. Required for debug toolbar. - -- Once ``__call__`` or ``prepare`` is called on a WSGIHTTPException, the body - will be set, and subsequent calls to ``__call__`` will always return the - same body. Delete the body attribute to rerender the exception body. - -- Previously the ``pyramid.events.BeforeRender`` event *wrapped* a dictionary - (it addressed it as its ``_system`` attribute). Now it *is* a dictionary - (it inherits from ``dict``), and it's the value that is passed to templates - as a top-level dictionary. - -- The ``route_url``, ``route_path``, ``resource_url``, ``static_url``, and - ``current_route_url`` functions in the ``pyramid.url`` package now delegate - to a method on the request they've been passed, instead of the other way - around. The pyramid.request.Request object now inherits from a mixin named - pyramid.url.URLMethodsMixin to make this possible, and all url/path - generation logic is embedded in this mixin. - -- Refactor ``pyramid.config`` into a package. - -- Removed the ``_set_security_policies`` method of the Configurator. - -- Moved the ``StaticURLInfo`` class from ``pyramid.static`` to - ``pyramid.config.views``. - -- Move the ``Settings`` class from ``pyramid.settings`` to - ``pyramid.config.settings``. - -- Move the ``OverrideProvider``, ``PackageOverrides``, ``DirectoryOverride``, - and ``FileOverride`` classes from ``pyramid.asset`` to - ``pyramid.config.assets``. - -Deprecations ------------- - -- All Pyramid-related deployment settings (e.g. ``debug_all``, - ``debug_notfound``) are now meant to be prefixed with the prefix - ``pyramid.``. For example: ``debug_all`` -> ``pyramid.debug_all``. The - old non-prefixed settings will continue to work indefinitely but supplying - them may eventually print a deprecation warning. All scaffolds and - tutorials have been changed to use prefixed settings. - -- The ``settings`` dictionary now raises a deprecation warning when you - attempt to access its values via ``__getattr__`` instead of - via ``__getitem__``. - Backwards Incompatibilities --------------------------- -- If a string is passed as the ``debug_logger`` parameter to a Configurator, - that string is considered to be the name of a global Python logger rather - than a dotted name to an instance of a logger. - -- The ``pyramid.config.Configurator.include`` method now accepts only a - single ``callable`` argument (a sequence of callables used to be - permitted). If you are passing more than one ``callable`` to - ``pyramid.config.Configurator.include``, it will break. You now must now - instead make a separate call to the method for each callable. This change - was introduced to support the ``route_prefix`` feature of include. - -- It may be necessary to more strictly order configuration route and view - statements when using an "autocommitting" Configurator. In the past, it - was possible to add a view which named a route name before adding a route - with that name when you used an autocommitting configurator. For example:: - - config = Configurator(autocommit=True) - config.add_view('my.pkg.someview', route_name='foo') - config.add_route('foo', '/foo') - - The above will raise an exception when the view attempts to add itself. - Now you must add the route before adding the view:: - - config = Configurator(autocommit=True) - config.add_route('foo', '/foo') - config.add_view('my.pkg.someview', route_name='foo') - - This won't effect "normal" users, only people who have legacy BFG codebases - that used an autommitting configurator and possibly tests that use the - configurator API (the configurator returned by ``pyramid.testing.setUp`` is - an autocommitting configurator). The right way to get around this is to - use a non-autocommitting configurator (the default), which does not have - these directive ordering requirements. - -- The ``pyramid.config.Configurator.add_route`` directive no longer returns a - route object. This change was required to make route vs. view - configuration processing work properly. - -Documentation -------------- - -- Narrative and API documentation which used the ``route_url``, - ``route_path``, ``resource_url``, ``static_url``, and ``current_route_url`` - functions in the ``pyramid.url`` package have now been changed to use - eponymous methods of the request instead. - -- Added a section entitled "Using a Route Prefix to Compose Applications" to - the "URL Dispatch" narrative documentation chapter. - -- Added a new module to the API docs: ``pyramid.tweens``. +- Pyramid no longer runs on Python 2.5 (which includes the most recent + release of Jython, and the current version of GAE). -- Added a "Registering Tweens" section to the "Hooks" narrative chapter. - -- Added a "Displaying Tweens" section to the "Command-Line Pyramid" narrative - chapter. - -- Added documentation for the ``pyramid.tweens`` and ``pyramid.includes`` - configuration settings to the "Environment Variables and ``.ini`` Files - Settings" chapter. - -- Added a Logging chapter to the narrative docs (based on the Pylons logging - docs, thanks Phil). - -- Added a Paste chapter to the narrative docs (moved content from the Project - chapter). - -- Added the ``pyramid.interfaces.IDict`` interface representing the methods - of a dictionary, for documentation purposes only (IMultiDict and - IBeforeRender inherit from it). - -- All tutorials now use - The ``route_url``, ``route_path``, - ``resource_url``, ``static_url``, and ``current_route_url`` methods of the - request rather than the function variants imported from ``pyramid.url``. - -- The ZODB wiki tutorial now uses the ``pyramid_zodbconn`` package rather - than the ``repoze.zodbconn`` package to provide ZODB integration. - -Dependency Changes ------------------- - -- Pyramid now relies on PasteScript >= 1.7.4. This version contains a - feature important for allowing flexible logging configuration. - -Scaffolds ----------- - -- All scaffolds now use the ``pyramid_tm`` package rather than the - ``repoze.tm2`` middleware to manage transaction management. - -- The ZODB scaffold now uses the ``pyramid_zodbconn`` package rather than the - ``repoze.zodbconn`` package to provide ZODB integration. - -- All scaffolds now use the ``pyramid_debugtoolbar`` package rather than the - ``WebError`` package to provide interactive debugging features. - -- Projects created via a scaffold no longer depend on the ``WebError`` - package at all; configuration in the ``production.ini`` file which used to - require its ``error_catcher`` middleware has been removed. Configuring - error catching / email sending is now the domain of the ``pyramid_exclog`` - package (see https://docs.pylonsproject.org/projects/pyramid_exclog/dev/). +Dependencies +------------ -Bug Fixes ---------- +- Pyramid no longer depends on the zope.component package, except as a + testing dependency. -- Fixed an issue with the default renderer not working at certain times. See - https://github.com/Pylons/pyramid/issues/249 +- Pyramid now depends on a zope.interface>=3.8.0, WebOb>=1.2dev, + repoze.lru>=0.4, zope.deprecation>=3.5.0, translationstring>=0.4 (for + Python 3 compatibility purposes). It also, as a testing dependency, + depends on WebTest>=1.3.1 for the same reason. diff --git a/HISTORY.txt b/HISTORY.txt index 5cabb01114..956f07362d 100644 --- a/HISTORY.txt +++ b/HISTORY.txt @@ -1,3 +1,470 @@ +1.2 (2011-09-12) +================ + +Features +-------- + +- Route pattern replacement marker names can now begin with an underscore. + See https://github.com/Pylons/pyramid/issues/276. + +1.2b3 (2011-09-11) +================== + +Bug Fixes +--------- + +- The route prefix was not taken into account when a static view was added in + an "include". See https://github.com/Pylons/pyramid/issues/266 . + +1.2b2 (2011-09-08) +================== + +Bug Fixes +--------- + +- The 1.2b1 tarball was a brownbag (particularly for Windows users) because + it contained filenames with stray quotation marks in inappropriate places. + We depend on ``setuptools-git`` to produce release tarballs, and when it + was run to produce the 1.2b1 tarball, it didn't yet cope well with files + present in git repositories with high-order characters in their filenames. + +Documentation +------------- + +- Minor tweaks to the "Introduction" narrative chapter example app and + wording. + +1.2b1 (2011-09-08) +================== + +Bug Fixes +--------- + +- Sometimes falling back from territory translations (``de_DE``) to language + translations (``de``) would not work properly when using a localizer. See + https://github.com/Pylons/pyramid/issues/263 + +- The static file serving machinery could not serve files that started with a + ``.`` (dot) character. + +- Static files with high-order (super-ASCII) characters in their names could + not be served by a static view. The static file serving machinery + inappropriately URL-quoted path segments in filenames when asking for files + from the filesystem. + +- Within ``pyramid.traversal.traversal_path`` , canonicalize URL segments + from UTF-8 to Unicode before checking whether a segment matches literally + one of ``.``, the empty string, or ``..`` in case there's some sneaky way + someone might tunnel those strings via UTF-8 that don't match the literals + before decoded. + +Documentation +------------- + +- Added a "What Makes Pyramid Unique" section to the Introduction narrative + chapter. + +1.2a6 (2011-09-06) +================== + +Bug Fixes +--------- + +- AuthTktAuthenticationPolicy with a ``reissue_time`` interfered with logout. + See https://github.com/Pylons/pyramid/issues/262. + +Internal +-------- + +- Internalize code previously depended upon as imports from the + ``paste.auth`` module (futureproof). + +- Replaced use of ``paste.urlparser.StaticURLParser`` with a derivative of + Chris Rossi's "happy" static file serving code (futureproof). + +- Fixed test suite; on some systems tests would fail due to indeterminate + test run ordering and a double-push-single-pop of a shared test variable. + +Behavior Differences +-------------------- + +- An ETag header is no longer set when serving a static file. A + Last-Modified header is set instead. + +- Static file serving no longer supports the ``wsgi.file_wrapper`` extension. + +- Instead of returning a ``403 Forbidden`` error when a static file is served + that cannot be accessed by the Pyramid process' user due to file + permissions, an IOError (or similar) will be raised. + +Scaffolds +--------- + +- All scaffolds now send the ``cache_max_age`` parameter to the + ``add_static_view`` method. + +1.2a5 (2011-09-04) +================== + +Bug Fixes +--------- + +- The ``route_prefix`` of a configurator was not properly taken into account + when registering routes in certain circumstances. See + https://github.com/Pylons/pyramid/issues/260 + +Dependencies +------------ + +- The ``zope.configuration`` package is no longer a dependency. + +1.2a4 (2011-09-02) +================== + +Features +-------- + +- Support an ``onerror`` keyword argument to + ``pyramid.config.Configurator.scan()``. This onerror keyword argument is + passed to ``venusian.Scanner.scan()`` to influence error behavior when + an exception is raised during scanning. + +- The ``request_method`` predicate argument to + ``pyramid.config.Configurator.add_view`` and + ``pyramid.config.Configurator.add_route`` is now permitted to be a tuple of + HTTP method names. Previously it was restricted to being a string + representing a single HTTP method name. + +- Undeprecated ``pyramid.traversal.find_model``, + ``pyramid.traversal.model_path``, ``pyramid.traversal.model_path_tuple``, + and ``pyramid.url.model_url``, which were all deprecated in Pyramid 1.0. + There's just not much cost to keeping them around forever as aliases to + their renamed ``resource_*`` prefixed functions. + +- Undeprecated ``pyramid.view.bfg_view``, which was deprecated in Pyramid + 1.0. This is a low-cost alias to ``pyramid.view.view_config`` which we'll + just keep around forever. + +Dependencies +------------ + +- Pyramid now requires Venusian 1.0a1 or better to support the ``onerror`` + keyword argument to ``pyramid.config.Configurator.scan``. + +1.2a3 (2011-08-29) +================== + +Bug Fixes +--------- + +- Pyramid did not properly generate static URLs using + ``pyramid.url.static_url`` when passed a caller-package relative path due + to a refactoring done in 1.2a1. + +- The ``settings`` object emitted a deprecation warning any time + ``__getattr__`` was called upon it. However, there are legitimate + situations in which ``__getattr__`` is called on arbitrary objects + (e.g. ``hasattr``). Now, the ``settings`` object only emits the warning + upon successful lookup. + +Internal +-------- + +- Use ``config.with_package`` in view_config decorator rather than + manufacturing a new renderer helper (cleanup). + +1.2a2 (2011-08-27) +================== + +Bug Fixes +--------- + +- When a ``renderers=`` argument is not specified to the Configurator + constructor, eagerly register and commit the default renderer set. This + permits the overriding of the default renderers, which was broken in 1.2a1 + without a commit directly after Configurator construction. + +- Mako rendering exceptions had the wrong value for an error message. + +- An include could not set a root factory successfully because the + Configurator constructor unconditionally registered one that would be + treated as if it were "the word of the user". + +Features +-------- + +- A session factory can now be passed in using the dotted name syntax. + +1.2a1 (2011-08-24) +================== + +Features +-------- + +- The ``[pshell]`` section in an ini configuration file now treats a + ``setup`` key as a dotted name that points to a callable that is passed the + bootstrap environment. It can mutate the environment as necessary for + great justice. + +- A new configuration setting named ``pyramid.includes`` is now available. + It is described in the "Environment Variables and ``.ini`` Files Settings" + narrative documentation chapter. + +- Added a ``route_prefix`` argument to the + ``pyramid.config.Configurator.include`` method. This argument allows you + to compose URL dispatch applications together. See the section entitled + "Using a Route Prefix to Compose Applications" in the "URL Dispatch" + narrative documentation chapter. + +- Added a ``pyramid.security.NO_PERMISSION_REQUIRED`` constant for use in + ``permission=`` statements to view configuration. This constant has a + value of the string ``__no_permission_required__``. This string value was + previously referred to in documentation; now the documentation uses the + constant. + +- Added a decorator-based way to configure a response adapter: + ``pyramid.response.response_adapter``. This decorator has the same use as + ``pyramid.config.Configurator.add_response_adapter`` but it's declarative. + +- The ``pyramid.events.BeforeRender`` event now has an attribute named + ``rendering_val``. This can be used to introspect the value returned by a + view in a BeforeRender subscriber. + +- New configurator directive: ``pyramid.config.Configurator.add_tween``. + This directive adds a "tween". A "tween" is used to wrap the Pyramid + router's primary request handling function. This is a feature may be used + by Pyramid framework extensions, to provide, for example, view timing + support and as a convenient place to hang bookkeeping code. + + Tweens are further described in the narrative docs section in the Hooks + chapter, named "Registering Tweens". + +- New paster command ``paster ptweens``, which prints the current "tween" + configuration for an application. See the section entitled "Displaying + Tweens" in the Command-Line Pyramid chapter of the narrative documentation + for more info. + +- The Pyramid debug logger now uses the standard logging configuration + (usually set up by Paste as part of startup). This means that output from + e.g. ``debug_notfound``, ``debug_authorization``, etc. will go to the + normal logging channels. The logger name of the debug logger will be the + package name of the *caller* of the Configurator's constructor. + +- A new attribute is available on request objects: ``exc_info``. Its value + will be ``None`` until an exception is caught by the Pyramid router, after + which it will be the result of ``sys.exc_info()``. + +- ``pyramid.testing.DummyRequest`` now implements the + ``add_finished_callback`` and ``add_response_callback`` methods. + +- New methods of the ``pyramid.config.Configurator`` class: + ``set_authentication_policy`` and ``set_authorization_policy``. These are + meant to be consumed mostly by add-on authors. + +- New Configurator method: ``set_root_factory``. + +- Pyramid no longer eagerly commits some default configuration statements at + Configurator construction time, which permits values passed in as + constructor arguments (e.g. ``authentication_policy`` and + ``authorization_policy``) to override the same settings obtained via an + "include". + +- Better Mako rendering exceptions via + ``pyramid.mako_templating.MakoRenderingException`` + +- New request methods: ``current_route_url``, ``current_route_path``, and + ``static_path``. + +- New functions in ``pyramid.url``: ``current_route_path`` and + ``static_path``. + +- The ``pyramid.request.Request.static_url`` API (and its brethren + ``pyramid.request.Request.static_path``, ``pyramid.url.static_url``, and + ``pyramid.url.static_path``) now accept an asbolute filename as a "path" + argument. This will generate a URL to an asset as long as the filename is + in a directory which was previously registered as a static view. + Previously, trying to generate a URL to an asset using an absolute file + path would raise a ValueError. + +- The ``RemoteUserAuthenticationPolicy ``, ``AuthTktAuthenticationPolicy``, + and ``SessionAuthenticationPolicy`` constructors now accept an additional + keyword argument named ``debug``. By default, this keyword argument is + ``False``. When it is ``True``, debug information will be sent to the + Pyramid debug logger (usually on stderr) when the ``authenticated_userid`` + or ``effective_principals`` method is called on any of these policies. The + output produced can be useful when trying to diagnose + authentication-related problems. + +- New view predicate: ``match_param``. Example: a view added via + ``config.add_view(aview, match_param='action=edit')`` will be called only + when the ``request.matchdict`` has a value inside it named ``action`` with + a value of ``edit``. + +Internal +-------- + +- The Pyramid "exception view" machinery is now implemented as a "tween" + (``pyramid.tweens.excview_tween_factory``). + +- WSGIHTTPException (HTTPFound, HTTPNotFound, etc) now has a new API named + "prepare" which renders the body and content type when it is provided with + a WSGI environ. Required for debug toolbar. + +- Once ``__call__`` or ``prepare`` is called on a WSGIHTTPException, the body + will be set, and subsequent calls to ``__call__`` will always return the + same body. Delete the body attribute to rerender the exception body. + +- Previously the ``pyramid.events.BeforeRender`` event *wrapped* a dictionary + (it addressed it as its ``_system`` attribute). Now it *is* a dictionary + (it inherits from ``dict``), and it's the value that is passed to templates + as a top-level dictionary. + +- The ``route_url``, ``route_path``, ``resource_url``, ``static_url``, and + ``current_route_url`` functions in the ``pyramid.url`` package now delegate + to a method on the request they've been passed, instead of the other way + around. The pyramid.request.Request object now inherits from a mixin named + pyramid.url.URLMethodsMixin to make this possible, and all url/path + generation logic is embedded in this mixin. + +- Refactor ``pyramid.config`` into a package. + +- Removed the ``_set_security_policies`` method of the Configurator. + +- Moved the ``StaticURLInfo`` class from ``pyramid.static`` to + ``pyramid.config.views``. + +- Move the ``Settings`` class from ``pyramid.settings`` to + ``pyramid.config.settings``. + +- Move the ``OverrideProvider``, ``PackageOverrides``, ``DirectoryOverride``, + and ``FileOverride`` classes from ``pyramid.asset`` to + ``pyramid.config.assets``. + +Deprecations +------------ + +- All Pyramid-related deployment settings (e.g. ``debug_all``, + ``debug_notfound``) are now meant to be prefixed with the prefix + ``pyramid.``. For example: ``debug_all`` -> ``pyramid.debug_all``. The + old non-prefixed settings will continue to work indefinitely but supplying + them may eventually print a deprecation warning. All scaffolds and + tutorials have been changed to use prefixed settings. + +- The ``settings`` dictionary now raises a deprecation warning when you + attempt to access its values via ``__getattr__`` instead of + via ``__getitem__``. + +Backwards Incompatibilities +--------------------------- + +- If a string is passed as the ``debug_logger`` parameter to a Configurator, + that string is considered to be the name of a global Python logger rather + than a dotted name to an instance of a logger. + +- The ``pyramid.config.Configurator.include`` method now accepts only a + single ``callable`` argument (a sequence of callables used to be + permitted). If you are passing more than one ``callable`` to + ``pyramid.config.Configurator.include``, it will break. You now must now + instead make a separate call to the method for each callable. This change + was introduced to support the ``route_prefix`` feature of include. + +- It may be necessary to more strictly order configuration route and view + statements when using an "autocommitting" Configurator. In the past, it + was possible to add a view which named a route name before adding a route + with that name when you used an autocommitting configurator. For example:: + + config = Configurator(autocommit=True) + config.add_view('my.pkg.someview', route_name='foo') + config.add_route('foo', '/foo') + + The above will raise an exception when the view attempts to add itself. + Now you must add the route before adding the view:: + + config = Configurator(autocommit=True) + config.add_route('foo', '/foo') + config.add_view('my.pkg.someview', route_name='foo') + + This won't effect "normal" users, only people who have legacy BFG codebases + that used an autommitting configurator and possibly tests that use the + configurator API (the configurator returned by ``pyramid.testing.setUp`` is + an autocommitting configurator). The right way to get around this is to + use a non-autocommitting configurator (the default), which does not have + these directive ordering requirements. + +- The ``pyramid.config.Configurator.add_route`` directive no longer returns a + route object. This change was required to make route vs. view + configuration processing work properly. + +Documentation +------------- + +- Narrative and API documentation which used the ``route_url``, + ``route_path``, ``resource_url``, ``static_url``, and ``current_route_url`` + functions in the ``pyramid.url`` package have now been changed to use + eponymous methods of the request instead. + +- Added a section entitled "Using a Route Prefix to Compose Applications" to + the "URL Dispatch" narrative documentation chapter. + +- Added a new module to the API docs: ``pyramid.tweens``. + +- Added a "Registering Tweens" section to the "Hooks" narrative chapter. + +- Added a "Displaying Tweens" section to the "Command-Line Pyramid" narrative + chapter. + +- Added documentation for the ``pyramid.tweens`` and ``pyramid.includes`` + configuration settings to the "Environment Variables and ``.ini`` Files + Settings" chapter. + +- Added a Logging chapter to the narrative docs (based on the Pylons logging + docs, thanks Phil). + +- Added a Paste chapter to the narrative docs (moved content from the Project + chapter). + +- Added the ``pyramid.interfaces.IDict`` interface representing the methods + of a dictionary, for documentation purposes only (IMultiDict and + IBeforeRender inherit from it). + +- All tutorials now use - The ``route_url``, ``route_path``, + ``resource_url``, ``static_url``, and ``current_route_url`` methods of the + request rather than the function variants imported from ``pyramid.url``. + +- The ZODB wiki tutorial now uses the ``pyramid_zodbconn`` package rather + than the ``repoze.zodbconn`` package to provide ZODB integration. + +Dependency Changes +------------------ + +- Pyramid now relies on PasteScript >= 1.7.4. This version contains a + feature important for allowing flexible logging configuration. + +Scaffolds +---------- + +- All scaffolds now use the ``pyramid_tm`` package rather than the + ``repoze.tm2`` middleware to manage transaction management. + +- The ZODB scaffold now uses the ``pyramid_zodbconn`` package rather than the + ``repoze.zodbconn`` package to provide ZODB integration. + +- All scaffolds now use the ``pyramid_debugtoolbar`` package rather than the + ``WebError`` package to provide interactive debugging features. + +- Projects created via a scaffold no longer depend on the ``WebError`` + package at all; configuration in the ``production.ini`` file which used to + require its ``error_catcher`` middleware has been removed. Configuring + error catching / email sending is now the domain of the ``pyramid_exclog`` + package (see https://docs.pylonsproject.org/projects/pyramid_exclog/dev/). + +Bug Fixes +--------- + +- Fixed an issue with the default renderer not working at certain times. See + https://github.com/Pylons/pyramid/issues/249 + + 1.1 (2011-07-22) ================ diff --git a/pyramid/asset.py b/pyramid/asset.py index 730969a4a6..4bf0d7bf4b 100644 --- a/pyramid/asset.py +++ b/pyramid/asset.py @@ -1,11 +1,13 @@ import os import pkg_resources +from pyramid.compat import string_types + from pyramid.path import package_path from pyramid.path import package_name def resolve_asset_spec(spec, pname='__main__'): - if pname and not isinstance(pname, basestring): + if pname and not isinstance(pname, string_types): pname = pname.__name__ # as package if os.path.isabs(spec): return None, spec diff --git a/pyramid/authentication.py b/pyramid/authentication.py index e2014b9a18..e38c984b3e 100644 --- a/pyramid/authentication.py +++ b/pyramid/authentication.py @@ -1,12 +1,19 @@ from codecs import utf_8_decode from codecs import utf_8_encode from hashlib import md5 +import base64 import datetime import re import time as time_mod -import urllib -from zope.interface import implements +from zope.interface import implementer + +from pyramid.compat import long +from pyramid.compat import text_type +from pyramid.compat import binary_type +from pyramid.compat import url_unquote +from pyramid.compat import url_quote +from pyramid.compat import bytes_ from pyramid.interfaces import IAuthenticationPolicy from pyramid.interfaces import IDebugLogger @@ -105,6 +112,7 @@ def effective_principals(self, request): ) return effective_principals +@implementer(IAuthenticationPolicy) class RepozeWho1AuthenticationPolicy(CallbackAuthenticationPolicy): """ A :app:`Pyramid` :term:`authentication policy` which obtains data from the :mod:`repoze.who` 1.X WSGI 'API' (the @@ -129,7 +137,6 @@ class RepozeWho1AuthenticationPolicy(CallbackAuthenticationPolicy): Objects of this class implement the interface described by :class:`pyramid.interfaces.IAuthenticationPolicy`. """ - implements(IAuthenticationPolicy) def __init__(self, identifier_name='auth_tkt', callback=None): self.identifier_name = identifier_name @@ -193,6 +200,7 @@ def forget(self, request): identity = self._get_identity(request) return identifier.forget(request.environ, identity) +@implementer(IAuthenticationPolicy) class RemoteUserAuthenticationPolicy(CallbackAuthenticationPolicy): """ A :app:`Pyramid` :term:`authentication policy` which obtains data from the ``REMOTE_USER`` WSGI environment variable. @@ -222,7 +230,6 @@ class RemoteUserAuthenticationPolicy(CallbackAuthenticationPolicy): Objects of this class implement the interface described by :class:`pyramid.interfaces.IAuthenticationPolicy`. """ - implements(IAuthenticationPolicy) def __init__(self, environ_key='REMOTE_USER', callback=None, debug=False): self.environ_key = environ_key @@ -238,6 +245,7 @@ def remember(self, request, principal, **kw): def forget(self, request): return [] +@implementer(IAuthenticationPolicy) class AuthTktAuthenticationPolicy(CallbackAuthenticationPolicy): """ A :app:`Pyramid` :term:`authentication policy` which obtains data from an :class:`paste.auth.auth_tkt` cookie. @@ -340,7 +348,6 @@ class AuthTktAuthenticationPolicy(CallbackAuthenticationPolicy): Objects of this class implement the interface described by :class:`pyramid.interfaces.IAuthenticationPolicy`. """ - implements(IAuthenticationPolicy) def __init__(self, secret, callback=None, @@ -383,10 +390,10 @@ def forget(self, request): return self.cookie.forget(request) def b64encode(v): - return v.encode('base64').strip().replace('\n', '') + return base64.b64encode(bytes_(v)).strip().replace(b'\n', b'') def b64decode(v): - return v.decode('base64') + return base64.b64decode(bytes_(v)) # this class licensed under the MIT license (stolen from Paste) class AuthTicket(object): @@ -440,7 +447,7 @@ def digest(self): def cookie_value(self): v = '%s%08x%s!' % (self.digest(), int(self.time), - urllib.quote(self.userid)) + url_quote(self.userid)) if self.tokens: v += self.tokens + '!' v += self.user_data @@ -469,13 +476,13 @@ def parse_ticket(secret, ticket, ip): digest = ticket[:32] try: timestamp = int(ticket[32:40], 16) - except ValueError, e: + except ValueError as e: raise BadTicket('Timestamp is not a hex integer: %s' % e) try: userid, data = ticket[40:].split('!', 1) except ValueError: raise BadTicket('userid is not followed by !') - userid = urllib.unquote(userid) + userid = url_unquote(userid) if '!' in data: tokens, user_data = data.split('!', 1) else: # pragma: no cover (never generated) @@ -496,14 +503,14 @@ def parse_ticket(secret, ticket, ip): # this function licensed under the MIT license (stolen from Paste) def calculate_digest(ip, timestamp, secret, userid, tokens, user_data): - secret = maybe_encode(secret) - userid = maybe_encode(userid) - tokens = maybe_encode(tokens) - user_data = maybe_encode(user_data) + secret = bytes_(secret, 'utf-8') + userid = bytes_(userid, 'utf-8') + tokens = bytes_(tokens, 'utf-8') + user_data = bytes_(user_data, 'utf-8') digest0 = md5( - encode_ip_timestamp(ip, timestamp) + secret + userid + '\0' - + tokens + '\0' + user_data).hexdigest() - digest = md5(digest0 + secret).hexdigest() + encode_ip_timestamp(ip, timestamp) + secret + userid + b'\0' + + tokens + b'\0' + user_data).hexdigest() + digest = md5(bytes_(digest0) + secret).hexdigest() return digest # this function licensed under the MIT license (stolen from Paste) @@ -515,12 +522,7 @@ def encode_ip_timestamp(ip, timestamp): (t & 0xff00) >> 8, t & 0xff) ts_chars = ''.join(map(chr, ts)) - return ip_chars + ts_chars - -def maybe_encode(s, encoding='utf8'): - if isinstance(s, unicode): - s = s.encode(encoding) - return s + return bytes_(ip_chars + ts_chars) EXPIRE = object() @@ -546,8 +548,8 @@ class AuthTktCookieHelper(object): userid_type_encoders = { int: ('int', str), long: ('int', str), - unicode: ('b64unicode', lambda x: b64encode(utf_8_encode(x)[0])), - str: ('b64str', lambda x: b64encode(x)), + text_type: ('b64unicode', lambda x: b64encode(utf_8_encode(x)[0])), + binary_type: ('b64str', lambda x: b64encode(x)), } def __init__(self, secret, cookie_name='auth_tkt', secure=False, @@ -658,7 +660,7 @@ def identify(self, request): if reissue and not hasattr(request, '_authtkt_reissued'): if ( (now - timestamp) > self.reissue_time ): # work around https://github.com/Pylons/pyramid/issues#issue/108 - tokens = filter(None, tokens) + tokens = list(filter(None, tokens)) headers = self.remember(request, userid, max_age=self.max_age, tokens=tokens) def reissue_authtkt(request, response): @@ -725,6 +727,11 @@ def remember(self, request, userid, max_age=None, tokens=()): user_data = 'userid_type:%s' % encoding for token in tokens: + if isinstance(token, text_type): + try: + token.encode('ascii') + except UnicodeEncodeError: + raise ValueError("Invalid token %r" % (token,)) if not (isinstance(token, str) and VALID_TOKEN.match(token)): raise ValueError("Invalid token %r" % (token,)) @@ -743,6 +750,7 @@ def remember(self, request, userid, max_age=None, tokens=()): cookie_value = ticket.cookie_value() return self._get_cookies(environ, cookie_value, max_age) +@implementer(IAuthenticationPolicy) class SessionAuthenticationPolicy(CallbackAuthenticationPolicy): """ A :app:`Pyramid` authentication policy which gets its data from the configured :term:`session`. For this authentication policy to work, you @@ -772,7 +780,6 @@ class SessionAuthenticationPolicy(CallbackAuthenticationPolicy): or IRC channels when asking for support. """ - implements(IAuthenticationPolicy) def __init__(self, prefix='auth.', callback=None, debug=False): self.callback = callback diff --git a/pyramid/authorization.py b/pyramid/authorization.py index ac8f195f2d..b1ef10033a 100644 --- a/pyramid/authorization.py +++ b/pyramid/authorization.py @@ -1,4 +1,4 @@ -from zope.interface import implements +from zope.interface import implementer from pyramid.interfaces import IAuthorizationPolicy @@ -9,6 +9,7 @@ from pyramid.security import Deny from pyramid.security import Everyone +@implementer(IAuthorizationPolicy) class ACLAuthorizationPolicy(object): """ An :term:`authorization policy` which consults an :term:`ACL` object attached to a :term:`context` to determine authorization @@ -60,8 +61,6 @@ class ACLAuthorizationPolicy(object): :class:`pyramid.interfaces.IAuthorizationPolicy` interface. """ - implements(IAuthorizationPolicy) - def permits(self, context, principals, permission): """ Return an instance of :class:`pyramid.security.ACLAllowed` instance if the policy diff --git a/pyramid/chameleon_text.py b/pyramid/chameleon_text.py index 676985853d..872d3b920a 100644 --- a/pyramid/chameleon_text.py +++ b/pyramid/chameleon_text.py @@ -1,7 +1,9 @@ import sys from zope.deprecation import deprecated -from zope.interface import implements +from zope.interface import implementer + +from pyramid.compat import reraise try: from chameleon.zpt.template import PageTextTemplateFile @@ -12,7 +14,7 @@ # Chameleon doesn't work on non-CPython platforms class PageTextTemplateFile(object): def __init__(self, *arg, **kw): - raise ImportError, exc, tb + reraise(ImportError, exc, tb) from pyramid.interfaces import ITemplateRenderer @@ -23,8 +25,8 @@ def __init__(self, *arg, **kw): def renderer_factory(info): return renderers.template_renderer_factory(info, TextTemplateRenderer) +@implementer(ITemplateRenderer) class TextTemplateRenderer(object): - implements(ITemplateRenderer) def __init__(self, path, lookup): self.path = path self.lookup = lookup diff --git a/pyramid/chameleon_zpt.py b/pyramid/chameleon_zpt.py index ca96d93560..aa6f89e07c 100644 --- a/pyramid/chameleon_zpt.py +++ b/pyramid/chameleon_zpt.py @@ -1,7 +1,9 @@ import sys from zope.deprecation import deprecated -from zope.interface import implements +from zope.interface import implementer + +from pyramid.compat import reraise try: from chameleon.zpt.template import PageTemplateFile @@ -11,7 +13,7 @@ # Chameleon doesn't work on non-CPython platforms class PageTemplateFile(object): def __init__(self, *arg, **kw): - raise ImportError, exc, tb + reraise(ImportError, exc, tb) from pyramid.interfaces import ITemplateRenderer @@ -22,8 +24,8 @@ def __init__(self, *arg, **kw): def renderer_factory(info): return renderers.template_renderer_factory(info, ZPTTemplateRenderer) +@implementer(ITemplateRenderer) class ZPTTemplateRenderer(object): - implements(ITemplateRenderer) def __init__(self, path, lookup): self.path = path self.lookup = lookup diff --git a/pyramid/compat.py b/pyramid/compat.py index 7d723715e8..e686be27dc 100644 --- a/pyramid/compat.py +++ b/pyramid/compat.py @@ -1,3 +1,18 @@ +import sys +import types + +try: # pragma: no cover + import __pypy__ + PYPY = True +except: # pragma: no cover + __pypy__ = None + PYPY = False + +try: + import cPickle as pickle +except ImportError: # pragma: no cover + import pickle + try: import json except ImportError: # pragma: no cover @@ -6,3 +21,206 @@ except NotImplementedError: from django.utils import simplejson as json # GAE +# True if we are running on Python 3. +PY3 = sys.version_info[0] == 3 + +if PY3: # pragma: no cover + string_types = str, + integer_types = int, + class_types = type, + text_type = str + binary_type = bytes + long = int +else: + string_types = basestring, + integer_types = (int, long) + class_types = (type, types.ClassType) + text_type = unicode + binary_type = str + long = long + +def text_(s, encoding='latin-1', errors='strict'): + if isinstance(s, binary_type): + return s.decode(encoding, errors) + return s # pragma: no cover + +def bytes_(s, encoding='latin-1', errors='strict'): + if isinstance(s, text_type): + return s.encode(encoding, errors) + return s + +if PY3: # pragma: no cover + def ascii_native_(s): + if isinstance(s, text_type): + s = s.encode('ascii') + return str(s, 'ascii', 'strict') +else: + def ascii_native_(s): + if isinstance(s, text_type): + s = s.encode('ascii') + return str(s) + +if PY3: # pragma: no cover + def native_(s, encoding='latin-1', errors='strict'): + if isinstance(s, text_type): + return s + return str(s, encoding, errors) +else: + def native_(s, encoding='latin-1', errors='strict'): + if isinstance(s, text_type): + return s.encode(encoding, errors) + return str(s) + +if PY3: # pragma: no cover + from urllib import parse + urlparse = parse + from urllib.parse import quote as url_quote + from urllib.parse import quote_plus as url_quote_plus + from urllib.parse import unquote as url_unquote + from urllib.parse import urlencode as url_encode + from urllib.request import urlopen as url_open + url_unquote_text = url_unquote + url_unquote_native = url_unquote +else: + import urlparse + from urllib import quote as url_quote + from urllib import quote_plus as url_quote_plus + from urllib import unquote as url_unquote + from urllib import urlencode as url_encode + from urllib2 import urlopen as url_open + def url_unquote_text(v, encoding='utf-8', errors='replace'): + v = url_unquote(v) + return v.decode(encoding, errors) + def url_unquote_native(v, encoding='utf-8', errors='replace'): + return native_(url_unquote_text(v, encoding, errors)) + + +if PY3: # pragma: no cover + import builtins + exec_ = getattr(builtins, "exec") + + + def reraise(tp, value, tb=None): + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + + + print_ = getattr(builtins, "print") + del builtins + +else: # pragma: no cover + def exec_(code, globs=None, locs=None): + """Execute code in a namespace.""" + if globs is None: + frame = sys._getframe(1) + globs = frame.f_globals + if locs is None: + locs = frame.f_locals + del frame + elif locs is None: + locs = globs + exec("""exec code in globs, locs""") + + + exec_("""def reraise(tp, value, tb=None): + raise tp, value, tb +""") + + + def print_(*args, **kwargs): + """The new-style print function.""" + fp = kwargs.pop("file", sys.stdout) + if fp is None: + return + def write(data): + if not isinstance(data, basestring): + data = str(data) + fp.write(data) + want_unicode = False + sep = kwargs.pop("sep", None) + if sep is not None: + if isinstance(sep, unicode): + want_unicode = True + elif not isinstance(sep, str): + raise TypeError("sep must be None or a string") + end = kwargs.pop("end", None) + if end is not None: + if isinstance(end, unicode): + want_unicode = True + elif not isinstance(end, str): + raise TypeError("end must be None or a string") + if kwargs: + raise TypeError("invalid keyword arguments to print()") + if not want_unicode: + for arg in args: + if isinstance(arg, unicode): + want_unicode = True + break + if want_unicode: + newline = unicode("\n") + space = unicode(" ") + else: + newline = "\n" + space = " " + if sep is None: + sep = space + if end is None: + end = newline + for i, arg in enumerate(args): + if i: + write(sep) + write(arg) + write(end) + +if PY3: # pragma: no cover + def iteritems_(d): + return d.items() + def itervalues_(d): + return d.values() + def iterkeys_(d): + return d.keys() +else: + def iteritems_(d): + return d.iteritems() + def itervalues_(d): + return d.itervalues() + def iterkeys_(d): + return d.iterkeys() + + +if PY3: # pragma: no cover + def map_(*arg): + return list(map(*arg)) +else: + map_ = map + +if PY3: # pragma: no cover + def is_nonstr_iter(v): + if isinstance(v, str): + return False + return hasattr(v, '__iter__') +else: + def is_nonstr_iter(v): + return hasattr(v, '__iter__') + +if PY3: # pragma: no cover + im_func = '__func__' +else: + im_func = 'im_func' + +try: # pragma: no cover + import configparser +except ImportError: # pragma: no cover + import ConfigParser + configparser = ConfigParser + +try: + from Cookie import SimpleCookie +except ImportError: # pragma: no cover + from http.cookies import SimpleCookie + +if PY3: # pragma: no cover + from html import escape +else: + from cgi import escape diff --git a/pyramid/config/__init__.py b/pyramid/config/__init__.py index 9effeed171..8085bbc79c 100644 --- a/pyramid/config/__init__.py +++ b/pyramid/config/__init__.py @@ -13,6 +13,10 @@ from pyramid.asset import resolve_asset_spec from pyramid.authorization import ACLAuthorizationPolicy +from pyramid.compat import text_ +from pyramid.compat import reraise +from pyramid.compat import string_types +from pyramid.compat import PY3 from pyramid.events import ApplicationCreated from pyramid.exceptions import ConfigurationConflictError from pyramid.exceptions import ConfigurationError @@ -42,6 +46,8 @@ from pyramid.config.views import ViewsConfiguratorMixin from pyramid.config.zca import ZCAConfiguratorMixin +empty = text_('') + ConfigurationError = ConfigurationError # pyflakes class Configurator( @@ -284,7 +290,7 @@ def setup_registry(self, settings=None, root_factory=None, self._set_settings(settings) self._register_response_adapters() - if isinstance(debug_logger, basestring): + if isinstance(debug_logger, string_types): debug_logger = logging.getLogger(debug_logger) if debug_logger is None: @@ -402,7 +408,7 @@ def queryAdapterOrSelf(object, interface, default=None): if not hasattr(_registry, 'registerSelfAdapter'): def registerSelfAdapter(required=None, provided=None, - name=u'', info=u'', event=True): + name=empty, info=empty, event=True): return _registry.registerAdapter(lambda x: x, required=required, provided=provided, name=name, @@ -655,7 +661,10 @@ def __getattr__(self, name): c, action_wrap = c if action_wrap: c = action_method(c) - m = types.MethodType(c, self, self.__class__) + if PY3: # pragma: no cover + m = types.MethodType(c, self) + else: + m = types.MethodType(c, self, self.__class__) return m @classmethod @@ -710,7 +719,7 @@ def absolute_asset_spec(self, relative_spec): when generating an absolute asset specification. If the provided ``relative_spec`` argument is already absolute, or if the ``relative_spec`` is not a string, it is simply returned.""" - if not isinstance(relative_spec, basestring): + if not isinstance(relative_spec, string_types): return relative_spec return self._make_spec(relative_spec) @@ -908,7 +917,9 @@ def execute_actions(self, clear=True): except: t, v, tb = sys.exc_info() try: - raise ConfigurationExecutionError(t, v, info), None, tb + reraise(ConfigurationExecutionError, + ConfigurationExecutionError(t, v, info), + tb) finally: del t, v, tb finally: @@ -994,7 +1005,11 @@ def resolveConflicts(actions): # We need to sort the actions by the paths so that the shortest # path with a given prefix comes first: - dups.sort() + def allbutfunc(stupid): + # f me with a shovel, py3 cant cope with sorting when the + # callable function is in the list + return stupid[0:2] + stupid[3:] + dups.sort(key=allbutfunc) (basepath, i, callable, args, kw, baseinfo) = dups[0] output.append( (i, discriminator, callable, args, kw, basepath, baseinfo) diff --git a/pyramid/config/assets.py b/pyramid/config/assets.py index 1b52540723..08cc6dc38d 100644 --- a/pyramid/config/assets.py +++ b/pyramid/config/assets.py @@ -1,7 +1,7 @@ import pkg_resources import sys -from zope.interface import implements +from zope.interface import implementer from pyramid.interfaces import IPackageOverrides @@ -80,8 +80,8 @@ def resource_listdir(self, resource_name): return pkg_resources.DefaultProvider.resource_listdir( self, resource_name) +@implementer(IPackageOverrides) class PackageOverrides: - implements(IPackageOverrides) # pkg_resources arg in kw args below for testing def __init__(self, package, pkg_resources=pkg_resources): if hasattr(package, '__loader__') and not isinstance(package.__loader__, diff --git a/pyramid/config/settings.py b/pyramid/config/settings.py index 6e636bf583..565a6699c2 100644 --- a/pyramid/config/settings.py +++ b/pyramid/config/settings.py @@ -1,7 +1,7 @@ import os import warnings -from zope.interface import implements +from zope.interface import implementer from pyramid.interfaces import ISettings @@ -54,12 +54,12 @@ def get_settings(self): return self.registry.settings +@implementer(ISettings) class Settings(dict): """ Deployment settings. Update application settings (usually from PasteDeploy keywords) with framework-specific key/value pairs (e.g. find ``PYRAMID_DEBUG_AUTHORIZATION`` in os.environ and jam into keyword args).""" - implements(ISettings) # _environ_ is dep inj for testing def __init__(self, d=None, _environ_=os.environ, **kw): if d is None: diff --git a/pyramid/config/testing.py b/pyramid/config/testing.py index 0f709f6340..36729acdf3 100644 --- a/pyramid/config/testing.py +++ b/pyramid/config/testing.py @@ -6,7 +6,7 @@ from pyramid.interfaces import IRendererFactory from pyramid.renderers import RendererHelper -from pyramid.traversal import traversal_path +from pyramid.traversal import traversal_path_info from pyramid.config.util import action_method @@ -66,7 +66,7 @@ def __init__(self, context): def __call__(self, request): path = request.environ['PATH_INFO'] ob = resources[path] - traversed = traversal_path(path) + traversed = traversal_path_info(path) return {'context':ob, 'view_name':'','subpath':(), 'traversed':traversed, 'virtual_root':ob, 'virtual_root_path':(), 'root':ob} diff --git a/pyramid/config/tweens.py b/pyramid/config/tweens.py index 2704b89c13..3c7ee384f3 100644 --- a/pyramid/config/tweens.py +++ b/pyramid/config/tweens.py @@ -1,7 +1,10 @@ -from zope.interface import implements +from zope.interface import implementer from pyramid.interfaces import ITweens +from pyramid.compat import string_types +from pyramid.compat import is_nonstr_iter +from pyramid.compat import string_types from pyramid.exceptions import ConfigurationError from pyramid.tweens import excview_tween_factory from pyramid.tweens import MAIN, INGRESS, EXCVIEW @@ -96,7 +99,7 @@ def add_tween(self, tween_factory, under=None, over=None): @action_method def _add_tween(self, tween_factory, under=None, over=None, explicit=False): - if not isinstance(tween_factory, basestring): + if not isinstance(tween_factory, string_types): raise ConfigurationError( 'The "tween_factory" argument to add_tween must be a ' 'dotted name to a globally importable object, not %r' % @@ -110,7 +113,7 @@ def _add_tween(self, tween_factory, under=None, over=None, explicit=False): tween_factory = self.maybe_dotted(tween_factory) def is_string_or_iterable(v): - if isinstance(v, basestring): + if isinstance(v, string_types): return True if hasattr(v, '__iter__'): return True @@ -121,10 +124,10 @@ def is_string_or_iterable(v): raise ConfigurationError( '"%s" must be a string or iterable, not %s' % (t, p)) - if over is INGRESS or hasattr(over, '__iter__') and INGRESS in over: + if over is INGRESS or is_nonstr_iter(over) and INGRESS in over: raise ConfigurationError('%s cannot be over INGRESS' % name) - if under is MAIN or hasattr(under, '__iter__') and MAIN in under: + if under is MAIN or is_nonstr_iter(under) and MAIN in under: raise ConfigurationError('%s cannot be under MAIN' % name) registry = self.registry @@ -157,8 +160,8 @@ def __str__(self): msg = 'Implicit tween ordering cycle:' + '; '.join(L) return msg +@implementer(ITweens) class Tweens(object): - implements(ITweens) def __init__(self): self.explicit = [] self.names = [] @@ -176,12 +179,12 @@ def add_implicit(self, name, factory, under=None, over=None): if under is None and over is None: under = INGRESS if under is not None: - if not hasattr(under, '__iter__'): + if not is_nonstr_iter(under): under = (under,) self.order += [(u, name) for u in under] self.req_under.add(name) if over is not None: - if not hasattr(over, '__iter__'): + if not is_nonstr_iter(over): #hasattr(over, '__iter__'): over = (over,) self.order += [(name, o) for o in over] self.req_over.add(name) @@ -197,7 +200,7 @@ def implicit(self): order.append((a, b)) def add_node(node): - if not graph.has_key(node): + if not node in graph: roots.append(node) graph[node] = [0] # 0 = number of arcs coming into this node diff --git a/pyramid/config/util.py b/pyramid/config/util.py index 1e54213ac8..0336b103d9 100644 --- a/pyramid/config/util.py +++ b/pyramid/config/util.py @@ -1,9 +1,12 @@ import re import traceback +from pyramid.compat import string_types +from pyramid.compat import bytes_ +from pyramid.compat import is_nonstr_iter from pyramid.exceptions import ConfigurationError from pyramid.traversal import find_interface -from pyramid.traversal import traversal_path +from pyramid.traversal import traversal_path_info from hashlib import md5 @@ -93,10 +96,10 @@ def xhr_predicate(context, request): xhr_predicate.__text__ = "xhr = True" weights.append(1 << 1) predicates.append(xhr_predicate) - h.update('xhr:%r' % bool(xhr)) + h.update(bytes_('xhr:%r' % bool(xhr))) if request_method is not None: - if not hasattr(request_method, '__iter__'): + if not is_nonstr_iter(request_method): request_method = (request_method,) request_method = sorted(request_method) def request_method_predicate(context, request): @@ -106,20 +109,20 @@ def request_method_predicate(context, request): weights.append(1 << 2) predicates.append(request_method_predicate) for m in request_method: - h.update('request_method:%r' % m) + h.update(bytes_('request_method:%r' % m)) if path_info is not None: try: path_info_val = re.compile(path_info) - except re.error, why: - raise ConfigurationError(why[0]) + except re.error as why: + raise ConfigurationError(why.args[0]) def path_info_predicate(context, request): return path_info_val.match(request.path_info) is not None text = "path_info = %s" path_info_predicate.__text__ = text % path_info weights.append(1 << 3) predicates.append(path_info_predicate) - h.update('path_info:%r' % path_info) + h.update(bytes_('path_info:%r' % path_info)) if request_param is not None: request_param_val = None @@ -136,7 +139,8 @@ def request_param_predicate(context, request): request_param_predicate.__text__ = text weights.append(1 << 4) predicates.append(request_param_predicate) - h.update('request_param:%r=%r' % (request_param, request_param_val)) + h.update( + bytes_('request_param:%r=%r' % (request_param, request_param_val))) if header is not None: header_name = header @@ -145,8 +149,8 @@ def request_param_predicate(context, request): header_name, header_val = header.split(':', 1) try: header_val = re.compile(header_val) - except re.error, why: - raise ConfigurationError(why[0]) + except re.error as why: + raise ConfigurationError(why.args[0]) if header_val is None: text = "header %s" % header_name else: @@ -161,7 +165,7 @@ def header_predicate(context, request): header_predicate.__text__ = text weights.append(1 << 5) predicates.append(header_predicate) - h.update('header:%r=%r' % (header_name, header_val)) + h.update(bytes_('header:%r=%r' % (header_name, header_val))) if accept is not None: def accept_predicate(context, request): @@ -169,7 +173,7 @@ def accept_predicate(context, request): accept_predicate.__text__ = "accept = %s" % accept weights.append(1 << 6) predicates.append(accept_predicate) - h.update('accept:%r' % accept) + h.update(bytes_('accept:%r' % accept)) if containment is not None: def containment_predicate(context, request): @@ -177,7 +181,7 @@ def containment_predicate(context, request): containment_predicate.__text__ = "containment = %s" % containment weights.append(1 << 7) predicates.append(containment_predicate) - h.update('containment:%r' % hash(containment)) + h.update(bytes_('containment:%r' % hash(containment))) if request_type is not None: def request_type_predicate(context, request): @@ -186,22 +190,22 @@ def request_type_predicate(context, request): request_type_predicate.__text__ = text % request_type weights.append(1 << 8) predicates.append(request_type_predicate) - h.update('request_type:%r' % hash(request_type)) + h.update(bytes_('request_type:%r' % hash(request_type))) if match_param is not None: - if isinstance(match_param, basestring): + if isinstance(match_param, string_types): match_param, match_param_val = match_param.split('=', 1) match_param = {match_param: match_param_val} text = "match_param %s" % match_param def match_param_predicate(context, request): - for k, v in match_param.iteritems(): + for k, v in match_param.items(): if request.matchdict.get(k) != v: return False return True match_param_predicate.__text__ = text weights.append(1 << 9) predicates.append(match_param_predicate) - h.update('match_param:%r' % match_param) + h.update(bytes_('match_param:%r' % match_param)) if custom: for num, predicate in enumerate(custom): @@ -222,7 +226,7 @@ def match_param_predicate(context, request): # functions for custom predicates, so that the hash output # of predicate instances which are "logically the same" # may compare equal. - h.update('custom%s:%r' % (num, hash(predicate))) + h.update(bytes_('custom%s:%r' % (num, hash(predicate)))) weights.append(1 << 10) if traverse is not None: @@ -237,7 +241,7 @@ def traverse_predicate(context, request): return True m = context['match'] tvalue = tgenerate(m) - m['traverse'] = traversal_path(tvalue) + m['traverse'] = traversal_path_info(tvalue) return True # This isn't actually a predicate, it's just a infodict # modifier that injects ``traverse`` into the matchdict. As a @@ -254,7 +258,7 @@ def traverse_predicate(context, request): return order, predicates, phash def as_sorted_tuple(val): - if not hasattr(val, '__iter__'): + if not is_nonstr_iter(val): val = (val,) val = tuple(sorted(val)) return val diff --git a/pyramid/config/views.py b/pyramid/config/views.py index 3da41861da..179d4065cc 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -1,11 +1,9 @@ import inspect -from urlparse import urljoin -from urlparse import urlparse from zope.interface import Interface from zope.interface import classProvides from zope.interface import implementedBy -from zope.interface import implements +from zope.interface import implementer from zope.interface.interfaces import IInterface from pyramid.interfaces import IAuthenticationPolicy @@ -28,6 +26,9 @@ from pyramid.interfaces import PHASE1_CONFIG from pyramid import renderers +from pyramid.compat import string_types +from pyramid.compat import urlparse +from pyramid.compat import im_func from pyramid.exceptions import ConfigurationError from pyramid.exceptions import PredicateMismatch from pyramid.httpexceptions import HTTPForbidden @@ -43,6 +44,9 @@ from pyramid.config.util import as_sorted_tuple from pyramid.config.util import make_predicates +urljoin = urlparse.urljoin +url_parse = urlparse.urlparse + def wraps_view(wrapper): def inner(self, view): wrapper_view = wrapper(self, view) @@ -333,9 +337,9 @@ def decorated_view(self, view): return view return decorator(view) +@implementer(IViewMapper) class DefaultViewMapper(object): classProvides(IViewMapperFactory) - implements(IViewMapper) def __init__(self, **kw): self.attr = kw.get('attr') @@ -414,6 +418,7 @@ def _attr_view(context, request): return _attr_view def requestonly(view, attr=None): + ismethod = False if attr is None: attr = '__call__' if inspect.isroutine(view): @@ -423,6 +428,7 @@ def requestonly(view, attr=None): fn = view.__init__ except AttributeError: return False + ismethod = hasattr(fn, '__call__') else: try: fn = getattr(view, attr) @@ -436,7 +442,8 @@ def requestonly(view, attr=None): args = argspec[0] - if inspect.ismethod(fn): + if hasattr(fn, im_func) or ismethod: + # it's an instance method (or unbound method on py2) if not args: return False args = args[1:] @@ -456,8 +463,8 @@ def requestonly(view, attr=None): return False +@implementer(IMultiView) class MultiView(object): - implements(IMultiView) def __init__(self, name): self.name = name @@ -910,7 +917,7 @@ def view(context, request): if not IInterface.providedBy(r_context): r_context = implementedBy(r_context) - if isinstance(renderer, basestring): + if isinstance(renderer, string_types): renderer = renderers.RendererHelper( name=renderer, package=self.package, registry = self.registry) @@ -1057,7 +1064,7 @@ def regclosure(): 'view', context, name, request_type, IView, containment, request_param, request_method, route_name, attr, xhr, accept, header, path_info, match_param] - discriminator.extend(sorted(custom_predicates)) + discriminator.extend(sorted([hash(x) for x in custom_predicates])) discriminator = tuple(discriminator) self.action(discriminator, register) @@ -1147,7 +1154,7 @@ def _derive_view(self, view, permission=None, predicates=(), mapper=None, http_cache=None): view = self.maybe_dotted(view) mapper = self.maybe_dotted(mapper) - if isinstance(renderer, basestring): + if isinstance(renderer, string_types): renderer = renderers.RendererHelper( name=renderer, package=self.package, registry = self.registry) @@ -1206,7 +1213,7 @@ def set_forbidden_view(self, view=None, attr=None, renderer=None, The ``wrapper`` argument should be the name of another view which will wrap this view when rendered (see the ``add_view`` method's ``wrapper`` argument for a description).""" - if isinstance(renderer, basestring): + if isinstance(renderer, string_types): renderer = renderers.RendererHelper( name=renderer, package=self.package, registry = self.registry) @@ -1248,7 +1255,7 @@ def set_notfound_view(self, view=None, attr=None, renderer=None, which will wrap this view when rendered (see the ``add_view`` method's ``wrapper`` argument for a description). """ - if isinstance(renderer, basestring): + if isinstance(renderer, string_types): renderer = renderers.RendererHelper( name=renderer, package=self.package, registry=self.registry) @@ -1404,8 +1411,8 @@ def isexception(o): ) +@implementer(IStaticURLInfo) class StaticURLInfo(object): - implements(IStaticURLInfo) def _get_registrations(self, registry): try: @@ -1449,7 +1456,7 @@ def add(self, config, name, spec, **extra): # make sure it ends with a slash name = name + '/' - if urlparse(name)[0]: + if url_parse(name)[0]: # it's a URL # url, spec, route_name url = name diff --git a/pyramid/encode.py b/pyramid/encode.py index 826e6a6620..a259d1414f 100644 --- a/pyramid/encode.py +++ b/pyramid/encode.py @@ -1,58 +1,11 @@ -import re +from pyramid.compat import text_type +from pyramid.compat import binary_type +from pyramid.compat import is_nonstr_iter +from pyramid.compat import url_quote as _url_quote +from pyramid.compat import url_quote_plus as quote_plus # bw compat api (dnr) -always_safe = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ' - 'abcdefghijklmnopqrstuvwxyz' - '0123456789' '_.-') -_safemaps = {} -_must_quote = {} - -def url_quote(s, safe=''): - """quote('abc def') -> 'abc%20def' - - Each part of a URL, e.g. the path info, the query, etc., has a - different set of reserved characters that must be quoted. - - RFC 2396 Uniform Resource Identifiers (URI): Generic Syntax lists - the following reserved characters:: - - reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | - "$" | "," - - Each of these characters is reserved in some component of a URL, - but not necessarily in all of them. - - Unlike the default version of this function in the Python stdlib, by - default, the url_quote function is intended for quoting individual path - segments instead of an already composed path that might have ``/`` - characters in it. Thus, it *will* encode any ``/`` character it finds in a - string unless ``/`` is marked as 'safe'. It is also slightly faster than - the stdlib version. - """ - cachekey = (safe, always_safe) - try: - safe_map = _safemaps[cachekey] - if not _must_quote[cachekey].search(s): - return s - except KeyError: - safe += always_safe - _must_quote[cachekey] = re.compile(r'[^%s]' % safe) - safe_map = {} - for i in range(256): - c = chr(i) - if c in safe: - safe_map[c] = c - else: - safe_map[c] = '%%%02X' % i - _safemaps[cachekey] = safe_map - res = map(safe_map.__getitem__, s) - return ''.join(res) - -def quote_plus(s, safe=''): - """ Version of stdlib quote_plus which uses faster url_quote """ - if ' ' in s: - s = url_quote(s, safe + ' ') - return s.replace(' ', '+') - return url_quote(s, safe) +def url_quote(s, safe=''): # bw compat api + return _url_quote(s, safe=safe) def urlencode(query, doseq=True): """ @@ -88,21 +41,26 @@ def urlencode(query, doseq=True): prefix = '' for (k, v) in query: - if k.__class__ is unicode: - k = k.encode('utf-8') - k = quote_plus(str(k)) - if hasattr(v, '__iter__'): + k = _enc(k) + + if is_nonstr_iter(v): for x in v: - if x.__class__ is unicode: - x = x.encode('utf-8') - x = quote_plus(str(x)) + x = _enc(x) result += '%s%s=%s' % (prefix, k, x) prefix = '&' else: - if v.__class__ is unicode: - v = v.encode('utf-8') - v = quote_plus(str(v)) + v = _enc(v) result += '%s%s=%s' % (prefix, k, v) + prefix = '&' return result + +def _enc(val): + cls = val.__class__ + if cls is text_type: + val = val.encode('utf-8') + elif cls is not binary_type: + val = str(val).encode('utf-8') + return quote_plus(val) + diff --git a/pyramid/events.py b/pyramid/events.py index 9473d6330f..a495d9c292 100644 --- a/pyramid/events.py +++ b/pyramid/events.py @@ -1,6 +1,6 @@ import venusian -from zope.interface import implements +from zope.interface import implementer from pyramid.interfaces import IContextFound from pyramid.interfaces import INewRequest @@ -71,16 +71,17 @@ def __call__(self, wrapped): self.venusian.attach(wrapped, self.register, category='pyramid') return wrapped +@implementer(INewRequest) class NewRequest(object): """ An instance of this class is emitted as an :term:`event` whenever :app:`Pyramid` begins to process a new request. The even instance has an attribute, ``request``, which is a :term:`request` object. This event class implements the :class:`pyramid.interfaces.INewRequest` interface.""" - implements(INewRequest) def __init__(self, request): self.request = request +@implementer(INewResponse) class NewResponse(object): """ An instance of this class is emitted as an :term:`event` whenever any :app:`Pyramid` :term:`view` or :term:`exception @@ -112,11 +113,11 @@ class NewResponse(object): almost purely for symmetry with the :class:`pyramid.interfaces.INewRequest` event. """ - implements(INewResponse) def __init__(self, request, response): self.request = request self.response = response +@implementer(IContextFound) class ContextFound(object): """ An instance of this class is emitted as an :term:`event` after the :app:`Pyramid` :term:`router` finds a :term:`context` @@ -137,13 +138,13 @@ class ContextFound(object): As of :app:`Pyramid` 1.0, for backwards compatibility purposes, this event may also be imported as :class:`pyramid.events.AfterTraversal`. """ - implements(IContextFound) def __init__(self, request): self.request = request AfterTraversal = ContextFound # b/c as of 1.0 -class ApplicationCreated(object): +@implementer(IApplicationCreated) +class ApplicationCreated(object): """ An instance of this class is emitted as an :term:`event` when the :meth:`pyramid.config.Configurator.make_wsgi_app` is called. The instance has an attribute, ``app``, which is an @@ -157,13 +158,13 @@ class ApplicationCreated(object): :class:`pyramid.events.WSGIApplicationCreatedEvent`. This was the name of the event class before :app:`Pyramid` 1.0. """ - implements(IApplicationCreated) def __init__(self, app): self.app = app self.object = app WSGIApplicationCreatedEvent = ApplicationCreated # b/c (as of 1.0) +@implementer(IBeforeRender) class BeforeRender(dict): """ Subscribers to this event may introspect the and modify the set of @@ -201,7 +202,6 @@ def add_global(event): See also :class:`pyramid.interfaces.IBeforeRender`. """ - implements(IBeforeRender) def __init__(self, system, rendering_val=None): dict.__init__(self, system) self.rendering_val = rendering_val diff --git a/pyramid/exceptions.py b/pyramid/exceptions.py index cafdb93f06..ff598fe2d5 100644 --- a/pyramid/exceptions.py +++ b/pyramid/exceptions.py @@ -4,6 +4,8 @@ NotFound = HTTPNotFound # bw compat Forbidden = HTTPForbidden # bw compat +CR = '\n' + class PredicateMismatch(HTTPNotFound): """ Internal exception (not an API) raised by multiviews when no @@ -37,15 +39,14 @@ def __init__(self, conflicts): def __str__(self): r = ["Conflicting configuration actions"] - items = self._conflicts.items() - items.sort() + items = sorted(self._conflicts.items()) for discriminator, infos in items: r.append(" For: %s" % (discriminator, )) for info in infos: - for line in unicode(info).rstrip().split(u'\n'): - r.append(u" "+line) + for line in str(info).rstrip().split(CR): + r.append(" "+line) - return "\n".join(r) + return CR.join(r) class ConfigurationExecutionError(ConfigurationError): diff --git a/pyramid/fixers/fix_bfg_imports.py b/pyramid/fixers/fix_bfg_imports.py index 8fd32a797b..d9a4a6dfbd 100644 --- a/pyramid/fixers/fix_bfg_imports.py +++ b/pyramid/fixers/fix_bfg_imports.py @@ -199,6 +199,7 @@ def fix_zcml(path): newf.write(newt) newf.flush() newf.close() + text.close() for dir in dirs: if dir.startswith('.'): diff --git a/pyramid/httpexceptions.py b/pyramid/httpexceptions.py index 0887b0eec4..4dbca7021d 100644 --- a/pyramid/httpexceptions.py +++ b/pyramid/httpexceptions.py @@ -122,31 +122,36 @@ ``location``, which indicates the location to which to redirect. """ -import types from string import Template -from zope.interface import implements +from zope.interface import implementer from webob import html_escape as _html_escape from pyramid.interfaces import IExceptionResponse from pyramid.response import Response +from pyramid.compat import class_types +from pyramid.compat import text_type +from pyramid.compat import binary_type +from pyramid.compat import text_ def _no_escape(value): if value is None: return '' - if not isinstance(value, basestring): + if not isinstance(value, text_type): if hasattr(value, '__unicode__'): - value = unicode(value) + value = value.__unicode__() + if isinstance(value, binary_type): + value = text_(value, 'utf-8') else: - value = str(value) + value = text_type(value) return value class HTTPException(Exception): # bw compat """ Base class for all :term:`exception response` objects.""" +@implementer(IExceptionResponse) class WSGIHTTPException(Response, HTTPException): - implements(IExceptionResponse) ## You should set in subclasses: # code = 200 @@ -259,7 +264,7 @@ def prepare(self, environ): args[k.lower()] = escape(v) body = body_tmpl.substitute(args) page = page_template.substitute(status=self.status, body=body) - if isinstance(page, unicode): + if isinstance(page, text_type): page = page.encode(self.charset) self.app_iter = [page] self.body = page @@ -1016,8 +1021,8 @@ def default_exceptionresponse_view(context, request): status_map={} code = None -for name, value in globals().items(): - if (isinstance(value, (type, types.ClassType)) and +for name, value in list(globals().items()): + if (isinstance(value, class_types) and issubclass(value, HTTPException) and not name.startswith('_')): code = getattr(value, 'code', None) diff --git a/pyramid/i18n.py b/pyramid/i18n.py index f16aeb3789..889227130f 100644 --- a/pyramid/i18n.py +++ b/pyramid/i18n.py @@ -9,6 +9,7 @@ TranslationString = TranslationString # PyFlakes TranslationStringFactory = TranslationStringFactory # PyFlakes +from pyramid.compat import PY3 from pyramid.interfaces import ILocalizer from pyramid.interfaces import ITranslationDirectories from pyramid.interfaces import ILocaleNegotiator @@ -180,10 +181,10 @@ def make_localizer(current_locale_name, translation_directories): mopath = os.path.realpath(os.path.join(messages_dir, mofile)) if mofile.endswith('.mo') and os.path.isfile(mopath): - mofp = open(mopath, 'rb') - domain = mofile[:-3] - dtrans = Translations(mofp, domain) - translations.add(dtrans) + with open(mopath, 'rb') as mofp: + domain = mofile[:-3] + dtrans = Translations(mofp, domain) + translations.add(dtrans) return Localizer(locale_name=current_locale_name, translations=translations) @@ -231,7 +232,7 @@ def __init__(self, fileobj=None, domain=DEFAULT_DOMAIN): # this domain; see https://github.com/Pylons/pyramid/issues/235 self.plural = lambda n: int(n != 1) gettext.GNUTranslations.__init__(self, fp=fileobj) - self.files = filter(None, [getattr(fileobj, 'name', None)]) + self.files = list(filter(None, [getattr(fileobj, 'name', None)])) self.domain = domain self._domains = {} @@ -257,7 +258,8 @@ def load(cls, dirname=None, locales=None, domain=DEFAULT_DOMAIN): filename = gettext.find(domain, dirname, locales) if not filename: return gettext.NullTranslations() - return cls(fileobj=open(filename, 'rb'), domain=domain) + with open(filename, 'rb') as fp: + return cls(fileobj=fp, domain=domain) def __repr__(self): return '<%s: "%s">' % (type(self).__name__, @@ -327,7 +329,10 @@ def dugettext(self, domain, message): """Like ``ugettext()``, but look the message up in the specified domain. """ - return self._domains.get(domain, self).ugettext(message) + if PY3: # pragma: no cover + return self._domains.get(domain, self).gettext(message) + else: # pragma: no cover + return self._domains.get(domain, self).ugettext(message) def dngettext(self, domain, singular, plural, num): """Like ``ngettext()``, but look the message up in the specified @@ -345,5 +350,10 @@ def dungettext(self, domain, singular, plural, num): """Like ``ungettext()`` but look the message up in the specified domain. """ - return self._domains.get(domain, self).ungettext(singular, plural, num) + if PY3: # pragma: no cover + return self._domains.get(domain, self).ngettext( + singular, plural, num) + else: # pragma: no cover + return self._domains.get(domain, self).ungettext( + singular, plural, num) diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index d90f56b75d..f08bd5fbb6 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -1,6 +1,8 @@ from zope.interface import Attribute from zope.interface import Interface +from pyramid.compat import PY3 + # public API interfaces class IContextFound(Interface): @@ -46,7 +48,7 @@ class IApplicationCreated(Interface): versions before 1.0, this interface can also be imported as :class:`pyramid.interfaces.IWSGIApplicationCreatedEvent`. """ - app = Attribute(u"Created application") + app = Attribute("Created application") IWSGIApplicationCreatedEvent = IApplicationCreated # b /c @@ -296,25 +298,27 @@ def get(k, default=None): """ Return the value for key ``k`` from the renderer dictionary, or the default if no such value exists.""" - has_key = __contains__ - def items(): """ Return a list of [(k,v)] pairs from the dictionary """ - def iteritems(): - """ Return an iterator of (k,v) pairs from the dictionary """ - def keys(): """ Return a list of keys from the dictionary """ - def iterkeys(): - """ Return an iterator of keys from the dictionary """ - def values(): """ Return a list of values from the dictionary """ - def itervalues(): - """ Return an iterator of values from the dictionary """ + if not PY3: + + def iterkeys(): + """ Return an iterator of keys from the dictionary """ + + def iteritems(): + """ Return an iterator of (k,v) pairs from the dictionary """ + + def itervalues(): + """ Return an iterator of values from the dictionary """ + + has_key = __contains__ def pop(k, default=None): """ Pop the key k from the dictionary and return its value. If k diff --git a/pyramid/mako_templating.py b/pyramid/mako_templating.py index c79de72173..29be339f23 100644 --- a/pyramid/mako_templating.py +++ b/pyramid/mako_templating.py @@ -2,11 +2,12 @@ import sys import threading -from zope.interface import implements +from zope.interface import implementer from zope.interface import Interface from pyramid.asset import resolve_asset_spec from pyramid.asset import abspath_from_asset_spec +from pyramid.compat import is_nonstr_iter from pyramid.exceptions import ConfigurationError from pyramid.interfaces import ITemplateRenderer from pyramid.settings import asbool @@ -74,8 +75,8 @@ def renderer_factory(info): if directories is None: raise ConfigurationError( 'Mako template used without a ``mako.directories`` setting') - if not hasattr(directories, '__iter__'): - directories = filter(None, directories.splitlines()) + if not is_nonstr_iter(directories): + directories = list(filter(None, directories.splitlines())) directories = [ abspath_from_asset_spec(d) for d in directories ] if module_directory is not None: module_directory = abspath_from_asset_spec(module_directory) @@ -83,11 +84,12 @@ def renderer_factory(info): dotted = DottedNameResolver(info.package) error_handler = dotted.maybe_resolve(error_handler) if default_filters is not None: - if not hasattr(default_filters, '__iter__'): - default_filters = filter(None, default_filters.splitlines()) + if not is_nonstr_iter(default_filters): + default_filters = list(filter( + None, default_filters.splitlines())) if imports is not None: - if not hasattr(imports, '__iter__'): - imports = filter(None, imports.splitlines()) + if not is_nonstr_iter(imports): + imports = list(filter(None, imports.splitlines())) strict_undefined = asbool(strict_undefined) if preprocessor is not None: dotted = DottedNameResolver(info.package) @@ -120,8 +122,8 @@ def __repr__(self): __str__ = __repr__ +@implementer(ITemplateRenderer) class MakoLookupTemplateRenderer(object): - implements(ITemplateRenderer) def __init__(self, path, lookup): self.path = path self.lookup = lookup diff --git a/pyramid/paster.py b/pyramid/paster.py index bdf7df1090..c9c356a92f 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -1,19 +1,28 @@ -import ConfigParser import os import sys from code import interact import zope.deprecation -from paste.deploy import loadapp -from paste.script.command import Command +try: + from paste.deploy import loadapp +except ImportError: # pragma: no cover + def loadapp(*arg, **kw): + raise NotImplementedError + +try: + from paste.script.command import Command +except ImportError: # pragma: no cover + class Command: + pass from pyramid.interfaces import IMultiView from pyramid.interfaces import ITweens +from pyramid.compat import print_ +from pyramid.compat import configparser from pyramid.scripting import prepare from pyramid.util import DottedNameResolver - from pyramid.tweens import MAIN from pyramid.tweens import INGRESS @@ -138,7 +147,7 @@ class PShellCommand(PCommand): "option will override the 'setup' key in the " "[pshell] ini section.")) - ConfigParser = ConfigParser.ConfigParser # testing + ConfigParser = configparser.ConfigParser # testing loaded_objects = {} object_help = {} @@ -149,7 +158,7 @@ def pshell_file_config(self, filename): config.read(filename) try: items = config.items('pshell') - except ConfigParser.NoSectionError: + except configparser.NoSectionError: return resolver = DottedNameResolver(None) @@ -301,8 +310,8 @@ def _get_mapper(self, registry): return config.get_routes_mapper() def out(self, msg): # pragma: no cover - print msg - + print_(msg) + def command(self): from pyramid.interfaces import IRouteRequest from pyramid.interfaces import IViewClassifier @@ -360,8 +369,8 @@ class PViewsCommand(PCommand): parser = Command.standard_parser(simulate=True) def out(self, msg): # pragma: no cover - print msg - + print_(msg) + def _find_multi_routes(self, mapper, request): infos = [] path = request.environ['PATH_INFO'] @@ -380,7 +389,7 @@ def _find_view(self, url, registry): configuration` within the application registry; return the view. """ from zope.interface import providedBy - from zope.interface import implements + from zope.interface import implementer from pyramid.interfaces import IRequest from pyramid.interfaces import IRootFactory from pyramid.interfaces import IRouteRequest @@ -401,8 +410,8 @@ def _find_view(self, url, registry): adapters = registry.adapters request = None + @implementer(IMultiView) class RoutesMultiView(object): - implements(IMultiView) def __init__(self, infos, context_iface, root_factory, request): self.views = [] @@ -606,7 +615,7 @@ def _get_tweens(self, registry): return config.registry.queryUtility(ITweens) def out(self, msg): # pragma: no cover - print msg + print_(msg) def show_chain(self, chain): fmt = '%-10s %-65s' diff --git a/pyramid/registry.py b/pyramid/registry.py index 6b287e4f19..ac706595ef 100644 --- a/pyramid/registry.py +++ b/pyramid/registry.py @@ -1,7 +1,10 @@ -from zope.component.registry import Components +from zope.interface.registry import Components +from pyramid.compat import text_ from pyramid.interfaces import ISettings +empty = text_('') + class Registry(Components, dict): """ A registry object is an :term:`application registry`. It is used by the framework itself to perform mappings of URLs to view callables, as @@ -34,8 +37,8 @@ def registerSubscriptionAdapter(self, *arg, **kw): self.has_listeners = True return result - def registerSelfAdapter(self, required=None, provided=None, name=u'', - info=u'', event=True): + def registerSelfAdapter(self, required=None, provided=None, name=empty, + info=empty, event=True): # registerAdapter analogue which always returns the object itself # when required is matched return self.registerAdapter(lambda x: x, required=required, diff --git a/pyramid/renderers.py b/pyramid/renderers.py index 2efe0f1231..88cb869a50 100644 --- a/pyramid/renderers.py +++ b/pyramid/renderers.py @@ -2,7 +2,7 @@ import pkg_resources import threading -from zope.interface import implements +from zope.interface import implementer from zope.deprecation import deprecated from pyramid.interfaces import IChameleonLookup @@ -15,6 +15,9 @@ from pyramid.asset import asset_spec_from_abspath from pyramid.compat import json +from pyramid.compat import string_types +from pyramid.compat import text_type +from pyramid.compat import native_ from pyramid.decorator import reify from pyramid.events import BeforeRender from pyramid.path import caller_package @@ -145,7 +148,7 @@ def _render(value, system): def string_renderer_factory(info): def _render(value, system): - if not isinstance(value, basestring): + if not isinstance(value, string_types): value = str(value) request = system.get('request') if request is not None: @@ -225,8 +228,8 @@ def _render(value, system): # utility functions, not API +@implementer(IChameleonLookup) class ChameleonRendererLookup(object): - implements(IChameleonLookup) def __init__(self, impl, registry): self.impl = impl self.registry = registry @@ -348,8 +351,8 @@ def renderer_from_name(path, package=None): 'the next major release. To replace it, use the ' '``pyramid.renderers.get_renderer`` API instead. ') +@implementer(IRendererInfo) class RendererHelper(object): - implements(IRendererInfo) def __init__(self, name=None, package=None, registry=None): if name and '.' in name: rtype = os.path.splitext(name)[1] @@ -437,8 +440,8 @@ def _make_response(self, result, request): if result is None: result = '' - if isinstance(result, unicode): - response.unicode_body = result + if isinstance(result, text_type): + response.text = result else: response.body = result diff --git a/pyramid/request.py b/pyramid/request.py index 9704a42a4d..eae83da6f3 100644 --- a/pyramid/request.py +++ b/pyramid/request.py @@ -1,6 +1,6 @@ from zope.deprecation import deprecate from zope.deprecation.deprecation import deprecated -from zope.interface import implements +from zope.interface import implementer from zope.interface.interface import InterfaceClass from webob import BaseRequest @@ -11,6 +11,10 @@ from pyramid.interfaces import IResponseFactory from pyramid.compat import json +from pyramid.compat import iterkeys_, itervalues_, iteritems_ +from pyramid.compat import text_ +from pyramid.compat import bytes_ +from pyramid.compat import native_ from pyramid.exceptions import ConfigurationError from pyramid.decorator import reify from pyramid.response import Response @@ -64,15 +68,15 @@ def items(self): @deprecate(dictlike) def iteritems(self): - return self.environ.iteritems() + return iteritems_(self.environ) @deprecate(dictlike) def iterkeys(self): - return self.environ.iterkeys() + return iterkeys_(self.environ) @deprecate(dictlike) def itervalues(self): - return self.environ.itervalues() + return itervalues_(self.environ) @deprecate(dictlike) def keys(self): @@ -283,6 +287,7 @@ def _process_finished_callbacks(self): callback = callbacks.pop(0) callback(self) +@implementer(IRequest) class Request(BaseRequest, DeprecatedRequestMethodsMixin, URLMethodsMixin, CallbackMethodsMixin): """ @@ -305,7 +310,6 @@ class Request(BaseRequest, DeprecatedRequestMethodsMixin, URLMethodsMixin, release of this :app:`Pyramid` version. See http://pythonpaste.org/webob/ for further information. """ - implements(IRequest) exception = None exc_info = None matchdict = None @@ -360,7 +364,7 @@ def is_response(self, ob): @property def json_body(self): - return json.loads(self.body, encoding=self.charset) + return json.loads(text_(self.body, self.charset)) def route_request_iface(name, bases=()): # zope.interface treats the __name__ as the __doc__ and changes __name__ @@ -404,7 +408,8 @@ def call_app_with_subpath_as_path_info(request, app): new_script_name = '' # compute new_path_info - new_path_info = '/' + '/'.join([x.encode('utf-8') for x in subpath]) + new_path_info = '/' + '/'.join([native_(x.encode('utf-8'), 'latin-1') + for x in subpath]) if new_path_info != '/': # don't want a sole double-slash if path_info != '/': # if orig path_info is '/', we're already done @@ -422,7 +427,7 @@ def call_app_with_subpath_as_path_info(request, app): break el = workback.pop() if el: - tmp.insert(0, el.decode('utf-8')) + tmp.insert(0, text_(bytes_(el, 'latin-1'), 'utf-8')) # strip all trailing slashes from workback to avoid appending undue slashes # to end of script_name diff --git a/pyramid/resource.py b/pyramid/resource.py index 5e8f3c9686..986c75e370 100644 --- a/pyramid/resource.py +++ b/pyramid/resource.py @@ -1,5 +1,5 @@ """ Backwards compatibility shim module (forever). """ -from asset import * # b/w compat +from pyramid.asset import * # b/w compat resolve_resource_spec = resolve_asset_spec resource_spec_from_abspath = asset_spec_from_abspath abspath_from_resource_spec = abspath_from_asset_spec diff --git a/pyramid/response.py b/pyramid/response.py index db53de0c36..b0c9652964 100644 --- a/pyramid/response.py +++ b/pyramid/response.py @@ -1,12 +1,12 @@ import venusian from webob import Response as _Response -from zope.interface import implements +from zope.interface import implementer from pyramid.interfaces import IResponse +@implementer(IResponse) class Response(_Response): - implements(IResponse) - + pass class response_adapter(object): """ Decorator activated via a :term:`scan` which treats the function diff --git a/pyramid/router.py b/pyramid/router.py index 746cf88cf5..fb309eb03b 100644 --- a/pyramid/router.py +++ b/pyramid/router.py @@ -1,4 +1,4 @@ -from zope.interface import implements +from zope.interface import implementer from zope.interface import providedBy from pyramid.interfaces import IDebugLogger @@ -23,8 +23,8 @@ from pyramid.traversal import ResourceTreeTraverser from pyramid.tweens import excview_tween_factory +@implementer(IRouter) class Router(object): - implements(IRouter) debug_notfound = False debug_routematch = False diff --git a/pyramid/scaffolds/__init__.py b/pyramid/scaffolds/__init__.py index a49bbd9f66..673f22e21e 100644 --- a/pyramid/scaffolds/__init__.py +++ b/pyramid/scaffolds/__init__.py @@ -1,7 +1,18 @@ import os -from paste.script.templates import Template -from paste.util.template import paste_script_template_renderer +from pyramid.compat import print_ + +try: + from paste.script.templates import Template +except ImportError: # pragma: no cover + class Template: + pass + +try: + from paste.util.template import paste_script_template_renderer +except ImportError: # pragma: no cover + def paste_script_template_renderer(): + pass class PyramidTemplate(Template): def pre(self, command, output_dir, vars): @@ -18,7 +29,7 @@ def post(self, command, output_dir, vars): return Template.post(self, command, output_dir, vars) def out(self, msg): # pragma: no cover (replaceable testing hook) - print msg + print_(msg) class StarterProjectTemplate(PyramidTemplate): _template_dir = 'starter' diff --git a/pyramid/scaffolds/tests.py b/pyramid/scaffolds/tests.py index fe5d6957aa..7eb838e2f5 100644 --- a/pyramid/scaffolds/tests.py +++ b/pyramid/scaffolds/tests.py @@ -1,5 +1,4 @@ import sys -import httplib import os import pkg_resources import shutil @@ -8,6 +7,11 @@ import time import signal +try: + import httplib +except ImportError: # pragma: no cover + import http.client as httplib + class TemplateTest(object): def make_venv(self, directory): # pragma: no cover import virtualenv diff --git a/pyramid/security.py b/pyramid/security.py index 5aed7b5faf..a552b613ab 100644 --- a/pyramid/security.py +++ b/pyramid/security.py @@ -5,6 +5,7 @@ from pyramid.interfaces import ISecuredView from pyramid.interfaces import IViewClassifier +from pyramid.compat import map_ from pyramid.threadlocal import get_current_registry Everyone = 'system.Everyone' @@ -134,7 +135,7 @@ def view_execution_permitted(context, request, name=''): reg = request.registry except AttributeError: reg = get_current_registry() # b/c - provides = [IViewClassifier] + map(providedBy, (request, context)) + provides = [IViewClassifier] + map_(providedBy, (request, context)) view = reg.adapters.lookup(provides, ISecuredView, name=name) if view is None: return Allowed( diff --git a/pyramid/session.py b/pyramid/session.py index bfa80ff102..a59f9c6289 100644 --- a/pyramid/session.py +++ b/pyramid/session.py @@ -1,9 +1,3 @@ - -try: - import cPickle as pickle -except ImportError: # pragma: no cover - import pickle - from hashlib import sha1 import base64 import binascii @@ -11,8 +5,13 @@ import time import os -from zope.interface import implements +from zope.interface import implementer +from pyramid.compat import pickle +from pyramid.compat import PY3 +from pyramid.compat import text_ +from pyramid.compat import bytes_ +from pyramid.compat import native_ from pyramid.interfaces import ISession def manage_accessed(wrapped): @@ -88,9 +87,9 @@ def UnencryptedCookieSessionFactoryConfig( """ + @implementer(ISession) class UnencryptedCookieSessionFactory(dict): """ Dictionary-like session object """ - implements(ISession) # configuration parameters _cookie_name = cookie_name @@ -144,16 +143,18 @@ def invalidate(self): get = manage_accessed(dict.get) __getitem__ = manage_accessed(dict.__getitem__) items = manage_accessed(dict.items) - iteritems = manage_accessed(dict.iteritems) values = manage_accessed(dict.values) - itervalues = manage_accessed(dict.itervalues) keys = manage_accessed(dict.keys) - iterkeys = manage_accessed(dict.iterkeys) __contains__ = manage_accessed(dict.__contains__) - has_key = manage_accessed(dict.has_key) __len__ = manage_accessed(dict.__len__) __iter__ = manage_accessed(dict.__iter__) + if not PY3: + iteritems = manage_accessed(dict.iteritems) + itervalues = manage_accessed(dict.itervalues) + iterkeys = manage_accessed(dict.iterkeys) + has_key = manage_accessed(dict.has_key) + # modifying dictionary methods clear = manage_accessed(dict.clear) update = manage_accessed(dict.update) @@ -183,7 +184,7 @@ def peek_flash(self, queue=''): # CSRF API methods @manage_accessed def new_csrf_token(self): - token = os.urandom(20).encode('hex') + token = text_(binascii.hexlify(os.urandom(20))) self['_csrft_'] = token return token @@ -235,8 +236,8 @@ def signed_serialize(data, secret): response.set_cookie('signed_cookie', cookieval) """ pickled = pickle.dumps(data, pickle.HIGHEST_PROTOCOL) - sig = hmac.new(secret, pickled, sha1).hexdigest() - return sig + base64.standard_b64encode(pickled) + sig = hmac.new(bytes_(secret), pickled, sha1).hexdigest() + return sig + native_(base64.b64encode(pickled)) def signed_deserialize(serialized, secret, hmac=hmac): """ Deserialize the value returned from ``signed_serialize``. If @@ -254,12 +255,12 @@ def signed_deserialize(serialized, secret, hmac=hmac): # hmac parameterized only for unit tests try: input_sig, pickled = (serialized[:40], - base64.standard_b64decode(serialized[40:])) - except (binascii.Error, TypeError), e: + base64.b64decode(bytes_(serialized[40:]))) + except (binascii.Error, TypeError) as e: # Badly formed data can make base64 die raise ValueError('Badly formed base64 data: %s' % e) - sig = hmac.new(secret, pickled, sha1).hexdigest() + sig = hmac.new(bytes_(secret), pickled, sha1).hexdigest() if len(sig) != len(input_sig): raise ValueError('Wrong signature length') diff --git a/pyramid/settings.py b/pyramid/settings.py index 3c376c4a91..de91042eb9 100644 --- a/pyramid/settings.py +++ b/pyramid/settings.py @@ -1,6 +1,7 @@ from zope.deprecation import deprecated from pyramid.threadlocal import get_current_registry +from pyramid.compat import string_types def get_settings(): """ @@ -39,9 +40,9 @@ def asbool(s): return s.lower() in ('t', 'true', 'y', 'yes', 'on', '1') def aslist_cronly(value): - if isinstance(value, basestring): + if isinstance(value, string_types): value = filter(None, [x.strip() for x in value.splitlines()]) - return value + return list(value) def aslist(value): values = aslist_cronly(value) diff --git a/pyramid/static.py b/pyramid/static.py index 17a74a16a3..50a8b918b5 100644 --- a/pyramid/static.py +++ b/pyramid/static.py @@ -15,11 +15,14 @@ from repoze.lru import lru_cache from pyramid.asset import resolve_asset_spec +from pyramid.compat import text_ from pyramid.httpexceptions import HTTPNotFound from pyramid.httpexceptions import HTTPMovedPermanently from pyramid.path import caller_package from pyramid.response import Response -from pyramid.traversal import traversal_path +from pyramid.traversal import traversal_path_info + +slash = text_('/') def init_mimetypes(mimetypes): # this is a function so it can be unittested @@ -72,6 +75,8 @@ def next(self): raise StopIteration return data + __next__ = next # py3 + def close(self): self.file.close() @@ -107,8 +112,8 @@ class static_view(object): ``PATH_INFO`` when calling the underlying WSGI application which actually serves the static files. If it is ``True``, the static application will consider ``request.subpath`` as ``PATH_INFO`` input. If it is ``False``, - the static application will consider request.path_info as ``PATH_INFO`` - input. By default, this is ``False``. + the static application will consider request.environ[``PATH_INFO``] as + ``PATH_INFO`` input. By default, this is ``False``. .. note:: @@ -139,7 +144,7 @@ def __call__(self, context, request): if self.use_subpath: path_tuple = request.subpath else: - path_tuple = traversal_path(request.path_info) + path_tuple = traversal_path_info(request.environ['PATH_INFO']) path = _secure_path(path_tuple) @@ -194,6 +199,6 @@ def _secure_path(path_tuple): return None if any([_contains_slash(item) for item in path_tuple]): return None - encoded = u'/'.join(path_tuple) # will be unicode + encoded = slash.join(path_tuple) # will be unicode return encoded diff --git a/pyramid/testing.py b/pyramid/testing.py index 07f5238685..89cd39a1bf 100644 --- a/pyramid/testing.py +++ b/pyramid/testing.py @@ -3,7 +3,7 @@ from zope.deprecation import deprecated -from zope.interface import implements +from zope.interface import implementer from zope.interface import Interface from zope.interface import alsoProvides @@ -14,6 +14,9 @@ from pyramid.interfaces import IViewClassifier from pyramid.interfaces import ISession +from pyramid.compat import PY3 +from pyramid.compat import PYPY +from pyramid.compat import class_types from pyramid.config import Configurator from pyramid.decorator import reify from pyramid.httpexceptions import HTTPForbidden @@ -28,6 +31,13 @@ from pyramid.request import CallbackMethodsMixin from pyramid.url import URLMethodsMixin +try: + import zope.component + zope.component + have_zca = True +except ImportError: # pragma: no cover + have_zca = False + _marker = object() def registerDummySecurityPolicy(userid=None, groupids=(), permissive=True): @@ -590,8 +600,8 @@ def clone(self, __name__=_marker, __parent__=_marker, **kw): DummyModel = DummyResource # b/w compat (forever) +@implementer(ISession) class DummySession(dict): - implements(ISession) created = None new = True def changed(self): @@ -621,6 +631,7 @@ def new_csrf_token(self): def get_csrf_token(self): return self.get('_csrft_', None) +@implementer(IRequest) class DummyRequest(DeprecatedRequestMethodsMixin, URLMethodsMixin, CallbackMethodsMixin): """ A DummyRequest object (incompletely) imitates a :term:`request` object. @@ -649,7 +660,6 @@ class DummyRequest(DeprecatedRequestMethodsMixin, URLMethodsMixin, a Request, use the :class:`pyramid.request.Request` class itself rather than this class while writing tests. """ - implements(IRequest) method = 'GET' application_url = 'http://example.com' host = 'example.com:80' @@ -801,7 +811,7 @@ def setUp(registry=None, request=None, hook_zca=True, autocommit=True, # any existing renderer factory lookup system. config.add_renderer(name, renderer) config.commit() - hook_zca and config.hook_zca() + have_zca and hook_zca and config.hook_zca() config.begin(request=request) return config @@ -889,21 +899,25 @@ def __call__(self, *arg, **kw): return self.response def skip_on(*platforms): + skip = False + for platform in platforms: + if skip_on.os_name.startswith(platform): + skip = True + if platform == 'pypy' and PYPY: # pragma: no cover + skip = True + if platform == 'py3' and PY3: # pragma: no cover + skip = True def decorator(func): - def wrapper(*args, **kw): - for platform in platforms: - if skip_on.os_name.startswith(platform): - return - if platform == 'pypy' and skip_on.pypy: # pragma: no cover + if isinstance(func, class_types): + if skip: return None + else: return func + else: + def wrapper(*args, **kw): + if skip: return - return func(*args, **kw) - wrapper.__name__ = func.__name__ - wrapper.__doc__ = func.__doc__ - return wrapper + return func(*args, **kw) + wrapper.__name__ = func.__name__ + wrapper.__doc__ = func.__doc__ + return wrapper return decorator skip_on.os_name = os.name # for testing -try: # pragma: no cover - import __pypy__ - skip_on.pypy = True -except ImportError: - skip_on.pypy = False diff --git a/pyramid/tests/fixtures/helloworld.mak b/pyramid/tests/fixtures/helloworld.mak index efcf791e83..25283a50dc 100644 --- a/pyramid/tests/fixtures/helloworld.mak +++ b/pyramid/tests/fixtures/helloworld.mak @@ -1,3 +1,3 @@ ## -*- coding: utf-8 -*- -<% a, b = 'foo', u'föö' %> -Hello ${u'föö'} +<%!from pyramid.compat import text_%><% a, b = 'foo', text_('föö', 'utf-8') %> +Hello ${text_('föö', 'utf-8')} diff --git a/pyramid/tests/fixtures/helloworld.mako b/pyramid/tests/fixtures/helloworld.mako index efcf791e83..25283a50dc 100644 --- a/pyramid/tests/fixtures/helloworld.mako +++ b/pyramid/tests/fixtures/helloworld.mako @@ -1,3 +1,3 @@ ## -*- coding: utf-8 -*- -<% a, b = 'foo', u'föö' %> -Hello ${u'föö'} +<%!from pyramid.compat import text_%><% a, b = 'foo', text_('föö', 'utf-8') %> +Hello ${text_('föö', 'utf-8')} diff --git a/pyramid/tests/pkgs/exceptionviewapp/views.py b/pyramid/tests/pkgs/exceptionviewapp/views.py index 1432618cfe..33b97671ee 100644 --- a/pyramid/tests/pkgs/exceptionviewapp/views.py +++ b/pyramid/tests/pkgs/exceptionviewapp/views.py @@ -1,5 +1,5 @@ from webob import Response -from models import AnException +from .models import AnException def no(request): return Response('no') diff --git a/pyramid/tests/pkgs/fixtureapp/__init__.py b/pyramid/tests/pkgs/fixtureapp/__init__.py index c74747bfd4..27063aae29 100644 --- a/pyramid/tests/pkgs/fixtureapp/__init__.py +++ b/pyramid/tests/pkgs/fixtureapp/__init__.py @@ -5,7 +5,7 @@ def includeme(config): config.add_view('.views.erroneous_view', name='error.html') config.add_view('.views.fixture_view', name='dummyskin.html', request_type='.views.IDummy') - from models import fixture, IFixture + from .models import fixture, IFixture config.registry.registerUtility(fixture, IFixture) config.add_view('.views.fixture_view', name='another.html') diff --git a/pyramid/tests/pkgs/forbiddenapp/__init__.py b/pyramid/tests/pkgs/forbiddenapp/__init__.py index 7001b87f59..888dc93173 100644 --- a/pyramid/tests/pkgs/forbiddenapp/__init__.py +++ b/pyramid/tests/pkgs/forbiddenapp/__init__.py @@ -1,5 +1,6 @@ from webob import Response from pyramid.httpexceptions import HTTPForbidden +from pyramid.compat import bytes_ def x_view(request): # pragma: no cover return Response('this is private!') @@ -9,7 +10,7 @@ def forbidden_view(context, request): result = context.result message = msg + '\n' + str(result) resp = HTTPForbidden() - resp.body = message + resp.body = bytes_(message) return resp def includeme(config): diff --git a/pyramid/tests/pkgs/permbugapp/__init__.py b/pyramid/tests/pkgs/permbugapp/__init__.py index 10a244f3bd..330d983ab3 100644 --- a/pyramid/tests/pkgs/permbugapp/__init__.py +++ b/pyramid/tests/pkgs/permbugapp/__init__.py @@ -1,6 +1,6 @@ -from cgi import escape +from pyramid.compat import escape from pyramid.security import view_execution_permitted -from webob import Response +from pyramid.response import Response def x_view(request): # pragma: no cover return Response('this is private!') diff --git a/pyramid/tests/pkgs/wsgiapp2app/__init__.py b/pyramid/tests/pkgs/wsgiapp2app/__init__.py index 0880556efb..e2024198ec 100644 --- a/pyramid/tests/pkgs/wsgiapp2app/__init__.py +++ b/pyramid/tests/pkgs/wsgiapp2app/__init__.py @@ -8,7 +8,7 @@ def hello(environ, start_response): assert environ['SCRIPT_NAME'] == '/hello' response_headers = [('Content-Type', 'text/plain')] start_response('200 OK', response_headers) - return ['Hello!'] + return [b'Hello!'] def main(): from pyramid.config import Configurator diff --git a/pyramid/tests/test_authentication.py b/pyramid/tests/test_authentication.py index ff96ae4710..b612c57897 100644 --- a/pyramid/tests/test_authentication.py +++ b/pyramid/tests/test_authentication.py @@ -1,5 +1,7 @@ import unittest from pyramid import testing +from pyramid.compat import text_ +from pyramid.compat import bytes_ class TestCallbackAuthenticationPolicyDebugging(unittest.TestCase): def setUp(self): @@ -462,7 +464,7 @@ def _parseHeader(self, header): return cookie def _parseCookie(self, cookie): - from Cookie import SimpleCookie + from pyramid.compat import SimpleCookie cookies = SimpleCookie() cookies.load(cookie) return cookies.get('auth_tkt') @@ -562,14 +564,15 @@ def test_identify_good_cookie_unknown_useridtype(self): self.assertEqual(environ['AUTH_TYPE'],'cookie') def test_identify_good_cookie_b64str_useridtype(self): + from base64 import b64encode helper = self._makeOne('secret', include_ip=False) - helper.auth_tkt.userid = 'encoded'.encode('base64').strip() + helper.auth_tkt.userid = b64encode(b'encoded').strip() helper.auth_tkt.user_data = 'userid_type:b64str' request = self._makeRequest('ticket') result = helper.identify(request) self.assertEqual(len(result), 4) self.assertEqual(result['tokens'], ()) - self.assertEqual(result['userid'], 'encoded') + self.assertEqual(result['userid'], b'encoded') self.assertEqual(result['userdata'], 'userid_type:b64str') self.assertEqual(result['timestamp'], 0) environ = request.environ @@ -578,14 +581,15 @@ def test_identify_good_cookie_b64str_useridtype(self): self.assertEqual(environ['AUTH_TYPE'],'cookie') def test_identify_good_cookie_b64unicode_useridtype(self): + from base64 import b64encode helper = self._makeOne('secret', include_ip=False) - helper.auth_tkt.userid = '\xc3\xa9ncoded'.encode('base64').strip() + helper.auth_tkt.userid = b64encode(b'\xc3\xa9ncoded').strip() helper.auth_tkt.user_data = 'userid_type:b64unicode' request = self._makeRequest('ticket') result = helper.identify(request) self.assertEqual(len(result), 4) self.assertEqual(result['tokens'], ()) - self.assertEqual(result['userid'], unicode('\xc3\xa9ncoded', 'utf-8')) + self.assertEqual(result['userid'], text_(b'\xc3\xa9ncoded', 'utf-8')) self.assertEqual(result['userdata'], 'userid_type:b64unicode') self.assertEqual(result['timestamp'], 0) environ = request.environ @@ -822,14 +826,16 @@ def test_remember_domain_has_port(self): self.assertTrue(result[1][1].endswith('; Path=/; Domain=example.com')) self.assertTrue(result[1][1].startswith('auth_tkt=')) - def test_remember_string_userid(self): + def test_remember_binary_userid(self): + import base64 helper = self._makeOne('secret') request = self._makeRequest() - result = helper.remember(request, 'userid') + result = helper.remember(request, b'userid') values = self._parseHeaders(result) self.assertEqual(len(result), 3) val = self._cookieValue(values[0]) - self.assertEqual(val['userid'], 'userid'.encode('base64').strip()) + self.assertEqual(val['userid'], + bytes_(base64.b64encode(b'userid').strip())) self.assertEqual(val['user_data'], 'userid_type:b64str') def test_remember_int_userid(self): @@ -843,6 +849,7 @@ def test_remember_int_userid(self): self.assertEqual(val['user_data'], 'userid_type:int') def test_remember_long_userid(self): + from pyramid.compat import long helper = self._makeOne('secret') request = self._makeRequest() result = helper.remember(request, long(1)) @@ -853,15 +860,16 @@ def test_remember_long_userid(self): self.assertEqual(val['user_data'], 'userid_type:int') def test_remember_unicode_userid(self): + import base64 helper = self._makeOne('secret') request = self._makeRequest() - userid = unicode('\xc2\xa9', 'utf-8') + userid = text_(b'\xc2\xa9', 'utf-8') result = helper.remember(request, userid) values = self._parseHeaders(result) self.assertEqual(len(result), 3) val = self._cookieValue(values[0]) self.assertEqual(val['userid'], - userid.encode('utf-8').encode('base64').strip()) + base64.b64encode(userid.encode('utf-8'))) self.assertEqual(val['user_data'], 'userid_type:b64unicode') def test_remember_insane_userid(self): @@ -899,11 +907,12 @@ def test_remember_tokens(self): self.assertEqual(result[2][0], 'Set-Cookie') self.assertTrue("'tokens': ('foo', 'bar')" in result[2][1]) - def test_remember_non_string_token(self): + def test_remember_nonascii_token(self): helper = self._makeOne('secret') request = self._makeRequest() + la = text_(b'La Pe\xc3\xb1a', 'utf-8') self.assertRaises(ValueError, helper.remember, request, 'other', - tokens=(u'foo',)) + tokens=(la,)) def test_remember_invalid_token_format(self): helper = self._makeOne('secret') @@ -1086,15 +1095,6 @@ def test_forget_no_identity(self): self.assertEqual(request.session.get('userid'), None) self.assertEqual(result, []) -class Test_maybe_encode(unittest.TestCase): - def _callFUT(self, s, encoding='utf-8'): - from pyramid.authentication import maybe_encode - return maybe_encode(s, encoding) - - def test_unicode(self): - result = self._callFUT(u'abc') - self.assertEqual(result, 'abc') - class DummyContext: pass diff --git a/pyramid/tests/test_chameleon_text.py b/pyramid/tests/test_chameleon_text.py index 213f25f516..8d23c8753c 100644 --- a/pyramid/tests/test_chameleon_text.py +++ b/pyramid/tests/test_chameleon_text.py @@ -1,5 +1,6 @@ import unittest +from pyramid.compat import binary_type from pyramid.testing import skip_on from pyramid import testing @@ -98,8 +99,8 @@ def test_call(self): lookup = DummyLookup() instance = self._makeOne(minimal, lookup) result = instance({}, {}) - self.assertTrue(isinstance(result, str)) - self.assertEqual(result, 'Hello.\n') + self.assertTrue(isinstance(result, binary_type)) + self.assertEqual(result, b'Hello.\n') @skip_on('java') def test_call_with_nondict_value(self): @@ -114,8 +115,8 @@ def test_call_nonminimal(self): lookup = DummyLookup() instance = self._makeOne(nonminimal, lookup) result = instance({'name':'Chris'}, {}) - self.assertTrue(isinstance(result, str)) - self.assertEqual(result, 'Hello, Chris!\n') + self.assertTrue(isinstance(result, binary_type)) + self.assertEqual(result, b'Hello, Chris!\n') @skip_on('java') def test_implementation(self): @@ -123,8 +124,8 @@ def test_implementation(self): lookup = DummyLookup() instance = self._makeOne(minimal, lookup) result = instance.implementation()() - self.assertTrue(isinstance(result, str)) - self.assertEqual(result, 'Hello.\n') + self.assertTrue(isinstance(result, binary_type)) + self.assertEqual(result, b'Hello.\n') class RenderTemplateTests(Base, unittest.TestCase): def _callFUT(self, name, **kw): @@ -135,8 +136,8 @@ def _callFUT(self, name, **kw): def test_it(self): minimal = self._getTemplatePath('minimal.txt') result = self._callFUT(minimal) - self.assertTrue(isinstance(result, str)) - self.assertEqual(result, 'Hello.\n') + self.assertTrue(isinstance(result, binary_type)) + self.assertEqual(result, b'Hello.\n') class RenderTemplateToResponseTests(Base, unittest.TestCase): def _callFUT(self, name, **kw): @@ -149,7 +150,7 @@ def test_minimal(self): result = self._callFUT(minimal) from webob import Response self.assertTrue(isinstance(result, Response)) - self.assertEqual(result.app_iter, ['Hello.\n']) + self.assertEqual(result.app_iter, [b'Hello.\n']) self.assertEqual(result.status, '200 OK') self.assertEqual(len(result.headerlist), 2) diff --git a/pyramid/tests/test_chameleon_zpt.py b/pyramid/tests/test_chameleon_zpt.py index 84eaedcf49..1a8e6767e5 100644 --- a/pyramid/tests/test_chameleon_zpt.py +++ b/pyramid/tests/test_chameleon_zpt.py @@ -2,6 +2,7 @@ from pyramid.testing import skip_on from pyramid import testing +from pyramid.compat import text_type class Base(object): def setUp(self): @@ -51,7 +52,7 @@ def test_call(self): lookup = DummyLookup() instance = self._makeOne(minimal, lookup) result = instance({}, {}) - self.assertTrue(isinstance(result, unicode)) + self.assertTrue(isinstance(result, text_type)) self.assertEqual(result.rstrip('\n'), '
\n
') @@ -126,7 +127,7 @@ def test_implementation(self): lookup = DummyLookup() instance = self._makeOne(minimal, lookup) result = instance.implementation()() - self.assertTrue(isinstance(result, unicode)) + self.assertTrue(isinstance(result, text_type)) self.assertEqual(result.rstrip('\n'), '
\n
') @@ -140,7 +141,7 @@ def _callFUT(self, name, **kw): def test_it(self): minimal = self._getTemplatePath('minimal.pt') result = self._callFUT(minimal) - self.assertTrue(isinstance(result, unicode)) + self.assertTrue(isinstance(result, text_type)) self.assertEqual(result.rstrip('\n'), '
\n
') @@ -155,8 +156,8 @@ def test_it(self): result = self._callFUT(minimal) from webob import Response self.assertTrue(isinstance(result, Response)) - self.assertEqual(result.app_iter[0].rstrip('\n'), - '
\n
') + self.assertEqual(result.app_iter[0].rstrip(b'\n'), + b'
\n
') self.assertEqual(result.status, '200 OK') self.assertEqual(len(result.headerlist), 2) diff --git a/pyramid/tests/test_config/__init__.py b/pyramid/tests/test_config/__init__.py index 2f9f516ae2..5b40a8c094 100644 --- a/pyramid/tests/test_config/__init__.py +++ b/pyramid/tests/test_config/__init__.py @@ -1,6 +1,6 @@ # package -from zope.interface import implements +from zope.interface import implementer from zope.interface import Interface class IFactory(Interface): @@ -23,8 +23,8 @@ def dummy_include2(config): class DummyContext: pass +@implementer(IFactory) class DummyFactory(object): - implements(IFactory) def __call__(self): """ """ diff --git a/pyramid/tests/test_config/test_adapters.py b/pyramid/tests/test_config/test_adapters.py index 29c099e0e6..84b7119cf3 100644 --- a/pyramid/tests/test_config/test_adapters.py +++ b/pyramid/tests/test_config/test_adapters.py @@ -1,5 +1,6 @@ import unittest +from pyramid.compat import PY3 from pyramid.tests.test_config import IDummy class AdaptersConfiguratorMixinTests(unittest.TestCase): @@ -9,12 +10,13 @@ def _makeOne(self, *arg, **kw): return config def test_add_subscriber_defaults(self): - from zope.interface import implements + from zope.interface import implementer from zope.interface import Interface class IEvent(Interface): pass + @implementer(IEvent) class Event: - implements(IEvent) + pass L = [] def subscriber(event): L.append(event) @@ -28,12 +30,13 @@ def subscriber(event): self.assertEqual(len(L), 2) def test_add_subscriber_iface_specified(self): - from zope.interface import implements + from zope.interface import implementer from zope.interface import Interface class IEvent(Interface): pass + @implementer(IEvent) class Event: - implements(IEvent) + pass L = [] def subscriber(event): L.append(event) @@ -59,13 +62,13 @@ def test_add_subscriber_dottednames(self): self.assertEqual(handler.required, (INewRequest,)) def test_add_object_event_subscriber(self): - from zope.interface import implements + from zope.interface import implementer from zope.interface import Interface class IEvent(Interface): pass + @implementer(IEvent) class Event: object = 'foo' - implements(IEvent) event = Event() L = [] def subscriber(object, event): @@ -101,8 +104,11 @@ class Adapter(object): def test_add_response_adapter_dottednames(self): from pyramid.interfaces import IResponse config = self._makeOne(autocommit=True) - config.add_response_adapter('pyramid.response.Response', - 'types.StringType') + if PY3: # pragma: no cover + str_name = 'builtins.str' + else: + str_name = '__builtin__.str' + config.add_response_adapter('pyramid.response.Response', str_name) result = config.registry.queryAdapter('foo', IResponse) - self.assertTrue(result.body, 'foo') + self.assertTrue(result.body, b'foo') diff --git a/pyramid/tests/test_config/test_assets.py b/pyramid/tests/test_config/test_assets.py index 322c854f54..627eefba7e 100644 --- a/pyramid/tests/test_config/test_assets.py +++ b/pyramid/tests/test_config/test_assets.py @@ -163,9 +163,9 @@ def test_get_resource_stream_no_overrides(self): import pyramid.tests.test_config provider = self._makeOne(pyramid.tests.test_config) here = os.path.dirname(os.path.abspath(__file__)) - expected = open(os.path.join(here, resource_name)).read() - result = provider.get_resource_stream(None, resource_name) - self.assertEqual(result.read().replace('\r', ''), expected) + expected = read_(os.path.join(here, resource_name)) + with provider.get_resource_stream(None, resource_name) as result: + self.assertEqual(result.read().replace(b'\r', b''), expected) def test_get_resource_string_no_overrides(self): import os @@ -173,9 +173,9 @@ def test_get_resource_string_no_overrides(self): import pyramid.tests.test_config provider = self._makeOne(pyramid.tests.test_config) here = os.path.dirname(os.path.abspath(__file__)) - expected = open(os.path.join(here, resource_name)).read() + expected = read_(os.path.join(here, resource_name)) result = provider.get_resource_string(None, resource_name) - self.assertEqual(result.replace('\r', ''), expected) + self.assertEqual(result.replace(b'\r', b''), expected) def test_has_resource_no_overrides(self): resource_name = 'test_assets.py' @@ -221,9 +221,9 @@ def test_get_resource_stream_override_returns_None(self): import pyramid.tests.test_config provider = self._makeOne(pyramid.tests.test_config) here = os.path.dirname(os.path.abspath(__file__)) - expected = open(os.path.join(here, resource_name)).read() - result = provider.get_resource_stream(None, resource_name) - self.assertEqual(result.read(), expected) + expected = read_(os.path.join(here, resource_name)) + with provider.get_resource_stream(None, resource_name) as result: + self.assertEqual(result.read(), expected) def test_get_resource_string_override_returns_None(self): overrides = DummyOverrides(None) @@ -233,7 +233,7 @@ def test_get_resource_string_override_returns_None(self): import pyramid.tests.test_config provider = self._makeOne(pyramid.tests.test_config) here = os.path.dirname(os.path.abspath(__file__)) - expected = open(os.path.join(here, resource_name)).read() + expected = read_(os.path.join(here, resource_name)) result = provider.get_resource_string(None, resource_name) self.assertEqual(result, expected) @@ -273,12 +273,13 @@ def test_get_resource_filename_override_returns_value(self): self.assertEqual(result, 'value') def test_get_resource_stream_override_returns_value(self): - overrides = DummyOverrides('value') + from io import BytesIO + overrides = DummyOverrides(BytesIO(b'value')) import pyramid.tests.test_config self._registerOverrides(overrides) provider = self._makeOne(pyramid.tests.test_config) - result = provider.get_resource_stream(None, 'test_assets.py') - self.assertEqual(result, 'value') + with provider.get_resource_stream(None, 'test_assets.py') as stream: + self.assertEqual(stream.getvalue(), b'value') def test_get_resource_string_override_returns_value(self): overrides = DummyOverrides('value') @@ -419,9 +420,10 @@ def test_get_stream(self): po = self._makeOne(package) po.overrides= overrides here = os.path.dirname(os.path.abspath(__file__)) - expected = open(os.path.join(here, 'test_assets.py')).read() - self.assertEqual(po.get_stream('whatever').read().replace('\r', ''), - expected) + expected = read_(os.path.join(here, 'test_assets.py')) + with po.get_stream('whatever') as stream: + self.assertEqual(stream.read().replace(b'\r', b''), + expected) def test_get_stream_file_doesnt_exist(self): overrides = [ DummyOverride(None), DummyOverride( @@ -439,8 +441,9 @@ def test_get_string(self): po = self._makeOne(package) po.overrides= overrides here = os.path.dirname(os.path.abspath(__file__)) - expected = open(os.path.join(here, 'test_assets.py')).read() - self.assertEqual(po.get_string('whatever').replace('\r', ''), expected) + expected = read_(os.path.join(here, 'test_assets.py')) + self.assertEqual(po.get_string('whatever').replace(b'\r', b''), + expected) def test_get_string_file_doesnt_exist(self): overrides = [ DummyOverride(None), DummyOverride( @@ -581,9 +584,14 @@ def __init__(self, name): class DummyUnderOverride: def __call__(self, package, path, override_package, override_prefix, - _info=u''): + _info=''): self.package = package self.path = path self.override_package = override_package self.override_prefix = override_prefix +def read_(src): + with open(src, 'rb') as f: + contents = f.read() + return contents + diff --git a/pyramid/tests/test_config/test_i18n.py b/pyramid/tests/test_config/test_i18n.py index 03b410445c..25cb88cc37 100644 --- a/pyramid/tests/test_config/test_i18n.py +++ b/pyramid/tests/test_config/test_i18n.py @@ -92,7 +92,7 @@ def test_add_translation_dirs_registers_chameleon_translate(self): try: config.add_translation_dirs('pyramid.tests.pkgs.localeapp:locale') translate = config.registry.getUtility(IChameleonTranslate) - self.assertEqual(translate('Approve'), u'Approve') + self.assertEqual(translate('Approve'), 'Approve') finally: manager.pop() diff --git a/pyramid/tests/test_config/test_init.py b/pyramid/tests/test_config/test_init.py index 1dccd00e10..ca15082950 100644 --- a/pyramid/tests/test_config/test_init.py +++ b/pyramid/tests/test_config/test_init.py @@ -2,6 +2,10 @@ import os +from pyramid.compat import PYPY +from pyramid.compat import im_func +from pyramid.testing import skip_on + from pyramid.tests.test_config import dummy_tween_factory from pyramid.tests.test_config import dummy_include from pyramid.tests.test_config import dummy_extend @@ -9,14 +13,8 @@ from pyramid.tests.test_config import IDummy from pyramid.tests.test_config import DummyContext -try: - import __pypy__ -except: - __pypy__ = None - from pyramid.exceptions import ConfigurationExecutionError from pyramid.exceptions import ConfigurationConflictError -from pyramid.exceptions import ConfigurationError class ConfiguratorTests(unittest.TestCase): def _makeOne(self, *arg, **kw): @@ -70,7 +68,7 @@ def test_ctor_no_registry(self): config.commit() self.assertTrue(config.registry.getUtility(IRendererFactory, 'json')) self.assertTrue(config.registry.getUtility(IRendererFactory, 'string')) - if not __pypy__: + if not PYPY: self.assertTrue(config.registry.getUtility(IRendererFactory, '.pt')) self.assertTrue(config.registry.getUtility(IRendererFactory,'.txt')) self.assertTrue(config.registry.getUtility(IRendererFactory, '.mak')) @@ -309,10 +307,12 @@ def test__fix_registry_notify(self): def test__fix_registry_queryAdapterOrSelf(self): from zope.interface import Interface + from zope.interface import implementer class IFoo(Interface): pass + @implementer(IFoo) class Foo(object): - implements(IFoo) + pass class Bar(object): pass adaptation = () @@ -333,7 +333,7 @@ def test__fix_registry_registerSelfAdapter(self): args, kw = reg.adapters[0] self.assertEqual(args[0]('abc'), 'abc') self.assertEqual(kw, - {'info': u'', 'provided': 'provided', + {'info': '', 'provided': 'provided', 'required': 'required', 'name': 'abc', 'event': True}) def test_setup_registry_calls_fix_registry(self): @@ -605,8 +605,8 @@ def test_setup_registry_includes(self): pyramid.tests.test_config.dummy_include2""", } config.setup_registry(settings=settings) - self.assert_(reg.included) - self.assert_(reg.also_included) + self.assertTrue(reg.included) + self.assertTrue(reg.also_included) def test_setup_registry_includes_spaces(self): from pyramid.registry import Registry @@ -617,8 +617,8 @@ def test_setup_registry_includes_spaces(self): """pyramid.tests.test_config.dummy_include pyramid.tests.test_config.dummy_include2""", } config.setup_registry(settings=settings) - self.assert_(reg.included) - self.assert_(reg.also_included) + self.assertTrue(reg.included) + self.assertTrue(reg.also_included) def test_setup_registry_tweens(self): from pyramid.interfaces import ITweens @@ -917,7 +917,7 @@ def test_scan_integration_conflict(self): c.scan(selfscan) try: c.commit() - except ConfigurationConflictError, why: + except ConfigurationConflictError as why: def scanconflicts(e): conflicts = e._conflicts.values() for conflict in conflicts: @@ -929,6 +929,7 @@ def scanconflicts(e): self.assertTrue("@view_config(name='two', renderer='string')" in which) + @skip_on('py3') def test_hook_zca(self): from zope.component import getSiteManager def foo(): @@ -942,6 +943,7 @@ def foo(): finally: getSiteManager.reset() + @skip_on('py3') def test_unhook_zca(self): from zope.component import getSiteManager def foo(): @@ -987,7 +989,7 @@ def includeme2(config): config.include(includeme2) try: config.commit() - except ConfigurationConflictError, why: + except ConfigurationConflictError as why: c1, c2 = _conflictFunctions(why) self.assertEqual(c1, 'includeme1') self.assertEqual(c2, 'includeme2') @@ -1031,7 +1033,7 @@ def view2(request): pass config.set_notfound_view(view2) try: config.commit() - except ConfigurationConflictError, why: + except ConfigurationConflictError as why: c1, c2 = _conflictFunctions(why) self.assertEqual(c1, 'test_conflict_set_notfound_view') self.assertEqual(c2, 'test_conflict_set_notfound_view') @@ -1046,7 +1048,7 @@ def view2(request): pass config.set_forbidden_view(view2) try: config.commit() - except ConfigurationConflictError, why: + except ConfigurationConflictError as why: c1, c2 = _conflictFunctions(why) self.assertEqual(c1, 'test_conflict_set_forbidden_view') self.assertEqual(c2, 'test_conflict_set_forbidden_view') @@ -1069,7 +1071,7 @@ def foo(config): pass directives = {'foo':(foo, True)} config.registry._directives = directives foo_meth = config.foo - self.assertTrue(foo_meth.im_func.__docobj__ is foo) + self.assertTrue(getattr(foo_meth, im_func).__docobj__ is foo) def test___getattr__matches_no_action_wrap(self): config = self._makeOne() @@ -1077,7 +1079,7 @@ def foo(config): pass directives = {'foo':(foo, False)} config.registry._directives = directives foo_meth = config.foo - self.assertTrue(foo_meth.im_func is foo) + self.assertTrue(getattr(foo_meth, im_func) is foo) class TestConfiguratorDeprecatedFeatures(unittest.TestCase): def setUp(self): @@ -1118,9 +1120,9 @@ def _getViewCallable(self, config, ctx_iface=None, request_iface=None, def _registerRenderer(self, config, name='.txt'): from pyramid.interfaces import IRendererFactory from pyramid.interfaces import ITemplateRenderer - from zope.interface import implements + from zope.interface import implementer + @implementer(ITemplateRenderer) class Renderer: - implements(ITemplateRenderer) def __init__(self, info): self.__class__.info = info def __call__(self, *arg): @@ -1220,7 +1222,7 @@ def test_add_route_with_view_renderer(self): request_type = self._getRouteRequestIface(config, 'name') wrapper = self._getViewCallable(config, None, request_type) self._assertRoute(config, 'name', 'path') - self.assertEqual(wrapper(None, None).body, 'Hello!') + self.assertEqual(wrapper(None, None).body, b'Hello!') def test_add_route_with_view_attr(self): from pyramid.renderers import null_renderer @@ -1248,7 +1250,7 @@ def test_add_route_with_view_renderer_alias(self): request_type = self._getRouteRequestIface(config, 'name') wrapper = self._getViewCallable(config, None, request_type) self._assertRoute(config, 'name', 'path') - self.assertEqual(wrapper(None, None).body, 'Hello!') + self.assertEqual(wrapper(None, None).body, b'Hello!') def test_add_route_with_view_permission(self): from pyramid.interfaces import IAuthenticationPolicy @@ -1286,7 +1288,7 @@ def view2(request): pass config.add_route('a', '/a', view=view2) try: config.commit() - except ConfigurationConflictError, why: + except ConfigurationConflictError as why: c1, c2, c3, c4, c5, c6 = _conflictFunctions(why) self.assertEqual(c1, 'test_conflict_route_with_view') self.assertEqual(c2, 'test_conflict_route_with_view') @@ -1308,7 +1310,7 @@ def test_extend_with_dotted_name(self): config = self.config config.add_directive( 'dummy_extend', 'pyramid.tests.test_config.dummy_extend') - self.assert_(hasattr(config, 'dummy_extend')) + self.assertTrue(hasattr(config, 'dummy_extend')) config.dummy_extend('discrim') after = config.action_state self.assertEqual( @@ -1321,7 +1323,7 @@ def test_extend_with_python_callable(self): config = self.config config.add_directive( 'dummy_extend', dummy_extend) - self.assert_(hasattr(config, 'dummy_extend')) + self.assertTrue(hasattr(config, 'dummy_extend')) config.dummy_extend('discrim') after = config.action_state self.assertEqual( @@ -1335,7 +1337,7 @@ def test_extend_same_name_doesnt_conflict(self): 'dummy_extend', dummy_extend) config.add_directive( 'dummy_extend', dummy_extend2) - self.assert_(hasattr(config, 'dummy_extend')) + self.assertTrue(hasattr(config, 'dummy_extend')) config.dummy_extend('discrim') after = config.action_state self.assertEqual( @@ -1530,9 +1532,10 @@ def push(self, d): def pop(self): self.popped = True -from zope.interface import implements +from zope.interface import implementer +@implementer(IDummy) class DummyEvent: - implements(IDummy) + pass class DummyRegistry(object): def __init__(self, adaptation=None): @@ -1550,8 +1553,9 @@ def queryAdapter(self, *arg, **kw): return self.adaptation from pyramid.interfaces import IResponse +@implementer(IResponse) class DummyResponse(object): - implements(IResponse) + pass from zope.interface import Interface class IOther(Interface): diff --git a/pyramid/tests/test_config/test_testing.py b/pyramid/tests/test_config/test_testing.py index 494a2b099f..6c048b46d5 100644 --- a/pyramid/tests/test_config/test_testing.py +++ b/pyramid/tests/test_config/test_testing.py @@ -1,5 +1,6 @@ import unittest +from pyramid.compat import text_ from pyramid.tests.test_config import IDummy class TestingConfiguratorMixinTests(unittest.TestCase): @@ -35,14 +36,14 @@ def test_testing_resources(self): self.assertEqual(result['context'], ob1) self.assertEqual(result['view_name'], '') self.assertEqual(result['subpath'], ()) - self.assertEqual(result['traversed'], (u'ob1',)) + self.assertEqual(result['traversed'], (text_('ob1'),)) self.assertEqual(result['virtual_root'], ob1) self.assertEqual(result['virtual_root_path'], ()) result = adapter(DummyRequest({'PATH_INFO':'/ob2'})) self.assertEqual(result['context'], ob2) self.assertEqual(result['view_name'], '') self.assertEqual(result['subpath'], ()) - self.assertEqual(result['traversed'], (u'ob2',)) + self.assertEqual(result['traversed'], (text_('ob2'),)) self.assertEqual(result['virtual_root'], ob2) self.assertEqual(result['virtual_root_path'], ()) self.assertRaises(KeyError, adapter, DummyRequest({'PATH_INFO':'/ob3'})) @@ -166,9 +167,10 @@ def test_testing_add_template(self): renderer.assert_(bar=2) renderer.assert_(request=request) -from zope.interface import implements +from zope.interface import implementer +@implementer(IDummy) class DummyEvent: - implements(IDummy) + pass class DummyRequest: subpath = () diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py index f4e69f4e10..1f7ea05c2a 100644 --- a/pyramid/tests/test_config/test_views.py +++ b/pyramid/tests/test_config/test_views.py @@ -5,6 +5,7 @@ from pyramid.tests.test_config import dummy_view +from pyramid.compat import im_func from pyramid.exceptions import ConfigurationError from pyramid.exceptions import ConfigurationExecutionError from pyramid.exceptions import ConfigurationConflictError @@ -37,9 +38,9 @@ def _getViewCallable(self, config, ctx_iface=None, request_iface=None, def _registerRenderer(self, config, name='.txt'): from pyramid.interfaces import IRendererFactory from pyramid.interfaces import ITemplateRenderer - from zope.interface import implements + from zope.interface import implementer + @implementer(ITemplateRenderer) class Renderer: - implements(ITemplateRenderer) def __init__(self, info): self.__class__.info = info def __call__(self, *arg): @@ -103,7 +104,7 @@ def test_add_view_view_callable_None_with_renderer(self): self._registerRenderer(config, name='dummy') config.add_view(renderer='dummy') view = self._getViewCallable(config) - self.assertTrue('Hello!' in view(None, None).body) + self.assertTrue(b'Hello!' in view(None, None).body) def test_add_view_wrapped_view_is_decorated(self): def view(request): # request-only wrapper @@ -352,7 +353,7 @@ def test_add_view_same_phash_overrides_existing_single_view(self): from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IMultiView phash = md5() - phash.update('xhr:True') + phash.update(b'xhr:True') view = lambda *arg: 'NOT OK' view.__phash__ = phash.hexdigest() config = self._makeOne(autocommit=True) @@ -376,7 +377,7 @@ def test_add_view_exc_same_phash_overrides_existing_single_view(self): from pyramid.interfaces import IExceptionViewClassifier from pyramid.interfaces import IMultiView phash = md5() - phash.update('xhr:True') + phash.update(b'xhr:True') view = lambda *arg: 'NOT OK' view.__phash__ = phash.hexdigest() config = self._makeOne(autocommit=True) @@ -891,7 +892,7 @@ def __call__(self): wrapper = self._getViewCallable(config) request = self._makeRequest(config) result = wrapper(None, request) - self.assertEqual(result.body, 'Hello!') + self.assertEqual(result.body, b'Hello!') settings = config.registry.queryUtility(ISettings) result = renderer.info self.assertEqual(result.registry, config.registry) @@ -919,7 +920,7 @@ def __call__(self, *arg, **kw): wrapper = self._getViewCallable(config) request = self._makeRequest(config) result = wrapper(None, request) - self.assertEqual(result.body, 'moo') + self.assertEqual(result.body, b'moo') def test_add_view_with_template_renderer_no_callable(self): from pyramid.tests import test_config @@ -931,7 +932,7 @@ def test_add_view_with_template_renderer_no_callable(self): wrapper = self._getViewCallable(config) request = self._makeRequest(config) result = wrapper(None, request) - self.assertEqual(result.body, 'Hello!') + self.assertEqual(result.body, b'Hello!') settings = config.registry.queryUtility(ISettings) result = renderer.info self.assertEqual(result.registry, config.registry) @@ -1392,7 +1393,7 @@ def view(request): return 'OK' result = config.derive_view(view) self.assertFalse(result is view) - self.assertEqual(result(None, None).body, 'moo') + self.assertEqual(result(None, None).body, b'moo') def test_derive_view_with_default_renderer_with_explicit_renderer(self): class moo(object): pass @@ -1410,7 +1411,7 @@ def view(request): result = config.derive_view(view, renderer='foo') self.assertFalse(result is view) request = self._makeRequest(config) - self.assertEqual(result(None, request).body, 'foo') + self.assertEqual(result(None, request).body, b'foo') def test_add_static_view_here_no_utility_registered(self): from pyramid.renderers import null_renderer @@ -1545,7 +1546,7 @@ def test_set_notfound_view_with_renderer(self): result = view(None, request) finally: config.end() - self.assertTrue('div' in result.body) + self.assertTrue(b'div' in result.body) @testing.skip_on('java') def test_set_forbidden_view_with_renderer(self): @@ -1566,7 +1567,7 @@ def test_set_forbidden_view_with_renderer(self): result = view(None, request) finally: config.end() - self.assertTrue('div' in result.body) + self.assertTrue(b'div' in result.body) def test_set_view_mapper(self): from pyramid.interfaces import IViewMapperFactory @@ -2117,7 +2118,7 @@ def view(request): request = self._makeRequest() request.override_renderer = 'moo' context = testing.DummyResource() - self.assertEqual(result(context, request).body, 'moo') + self.assertEqual(result(context, request).body, b'moo') def test_requestonly_function_with_renderer_request_has_view(self): response = DummyResponse() @@ -2283,7 +2284,7 @@ def __call__(self, request): self.assertFalse(result is view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) - self.assertTrue('instance' in result.__name__) + self.assertTrue('test_views' in result.__name__) self.assertFalse(hasattr(result, '__call_permissive__')) self.assertEqual(result(None, None), response) @@ -2537,7 +2538,7 @@ def test_secured_view_raises_forbidden_no_name(self): request.url = 'url' try: result(None, request) - except HTTPForbidden, e: + except HTTPForbidden as e: self.assertEqual(e.message, 'Unauthorized: failed permission check') else: # pragma: no cover @@ -2559,7 +2560,7 @@ def myview(request): pass request.url = 'url' try: result(None, request) - except HTTPForbidden, e: + except HTTPForbidden as e: self.assertEqual(e.message, 'Unauthorized: myview failed permission check') else: # pragma: no cover @@ -2577,7 +2578,7 @@ def predicate1(context, request): request.method = 'POST' try: result(None, None) - except PredicateMismatch, e: + except PredicateMismatch as e: self.assertEqual(e.detail, 'predicate mismatch for view ') else: # pragma: no cover raise AssertionError @@ -2593,7 +2594,7 @@ def predicate1(context, request): request.method = 'POST' try: result(None, None) - except PredicateMismatch, e: + except PredicateMismatch as e: self.assertEqual(e.detail, 'predicate mismatch for view myview') else: # pragma: no cover raise AssertionError @@ -2662,7 +2663,7 @@ def outer_view(context, request): self.assertEqual(request.wrapped_body, inner_response.body) self.assertEqual(request.wrapped_view.__original_view__, inner_view) - return Response('outer ' + request.wrapped_body) + return Response(b'outer ' + request.wrapped_body) self.config.registry.registerAdapter( outer_view, (IViewClassifier, None, None), IView, 'owrap') deriver = self._makeOne(viewname='inner', @@ -2673,7 +2674,7 @@ def outer_view(context, request): self.assertEqual(inner_view.__doc__, result.__doc__) request = self._makeRequest() response = result(None, request) - self.assertEqual(response.body, 'outer OK') + self.assertEqual(response.body, b'outer OK') def test_with_wrapper_viewname_notfound(self): from pyramid.response import Response @@ -3252,12 +3253,12 @@ def __permitted__(self, context, request): self.assertTrue(view1.__doc__ is view2.__doc__) self.assertTrue(view1.__module__ is view2.__module__) self.assertTrue(view1.__name__ is view2.__name__) - self.assertTrue(view1.__call_permissive__.im_func is - view2.__call_permissive__.im_func) - self.assertTrue(view1.__permitted__.im_func is - view2.__permitted__.im_func) - self.assertTrue(view1.__predicated__.im_func is - view2.__predicated__.im_func) + self.assertTrue(getattr(view1.__call_permissive__, im_func) is + getattr(view2.__call_permissive__, im_func)) + self.assertTrue(getattr(view1.__permitted__, im_func) is + getattr(view2.__permitted__, im_func)) + self.assertTrue(getattr(view1.__predicated__, im_func) is + getattr(view2.__predicated__, im_func)) class TestStaticURLInfo(unittest.TestCase): @@ -3463,10 +3464,11 @@ def __init__(self, environ=None): class DummyContext: pass -from zope.interface import implements +from zope.interface import implementer from pyramid.interfaces import IResponse +@implementer(IResponse) class DummyResponse(object): - implements(IResponse) + pass class DummyAccept(object): def __init__(self, *matches): @@ -3512,10 +3514,10 @@ def add_view(self, *args, **kw): def action(self, discriminator, callable): callable() -from zope.interface import implements +from zope.interface import implementer from pyramid.interfaces import IMultiView +@implementer(IMultiView) class DummyMultiView: - implements(IMultiView) def __init__(self): self.views = [] self.name = 'name' diff --git a/pyramid/tests/test_docs.py b/pyramid/tests/test_docs.py index bf06c7ca67..eba95b2104 100644 --- a/pyramid/tests/test_docs.py +++ b/pyramid/tests/test_docs.py @@ -1,4 +1,5 @@ import unittest +from pyramid.compat import print_ if 0: # no released version of manuel actually works with :lineno: @@ -31,5 +32,5 @@ def test_docs(cls): if filename.endswith('.rst'): docs.append(os.path.join(root, filename)) - print path + print_(path) return manuel.testing.TestSuite(m, *docs) diff --git a/pyramid/tests/test_encode.py b/pyramid/tests/test_encode.py index 27965aea97..ccc8f16e3d 100644 --- a/pyramid/tests/test_encode.py +++ b/pyramid/tests/test_encode.py @@ -1,4 +1,6 @@ import unittest +from pyramid.compat import text_ +from pyramid.compat import native_ class UrlEncodeTests(unittest.TestCase): def _callFUT(self, query, doseq=False): @@ -10,17 +12,17 @@ def test_ascii_only(self): self.assertEqual(result, 'a=1&b=2') def test_unicode_key(self): - la = unicode('LaPe\xc3\xb1a', 'utf-8') + la = text_(b'LaPe\xc3\xb1a', 'utf-8') result = self._callFUT([(la, 1), ('b',2)]) self.assertEqual(result, 'LaPe%C3%B1a=1&b=2') def test_unicode_val_single(self): - la = unicode('LaPe\xc3\xb1a', 'utf-8') + la = text_(b'LaPe\xc3\xb1a', 'utf-8') result = self._callFUT([('a', la), ('b',2)]) self.assertEqual(result, 'a=LaPe%C3%B1a&b=2') def test_unicode_val_multiple(self): - la = [unicode('LaPe\xc3\xb1a', 'utf-8')] * 2 + la = [text_(b'LaPe\xc3\xb1a', 'utf-8')] * 2 result = self._callFUT([('a', la), ('b',2)], doseq=True) self.assertEqual(result, 'a=LaPe%C3%B1a&a=LaPe%C3%B1a&b=2') @@ -42,29 +44,17 @@ def _callFUT(self, val, safe=''): from pyramid.encode import url_quote return url_quote(val, safe) - def test_it_default(self): - la = 'La/Pe\xc3\xb1a' + def test_it_bytes(self): + la = b'La/Pe\xc3\xb1a' result = self._callFUT(la) self.assertEqual(result, 'La%2FPe%C3%B1a') - def test_it_with_safe(self): - la = 'La/Pe\xc3\xb1a' - result = self._callFUT(la, '/') - self.assertEqual(result, 'La/Pe%C3%B1a') - -class TestQuotePlus(unittest.TestCase): - def _callFUT(self, val, safe=''): - from pyramid.encode import quote_plus - return quote_plus(val, safe) - - def test_it_default(self): - la = 'La Pe\xc3\xb1a' + def test_it_native(self): + la = native_(b'La/Pe\xc3\xb1a', 'utf-8') result = self._callFUT(la) - self.assertEqual(result, 'La+Pe%C3%B1a') - + self.assertEqual(result, 'La%2FPe%C3%B1a') + def test_it_with_safe(self): - la = 'La /Pe\xc3\xb1a' + la = b'La/Pe\xc3\xb1a' result = self._callFUT(la, '/') - self.assertEqual(result, 'La+/Pe%C3%B1a') - - + self.assertEqual(result, 'La/Pe%C3%B1a') diff --git a/pyramid/tests/test_httpexceptions.py b/pyramid/tests/test_httpexceptions.py index 9c7b38e272..927d277331 100644 --- a/pyramid/tests/test_httpexceptions.py +++ b/pyramid/tests/test_httpexceptions.py @@ -1,5 +1,8 @@ import unittest +from pyramid.compat import bytes_ +from pyramid.compat import text_ + class Test_exception_response(unittest.TestCase): def _callFUT(self, *arg, **kw): from pyramid.httpexceptions import exception_response @@ -48,9 +51,9 @@ def test_not_basestring(self): def test_unicode(self): class DummyUnicodeObject(object): def __unicode__(self): - return u'42' + return text_('42') duo = DummyUnicodeObject() - self.assertEqual(self._callFUT(duo), u'42') + self.assertEqual(self._callFUT(duo), text_('42')) class TestWSGIHTTPException(unittest.TestCase): def _getTargetClass(self): @@ -124,16 +127,16 @@ def test_ctor_with_empty_body(self): self.assertEqual(exc.content_length, None) def test_ctor_with_body_doesnt_set_default_app_iter(self): - exc = self._makeOne(body='123') - self.assertEqual(exc.app_iter, ['123']) + exc = self._makeOne(body=b'123') + self.assertEqual(exc.app_iter, [b'123']) def test_ctor_with_unicode_body_doesnt_set_default_app_iter(self): - exc = self._makeOne(unicode_body=u'123') - self.assertEqual(exc.app_iter, ['123']) + exc = self._makeOne(unicode_body=text_('123')) + self.assertEqual(exc.app_iter, [b'123']) def test_ctor_with_app_iter_doesnt_set_default_app_iter(self): - exc = self._makeOne(app_iter=['123']) - self.assertEqual(exc.app_iter, ['123']) + exc = self._makeOne(app_iter=[b'123']) + self.assertEqual(exc.app_iter, [b'123']) def test_ctor_with_body_sets_default_app_iter_html(self): cls = self._getTargetSubclass() @@ -142,10 +145,10 @@ def test_ctor_with_body_sets_default_app_iter_html(self): environ['HTTP_ACCEPT'] = 'text/html' start_response = DummyStartResponse() body = list(exc(environ, start_response))[0] - self.assertTrue(body.startswith('' in body) + self.assertTrue(b'' in body) def test__default_app_iter_with_comment_html2(self): cls = self._getTargetSubclass() @@ -232,7 +235,7 @@ def test__default_app_iter_with_comment_html2(self): environ['HTTP_ACCEPT'] = 'text/html' start_response = DummyStartResponse() body = list(exc(environ, start_response))[0] - self.assertTrue('' in body) + self.assertTrue(b'' in body) def test_custom_body_template(self): cls = self._getTargetSubclass() @@ -240,7 +243,7 @@ def test_custom_body_template(self): environ = _makeEnviron() start_response = DummyStartResponse() body = list(exc(environ, start_response))[0] - self.assertEqual(body, '200 OK\n\nGET') + self.assertEqual(body, b'200 OK\n\nGET') def test_custom_body_template_with_custom_variable_doesnt_choke(self): cls = self._getTargetSubclass() @@ -251,16 +254,16 @@ def __str__(self): raise ValueError environ['gardentheory.user'] = Choke() start_response = DummyStartResponse() body = list(exc(environ, start_response))[0] - self.assertEqual(body, '200 OK\n\nGET') + self.assertEqual(body, b'200 OK\n\nGET') def test_body_template_unicode(self): cls = self._getTargetSubclass() - la = unicode('/La Pe\xc3\xb1a', 'utf-8') + la = text_(b'/La Pe\xc3\xb1a', 'utf-8') environ = _makeEnviron(unicodeval=la) exc = cls(body_template='${unicodeval}') start_response = DummyStartResponse() body = list(exc(environ, start_response))[0] - self.assertEqual(body, '200 OK\n\n/La Pe\xc3\xb1a') + self.assertEqual(body, b'200 OK\n\n/La Pe\xc3\xb1a') class TestRenderAllExceptionsWithoutArguments(unittest.TestCase): def _doit(self, content_type): @@ -274,9 +277,9 @@ def _doit(self, content_type): exc.content_type = content_type result = list(exc(environ, start_response))[0] if exc.empty_body: - self.assertEqual(result, '') + self.assertEqual(result, b'') else: - self.assertTrue(exc.status in result) + self.assertTrue(bytes_(exc.status) in result) L.append(result) self.assertEqual(len(L), len(status_map)) @@ -309,8 +312,8 @@ def test_it_call_with_default_body_tmpl(self): start_response = DummyStartResponse() app_iter = exc(environ, start_response) self.assertEqual(app_iter[0], - ('None None\n\nThe resource has been moved to foo; ' - 'you should be redirected automatically.\n\n')) + (b'None None\n\nThe resource has been moved to foo; ' + b'you should be redirected automatically.\n\n')) class TestHTTPForbidden(unittest.TestCase): def _makeOne(self, *arg, **kw): @@ -336,8 +339,8 @@ def test_it_with_default_body_tmpl(self): start_response = DummyStartResponse() app_iter = exc(environ, start_response) self.assertEqual(app_iter[0], - ('405 Method Not Allowed\n\nThe method GET is not ' - 'allowed for this resource. \n\n\n')) + (b'405 Method Not Allowed\n\nThe method GET is not ' + b'allowed for this resource. \n\n\n')) class DummyRequest(object): diff --git a/pyramid/tests/test_i18n.py b/pyramid/tests/test_i18n.py index 008cf525a0..bd4998b106 100644 --- a/pyramid/tests/test_i18n.py +++ b/pyramid/tests/test_i18n.py @@ -50,9 +50,9 @@ def test_translate(self): def test_pluralize(self): translations = DummyTranslations() localizer = self._makeOne(None, translations) - self.assertEqual(localizer.pluralize('singular', 'plural', 1, - domain='1', mapping={}), - 'singular') + result = localizer.pluralize('singular', 'plural', 1, + domain='1', mapping={}) + self.assertEqual(result, 'singular') self.assertTrue(localizer.pluralizer) def test_pluralize_pluralizer_already_added(self): @@ -434,8 +434,8 @@ def test_dgettext(self): def test_ldgettext(self): t = self._makeOne() - self.assertEqual(t.ldgettext('messages', 'foo'), 'Voh') - self.assertEqual(t.ldgettext('messages1', 'foo'), 'VohD') + self.assertEqual(t.ldgettext('messages', 'foo'), b'Voh') + self.assertEqual(t.ldgettext('messages1', 'foo'), b'VohD') def test_dugettext(self): t = self._makeOne() @@ -449,8 +449,8 @@ def test_dngettext(self): def test_ldngettext(self): t = self._makeOne() - self.assertEqual(t.ldngettext('messages', 'foo1', 'foos1', 1), 'Voh1') - self.assertEqual(t.ldngettext('messages1', 'foo1', 'foos1', 1), 'VohD1') + self.assertEqual(t.ldngettext('messages', 'foo1', 'foos1', 1), b'Voh1') + self.assertEqual(t.ldngettext('messages1', 'foo1', 'foos1', 1),b'VohD1') def test_dungettext(self): t = self._makeOne() @@ -476,5 +476,9 @@ class DummyTranslations(object): def ugettext(self, text): return text + gettext = ugettext + def ungettext(self, singular, plural, n): return singular + + ngettext = ungettext diff --git a/pyramid/tests/test_integration.py b/pyramid/tests/test_integration.py index 1fa1cbbcf3..0c17b88cec 100644 --- a/pyramid/tests/test_integration.py +++ b/pyramid/tests/test_integration.py @@ -6,6 +6,8 @@ from pyramid.wsgi import wsgiapp from pyramid.view import view_config from pyramid.static import static_view +from pyramid.compat import text_ +from pyramid.compat import url_quote from zope.interface import Interface @@ -62,45 +64,42 @@ def tearDown(self): here = os.path.dirname(__file__) class TestStaticAppBase(IntegrationBase): - def _assertBody(self, body, filename): - self.assertEqual( - body.replace('\r', ''), - open(filename, 'r').read() - ) - def test_basic(self): res = self.testapp.get('/minimal.pt', status=200) - self._assertBody(res.body, os.path.join(here, 'fixtures/minimal.pt')) + _assertBody(res.body, os.path.join(here, 'fixtures/minimal.pt')) def test_hidden(self): res = self.testapp.get('/static/.hiddenfile', status=200) - self._assertBody( - res.body, - os.path.join(here, 'fixtures/static/.hiddenfile') - ) + _assertBody(res.body, os.path.join(here, 'fixtures/static/.hiddenfile')) def test_highchars_in_pathelement(self): - res = self.testapp.get('/static/héhé/index.html', status=200) - self._assertBody( - res.body, os.path.join(here, u'fixtures/static/héhé/index.html') + url = url_quote('/static/héhé/index.html') + res = self.testapp.get(url, status=200) + _assertBody( + res.body, + os.path.join(here, + text_('fixtures/static/héhé/index.html', 'utf-8')) ) def test_highchars_in_filename(self): - res = self.testapp.get('/static/héhé.html', status=200) - self._assertBody( - res.body, os.path.join(here, u'fixtures/static/héhé.html') + url = url_quote('/static/héhé.html') + res = self.testapp.get(url, status=200) + _assertBody( + res.body, + os.path.join(here, + text_('fixtures/static/héhé.html', 'utf-8')) ) def test_not_modified(self): self.testapp.extra_environ = { 'HTTP_IF_MODIFIED_SINCE':httpdate(pow(2, 32)-1)} res = self.testapp.get('/minimal.pt', status=304) - self.assertEqual(res.body, '') + self.assertEqual(res.body, b'') def test_file_in_subdir(self): fn = os.path.join(here, 'fixtures/static/index.html') res = self.testapp.get('/static/index.html', status=200) - self._assertBody(res.body, fn) + _assertBody(res.body, fn) def test_directory_noslash_redir(self): res = self.testapp.get('/static', status=301) @@ -120,30 +119,30 @@ def test_directory_noslash_redir_with_scriptname(self): def test_directory_withslash(self): fn = os.path.join(here, 'fixtures/static/index.html') res = self.testapp.get('/static/', status=200) - self._assertBody(res.body, fn) + _assertBody(res.body, fn) def test_range_inclusive(self): self.testapp.extra_environ = {'HTTP_RANGE':'bytes=1-2'} res = self.testapp.get('/static/index.html', status=206) - self.assertEqual(res.body, 'ht') + self.assertEqual(res.body, b'ht') def test_range_tilend(self): self.testapp.extra_environ = {'HTTP_RANGE':'bytes=-5'} res = self.testapp.get('/static/index.html', status=206) - self.assertEqual(res.body, 'tml>\n') + self.assertEqual(res.body, b'tml>\n') def test_range_notbytes(self): self.testapp.extra_environ = {'HTTP_RANGE':'kHz=-5'} res = self.testapp.get('/static/index.html', status=200) - self._assertBody(res.body, - os.path.join(here, 'fixtures/static/index.html')) + _assertBody(res.body, + os.path.join(here, 'fixtures/static/index.html')) def test_range_multiple(self): res = self.testapp.get('/static/index.html', [('HTTP_RANGE', 'bytes=10-11,11-12')], status=200) - self._assertBody(res.body, - os.path.join(here, 'fixtures/static/index.html')) + _assertBody(res.body, + os.path.join(here, 'fixtures/static/index.html')) def test_range_oob(self): self.testapp.extra_environ = {'HTTP_RANGE':'bytes=1000-1002'} @@ -171,7 +170,7 @@ class TestStaticAppNoSubpath(unittest.TestCase): staticapp = static_view(os.path.join(here, 'fixtures'), use_subpath=False) def _makeRequest(self, extra): from pyramid.request import Request - from StringIO import StringIO + from io import BytesIO kw = {'PATH_INFO':'', 'SCRIPT_NAME':'', 'SERVER_NAME':'localhost', @@ -179,59 +178,48 @@ def _makeRequest(self, extra): 'REQUEST_METHOD':'GET', 'wsgi.version':(1,0), 'wsgi.url_scheme':'http', - 'wsgi.input':StringIO()} + 'wsgi.input':BytesIO()} kw.update(extra) request = Request(kw) return request - def _assertBody(self, body, filename): - self.assertEqual( - body.replace('\r', ''), - open(filename, 'r').read() - ) - def test_basic(self): request = self._makeRequest({'PATH_INFO':'/minimal.pt'}) context = DummyContext() result = self.staticapp(context, request) self.assertEqual(result.status, '200 OK') - self._assertBody(result.body, os.path.join(here, 'fixtures/minimal.pt')) + _assertBody(result.body, os.path.join(here, 'fixtures/minimal.pt')) class TestStaticAppWithRoutePrefix(IntegrationBase, unittest.TestCase): package = 'pyramid.tests.pkgs.static_routeprefix' - def _assertBody(self, body, filename): - self.assertEqual( - body.replace('\r', ''), - open(filename, 'r').read() - ) def test_includelevel1(self): res = self.testapp.get('/static/minimal.pt', status=200) - self._assertBody(res.body, - os.path.join(here, 'fixtures/minimal.pt')) + _assertBody(res.body, + os.path.join(here, 'fixtures/minimal.pt')) def test_includelevel2(self): res = self.testapp.get('/prefix/static/index.html', status=200) - self._assertBody(res.body, - os.path.join(here, 'fixtures/static/index.html')) + _assertBody(res.body, + os.path.join(here, 'fixtures/static/index.html')) class TestFixtureApp(IntegrationBase, unittest.TestCase): package = 'pyramid.tests.pkgs.fixtureapp' def test_another(self): res = self.testapp.get('/another.html', status=200) - self.assertEqual(res.body, 'fixture') + self.assertEqual(res.body, b'fixture') def test_root(self): res = self.testapp.get('/', status=200) - self.assertEqual(res.body, 'fixture') + self.assertEqual(res.body, b'fixture') def test_dummyskin(self): self.testapp.get('/dummyskin.html', status=404) def test_error(self): res = self.testapp.get('/error.html', status=200) - self.assertEqual(res.body, 'supressed') + self.assertEqual(res.body, b'supressed') def test_protected(self): self.testapp.get('/protected.html', status=403) @@ -242,8 +230,8 @@ class TestStaticPermApp(IntegrationBase, unittest.TestCase): def test_allowed(self): result = self.testapp.get('/allowed/index.html', status=200) self.assertEqual( - result.body.replace('\r', ''), - open(os.path.join(here, 'fixtures/static/index.html'), 'r').read()) + result.body.replace(b'\r', b''), + read_(os.path.join(here, 'fixtures/static/index.html'))) def test_denied_via_acl_global_root_factory(self): self.testapp.extra_environ = {'REMOTE_USER':'bob'} @@ -253,8 +241,8 @@ def test_allowed_via_acl_global_root_factory(self): self.testapp.extra_environ = {'REMOTE_USER':'fred'} result = self.testapp.get('/protected/index.html', status=200) self.assertEqual( - result.body.replace('\r', ''), - open(os.path.join(here, 'fixtures/static/index.html'), 'r').read()) + result.body.replace(b'\r', b''), + read_(os.path.join(here, 'fixtures/static/index.html'))) def test_denied_via_acl_local_root_factory(self): self.testapp.extra_environ = {'REMOTE_USER':'fred'} @@ -264,8 +252,8 @@ def test_allowed_via_acl_local_root_factory(self): self.testapp.extra_environ = {'REMOTE_USER':'bob'} result = self.testapp.get('/factory_protected/index.html', status=200) self.assertEqual( - result.body.replace('\r', ''), - open(os.path.join(here, 'fixtures/static/index.html'), 'r').read()) + result.body.replace(b'\r', b''), + read_(os.path.join(here, 'fixtures/static/index.html'))) class TestCCBug(IntegrationBase, unittest.TestCase): # "unordered" as reported in IRC by author of @@ -273,11 +261,11 @@ class TestCCBug(IntegrationBase, unittest.TestCase): package = 'pyramid.tests.pkgs.ccbugapp' def test_rdf(self): res = self.testapp.get('/licenses/1/v1/rdf', status=200) - self.assertEqual(res.body, 'rdf') + self.assertEqual(res.body, b'rdf') def test_juri(self): res = self.testapp.get('/licenses/1/v1/juri', status=200) - self.assertEqual(res.body, 'juri') + self.assertEqual(res.body, b'juri') class TestHybridApp(IntegrationBase, unittest.TestCase): # make sure views registered for a route "win" over views registered @@ -286,19 +274,19 @@ class TestHybridApp(IntegrationBase, unittest.TestCase): package = 'pyramid.tests.pkgs.hybridapp' def test_root(self): res = self.testapp.get('/', status=200) - self.assertEqual(res.body, 'global') + self.assertEqual(res.body, b'global') def test_abc(self): res = self.testapp.get('/abc', status=200) - self.assertEqual(res.body, 'route') + self.assertEqual(res.body, b'route') def test_def(self): res = self.testapp.get('/def', status=200) - self.assertEqual(res.body, 'route2') + self.assertEqual(res.body, b'route2') def test_ghi(self): res = self.testapp.get('/ghi', status=200) - self.assertEqual(res.body, 'global') + self.assertEqual(res.body, b'global') def test_jkl(self): self.testapp.get('/jkl', status=404) @@ -308,41 +296,41 @@ def test_mno(self): def test_pqr_global2(self): res = self.testapp.get('/pqr/global2', status=200) - self.assertEqual(res.body, 'global2') + self.assertEqual(res.body, b'global2') def test_error(self): res = self.testapp.get('/error', status=200) - self.assertEqual(res.body, 'supressed') + self.assertEqual(res.body, b'supressed') def test_error2(self): res = self.testapp.get('/error2', status=200) - self.assertEqual(res.body, 'supressed2') + self.assertEqual(res.body, b'supressed2') def test_error_sub(self): res = self.testapp.get('/error_sub', status=200) - self.assertEqual(res.body, 'supressed2') + self.assertEqual(res.body, b'supressed2') class TestRestBugApp(IntegrationBase, unittest.TestCase): # test bug reported by delijati 2010/2/3 (http://pastebin.com/d4cc15515) package = 'pyramid.tests.pkgs.restbugapp' def test_it(self): res = self.testapp.get('/pet', status=200) - self.assertEqual(res.body, 'gotten') + self.assertEqual(res.body, b'gotten') class TestForbiddenAppHasResult(IntegrationBase, unittest.TestCase): # test that forbidden exception has ACLDenied result attached package = 'pyramid.tests.pkgs.forbiddenapp' def test_it(self): res = self.testapp.get('/x', status=403) - message, result = [x.strip() for x in res.body.split('\n')] - self.assertTrue(message.endswith('failed permission check')) + message, result = [x.strip() for x in res.body.split(b'\n')] + self.assertTrue(message.endswith(b'failed permission check')) self.assertTrue( - result.startswith("ACLDenied permission 'private' via ACE " - "'' in ACL " - "'' on context")) + result.startswith(b"ACLDenied permission 'private' via ACE " + b"'' in ACL " + b"'' on context")) self.assertTrue( - result.endswith("for principals ['system.Everyone']")) + result.endswith(b"for principals ['system.Everyone']")) class TestViewDecoratorApp(IntegrationBase, unittest.TestCase): package = 'pyramid.tests.pkgs.viewdecoratorapp' @@ -357,20 +345,20 @@ def test_first(self): # we use mako here instead of chameleon because it works on Jython self._configure_mako() res = self.testapp.get('/first', status=200) - self.assertTrue('OK' in res.body) + self.assertTrue(b'OK' in res.body) def test_second(self): # we use mako here instead of chameleon because it works on Jython self._configure_mako() res = self.testapp.get('/second', status=200) - self.assertTrue('OK2' in res.body) + self.assertTrue(b'OK2' in res.body) class TestViewPermissionBug(IntegrationBase, unittest.TestCase): # view_execution_permitted bug as reported by Shane at http://lists.repoze.org/pipermail/repoze-dev/2010-October/003603.html package = 'pyramid.tests.pkgs.permbugapp' def test_test(self): res = self.testapp.get('/test', status=200) - self.assertTrue('ACLDenied' in res.body) + self.assertTrue(b'ACLDenied' in res.body) def test_x(self): self.testapp.get('/x', status=403) @@ -380,15 +368,15 @@ class TestDefaultViewPermissionBug(IntegrationBase, unittest.TestCase): package = 'pyramid.tests.pkgs.defpermbugapp' def test_x(self): res = self.testapp.get('/x', status=403) - self.assertTrue('failed permission check' in res.body) + self.assertTrue(b'failed permission check' in res.body) def test_y(self): res = self.testapp.get('/y', status=403) - self.assertTrue('failed permission check' in res.body) + self.assertTrue(b'failed permission check' in res.body) def test_z(self): res = self.testapp.get('/z', status=200) - self.assertTrue('public' in res.body) + self.assertTrue(b'public' in res.body) from pyramid.tests.pkgs.exceptionviewapp.models import \ AnException, NotAnException @@ -400,31 +388,31 @@ class TestExceptionViewsApp(IntegrationBase, unittest.TestCase): root_factory = lambda *arg: excroot def test_root(self): res = self.testapp.get('/', status=200) - self.assertTrue('maybe' in res.body) + self.assertTrue(b'maybe' in res.body) def test_notanexception(self): res = self.testapp.get('/notanexception', status=200) - self.assertTrue('no' in res.body) + self.assertTrue(b'no' in res.body) def test_anexception(self): res = self.testapp.get('/anexception', status=200) - self.assertTrue('yes' in res.body) + self.assertTrue(b'yes' in res.body) def test_route_raise_exception(self): res = self.testapp.get('/route_raise_exception', status=200) - self.assertTrue('yes' in res.body) + self.assertTrue(b'yes' in res.body) def test_route_raise_exception2(self): res = self.testapp.get('/route_raise_exception2', status=200) - self.assertTrue('yes' in res.body) + self.assertTrue(b'yes' in res.body) def test_route_raise_exception3(self): res = self.testapp.get('/route_raise_exception3', status=200) - self.assertTrue('whoa' in res.body) + self.assertTrue(b'whoa' in res.body) def test_route_raise_exception4(self): res = self.testapp.get('/route_raise_exception4', status=200) - self.assertTrue('whoa' in res.body) + self.assertTrue(b'whoa' in res.body) class TestConflictApp(unittest.TestCase): package = 'pyramid.tests.pkgs.conflictapp' @@ -440,9 +428,9 @@ def test_autoresolved_view(self): from webtest import TestApp self.testapp = TestApp(app) res = self.testapp.get('/') - self.assertTrue('a view' in res.body) + self.assertTrue(b'a view' in res.body) res = self.testapp.get('/route') - self.assertTrue('route view' in res.body) + self.assertTrue(b'route view' in res.body) def test_overridden_autoresolved_view(self): from pyramid.response import Response @@ -455,7 +443,7 @@ def thisview(request): from webtest import TestApp self.testapp = TestApp(app) res = self.testapp.get('/') - self.assertTrue('this view' in res.body) + self.assertTrue(b'this view' in res.body) def test_overridden_route_view(self): from pyramid.response import Response @@ -468,7 +456,7 @@ def thisview(request): from webtest import TestApp self.testapp = TestApp(app) res = self.testapp.get('/route') - self.assertTrue('this view' in res.body) + self.assertTrue(b'this view' in res.body) def test_nonoverridden_authorization_policy(self): config = self._makeConfig() @@ -477,7 +465,7 @@ def test_nonoverridden_authorization_policy(self): from webtest import TestApp self.testapp = TestApp(app) res = self.testapp.get('/protected', status=403) - self.assertTrue('403 Forbidden' in res) + self.assertTrue(b'403 Forbidden' in res.body) def test_overridden_authorization_policy(self): config = self._makeConfig() @@ -507,15 +495,15 @@ def tearDown(self): def test_root(self): res = self.testapp.get('/', status=200) - self.assertTrue('root' in res.body) + self.assertTrue(b'root' in res.body) def test_two(self): res = self.testapp.get('/two', status=200) - self.assertTrue('two' in res.body) + self.assertTrue(b'two' in res.body) def test_three(self): res = self.testapp.get('/three', status=200) - self.assertTrue('three' in res.body) + self.assertTrue(b'three' in res.body) class SelfScanAppTest(unittest.TestCase): def setUp(self): @@ -531,11 +519,11 @@ def tearDown(self): def test_root(self): res = self.testapp.get('/', status=200) - self.assertTrue('root' in res.body) + self.assertTrue(b'root' in res.body) def test_two(self): res = self.testapp.get('/two', status=200) - self.assertTrue('two' in res.body) + self.assertTrue(b'two' in res.body) class WSGIApp2AppTest(unittest.TestCase): def setUp(self): @@ -551,18 +539,18 @@ def tearDown(self): def test_hello(self): res = self.testapp.get('/hello', status=200) - self.assertTrue('Hello' in res.body) + self.assertTrue(b'Hello' in res.body) if os.name != 'java': # uses chameleon class RendererScanAppTest(IntegrationBase, unittest.TestCase): package = 'pyramid.tests.pkgs.rendererscanapp' def test_root(self): res = self.testapp.get('/one', status=200) - self.assertTrue('One!' in res.body) + self.assertTrue(b'One!' in res.body) def test_two(self): res = self.testapp.get('/two', status=200) - self.assertTrue('Two!' in res.body) + self.assertTrue(b'Two!' in res.body) def test_rescan(self): self.config.scan('pyramid.tests.pkgs.rendererscanapp') @@ -570,9 +558,9 @@ def test_rescan(self): from webtest import TestApp testapp = TestApp(app) res = testapp.get('/one', status=200) - self.assertTrue('One!' in res.body) + self.assertTrue(b'One!' in res.body) res = testapp.get('/two', status=200) - self.assertTrue('Two!' in res.body) + self.assertTrue(b'Two!' in res.body) class DummyContext(object): pass @@ -588,3 +576,12 @@ def httpdate(ts): import datetime ts = datetime.datetime.utcfromtimestamp(ts) return ts.strftime("%a, %d %b %Y %H:%M:%S GMT") + +def read_(filename): + with open(filename, 'rb') as fp: + val = fp.read() + return val + +def _assertBody(body, filename): + assert(body.replace(b'\r', b'') == read_(filename)) + diff --git a/pyramid/tests/test_location.py b/pyramid/tests/test_location.py index 3d30c9954e..e1f47f4ab3 100644 --- a/pyramid/tests/test_location.py +++ b/pyramid/tests/test_location.py @@ -34,7 +34,7 @@ def test_lineage(self): self.assertEqual(result, [o1]) from pyramid.interfaces import ILocation -from zope.interface import implements +from zope.interface import implementer +@implementer(ILocation) class Location(object): - implements(ILocation) __name__ = __parent__ = None diff --git a/pyramid/tests/test_mako_templating.py b/pyramid/tests/test_mako_templating.py index 162f774f53..074d28b85e 100644 --- a/pyramid/tests/test_mako_templating.py +++ b/pyramid/tests/test_mako_templating.py @@ -2,6 +2,8 @@ import unittest from pyramid import testing +from pyramid.compat import text_ +from pyramid.compat import text_type class Base(object): def setUp(self): @@ -275,16 +277,16 @@ def test_call(self): lookup = DummyLookup() instance = self._makeOne('path', lookup) result = instance({}, {'system':1}) - self.assertTrue(isinstance(result, unicode)) - self.assertEqual(result, u'result') + self.assertTrue(isinstance(result, text_type)) + self.assertEqual(result, text_('result')) def test_call_with_system_context(self): # lame lookup = DummyLookup() instance = self._makeOne('path', lookup) result = instance({}, {'context':1}) - self.assertTrue(isinstance(result, unicode)) - self.assertEqual(result, u'result') + self.assertTrue(isinstance(result, text_type)) + self.assertEqual(result, text_('result')) self.assertEqual(lookup.values, {'_context':1}) def test_call_with_tuple_value(self): @@ -292,7 +294,7 @@ def test_call_with_tuple_value(self): instance = self._makeOne('path', lookup) result = instance(('fub', {}), {'context':1}) self.assertEqual(lookup.deffed, 'fub') - self.assertEqual(result, u'result') + self.assertEqual(result, text_('result')) self.assertEqual(lookup.values, {'_context':1}) def test_call_with_nondict_value(self): @@ -306,7 +308,7 @@ def test_call_render_raises(self): instance = self._makeOne('path', lookup) try: instance({}, {}) - except MakoRenderingException, e: + except MakoRenderingException as e: self.assertTrue('NotImplementedError' in e.text) else: # pragma: no cover raise AssertionError @@ -315,8 +317,8 @@ def test_implementation(self): lookup = DummyLookup() instance = self._makeOne('path', lookup) result = instance.implementation().render_unicode() - self.assertTrue(isinstance(result, unicode)) - self.assertEqual(result, u'result') + self.assertTrue(isinstance(result, text_type)) + self.assertEqual(result, text_('result')) class TestIntegration(unittest.TestCase): def setUp(self): @@ -333,45 +335,48 @@ def tearDown(self): def test_render(self): from pyramid.renderers import render result = render('helloworld.mak', {'a':1}).replace('\r','') - self.assertEqual(result, u'\nHello föö\n') + self.assertEqual(result, text_('\nHello föö\n', 'utf-8')) def test_render_from_fs(self): from pyramid.renderers import render self.config.add_settings({'reload_templates': True}) result = render('helloworld.mak', {'a':1}).replace('\r','') - self.assertEqual(result, u'\nHello föö\n') + self.assertEqual(result, text_('\nHello föö\n', 'utf-8')) def test_render_inheritance(self): from pyramid.renderers import render result = render('helloinherit.mak', {}).replace('\r','') - self.assertEqual(result, u'Layout\nHello World!\n') + self.assertEqual(result, text_('Layout\nHello World!\n')) def test_render_inheritance_pkg_spec(self): from pyramid.renderers import render result = render('hello_inherit_pkg.mak', {}).replace('\r','') - self.assertEqual(result, u'Layout\nHello World!\n') + self.assertEqual(result, text_('Layout\nHello World!\n')) def test_render_to_response(self): from pyramid.renderers import render_to_response result = render_to_response('helloworld.mak', {'a':1}) - self.assertEqual(result.ubody.replace('\r',''), u'\nHello föö\n') + self.assertEqual(result.ubody.replace('\r',''), + text_('\nHello föö\n', 'utf-8')) def test_render_to_response_pkg_spec(self): from pyramid.renderers import render_to_response result = render_to_response('pyramid.tests:fixtures/helloworld.mak', {'a':1}) - self.assertEqual(result.ubody.replace('\r', ''), u'\nHello föö\n') + self.assertEqual(result.ubody.replace('\r', ''), + text_('\nHello föö\n', 'utf-8')) def test_render_with_abs_path(self): from pyramid.renderers import render result = render('/helloworld.mak', {'a':1}).replace('\r','') - self.assertEqual(result, u'\nHello föö\n') + self.assertEqual(result, text_('\nHello föö\n', 'utf-8')) def test_get_renderer(self): from pyramid.renderers import get_renderer result = get_renderer('helloworld.mak') - self.assertEqual(result.implementation().render_unicode().replace('\r',''), - u'\nHello föö\n') + self.assertEqual( + result.implementation().render_unicode().replace('\r',''), + text_('\nHello föö\n', 'utf-8')) def test_template_not_found(self): from pyramid.renderers import render @@ -381,8 +386,9 @@ def test_template_not_found(self): def test_template_default_escaping(self): from pyramid.renderers import render - result = render('nonminimal.mak', {'name':'fred'}).replace('\r','') - self.assertEqual(result, u'Hello, <b>fred</b>!\n') + result = render('nonminimal.mak', + {'name':'fred'}).replace('\r','') + self.assertEqual(result, text_('Hello, <b>fred</b>!\n')) class TestPkgResourceTemplateLookup(unittest.TestCase): def _makeOne(self, **kw): @@ -448,7 +454,7 @@ def render_unicode(self, **values): if self.exc: raise self.exc self.values = values - return u'result' + return text_('result') class DummyRendererInfo(object): def __init__(self, kw): diff --git a/pyramid/tests/test_paster.py b/pyramid/tests/test_paster.py index 995e05d463..a697b56917 100644 --- a/pyramid/tests/test_paster.py +++ b/pyramid/tests/test_paster.py @@ -1,5 +1,7 @@ import unittest +from pyramid.testing import skip_on +@skip_on('py3') class TestPShellCommand(unittest.TestCase): def _getTargetClass(self): from pyramid.paster import PShellCommand @@ -246,6 +248,7 @@ def test_command_custom_section_override(self): self.assertTrue(self.bootstrap.closer.called) self.assertTrue(shell.help) +@skip_on('py3') class TestPRoutesCommand(unittest.TestCase): def _getTargetClass(self): from pyramid.paster import PRoutesCommand @@ -372,6 +375,7 @@ def test__get_mapper(self): result = command._get_mapper(registry) self.assertEqual(result.__class__, RoutesMapper) +@skip_on('py3') class TestPViewsCommand(unittest.TestCase): def _getTargetClass(self): from pyramid.paster import PViewsCommand @@ -397,7 +401,7 @@ def test__find_view_no_match(self): self.assertEqual(result, None) def test__find_view_no_match_multiview_registered(self): - from zope.interface import implements + from zope.interface import implementer from zope.interface import providedBy from pyramid.interfaces import IRequest from pyramid.interfaces import IViewClassifier @@ -405,8 +409,9 @@ def test__find_view_no_match_multiview_registered(self): from pyramid.traversal import DefaultRootFactory from pyramid.registry import Registry registry = Registry() + @implementer(IMultiView) class View1(object): - implements(IMultiView) + pass request = DummyRequest({'PATH_INFO':'/a'}) root = DefaultRootFactory(request) root_iface = providedBy(root) @@ -439,7 +444,7 @@ def view1(): pass self.assertEqual(result, view1) def test__find_view_traversal_multiview(self): - from zope.interface import implements + from zope.interface import implementer from zope.interface import providedBy from pyramid.interfaces import IRequest from pyramid.interfaces import IViewClassifier @@ -447,8 +452,9 @@ def test__find_view_traversal_multiview(self): from pyramid.traversal import DefaultRootFactory from pyramid.registry import Registry registry = Registry() + @implementer(IMultiView) class View1(object): - implements(IMultiView) + pass request = DummyRequest({'PATH_INFO':'/a'}) root = DefaultRootFactory(request) root_iface = providedBy(root) @@ -463,7 +469,7 @@ class View1(object): def test__find_view_route_no_multiview(self): from zope.interface import Interface - from zope.interface import implements + from zope.interface import implementer from pyramid.interfaces import IRouteRequest from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IView @@ -478,8 +484,8 @@ class IMyRoute(Interface): (IViewClassifier, IMyRoute, IMyRoot), IView, '') registry.registerUtility(IMyRoute, IRouteRequest, name='a') + @implementer(IMyRoot) class Factory(object): - implements(IMyRoot) def __init__(self, request): pass routes = [DummyRoute('a', '/a', factory=Factory, matchdict={}), @@ -491,7 +497,7 @@ def __init__(self, request): def test__find_view_route_multiview_no_view_registered(self): from zope.interface import Interface - from zope.interface import implements + from zope.interface import implementer from pyramid.interfaces import IRouteRequest from pyramid.interfaces import IMultiView from pyramid.interfaces import IRootFactory @@ -507,8 +513,8 @@ class IMyRoute2(Interface): pass registry.registerUtility(IMyRoute1, IRouteRequest, name='a') registry.registerUtility(IMyRoute2, IRouteRequest, name='b') + @implementer(IMyRoot) class Factory(object): - implements(IMyRoot) def __init__(self, request): pass registry.registerUtility(Factory, IRootFactory) @@ -521,7 +527,7 @@ def __init__(self, request): def test__find_view_route_multiview(self): from zope.interface import Interface - from zope.interface import implements + from zope.interface import implementer from pyramid.interfaces import IRouteRequest from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IView @@ -545,8 +551,8 @@ class IMyRoute2(Interface): IView, '') registry.registerUtility(IMyRoute1, IRouteRequest, name='a') registry.registerUtility(IMyRoute2, IRouteRequest, name='b') + @implementer(IMyRoot) class Factory(object): - implements(IMyRoot) def __init__(self, request): pass registry.registerUtility(Factory, IRootFactory) @@ -822,6 +828,7 @@ def predicate(): pass self.assertEqual(L[8], ' pyramid.tests.test_paster.view.call') self.assertEqual(L[9], ' view predicates (predicate = x)') +@skip_on('py3') class TestGetApp(unittest.TestCase): def _callFUT(self, config_file, section_name, loadapp): from pyramid.paster import get_app @@ -857,6 +864,7 @@ def test_it_with_hash_and_name_override(self): self.assertEqual(loadapp.relative_to, os.getcwd()) self.assertEqual(result, app) +@skip_on('py3') class TestBootstrap(unittest.TestCase): def _callFUT(self, config_uri, request=None): from pyramid.paster import bootstrap @@ -896,6 +904,7 @@ def test_it_request_with_registry(self): self.assertEqual(result['root'], self.root) self.assert_('closer' in result) +@skip_on('py3') class TestPTweensCommand(unittest.TestCase): def _getTargetClass(self): from pyramid.paster import PTweensCommand @@ -1042,10 +1051,10 @@ class DummyView(object): def __init__(self, **attrs): self.__request_attrs__ = attrs +from zope.interface import implementer +from pyramid.interfaces import IMultiView +@implementer(IMultiView) class DummyMultiView(object): - from zope.interface import implements - from pyramid.interfaces import IMultiView - implements(IMultiView) def __init__(self, *views, **attrs): self.views = [(None, view, None) for view in views] @@ -1062,7 +1071,7 @@ def items(self, section): self.section = section if self.result is None: from ConfigParser import NoSectionError - raise NoSectionError, section + raise NoSectionError(section) return self.result class DummyConfigParserFactory(object): diff --git a/pyramid/tests/test_registry.py b/pyramid/tests/test_registry.py index 6a20eaa5de..c3104bd319 100644 --- a/pyramid/tests/test_registry.py +++ b/pyramid/tests/test_registry.py @@ -48,9 +48,11 @@ class DummyModule: __file__ = '' from zope.interface import Interface -from zope.interface import implements +from zope.interface import implementer class IDummyEvent(Interface): pass +@implementer(IDummyEvent) class DummyEvent(object): - implements(IDummyEvent) + pass + diff --git a/pyramid/tests/test_renderers.py b/pyramid/tests/test_renderers.py index 48b1bed654..1054dcb1ca 100644 --- a/pyramid/tests/test_renderers.py +++ b/pyramid/tests/test_renderers.py @@ -2,6 +2,7 @@ from pyramid.testing import cleanUp from pyramid import testing +from pyramid.compat import text_ class TestTemplateRendererFactory(unittest.TestCase): def setUp(self): @@ -433,7 +434,7 @@ def _callFUT(self, name): def test_it_unicode(self): renderer = self._callFUT(None) - value = unicode('La Pe\xc3\xb1a', 'utf-8') + value = text_('La Pe\xc3\xb1a', 'utf-8') result = renderer(value, {}) self.assertEqual(result, value) @@ -452,14 +453,14 @@ def test_it_other(self): def test_with_request_content_type_notset(self): request = testing.DummyRequest() renderer = self._callFUT(None) - renderer(None, {'request':request}) + renderer('', {'request':request}) self.assertEqual(request.response.content_type, 'text/plain') def test_with_request_content_type_set(self): request = testing.DummyRequest() request.response.content_type = 'text/mishmash' renderer = self._callFUT(None) - renderer(None, {'request':request}) + renderer('', {'request':request}) self.assertEqual(request.response.content_type, 'text/mishmash') @@ -593,22 +594,22 @@ def test__make_response_request_is_None(self): request = None helper = self._makeOne('loo.foo') response = helper._make_response('abc', request) - self.assertEqual(response.body, 'abc') + self.assertEqual(response.body, b'abc') def test__make_response_request_is_None_response_factory_exists(self): self._registerResponseFactory() request = None helper = self._makeOne('loo.foo') - response = helper._make_response('abc', request) + response = helper._make_response(b'abc', request) self.assertEqual(response.__class__.__name__, 'ResponseFactory') - self.assertEqual(response.body, 'abc') + self.assertEqual(response.body, b'abc') def test__make_response_result_is_unicode(self): from pyramid.response import Response request = testing.DummyRequest() request.response = Response() helper = self._makeOne('loo.foo') - la = unicode('/La Pe\xc3\xb1a', 'utf-8') + la = text_('/La Pe\xc3\xb1a', 'utf-8') response = helper._make_response(la, request) self.assertEqual(response.body, la.encode('utf-8')) @@ -617,7 +618,7 @@ def test__make_response_result_is_str(self): request = testing.DummyRequest() request.response = Response() helper = self._makeOne('loo.foo') - la = unicode('/La Pe\xc3\xb1a', 'utf-8') + la = text_('/La Pe\xc3\xb1a', 'utf-8') response = helper._make_response(la.encode('utf-8'), request) self.assertEqual(response.body, la.encode('utf-8')) @@ -630,7 +631,7 @@ def test__make_response_with_content_type(self): helper = self._makeOne('loo.foo') response = helper._make_response('abc', request) self.assertEqual(response.content_type, 'text/nonsense') - self.assertEqual(response.body, 'abc') + self.assertEqual(response.body, b'abc') def test__make_response_with_headerlist(self): from pyramid.response import Response @@ -645,7 +646,7 @@ def test__make_response_with_headerlist(self): ('Content-Length', '3'), ('a', '1'), ('b', '2')]) - self.assertEqual(response.body, 'abc') + self.assertEqual(response.body, b'abc') def test__make_response_with_status(self): from pyramid.response import Response @@ -656,7 +657,7 @@ def test__make_response_with_status(self): helper = self._makeOne('loo.foo') response = helper._make_response('abc', request) self.assertEqual(response.status, '406 You Lose') - self.assertEqual(response.body, 'abc') + self.assertEqual(response.body, b'abc') def test__make_response_with_charset(self): from pyramid.response import Response @@ -686,9 +687,9 @@ def __init__(self): self.config.registry.registerUtility(ResponseFactory, IResponseFactory) request = testing.DummyRequest() helper = self._makeOne('loo.foo') - response = helper._make_response('abc', request) + response = helper._make_response(b'abc', request) self.assertEqual(response.__class__, ResponseFactory) - self.assertEqual(response.body, 'abc') + self.assertEqual(response.body, b'abc') def test__make_response_with_real_request(self): # functional @@ -699,7 +700,7 @@ def test__make_response_with_real_request(self): helper = self._makeOne('loo.foo') response = helper._make_response('abc', request) self.assertEqual(response.status, '406 You Lose') - self.assertEqual(response.body, 'abc') + self.assertEqual(response.body, b'abc') def test_clone_noargs(self): helper = self._makeOne('name', 'package', 'registry') @@ -811,7 +812,7 @@ def test_it_no_request(self): 'pyramid.tests:abc/def.pt') renderer.string_response = 'abc' response = self._callFUT('abc/def.pt', dict(a=1)) - self.assertEqual(response.body, 'abc') + self.assertEqual(response.body, b'abc') renderer.assert_(a=1) renderer.assert_(request=None) @@ -822,7 +823,7 @@ def test_it_with_request(self): request = testing.DummyRequest() response = self._callFUT('abc/def.pt', dict(a=1), request=request) - self.assertEqual(response.body, 'abc') + self.assertEqual(response.body, b'abc') renderer.assert_(a=1) renderer.assert_(request=request) @@ -834,7 +835,7 @@ def test_it_with_package(self): request = testing.DummyRequest() response = self._callFUT('abc/def.pt', dict(a=1), request=request, package=pyramid.tests) - self.assertEqual(response.body, 'abc') + self.assertEqual(response.body, b'abc') renderer.assert_(a=1) renderer.assert_(request=request) diff --git a/pyramid/tests/test_request.py b/pyramid/tests/test_request.py index 066aa92075..9d48d3b6e8 100644 --- a/pyramid/tests/test_request.py +++ b/pyramid/tests/test_request.py @@ -1,6 +1,11 @@ import unittest from pyramid import testing +from pyramid.compat import text_ +from pyramid.compat import bytes_ +from pyramid.compat import native_ +from pyramid.compat import iteritems_, iterkeys_, itervalues_ + class TestRequest(unittest.TestCase): def setUp(self): self.config = testing.setUp() @@ -50,7 +55,7 @@ def test_params_decoded_from_utf_8_by_default(self): } request = self._makeOne(environ) request.charset = None - self.assertEqual(request.GET['la'], u'La Pe\xf1a') + self.assertEqual(request.GET['la'], text_(b'La Pe\xf1a')) def test_class_implements(self): from pyramid.interfaces import IRequest @@ -166,7 +171,7 @@ def test_route_url(self): self.config.registry.registerUtility(mapper, IRoutesMapper) result = inst.route_url('flub', 'extra1', 'extra2', a=1, b=2, c=3, _query={'a':1}, - _anchor=u"foo") + _anchor=text_("foo")) self.assertEqual(result, 'http://example.com:5432/1/2/3/extra1/extra2?a=1#foo') @@ -184,7 +189,7 @@ def test_route_path(self): self.config.registry.registerUtility(mapper, IRoutesMapper) result = inst.route_path('flub', 'extra1', 'extra2', a=1, b=2, c=3, _query={'a':1}, - _anchor=u"foo") + _anchor=text_("foo")) self.assertEqual(result, '/1/2/3/extra1/extra2?a=1#foo') def test_static_url(self): @@ -235,20 +240,20 @@ def adapter(ob): def test_json_body_invalid_json(self): request = self._makeOne({'REQUEST_METHOD':'POST'}) - request.body = '{' + request.body = b'{' self.assertRaises(ValueError, getattr, request, 'json_body') def test_json_body_valid_json(self): request = self._makeOne({'REQUEST_METHOD':'POST'}) - request.body = '{"a":1}' + request.body = b'{"a":1}' self.assertEqual(request.json_body, {'a':1}) def test_json_body_alternate_charset(self): from pyramid.compat import json request = self._makeOne({'REQUEST_METHOD':'POST'}) request.charset = 'latin-1' - la = unicode('La Pe\xc3\xb1a', 'utf-8') - body = json.dumps({'a':la}, encoding='latin-1') + la = text_(b'La Pe\xc3\xb1a', 'utf-8') + body = bytes_(json.dumps({'a':la}), 'latin-1') request.body = body self.assertEqual(request.json_body, {'a':la}) @@ -322,17 +327,17 @@ def test_items(self): def test_iteritems(self): environ = {'zooma':1} inst = self._makeOne(environ) - self.assertEqual(list(inst.iteritems()), list(environ.iteritems())) + self.assertEqual(list(inst.iteritems()), list(iteritems_(environ))) def test_iterkeys(self): environ = {'zooma':1} inst = self._makeOne(environ) - self.assertEqual(list(inst.iterkeys()), list(environ.iterkeys())) + self.assertEqual(list(inst.iterkeys()), list(iterkeys_(environ))) def test_itervalues(self): environ = {'zooma':1} inst = self._makeOne(environ) - self.assertEqual(list(inst.itervalues()), list(environ.itervalues())) + self.assertEqual(list(inst.itervalues()), list(itervalues_(environ))) def test_keys(self): environ = {'zooma':1} @@ -370,8 +375,8 @@ def test_update(self): def test_values(self): environ = {'zooma':1} inst = self._makeOne(environ) - result = inst.values() - self.assertEqual(result, environ.values()) + result = list(inst.values()) + self.assertEqual(result, list(environ.values())) def test_response_content_type(self): inst = self._makeOne() @@ -497,14 +502,16 @@ def test_it_with_extra_slashes_in_path_info(self): self.assertEqual(request.environ['PATH_INFO'], '/hello/') def test_subpath_path_info_and_script_name_have_utf8(self): - la = 'La Pe\xc3\xb1a' - request = DummyRequest({'PATH_INFO':'/'+la, 'SCRIPT_NAME':'/'+la}) - request.subpath = (unicode(la, 'utf-8'), ) + encoded = native_(text_(b'La Pe\xc3\xb1a')) + decoded = text_(bytes_(encoded), 'utf-8') + request = DummyRequest({'PATH_INFO':'/' + encoded, + 'SCRIPT_NAME':'/' + encoded}) + request.subpath = (decoded, ) response = self._callFUT(request, 'app') self.assertTrue(request.copied) self.assertEqual(response, 'app') - self.assertEqual(request.environ['SCRIPT_NAME'], '/' + la) - self.assertEqual(request.environ['PATH_INFO'], '/' + la) + self.assertEqual(request.environ['SCRIPT_NAME'], '/' + encoded) + self.assertEqual(request.environ['PATH_INFO'], '/' + encoded) class DummyRequest: def __init__(self, environ=None): diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py index a980185008..eb9b7285d4 100644 --- a/pyramid/tests/test_router.py +++ b/pyramid/tests/test_router.py @@ -177,7 +177,7 @@ def make_response(s): return Response(s) router.registry.registerAdapter(make_response, (str,), IResponse) app_iter = router(environ, start_response) - self.assertEqual(app_iter, ['abc']) + self.assertEqual(app_iter, [b'abc']) self.assertEqual(start_response.status, '200 OK') self.assertEqual(environ['handled'], ['two', 'one']) @@ -188,8 +188,8 @@ def test_call_traverser_default(self): router = self._makeOne() start_response = DummyStartResponse() why = exc_raised(HTTPNotFound, router, environ, start_response) - self.assertTrue('/' in why[0], why) - self.assertFalse('debug_notfound' in why[0]) + self.assertTrue('/' in why.args[0], why) + self.assertFalse('debug_notfound' in why.args[0]) self.assertEqual(len(logger.messages), 0) def test_traverser_raises_notfound_class(self): @@ -209,7 +209,7 @@ def test_traverser_raises_notfound_instance(self): router = self._makeOne() start_response = DummyStartResponse() why = exc_raised(HTTPNotFound, router, environ, start_response) - self.assertTrue('foo' in why[0], why) + self.assertTrue('foo' in why.args[0], why) def test_traverser_raises_forbidden_class(self): from pyramid.httpexceptions import HTTPForbidden @@ -229,7 +229,7 @@ def test_traverser_raises_forbidden_instance(self): router = self._makeOne() start_response = DummyStartResponse() why = exc_raised(HTTPForbidden, router, environ, start_response) - self.assertTrue('foo' in why[0], why) + self.assertTrue('foo' in why.args[0], why) def test_call_no_view_registered_no_isettings(self): from pyramid.httpexceptions import HTTPNotFound @@ -240,8 +240,8 @@ def test_call_no_view_registered_no_isettings(self): router = self._makeOne() start_response = DummyStartResponse() why = exc_raised(HTTPNotFound, router, environ, start_response) - self.assertTrue('/' in why[0], why) - self.assertFalse('debug_notfound' in why[0]) + self.assertTrue('/' in why.args[0], why) + self.assertFalse('debug_notfound' in why.args[0]) self.assertEqual(len(logger.messages), 0) def test_call_no_view_registered_debug_notfound_false(self): @@ -254,8 +254,8 @@ def test_call_no_view_registered_debug_notfound_false(self): router = self._makeOne() start_response = DummyStartResponse() why = exc_raised(HTTPNotFound, router, environ, start_response) - self.assertTrue('/' in why[0], why) - self.assertFalse('debug_notfound' in why[0]) + self.assertTrue('/' in why.args[0], why) + self.assertFalse('debug_notfound' in why.args[0]) self.assertEqual(len(logger.messages), 0) def test_call_no_view_registered_debug_notfound_true(self): @@ -269,15 +269,15 @@ def test_call_no_view_registered_debug_notfound_true(self): start_response = DummyStartResponse() why = exc_raised(HTTPNotFound, router, environ, start_response) self.assertTrue( - "debug_notfound of url http://localhost:8080/; " in why[0]) - self.assertTrue("view_name: '', subpath: []" in why[0]) - self.assertTrue('http://localhost:8080' in why[0], why) + "debug_notfound of url http://localhost:8080/; " in why.args[0]) + self.assertTrue("view_name: '', subpath: []" in why.args[0]) + self.assertTrue('http://localhost:8080' in why.args[0], why) self.assertEqual(len(logger.messages), 1) message = logger.messages[0] self.assertTrue('of url http://localhost:8080' in message) self.assertTrue("path_info: " in message) - self.assertTrue('DummyContext instance at' in message) + self.assertTrue('DummyContext' in message) self.assertTrue("view_name: ''" in message) self.assertTrue("subpath: []" in message) @@ -309,7 +309,7 @@ def make_response(s): return Response(s) router.registry.registerAdapter(make_response, (str,), IResponse) app_iter = router(environ, start_response) - self.assertEqual(app_iter, ['abc']) + self.assertEqual(app_iter, [b'abc']) self.assertEqual(start_response.status, '200 OK') def test_call_view_registered_nonspecific_default_path(self): @@ -427,7 +427,7 @@ class IContext(Interface): router = self._makeOne() start_response = DummyStartResponse() why = exc_raised(HTTPForbidden, router, environ, start_response) - self.assertEqual(why[0], 'unauthorized') + self.assertEqual(why.args[0], 'unauthorized') def test_call_view_raises_notfound(self): from zope.interface import Interface @@ -447,7 +447,7 @@ class IContext(Interface): router = self._makeOne() start_response = DummyStartResponse() why = exc_raised(HTTPNotFound, router, environ, start_response) - self.assertEqual(why[0], 'notfound') + self.assertEqual(why.args[0], 'notfound') def test_call_view_raises_response_cleared(self): from zope.interface import Interface @@ -465,7 +465,7 @@ def view(context, request): raise KeyError def exc_view(context, request): self.assertFalse(hasattr(request.response, 'a')) - request.response.body = 'OK' + request.response.body = b'OK' return request.response environ = self._makeEnviron() self._registerView(view, '', IViewClassifier, IRequest, IContext) @@ -474,7 +474,7 @@ def exc_view(context, request): router = self._makeOne() start_response = DummyStartResponse() itera = router(environ, start_response) - self.assertEqual(itera, ['OK']) + self.assertEqual(itera, [b'OK']) def test_call_request_has_response_callbacks(self): from zope.interface import Interface @@ -650,7 +650,6 @@ def factory(request): self.assertEqual(environ['bfg.routes.route'].name, 'foo') self.assertEqual(request.matchdict, matchdict) self.assertEqual(request.matched_route.name, 'foo') - self.assertEqual(len(logger.messages), 1) self.assertTrue( logger.messages[0].startswith( @@ -735,7 +734,7 @@ class IContext(Interface): router = self._makeOne() start_response = DummyStartResponse() why = exc_raised(HTTPNotFound, router, environ, start_response) - self.assertTrue('from root factory' in why[0]) + self.assertTrue('from root factory' in why.args[0]) def test_root_factory_raises_forbidden(self): from pyramid.interfaces import IRootFactory @@ -753,7 +752,7 @@ class IContext(Interface): router = self._makeOne() start_response = DummyStartResponse() why = exc_raised(HTTPForbidden, router, environ, start_response) - self.assertTrue('from root factory' in why[0]) + self.assertTrue('from root factory' in why.args[0]) def test_root_factory_exception_propagating(self): from pyramid.interfaces import IRootFactory @@ -1162,10 +1161,10 @@ def __call__(self, status, headers): self.headers = headers from pyramid.interfaces import IResponse -from zope.interface import implements +from zope.interface import implementer +@implementer(IResponse) class DummyResponse(object): - implements(IResponse) headerlist = () app_iter = () environ = None @@ -1202,7 +1201,7 @@ def info(self, msg): def exc_raised(exc, func, *arg, **kw): try: func(*arg, **kw) - except exc, e: + except exc as e: return e else: raise AssertionError('%s not raised' % exc) # pragma: no cover diff --git a/pyramid/tests/test_scaffolds.py b/pyramid/tests/test_scaffolds.py index ed2c5a9936..265e20c3b5 100644 --- a/pyramid/tests/test_scaffolds.py +++ b/pyramid/tests/test_scaffolds.py @@ -1,5 +1,7 @@ import unittest +from pyramid.testing import skip_on +@skip_on('py3') class TestPyramidTemplate(unittest.TestCase): def _getTargetClass(self): from pyramid.scaffolds import PyramidTemplate diff --git a/pyramid/tests/test_session.py b/pyramid/tests/test_session.py index 5c6454a385..6d75c7950e 100644 --- a/pyramid/tests/test_session.py +++ b/pyramid/tests/test_session.py @@ -263,21 +263,15 @@ def test_cookie_is_set(self): self.assertEqual(session.response, response) def serialize(data, secret): - try: - from hashlib import sha1 - except ImportError: # pragma: no cover - import sha as sha1 - - try: - import cPickle as pickle - except ImportError: # pragma: no cover - import pickle - import hmac import base64 - pickled = pickle.dumps('123', pickle.HIGHEST_PROTOCOL) - sig = hmac.new(secret, pickled, sha1).hexdigest() - return sig + base64.standard_b64encode(pickled) + from hashlib import sha1 + from pyramid.compat import bytes_ + from pyramid.compat import native_ + from pyramid.compat import pickle + pickled = pickle.dumps(data, pickle.HIGHEST_PROTOCOL) + sig = hmac.new(bytes_(secret), pickled, sha1).hexdigest() + return sig + native_(base64.b64encode(pickled)) class Test_signed_serialize(unittest.TestCase): def _callFUT(self, data, secret): diff --git a/pyramid/tests/test_settings.py b/pyramid/tests/test_settings.py index 5cc4ce5619..d02b3cd3e8 100644 --- a/pyramid/tests/test_settings.py +++ b/pyramid/tests/test_settings.py @@ -87,7 +87,7 @@ def _callFUT(self, val): def test_with_list(self): result = self._callFUT(['abc', 'def']) - self.assertEqual(result, ['abc', 'def']) + self.assertEqual(list(result), ['abc', 'def']) def test_with_string(self): result = self._callFUT('abc def') diff --git a/pyramid/tests/test_static.py b/pyramid/tests/test_static.py index 6dc38fc570..427b03c803 100644 --- a/pyramid/tests/test_static.py +++ b/pyramid/tests/test_static.py @@ -37,14 +37,14 @@ def test_call_adds_slash_path_info_empty(self): response = inst(context, request) response.prepare(request.environ) self.assertEqual(response.status, '301 Moved Permanently') - self.assertTrue('http://example.com:6543/' in response.body) + self.assertTrue(b'http://example.com:6543/' in response.body) def test_path_info_slash_means_index_html(self): inst = self._makeOne('pyramid.tests:fixtures/static') request = self._makeRequest() context = DummyContext() response = inst(context, request) - self.assertTrue('static' in response.body) + self.assertTrue(b'static' in response.body) def test_oob_singledot(self): inst = self._makeOne('pyramid.tests:fixtures/static') @@ -52,7 +52,7 @@ def test_oob_singledot(self): context = DummyContext() response = inst(context, request) self.assertEqual(response.status, '200 OK') - self.assertTrue('static' in response.body) + self.assertTrue(b'static' in response.body) def test_oob_emptyelement(self): inst = self._makeOne('pyramid.tests:fixtures/static') @@ -60,7 +60,7 @@ def test_oob_emptyelement(self): context = DummyContext() response = inst(context, request) self.assertEqual(response.status, '200 OK') - self.assertTrue('static' in response.body) + self.assertTrue(b'static' in response.body) def test_oob_dotdotslash(self): inst = self._makeOne('pyramid.tests:fixtures/static') @@ -99,21 +99,21 @@ def test_resource_isdir(self): request = self._makeRequest({'PATH_INFO':'/subdir/'}) context = DummyContext() response = inst(context, request) - self.assertTrue('subdir' in response.body) + self.assertTrue(b'subdir' in response.body) def test_resource_is_file(self): inst = self._makeOne('pyramid.tests:fixtures/static') request = self._makeRequest({'PATH_INFO':'/index.html'}) context = DummyContext() response = inst(context, request) - self.assertTrue('static' in response.body) + self.assertTrue(b'static' in response.body) def test_resource_is_file_with_cache_max_age(self): inst = self._makeOne('pyramid.tests:fixtures/static', cache_max_age=600) request = self._makeRequest({'PATH_INFO':'/index.html'}) context = DummyContext() response = inst(context, request) - self.assertTrue('static' in response.body) + self.assertTrue(b'static' in response.body) self.assertEqual(len(response.headerlist), 5) header_names = [ x[0] for x in response.headerlist ] header_names.sort() @@ -127,7 +127,7 @@ def test_resource_is_file_with_no_cache_max_age(self): request = self._makeRequest({'PATH_INFO':'/index.html'}) context = DummyContext() response = inst(context, request) - self.assertTrue('static' in response.body) + self.assertTrue(b'static' in response.body) self.assertEqual(len(response.headerlist), 3) header_names = [ x[0] for x in response.headerlist ] header_names.sort() @@ -192,7 +192,7 @@ def test_call_adds_slash_path_info_empty(self): response = inst(context, request) response.prepare(request.environ) self.assertEqual(response.status, '301 Moved Permanently') - self.assertTrue('http://example.com:6543/' in response.body) + self.assertTrue(b'http://example.com:6543/' in response.body) def test_path_info_slash_means_index_html(self): inst = self._makeOne('pyramid.tests:fixtures/static') @@ -200,7 +200,7 @@ def test_path_info_slash_means_index_html(self): request.subpath = () context = DummyContext() response = inst(context, request) - self.assertTrue('static' in response.body) + self.assertTrue(b'static' in response.body) def test_oob_singledot(self): inst = self._makeOne('pyramid.tests:fixtures/static') @@ -258,7 +258,7 @@ def test_resource_isdir(self): request.subpath = ('subdir',) context = DummyContext() response = inst(context, request) - self.assertTrue('subdir' in response.body) + self.assertTrue(b'subdir' in response.body) def test_resource_is_file(self): inst = self._makeOne('pyramid.tests:fixtures/static') @@ -266,7 +266,7 @@ def test_resource_is_file(self): request.subpath = ('index.html',) context = DummyContext() response = inst(context, request) - self.assertTrue('static' in response.body) + self.assertTrue(b'static' in response.body) def test_resource_is_file_with_cache_max_age(self): inst = self._makeOne('pyramid.tests:fixtures/static', cache_max_age=600) @@ -274,7 +274,7 @@ def test_resource_is_file_with_cache_max_age(self): request.subpath = ('index.html',) context = DummyContext() response = inst(context, request) - self.assertTrue('static' in response.body) + self.assertTrue(b'static' in response.body) self.assertEqual(len(response.headerlist), 5) header_names = [ x[0] for x in response.headerlist ] header_names.sort() @@ -289,7 +289,7 @@ def test_resource_is_file_with_no_cache_max_age(self): request.subpath = ('index.html',) context = DummyContext() response = inst(context, request) - self.assertTrue('static' in response.body) + self.assertTrue(b'static' in response.body) self.assertEqual(len(response.headerlist), 3) header_names = [ x[0] for x in response.headerlist ] header_names.sort() diff --git a/pyramid/tests/test_testing.py b/pyramid/tests/test_testing.py index 41a4788ec2..05ef36fe9f 100644 --- a/pyramid/tests/test_testing.py +++ b/pyramid/tests/test_testing.py @@ -1,5 +1,5 @@ - import unittest +from pyramid.compat import text_ class TestBase(unittest.TestCase): def setUp(self): @@ -35,8 +35,10 @@ def test_registerDummySecurityPolicy(self): class Test_registerResources(TestBase): def test_it(self): - ob1 = object() - ob2 = object() + class Dummy: + pass + ob1 = Dummy() + ob2 = Dummy() resources = {'/ob1':ob1, '/ob2':ob2} from pyramid import testing testing.registerResources(resources) @@ -46,14 +48,14 @@ def test_it(self): self.assertEqual(result['context'], ob1) self.assertEqual(result['view_name'], '') self.assertEqual(result['subpath'], ()) - self.assertEqual(result['traversed'], (u'ob1',)) + self.assertEqual(result['traversed'], (text_('ob1'),)) self.assertEqual(result['virtual_root'], ob1) self.assertEqual(result['virtual_root_path'], ()) result = adapter(DummyRequest({'PATH_INFO':'/ob2'})) self.assertEqual(result['context'], ob2) self.assertEqual(result['view_name'], '') self.assertEqual(result['subpath'], ()) - self.assertEqual(result['traversed'], (u'ob2',)) + self.assertEqual(result['traversed'], (text_('ob2'),)) self.assertEqual(result['virtual_root'], ob2) self.assertEqual(result['virtual_root_path'], ()) self.assertRaises(KeyError, adapter, DummyRequest({'PATH_INFO':'/ob3'})) @@ -132,7 +134,7 @@ def test_registerView_withresult(self): request = DummyRequest() request.registry = self.registry response = render_view_to_response(None, request, 'moo.html') - self.assertEqual(response.body, 'yo') + self.assertEqual(response.body, b'yo') def test_registerView_custom(self): from pyramid import testing @@ -146,7 +148,7 @@ def view(context, request): request = DummyRequest() request.registry = self.registry response = render_view_to_response(None, request, 'moo.html') - self.assertEqual(response.body, '123') + self.assertEqual(response.body, b'123') def test_registerView_with_permission_denying(self): from pyramid import testing @@ -188,7 +190,7 @@ def view(context, request): request = DummyRequest() request.registry = self.registry result = render_view_to_response(None, request, 'moo.html') - self.assertEqual(result.app_iter, ['123']) + self.assertEqual(result.app_iter, [b'123']) class Test_registerAdapter(TestBase): @@ -222,12 +224,12 @@ class for_(Interface): class Test_registerUtility(TestBase): def test_registerUtility(self): - from zope.interface import implements + from zope.interface import implementer from zope.interface import Interface class iface(Interface): pass + @implementer(iface) class impl: - implements(iface) def __call__(self): return 'foo' utility = impl() @@ -380,9 +382,10 @@ class Dummy: resource = self._makeOne() resource['abc'] = Dummy() resource['def'] = Dummy() - self.assertEqual(resource.values(), resource.subs.values()) - self.assertEqual(resource.items(), resource.subs.items()) - self.assertEqual(resource.keys(), resource.subs.keys()) + L = list + self.assertEqual(L(resource.values()), L(resource.subs.values())) + self.assertEqual(L(resource.items()), L(resource.subs.items())) + self.assertEqual(L(resource.keys()), L(resource.subs.keys())) self.assertEqual(len(resource), 2) def test_nonzero(self): @@ -586,87 +589,73 @@ def _callFUT(self, **kw): from pyramid.testing import setUp return setUp(**kw) + def tearDown(self): + from pyramid.threadlocal import manager + manager.clear() + getSiteManager = self._getSM() + if getSiteManager is not None: + getSiteManager.reset() + + def _getSM(self): + try: + from zope.component import getSiteManager + except ImportError: # pragma: no cover + getSiteManager = None + return getSiteManager + + def _assertSMHook(self, hook): + getSiteManager = self._getSM() + if getSiteManager is not None: + result = getSiteManager.sethook(None) + self.assertEqual(result, hook) + def test_it_defaults(self): from pyramid.threadlocal import manager from pyramid.threadlocal import get_current_registry from pyramid.registry import Registry - from zope.component import getSiteManager old = True manager.push(old) - try: - config = self._callFUT() - current = manager.get() - self.assertFalse(current is old) - self.assertEqual(config.registry, current['registry']) - self.assertEqual(current['registry'].__class__, Registry) - self.assertEqual(current['request'], None) - finally: - result = getSiteManager.sethook(None) - self.assertEqual(result, get_current_registry) - getSiteManager.reset() - manager.clear() + config = self._callFUT() + current = manager.get() + self.assertFalse(current is old) + self.assertEqual(config.registry, current['registry']) + self.assertEqual(current['registry'].__class__, Registry) + self.assertEqual(current['request'], None) + self._assertSMHook(get_current_registry) def test_it_with_registry(self): from pyramid.registry import Registry - from zope.component import getSiteManager from pyramid.threadlocal import manager registry = Registry() - try: - self._callFUT(registry=registry) - current = manager.get() - self.assertEqual(current['registry'], registry) - finally: - getSiteManager.reset() - manager.clear() + self._callFUT(registry=registry) + current = manager.get() + self.assertEqual(current['registry'], registry) def test_it_with_request(self): - from zope.component import getSiteManager from pyramid.threadlocal import manager request = object() - try: - self._callFUT(request=request) - current = manager.get() - self.assertEqual(current['request'], request) - finally: - getSiteManager.reset() - manager.clear() + self._callFUT(request=request) + current = manager.get() + self.assertEqual(current['request'], request) def test_it_with_hook_zca_false(self): - from zope.component import getSiteManager - from pyramid.threadlocal import manager from pyramid.registry import Registry registry = Registry() - try: - self._callFUT(registry=registry, hook_zca=False) + self._callFUT(registry=registry, hook_zca=False) + getSiteManager = self._getSM() + if getSiteManager is not None: sm = getSiteManager() self.assertFalse(sm is registry) - finally: - getSiteManager.reset() - manager.clear() def test_it_with_settings_passed_explicit_registry(self): - from zope.component import getSiteManager - from pyramid.threadlocal import manager from pyramid.registry import Registry registry = Registry() - try: - self._callFUT(registry=registry, hook_zca=False, - settings=dict(a=1)) - self.assertEqual(registry.settings['a'], 1) - finally: - getSiteManager.reset() - manager.clear() + self._callFUT(registry=registry, hook_zca=False, settings=dict(a=1)) + self.assertEqual(registry.settings['a'], 1) def test_it_with_settings_passed_implicit_registry(self): - from zope.component import getSiteManager - from pyramid.threadlocal import manager - try: - config = self._callFUT(hook_zca=False, - settings=dict(a=1)) - self.assertEqual(config.registry.settings['a'], 1) - finally: - getSiteManager.reset() - manager.clear() + config = self._callFUT(hook_zca=False, settings=dict(a=1)) + self.assertEqual(config.registry.settings['a'], 1) class Test_cleanUp(Test_setUp): def _callFUT(self, *arg, **kw): @@ -678,24 +667,48 @@ def _callFUT(self, **kw): from pyramid.testing import tearDown return tearDown(**kw) + def tearDown(self): + from pyramid.threadlocal import manager + manager.clear() + getSiteManager = self._getSM() + if getSiteManager is not None: + getSiteManager.reset() + + def _getSM(self): + try: + from zope.component import getSiteManager + except ImportError: # pragma: no cover + getSiteManager = None + return getSiteManager + + def _assertSMHook(self, hook): + getSiteManager = self._getSM() + if getSiteManager is not None: + result = getSiteManager.sethook(None) + self.assertEqual(result, hook) + + def _setSMHook(self, hook): + getSiteManager = self._getSM() + if getSiteManager is not None: + getSiteManager.sethook(hook) + def test_defaults(self): from pyramid.threadlocal import manager - from zope.component import getSiteManager registry = DummyRegistry() old = {'registry':registry} hook = lambda *arg: None try: - getSiteManager.sethook(hook) + self._setSMHook(hook) manager.push(old) self._callFUT() current = manager.get() self.assertNotEqual(current, old) self.assertEqual(registry.inited, 2) finally: - result = getSiteManager.sethook(None) - self.assertNotEqual(result, hook) - getSiteManager.reset() - manager.clear() + getSiteManager = self._getSM() + if getSiteManager is not None: + result = getSiteManager.sethook(None) + self.assertNotEqual(result, hook) def test_registry_cannot_be_inited(self): from pyramid.threadlocal import manager @@ -714,17 +727,12 @@ def raiseit(name): manager.clear() def test_unhook_zc_false(self): - from pyramid.threadlocal import manager - from zope.component import getSiteManager hook = lambda *arg: None try: - getSiteManager.sethook(hook) + self._setSMHook(hook) self._callFUT(unhook_zca=False) finally: - result = getSiteManager.sethook(None) - self.assertEqual(result, hook) - getSiteManager.reset() - manager.clear() + self._assertSMHook(hook) class TestDummyRendererFactory(unittest.TestCase): def _makeOne(self, name, factory): @@ -888,13 +896,15 @@ def test_get_csrf_token(self): from zope.interface import Interface -from zope.interface import implements +from zope.interface import implementer class IDummy(Interface): pass +@implementer(IDummy) class DummyEvent: - implements(IDummy) + pass + class DummyRequest: application_url = 'http://example.com' diff --git a/pyramid/tests/test_traversal.py b/pyramid/tests/test_traversal.py index 95caf21be2..72192b23b1 100644 --- a/pyramid/tests/test_traversal.py +++ b/pyramid/tests/test_traversal.py @@ -1,69 +1,91 @@ import unittest from pyramid.testing import cleanUp +from pyramid.compat import text_ +from pyramid.compat import native_ +from pyramid.compat import text_type +from pyramid.compat import url_quote class TraversalPathTests(unittest.TestCase): def _callFUT(self, path): from pyramid.traversal import traversal_path return traversal_path(path) + def test_utf8(self): + la = b'La Pe\xc3\xb1a' + encoded = url_quote(la) + decoded = text_(la, 'utf-8') + path = '/'.join([encoded, encoded]) + result = self._callFUT(path) + self.assertEqual(result, (decoded, decoded)) + + def test_utf16(self): + from pyramid.exceptions import URLDecodeError + la = text_(b'La Pe\xc3\xb1a', 'utf-8').encode('utf-16') + encoded = url_quote(la) + path = '/'.join([encoded, encoded]) + self.assertRaises(URLDecodeError, self._callFUT, path) + + def test_unicode_highorder_chars(self): + path = text_('/%E6%B5%81%E8%A1%8C%E8%B6%8B%E5%8A%BF') + self.assertEqual(self._callFUT(path), + (text_('\u6d41\u884c\u8d8b\u52bf', 'unicode_escape'),)) + + def test_element_urllquoted(self): + self.assertEqual(self._callFUT('/foo/space%20thing/bar'), + (text_('foo'), text_('space thing'), text_('bar'))) + + def test_unicode_undecodeable_to_ascii(self): + path = text_(b'/La Pe\xc3\xb1a', 'utf-8') + self.assertRaises(UnicodeEncodeError, self._callFUT, path) + +class TraversalPathInfoTests(unittest.TestCase): + def _callFUT(self, path): + from pyramid.traversal import traversal_path_info + return traversal_path_info(path) + def test_path_startswith_endswith(self): - self.assertEqual(self._callFUT('/foo/'), (u'foo',)) + self.assertEqual(self._callFUT('/foo/'), (text_('foo'),)) def test_empty_elements(self): - self.assertEqual(self._callFUT('foo///'), (u'foo',)) + self.assertEqual(self._callFUT('foo///'), (text_('foo'),)) def test_onedot(self): - self.assertEqual(self._callFUT('foo/./bar'), (u'foo', u'bar')) + self.assertEqual(self._callFUT('foo/./bar'), + (text_('foo'), text_('bar'))) def test_twodots(self): - self.assertEqual(self._callFUT('foo/../bar'), (u'bar',)) + self.assertEqual(self._callFUT('foo/../bar'), (text_('bar'),)) def test_twodots_at_start(self): - self.assertEqual(self._callFUT('../../bar'), (u'bar',)) - - def test_element_urllquoted(self): - self.assertEqual(self._callFUT('/foo/space%20thing/bar'), - (u'foo', u'space thing', u'bar')) + self.assertEqual(self._callFUT('../../bar'), (text_('bar'),)) def test_segments_are_unicode(self): result = self._callFUT('/foo/bar') - self.assertEqual(type(result[0]), unicode) - self.assertEqual(type(result[1]), unicode) + self.assertEqual(type(result[0]), text_type) + self.assertEqual(type(result[1]), text_type) def test_same_value_returned_if_cached(self): result1 = self._callFUT('/foo/bar') result2 = self._callFUT('/foo/bar') - self.assertEqual(result1, (u'foo', u'bar')) - self.assertEqual(result2, (u'foo', u'bar')) - - def test_utf8(self): - import urllib - la = 'La Pe\xc3\xb1a' - encoded = urllib.quote(la) - decoded = unicode(la, 'utf-8') - path = '/'.join([encoded, encoded]) - self.assertEqual(self._callFUT(path), (decoded, decoded)) - - def test_utf16(self): - from pyramid.exceptions import URLDecodeError - import urllib - la = unicode('La Pe\xc3\xb1a', 'utf-8').encode('utf-16') - encoded = urllib.quote(la) - path = '/'.join([encoded, encoded]) - self.assertRaises(URLDecodeError, self._callFUT, path) - - def test_unicode_highorder_chars(self): - path = u'/%E6%B5%81%E8%A1%8C%E8%B6%8B%E5%8A%BF' - self.assertEqual(self._callFUT(path), (u'\u6d41\u884c\u8d8b\u52bf',)) + self.assertEqual(result1, (text_('foo'), text_('bar'))) + self.assertEqual(result2, (text_('foo'), text_('bar'))) def test_unicode_simple(self): - path = u'/abc' - self.assertEqual(self._callFUT(path), (u'abc',)) + path = text_('/abc') + self.assertEqual(self._callFUT(path), (text_('abc'),)) - def test_unicode_undecodeable_to_ascii(self): - path = unicode('/La Pe\xc3\xb1a', 'utf-8') - self.assertRaises(UnicodeEncodeError, self._callFUT, path) + def test_highorder(self): + la = b'La Pe\xc3\xb1a' + latin1 = native_(la) + result = self._callFUT(latin1) + self.assertEqual(result, (text_(la, 'utf-8'),)) + + def test_highorder_undecodeable(self): + from pyramid.exceptions import URLDecodeError + la = text_(b'La Pe\xc3\xb1a', 'utf-8') + notlatin1 = native_(la) + self.assertRaises(URLDecodeError, self._callFUT, notlatin1) class ResourceTreeTraverserTests(unittest.TestCase): def setUp(self): @@ -146,7 +168,7 @@ def test_call_withconn_getitem_withpath_nosubpath(self): self.assertEqual(result['context'], foo) self.assertEqual(result['view_name'], 'bar') self.assertEqual(result['subpath'], ()) - self.assertEqual(result['traversed'], (u'foo',)) + self.assertEqual(result['traversed'], (text_('foo'),)) self.assertEqual(result['root'], root) self.assertEqual(result['virtual_root'], root) self.assertEqual(result['virtual_root_path'], ()) @@ -161,7 +183,7 @@ def test_call_withconn_getitem_withpath_withsubpath(self): self.assertEqual(result['context'], foo) self.assertEqual(result['view_name'], 'bar') self.assertEqual(result['subpath'], ('baz', 'buz')) - self.assertEqual(result['traversed'], (u'foo',)) + self.assertEqual(result['traversed'], (text_('foo'),)) self.assertEqual(result['root'], root) self.assertEqual(result['virtual_root'], root) self.assertEqual(result['virtual_root_path'], ()) @@ -194,10 +216,12 @@ def test_call_with_vh_root(self): self.assertEqual(result['context'], baz) self.assertEqual(result['view_name'], '') self.assertEqual(result['subpath'], ()) - self.assertEqual(result['traversed'], (u'foo', u'bar', u'baz')) + self.assertEqual(result['traversed'], + (text_('foo'), text_('bar'), text_('baz'))) self.assertEqual(result['root'], root) self.assertEqual(result['virtual_root'], bar) - self.assertEqual(result['virtual_root_path'], (u'foo', u'bar')) + self.assertEqual(result['virtual_root_path'], + (text_('foo'), text_('bar'))) def test_call_with_vh_root2(self): environ = self._getEnviron(PATH_INFO='/bar/baz', @@ -212,10 +236,11 @@ def test_call_with_vh_root2(self): self.assertEqual(result['context'], baz) self.assertEqual(result['view_name'], '') self.assertEqual(result['subpath'], ()) - self.assertEqual(result['traversed'], (u'foo', u'bar', u'baz')) + self.assertEqual(result['traversed'], + (text_('foo'), text_('bar'), text_('baz'))) self.assertEqual(result['root'], root) self.assertEqual(result['virtual_root'], foo) - self.assertEqual(result['virtual_root_path'], (u'foo',)) + self.assertEqual(result['virtual_root_path'], (text_('foo'),)) def test_call_with_vh_root3(self): environ = self._getEnviron(PATH_INFO='/foo/bar/baz', @@ -230,7 +255,8 @@ def test_call_with_vh_root3(self): self.assertEqual(result['context'], baz) self.assertEqual(result['view_name'], '') self.assertEqual(result['subpath'], ()) - self.assertEqual(result['traversed'], (u'foo', u'bar', u'baz')) + self.assertEqual(result['traversed'], + (text_('foo'), text_('bar'), text_('baz'))) self.assertEqual(result['root'], root) self.assertEqual(result['virtual_root'], root) self.assertEqual(result['virtual_root_path'], ()) @@ -248,10 +274,12 @@ def test_call_with_vh_root4(self): self.assertEqual(result['context'], baz) self.assertEqual(result['view_name'], '') self.assertEqual(result['subpath'], ()) - self.assertEqual(result['traversed'], (u'foo', u'bar', u'baz')) + self.assertEqual(result['traversed'], + (text_('foo'), text_('bar'), text_('baz'))) self.assertEqual(result['root'], root) self.assertEqual(result['virtual_root'], baz) - self.assertEqual(result['virtual_root_path'], (u'foo', u'bar', u'baz')) + self.assertEqual(result['virtual_root_path'], + (text_('foo'), text_('bar'), text_('baz'))) def test_call_with_vh_root_path_root(self): policy = self._makeOne(None) @@ -268,23 +296,23 @@ def test_call_with_vh_root_path_root(self): self.assertEqual(result['virtual_root_path'], ()) def test_non_utf8_path_segment_unicode_path_segments_fails(self): + from pyramid.exceptions import URLDecodeError foo = DummyContext() root = DummyContext(foo) policy = self._makeOne(root) - segment = unicode('LaPe\xc3\xb1a', 'utf-8').encode('utf-16') + segment = native_(text_(b'LaPe\xc3\xb1a', 'utf-8'), 'utf-16') environ = self._getEnviron(PATH_INFO='/%s' % segment) request = DummyRequest(environ) - from pyramid.exceptions import URLDecodeError self.assertRaises(URLDecodeError, policy, request) def test_non_utf8_path_segment_settings_unicode_path_segments_fails(self): + from pyramid.exceptions import URLDecodeError foo = DummyContext() root = DummyContext(foo) policy = self._makeOne(root) - segment = unicode('LaPe\xc3\xb1a', 'utf-8').encode('utf-16') + segment = native_(text_(b'LaPe\xc3\xb1a', 'utf-8'), 'utf-16') environ = self._getEnviron(PATH_INFO='/%s' % segment) request = DummyRequest(environ) - from pyramid.exceptions import URLDecodeError self.assertRaises(URLDecodeError, policy, request) def test_withroute_nothingfancy(self): @@ -592,13 +620,16 @@ def test_absolute_unicode_found(self): unprintable = DummyContext() root = DummyContext(unprintable) unprintable.__parent__ = root - unprintable.__name__ = unicode( - '/\xe6\xb5\x81\xe8\xa1\x8c\xe8\xb6\x8b\xe5\x8a\xbf', 'utf-8') + unprintable.__name__ = text_( + b'/\xe6\xb5\x81\xe8\xa1\x8c\xe8\xb6\x8b\xe5\x8a\xbf', 'utf-8') root.__parent__ = None root.__name__ = None traverser = ResourceTreeTraverser self._registerTraverser(traverser) - result = self._callFUT(root, u'/%E6%B5%81%E8%A1%8C%E8%B6%8B%E5%8A%BF') + result = self._callFUT( + root, + text_(b'/%E6%B5%81%E8%A1%8C%E8%B6%8B%E5%8A%BF') + ) self.assertEqual(result, unprintable) class ResourcePathTests(unittest.TestCase): @@ -744,7 +775,7 @@ def _callFUT(self, s): return quote_path_segment(s) def test_unicode(self): - la = unicode('/La Pe\xc3\xb1a', 'utf-8') + la = text_(b'/La Pe\xc3\xb1a', 'utf-8') result = self._callFUT(la) self.assertEqual(result, '%2FLa%20Pe%C3%B1a') @@ -759,8 +790,9 @@ def test_int(self): self.assertEqual(result, '12345') def test_long(self): + from pyramid.compat import long import sys - s = long(sys.maxint + 1) + s = long(sys.maxsize + 1) result = self._callFUT(s) expected = str(s) self.assertEqual(result, expected) @@ -833,15 +865,16 @@ def test_call_unicode_mixed_with_bytes_in_resource_names(self): root.__name__ = None one = DummyContext() one.__parent__ = root - one.__name__ = unicode('La Pe\xc3\xb1a', 'utf-8') + one.__name__ = text_(b'La Pe\xc3\xb1a', 'utf-8') two = DummyContext() two.__parent__ = one - two.__name__ = 'La Pe\xc3\xb1a' + two.__name__ = b'La Pe\xc3\xb1a' request = DummyRequest() context_url = self._makeOne(two, request) result = context_url() - self.assertEqual(result, - 'http://example.com:5432/La%20Pe%C3%B1a/La%20Pe%C3%B1a/') + self.assertEqual( + result, + 'http://example.com:5432/La%20Pe%C3%B1a/La%20Pe%C3%B1a/') def test_call_with_virtual_root_path(self): from pyramid.interfaces import VH_ROOT_KEY @@ -1036,6 +1069,13 @@ def test_self_string_found(self): self._callFUT(resource, '') self.assertEqual(resource.request.environ['PATH_INFO'], '') + def test_self_unicode_found(self): + resource = DummyContext() + traverser = make_traverser({'context':resource, 'view_name':''}) + self._registerTraverser(traverser) + self._callFUT(resource, text_('')) + self.assertEqual(resource.request.environ['PATH_INFO'], '') + def test_self_tuple_found(self): resource = DummyContext() traverser = make_traverser({'context':resource, 'view_name':''}) @@ -1168,7 +1208,7 @@ def __init__(self, next=None, name=None): def __getitem__(self, name): if self.next is None: - raise KeyError, name + raise KeyError(name) return self.next def __repr__(self): diff --git a/pyramid/tests/test_url.py b/pyramid/tests/test_url.py index 8b95374fba..4c39d8e9ce 100644 --- a/pyramid/tests/test_url.py +++ b/pyramid/tests/test_url.py @@ -2,6 +2,8 @@ from pyramid.testing import setUp from pyramid.testing import tearDown +from pyramid.compat import text_ +from pyramid.compat import native_ class TestURLMethodsMixin(unittest.TestCase): def setUp(self): @@ -48,7 +50,7 @@ def test_resource_url_extra_args(self): def test_resource_url_unicode_in_element_names(self): request = self._makeOne() self._registerContextURL(request.registry) - uc = unicode('La Pe\xc3\xb1a', 'utf-8') + uc = text_(b'La Pe\xc3\xb1a', 'utf-8') context = DummyContext() result = request.resource_url(context, uc) self.assertEqual(result, @@ -73,7 +75,7 @@ def test_resource_url_with_query_dict(self): request = self._makeOne() self._registerContextURL(request.registry) context = DummyContext() - uc = unicode('La Pe\xc3\xb1a', 'utf-8') + uc = text_(b'La Pe\xc3\xb1a', 'utf-8') result = request.resource_url(context, 'a', query={'a':uc}) self.assertEqual(result, 'http://example.com/context/a?a=La+Pe%C3%B1a') @@ -82,9 +84,9 @@ def test_resource_url_with_query_seq(self): request = self._makeOne() self._registerContextURL(request.registry) context = DummyContext() - uc = unicode('La Pe\xc3\xb1a', 'utf-8') + uc = text_(b'La Pe\xc3\xb1a', 'utf-8') result = request.resource_url(context, 'a', query=[('a', 'hi there'), - ('b', uc)]) + ('b', uc)]) self.assertEqual(result, 'http://example.com/context/a?a=hi+there&b=La+Pe%C3%B1a') @@ -117,10 +119,15 @@ def test_resource_url_anchor_is_encoded_utf8_if_unicode(self): request = self._makeOne() self._registerContextURL(request.registry) context = DummyContext() - uc = unicode('La Pe\xc3\xb1a', 'utf-8') + uc = text_(b'La Pe\xc3\xb1a', 'utf-8') result = request.resource_url(context, anchor=uc) - self.assertEqual(result, - 'http://example.com/context/#La Pe\xc3\xb1a') + self.assertEqual( + result, + native_( + text_(b'http://example.com/context/#La Pe\xc3\xb1a', + 'utf-8'), + 'utf-8') + ) def test_resource_url_anchor_is_not_urlencoded(self): request = self._makeOne() @@ -172,28 +179,42 @@ def test_route_url_no_elements(self): mapper = DummyRoutesMapper(route=DummyRoute('/1/2/3')) request.registry.registerUtility(mapper, IRoutesMapper) result = request.route_url('flub', a=1, b=2, c=3, _query={'a':1}, - _anchor=u"foo") + _anchor=text_(b"foo")) self.assertEqual(result, 'http://example.com:5432/1/2/3?a=1#foo') - def test_route_url_with_anchor_string(self): + def test_route_url_with_anchor_binary(self): from pyramid.interfaces import IRoutesMapper request = self._makeOne() mapper = DummyRoutesMapper(route=DummyRoute('/1/2/3')) request.registry.registerUtility(mapper, IRoutesMapper) - result = request.route_url('flub', _anchor="La Pe\xc3\xb1a") - self.assertEqual(result, - 'http://example.com:5432/1/2/3#La Pe\xc3\xb1a') + result = request.route_url('flub', _anchor=b"La Pe\xc3\xb1a") + + self.assertEqual( + result, + native_( + text_( + b'http://example.com:5432/1/2/3#La Pe\xc3\xb1a', + 'utf-8'), + 'utf-8') + ) def test_route_url_with_anchor_unicode(self): from pyramid.interfaces import IRoutesMapper request = self._makeOne() mapper = DummyRoutesMapper(route=DummyRoute('/1/2/3')) request.registry.registerUtility(mapper, IRoutesMapper) - anchor = unicode('La Pe\xc3\xb1a', 'utf-8') + anchor = text_(b'La Pe\xc3\xb1a', 'utf-8') result = request.route_url('flub', _anchor=anchor) - self.assertEqual(result, - 'http://example.com:5432/1/2/3#La Pe\xc3\xb1a') + + self.assertEqual( + result, + native_( + text_( + b'http://example.com:5432/1/2/3#La Pe\xc3\xb1a', + 'utf-8'), + 'utf-8') + ) def test_route_url_with_query(self): from pyramid.interfaces import IRoutesMapper @@ -281,7 +302,7 @@ def test_current_route_url_with_elements_query_and_anchor(self): request.matchdict = {} request.registry.registerUtility(mapper, IRoutesMapper) result = request.current_route_url('extra1', 'extra2', _query={'a':1}, - _anchor=u"foo") + _anchor=text_(b"foo")) self.assertEqual(result, 'http://example.com:5432/1/2/3/extra1/extra2?a=1#foo') @@ -294,7 +315,8 @@ def test_current_route_url_with_route_name(self): request.matchdict = {} request.registry.registerUtility(mapper, IRoutesMapper) result = request.current_route_url('extra1', 'extra2', _query={'a':1}, - _anchor=u"foo", _route_name='bar') + _anchor=text_(b"foo"), + _route_name='bar') self.assertEqual(result, 'http://example.com:5432/1/2/3/extra1/extra2?a=1#foo') @@ -308,7 +330,7 @@ def test_current_route_path(self): request.script_name = '/script_name' request.registry.registerUtility(mapper, IRoutesMapper) result = request.current_route_path('extra1', 'extra2', _query={'a':1}, - _anchor=u"foo") + _anchor=text_(b"foo")) self.assertEqual(result, '/script_name/1/2/3/extra1/extra2?a=1#foo') def test_route_path_with_elements(self): @@ -319,7 +341,7 @@ def test_route_path_with_elements(self): request.script_name = '' result = request.route_path('flub', 'extra1', 'extra2', a=1, b=2, c=3, _query={'a':1}, - _anchor=u"foo") + _anchor=text_(b"foo")) self.assertEqual(result, '/1/2/3/extra1/extra2?a=1#foo') def test_route_path_with_script_name(self): @@ -330,7 +352,7 @@ def test_route_path_with_script_name(self): request.registry.registerUtility(mapper, IRoutesMapper) result = request.route_path('flub', 'extra1', 'extra2', a=1, b=2, c=3, _query={'a':1}, - _anchor=u"foo") + _anchor=text_(b"foo")) self.assertEqual(result, '/foo/1/2/3/extra1/extra2?a=1#foo') def test_static_url_staticurlinfo_notfound(self): diff --git a/pyramid/tests/test_urldispatch.py b/pyramid/tests/test_urldispatch.py index 3c92e87beb..be823b045c 100644 --- a/pyramid/tests/test_urldispatch.py +++ b/pyramid/tests/test_urldispatch.py @@ -1,5 +1,7 @@ import unittest from pyramid import testing +from pyramid.compat import text_ +from pyramid.compat import native_ class TestRoute(unittest.TestCase): def _getTargetClass(self): @@ -146,7 +148,7 @@ def test___call__route_fails_to_match_with_predicates(self): def test___call__custom_predicate_gets_info(self): mapper = self._makeOne() def pred(info, request): - self.assertEqual(info['match'], {'action':u'action1'}) + self.assertEqual(info['match'], {'action':'action1'}) self.assertEqual(info['route'], mapper.routes['foo']) return True mapper.connect('foo', 'archives/:action/article1', predicates=[pred]) @@ -269,7 +271,7 @@ def test_with_star(self): 'traverse':('everything', 'else', 'here')}) self.assertEqual(matcher('foo/baz/biz/buz/bar'), None) self.assertEqual(generator( - {'baz':1, 'buz':2, 'traverse':u'/a/b'}), '/foo/1/biz/2/bar/a/b') + {'baz':1, 'buz':2, 'traverse':'/a/b'}), '/foo/1/biz/2/bar/a/b') def test_with_bracket_star(self): matcher, generator = self._callFUT( @@ -293,7 +295,8 @@ def test_no_beginning_slash(self): def test_url_decode_error(self): from pyramid.exceptions import URLDecodeError matcher, generator = self._callFUT('/:foo') - self.assertRaises(URLDecodeError, matcher, '/%FF%FE%8B%00') + self.assertRaises(URLDecodeError, matcher, + native_(b'/\xff\xfe\x8b\x00')) def test_custom_regex(self): matcher, generator = self._callFUT('foo/{baz}/biz/{buz:[^/\.]+}.{bar}') @@ -363,10 +366,13 @@ def test_matcher_functional_newstyle(self): self.matches('zzz/{x}*traverse', '/zzz/abc/def/g', {'x':'abc', 'traverse':('def', 'g')}) self.matches('*traverse', '/zzz/abc', {'traverse':('zzz', 'abc')}) - self.matches('*traverse', '/zzz/%20abc', {'traverse':('zzz', ' abc')}) - self.matches('{x}', '/La%20Pe%C3%B1a', {'x':u'La Pe\xf1a'}) - self.matches('*traverse', '/La%20Pe%C3%B1a/x', - {'traverse':(u'La Pe\xf1a', 'x')}) + self.matches('*traverse', '/zzz/ abc', {'traverse':('zzz', ' abc')}) + #'/La%20Pe%C3%B1a' + self.matches('{x}', native_(b'/La Pe\xc3\xb1a'), + {'x':text_(b'La Pe\xf1a')}) + # '/La%20Pe%C3%B1a/x' + self.matches('*traverse', native_(b'/La Pe\xc3\xb1a/x'), + {'traverse':(text_(b'La Pe\xf1a'), 'x')}) self.matches('/foo/{id}.html', '/foo/bar.html', {'id':'bar'}) self.matches('/{num:[0-9]+}/*traverse', '/555/abc/def', {'num':'555', 'traverse':('abc', 'def')}) @@ -386,10 +392,13 @@ def test_matcher_functional_oldstyle(self): self.matches('zzz/:x*traverse', '/zzz/abc/def/g', {'x':'abc', 'traverse':('def', 'g')}) self.matches('*traverse', '/zzz/abc', {'traverse':('zzz', 'abc')}) - self.matches('*traverse', '/zzz/%20abc', {'traverse':('zzz', ' abc')}) - self.matches(':x', '/La%20Pe%C3%B1a', {'x':u'La Pe\xf1a'}) - self.matches('*traverse', '/La%20Pe%C3%B1a/x', - {'traverse':(u'La Pe\xf1a', 'x')}) + self.matches('*traverse', '/zzz/ abc', {'traverse':('zzz', ' abc')}) + #'/La%20Pe%C3%B1a' + self.matches(':x', native_(b'/La Pe\xc3\xb1a'), + {'x':text_(b'La Pe\xf1a')}) + # '/La%20Pe%C3%B1a/x' + self.matches('*traverse', native_(b'/La Pe\xc3\xb1a/x'), + {'traverse':(text_(b'La Pe\xf1a'), 'x')}) self.matches('/foo/:id.html', '/foo/bar.html', {'id':'bar'}) self.matches('/foo/:id_html', '/foo/bar_html', {'id_html':'bar_html'}) self.matches('zzz/:_', '/zzz/abc', {'_':'abc'}) @@ -408,12 +417,12 @@ def test_generator_functional_newstyle(self): '/zzz/abc') self.generates('zzz/{x}*traverse', {'x':'abc', 'traverse':'/def/g'}, '/zzz/abc/def/g') - self.generates('/{x}', {'x':unicode('/La Pe\xc3\xb1a', 'utf-8')}, + self.generates('/{x}', {'x':text_(b'/La Pe\xc3\xb1a', 'utf-8')}, '/%2FLa%20Pe%C3%B1a') - self.generates('/{x}*y', {'x':unicode('/La Pe\xc3\xb1a', 'utf-8'), + self.generates('/{x}*y', {'x':text_(b'/La Pe\xc3\xb1a', 'utf-8'), 'y':'/rest/of/path'}, '/%2FLa%20Pe%C3%B1a/rest/of/path') - self.generates('*traverse', {'traverse':('a', u'La Pe\xf1a')}, + self.generates('*traverse', {'traverse':('a', text_(b'La Pe\xf1a'))}, '/a/La%20Pe%C3%B1a') self.generates('/foo/{id}.html', {'id':'bar'}, '/foo/bar.html') self.generates('/foo/{_}', {'_':'20'}, '/foo/20') @@ -428,12 +437,12 @@ def test_generator_functional_oldstyle(self): '/zzz/abc') self.generates('zzz/:x*traverse', {'x':'abc', 'traverse':'/def/g'}, '/zzz/abc/def/g') - self.generates('/:x', {'x':unicode('/La Pe\xc3\xb1a', 'utf-8')}, + self.generates('/:x', {'x':text_(b'/La Pe\xc3\xb1a', 'utf-8')}, '/%2FLa%20Pe%C3%B1a') - self.generates('/:x*y', {'x':unicode('/La Pe\xc3\xb1a', 'utf-8'), + self.generates('/:x*y', {'x':text_(b'/La Pe\xc3\xb1a', 'utf-8'), 'y':'/rest/of/path'}, '/%2FLa%20Pe%C3%B1a/rest/of/path') - self.generates('*traverse', {'traverse':('a', u'La Pe\xf1a')}, + self.generates('*traverse', {'traverse':('a', text_(b'La Pe\xf1a'))}, '/a/La%20Pe%C3%B1a') self.generates('/foo/:id.html', {'id':'bar'}, '/foo/bar.html') self.generates('/foo/:_', {'_':'20'}, '/foo/20') diff --git a/pyramid/tests/test_util.py b/pyramid/tests/test_util.py index 247b61dad5..2883a968c3 100644 --- a/pyramid/tests/test_util.py +++ b/pyramid/tests/test_util.py @@ -9,7 +9,7 @@ def config_exc(self, func, *arg, **kw): from pyramid.exceptions import ConfigurationError try: func(*arg, **kw) - except ConfigurationError, e: + except ConfigurationError as e: return e else: raise AssertionError('Invalid not raised') # pragma: no cover @@ -190,7 +190,7 @@ def test_add_item(self): reg = Dummy() wos.add(reg) self.assertEqual(list(wos), [reg]) - self.assert_(reg in wos) + self.assertTrue(reg in wos) self.assertEqual(wos.last, reg) def test_add_multiple_items(self): @@ -201,8 +201,8 @@ def test_add_multiple_items(self): wos.add(reg2) self.assertEqual(len(wos), 2) self.assertEqual(list(wos), [reg1, reg2]) - self.assert_(reg1 in wos) - self.assert_(reg2 in wos) + self.assertTrue(reg1 in wos) + self.assertTrue(reg2 in wos) self.assertEqual(wos.last, reg2) def test_add_duplicate_items(self): @@ -212,7 +212,7 @@ def test_add_duplicate_items(self): wos.add(reg) self.assertEqual(len(wos), 1) self.assertEqual(list(wos), [reg]) - self.assert_(reg in wos) + self.assertTrue(reg in wos) self.assertEqual(wos.last, reg) def test_weakref_removal(self): diff --git a/pyramid/tests/test_view.py b/pyramid/tests/test_view.py index 7f66a7563e..29e468cd2c 100644 --- a/pyramid/tests/test_view.py +++ b/pyramid/tests/test_view.py @@ -1,6 +1,8 @@ import unittest import sys +from zope.interface import implementer + from pyramid.testing import setUp from pyramid.testing import tearDown @@ -406,8 +408,9 @@ def foo(): pass def test_call_with_renderer_IRendererInfo(self): import pyramid.tests from pyramid.interfaces import IRendererInfo + @implementer(IRendererInfo) class DummyRendererHelper(object): - implements(IRendererInfo) + pass renderer_helper = DummyRendererHelper() decorator = self._makeOne(renderer=renderer_helper) venusian = DummyVenusian() @@ -588,10 +591,9 @@ def __init__(self, environ=None): self.environ = environ from pyramid.interfaces import IResponse -from zope.interface import implements +@implementer(IResponse) class DummyResponse(object): - implements(IResponse) headerlist = () app_iter = () status = '200 OK' diff --git a/pyramid/traversal.py b/pyramid/traversal.py index bf2606529e..ee6b5fb7ac 100644 --- a/pyramid/traversal.py +++ b/pyramid/traversal.py @@ -1,7 +1,6 @@ -import urllib import warnings -from zope.interface import implements +from zope.interface import implementer from zope.interface.interfaces import IInterface from repoze.lru import lru_cache @@ -11,11 +10,22 @@ from pyramid.interfaces import ITraverser from pyramid.interfaces import VH_ROOT_KEY +from pyramid.compat import PY3 +from pyramid.compat import native_ +from pyramid.compat import text_ +from pyramid.compat import bytes_ +from pyramid.compat import ascii_native_ +from pyramid.compat import text_type +from pyramid.compat import binary_type +from pyramid.compat import url_unquote_native +from pyramid.compat import is_nonstr_iter from pyramid.encode import url_quote from pyramid.exceptions import URLDecodeError from pyramid.location import lineage from pyramid.threadlocal import get_current_registry +empty = text_('') + def find_root(resource): """ Find the root node in the resource tree to which ``resource`` belongs. Note that ``resource`` should be :term:`location`-aware. @@ -58,6 +68,10 @@ def find_resource(resource, path): :func:`pyramid.traversal.resource_path` function generates strings which follow these rules (albeit only absolute ones). + Rules for passing *text* (Unicode) as the ``path`` argument are the same + as those for a string. In particular, the text may not have any nonascii + characters in it. + Rules for passing a *tuple* as the ``path`` argument: if the first element in the path tuple is the empty string (for example ``('', 'a', 'b', 'c')``, the path is considered absolute and the resource tree @@ -77,6 +91,8 @@ def find_resource(resource, path): be imported as :func:`pyramid.traversal.find_model`, although doing so will emit a deprecation warning. """ + if isinstance(path, text_type): + path = ascii_native_(path) D = traverse(resource, path) view_name = D['view_name'] context = D['context'] @@ -276,7 +292,7 @@ def traverse(resource, path): and will be URL-decoded. """ - if hasattr(path, '__iter__'): + if is_nonstr_iter(path): # the traverser factory expects PATH_INFO to be a string, not # unicode and it expects path segments to be utf-8 and # urlencoded (it's the same traverser which accepts PATH_INFO @@ -294,8 +310,7 @@ def traverse(resource, path): # step rather than later down the line as the result of calling # ``traversal_path``). - if isinstance(path, unicode): - path = path.encode('ascii') + path = ascii_native_(path) if path and path[0] == '/': resource = find_root(resource) @@ -407,26 +422,35 @@ def virtual_root(resource, request): urlgenerator = TraversalContextURL(resource, request) return urlgenerator.virtual_root() -@lru_cache(1000) def traversal_path(path): - """ Given a ``PATH_INFO`` string (slash-separated path segments), - return a tuple representing that path which can be used to - traverse a resource tree. - - The ``PATH_INFO`` is split on slashes, creating a list of - segments. Each segment is URL-unquoted, and subsequently decoded - into Unicode. Each segment is assumed to be encoded using the - UTF-8 encoding (or a subset, such as ASCII); a - :exc:`pyramid.exceptions.URLDecodeError` is raised if a segment - cannot be decoded. If a segment name is empty or if it is ``.``, - it is ignored. If a segment name is ``..``, the previous segment - is deleted, and the ``..`` is ignored. + """ Variant of :func:`pyramid.traversal.traversal_path_info` suitable for + decoding paths that are URL-encoded.""" + path = ascii_native_(path) + path = url_unquote_native(path, 'latin-1', 'strict') + return traversal_path_info(path) - If this function is passed a Unicode object instead of a string, - that Unicode object *must* directly encodeable to ASCII. For - example, u'/foo' will work but u'/' (a - Unicode object with characters that cannot be encoded to ascii) - will not. +@lru_cache(1000) +def traversal_path_info(path): + """ Given a ``PATH_INFO`` environ value (slash-separated path segments), + return a tuple representing that path which can be used to traverse a + resource tree. + + ``PATH_INFO`` is assumed to already be URL-decoded. It is encoded to + bytes using the Latin-1 encoding; the resulting set of bytes is + subsequently decoded to text using the UTF-8 encoding; a + :exc:`pyramid.exc.URLDecodeError` is raised if a the URL cannot be + decoded. + + The ``PATH_INFO`` is split on slashes, creating a list of segments. Each + segment subsequently decoded into Unicode. If a segment name is empty or + if it is ``.``, it is ignored. If a segment name is ``..``, the previous + segment is deleted, and the ``..`` is ignored. + + If this function is passed a Unicode object instead of a string, that + Unicode object *must* directly encodeable to ASCII. For example, u'/foo' + will work but u'/' (a Unicode object with characters + that cannot be encoded to ascii) will not. A :exc:`UnicodeError` will be + raised if the Unicode cannot be encoded directly to ASCII. Examples: @@ -474,16 +498,13 @@ def traversal_path(path): writing their own traversal machinery, as opposed to users writing applications in :app:`Pyramid`. """ - if isinstance(path, unicode): - path = path.encode('ascii') + try: + path = bytes_(path, 'latin-1').decode('utf-8') + except UnicodeDecodeError as e: + raise URLDecodeError(e.encoding, e.object, e.start, e.end, e.reason) path = path.strip('/') clean = [] for segment in path.split('/'): - segment = urllib.unquote(segment) - try: - segment = segment.decode('utf-8') - except UnicodeDecodeError, e: - raise URLDecodeError(e.encoding, e.object, e.start, e.end, e.reason) if not segment or segment == '.': continue elif segment == '..': @@ -495,55 +516,82 @@ def traversal_path(path): _segment_cache = {} -def quote_path_segment(segment, safe=''): - """ Return a quoted representation of a 'path segment' (such as - the string ``__name__`` attribute of a resource) as a string. If the - ``segment`` passed in is a unicode object, it is converted to a - UTF-8 string, then it is URL-quoted using Python's - ``urllib.quote``. If the ``segment`` passed in is a string, it is - URL-quoted using Python's :mod:`urllib.quote`. If the segment - passed in is not a string or unicode object, an error will be - raised. The return value of ``quote_path_segment`` is always a - string, never Unicode. - - You may pass a string of characters that need not be encoded as - the ``safe`` argument to this function. This corresponds to the - ``safe`` argument to :mod:`urllib.quote`. - - .. note:: The return value for each segment passed to this - function is cached in a module-scope dictionary for - speed: the cached version is returned when possible - rather than recomputing the quoted version. No cache - emptying is ever done for the lifetime of an - application, however. If you pass arbitrary - user-supplied strings to this function (as opposed to - some bounded set of values from a 'working set' known to - your application), it may become a memory leak. - """ - # The bit of this code that deals with ``_segment_cache`` is an - # optimization: we cache all the computation of URL path segments - # in this module-scope dictionary with the original string (or - # unicode value) as the key, so we can look it up later without - # needing to reencode or re-url-quote it - try: - return _segment_cache[(segment, safe)] - except KeyError: - if segment.__class__ is unicode: # isinstance slighly slower (~15%) - result = url_quote(segment.encode('utf-8'), safe) - else: - result = url_quote(str(segment), safe) - # we don't need a lock to mutate _segment_cache, as the below - # will generate exactly one Python bytecode (STORE_SUBSCR) - _segment_cache[(segment, safe)] = result - return result - +quote_path_segment_doc = """ \ +Return a quoted representation of a 'path segment' (such as +the string ``__name__`` attribute of a resource) as a string. If the +``segment`` passed in is a unicode object, it is converted to a +UTF-8 string, then it is URL-quoted using Python's +``urllib.quote``. If the ``segment`` passed in is a string, it is +URL-quoted using Python's :mod:`urllib.quote`. If the segment +passed in is not a string or unicode object, an error will be +raised. The return value of ``quote_path_segment`` is always a +string, never Unicode. + +You may pass a string of characters that need not be encoded as +the ``safe`` argument to this function. This corresponds to the +``safe`` argument to :mod:`urllib.quote`. + +.. note:: + + The return value for each segment passed to this + function is cached in a module-scope dictionary for + speed: the cached version is returned when possible + rather than recomputing the quoted version. No cache + emptying is ever done for the lifetime of an + application, however. If you pass arbitrary + user-supplied strings to this function (as opposed to + some bounded set of values from a 'working set' known to + your application), it may become a memory leak. +""" + + +if PY3: # pragma: no cover + # special-case on Python 2 for speed? unchecked + def quote_path_segment(segment, safe=''): + """ %s """ % quote_path_segment_doc + # The bit of this code that deals with ``_segment_cache`` is an + # optimization: we cache all the computation of URL path segments + # in this module-scope dictionary with the original string (or + # unicode value) as the key, so we can look it up later without + # needing to reencode or re-url-quote it + try: + return _segment_cache[(segment, safe)] + except KeyError: + if segment.__class__ not in (text_type, binary_type): + segment = str(segment) + result = url_quote(native_(segment, 'utf-8'), safe) + # we don't need a lock to mutate _segment_cache, as the below + # will generate exactly one Python bytecode (STORE_SUBSCR) + _segment_cache[(segment, safe)] = result + return result +else: + def quote_path_segment(segment, safe=''): + """ %s """ % quote_path_segment_doc + # The bit of this code that deals with ``_segment_cache`` is an + # optimization: we cache all the computation of URL path segments + # in this module-scope dictionary with the original string (or + # unicode value) as the key, so we can look it up later without + # needing to reencode or re-url-quote it + try: + return _segment_cache[(segment, safe)] + except KeyError: + if segment.__class__ is text_type: #isinstance slighly slower (~15%) + result = url_quote(segment.encode('utf-8'), safe) + else: + result = url_quote(str(segment), safe) + # we don't need a lock to mutate _segment_cache, as the below + # will generate exactly one Python bytecode (STORE_SUBSCR) + _segment_cache[(segment, safe)] = result + return result + + +@implementer(ITraverser) class ResourceTreeTraverser(object): """ A resource tree traverser that should be used (for speed) when every resource in the tree supplies a ``__name__`` and ``__parent__`` attribute (ie. every resource in the tree is :term:`location` aware) .""" - implements(ITraverser) VIEW_SELECTOR = '@@' @@ -567,14 +615,14 @@ def __call__(self, request): matchdict = environ['bfg.routes.matchdict'] path = matchdict.get('traverse', '/') or '/' - if hasattr(path, '__iter__'): + if is_nonstr_iter(path): # this is a *traverse stararg (not a {traverse}) path = '/'.join([quote_path_segment(x) for x in path]) or '/' subpath = matchdict.get('subpath', ()) - if not hasattr(subpath, '__iter__'): + if not is_nonstr_iter(subpath): # this is not a *subpath stararg (just a {subpath}) - subpath = traversal_path(subpath) + subpath = traversal_path_info(subpath) else: # this request did not match a route @@ -586,7 +634,7 @@ def __call__(self, request): if VH_ROOT_KEY in environ: vroot_path = environ[VH_ROOT_KEY] - vroot_tuple = traversal_path(vroot_path) + vroot_tuple = traversal_path_info(vroot_path) vpath = vroot_path + path vroot_idx = len(vroot_tuple) -1 else: @@ -607,7 +655,7 @@ def __call__(self, request): # and this hurts readability; apologies i = 0 view_selector = self.VIEW_SELECTOR - vpath_tuple = traversal_path(vpath) + vpath_tuple = traversal_path_info(vpath) for segment in vpath_tuple: if segment[:2] == view_selector: return {'context':ob, @@ -643,16 +691,16 @@ def __call__(self, request): ob = next i += 1 - return {'context':ob, 'view_name':u'', 'subpath':subpath, + return {'context':ob, 'view_name':empty, 'subpath':subpath, 'traversed':vpath_tuple, 'virtual_root':vroot, 'virtual_root_path':vroot_tuple, 'root':root} ModelGraphTraverser = ResourceTreeTraverser # b/w compat, not API, used in wild +@implementer(IContextURL) class TraversalContextURL(object): """ The IContextURL adapter used to generate URLs for a resource in a resource tree""" - implements(IContextURL) vroot_varname = VH_ROOT_KEY diff --git a/pyramid/tweens.py b/pyramid/tweens.py index b15204e9d5..65d7c39194 100644 --- a/pyramid/tweens.py +++ b/pyramid/tweens.py @@ -15,7 +15,7 @@ def excview_tween(request): attrs = request.__dict__ try: response = handler(request) - except Exception, exc: + except Exception as exc: # WARNING: do not assign the result of sys.exc_info() to a # local var here, doing so will cause a leak attrs['exc_info'] = sys.exc_info() diff --git a/pyramid/url.py b/pyramid/url.py index c2ff43ddde..7a7dd3b4cd 100644 --- a/pyramid/url.py +++ b/pyramid/url.py @@ -8,6 +8,8 @@ from pyramid.interfaces import IRoutesMapper from pyramid.interfaces import IStaticURLInfo +from pyramid.compat import native_ +from pyramid.compat import text_type from pyramid.encode import urlencode from pyramid.path import caller_package from pyramid.threadlocal import get_current_registry @@ -137,8 +139,7 @@ def route_url(self, route_name, *elements, **kw): if '_anchor' in kw: anchor = kw.pop('_anchor') - if isinstance(anchor, unicode): - anchor = anchor.encode('utf-8') + anchor = native_(anchor, 'utf-8') anchor = '#' + anchor if '_app_url' in kw: @@ -310,8 +311,8 @@ def resource_url(self, resource, *elements, **kw): if 'anchor' in kw: anchor = kw['anchor'] - if isinstance(anchor, unicode): - anchor = anchor.encode('utf-8') + if isinstance(anchor, text_type): + anchor = native_(anchor, 'utf-8') anchor = '#' + anchor if elements: diff --git a/pyramid/urldispatch.py b/pyramid/urldispatch.py index 73318193c5..6626158458 100644 --- a/pyramid/urldispatch.py +++ b/pyramid/urldispatch.py @@ -1,19 +1,23 @@ import re -from urllib import unquote -from zope.interface import implements +from zope.interface import implementer from pyramid.interfaces import IRoutesMapper from pyramid.interfaces import IRoute -from pyramid.encode import url_quote +from pyramid.compat import native_ +from pyramid.compat import bytes_ +from pyramid.compat import text_type +from pyramid.compat import string_types +from pyramid.compat import is_nonstr_iter +from pyramid.compat import url_quote from pyramid.exceptions import URLDecodeError -from pyramid.traversal import traversal_path +from pyramid.traversal import traversal_path_info from pyramid.traversal import quote_path_segment _marker = object() +@implementer(IRoute) class Route(object): - implements(IRoute) def __init__(self, name, pattern, factory=None, predicates=(), pregenerator=None): self.pattern = pattern @@ -24,8 +28,8 @@ def __init__(self, name, pattern, factory=None, predicates=(), self.predicates = predicates self.pregenerator = pregenerator +@implementer(IRoutesMapper) class RoutesMapper(object): - implements(IRoutesMapper) def __init__(self): self.routelist = [] self.routes = {} @@ -133,14 +137,14 @@ def matcher(path): if m is None: return m d = {} - for k, v in m.groupdict().iteritems(): + for k, v in m.groupdict().items(): if k == star: - d[k] = traversal_path(v) + d[k] = traversal_path_info(v) else: - encoded = unquote(v) try: - d[k] = encoded.decode('utf-8') - except UnicodeDecodeError, e: + val = bytes_(v).decode('utf-8', 'strict') + d[k] = val + except UnicodeDecodeError as e: raise URLDecodeError( e.encoding, e.object, e.start, e.end, e.reason ) @@ -153,15 +157,14 @@ def matcher(path): def generator(dict): newdict = {} for k, v in dict.items(): - if isinstance(v, unicode): - v = v.encode('utf-8') - if k == star and hasattr(v, '__iter__'): + if v.__class__ is text_type: + v = native_(v, 'utf-8') + if k == star and is_nonstr_iter(v): v = '/'.join([quote_path_segment(x) for x in v]) elif k != star: - try: - v = url_quote(v) - except TypeError: - pass + if v.__class__ not in string_types: + v = str(v) + v = url_quote(v, safe='') newdict[k] = v return gen % newdict diff --git a/pyramid/util.py b/pyramid/util.py index c0e7640c41..a43b50aef3 100644 --- a/pyramid/util.py +++ b/pyramid/util.py @@ -2,6 +2,7 @@ import sys import weakref +from pyramid.compat import string_types from pyramid.exceptions import ConfigurationError from pyramid.path import package_of @@ -69,7 +70,7 @@ def __init__(self, package): self.package_name = None self.package = None else: - if isinstance(package, basestring): + if isinstance(package, string_types): try: __import__(package) except ImportError: @@ -132,12 +133,12 @@ def _zope_dottedname_style(self, value): return found def resolve(self, dotted): - if not isinstance(dotted, basestring): + if not isinstance(dotted, string_types): raise ConfigurationError('%r is not a string' % (dotted,)) return self.maybe_resolve(dotted) def maybe_resolve(self, dotted): - if isinstance(dotted, basestring): + if isinstance(dotted, string_types): if ':' in dotted: return self._pkg_resources_style(dotted) else: diff --git a/pyramid/view.py b/pyramid/view.py index 6046408f65..581e421853 100644 --- a/pyramid/view.py +++ b/pyramid/view.py @@ -7,6 +7,7 @@ from pyramid.interfaces import IView from pyramid.interfaces import IViewClassifier +from pyramid.compat import map_ from pyramid.httpexceptions import HTTPFound from pyramid.httpexceptions import default_exceptionresponse_view from pyramid.path import caller_package @@ -52,7 +53,7 @@ def render_view_to_response(context, request, name='', secure=True): disallowed. If ``secure`` is ``False``, no permission checking is done.""" - provides = [IViewClassifier] + map(providedBy, (request, context)) + provides = [IViewClassifier] + map_(providedBy, (request, context)) try: reg = request.registry except AttributeError: diff --git a/setup.py b/setup.py index 82f4471037..08aa1643e3 100644 --- a/setup.py +++ b/setup.py @@ -13,11 +13,15 @@ ############################################################################## import os -import platform import sys from setuptools import setup, find_packages +if sys.version_info[:2] < (2, 6): + raise RuntimeError('Requires Python 2.6 or better') + +PY3 = sys.version_info[0] == 3 + here = os.path.abspath(os.path.dirname(__file__)) try: README = open(os.path.join(here, 'README.rst')).read() @@ -26,38 +30,37 @@ README = CHANGES = '' install_requires=[ + 'setuptools', 'Chameleon >= 1.2.3', 'Mako >= 0.3.6', # strict_undefined - 'Paste > 1.7', # temp version pin to prevent PyPi install failure :-( - 'PasteDeploy', - 'PasteScript >= 1.7.4', # "here" in logging fileConfig - 'WebOb >= 1.0.2', # no "default_charset"; request.script_name doesnt error - 'repoze.lru', - 'setuptools', - 'zope.component >= 3.6.0', # independent of zope.hookable - 'zope.interface >= 3.5.1', # 3.5.0 comment: "allow to bootstrap on jython" - 'zope.deprecation', + 'WebOb >= 1.2dev', # response.text / py3 compat + 'repoze.lru >= 0.4', # py3 compat + 'zope.interface >= 3.8.0', # has zope.interface.registry + 'zope.deprecation >= 3.5.0', # py3 compat 'venusian >= 1.0a1', # ``onerror`` - 'translationstring', + 'translationstring >= 0.4', # py3 compat ] -if platform.system() == 'Java': - tests_require = install_requires + [ - 'WebTest', - 'virtualenv', - ] -else: - tests_require= install_requires + [ +if not PY3: + install_requires.extend([ + 'Paste > 1.7', # temp version pin to prevent PyPi install failure :-( + 'PasteDeploy', + 'PasteScript >= 1.7.4', # "here" in logging fileConfig + ]) + +tests_require = install_requires + [ + 'WebTest >= 1.3.1', # py3 compat + 'virtualenv', + ] + +if not PY3: + tests_require.extend([ 'Sphinx', 'docutils', 'repoze.sphinx.autointerface', - 'WebTest', - 'virtualenv', - ] + 'zope.component>=3.11.0', + ]) -if sys.version_info[:2] < (2, 6): - install_requires.append('simplejson') - setup(name='pyramid', version='1.2', description=('The Pyramid web application development framework, a ' diff --git a/tox.ini b/tox.ini index 40711a5f2f..a422b2eb69 100644 --- a/tox.ini +++ b/tox.ini @@ -1,19 +1,25 @@ [tox] envlist = - py25,py26,py27,jython,pypy,cover + py26,py27,py32,pypy,cover [testenv] commands = python setup.py test -q deps = + https://github.com/Pylons/webob/zipball/master + zope.component Sphinx - WebTest repoze.sphinx.autointerface + WebTest virtualenv -[testenv:jython] +[testenv:py32] commands = - jython setup.py test -q + python setup.py test -q +deps = + https://github.com/Pylons/webob/zipball/master + WebTest + virtualenv [testenv:cover] basepython = @@ -21,6 +27,8 @@ basepython = commands = python setup.py nosetests --with-xunit --with-xcoverage deps = + https://github.com/Pylons/webob/zipball/master + zope.component Sphinx WebTest repoze.sphinx.autointerface