Skip to content

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also .

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also .
...
  • 10 commits
  • 45 files changed
  • 0 commit comments
  • 1 contributor
Showing with 302 additions and 287 deletions.
  1. +1 −63 configuration.rst → configuration/django_settings.rst
  2. +7 −0 configuration/index.rst
  3. 0 { → database}/catalog.rst
  4. +1 −1 { → database}/couchdb.rst
  5. +11 −0 database/index.rst
  6. 0 mongo.rst → database/mongodb.rst
  7. +1 −1 sqla.rst → database/sqlalchemy.rst
  8. 0 zeo.rst → database/zodb_zeo.rst
  9. +57 −0 deployment/deployment.rst
  10. 0 deployment.rst → deployment/deployment_long.rst
  11. 0 { → deployment}/gae.rst
  12. +5 −53 deployment/index.rst
  13. +65 −0 forms/file_uploads.rst
  14. +6 −0 forms/index.rst
  15. +0 −140 glossary.rst
  16. +15 −24 index.rst
  17. +7 −0 logging/index.rst
  18. 0 { → logging}/sqlalchemy_logger.rst
  19. 0 { → misc}/events.rst
  20. +9 −0 misc/index.rst
  21. 0 { → misc}/interfaces.rst
  22. 0 { → misc}/mac_install.rst
  23. 0 { → porting}/cmf/actions.rst
  24. 0 { → porting}/cmf/catalog.rst
  25. 0 { → porting}/cmf/content.rst
  26. 0 { → porting}/cmf/index.rst
  27. 0 { → porting}/cmf/missing.rst
  28. 0 { → porting}/cmf/skins.rst
  29. 0 { → porting}/cmf/workflow.rst
  30. +12 −0 porting/index.rst
  31. 0 { → porting}/legacy.rst
  32. 0 porting.rst → porting/wsgi.rst
  33. +4 −0 pylons/index.rst
  34. 0 traversal.rst → routing/combining.rst
  35. +7 −0 routing/index.rst
  36. +5 −5 { → static_assets}/files.rst
  37. +7 −0 static_assets/index.rst
  38. 0 { → templates}/chameleon_i18n.rst
  39. +9 −0 templates/index.rst
  40. 0 i18n.rst → templates/mako_i18n.rst
  41. 0 { → templates}/templates.rst
  42. +7 −0 testing/index.rst
  43. 0 testing.rst → testing/testing_post_curl.rst
  44. +59 −0 views/chaining_decorators.rst
  45. +7 −0 views/index.rst
View
64 configuration.rst → configuration/django_settings.rst
@@ -1,7 +1,5 @@
-.. _configuration:
-
Django-Style "settings.py" Configuration
-----------------------------------------
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
If you enjoy accessing global configuration via import statemetns ala
Django's ``settings.py``, you can do something similar in Pyramid.
@@ -73,63 +71,3 @@ this format::
the section name in the config file that represents your app
(e.g. ``[app:myapp]``). In the above example, your application will refuse
to start without this environment variable being present.
-
-Chaining Decorators
--------------------
-
-Pyramid has a ``decorator=`` argument to its view configuration. It accepts
-a single decorator that will wrap the *mapped* view callable represented by
-the view configuration. That means that, no matter what the signature and
-return value of the original view callable, the decorated view callable will
-receive two arguments: ``context`` and ``request`` and will return a response
-object:
-
-.. code-block:: python
-
- # the decorator
-
- def decorator(view_callable):
- def inner(context, request):
- return view_callable(context, request)
- return inner
-
- # the view configuration
-
- @view_config(decorator=decorator, renderer='json')
- def myview(request):
- return {'a':1}
-
-But the ``decorator`` argument only takes a single decorator. What happens
-if you want to use more than one decorator? You can chain them together:
-
-.. code-block:: python
-
- def combine(*decorators):
- def floo(view_callable):
- for decorator in decorators:
- view_callable = decorator(view_callable)
- return view_callable
- return floo
-
- def decorator1(view_callable):
- def inner(context, request):
- return view_callable(context, request)
- return inner
-
- def decorator2(view_callable):
- def inner(context, request):
- return view_callable(context, request)
- return inner
-
- def decorator3(view_callable):
- def inner(context, request):
- return view_callable(context, request)
- return inner
-
- alldecs = combine(decorator1, decorator2, decorator3)
- two_and_three = combine(decorator2, decorator3)
- one_and_three = combine(decorator1, decorator3)
-
- @view_config(decorator=alldecs, renderer='json')
- def myview(request):
- return {'a':1}
View
7 configuration/index.rst
@@ -0,0 +1,7 @@
+Configuration
+%%%%%%%%%%%%%
+
+.. toctree::
+ :maxdepth: 2
+
+ django_settings
View
0 catalog.rst → database/catalog.rst
File renamed without changes.
View
2 couchdb.rst → database/couchdb.rst
@@ -5,7 +5,7 @@ If you want to use CouchDB (via the
`CouchDB package <http://pypi.python.org/pypi/CouchDB>`_)
in Pyramid, you
can use the following pattern to make your CouchDB database available as a
-request attribute. (This follows the same pattern as the :doc:`mongo` example.)
+request attribute. (This follows the same pattern as the :doc:`mongodb` example.)
First add some hair to your ``development.ini`` file, including a CouchDB URI
and a "db_name" (the CouchDB database name, can be anything).
View
11 database/index.rst
@@ -0,0 +1,11 @@
+Databases
+%%%%%%%%%
+
+.. toctree::
+ :maxdepth: 2
+
+ sqlalchemy
+ couchdb
+ mongodb
+ zodb_zeo
+ catalog
View
0 mongo.rst → database/mongodb.rst
File renamed without changes.
View
2 sqla.rst → database/sqlalchemy.rst
@@ -37,7 +37,7 @@ decorator, but it is only called once per request: effectively the first time
it's called, the result of ``maker()`` replaces the decorator as
``request.db``.
-You can then use MyRequest as a :term:`request factory` within your
+You can then use MyRequest as a request factory within your
``__init__.py`` ``main`` function:
.. code-block:: python
View
0 zeo.rst → database/zodb_zeo.rst
File renamed without changes.
View
57 deployment/deployment.rst
@@ -0,0 +1,57 @@
+Deploying Your Pyramid Application
+----------------------------------
+
+So you've written a sweet application and you want to deploy it outside of
+your local machine. We're not going to cover caching here, but suffice it to
+say that there are a lot of things to consider when optimizing your pyramid
+application.
+
+At a high level, you need to expose a server on ports 80 (HTTP) and 443
+(HTTPS) to handle your various traffic. Underneath this layer, however, is
+a plethora of different configurations that can be used to get a request
+from a client, into your application, and return the response.
+
+::
+
+ Client <---> WSGI Server <---> Your Application
+
+Due to the beauty of standards, many different configurations can be used to
+generate this basic setup, injecting caching layers, load balancers, etc into
+the basic workflow.
+
+Disclaimer
+++++++++++
+
+It's important to note that the setups discussed here are meant to give some
+direction to newer users. Deployment is *almost always* highly dependent on
+the application's specific purposes. These setups have been used for many
+different projects in production with much success, but never verbatim.
+
+What is WSGI?
++++++++++++++
+
+WSGI is a Python standard dictating the interface between a server and an
+application. The entry point to your pyramid application is an object
+implementing the WSGI interface. Thus, your application can be served by any
+server supporting WSGI.
+
+There are many different servers implementing the WSGI standard in existance.
+A short list includes:
+
++ ``paste.httpserver``
+
++ ``CherryPy``
+
++ ``uwsgi``
+
++ ``gevent``
+
++ ``mod_wsgi``
+
+.. toctree::
+ :maxdepth: 2
+
+ nginx
+ apache
+ gevent
+ heroku
View
0 deployment.rst → deployment/deployment_long.rst
File renamed without changes.
View
0 gae.rst → deployment/gae.rst
File renamed without changes.
View
58 deployment/index.rst
@@ -1,57 +1,9 @@
-Deploying Your Pyramid Application
-----------------------------------
-
-So you've written a sweet application and you want to deploy it outside of
-your local machine. We're not going to cover caching here, but suffice it to
-say that there are a lot of things to consider when optimizing your pyramid
-application.
-
-At a high level, you need to expose a server on ports 80 (HTTP) and 443
-(HTTPS) to handle your various traffic. Underneath this layer, however, is
-a plethora of different configurations that can be used to get a request
-from a client, into your application, and return the response.
-
-::
-
- Client <---> WSGI Server <---> Your Application
-
-Due to the beauty of standards, many different configurations can be used to
-generate this basic setup, injecting caching layers, load balancers, etc into
-the basic workflow.
-
-Disclaimer
-++++++++++
-
-It's important to note that the setups discussed here are meant to give some
-direction to newer users. Deployment is *almost always* highly dependent on
-the application's specific purposes. These setups have been used for many
-different projects in production with much success, but never verbatim.
-
-What is WSGI?
-+++++++++++++
-
-WSGI is a Python standard dictating the interface between a server and an
-application. The entry point to your pyramid application is an object
-implementing the WSGI interface. Thus, your application can be served by any
-server supporting WSGI.
-
-There are many different servers implementing the WSGI standard in existance.
-A short list includes:
-
-+ ``paste.httpserver``
-
-+ ``CherryPy``
-
-+ ``uwsgi``
-
-+ ``gevent``
-
-+ ``mod_wsgi``
+Deployment
+%%%%%%%%%%
.. toctree::
:maxdepth: 2
- nginx
- apache
- gevent
- heroku
+ deployment
+ Deploying (TODO: consolidate into previous section) <deployment_long>
+ gae
View
65 forms/file_uploads.rst
@@ -0,0 +1,65 @@
+File Uploads
+%%%%%%%%%%%%
+
+There are two parts necessary for handling file uploads. The first is to
+make sure you have a form that's been setup correctly to accept files. This
+means adding ``enctype`` attribute to your ``form`` element with the value of
+``multipart/form-data``. A very simple example would be a form that accepts
+an mp3 file. Notice we've setup the form as previously explained and also
+added an ``input`` element of the ``file`` type.
+
+.. code-block:: html
+ :linenos:
+
+ <form action="/store_mp3_view" method="post" accept-charset="utf-8"
+ enctype="multipart/form-data">
+
+ <label for="mp3">Mp3</label>
+ <input id="mp3" name="mp3" type="file" value="" />
+
+ <input type="submit" value="submit" />
+ </form>
+
+The second part is handling the file upload in your view callable (above,
+assumed to answer on ``/store_mp3_view``). The uploaded file is added to the
+request object as a ``cgi.FieldStorage`` object accessible through the
+``request.POST`` multidict. The two properties we're interested in are the
+``file`` and ``filename`` and we'll use those to write the file to disk.
+
+.. code-block:: python
+ :linenos:
+
+ import os
+ from pyramid.response import Response
+
+ def store_mp3_view(request):
+ # ``filename`` contains the name of the file in string format.
+ #
+ # WARNING: this example does not deal with the fact that IE sends an
+ # absolute file *path* as the filename. This example is naive; it
+ # trusts user input.
+
+ filename = request.POST['mp3'].filename
+
+ # ``input_file`` contains the actual file data which needs to be
+ # stored somewhere.
+
+ input_file = request.POST['mp3'].file
+
+ # Using the filename like this without cleaning it is very
+ # insecure so please keep that in mind when writing your own
+ # file handling.
+ file_path = os.path.join('/tmp', filename)
+ output_file = open(file_path, 'wb')
+
+ # Finally write the data to the output file
+ input_file.seek(0)
+ while 1:
+ data = input_file.read(2<<16)
+ if not data:
+ break
+ output_file.write(data)
+ output_file.close()
+
+ return Response('OK')
+
View
6 forms/index.rst
@@ -0,0 +1,6 @@
+Forms
+%%%%%
+
+.. toctree::
+
+ file_uploads
View
140 glossary.rst
@@ -1,140 +0,0 @@
-.. _glossary:
-
-Glossary
-========
-
-.. glossary::
- :sorted:
-
- traversal
- The act of descending "up" a tree of resource objects from a root
- resource in order to find a :term:`context` resource. See the
- :ref:`traversal_chapter` chapter for more information.
-
- context
- An resource in the resource tree that is found during :term:`traversal`
- or :term:`URL dispatch` based on URL data; if it's found via traversal,
- it's usually a :term:`resource` object that is part of a resource tree;
- if it's found via :term:`URL dispatch`, it's a object manufactured on
- behalf of the route's "factory". A context resource becomes the subject
- of a :term:`view`, and often has security information attached to
- it. See the :ref:`traversal_chapter` chapter and the
- :ref:`urldispatch_chapter` chapter for more information about how a URL
- is resolved to a context resource.
-
- resource
- An object representing a node in the :term:`resource tree` of an
- application. If :mod:`traversal` is used, a resource is an element in
- the resource tree traversed by the system. When traversal is used, a
- resource becomes the :term:`context` of a :term:`view`. If :mod:`url
- dispatch` is used, a single resource is generated for each request and
- is used as the context resource of a view.
-
- resource tree
- A nested set of dictionary-like objects, each of which is a
- :term:`resource`. The act of :term:`traversal` uses the resource tree
- to find a :term:`context` resource.
-
- URL dispatch
- An alternative to :term:`traversal` as a mechanism for locating a
- :term:`context` resource for a :term:`view`. When you use a
- :term:`route` in your Pyramid application via a :term:`route
- configuration`, you are using URL dispatch. See the
- :ref:`urldispatch_chapter` for more information.
-
- view
- Common vernacular for a :term:`view callable`.
-
- view callable
- A "view callable" is a callable Python object which is associated
- with a :term:`view configuration`; it returns a :term:`response`
- object . A view callable accepts a single argument: ``request``,
- which will be an instance of a :term:`request` object. An
- alternate calling convention allows a view to be defined as a
- callable which accepts a pair of arguments: ``context`` and
- ``request``: this calling convention is useful for
- traversal-based applications in which a :term:`context` is always
- very important. A view callable is the primary mechanism by
- which a developer writes user interface code within
- Pyramid. See :ref:`views_chapter` for more information
- about Pyramid view callables.
-
- request
- A ``WebOb`` request object. See :ref:`webob_chapter` (narrative)
- and :ref:`request_module` (API documentation) for information
- about request objects.
-
- response
- An object that has three attributes: ``app_iter`` (representing an
- iterable body), ``headerlist`` (representing the http headers sent
- to the user agent), and ``status`` (representing the http status
- string sent to the user agent). This is the interface defined for
- ``WebOb`` response objects. See :ref:`webob_chapter` for
- information about response objects.
-
- router
- The :term:`WSGI` application created when you start a Pyramid
- application. The router intercepts requests, invokes traversal and/or
- URL dispatch, calls view functions, and returns responses to the WSGI
- server on behalf of your Pyramid application.
-
- WSGI
- `Web Server Gateway Interface <http://wsgi.org/>`_. This is a
- Python standard for connecting web applications to web servers,
- similar to the concept of Java Servlets. Pyramid requires
- that your application be served as a WSGI application.
-
- view configuration
- View configuration is the act of associating a :term:`view callable`
- with configuration information. This configuration information helps
- map a given :term:`request` to a particular view callable and it can
- influence the response of a view callable. See
- :ref:`view_config_chapter` for more information about view
- configuration.
-
- route
- A single pattern matched by the :term:`url dispatch` subsystem,
- which generally resolves to a :term:`root factory` (and then
- ultimately a :term:`view`). See also :term:`url dispatch`.
-
- root factory
- The "root factory" of an Pyramid application is called
- on every request sent to the application. The root factory
- returns the traversal root of an application.
-
- route configuration
- Route configuration is the act of associating request parameters with a
- particular :term:`route` using pattern matching. See
- :ref:`urldispatch_chapter` for more information about route
- configuration.
-
- asset
- Any file contained within a Python :term:`package` which is *not*
- a Python source code file.
-
- asset specification
- A colon-delimited identifier for an :term:`asset`. The colon
- separates a Python :term:`package` name from a package subpath.
- For example, the asset specification
- ``my.package:static/baz.css`` identifies the file named
- ``baz.css`` in the ``static`` subdirectory of the ``my.package``
- Python :term:`package`. See :ref:`asset_specifications` for more
- info.
-
- package
- A directory on disk which contains an ``__init__.py`` file, making
- it recognizable to Python as a location which can be ``import`` -ed.
- A package exists to contain :term:`module` files.
-
- module
- A Python source file; a file on the filesystem that typically ends with
- the extension ``.py`` or ``.pyc``. Modules often live in a
- :term:`package`.
-
- static view
- A view added to a Pyramid application via
- :meth:`pyramid.config.Configurator.add_static_view`.
-
- request factory
- An object which, provided a WSGI environment as a single
- positional argument, returns a ``WebOb`` compatible request.
View
39 index.rst
@@ -4,36 +4,27 @@ Pyramid Cookbook
The Pyramid Cookbook presents topical, practical usages of :mod:`Pyramid`.
.. toctree::
- :maxdepth: 2
+ :maxdepth: 1
auth/index
+ configuration/index
+ database/index
debugging
deployment/index
- Deplying Your Pyramid Application (TODO: consolidate into previous section) <deployment>
-
- files
- templates
- catalog
- sqla
- sqlalchemy_logger
- mongo
- legacy
- couchdb
- i18n
- interfaces
- zeo
- configuration
- events
- chameleon_i18n
- porting
- testing
- traversal
- cmf/index.rst
- mac_install
- gae
- glossary
+ forms/index
+ logging/index
+ porting/index
+ pylons/index
+ routing/index
+ static_assets/index
+ templates/index
+ testing/index
+ views/index
+ misc/index
todo
+:ref:`Pyramid Glossary <glossary>`
+
Indices and tables
==================
View
7 logging/index.rst
@@ -0,0 +1,7 @@
+Logging
+%%%%%%%
+
+.. toctree::
+ :maxdepth: 2
+
+ sqlalchemy_logger
View
0 sqlalchemy_logger.rst → logging/sqlalchemy_logger.rst
File renamed without changes.
View
0 events.rst → misc/events.rst
File renamed without changes.
View
9 misc/index.rst
@@ -0,0 +1,9 @@
+Miscellaneous
+%%%%%%%%%%%%%
+
+.. toctree::
+ :maxdepth: 2
+
+ interfaces
+ events
+ mac_install
View
0 interfaces.rst → misc/interfaces.rst
File renamed without changes.
View
0 mac_install.rst → misc/mac_install.rst
File renamed without changes.
View
0 cmf/actions.rst → porting/cmf/actions.rst
File renamed without changes.
View
0 cmf/catalog.rst → porting/cmf/catalog.rst
File renamed without changes.
View
0 cmf/content.rst → porting/cmf/content.rst
File renamed without changes.
View
0 cmf/index.rst → porting/cmf/index.rst
File renamed without changes.
View
0 cmf/missing.rst → porting/cmf/missing.rst
File renamed without changes.
View
0 cmf/skins.rst → porting/cmf/skins.rst
File renamed without changes.
View
0 cmf/workflow.rst → porting/cmf/workflow.rst
File renamed without changes.
View
12 porting/index.rst
@@ -0,0 +1,12 @@
+Porting Applications to Pyramid
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+*Note:* Other articles about Pylons applications are in the
+:doc:`../pylons/index` section.
+
+.. toctree::
+ :maxdepth: 2
+
+ legacy
+ wsgi
+ cmf/index
View
0 legacy.rst → porting/legacy.rst
File renamed without changes.
View
0 porting.rst → porting/wsgi.rst
File renamed without changes.
View
4 pylons/index.rst
@@ -0,0 +1,4 @@
+Pyramid for Pylons Users
+%%%%%%%%%%%%%%%%%%%%%%%%
+
+Coming soon.
View
0 traversal.rst → routing/combining.rst
File renamed without changes.
View
7 routing/index.rst
@@ -0,0 +1,7 @@
+Traversal and URL dispatch
+%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+.. toctree::
+ :maxdepth: 2
+
+ combining
View
10 files.rst → static_assets/files.rst
@@ -6,7 +6,7 @@ These cookbook recipes deal with serving files and handling file uploads.
Serving File Content Dynamically
--------------------------------
-Usually you'll use a :term:`static view` (via "config.add_static_view") to
+Usually you'll use a static view (via "config.add_static_view") to
serve file content that lives on the filesystem. But sometimes files need to
be composed and read from a nonstatic area, or composed on the fly by view
code and served out (for example, a view callable might construct and return
@@ -43,7 +43,7 @@ Serving a Single File from the Root
If you need to serve a single file such as ``/robots.txt`` or
``/favicon.ico`` that *must* be served from the root, you cannot use a
-:term:`static view` to do it, as static views cannot serve files from the
+static view to do it, as static views cannot serve files from the
root (a static view must have a nonempty prefix such as ``/static``). To
work around this limitation, create views "by hand" that serve up the raw
file data. Below is an example of creating two views: one serves up a
@@ -101,7 +101,7 @@ its behavior is almost exactly the same once it's configured.
.. warning::
The following example *will not work* for applications that use
- :term:`traversal`, it will only work if you use :term:`URL dispatch`
+ traversal, it will only work if you use URL dispatch
exclusively. The root-relative route we'll be registering will always be
matched before traversal takes place, subverting any views registered via
``add_view`` (at least those without a ``route_name``). A
@@ -121,8 +121,8 @@ application root as below.
from pyramid.static import static_view
www = static_view('/path/to/static/dir', use_subpath=True)
-.. note:: For better cross-system flexibility, use an :term:`asset
- specification` as the argument to :class:`pyramid.static.static_view`
+.. note:: For better cross-system flexibility, use an asset
+ specification as the argument to :class:`pyramid.static.static_view`
instead of a physical absolute filesystem path, e.g. ``mypackage:static``
instead of ``/path/to/mypackage/static``.
View
7 static_assets/index.rst
@@ -0,0 +1,7 @@
+Static Assets (Static Files)
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+.. toctree::
+ :maxdepth: 2
+
+ files
View
0 chameleon_i18n.rst → templates/chameleon_i18n.rst
File renamed without changes.
View
9 templates/index.rst
@@ -0,0 +1,9 @@
+Templates and Renderers
+%%%%%%%%%%%%%%%%%%%%%%%
+
+.. toctree::
+ :maxdepth: 2
+
+ templates
+ mako_i18n
+ chameleon_i18n
View
0 i18n.rst → templates/mako_i18n.rst
File renamed without changes.
View
0 templates.rst → templates/templates.rst
File renamed without changes.
View
7 testing/index.rst
@@ -0,0 +1,7 @@
+Testing
+%%%%%%%
+
+.. toctree::
+ :maxdepth: 2
+
+ testing_post_curl
View
0 testing.rst → testing/testing_post_curl.rst
File renamed without changes.
View
59 views/chaining_decorators.rst
@@ -0,0 +1,59 @@
+Chaining Decorators
+%%%%%%%%%%%%%%%%%%%
+
+Pyramid has a ``decorator=`` argument to its view configuration. It accepts
+a single decorator that will wrap the *mapped* view callable represented by
+the view configuration. That means that, no matter what the signature and
+return value of the original view callable, the decorated view callable will
+receive two arguments: ``context`` and ``request`` and will return a response
+object:
+
+.. code-block:: python
+
+ # the decorator
+
+ def decorator(view_callable):
+ def inner(context, request):
+ return view_callable(context, request)
+ return inner
+
+ # the view configuration
+
+ @view_config(decorator=decorator, renderer='json')
+ def myview(request):
+ return {'a':1}
+
+But the ``decorator`` argument only takes a single decorator. What happens
+if you want to use more than one decorator? You can chain them together:
+
+.. code-block:: python
+
+ def combine(*decorators):
+ def floo(view_callable):
+ for decorator in decorators:
+ view_callable = decorator(view_callable)
+ return view_callable
+ return floo
+
+ def decorator1(view_callable):
+ def inner(context, request):
+ return view_callable(context, request)
+ return inner
+
+ def decorator2(view_callable):
+ def inner(context, request):
+ return view_callable(context, request)
+ return inner
+
+ def decorator3(view_callable):
+ def inner(context, request):
+ return view_callable(context, request)
+ return inner
+
+ alldecs = combine(decorator1, decorator2, decorator3)
+ two_and_three = combine(decorator2, decorator3)
+ one_and_three = combine(decorator1, decorator3)
+
+ @view_config(decorator=alldecs, renderer='json')
+ def myview(request):
+ return {'a':1}
View
7 views/index.rst
@@ -0,0 +1,7 @@
+Views
+%%%%%
+
+.. toctree::
+ :maxdepth: 2
+
+ chaining_decorators

No commit comments for this range

Something went wrong with that request. Please try again.