diff options
| author | Chris McDonough <chrism@plope.com> | 2011-09-28 04:48:39 -0400 |
|---|---|---|
| committer | Chris McDonough <chrism@plope.com> | 2011-09-28 04:48:39 -0400 |
| commit | 311a894e32936a1087cb44543183da67d8c7fdec (patch) | |
| tree | 98ae57cf94166e698351ab4f16d0a83236765687 | |
| parent | bbc4f46ccf6b3e9c2235f0fd60622a0deff6b30e (diff) | |
| parent | dd5a91eb937369d06f3fc438c817e046fc81f891 (diff) | |
| download | pyramid-311a894e32936a1087cb44543183da67d8c7fdec.tar.gz pyramid-311a894e32936a1087cb44543183da67d8c7fdec.tar.bz2 pyramid-311a894e32936a1087cb44543183da67d8c7fdec.zip | |
Merge branch 'token-reissue' of https://github.com/wichert/pyramid into wichert-token-reissue
84 files changed, 2039 insertions, 1510 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index e90c5e127..047db6472 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 5cabb0111..956f07362 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 730969a4a..4bf0d7bf4 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 e2014b9a1..ed422b044 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 @@ class CallbackAuthenticationPolicy(object): ) 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 @@ class RepozeWho1AuthenticationPolicy(CallbackAuthenticationPolicy): 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 @@ class RemoteUserAuthenticationPolicy(CallbackAuthenticationPolicy): 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 @@ class AuthTktAuthenticationPolicy(CallbackAuthenticationPolicy): 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 @@ class AuthTicket(object): 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 @@ class AuthTktCookieHelper(object): 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 @@ class AuthTktCookieHelper(object): user_data = 'userid_type:%s' % encoding for token in tokens: + if isinstance(token, text_type): + try: + token = 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 @@ class AuthTktCookieHelper(object): 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 ac8f195f2..b1ef10033 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 Allow 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 676985853..872d3b920 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 @@ except ImportError: # pragma: no cover # 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 @@ from pyramid.path import caller_package 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 ca96d9356..aa6f89e07 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 @@ except ImportError: # pragma: no cover # 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 @@ from pyramid import renderers 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 7d723715e..e686be27d 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 ImportError: # pragma: no cover 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 9effeed17..8085bbc79 100644 --- a/pyramid/config/__init__.py +++ b/pyramid/config/__init__.py @@ -13,6 +13,10 @@ from pyramid.interfaces import IExceptionResponse 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.util import action_method from pyramid.config.views import ViewsConfiguratorMixin from pyramid.config.zca import ZCAConfiguratorMixin +empty = text_('') + ConfigurationError = ConfigurationError # pyflakes class Configurator( @@ -284,7 +290,7 @@ class Configurator( 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 @@ class Configurator( 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 @@ class Configurator( 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 @@ class Configurator( 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 @@ class ActionState(object): 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 1b5254072..08cc6dc38 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 @@ class OverrideProvider(pkg_resources.DefaultProvider): 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 6e636bf58..565a6699c 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 @@ class SettingsConfiguratorMixin(object): 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 0f709f634..36729acdf 100644 --- a/pyramid/config/testing.py +++ b/pyramid/config/testing.py @@ -6,7 +6,7 @@ from pyramid.interfaces import IAuthenticationPolicy 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 @@ class TestingConfiguratorMixin(object): 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 2704b89c1..3c7ee384f 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 @@ class TweensConfiguratorMixin(object): @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 @@ class TweensConfiguratorMixin(object): 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 @@ class TweensConfiguratorMixin(object): 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 @@ class CyclicDependencyError(Exception): 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 @@ class Tweens(object): 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 @@ class Tweens(object): 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 1e54213ac..0336b103d 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 make_predicates(xhr=None, request_method=None, path_info=None, 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 make_predicates(xhr=None, request_method=None, path_info=None, 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 make_predicates(xhr=None, request_method=None, path_info=None, 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 make_predicates(xhr=None, request_method=None, path_info=None, 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 make_predicates(xhr=None, request_method=None, path_info=None, 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 make_predicates(xhr=None, request_method=None, path_info=None, 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 make_predicates(xhr=None, request_method=None, path_info=None, 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 make_predicates(xhr=None, request_method=None, path_info=None, 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 make_predicates(xhr=None, request_method=None, path_info=None, # 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 make_predicates(xhr=None, request_method=None, path_info=None, 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 make_predicates(xhr=None, request_method=None, path_info=None, 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 3da41861d..179d4065c 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 IViewMapperFactory 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 action_method 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 @@ class ViewDeriver(object): 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 @@ class DefaultViewMapper(object): 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 @@ class ViewsConfiguratorMixin(object): 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 @@ class ViewsConfiguratorMixin(object): '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 @@ class ViewsConfiguratorMixin(object): 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 @@ class ViewsConfiguratorMixin(object): 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 @@ class ViewsConfiguratorMixin(object): 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 @@ class StaticURLInfo(object): # 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 826e6a662..a259d1414 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 9473d6330..a495d9c29 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 @@ class subscriber(object): 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 @@ class BeforeRender(dict): 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 cafdb93f0..ff598fe2d 100644 --- a/pyramid/exceptions.py +++ b/pyramid/exceptions.py @@ -4,6 +4,8 @@ from pyramid.httpexceptions import HTTPForbidden 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 @@ class ConfigurationConflictError(ConfigurationError): 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 8fd32a797..d9a4a6dfb 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 0887b0eec..4dbca7021 100644 --- a/pyramid/httpexceptions.py +++ b/pyramid/httpexceptions.py @@ -122,31 +122,36 @@ field. Reflecting this, these subclasses have one additional keyword argument: ``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 @@ ${body}''') 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 f16aeb378..889227130 100644 --- a/pyramid/i18n.py +++ b/pyramid/i18n.py @@ -9,6 +9,7 @@ from translationstring import TranslationStringFactory # API 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 @@ class Translations(gettext.GNUTranslations, object): # 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 @@ class Translations(gettext.GNUTranslations, object): 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 @@ class Translations(gettext.GNUTranslations, object): """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 @@ class Translations(gettext.GNUTranslations, object): """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 d90f56b75..f08bd5fbb 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 @@ class IDict(Interface): """ 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 c79de7217..29be339f2 100644 --- a/pyramid/mako_templating.py +++ b/pyramid/mako_templating.py @@ -2,11 +2,12 @@ import os 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 @@ class MakoRenderingException(Exception): __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 bdf7df109..c9c356a92 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 @@ class PShellCommand(PCommand): config.read(filename) try: items = config.items('pshell') - except ConfigParser.NoSectionError: + except configparser.NoSectionError: return resolver = DottedNameResolver(None) @@ -301,8 +310,8 @@ class PRoutesCommand(PCommand): 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 @@ class PViewsCommand(PCommand): 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 @@ class PViewsCommand(PCommand): 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 @@ class PTweensCommand(PCommand): 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 6b287e4f1..ac706595e 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 @@ class Registry(Components, dict): 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 2efe0f123..073ce444d 100644 --- a/pyramid/renderers.py +++ b/pyramid/renderers.py @@ -2,7 +2,7 @@ import os 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,8 @@ from pyramid.interfaces import IRendererInfo 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.decorator import reify from pyramid.events import BeforeRender from pyramid.path import caller_package @@ -145,7 +147,7 @@ def json_renderer_factory(info): 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 +227,8 @@ class JSONP(object): # utility functions, not API +@implementer(IChameleonLookup) class ChameleonRendererLookup(object): - implements(IChameleonLookup) def __init__(self, impl, registry): self.impl = impl self.registry = registry @@ -348,8 +350,8 @@ deprecated( '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 +439,8 @@ class RendererHelper(object): 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 9704a42a4..eae83da6f 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 ISessionFactory 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 @@ class DeprecatedRequestMethodsMixin(object): @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 @@ class CallbackMethodsMixin(object): 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 @@ class Request(BaseRequest, DeprecatedRequestMethodsMixin, URLMethodsMixin, @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 5e8f3c968..986c75e37 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 db53de0c3..b0c965296 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 746cf88cf..fb309eb03 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 DefaultRootFactory 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 a49bbd9f6..673f22e21 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 @@ class PyramidTemplate(Template): 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 fe5d6957a..7eb838e2f 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 tempfile 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 5aed7b5fa..a552b613a 100644 --- a/pyramid/security.py +++ b/pyramid/security.py @@ -5,6 +5,7 @@ from pyramid.interfaces import IAuthorizationPolicy 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 bfa80ff10..a59f9c628 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 hmac 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 UnencryptedCookieSessionFactoryConfig( 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 UnencryptedCookieSessionFactoryConfig( # 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 3c376c4a9..de91042eb 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 17a74a16a..50a8b918b 100644 --- a/pyramid/static.py +++ b/pyramid/static.py @@ -15,11 +15,14 @@ from pkg_resources import resource_isdir 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 @@ class _FileIter(object): 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 @@ class static_view(object): 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 07f523868..b24a6cac7 100644 --- a/pyramid/testing.py +++ b/pyramid/testing.py @@ -3,7 +3,7 @@ import os 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 IView 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 @@ -125,9 +128,7 @@ def registerEventListener(event_iface=None): event will be appended to the list. You can then compare the values in the list to expected event notifications. This method is useful when testing code that wants to call - :meth:`pyramid.registry.Registry.notify`, - :func:`zope.component.event.dispatch` or - :func:`zope.component.event.objectEventNotify`. + :meth:`pyramid.registry.Registry.notify`. The default value of ``event_iface`` (``None``) implies a subscriber registered for *any* kind of event. @@ -590,8 +591,8 @@ class DummyResource: DummyModel = DummyResource # b/w compat (forever) +@implementer(ISession) class DummySession(dict): - implements(ISession) created = None new = True def changed(self): @@ -621,6 +622,7 @@ class DummySession(dict): 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 +651,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' @@ -717,6 +718,8 @@ class DummyRequest(DeprecatedRequestMethodsMixin, URLMethodsMixin, f = self.registry.queryUtility(IResponseFactory, default=Response) return f() +have_zca = True + def setUp(registry=None, request=None, hook_zca=True, autocommit=True, settings=None): """ @@ -801,7 +804,12 @@ 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() + global have_zca + try: + have_zca and hook_zca and config.hook_zca() + except ImportError: + # pragma: no cover (dont choke on not being able to import z.component) + have_zca = False config.begin(request=request) return config @@ -816,12 +824,13 @@ def tearDown(unhook_zca=True): argument ``hook_zca=True``. If :mod:`zope.component` cannot be imported, ignore the argument. """ - if unhook_zca: + global have_zca + if unhook_zca and have_zca: try: from zope.component import getSiteManager getSiteManager.reset() except ImportError: # pragma: no cover - pass + have_zca = False info = manager.pop() manager.clear() if info is not None: @@ -889,21 +898,25 @@ class MockTemplate(object): 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 efcf791e8..25283a50d 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 efcf791e8..25283a50d 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 1432618cf..33b97671e 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 c74747bfd..27063aae2 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 7001b87f5..888dc9317 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 10a244f3b..330d983ab 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 0880556ef..e2024198e 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 ff96ae471..6b8393fc2 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 @@ class TestAuthTktCookieHelper(unittest.TestCase): 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 @@ class TestAuthTktCookieHelper(unittest.TestCase): 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 @@ class TestAuthTktCookieHelper(unittest.TestCase): 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 @@ -612,6 +616,7 @@ class TestAuthTktCookieHelper(unittest.TestCase): now = time.time() helper.auth_tkt.timestamp = now helper.now = now + 1 + helper.auth_tkt.tokens = (u'a', ) request = self._makeRequest('bogus') result = helper.identify(request) self.assertTrue(result) @@ -822,14 +827,16 @@ class TestAuthTktCookieHelper(unittest.TestCase): 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 +850,7 @@ class TestAuthTktCookieHelper(unittest.TestCase): 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 +861,16 @@ class TestAuthTktCookieHelper(unittest.TestCase): 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 +908,18 @@ class TestAuthTktCookieHelper(unittest.TestCase): 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_unicode_but_ascii_token(self): helper = self._makeOne('secret') request = self._makeRequest() + la = text_(b'foo', 'utf-8') + helper.remember(request, 'other', tokens=(la,)) + + 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 +1102,6 @@ class TestSessionAuthenticationPolicy(unittest.TestCase): 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 213f25f51..8d23c8753 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 @@ class TextTemplateRendererTests(Base, unittest.TestCase): 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 @@ class TextTemplateRendererTests(Base, unittest.TestCase): 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 @@ class TextTemplateRendererTests(Base, unittest.TestCase): 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 @@ class RenderTemplateTests(Base, unittest.TestCase): 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 @@ class RenderTemplateToResponseTests(Base, unittest.TestCase): 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 84eaedcf4..1a8e6767e 100644 --- a/pyramid/tests/test_chameleon_zpt.py +++ b/pyramid/tests/test_chameleon_zpt.py @@ -2,6 +2,7 @@ import unittest 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 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase): 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'), '<div xmlns="http://www.w3.org/1999/xhtml">\n</div>') @@ -126,7 +127,7 @@ class ZPTTemplateRendererTests(Base, unittest.TestCase): 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'), '<div xmlns="http://www.w3.org/1999/xhtml">\n</div>') @@ -140,7 +141,7 @@ class RenderTemplateTests(Base, unittest.TestCase): 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'), '<div xmlns="http://www.w3.org/1999/xhtml">\n</div>') @@ -155,8 +156,8 @@ class RenderTemplateToResponseTests(Base, unittest.TestCase): result = self._callFUT(minimal) from webob import Response self.assertTrue(isinstance(result, Response)) - self.assertEqual(result.app_iter[0].rstrip('\n'), - '<div xmlns="http://www.w3.org/1999/xhtml">\n</div>') + self.assertEqual(result.app_iter[0].rstrip(b'\n'), + b'<div xmlns="http://www.w3.org/1999/xhtml">\n</div>') 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 2f9f516ae..5b40a8c09 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 @@ includeme = dummy_include 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 29c099e0e..84b7119cf 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 @@ class AdaptersConfiguratorMixinTests(unittest.TestCase): 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 @@ class AdaptersConfiguratorMixinTests(unittest.TestCase): 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 @@ class AdaptersConfiguratorMixinTests(unittest.TestCase): 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 AdaptersConfiguratorMixinTests(unittest.TestCase): 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 322c854f5..627eefba7 100644 --- a/pyramid/tests/test_config/test_assets.py +++ b/pyramid/tests/test_config/test_assets.py @@ -163,9 +163,9 @@ class TestOverrideProvider(unittest.TestCase): 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 @@ class TestOverrideProvider(unittest.TestCase): 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 @@ class TestOverrideProvider(unittest.TestCase): 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 @@ class TestOverrideProvider(unittest.TestCase): 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 @@ class TestOverrideProvider(unittest.TestCase): 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 @@ class TestPackageOverrides(unittest.TestCase): 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 @@ class TestPackageOverrides(unittest.TestCase): 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 @@ class DummyPackage: 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 03b410445..25cb88cc3 100644 --- a/pyramid/tests/test_config/test_i18n.py +++ b/pyramid/tests/test_config/test_i18n.py @@ -92,7 +92,7 @@ class TestI18NConfiguratorMixin(unittest.TestCase): 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 1dccd00e1..ca1508295 100644 --- a/pyramid/tests/test_config/test_init.py +++ b/pyramid/tests/test_config/test_init.py @@ -2,6 +2,10 @@ import unittest 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 dummy_extend2 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 @@ class ConfiguratorTests(unittest.TestCase): 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 @@ class ConfiguratorTests(unittest.TestCase): 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 @@ class ConfiguratorTests(unittest.TestCase): 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 @@ class ConfiguratorTests(unittest.TestCase): 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 @@ pyramid.tests.test_config.dummy_include2""", """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 @@ pyramid.tests.test_config.dummy_include2""", 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 @@ pyramid.tests.test_config.dummy_include2""", 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 @@ pyramid.tests.test_config.dummy_include2""", finally: getSiteManager.reset() + @skip_on('py3') def test_unhook_zca(self): from zope.component import getSiteManager def foo(): @@ -987,7 +989,7 @@ pyramid.tests.test_config.dummy_include2""", 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 @@ pyramid.tests.test_config.dummy_include2""", 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 @@ pyramid.tests.test_config.dummy_include2""", 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 @@ pyramid.tests.test_config.dummy_include2""", 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 @@ pyramid.tests.test_config.dummy_include2""", 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 @@ class TestConfiguratorDeprecatedFeatures(unittest.TestCase): 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 @@ class TestConfiguratorDeprecatedFeatures(unittest.TestCase): 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 @@ class TestConfiguratorDeprecatedFeatures(unittest.TestCase): 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 @@ class TestConfiguratorDeprecatedFeatures(unittest.TestCase): 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 @@ class TestConfigurator_add_directive(unittest.TestCase): 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 @@ class TestConfigurator_add_directive(unittest.TestCase): 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 @@ class TestConfigurator_add_directive(unittest.TestCase): '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 @@ class DummyThreadLocalManager(object): 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 @@ class DummyRegistry(object): 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 494a2b099..6c048b46d 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 @@ class TestingConfiguratorMixinTests(unittest.TestCase): 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 @@ class TestingConfiguratorMixinTests(unittest.TestCase): 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 f4e69f4e1..0813eecdb 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 IDummy 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 @@ class TestViewsConfigurationMixin(unittest.TestCase): 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 @@ class TestViewsConfigurationMixin(unittest.TestCase): 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 @@ class TestViewsConfigurationMixin(unittest.TestCase): 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 @@ class TestViewsConfigurationMixin(unittest.TestCase): 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 @@ class TestViewsConfigurationMixin(unittest.TestCase): 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 @@ class TestViewsConfigurationMixin(unittest.TestCase): 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 @@ class TestViewsConfigurationMixin(unittest.TestCase): 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 @@ class TestViewsConfigurationMixin(unittest.TestCase): 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 @@ class TestViewsConfigurationMixin(unittest.TestCase): 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 @@ -1428,6 +1429,7 @@ class TestViewsConfigurationMixin(unittest.TestCase): request.subpath = ('minimal.pt', ) result = wrapped(None, request) self.assertEqual(result.status, '200 OK') + self.assertTrue(result.body.startswith(b'<div')) def test_add_static_view_package_relative(self): from pyramid.interfaces import IStaticURLInfo @@ -1545,7 +1547,7 @@ class TestViewsConfigurationMixin(unittest.TestCase): 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 +1568,7 @@ class TestViewsConfigurationMixin(unittest.TestCase): 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 +2119,7 @@ class TestViewDeriver(unittest.TestCase): 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 +2285,7 @@ class TestViewDeriver(unittest.TestCase): 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 +2539,7 @@ class TestViewDeriver(unittest.TestCase): request.url = 'url' try: result(None, request) - except HTTPForbidden, e: + except HTTPForbidden as e: self.assertEqual(e.message, 'Unauthorized: <lambda> failed permission check') else: # pragma: no cover @@ -2559,7 +2561,7 @@ class TestViewDeriver(unittest.TestCase): 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 +2579,7 @@ class TestViewDeriver(unittest.TestCase): request.method = 'POST' try: result(None, None) - except PredicateMismatch, e: + except PredicateMismatch as e: self.assertEqual(e.detail, 'predicate mismatch for view <lambda>') else: # pragma: no cover raise AssertionError @@ -2593,7 +2595,7 @@ class TestViewDeriver(unittest.TestCase): 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 +2664,7 @@ class TestViewDeriver(unittest.TestCase): 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 +2675,7 @@ class TestViewDeriver(unittest.TestCase): 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 +3254,12 @@ class Test_preserve_view_attrs(unittest.TestCase): 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 +3465,11 @@ class DummyRequest: 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 +3515,10 @@ class DummyConfig: 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 bf06c7ca6..eba95b210 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 @@ if 0: 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 27965aea9..ccc8f16e3 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 @@ class UrlEncodeTests(unittest.TestCase): 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 @@ class URLQuoteTests(unittest.TestCase): 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 9c7b38e27..927d27733 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 @@ class Test__no_escape(unittest.TestCase): 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 @@ class TestWSGIHTTPException(unittest.TestCase): 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 @@ class TestWSGIHTTPException(unittest.TestCase): environ['HTTP_ACCEPT'] = 'text/html' start_response = DummyStartResponse() body = list(exc(environ, start_response))[0] - self.assertTrue(body.startswith('<html')) - self.assertTrue('200 OK' in body) - self.assertTrue('explanation' in body) - self.assertTrue('detail' in body) + self.assertTrue(body.startswith(b'<html')) + self.assertTrue(b'200 OK' in body) + self.assertTrue(b'explanation' in body) + self.assertTrue(b'detail' in body) def test_ctor_with_body_sets_default_app_iter_text(self): cls = self._getTargetSubclass() @@ -153,7 +156,7 @@ class TestWSGIHTTPException(unittest.TestCase): environ = _makeEnviron() start_response = DummyStartResponse() body = list(exc(environ, start_response))[0] - self.assertEqual(body, '200 OK\n\nexplanation\n\n\ndetail\n\n') + self.assertEqual(body, b'200 OK\n\nexplanation\n\n\ndetail\n\n') def test__str__detail(self): exc = self._makeOne() @@ -198,7 +201,7 @@ class TestWSGIHTTPException(unittest.TestCase): environ = _makeEnviron() start_response = DummyStartResponse() body = list(exc(environ, start_response))[0] - self.assertEqual(body, '200 OK\n\nexplanation\n\n\n\n\n') + self.assertEqual(body, b'200 OK\n\nexplanation\n\n\n\n\n') def test__default_app_iter_with_comment_plain(self): cls = self._getTargetSubclass() @@ -206,7 +209,7 @@ class TestWSGIHTTPException(unittest.TestCase): environ = _makeEnviron() start_response = DummyStartResponse() body = list(exc(environ, start_response))[0] - self.assertEqual(body, '200 OK\n\nexplanation\n\n\n\ncomment\n') + self.assertEqual(body, b'200 OK\n\nexplanation\n\n\n\ncomment\n') def test__default_app_iter_no_comment_html(self): cls = self._getTargetSubclass() @@ -214,7 +217,7 @@ class TestWSGIHTTPException(unittest.TestCase): environ = _makeEnviron() start_response = DummyStartResponse() body = list(exc(environ, start_response))[0] - self.assertFalse('<!-- ' in body) + self.assertFalse(b'<!-- ' in body) def test__default_app_iter_with_comment_html(self): cls = self._getTargetSubclass() @@ -223,7 +226,7 @@ class TestWSGIHTTPException(unittest.TestCase): environ['HTTP_ACCEPT'] = '*/*' start_response = DummyStartResponse() body = list(exc(environ, start_response))[0] - self.assertTrue('<!-- comment & comment -->' in body) + self.assertTrue(b'<!-- comment & comment -->' in body) def test__default_app_iter_with_comment_html2(self): cls = self._getTargetSubclass() @@ -232,7 +235,7 @@ class TestWSGIHTTPException(unittest.TestCase): environ['HTTP_ACCEPT'] = 'text/html' start_response = DummyStartResponse() body = list(exc(environ, start_response))[0] - self.assertTrue('<!-- comment & comment -->' in body) + self.assertTrue(b'<!-- comment & comment -->' in body) def test_custom_body_template(self): cls = self._getTargetSubclass() @@ -240,7 +243,7 @@ class TestWSGIHTTPException(unittest.TestCase): 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 @@ class TestWSGIHTTPException(unittest.TestCase): 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 @@ class TestRenderAllExceptionsWithoutArguments(unittest.TestCase): 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 @@ class Test_HTTPMove(unittest.TestCase): 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 @@ class TestHTTPMethodNotAllowed(unittest.TestCase): 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 008cf525a..bd4998b10 100644 --- a/pyramid/tests/test_i18n.py +++ b/pyramid/tests/test_i18n.py @@ -50,9 +50,9 @@ class TestLocalizer(unittest.TestCase): 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 @@ class TestTranslations(unittest.TestCase): 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 @@ class TestTranslations(unittest.TestCase): 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 1fa1cbbcf..0c17b88ce 100644 --- a/pyramid/tests/test_integration.py +++ b/pyramid/tests/test_integration.py @@ -6,6 +6,8 @@ import unittest 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 @@ class IntegrationBase(object): 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 @@ class TestStaticAppBase(IntegrationBase): 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 @@ class TestStaticAppNoSubpath(unittest.TestCase): '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 @@ class TestStaticPermApp(IntegrationBase, unittest.TestCase): 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 @@ class TestStaticPermApp(IntegrationBase, unittest.TestCase): 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 @@ class TestHybridApp(IntegrationBase, unittest.TestCase): 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 " - "'<default deny>' in ACL " - "'<No ACL found on any object in resource " - "lineage>' on context")) + result.startswith(b"ACLDenied permission 'private' via ACE " + b"'<default deny>' in ACL " + b"'<No ACL found on any object in resource " + b"lineage>' 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 @@ class TestViewDecoratorApp(IntegrationBase, unittest.TestCase): # 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 @@ class TestConflictApp(unittest.TestCase): 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 @@ class TestConflictApp(unittest.TestCase): 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 @@ class TestConflictApp(unittest.TestCase): 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 @@ class TestConflictApp(unittest.TestCase): 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 @@ class ImperativeIncludeConfigurationTest(unittest.TestCase): 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 @@ class SelfScanAppTest(unittest.TestCase): 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 @@ class WSGIApp2AppTest(unittest.TestCase): 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 @@ if os.name != 'java': # uses chameleon 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 3d30c9954..e1f47f4ab 100644 --- a/pyramid/tests/test_location.py +++ b/pyramid/tests/test_location.py @@ -34,7 +34,7 @@ class TestLineage(unittest.TestCase): 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 162f774f5..074d28b85 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 @@ class MakoLookupTemplateRendererTests(Base, unittest.TestCase): 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 @@ class MakoLookupTemplateRendererTests(Base, unittest.TestCase): 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 @@ class MakoLookupTemplateRendererTests(Base, unittest.TestCase): 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 @@ class MakoLookupTemplateRendererTests(Base, unittest.TestCase): 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 @@ class TestIntegration(unittest.TestCase): 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 @@ class TestIntegration(unittest.TestCase): def test_template_default_escaping(self): from pyramid.renderers import render - result = render('nonminimal.mak', {'name':'<b>fred</b>'}).replace('\r','') - self.assertEqual(result, u'Hello, <b>fred</b>!\n') + result = render('nonminimal.mak', + {'name':'<b>fred</b>'}).replace('\r','') + self.assertEqual(result, text_('Hello, <b>fred</b>!\n')) class TestPkgResourceTemplateLookup(unittest.TestCase): def _makeOne(self, **kw): @@ -448,7 +454,7 @@ class DummyLookup(object): 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 995e05d46..a697b5691 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 @@ class TestPShellCommand(unittest.TestCase): 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 @@ class TestPRoutesCommand(unittest.TestCase): 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 @@ class TestPViewsCommand(unittest.TestCase): 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 @@ class TestPViewsCommand(unittest.TestCase): 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 @@ class TestPViewsCommand(unittest.TestCase): 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 @@ class TestPViewsCommand(unittest.TestCase): 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 TestPViewsCommand(unittest.TestCase): 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 TestPViewsCommand(unittest.TestCase): (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 @@ class TestPViewsCommand(unittest.TestCase): 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 TestPViewsCommand(unittest.TestCase): 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 @@ class TestPViewsCommand(unittest.TestCase): 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 TestPViewsCommand(unittest.TestCase): 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 @@ class TestPViewsCommand(unittest.TestCase): 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 @@ class TestGetApp(unittest.TestCase): 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 @@ class TestBootstrap(unittest.TestCase): 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 @@ class DummyConfigParser(object): 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 6a20eaa5d..c3104bd31 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 48b1bed65..1054dcb1c 100644 --- a/pyramid/tests/test_renderers.py +++ b/pyramid/tests/test_renderers.py @@ -2,6 +2,7 @@ import unittest 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 @@ class Test_string_renderer_factory(unittest.TestCase): 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 @@ class Test_string_renderer_factory(unittest.TestCase): 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 @@ class TestRendererHelper(unittest.TestCase): 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 @@ class TestRendererHelper(unittest.TestCase): 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 @@ class TestRendererHelper(unittest.TestCase): 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 @@ class TestRendererHelper(unittest.TestCase): ('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 @@ class TestRendererHelper(unittest.TestCase): 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 @@ class TestRendererHelper(unittest.TestCase): 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 @@ class TestRendererHelper(unittest.TestCase): 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 @@ class Test_render_to_response(unittest.TestCase): '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 @@ class Test_render_to_response(unittest.TestCase): 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 @@ class Test_render_to_response(unittest.TestCase): 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 066aa9207..5a11acd07 100644 --- a/pyramid/tests/test_request.py +++ b/pyramid/tests/test_request.py @@ -1,6 +1,12 @@ import unittest from pyramid import testing +from pyramid.compat import PY3 +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 +56,7 @@ class TestRequest(unittest.TestCase): } 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 +172,7 @@ class TestRequest(unittest.TestCase): 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 +190,7 @@ class TestRequest(unittest.TestCase): 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,22 +241,28 @@ class TestRequest(unittest.TestCase): 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') + inp = text_( + b'/\xe6\xb5\x81\xe8\xa1\x8c\xe8\xb6\x8b\xe5\x8a\xbf', + 'utf-8' + ) + if PY3: # pragma: no cover + body = bytes(json.dumps({'a':inp}), 'utf-16') + else: + body = json.dumps({'a':inp}).decode('utf-8').encode('utf-16') request.body = body - self.assertEqual(request.json_body, {'a':la}) + request.content_type = 'application/json; charset=utf-16' + self.assertEqual(request.json_body, {'a':inp}) def test_json_body_GET_request(self): request = self._makeOne({'REQUEST_METHOD':'GET'}) @@ -322,17 +334,17 @@ class TestRequestDeprecatedMethods(unittest.TestCase): 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 +382,8 @@ class TestRequestDeprecatedMethods(unittest.TestCase): 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 +509,16 @@ class Test_call_app_with_subpath_as_path_info(unittest.TestCase): 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 a98018500..eb9b7285d 100644 --- a/pyramid/tests/test_router.py +++ b/pyramid/tests/test_router.py @@ -177,7 +177,7 @@ class TestRouter(unittest.TestCase): 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 @@ class TestRouter(unittest.TestCase): 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 @@ class TestRouter(unittest.TestCase): 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 @@ class TestRouter(unittest.TestCase): 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 @@ class TestRouter(unittest.TestCase): 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 @@ class TestRouter(unittest.TestCase): 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 @@ class TestRouter(unittest.TestCase): 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 @@ class TestRouter(unittest.TestCase): 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 TestRouter(unittest.TestCase): 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 TestRouter(unittest.TestCase): 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 @@ class TestRouter(unittest.TestCase): 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 @@ class TestRouter(unittest.TestCase): 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 @@ class TestRouter(unittest.TestCase): 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 TestRouter(unittest.TestCase): 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 TestRouter(unittest.TestCase): 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 @@ class DummyStartResponse: 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 @@ class DummyLogger: 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 ed2c5a993..265e20c3b 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 5c6454a38..6d75c7950 100644 --- a/pyramid/tests/test_session.py +++ b/pyramid/tests/test_session.py @@ -263,21 +263,15 @@ class Test_manage_accessed(unittest.TestCase): 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 5cc4ce561..d02b3cd3e 100644 --- a/pyramid/tests/test_settings.py +++ b/pyramid/tests/test_settings.py @@ -87,7 +87,7 @@ class Test_aslist(unittest.TestCase): 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 6dc38fc57..9fdf29637 100644 --- a/pyramid/tests/test_static.py +++ b/pyramid/tests/test_static.py @@ -37,14 +37,14 @@ class Test_static_view_use_subpath_False(unittest.TestCase): 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('<html>static</html>' in response.body) + self.assertTrue(b'<html>static</html>' in response.body) def test_oob_singledot(self): inst = self._makeOne('pyramid.tests:fixtures/static') @@ -52,7 +52,7 @@ class Test_static_view_use_subpath_False(unittest.TestCase): context = DummyContext() response = inst(context, request) self.assertEqual(response.status, '200 OK') - self.assertTrue('<html>static</html>' in response.body) + self.assertTrue(b'<html>static</html>' in response.body) def test_oob_emptyelement(self): inst = self._makeOne('pyramid.tests:fixtures/static') @@ -60,7 +60,7 @@ class Test_static_view_use_subpath_False(unittest.TestCase): context = DummyContext() response = inst(context, request) self.assertEqual(response.status, '200 OK') - self.assertTrue('<html>static</html>' in response.body) + self.assertTrue(b'<html>static</html>' in response.body) def test_oob_dotdotslash(self): inst = self._makeOne('pyramid.tests:fixtures/static') @@ -99,21 +99,21 @@ class Test_static_view_use_subpath_False(unittest.TestCase): request = self._makeRequest({'PATH_INFO':'/subdir/'}) context = DummyContext() response = inst(context, request) - self.assertTrue('<html>subdir</html>' in response.body) + self.assertTrue(b'<html>subdir</html>' 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('<html>static</html>' in response.body) + self.assertTrue(b'<html>static</html>' 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('<html>static</html>' in response.body) + self.assertTrue(b'<html>static</html>' 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 @@ class Test_static_view_use_subpath_False(unittest.TestCase): request = self._makeRequest({'PATH_INFO':'/index.html'}) context = DummyContext() response = inst(context, request) - self.assertTrue('<html>static</html>' in response.body) + self.assertTrue(b'<html>static</html>' in response.body) self.assertEqual(len(response.headerlist), 3) header_names = [ x[0] for x in response.headerlist ] header_names.sort() @@ -143,8 +143,11 @@ class Test_static_view_use_subpath_False(unittest.TestCase): response = inst(context, request) start_response = DummyStartResponse() app_iter = response(request.environ, start_response) - self.assertEqual(start_response.status, '304 Not Modified') - self.assertEqual(list(app_iter), []) + try: + self.assertEqual(start_response.status, '304 Not Modified') + self.assertEqual(list(app_iter), []) + finally: + app_iter.close() def test_not_found(self): inst = self._makeOne('pyramid.tests:fixtures/static') @@ -192,7 +195,7 @@ class Test_static_view_use_subpath_True(unittest.TestCase): 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 +203,7 @@ class Test_static_view_use_subpath_True(unittest.TestCase): request.subpath = () context = DummyContext() response = inst(context, request) - self.assertTrue('<html>static</html>' in response.body) + self.assertTrue(b'<html>static</html>' in response.body) def test_oob_singledot(self): inst = self._makeOne('pyramid.tests:fixtures/static') @@ -258,7 +261,7 @@ class Test_static_view_use_subpath_True(unittest.TestCase): request.subpath = ('subdir',) context = DummyContext() response = inst(context, request) - self.assertTrue('<html>subdir</html>' in response.body) + self.assertTrue(b'<html>subdir</html>' in response.body) def test_resource_is_file(self): inst = self._makeOne('pyramid.tests:fixtures/static') @@ -266,7 +269,7 @@ class Test_static_view_use_subpath_True(unittest.TestCase): request.subpath = ('index.html',) context = DummyContext() response = inst(context, request) - self.assertTrue('<html>static</html>' in response.body) + self.assertTrue(b'<html>static</html>' 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 +277,7 @@ class Test_static_view_use_subpath_True(unittest.TestCase): request.subpath = ('index.html',) context = DummyContext() response = inst(context, request) - self.assertTrue('<html>static</html>' in response.body) + self.assertTrue(b'<html>static</html>' in response.body) self.assertEqual(len(response.headerlist), 5) header_names = [ x[0] for x in response.headerlist ] header_names.sort() @@ -289,7 +292,7 @@ class Test_static_view_use_subpath_True(unittest.TestCase): request.subpath = ('index.html',) context = DummyContext() response = inst(context, request) - self.assertTrue('<html>static</html>' in response.body) + self.assertTrue(b'<html>static</html>' in response.body) self.assertEqual(len(response.headerlist), 3) header_names = [ x[0] for x in response.headerlist ] header_names.sort() @@ -306,8 +309,11 @@ class Test_static_view_use_subpath_True(unittest.TestCase): response = inst(context, request) start_response = DummyStartResponse() app_iter = response(request.environ, start_response) - self.assertEqual(start_response.status, '304 Not Modified') - self.assertEqual(list(app_iter), []) + try: + self.assertEqual(start_response.status, '304 Not Modified') + self.assertEqual(list(app_iter), []) + finally: + app_iter.close() def test_not_found(self): inst = self._makeOne('pyramid.tests:fixtures/static') diff --git a/pyramid/tests/test_testing.py b/pyramid/tests/test_testing.py index 41a4788ec..05ef36fe9 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 @@ class Test_registerDummySecurityPolicy(TestBase): 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 @@ class Test_registerResources(TestBase): 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 @@ class Test_registerView(TestBase): 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 @@ class Test_registerView(TestBase): 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 @@ class Test_registerView(TestBase): 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 Test_registerAdapter(TestBase): 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 TestDummyResource(unittest.TestCase): 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 @@ class Test_setUp(unittest.TestCase): 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 @@ class Test_tearDown(unittest.TestCase): 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 @@ class Test_tearDown(unittest.TestCase): 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 @@ class TestDummySession(unittest.TestCase): 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 95caf21be..72192b23b 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 @@ class ResourceTreeTraverserTests(unittest.TestCase): 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 @@ class ResourceTreeTraverserTests(unittest.TestCase): 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 @@ class ResourceTreeTraverserTests(unittest.TestCase): 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 @@ class ResourceTreeTraverserTests(unittest.TestCase): 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 @@ class ResourceTreeTraverserTests(unittest.TestCase): 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 @@ class ResourceTreeTraverserTests(unittest.TestCase): 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 @@ class ResourceTreeTraverserTests(unittest.TestCase): 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 @@ class FindResourceTests(unittest.TestCase): 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 @@ class QuotePathSegmentTests(unittest.TestCase): 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 @@ class QuotePathSegmentTests(unittest.TestCase): 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 @@ class TraversalContextURLTests(unittest.TestCase): 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 @@ class TraverseTests(unittest.TestCase): 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 @@ class DummyContext(object): 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 8b95374fb..4c39d8e9c 100644 --- a/pyramid/tests/test_url.py +++ b/pyramid/tests/test_url.py @@ -2,6 +2,8 @@ import unittest 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 @@ class TestURLMethodsMixin(unittest.TestCase): 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 @@ class TestURLMethodsMixin(unittest.TestCase): 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 @@ class TestURLMethodsMixin(unittest.TestCase): 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 @@ class TestURLMethodsMixin(unittest.TestCase): 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 @@ class TestURLMethodsMixin(unittest.TestCase): 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 @@ class TestURLMethodsMixin(unittest.TestCase): 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 @@ class TestURLMethodsMixin(unittest.TestCase): 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 @@ class TestURLMethodsMixin(unittest.TestCase): 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 @@ class TestURLMethodsMixin(unittest.TestCase): 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 @@ class TestURLMethodsMixin(unittest.TestCase): 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 3c92e87be..be823b045 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 @@ class RoutesMapperTests(unittest.TestCase): 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 @@ class TestCompileRoute(unittest.TestCase): '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 @@ class TestCompileRoute(unittest.TestCase): 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 @@ class TestCompileRouteFunctional(unittest.TestCase): 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 @@ class TestCompileRouteFunctional(unittest.TestCase): 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 @@ class TestCompileRouteFunctional(unittest.TestCase): '/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 @@ class TestCompileRouteFunctional(unittest.TestCase): '/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 247b61dad..7c870bc45 100644 --- a/pyramid/tests/test_util.py +++ b/pyramid/tests/test_util.py @@ -1,4 +1,5 @@ import unittest +from pyramid.compat import PY3 class TestDottedNameResolver(unittest.TestCase): def _makeOne(self, package=None): @@ -9,11 +10,19 @@ class TestDottedNameResolver(unittest.TestCase): 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 + def test_zope_dottedname_style_resolve_builtin(self): + typ = self._makeOne() + if PY3: + result = typ._zope_dottedname_style('builtins.str') + else: + result = typ._zope_dottedname_style('__builtin__.str') + self.assertEqual(result, str) + def test_zope_dottedname_style_resolve_absolute(self): typ = self._makeOne() result = typ._zope_dottedname_style( @@ -190,7 +199,7 @@ class Test_WeakOrderedSet(unittest.TestCase): 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 +210,8 @@ class Test_WeakOrderedSet(unittest.TestCase): 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 +221,7 @@ class Test_WeakOrderedSet(unittest.TestCase): 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 7f66a7563..29e468cd2 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 @@ class TestViewConfigDecorator(unittest.TestCase): 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 @@ class DummyRequest: 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 bf2606529..ee6b5fb7a 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 IRequestFactory 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'/<unprintable unicode>' (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'/<unprintable unicode>' (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 @@ class ResourceTreeTraverser(object): 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 @@ class ResourceTreeTraverser(object): 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 @@ class ResourceTreeTraverser(object): # 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 @@ class ResourceTreeTraverser(object): 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 b15204e9d..65d7c3919 100644 --- a/pyramid/tweens.py +++ b/pyramid/tweens.py @@ -15,7 +15,7 @@ def excview_tween_factory(handler, registry): 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 c2ff43ddd..7a7dd3b4c 100644 --- a/pyramid/url.py +++ b/pyramid/url.py @@ -8,6 +8,8 @@ from pyramid.interfaces import IContextURL 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 @@ class URLMethodsMixin(object): 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 @@ class URLMethodsMixin(object): 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 73318193c..662615845 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 @@ class Route(object): 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 _compile_route(route): 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 _compile_route(route): 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 c0e7640c4..a43b50aef 100644 --- a/pyramid/util.py +++ b/pyramid/util.py @@ -2,6 +2,7 @@ import pkg_resources 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 @@ class DottedNameResolver(object): 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 @@ class DottedNameResolver(object): 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 6046408f6..581e42185 100644 --- a/pyramid/view.py +++ b/pyramid/view.py @@ -7,6 +7,7 @@ from pyramid.interfaces import IRoutesMapper 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: @@ -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 @@ except IOError: 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 ' @@ -66,6 +69,10 @@ setup(name='pyramid', classifiers=[ "Intended Audience :: Developers", "Programming Language :: Python", + "Programming Language :: Python :: 2.6", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.2", "Framework :: Pylons", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: WSGI", @@ -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 |
