├── .gitignore ├── .travis.yml ├── CONTRIBUTORS.txt ├── README.rst ├── contributing.md ├── docs ├── Makefile ├── auth │ ├── auth_tutorial.rst │ ├── basic.rst │ ├── custom.rst │ ├── enterprise.rst │ ├── index.rst │ ├── jwt_auth.rst │ ├── open_id_auth.rst │ ├── user_object.rst │ └── wiki2_auth.rst ├── automating_development_process │ ├── index.rst │ ├── part1.rst │ ├── part2.rst │ └── part3.rst ├── conf.py ├── configuration │ ├── django_settings.rst │ ├── index.rst │ └── whirlwind_tour.rst ├── database │ ├── couchdb.rst │ ├── index.rst │ ├── mongodb.rst │ └── sqlalchemy.rst ├── debugging │ ├── debugging_pyramid.rst │ ├── index.rst │ ├── pydev.rst │ └── using_pdb.rst ├── deployment │ ├── apache.rst │ ├── asgi.rst │ ├── aws_via_eb.rst │ ├── deployment.rst │ ├── dotcloud.rst │ ├── expresscloud.rst │ ├── forked_threaded_servers.rst │ ├── gae.rst │ ├── gae_buildout.rst │ ├── gae_flex_datastore.rst │ ├── gevent.rst │ ├── gunicorn.rst │ ├── heroku.rst │ ├── index.rst │ ├── nginx.rst │ ├── uwsgi.rst │ ├── uwsgi_cookiecutter_1_nginx.rst │ ├── uwsgi_cookiecutter_2_emperor.rst │ ├── uwsgi_nginx_systemd.rst │ └── windows.rst ├── development_tools │ ├── index.rst │ ├── pycharm.rst │ └── pycharm_images │ │ ├── create_new_project.png │ │ ├── create_setup.png │ │ ├── create_virtual_environment.png │ │ ├── edit_run_debug_configurations.png │ │ ├── install_package.png │ │ ├── install_package_pyramid.png │ │ ├── install_package_setuptools.png │ │ ├── python_interpreters_1.png │ │ ├── python_interpreters_2.png │ │ ├── run_configuration.png │ │ └── start_up_screen.png ├── forms │ ├── file_uploads.rst │ └── index.rst ├── index.rst ├── links.rst ├── logging │ ├── index.rst │ └── sqlalchemy_logger.rst ├── misc │ ├── events.rst │ ├── index.rst │ ├── interfaces.rst │ └── videos.rst ├── porting │ ├── index.rst │ ├── legacy.rst │ └── wsgi.rst ├── pylons │ ├── auth.rst │ ├── code │ │ ├── alchemy_main.py │ │ ├── model_development.ini │ │ ├── models.py │ │ ├── pyramid_handlers.py │ │ └── starter_main.py │ ├── deployment.rst │ ├── examples.rst │ ├── exceptions.rst │ ├── index.rst │ ├── ini_file.rst │ ├── intro.rst │ ├── launch.rst │ ├── main.rst │ ├── migrate.rst │ ├── models.rst │ ├── other.rst │ ├── request.rst │ ├── sessions.rst │ ├── static.rst │ ├── templates.rst │ └── views.rst ├── routing │ ├── combining.rst │ ├── index.rst │ ├── traversal_in_views.rst │ └── traversal_sqlalchemy.rst ├── sample_applications │ ├── index.rst │ ├── single_file_tasks.png │ ├── single_file_tasks.rst │ └── single_file_tasks_src │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── buildout.cfg │ │ ├── schema.sql │ │ ├── static │ │ └── style.css │ │ ├── tasks.py │ │ └── templates │ │ ├── layout.mako │ │ ├── list.mako │ │ ├── new.mako │ │ └── notfound.mako ├── static_assets │ ├── bundling-static-assets.rst │ ├── bundling │ │ ├── .gitignore │ │ ├── bundling_example │ │ │ ├── .coveragerc │ │ │ ├── .gitignore │ │ │ ├── CHANGES.txt │ │ │ ├── MANIFEST.in │ │ │ ├── README.txt │ │ │ ├── bundling_example │ │ │ │ ├── __init__.py │ │ │ │ ├── routes.py │ │ │ │ ├── scripts │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── build_static_assets.py │ │ │ │ ├── static │ │ │ │ │ ├── pyramid-16x16.png │ │ │ │ │ ├── pyramid.png │ │ │ │ │ └── theme.css │ │ │ │ ├── templates │ │ │ │ │ ├── 404.jinja2 │ │ │ │ │ ├── layout.jinja2 │ │ │ │ │ └── mytemplate.jinja2 │ │ │ │ ├── tests.py │ │ │ │ └── views │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── default.py │ │ │ │ │ └── notfound.py │ │ │ ├── development.ini │ │ │ ├── production.ini │ │ │ ├── pytest.ini │ │ │ └── setup.py │ │ └── frontend │ │ │ ├── package.json │ │ │ ├── src │ │ │ ├── index.js │ │ │ ├── sass.js │ │ │ └── sass │ │ │ │ └── main.scss │ │ │ └── webpack.config.js │ ├── index.rst │ ├── serving-files.rst │ └── uploading-files.rst ├── templates │ ├── chameleon_i18n.rst │ ├── customrenderers.rst │ ├── customrendererxlsx.rst │ ├── index.rst │ ├── mako_i18n.rst │ └── templates.rst ├── testing │ ├── index.rst │ └── testing_post_curl.rst ├── todo.rst ├── traversal_tutorial │ ├── addcontent.rst │ ├── addcontent │ │ ├── development.ini │ │ ├── setup.py │ │ └── tutorial │ │ │ ├── __init__.py │ │ │ ├── resources.py │ │ │ ├── templates │ │ │ ├── addform.jinja2 │ │ │ ├── breadcrumbs.jinja2 │ │ │ ├── contents.jinja2 │ │ │ ├── document.jinja2 │ │ │ ├── folder.jinja2 │ │ │ ├── header.jinja2 │ │ │ ├── layout.jinja2 │ │ │ └── root.jinja2 │ │ │ ├── tests.py │ │ │ └── views.py │ ├── hierarchy.rst │ ├── hierarchy │ │ ├── development.ini │ │ ├── setup.py │ │ └── tutorial │ │ │ ├── __init__.py │ │ │ ├── resources.py │ │ │ ├── templates │ │ │ ├── breadcrumbs.jinja2 │ │ │ ├── header.jinja2 │ │ │ ├── hello.jinja2 │ │ │ ├── home.jinja2 │ │ │ └── layout.jinja2 │ │ │ ├── tests.py │ │ │ └── views.py │ ├── index.rst │ ├── layout.rst │ ├── layout │ │ ├── development.ini │ │ ├── setup.py │ │ └── tutorial │ │ │ ├── __init__.py │ │ │ ├── templates │ │ │ ├── breadcrumbs.jinja2 │ │ │ ├── header.jinja2 │ │ │ ├── layout.jinja2 │ │ │ └── site.jinja2 │ │ │ ├── tests.py │ │ │ └── views.py │ ├── requirements.rst │ ├── siteroot.rst │ ├── siteroot │ │ ├── development.ini │ │ ├── setup.py │ │ └── tutorial │ │ │ ├── __init__.py │ │ │ ├── resources.py │ │ │ ├── templates │ │ │ ├── breadcrumbs.jinja2 │ │ │ ├── header.jinja2 │ │ │ ├── hello.jinja2 │ │ │ ├── home.jinja2 │ │ │ └── layout.jinja2 │ │ │ ├── tests.py │ │ │ └── views.py │ ├── sqladdcontent.rst │ ├── sqladdcontent │ │ ├── development.ini │ │ ├── setup.py │ │ └── tutorial │ │ │ ├── __init__.py │ │ │ ├── initialize_db.py │ │ │ ├── models.py │ │ │ ├── sqltraversal.py │ │ │ ├── templates │ │ │ ├── addform.jinja2 │ │ │ ├── breadcrumbs.jinja2 │ │ │ ├── contents.jinja2 │ │ │ ├── document.jinja2 │ │ │ ├── folder.jinja2 │ │ │ ├── header.jinja2 │ │ │ ├── layout.jinja2 │ │ │ └── root.jinja2 │ │ │ └── views.py │ ├── sqlauthentication │ │ ├── development.ini │ │ ├── setup.py │ │ └── tutorial │ │ │ ├── __init__.py │ │ │ ├── initialize_db.py │ │ │ ├── models.py │ │ │ ├── security.py │ │ │ ├── templates │ │ │ ├── addform.jinja2 │ │ │ ├── breadcrumbs.jinja2 │ │ │ ├── contents.jinja2 │ │ │ ├── document.jinja2 │ │ │ ├── folder.jinja2 │ │ │ ├── header.jinja2 │ │ │ ├── layout.jinja2 │ │ │ ├── login.jinja2 │ │ │ └── root.jinja2 │ │ │ └── views.py │ ├── sqlroot.rst │ ├── sqlroot │ │ ├── development.ini │ │ ├── setup.py │ │ └── tutorial │ │ │ ├── __init__.py │ │ │ ├── initialize_db.py │ │ │ ├── models.py │ │ │ ├── templates │ │ │ ├── breadcrumbs.jinja2 │ │ │ ├── header.jinja2 │ │ │ ├── hello.jinja2 │ │ │ ├── home.jinja2 │ │ │ └── layout.jinja2 │ │ │ └── views.py │ ├── typeviews.rst │ ├── typeviews │ │ ├── development.ini │ │ ├── setup.py │ │ └── tutorial │ │ │ ├── __init__.py │ │ │ ├── resources.py │ │ │ ├── templates │ │ │ ├── breadcrumbs.jinja2 │ │ │ ├── contents.jinja2 │ │ │ ├── document.jinja2 │ │ │ ├── folder.jinja2 │ │ │ ├── header.jinja2 │ │ │ ├── layout.jinja2 │ │ │ └── root.jinja2 │ │ │ ├── tests.py │ │ │ └── views.py │ ├── zodb.rst │ └── zodb │ │ ├── development.ini │ │ ├── setup.py │ │ └── tutorial │ │ ├── __init__.py │ │ ├── resources.py │ │ ├── templates │ │ ├── addform.jinja2 │ │ ├── breadcrumbs.jinja2 │ │ ├── contents.jinja2 │ │ ├── document.jinja2 │ │ ├── folder.jinja2 │ │ ├── header.jinja2 │ │ ├── layout.jinja2 │ │ └── root.jinja2 │ │ └── views.py └── views │ ├── chaining_decorators.rst │ ├── conditional_http.rst │ ├── index.rst │ └── params_mapper.rst ├── rtd.txt ├── setup.cfg ├── setup.py └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg 2 | *.egg-info 3 | .eggs/ 4 | *.pyc 5 | *.swp 6 | build/ 7 | dist/ 8 | docs/_build 9 | env/ 10 | .DS_Store 11 | 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Wire up travis 2 | language: python 3 | sudo: false 4 | 5 | matrix: 6 | include: 7 | - python: 3.6 8 | env: TOXENV=docs 9 | 10 | install: 11 | - travis_retry pip install tox 12 | 13 | script: 14 | - travis_retry tox 15 | 16 | cache: 17 | directories: 18 | - $HOME/.cache/pip 19 | 20 | notifications: 21 | email: 22 | - pyramid-checkins@lists.repoze.org 23 | irc: 24 | channels: 25 | - "chat.freenode.net#pyramid" 26 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Pyramid Community Cookbook 2 | ========================== 3 | 4 | The Pyramid Community Cookbook presents topical, practical "recipes" of using 5 | Pyramid. It supplements the `main Pyramid documentation 6 | `_. 7 | 8 | To contribute your recipe to the Pyramid Community Cookbook, read `Contributing 9 | `_. 10 | -------------------------------------------------------------------------------- /docs/auth/auth_tutorial.rst: -------------------------------------------------------------------------------- 1 | Pyramid Auth Demo 2 | ================= 3 | 4 | See Michael Merickel's article `Pyramid Auth Demo 5 | `_ with its `code on 6 | GitHub `_ for a demonstration 7 | of Pyramid authentication and authorization. -------------------------------------------------------------------------------- /docs/auth/basic.rst: -------------------------------------------------------------------------------- 1 | HTTP Basic Authentication Policy 2 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 3 | 4 | To adopt basic HTTP authentication, you can use Pyramid's built-in authentication policy, :class:`pyramid.authentication.BasicAuthAuthenticationPolicy`. 5 | 6 | This is a complete working example with very simple authentication and authorization:: 7 | 8 | from pyramid.authentication import BasicAuthAuthenticationPolicy 9 | from pyramid.authorization import ACLAuthorizationPolicy 10 | from pyramid.config import Configurator 11 | from pyramid.httpexceptions import HTTPForbidden 12 | from pyramid.httpexceptions import HTTPUnauthorized 13 | from pyramid.security import ALL_PERMISSIONS 14 | from pyramid.security import Allow 15 | from pyramid.security import Authenticated 16 | from pyramid.security import forget 17 | from pyramid.view import forbidden_view_config 18 | from pyramid.view import view_config 19 | 20 | @view_config(route_name='home', renderer='json', permission='view') 21 | def home_view(request): 22 | return { 23 | 'page': 'home', 24 | 'userid': request.authenticated_userid, 25 | 'principals': request.effective_principals, 26 | 'context_type': str(type(request.context)), 27 | } 28 | 29 | @forbidden_view_config() 30 | def forbidden_view(request): 31 | if request.authenticated_userid is None: 32 | response = HTTPUnauthorized() 33 | response.headers.update(forget(request)) 34 | 35 | # user is logged in but doesn't have permissions, reject wholesale 36 | else: 37 | response = HTTPForbidden() 38 | return response 39 | 40 | def check_credentials(username, password, request): 41 | if username == 'admin' and password == 'admin': 42 | # an empty list is enough to indicate logged-in... watch how this 43 | # affects the principals returned in the home view if you want to 44 | # expand ACLs later 45 | return [] 46 | 47 | class Root: 48 | # dead simple, give everyone who is logged in any permission 49 | # (see the home_view for an example permission) 50 | __acl__ = ( 51 | (Allow, Authenticated, ALL_PERMISSIONS), 52 | ) 53 | 54 | def main(global_conf, **settings): 55 | config = Configurator(settings=settings) 56 | 57 | authn_policy = BasicAuthAuthenticationPolicy(check_credentials) 58 | config.set_authentication_policy(authn_policy) 59 | config.set_authorization_policy(ACLAuthorizationPolicy()) 60 | config.set_root_factory(lambda request: Root()) 61 | 62 | config.add_route('home', '/') 63 | 64 | config.scan(__name__) 65 | return config.make_wsgi_app() 66 | 67 | if __name__ == '__main__': 68 | from waitress import serve 69 | app = main({}) 70 | serve(app, listen='localhost:8000') 71 | -------------------------------------------------------------------------------- /docs/auth/custom.rst: -------------------------------------------------------------------------------- 1 | Custom Authentication Policy 2 | %%%%%%%%%%%%%%%%%%%%%%%%%%%% 3 | 4 | Here is an example of a custom AuthenticationPolicy, based off of 5 | the native ``AuthTktAuthenticationPolicy``, but with added groups support. 6 | This example implies you have a ``user`` attribute on your request 7 | (see :ref:`user object`) and that the ``user`` should have a 8 | ``groups`` relation on it:: 9 | 10 | from pyramid.authentication import AuthTktCookieHelper 11 | from pyramid.security import Everyone, Authenticated 12 | 13 | class MyAuthenticationPolicy(object): 14 | 15 | def __init__(self, settings): 16 | self.cookie = AuthTktCookieHelper( 17 | settings.get('auth.secret'), 18 | cookie_name=settings.get('auth.token') or 'auth_tkt', 19 | secure=asbool(settings.get('auth.secure')), 20 | timeout=asint(settings.get('auth.timeout')), 21 | reissue_time=asint(settings.get('auth.reissue_time')), 22 | max_age=asint(settings.get('auth.max_age')), 23 | ) 24 | 25 | def remember(self, request, principal, **kw): 26 | return self.cookie.remember(request, principal, **kw) 27 | 28 | def forget(self, request): 29 | return self.cookie.forget(request) 30 | 31 | def unauthenticated_userid(self, request): 32 | result = self.cookie.identify(request) 33 | if result: 34 | return result['userid'] 35 | 36 | def authenticated_userid(self, request): 37 | if request.user: 38 | return request.user.id 39 | 40 | def effective_principals(self, request): 41 | principals = [Everyone] 42 | user = request.user 43 | if user: 44 | principals += [Authenticated, 'u:%s' % user.id] 45 | principals.extend(('g:%s' % g.name for g in user.groups)) 46 | return principals 47 | 48 | 49 | Thanks to `raydeo` for this one. 50 | -------------------------------------------------------------------------------- /docs/auth/enterprise.rst: -------------------------------------------------------------------------------- 1 | Integration with Enterprise Systems 2 | =================================== 3 | 4 | When using Pyramid within an "enterprise" (or an intranet), it is often desirable to 5 | integrate with existing authentication and authorization (entitlement) systems. 6 | For example, in Microsoft Network environments, the user database is typically 7 | maintained in Active Directory. At present, there is no ready-to-use recipe, but we 8 | are listing places that may be worth looking at for ideas when developing one: 9 | 10 | Authentication 11 | -------------- 12 | 13 | * `adpasswd project on pypi `_ 14 | * `Tim Golden's Active Directory Cookbook `_ 15 | * `python-ad `_ 16 | * `python-ldap.org `_ 17 | * `python-ntmlm `_ 18 | * `Blog post on managing AD from Python in Linux `_ 19 | 20 | Authorization 21 | ------------- 22 | 23 | * `Microsoft Authorization Manager `_ 24 | * `Fundamentals of WCF Security `_ 25 | * `Calling WCF Services from C++ using gSOAP `_ 26 | 27 | -------------------------------------------------------------------------------- /docs/auth/index.rst: -------------------------------------------------------------------------------- 1 | Authentication and Authorization 2 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 3 | .. toctree:: 4 | :maxdepth: 1 5 | 6 | basic 7 | custom 8 | jwt_auth 9 | user_object 10 | wiki2_auth 11 | auth_tutorial 12 | open_id_auth 13 | enterprise 14 | 15 | For basic information on authentication and authorization, see the 16 | `security `_ 17 | section of the Pyramid documentation. 18 | 19 | -------------------------------------------------------------------------------- /docs/auth/open_id_auth.rst: -------------------------------------------------------------------------------- 1 | Google, Facebook, Twitter, and any OpenID Authentication 2 | ======================================================== 3 | 4 | See `Wayne Witzel III's blog post 5 | `_ 6 | about using Velruse and Pyramid together to do Google OAuth authentication. 7 | 8 | See Matthew Housden and Chris Davies apex project for any basic and 9 | openid authentication such as Google, Facebook, Twitter and more at 10 | https://github.com/cd34/apex. 11 | 12 | -------------------------------------------------------------------------------- /docs/automating_development_process/index.rst: -------------------------------------------------------------------------------- 1 | Automating the Development Process 2 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | part1 8 | part2 9 | part3 10 | 11 | Based on `Davide Moro `_ articles (how to 12 | integrate the Yeoman workflow with Pyramid): 13 | 14 | - `Pyramid starter seed template powered by Yeoman (part 1) `_ 15 | - `Pyramid starter seed template powered by Yeoman (part 2) `_ 16 | - `Pyramid starter seed template powered by Yeoman (part 3) `_ 17 | -------------------------------------------------------------------------------- /docs/automating_development_process/part1.rst: -------------------------------------------------------------------------------- 1 | What is pyramid_starter_seed 2 | %%%%%%%%%%%%%%%%%%%%%%%%%%%% 3 | 4 | This tutorial should help you to start developing with the Pyramid web 5 | framework using a very minimal starter seed project based on: 6 | 7 | - a Pyramid's `pcreate -t starter` project 8 | - a `Yeoman `_ `generator-webapp` project 9 | 10 | You can find the Pyramid starter seed code here on Github: 11 | 12 | - `pyramid_starter_seed `_ 13 | 14 | Thanks to Yeoman you can improve your developer experience when you are in 15 | **development** or **production** mode thanks to: 16 | 17 | - Javascript testing setup 18 | - Javascript code linting 19 | - Javascript/CSS concat and minification 20 | - image assets optimization 21 | - html template minification 22 | - switch to CDN versions of you vendor plugins in production mode 23 | - uncss 24 | - much more (you can add features adding new Grunt tasks) 25 | 26 | We will see later how you can clone `pyramid_starter_seed` from github, add 27 | new features (eg: authentication, SQLAlchemy support, user models, a json 28 | REST API, add a modern Javascript framework as AngularJS, etc) and then 29 | launch a console script that helps you to rename the entire project with 30 | your more opinionated modifications, for example 31 | `pyramid_yourawesomeproduct`. 32 | 33 | Based on `Davide Moro `_ articles (how to 34 | integrate the Yeoman workflow with Pyramid): 35 | 36 | - `Pyramid starter seed template powered by Yeoman (part 1) `_ 37 | - `Pyramid starter seed template powered by Yeoman (part 2) `_ 38 | - `Pyramid starter seed template powered by Yeoman (part 3) `_ 39 | -------------------------------------------------------------------------------- /docs/automating_development_process/part2.rst: -------------------------------------------------------------------------------- 1 | Prerequisites 2 | %%%%%%%%%%%%% 3 | 4 | If you want to play with *pyramid_starter_seed* you'll need to install 5 | `NodeJS `_ and, obviously, Python. 6 | Once installed Python and Pyramid, you'll have to clone the 7 | pyramid_starter_seed repository from github and initialize the Yeoman stuff. 8 | 9 | Python and Pyramid 10 | ================== 11 | 12 | pyramid_starter_seed was tested with Python 2.7. 13 | Create an isolated Python environment as explained in the official Pyramid 14 | documentation and install Pyramid. 15 | 16 | Official Pyramid installation documentation 17 | 18 | - https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/install.html#installing-chapter 19 | 20 | NodeJS 21 | ====== 22 | 23 | You won't use NodeJS at all in your code, you just need to install 24 | development dependencies required by the Yeoman tools. 25 | 26 | Once installed NodeJS (if you want to easily install different versions 27 | on your system and manage them you can use the NodeJS Version Manager 28 | utility: `NVM `_), you need to 29 | enable the following tools: 30 | 31 | .. code-block:: bash 32 | 33 | $ npm install -g bower 34 | $ npm install -g grunt-cli 35 | $ npm install -g karma 36 | 37 | Tested with NodeJS version 0.10.31. 38 | 39 | How to install pyramid_starter_seed 40 | =================================== 41 | 42 | Clone *pyramid_starter_seed* from github: 43 | 44 | .. code-block:: bash 45 | 46 | $ git clone git@github.com:davidemoro/pyramid_starter_seed.git 47 | $ cd pyramid_starter_seed 48 | $ YOUR_VIRTUALENV_PYTHON_PATH/bin/python setup.py develop 49 | 50 | Yeoman initialization 51 | --------------------- 52 | 53 | Go to the folder where it lives our Yeoman project and initialize it. 54 | 55 | These are the standard commands (but, wait a moment, see the "Notes and 56 | known issues" subsection): 57 | 58 | .. code-block:: bash 59 | 60 | $ cd pyramid_starter_seed/webapp 61 | $ bower install 62 | $ npm install 63 | 64 | Known issues 65 | ------------ 66 | 67 | You'll need to perform these additional steps in order to get a working 68 | environment (the generator-webapp's version used by pyramid_starter_seed 69 | has a couple of known issues). 70 | 71 | Avoid imagemin errors on build: 72 | 73 | .. code-block:: bash 74 | 75 | $ npm cache clean 76 | $ npm install grunt-contrib-imagemin 77 | 78 | Avoid Mocha/PhantomJS issue (see 79 | `issues #446 `_): 80 | 81 | .. code-block:: bash 82 | 83 | $ cd test 84 | $ bower install 85 | 86 | Build 87 | ----- 88 | 89 | Run: 90 | 91 | .. code-block:: bash 92 | 93 | $ grunt build 94 | 95 | Run pyramid_starter_seed 96 | ======================== 97 | 98 | Now can choose to run Pyramid in development or production mode. 99 | 100 | Go to the root of your project directory, where the files `development.ini` 101 | and `production.ini` are located. 102 | 103 | .. code-block:: bash 104 | 105 | cd ../../.. 106 | 107 | Just type: 108 | 109 | .. code-block:: bash 110 | 111 | $ YOUR_VIRTUALENV_PYTHON_PATH/bin/pserve development.ini 112 | 113 | or: 114 | 115 | .. code-block:: bash 116 | 117 | $ YOUR_VIRTUALENV_PYTHON_PATH/bin/pserve production.ini 118 | 119 | -------------------------------------------------------------------------------- /docs/configuration/django_settings.rst: -------------------------------------------------------------------------------- 1 | Django-Style "settings.py" Configuration 2 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 3 | 4 | If you enjoy accessing global configuration via import statements ala 5 | Django's ``settings.py``, you can do something similar in Pyramid. 6 | 7 | - Create a ``settings.py`` file in your application's package (for example, 8 | if your application is named "myapp", put it in the filesystem directory 9 | named ``myapp``; the one with an ``__init__.py`` in it. 10 | 11 | - Add values to it at its top level. 12 | 13 | For example:: 14 | 15 | # settings.py 16 | import pytz 17 | 18 | timezone = pytz('US/Eastern') 19 | 20 | Then simply import the module into your application:: 21 | 22 | from myapp import settings 23 | 24 | def myview(request): 25 | timezone = settings.timezone 26 | return Response(timezone.zone) 27 | 28 | This is all you really need to do if you just want some global configuration 29 | values for your application. 30 | 31 | However, more frequently, values in your ``settings.py`` file need to be 32 | conditionalized based on deployment settings. For example, the timezone 33 | above is different between development and deployment. In order to 34 | conditionalize the values in your ``settings.py`` you can use *other* values 35 | from the Pyramid ``development.ini`` or ``production.ini``. To do so, 36 | your ``settings.py`` might instead do this:: 37 | 38 | import os 39 | 40 | ini = os.environ['PYRAMID_SETTINGS'] 41 | config_file, section_name = ini.split('#', 1) 42 | 43 | from paste.deploy.loadwsgi import appconfig 44 | config = appconfig('config:%s' % config_file, section_name) 45 | 46 | import pytz 47 | 48 | timezone = pytz.timezone(config['timezone']) 49 | 50 | The value of ``config`` in the above snippet will be a dictionary 51 | representing your application's ``development.ini`` configuration section. 52 | For example, for the above code to work, you'll need to add a ``timezone`` 53 | key/value pair to a section of your ``development.ini``:: 54 | 55 | [app:myapp] 56 | use = egg:MyApp 57 | timezone = US/Eastern 58 | 59 | If your ``settings.py`` is written like this, before starting Pyramid, ensure 60 | you have an OS environment value (akin to Django's ``DJANGO_SETTINGS``) in 61 | this format:: 62 | 63 | export PYRAMID_SETTINGS=/place/to/development.ini#myapp 64 | 65 | ``/place/to/development.ini`` is the full path to the ini file. ``myapp`` is 66 | the section name in the config file that represents your app 67 | (e.g. ``[app:myapp]``). In the above example, your application will refuse 68 | to start without this environment variable being present. 69 | -------------------------------------------------------------------------------- /docs/configuration/index.rst: -------------------------------------------------------------------------------- 1 | Configuration 2 | %%%%%%%%%%%%% 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | whirlwind_tour 8 | django_settings 9 | 10 | For more information on configuration see the following sections of the Pyramid documentation: 11 | 12 | - `basic configuration `_ 13 | - `advanced configuration `_ 14 | - `configuration introspection `_ 15 | - `extending configuration `_ 16 | - `PasteDeploy configuration `_ 17 | -------------------------------------------------------------------------------- /docs/database/index.rst: -------------------------------------------------------------------------------- 1 | Databases 2 | %%%%%%%%% 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | sqlalchemy 8 | couchdb 9 | mongodb 10 | -------------------------------------------------------------------------------- /docs/database/mongodb.rst: -------------------------------------------------------------------------------- 1 | MongoDB and Pyramid 2 | =================== 3 | 4 | Basics 5 | ------ 6 | 7 | If you want to use MongoDB (via PyMongo and perhaps GridFS) via Pyramid, you 8 | can use the following pattern to make your Mongo database available as a 9 | request attribute. 10 | 11 | First add the MongoDB URI to your ``development.ini`` file. (Note: ``user``, ``password`` and ``port`` are not required.) 12 | 13 | .. code-block:: ini 14 | :linenos: 15 | 16 | [app:myapp] 17 | # ... other settings ... 18 | mongo_uri = mongodb://user:password@host:port/database 19 | 20 | Then in your ``__init__.py``, set things up such that the database is 21 | attached to each new request:: 22 | 23 | from pyramid.config import Configurator 24 | 25 | try: 26 | # for python 2 27 | from urlparse import urlparse 28 | except ImportError: 29 | # for python 3 30 | from urllib.parse import urlparse 31 | 32 | from gridfs import GridFS 33 | from pymongo import MongoClient 34 | 35 | 36 | def main(global_config, **settings): 37 | """ This function returns a Pyramid WSGI application. 38 | """ 39 | config = Configurator(settings=settings) 40 | config.add_static_view('static', 'static', cache_max_age=3600) 41 | 42 | db_url = urlparse(settings['mongo_uri']) 43 | config.registry.db = MongoClient( 44 | host=db_url.hostname, 45 | port=db_url.port, 46 | ) 47 | 48 | def add_db(request): 49 | db = config.registry.db[db_url.path[1:]] 50 | if db_url.username and db_url.password: 51 | db.authenticate(db_url.username, db_url.password) 52 | return db 53 | 54 | def add_fs(request): 55 | return GridFS(request.db) 56 | 57 | config.add_request_method(add_db, 'db', reify=True) 58 | config.add_request_method(add_fs, 'fs', reify=True) 59 | 60 | config.add_route('dashboard', '/') 61 | # other routes and more config... 62 | config.scan() 63 | return config.make_wsgi_app() 64 | 65 | 66 | .. note:: 67 | 68 | ``Configurator.add_request_method`` has been available since Pyramid 1.4. 69 | You can use ``Configurator.set_request_property`` for Pyramid 1.3. 70 | 71 | At this point, in view code, you can use ``request.db`` as the PyMongo database 72 | connection. For example:: 73 | 74 | @view_config(route_name='dashboard', 75 | renderer="myapp:templates/dashboard.pt") 76 | def dashboard(request): 77 | vendors = request.db['vendors'].find() 78 | return {'vendors':vendors} 79 | 80 | 81 | Scaffolds 82 | --------- 83 | 84 | Niall O'Higgins provides a `pyramid_mongodb 85 | `_ scaffold for Pyramid that 86 | provides an easy way to get started with Pyramid and MongoDB. 87 | 88 | 89 | Video 90 | ----- 91 | 92 | Niall O'Higgins provides a presentation he gave at a Mongo conference in San 93 | Francisco at https://www.mongodb.com/presentations/weather-century 94 | 95 | 96 | Other Information 97 | ----------------- 98 | 99 | - Pyramid, Aket and MongoDB: 100 | http://niallohiggins.com/2011/05/18/mongodb-python-pyramid-akhet/ 101 | -------------------------------------------------------------------------------- /docs/debugging/index.rst: -------------------------------------------------------------------------------- 1 | Debugging 2 | %%%%%%%%% 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | using_pdb 8 | debugging_pyramid 9 | pydev 10 | 11 | -------------------------------------------------------------------------------- /docs/debugging/using_pdb.rst: -------------------------------------------------------------------------------- 1 | Using PDB to Debug Your Application 2 | +++++++++++++++++++++++++++++++++++ 3 | 4 | ``pdb`` is an interactive tool that comes with Python, which allows you to 5 | break your program at an arbitrary point, examine values, and step through 6 | code. It's often much more useful than print statements or logging 7 | statements to examine program state. You can place a ``pdb.set_trace()`` 8 | statement in your Pyramid application at a place where you'd like to examine 9 | program state. When you issue a request to the application, and that point 10 | in your code is reached, you will be dropped into the ``pdb`` debugging 11 | console within the terminal that you used to start your application. 12 | 13 | There are lots of great resources that can help you learn PDB. 14 | 15 | - Doug Hellmann's PyMOTW blog entry entitled "pdb - Interactive Debugger" at 16 | https://pymotw.com/3/pdb/ is the canonical text resource to learning PDB. 17 | 18 | - The PyCon video presentation by Chris McDonough entitled "Introduction to 19 | PDB" at https://pyvideo.org/video/644/introduction-to-pdb is a good place to 20 | start learning PDB. 21 | 22 | - The video at https://pyvideo.org/pycon-us-2012/introduction-to-pdb.html shows you 23 | how to start how to start to using pdb. The video describes using ``pdb`` 24 | in a command-line program. 25 | -------------------------------------------------------------------------------- /docs/deployment/apache.rst: -------------------------------------------------------------------------------- 1 | Apache + mod_wsgi 2 | +++++++++++++++++ 3 | 4 | `Pyramid mod_wsgi tutorial `_ 5 | -------------------------------------------------------------------------------- /docs/deployment/aws_via_eb.rst: -------------------------------------------------------------------------------- 1 | Amazon Web Services via Elastic Beanstalk 2 | +++++++++++++++++++++++++++++++++++++++++ 3 | 4 | Dan Clark published two tutorials for deploying Pyramid applications on `Amazon Web Services (AWS) `_ via `Elastic Beanstalk `_. 5 | 6 | `How-to: Hello Pyramid on AWS `_ shows how to deploy the `Hello World application `_. 7 | 8 | `How-to: Pyramid Starter on AWS `_ shows how to deploy a project generated from the `pyramid-cookiecutter-starter `_. 9 | -------------------------------------------------------------------------------- /docs/deployment/deployment.rst: -------------------------------------------------------------------------------- 1 | Deploying Your Pyramid Application 2 | ---------------------------------- 3 | 4 | So you've written a sweet application and you want to deploy it outside of 5 | your local machine. We're not going to cover caching here, but suffice it to 6 | say that there are a lot of things to consider when optimizing your pyramid 7 | application. 8 | 9 | At a high level, you need to expose a server on ports 80 (HTTP) and 443 10 | (HTTPS). Underneath this layer, however, is 11 | a plethora of different configurations that can be used to get a request 12 | from a client, into your application, and return the response. 13 | 14 | :: 15 | 16 | Client <---> WSGI Server <---> Your Application 17 | 18 | Due to the beauty of standards, many different configurations can be used to 19 | generate this basic setup, injecting caching layers, load balancers, and so on into 20 | the basic workflow. 21 | 22 | Disclaimer 23 | ++++++++++ 24 | 25 | It's important to note that the setups discussed here are meant to give some 26 | direction to newer users. Deployment is *almost always* highly dependent on 27 | the application's specific purposes. These setups have been used for many 28 | different projects in production with much success, but never verbatim. 29 | 30 | What is WSGI? 31 | +++++++++++++ 32 | 33 | WSGI is a `Python standard `_ 34 | dictating the interface between a server and an 35 | application. The entry point to your pyramid application is an object 36 | implementing the WSGI interface. Thus, your application can be served by any 37 | server supporting WSGI. 38 | 39 | There are many different servers implementing the WSGI standard in existence. 40 | A short list includes: 41 | 42 | + ``waitress`` 43 | 44 | + ``paste.httpserver`` 45 | 46 | + ``CherryPy`` 47 | 48 | + ``uWSGI`` 49 | 50 | + ``gevent`` 51 | 52 | + ``mod_wsgi`` 53 | 54 | For more information on WSGI, see the `WSGI home `_. 55 | 56 | Special Considerations 57 | ++++++++++++++++++++++ 58 | 59 | Certain environments and web servers require special considerations when 60 | deploying your Pyramid application due to implementation details of Python, the 61 | web server, or popular packages. 62 | 63 | Forked and threaded servers share some common gotchas and solutions. 64 | 65 | :doc:`forked_threaded_servers` 66 | -------------------------------------------------------------------------------- /docs/deployment/expresscloud.rst: -------------------------------------------------------------------------------- 1 | OpenShift Express Cloud 2 | +++++++++++++++++++++++ 3 | 4 | `This blog entry 5 | `_ 6 | describes deploying a Pyramid application to RedHat's OpenShift Express Cloud 7 | platform. 8 | 9 | Luke Macken's `OpenShift Quickstarter 10 | `_ also provides an easy 11 | way to get started using OpenShift. 12 | -------------------------------------------------------------------------------- /docs/deployment/gae.rst: -------------------------------------------------------------------------------- 1 | .. _appengine_tutorial: 2 | 3 | Google App Engine Standard and :app:`Pyramid` 4 | ============================================= 5 | 6 | It is possible to run a :app:`Pyramid` application on `Google App Engine `_. This tutorial is written in terms of using the command line on a UNIX system. It should be possible to perform similar actions on a Windows system. This tutorial also assumes you've already installed and created a :app:`Pyramid` application, and that you have a Google App Engine account. 7 | 8 | Setup 9 | ----- 10 | 11 | First we'll need to create a few files so that App Engine can communicate with our project properly. 12 | 13 | Create the files with content as follows. 14 | 15 | #. ``requirements.txt`` 16 | 17 | .. code-block:: text 18 | 19 | Pyramid 20 | waitress 21 | pyramid_debugtoolbar 22 | pyramid_chameleon 23 | 24 | #. ``main.py`` 25 | 26 | .. code-block:: python 27 | 28 | from pyramid.paster import get_app, setup_logging 29 | ini_path = 'production.ini' 30 | setup_logging(ini_path) 31 | application = get_app(ini_path, 'main') 32 | 33 | #. ``appengine_config.py`` 34 | 35 | .. code-block:: python 36 | 37 | from google.appengine.ext import vendor 38 | vendor.add('lib') 39 | 40 | #. ``app.yaml`` 41 | 42 | .. code-block:: yaml 43 | 44 | application: application-id 45 | version: version 46 | runtime: python27 47 | api_version: 1 48 | threadsafe: false 49 | 50 | handlers: 51 | - url: /static 52 | static_dir: pyramid_project/static 53 | - url: /.* 54 | script: main.application 55 | 56 | Configure this file with the following values: 57 | 58 | * Replace "application-id" with your App Engine application's ID. 59 | * Replace "version" with the version you want to deploy. 60 | * Replace "pyramid_project" in the definition for ``static_dir`` with the parent directory name of your static assets. If your static assets are in the root directory, you can just put "static". 61 | 62 | For more details about ``app.yaml``, see `app.yaml Reference `_. 63 | 64 | #. Install dependencies. 65 | 66 | .. code-block:: bash 67 | 68 | $ pip install -t lib -r requirements.txt 69 | 70 | 71 | Running locally 72 | --------------- 73 | 74 | At this point you should have everything you need to run your Pyramid application locally using ``dev_appserver``. Assuming you have appengine in your ``$PATH``: 75 | 76 | .. code-block:: bash 77 | 78 | $ dev_appserver.py app.yaml 79 | 80 | And voilà! You should have a perfectly-running Pyramid application via Google App Engine on your local machine. 81 | 82 | 83 | Deploying 84 | --------- 85 | 86 | If you've successfully launched your application locally, deploy with a single command. 87 | 88 | .. code-block:: bash 89 | 90 | $ appcfg.py update app.yaml 91 | 92 | Your Pyramid application is now live to the world! You can access it by navigating to your domain name, by ".appspot.com", or if you've specified a version outside of your default then it would be ".appspot.com". 93 | -------------------------------------------------------------------------------- /docs/deployment/gevent.rst: -------------------------------------------------------------------------------- 1 | gevent 2 | ++++++ 3 | 4 | gevent + pyramid_socketio 5 | ========================= 6 | 7 | Alexandre Bourget explains how he uses gevent + socketio to add functionality to a Pyramid application at https://pyvideo.org/pycon-ca-2012/gevent-socketio-cross-framework-real-time-web-li.html 8 | 9 | gevent + long polling 10 | ===================== 11 | 12 | https://michael.merickel.org/2011/6/21/tictactoe-and-long-polling-with-pyramid/ 13 | 14 | https://github.com/mmerickel/tictactoe 15 | 16 | For more information on gevent see the `gevent home page `_ 17 | -------------------------------------------------------------------------------- /docs/deployment/gunicorn.rst: -------------------------------------------------------------------------------- 1 | ******** 2 | gunicorn 3 | ******** 4 | 5 | The short story 6 | =============== 7 | Running your pyramid based application with gunicorn can be as easy as: 8 | 9 | .. code-block:: bash 10 | 11 | $ gunicorn --paste production.ini 12 | 13 | 14 | The long story 15 | ============== 16 | Similar to the ``pserve`` command that comes with Pyramid, gunicorn can also 17 | directly use your project's INI files, such as ``production.ini``, to launch 18 | your application. Just supply the ``--paste`` command line option together with 19 | the path of your configuration file to the ``gunicorn`` command, and it will 20 | try to load the app. 21 | 22 | As documented in the section `Paste Deployment 23 | `_, you 24 | may also add gunicorn specific settings to the ``[server:main]`` section of 25 | your INI file and continue using the ``pserve`` command. 26 | 27 | The following configuration will cause gunicorn to listen on a unix socket, use 28 | four workers, preload the application, output accesslog lines to stderr and use 29 | the debug loglevel. 30 | 31 | .. code-block:: ini 32 | 33 | [server:main] 34 | use = egg:gunicorn#main 35 | bind = unix:/var/run/app.sock 36 | workers = 4 37 | preload = true 38 | accesslog = - 39 | loglevel = debug 40 | 41 | For all configuration options that may be used, have a look at the `available 42 | settings `_. 43 | 44 | Keep in mind that settings defined within a gunicorn configuration file 45 | take precedence over the settings established within the INI file. 46 | 47 | For all of this to work, the Python interpreter used by gunicorn also needs to 48 | be able to load your application. In other words, gunicorn and your application 49 | need to be installed and used inside the same ``virtualenv``. 50 | 51 | Naturally, the ``paste`` option can also be combined with other gunicorn 52 | options that might be applicable for your deployment situation. Also you might 53 | want to put something like `nginx `_ in 54 | front of gunicorn and have gunicorn supervised by some process manager. Please 55 | have a look at the `gunicorn website `_ and the `gunicorn 56 | documentation on deployment `_ 57 | for more information on those topics. 58 | -------------------------------------------------------------------------------- /docs/deployment/index.rst: -------------------------------------------------------------------------------- 1 | Deployment 2 | ++++++++++ 3 | 4 | Introduction 5 | ------------ 6 | 7 | .. toctree:: 8 | :maxdepth: 1 9 | 10 | deployment 11 | 12 | 13 | Web Servers 14 | ----------- 15 | 16 | .. toctree:: 17 | :maxdepth: 1 18 | 19 | apache 20 | asgi 21 | forked_threaded_servers 22 | gevent 23 | gunicorn 24 | nginx 25 | uwsgi 26 | uwsgi_cookiecutter_1_nginx 27 | uwsgi_cookiecutter_2_emperor 28 | uwsgi_nginx_systemd 29 | 30 | 31 | Cloud Providers 32 | --------------- 33 | 34 | .. toctree:: 35 | :maxdepth: 1 36 | 37 | aws_via_eb 38 | dotcloud 39 | gae 40 | gae_buildout 41 | gae_flex_datastore 42 | heroku 43 | expresscloud 44 | 45 | 46 | Windows 47 | ------- 48 | 49 | .. toctree:: 50 | :maxdepth: 1 51 | 52 | windows 53 | -------------------------------------------------------------------------------- /docs/deployment/uwsgi.rst: -------------------------------------------------------------------------------- 1 | uWSGI 2 | +++++ 3 | 4 | This brief chapter covers how to configure a `uWSGI `_ server for Pyramid. 5 | 6 | Pyramid is a Paste-compatible web application framework. As such, you can use the uWSGI ``--paste`` option to conveniently deploy your application. 7 | 8 | For example, if you have a virtual environment in ``/opt/env`` containing a Pyramid application called ``wiki`` configured in ``/opt/env/wiki/development.ini``: 9 | 10 | .. code-block:: bash 11 | 12 | uwsgi --paste config:/opt/env/wiki/development.ini --socket :3031 -H /opt/env 13 | 14 | The example is modified from the `original example for Turbogears `_. 15 | -------------------------------------------------------------------------------- /docs/development_tools/index.rst: -------------------------------------------------------------------------------- 1 | Development Tools 2 | %%%%%%%%%%%%%%%%% 3 | 4 | This section is a collection of development tools and tips, resource files, and 5 | other things that help make writing code in Python for Pyramid easier and more 6 | fun. 7 | 8 | .. toctree:: 9 | :maxdepth: 2 10 | 11 | pycharm 12 | -------------------------------------------------------------------------------- /docs/development_tools/pycharm_images/create_new_project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pylons/pyramid_cookbook/35bbfad6637ea85adbf0e18d168b8ecc3d01707d/docs/development_tools/pycharm_images/create_new_project.png -------------------------------------------------------------------------------- /docs/development_tools/pycharm_images/create_setup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pylons/pyramid_cookbook/35bbfad6637ea85adbf0e18d168b8ecc3d01707d/docs/development_tools/pycharm_images/create_setup.png -------------------------------------------------------------------------------- /docs/development_tools/pycharm_images/create_virtual_environment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pylons/pyramid_cookbook/35bbfad6637ea85adbf0e18d168b8ecc3d01707d/docs/development_tools/pycharm_images/create_virtual_environment.png -------------------------------------------------------------------------------- /docs/development_tools/pycharm_images/edit_run_debug_configurations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pylons/pyramid_cookbook/35bbfad6637ea85adbf0e18d168b8ecc3d01707d/docs/development_tools/pycharm_images/edit_run_debug_configurations.png -------------------------------------------------------------------------------- /docs/development_tools/pycharm_images/install_package.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pylons/pyramid_cookbook/35bbfad6637ea85adbf0e18d168b8ecc3d01707d/docs/development_tools/pycharm_images/install_package.png -------------------------------------------------------------------------------- /docs/development_tools/pycharm_images/install_package_pyramid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pylons/pyramid_cookbook/35bbfad6637ea85adbf0e18d168b8ecc3d01707d/docs/development_tools/pycharm_images/install_package_pyramid.png -------------------------------------------------------------------------------- /docs/development_tools/pycharm_images/install_package_setuptools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pylons/pyramid_cookbook/35bbfad6637ea85adbf0e18d168b8ecc3d01707d/docs/development_tools/pycharm_images/install_package_setuptools.png -------------------------------------------------------------------------------- /docs/development_tools/pycharm_images/python_interpreters_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pylons/pyramid_cookbook/35bbfad6637ea85adbf0e18d168b8ecc3d01707d/docs/development_tools/pycharm_images/python_interpreters_1.png -------------------------------------------------------------------------------- /docs/development_tools/pycharm_images/python_interpreters_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pylons/pyramid_cookbook/35bbfad6637ea85adbf0e18d168b8ecc3d01707d/docs/development_tools/pycharm_images/python_interpreters_2.png -------------------------------------------------------------------------------- /docs/development_tools/pycharm_images/run_configuration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pylons/pyramid_cookbook/35bbfad6637ea85adbf0e18d168b8ecc3d01707d/docs/development_tools/pycharm_images/run_configuration.png -------------------------------------------------------------------------------- /docs/development_tools/pycharm_images/start_up_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pylons/pyramid_cookbook/35bbfad6637ea85adbf0e18d168b8ecc3d01707d/docs/development_tools/pycharm_images/start_up_screen.png -------------------------------------------------------------------------------- /docs/forms/file_uploads.rst: -------------------------------------------------------------------------------- 1 | File Uploads 2 | %%%%%%%%%%%% 3 | 4 | There are two parts necessary for handling file uploads. The first is to 5 | make sure you have a form that's been setup correctly to accept files. This 6 | means adding ``enctype`` attribute to your ``form`` element with the value of 7 | ``multipart/form-data``. A very simple example would be a form that accepts 8 | an mp3 file. Notice we've setup the form as previously explained and also 9 | added an ``input`` element of the ``file`` type. 10 | 11 | .. code-block:: html 12 | :linenos: 13 | 14 |
16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | The second part is handling the file upload in your view callable (above, 24 | assumed to answer on ``/store_mp3_view``). The uploaded file is added to the 25 | request object as a ``cgi.FieldStorage`` object accessible through the 26 | ``request.POST`` multidict. The two properties we're interested in are the 27 | ``file`` and ``filename`` and we'll use those to write the file to disk:: 28 | 29 | import os 30 | import uuid 31 | import shutil 32 | from pyramid.response import Response 33 | 34 | def store_mp3_view(request): 35 | # ``filename`` contains the name of the file in string format. 36 | # 37 | # WARNING: this example does not deal with the fact that IE sends an 38 | # absolute file *path* as the filename. This example is naive; it 39 | # trusts user input. 40 | 41 | filename = request.POST['mp3'].filename 42 | 43 | # ``input_file`` contains the actual file data which needs to be 44 | # stored somewhere. 45 | 46 | input_file = request.POST['mp3'].file 47 | 48 | # Note that we are generating our own filename instead of trusting 49 | # the incoming filename since that might result in insecure paths. 50 | # Please note that in a real application you would not use /tmp, 51 | # and if you write to an untrusted location you will need to do 52 | # some extra work to prevent symlink attacks. 53 | 54 | file_path = os.path.join('/tmp', '%s.mp3' % uuid.uuid4()) 55 | 56 | # We first write to a temporary file to prevent incomplete files from 57 | # being used. 58 | 59 | temp_file_path = file_path + '~' 60 | 61 | # Finally write the data to a temporary file 62 | input_file.seek(0) 63 | with open(temp_file_path, 'wb') as output_file: 64 | shutil.copyfileobj(input_file, output_file) 65 | 66 | # Now that we know the file has been fully saved to disk move it into place. 67 | 68 | os.rename(temp_file_path, file_path) 69 | 70 | return Response('OK') 71 | -------------------------------------------------------------------------------- /docs/forms/index.rst: -------------------------------------------------------------------------------- 1 | .. _forms: 2 | 3 | Forms 4 | %%%%% 5 | 6 | Pyramid does not include a form library because there are several good ones on 7 | PyPI, but none that is obviously better than the others. 8 | 9 | Deform_ is a form library written for Pyramid, and maintained by the Pylons 10 | Project. It has a `demo `_. 11 | 12 | You can use WebHelpers and FormEncode in Pyramid just like in Pylons. Use 13 | pyramid_simpleform_ to organize your view code. (This replaces Pylons' 14 | @validate decorator, which has no equivalent in Pyramid.) FormEncode's 15 | documentation is a bit obtuse and sparse, but it's so widely flexible that you 16 | can do things in FormEncode that you can't in other libraries, and you can also 17 | use it for non-HTML validation; e.g., to validate the settings in the INI file. 18 | 19 | Some Pyramid users have had luck with WTForms, Formish, ToscaWidgets, etc. 20 | 21 | There are also form packages tied to database records, most notably 22 | FormAlchemy. These will publish a form to add/modify/delete records of a 23 | certain ORM class. 24 | 25 | 26 | Articles 27 | -------- 28 | 29 | .. toctree:: 30 | 31 | file_uploads 32 | 33 | 34 | .. include:: ../links.rst 35 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. _pyramid-cookbook: 2 | 3 | Pyramid Community Cookbook 4 | ========================== 5 | 6 | The Pyramid Community Cookbook presents topical, practical "recipes" of using 7 | Pyramid. It supplements the :ref:`main documentation `. 8 | 9 | To contribute your recipe to the Pyramid Community Cookbook, read `Contributing 10 | `_. 11 | 12 | Table of contents 13 | ----------------- 14 | 15 | .. toctree:: 16 | :maxdepth: 2 17 | 18 | auth/index 19 | automating_development_process/index 20 | configuration/index 21 | database/index 22 | debugging/index 23 | 24 | .. toctree:: 25 | :maxdepth: 3 26 | 27 | deployment/index 28 | 29 | .. toctree:: 30 | :maxdepth: 2 31 | 32 | development_tools/index 33 | forms/index 34 | logging/index 35 | porting/index 36 | pylons/index 37 | routing/index 38 | sample_applications/index 39 | static_assets/index 40 | templates/index 41 | testing/index 42 | traversal_tutorial/index 43 | views/index 44 | misc/index 45 | todo 46 | 47 | :ref:`Pyramid Glossary ` 48 | 49 | Indices and tables 50 | ================== 51 | 52 | * :ref:`genindex` 53 | * :ref:`search` 54 | 55 | -------------------------------------------------------------------------------- /docs/links.rst: -------------------------------------------------------------------------------- 1 | .. 2 | This file contains external links referenced in one or more Cookbook 3 | articles. 4 | 5 | The Cookbook configuration has an "intersphinx inventory", which allows you 6 | to link to sections in the Pyramid manual and glossary without providing 7 | absolute URLs. To get a list of current reference names, download 8 | https://docs.pylonsproject.org/projects/pyramid/en/latest/objects.inv and 9 | uncompress it (GZIP format). It will be a text file with lines like this: 10 | 11 | response std:term -1 glossary.html#term-$ - 12 | mako_templates std:label -1 narr/templates.html#mako-templates Templating With Mako Templates 13 | 14 | Lines with "std:term" are glossary entries. You can put :term:`response` in 15 | your article and it will be rendered as a link to the glossary term. Lines 16 | with "std:label" are section targets. You can put :term:`mako_templates` in 17 | your article and it will be rendered as a link to that section in the 18 | Pyramid manual with the section title as the link text. Or you can override 19 | the link text with the Docutils text syntax: :ref:`Mako `. 20 | 21 | Note: this isn't working for me with multiword terms with spaces. 22 | 23 | 24 | .. _Pyramid manual: https://docs.pylonsproject.org/projects/pyramid/en/latest/ 25 | .. _Tutorials: https://docs.pylonsproject.org/projects/pyramid/en/latest/#tutorials 26 | .. _Installing Pyramid: https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/install.html 27 | .. _Creating a Pyramid Project: https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/project.html 28 | 29 | .. _Akhet: https://docs.pylonsproject.org/projects/akhet/en/latest/ 30 | .. _Beaker: https://beaker.readthedocs.io/en/latest/sessions.html 31 | .. _SQLAlchemy manual: http://docs.sqlalchemy.org/en/latest/ 32 | .. _pyramid_handlers: https://docs.pylonsproject.org/projects/pyramid-handlers/en/latest/ 33 | .. _pyramid_routehelper: https://github.com/Pylons/pyramid_routehelper/blob/master/pyramid_routehelper/__init__.py 34 | 35 | .. _Deform: https://docs.pylonsproject.org/projects/deform/en/latest/ 36 | .. _pyramid_simpleform: https://pythonhosted.org/pyramid_simpleform/ 37 | 38 | .. _Kotti: https://kotti.readthedocs.io/en/latest/ 39 | .. _Ptah: https://ptahproject.readthedocs.io/en/latest/ 40 | .. _Khufu: https://github.com/khufuproject 41 | -------------------------------------------------------------------------------- /docs/logging/index.rst: -------------------------------------------------------------------------------- 1 | Logging 2 | %%%%%%% 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | sqlalchemy_logger 8 | 9 | For more information on logging, see the `Logging 10 | `_ 11 | section of the Pyramid documentation. 12 | -------------------------------------------------------------------------------- /docs/misc/events.rst: -------------------------------------------------------------------------------- 1 | Using Object Events in Pyramid 2 | ------------------------------ 3 | 4 | .. warning:: This code works only in Pyramid 1.1a4+. It will also make your 5 | brain explode. 6 | 7 | Zope's Component Architecture supports the concept of "object events", which 8 | are events which call a subscriber with an context object *and* the event 9 | object. 10 | 11 | Here's an example of using an object event subscriber via the ``@subscriber`` 12 | decorator:: 13 | 14 | from zope.component.event import objectEventNotify 15 | from zope.component.interfaces import ObjectEvent 16 | 17 | from pyramid.events import subscriber 18 | from pyramid.view import view_config 19 | 20 | class ObjectThrownEvent(ObjectEvent): 21 | pass 22 | 23 | class Foo(object): 24 | pass 25 | 26 | @subscriber([Foo, ObjectThrownEvent]) 27 | def objectevent_listener(object, event): 28 | print object, event 29 | 30 | @view_config(renderer='string') 31 | def theview(request): 32 | objectEventNotify(ObjectThrownEvent(Foo())) 33 | objectEventNotify(ObjectThrownEvent(None)) 34 | objectEventNotify(ObjectEvent(Foo())) 35 | return 'OK' 36 | 37 | if __name__ == '__main__': 38 | from pyramid.config import Configurator 39 | from paste.httpserver import serve 40 | config = Configurator(autocommit=True) 41 | config.hook_zca() 42 | config.scan('__main__') 43 | serve(config.make_wsgi_app()) 44 | 45 | The ``objectevent_listener`` listener defined above will only be called when 46 | the ``object`` of the ObjectThrownEvent is of class ``Foo``. We can tell 47 | that's the case because only the first call to objectEventNotify actually 48 | invokes the subscriber. The second and third calls to objectEventNotify do 49 | not call the subscriber. The second call doesn't invoke the subscriber 50 | because its object type is ``None`` (and not ``Foo``). The third call 51 | doesn't invoke the subscriber because its objectevent type is ObjectEvent 52 | (and not ``ObjectThrownEvent``). Clear as mud? 53 | -------------------------------------------------------------------------------- /docs/misc/index.rst: -------------------------------------------------------------------------------- 1 | Miscellaneous 2 | %%%%%%%%%%%%% 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | interfaces 8 | events 9 | videos 10 | -------------------------------------------------------------------------------- /docs/misc/interfaces.rst: -------------------------------------------------------------------------------- 1 | Interfaces 2 | ========== 3 | 4 | This chapter contains information about using ``zope.interface`` with 5 | Pyramid. 6 | 7 | Dynamically Compute the Interfaces Provided by an Object 8 | -------------------------------------------------------- 9 | 10 | (Via Marius Gedminas) 11 | 12 | When persisting the interfaces that are provided by an object in a pickle or 13 | in ZODB is not reasonable for your application, you can use this trick to 14 | dynamically return the set of interfaces provided by an object based on other 15 | data in an instance of the object:: 16 | 17 | from zope.interface.declarations import Provides 18 | 19 | from mypackage import interfaces 20 | 21 | class MyClass(object): 22 | 23 | color = None 24 | 25 | @property 26 | def __provides__(self): 27 | # black magic happens here: we claim to provide the right IFrob 28 | # subinterface depending on the value of the ``color`` attribute. 29 | iface = getattr(interfaces, 'I%sFrob' % self.color.title(), 30 | interfaces.IFrob)) 31 | return Provides(self.__class__, iface) 32 | 33 | If you need the object to implement more than one interface, use 34 | ``Provides(self.__class__, iface1, iface2, ...)``. 35 | -------------------------------------------------------------------------------- /docs/misc/videos.rst: -------------------------------------------------------------------------------- 1 | Pyramid Tutorial and Informational Videos 2 | ----------------------------------------- 3 | 4 | * Six Feet Up's `Intro to Basic Pyramid 5 | `_. 6 | 7 | * Daniel Nouri's "Writing A Pyramid Application" (long, 3+ hours), from 8 | EuroPython 2012: 9 | 10 | - `Part 1 `_ 11 | 12 | - `Part 2 `_ 13 | 14 | See also the related `blog post `_. 15 | 16 | * Carlos de la Guardia's `Writing a Pyramid Application 17 | `_ 18 | tutorial from PyCon 2012 (long, 3+ hours). 19 | 20 | * Dylan Jay's `Pyramid: Lighter, faster, better web apps 21 | `_ from PyCon AU 2011 (~37 mins). 22 | 23 | * Carlos de la Guardia's `Patterns for building large Pyramid applications 24 | `_ (~25 minutes). 25 | 26 | * Eric Bieschke's `Pyramid Turbo Start Tutorial 27 | `_ (very short, 2 mins, 2011). 28 | 29 | * Chris McDonough `presentation 30 | `_ 31 | to Helsinki Python User's Group about Pyramid (2012), about 30 mins. 32 | 33 | * Chris McDonough at DjangoCon 2012, `About Django from the Pyramid Guy 34 | `_ (about 30 mins). 35 | 36 | * Chris McDonough and Mark Ramm: `FLOSS Weekly 151: The Pylons Project 37 | `_ (about 40 mins, 2010). 38 | 39 | * Kevin Gill's `What is Pyramid and where is it with respect to Django 40 | `_ (~43 mins) via Python Ireland May 2011. 41 | 42 | * Saiju M's `Create Pyramid Application With SQLAlchemy 43 | `_ (~ 17 mins). 44 | 45 | * George Dubus' `Pyramid advanced configuration tactics for nice apps and libs 46 | `_ from 47 | EuroPython 2013 (~34 mins). 48 | 49 | * Chris McDonough at PyCon 2013, `Pyramid Auth Is Hard, Let's Ride Bikes 50 | `_ (~30 51 | mins). 52 | 53 | * Dylan Jay's DjangoCon AU 2013 Keynote, `The myth of goldilocks and the three 54 | frameworks, Pyramid, Django, and Plone 55 | `_ (~45 mins). 56 | 57 | * Paul Everitt: `Python 3 Web Development with Pyramid and PyCharm 58 | `_ 59 | (~1 hr). 60 | -------------------------------------------------------------------------------- /docs/porting/index.rst: -------------------------------------------------------------------------------- 1 | .. _porting: 2 | 3 | Porting Applications to Pyramid 4 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 5 | 6 | *Note:* Other articles about Pylons applications are in the 7 | :doc:`../pylons/index` section. 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | 12 | legacy 13 | wsgi 14 | -------------------------------------------------------------------------------- /docs/porting/legacy.rst: -------------------------------------------------------------------------------- 1 | Porting a Legacy Pylons Application Piecemeal 2 | --------------------------------------------- 3 | 4 | You would like to move from Pylons 1.0 to Pyramid, but you're not going to be 5 | able manage a wholesale port any time soon. You're wondering if it would be 6 | practical to start using some parts of Pyramid within an existing Pylons 7 | project. 8 | 9 | One idea is to use a Pyramid "NotFound view" which delegates to the existing 10 | Pylons application, and port piecemeal:: 11 | 12 | # ... obtain pylons WSGI application object ... 13 | from mypylonsproject import thepylonsapp 14 | 15 | class LegacyView(object): 16 | def __init__(self, app): 17 | self.app = app 18 | def __call__(self, request): 19 | return request.get_response(self.app) 20 | 21 | if __name__ == '__main__': 22 | legacy_view = LegacyView(thepylonsapp) 23 | config = Configurator() 24 | config.add_view(context='pyramid.exceptions.NotFound', view=legacy_view) 25 | # ... rest of config ... 26 | 27 | At that point, whenever Pyramid cannot service a request because the URL 28 | doesn't match anything, it will invoke the Pylons application as a fallback, 29 | which will return things normally. At that point you can start moving logic 30 | incrementally into Pyramid from the Pylons application until you've ported 31 | everything. 32 | -------------------------------------------------------------------------------- /docs/porting/wsgi.rst: -------------------------------------------------------------------------------- 1 | Porting an Existing WSGI Application to Pyramid 2 | ----------------------------------------------- 3 | 4 | Pyramid is cool, but already-working code is cooler. You may not have the 5 | time, money or energy to port an existing Pylons, Django, Zope, or other 6 | WSGI-based application to Pyramid wholesale. In such cases, it can be useful 7 | to *incrementally* port an existing application to Pyramid. 8 | 9 | The broad-brush way to do this is: 10 | 11 | - Set up an *exception view* that will be called whenever a NotFound 12 | exception is raised by Pyramid. 13 | 14 | - In this exception view, delegate to your already-written WSGI application. 15 | 16 | Here's an example:: 17 | 18 | from pyramid.wsgi import wsgiapp2 19 | from pyramid.exceptions import NotFound 20 | 21 | if __name__ == '__main__': 22 | # during Pyramid configuration (usually in your Pyramid project's 23 | # __init__.py), get a hold of an instance of your existing WSGI 24 | # application. 25 | original_app = MyWSGIApplication() 26 | 27 | # using the pyramid.wsgi.wsgiapp2 wrapper function, wrap the 28 | # application into something that can be used as a Pyramid view. 29 | notfound_view = wsgiapp2(original_app) 30 | 31 | # in your configuration, use the wsgiapp2-wrapped application as 32 | # a NotFound exception view 33 | config = Configurator() 34 | 35 | # ... your other Pyramid configuration ... 36 | config.add_view(notfound_view, context=NotFound) 37 | # .. the remainder of your configuration ... 38 | 39 | 40 | When Pyramid cannot resolve a URL to a view, it will raise a NotFound 41 | exception. The ``add_view`` statement in the example above configures 42 | Pyramid to use your original WSGI application as the NotFound view. This 43 | means that whenever Pyramid cannot resolve a URL, your original application 44 | will be called. 45 | 46 | Incrementally, you can begin moving features from your existing WSGI 47 | application to Pyramid; if Pyramid can resolve a request to a view, the 48 | Pyramid "version" of the application logic will be used. If it cannot, the 49 | original WSGI application version of the logic will be used. Over time, you 50 | can move *all* of the logic into Pyramid without needing to do it all at 51 | once. 52 | -------------------------------------------------------------------------------- /docs/pylons/code/alchemy_main.py: -------------------------------------------------------------------------------- 1 | from pyramid.config import Configurator 2 | from sqlalchemy import engine_from_config 3 | 4 | from .models import DBSession 5 | 6 | def main(global_config, **settings): 7 | """ This function returns a Pyramid WSGI application. 8 | """ 9 | engine = engine_from_config(settings, 'sqlalchemy.') 10 | DBSession.configure(bind=engine) 11 | config = Configurator(settings=settings) 12 | config.add_static_view('static', 'static', cache_max_age=3600) 13 | config.add_route('home', '/') 14 | config.scan() 15 | return config.make_wsgi_app() 16 | 17 | -------------------------------------------------------------------------------- /docs/pylons/code/model_development.ini: -------------------------------------------------------------------------------- 1 | # development.ini 2 | [app:main] 3 | 4 | # Pyramid only 5 | pyramid.includes = 6 | pyramid_tm 7 | 8 | # Pyramid and Pylons 9 | sqlalchemy.url = sqlite:///%(here)s/PyramidApp.db 10 | 11 | 12 | [logger_sqlalchemy] 13 | 14 | # Pyramid and Pylons 15 | level = INFO 16 | handlers = 17 | qualname = sqlalchemy.engine 18 | # "level = INFO" logs SQL queries. 19 | # "level = DEBUG" logs SQL queries and results. 20 | # "level = WARN" logs neither. (Recommended for production systems.) 21 | -------------------------------------------------------------------------------- /docs/pylons/code/models.py: -------------------------------------------------------------------------------- 1 | # pyramidapp/__init__.py 2 | from sqlalchemy import engine_from_config 3 | from .models import DBSession 4 | 5 | def main(global_config, **settings): 6 | engine = engine_from_config(settings, 'sqlalchemy.') 7 | DBSession.configure(bind=engine) 8 | ... 9 | 10 | 11 | # pyramidapp/models.py 12 | from sqlalchemy import Column, Integer, Text 13 | from sqlalchemy.ext.declarative import declarative_base 14 | from sqlalchemy.orm import scoped_session, sessionmaker 15 | from zope.sqlalchemy import ZopeTransactionExtension 16 | 17 | DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension())) 18 | Base = declarative_base() 19 | 20 | class MyModel(Base): 21 | __tablename__ = 'models' 22 | id = Column(Integer, primary_key=True) 23 | name = Column(Text, unique=True) 24 | value = Column(Integer) 25 | 26 | def __init__(self, name, value): 27 | self.name = name 28 | self.value = value 29 | 30 | -------------------------------------------------------------------------------- /docs/pylons/code/pyramid_handlers.py: -------------------------------------------------------------------------------- 1 | # In the top-level __init__.py 2 | from .handlers import Hello 3 | def main(global_config, **settings): 4 | ... 5 | config.include("pyramid_handlers") 6 | config.add_handler("hello", "/hello/{action}", handler=Hello) 7 | 8 | # In zzz/handlers.py 9 | from pyramid_handlers import action 10 | class Hello(object): 11 | __autoexpose__ = None 12 | 13 | def __init__(self, request): 14 | self.request = request 15 | 16 | @action 17 | def index(self): 18 | return Response('Hello world!') 19 | 20 | @action(renderer="mytemplate.mak") 21 | def bye(self): 22 | return {} 23 | -------------------------------------------------------------------------------- /docs/pylons/code/starter_main.py: -------------------------------------------------------------------------------- 1 | from pyramid.config import Configurator 2 | 3 | def main(global_config, **settings): 4 | """ This function returns a Pyramid WSGI application. 5 | """ 6 | config = Configurator(settings=settings) 7 | config.add_static_view('static', 'static', cache_max_age=3600) 8 | config.add_route('home', '/') 9 | config.scan() 10 | return config.make_wsgi_app() 11 | -------------------------------------------------------------------------------- /docs/pylons/deployment.rst: -------------------------------------------------------------------------------- 1 | Deployment 2 | ++++++++++ 3 | 4 | Deployment is the same for Pyramid as for Pylons. Specify the desired WSGI 5 | server in the "[server:main]" and run "pserve" with it. The default server in 6 | Pyramid is Waitress, compared to PasteHTTPServer in Pylons. 7 | 8 | Waitress' advantage is that it runs on Python 3. Its disadvantage is that it 9 | doesn't seek and destroy stuck threads like PasteHTTPServer does. If you're 10 | like me, that's enough reason not to use Waitress in production. You can switch 11 | to PasteHTTPServer or CherryPy server if you wish, or use a method like 12 | mod_wsgi that doesn't require a Python HTTP server. 13 | -------------------------------------------------------------------------------- /docs/pylons/index.rst: -------------------------------------------------------------------------------- 1 | Pyramid for Pylons Users 2 | ++++++++++++++++++++++++ 3 | 4 | :Updated: 2012-06-12 5 | :Versions: Pyramid 1.3 6 | :Author: Mike Orr 7 | :Contributors: 8 | 9 | This guide discusses how Pyramid 1.3 differs from Pylons 1, and a few ways to 10 | make it more like Pylons. The guide may also be helpful to readers coming from 11 | Django or another Rails-like framework. The author has been a Pylons developer 12 | since 2007. The examples are based on Pyramid's default SQLAlchemy application 13 | and on the Akhet_ demo. 14 | 15 | If you haven't used Pyramid yet you can read this guide to get an overview of 16 | the differences and the Pyramid API. However, to actually start using Pyramid 17 | you'll want to read at least the first five chapters of the `Pyramid 18 | manual`_ (through `Creating a Pyramid Project`_) and go through the Tutorials_. 19 | Then you can come back to this guide to start designing your application, and 20 | skim through the rest of the manual to see which sections cover which topics. 21 | 22 | .. toctree:: 23 | :maxdepth: 1 24 | 25 | intro 26 | launch 27 | ini_file 28 | main 29 | models 30 | views 31 | examples 32 | request 33 | templates 34 | exceptions 35 | static 36 | sessions 37 | deployment 38 | auth 39 | other 40 | migrate 41 | 42 | 43 | .. include:: ../links.rst 44 | -------------------------------------------------------------------------------- /docs/pylons/launch.rst: -------------------------------------------------------------------------------- 1 | Launching the Application 2 | +++++++++++++++++++++++++ 3 | 4 | Pyramid and Pylons start up identically because they both use PasteDeploy and 5 | its INI-format configuration file. This is true even though Pyramid 1.3 6 | replaced "paster serve" with its own "pserve" command. Both "pserve" and 7 | "paster serve" do the following: 8 | 9 | 1. Read the INI file. 10 | 2. Instantiate an application based on the "[app:main]" section. 11 | 3. Instantiate a server based on the "[server:main]" section. 12 | 4. Configure Python logging based on the logging sections. 13 | 5. Call the server with the application. 14 | 15 | Steps 1-3 and 5 are essentially wrappers around PasteDeploy. Only step 2 is 16 | really "using Pyramid", because only the application depends on other parts of 17 | Pyramid. The rest of the routine is copied directly from "paster serve" and 18 | does not depend on other parts of Pyramid. 19 | 20 | The way the launcher instantiates an application is often misunderstood so 21 | let's stop for a moment and detail it. Here's part of the app section in the 22 | Akhet Demo: 23 | 24 | .. code-block:: ini 25 | 26 | [app:main] 27 | use = egg:akhet_demo#main 28 | pyramid.reload_templates = true 29 | pyramid.debug_authorization = false 30 | 31 | The "use=" line indirectly names a Python callable to load. "egg:" says to look up a 32 | Python object by entry point. (Entry points are a feature provided by 33 | Setuptools, which is why Pyramid/Pylons require it or Distribute to be 34 | installed.) "akhet_demo" is the name of the Python 35 | distribution to look in (the Pyramid application), and "main" is the entry 36 | point. The launcher calls 37 | ``pkg_resources.require("akhet_demo#main")`` in Setuptools, and Setuptools 38 | returns the Python object. Entry points are defined in the distribution's 39 | setup.py, and the installer writes them to an entry points file. Here's the 40 | *akhet_demo.egg-info/entry_points.txt* file: 41 | 42 | .. code-block:: ini 43 | 44 | [paste.app_factory] 45 | main = akhet_demo:main 46 | 47 | "paste.app_factory" is the entry point group, a name publicized in the 48 | PasteDeploy docs for all applications that want to be compatible with it. 49 | "main" (on the left side of the equal sign) is the entry point. 50 | "akhet_demo:main" says to import the ``akhet_demo`` package and load a "main" 51 | attribute. This is our ``main()`` function defined in 52 | *akhet_demo/\_\_init\_\_.py*. The other options in the "[app:main]" section 53 | become keyword arguments to this callable. These options are called "settings" 54 | in Pyramid and "config variables" in Pylons. (The options in the "[DEFAULT]" 55 | section are also passed as default values.) Both frameworks provide a way to 56 | access these variables in application code. In Pyramid they're in the 57 | ``request.registry.settings`` dict. In Pylons they're in the ``pylons.config`` 58 | magic global. 59 | 60 | The launcher loads the server in the same way, using the "[server:main]" 61 | section. 62 | 63 | *More details:* The heavy lifting is done by ``loadapp()`` and ``loadserver()`` 64 | in ``paste.deploy.loadwsgi``. Loadwsgi is obtuse and undocumented, but 65 | ``pyramid.paster`` has some convenience functions that either call or mimic some of 66 | its routines. 67 | 68 | Alternative launchers such as mod_wsgi read only the "[app:main]" section and 69 | ignore the server section, but they're still using PasteDeploy or the 70 | equivalent. It's also possible to instantiate the application manually without an 71 | INI file or PasteDeploy, as we'll see in the chapter called "The Main Function". 72 | 73 | Now that we know more about how the launcher loads the application, let's look 74 | closer at a Pyramid application itself. 75 | -------------------------------------------------------------------------------- /docs/pylons/migrate.rst: -------------------------------------------------------------------------------- 1 | Migrating an Existing Pylons Application 2 | ++++++++++++++++++++++++++++++++++++++++ 3 | 4 | There are two general ways to port a Pylons application to Pyramid. One is to 5 | start from scratch, expressing the application's behavior in Pyramid. Many 6 | aspects such as the models, templates, and static files can be used unchanged 7 | or mostly unchanged. Other aspects like such as the controllers and globals 8 | will have to be rewritten. The route map can be ported to the new syntax, or 9 | you can take the opportunity to restructure your routes. 10 | 11 | The other way is to port one URL at a time, and let Pyramid serve the ported 12 | URLs and Pylons serve the unported URLs. There are several ways to do this: 13 | 14 | * Run both the Pyramid and Python applications in Apache, and use mod_rewrite 15 | to send different URLs to different applications. 16 | * Set up ``paste.cascade`` in the INI file, so that it will first try one 17 | application and then the other if the URL returns "Not Found". (This is how 18 | Pylons serves static files.) 19 | * Wrap the Pylons application in a Pyramid view. See pyramid.wsgiapp.wsgiapp2_. 20 | 21 | Also see the :ref:`porting` section in the Cookbook. 22 | 23 | *Caution:* running a Pyramid and a Pylons application simultaneously may bring up 24 | some tricky issues such as coordiating database connections, sessions, data 25 | files, etc. These are beyond the scope of this Guide. 26 | 27 | You'll also have to choose whether to write the Pyramid application in Python 2 28 | or 3. Pyramid 1.3 runs on Python 3, along with Mako and SQLAlchemy, and the 29 | Waitress and CherryPy HTTP servers (but not PasteHTTPServer). But not all 30 | optional libraries have been ported yet, and your application may depend on 31 | libraries which haven't been. 32 | 33 | 34 | .. include:: ../links.rst 35 | 36 | 37 | .. _pyramid.wsgiapp.wsgiapp2: https://docs.pylonsproject.org/projects/pyramid/en/latest/api/wsgi.html#pyramid.wsgi.wsgiapp2 38 | -------------------------------------------------------------------------------- /docs/pylons/sessions.rst: -------------------------------------------------------------------------------- 1 | Sessions 2 | ++++++++ 3 | 4 | Pyramid uses Beaker sessions just like Pylons, but they're not enabled by 5 | default. To use them you'll have to add the "pyramid_beaker" package as a 6 | dependency, and put the following line in your ``main()`` function:: 7 | 8 | config.include("pyramid_beaker") 9 | 10 | (To add a dependency, put it in the ``requires`` list in setup.py, and 11 | reinstall the application.) 12 | 13 | The default configuration is in-memory sessions and (I think) no caching. You 14 | can customize this by putting configuration settings in your INI file or in the 15 | ``settings`` dict at the beginning of the ``main()`` function (before the 16 | Configurator is instantiated). The Akhet Demo configures Beaker with the 17 | following settings, borrowed from the Pylons configuration: 18 | 19 | .. code-block:: ini 20 | 21 | # Beaker cache 22 | cache.regions = default_term, second, short_term, long_term 23 | cache.type = memory 24 | cache.second.expire = 1 25 | cache.short_term.expire = 60 26 | cache.default_term.expire = 300 27 | cache.long_term.expire = 3600 28 | 29 | # Beaker sessions 30 | #session.type = file 31 | #session.data_dir = %(here)s/data/sessions/data 32 | #session.lock_dir = %(here)s/data/sessions/lock 33 | session.type = memory 34 | session.key = akhet_demo 35 | session.secret = 0cb243f53ad865a0f70099c0414ffe9cfcfe03ac 36 | 37 | To use file-based sessions like in Pylons, uncomment the first three session 38 | settings and comment out the "session.type = memory" line. 39 | 40 | You should set the "session.secret=" setting to a random string. It's used to 41 | digitally sign the session cookie to prevent session hijacking. 42 | 43 | Beaker has several persistence backends available, including memory, files, 44 | SQLAlchemy, memcached, and cookies (which stores each session variable in a 45 | client-side cookie, and has size limitationss). The most popular 46 | deployment backend nowadays is memcached, which can act as a shared storage 47 | between several processes and servers, thus providing the speed of memory with 48 | the ability to scale to a multi-server cluster. Pylons defaults to disk-based 49 | sessions. 50 | 51 | Beaker plugs into Pyramid's built-in session interface, which is accessed via 52 | ``request.session``. Use it like a dict. Unlike raw Beaker sessions, you don't 53 | have to call ``session.save()`` every time you change something, but you should 54 | call ``session.changed()`` if you've modified a *mutable* item in the session; 55 | e.g., ``session["mylist"].append(1)``. 56 | 57 | The Pyramid session interface also has some extra features. It can store a set 58 | of "flash messages" to display on the next page view, which is useful when you 59 | want to push a success/failure message and redirect, and the message will be 60 | displayed on the target page. It's based on ``webhelpers.flash``, which is 61 | incompatible with Pyramid because it depends on Pylons' magic globals. There 62 | are also methods to set a secure form token, which prevent form submissions 63 | that didn't come from a form requested earlier in the session (and thus may be 64 | a cross-site forgery attack). (Note: flash messages are not related to the Adobe 65 | Flash movie player.) 66 | 67 | See the :ref:`sessions_chapter` chapter in the Pyramid manual for the API of 68 | all these features and other features. The Beaker_ manual will help you 69 | configure a backend. The Akhet_ Demo is an example of using Pyramid with 70 | Beaker, and has flash messages. 71 | 72 | *Note:* I sometimes get an exception in the debug toolbar when sessions are 73 | enabled. They may be a code discrepency between the distributions. If this 74 | happens to you, you can disable the toolbar until the problem is fixed. 75 | 76 | 77 | .. include:: ../links.rst 78 | -------------------------------------------------------------------------------- /docs/routing/index.rst: -------------------------------------------------------------------------------- 1 | Routing: Traversal and URL Dispatch 2 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | combining 8 | traversal_in_views 9 | traversal_sqlalchemy 10 | 11 | For more information on URL dispatch, see the `URL Dispatch 12 | `_ 13 | section of the Pyramid documentation. 14 | 15 | For more information traversal, see the following sections of the Pyramid 16 | documentation: 17 | 18 | - `Hello Traversal `_ 19 | - `Much Ado about Traversal `_ 20 | - `Traversal `_ 21 | - `Hybrid Dispatching `_ 22 | - `Virtual Hosting `_ 23 | -------------------------------------------------------------------------------- /docs/routing/traversal_in_views.rst: -------------------------------------------------------------------------------- 1 | .. _traversal_in_views: 2 | 3 | Using Traversal in Pyramid Views 4 | ================================ 5 | 6 | A trivial example of how to use :term:`traversal` in your view code. 7 | 8 | You may remember that a Pyramid :term:`view` is called with a 9 | :term:`context` argument. 10 | 11 | .. code-block:: python 12 | 13 | def my_view(context, request): 14 | return render_view_to_response(context, request) 15 | 16 | 17 | When using traversal, ``context`` will be the :term:`resource` object 18 | that was found by traversal. Configuring which resources a view 19 | responds to can be done easily via either the ``@view.config`` 20 | decorator. 21 | 22 | .. code-block:: python 23 | 24 | from models import MyResource 25 | 26 | @view_config(context=MyResource) 27 | def my_view(context, request): 28 | return render_view_to_response(context, request) 29 | 30 | or via ``config.add_view``:: 31 | 32 | from models import MyResource 33 | config = Configurator() 34 | config.add_view('myapp.views.my_view', context=MyResource) 35 | 36 | Either way, any request that triggers traversal and traverses to a 37 | ``MyResource`` instance will result in calling this view with that 38 | instance as the ``context`` argument. 39 | 40 | Optional: Using Interfaces 41 | -------------------------- 42 | 43 | If your resource classes implement :term:`interfaces `, 44 | you can configure your views by interface. This is one way to decouple 45 | view code from a specific resource implementation. 46 | 47 | .. code-block:: python 48 | 49 | # models.py 50 | from zope.interface import implements 51 | from zope.interface import Interface 52 | 53 | class IMyResource(Interface): 54 | pass 55 | 56 | class MyResource(object): 57 | implements(IMyResource) 58 | 59 | # views.py 60 | from models import IMyResource 61 | 62 | @view_config(context=IMyResource) 63 | def my_view(context, request): 64 | return render_view_to_response(context, request) 65 | 66 | 67 | See Also 68 | -------- 69 | 70 | - :ref:`much_ado_about_traversal_chapter` 71 | 72 | - :ref:`comparing_traversal_and_dispatch` 73 | 74 | - The "Virginia" sample application: https://github.com/Pylons/virginia/blob/master/virginia/views.py 75 | 76 | - ZODB and Traversal in Pyramid tutorial: https://docs.pylonsproject.org/projects/pyramid/en/latest/tutorials/wiki/index.html#bfg-wiki-tutorial 77 | 78 | - Resources which implement interfaces: https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/resources.html#resources-which-implement-interfaces 79 | -------------------------------------------------------------------------------- /docs/sample_applications/index.rst: -------------------------------------------------------------------------------- 1 | Sample Pyramid Applications 2 | %%%%%%%%%%%%%%%%%%%%%%%%%%% 3 | 4 | This section is a collection of sample Pyramid applications. 5 | 6 | If you know of other applications, please submit an issue or pull request via 7 | the `Pyramid Community Cookbook repo on GitHub 8 | `_ to add it to this list. 9 | 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | single_file_tasks 15 | -------------------------------------------------------------------------------- /docs/sample_applications/single_file_tasks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pylons/pyramid_cookbook/35bbfad6637ea85adbf0e18d168b8ecc3d01707d/docs/sample_applications/single_file_tasks.png -------------------------------------------------------------------------------- /docs/sample_applications/single_file_tasks_src/.gitignore: -------------------------------------------------------------------------------- 1 | .installed.cfg 2 | bin/ 3 | bootstrap.py 4 | eggs 5 | parts/ 6 | tasks.db 7 | var/ 8 | *.pyc 9 | *.swp 10 | -------------------------------------------------------------------------------- /docs/sample_applications/single_file_tasks_src/Makefile: -------------------------------------------------------------------------------- 1 | SHELL = /bin/bash 2 | WGET = wget 3 | 4 | PYTHON_VERSION := $(shell python -V 2>&1) 5 | BOOTSTRAP_PY_URL = http://svn.zope.org/*checkout*/zc.buildout/branches/2/bootstrap/bootstrap.py 6 | 7 | run: bin/python 8 | bin/python tasks.py 9 | 10 | bin/python: bin/buildout buildout.cfg 11 | if [[ "$(PYTHON_VERSION)" == Python\ 3* ]]; then echo "*** Python 3 ***"; bin/buildout -c buildout-py3.cfg; else echo "*** Python 2 ***"; bin/buildout; fi 12 | 13 | run-buildout: bin/buildout buildout.cfg 14 | bin/buildout 15 | 16 | bin/buildout: bootstrap.py 17 | python bootstrap.py 18 | 19 | bootstrap.py: 20 | $(WGET) -O bootstrap.py $(BOOTSTRAP_PY_URL) 21 | 22 | clean: 23 | $(RM) -r bin bootstrap.py develop-eggs eggs parts tasks.db var .installed.cfg 24 | -------------------------------------------------------------------------------- /docs/sample_applications/single_file_tasks_src/buildout.cfg: -------------------------------------------------------------------------------- 1 | [buildout] 2 | parts = 3 | python 4 | 5 | [python] 6 | recipe = zc.recipe.egg 7 | eggs = 8 | pyramid 9 | interpreter = python 10 | -------------------------------------------------------------------------------- /docs/sample_applications/single_file_tasks_src/schema.sql: -------------------------------------------------------------------------------- 1 | create table if not exists tasks ( 2 | id integer primary key autoincrement, 3 | name char(100) not null, 4 | closed bool not null 5 | ); 6 | 7 | insert or ignore into tasks (id, name, closed) values (0, 'Start learning Pyramid', 0); 8 | insert or ignore into tasks (id, name, closed) values (1, 'Do quick tutorial', 0); 9 | insert or ignore into tasks (id, name, closed) values (2, 'Have some beer!', 0); 10 | 11 | -------------------------------------------------------------------------------- /docs/sample_applications/single_file_tasks_src/static/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: sans-serif; 3 | font-size: 14px; 4 | color: #3e4349; 5 | } 6 | 7 | h1, h2, h3, h4, h5, h6 { 8 | font-family: Georgia; 9 | color: #373839; 10 | } 11 | 12 | a { 13 | color: #1b61d6; 14 | text-decoration: none; 15 | } 16 | 17 | input { 18 | font-size: 14px; 19 | width: 400px; 20 | border: 1px solid #bbbbbb; 21 | padding: 5px; 22 | } 23 | 24 | .button { 25 | font-size: 14px; 26 | font-weight: bold; 27 | width: auto; 28 | background: #eeeeee; 29 | padding: 5px 20px 5px 20px; 30 | border: 1px solid #bbbbbb; 31 | border-left: none; 32 | border-right: none; 33 | } 34 | 35 | #flash, #notfound { 36 | font-size: 16px; 37 | width: 500px; 38 | text-align: center; 39 | background-color: #e1ecfe; 40 | border-top: 2px solid #7a9eec; 41 | border-bottom: 2px solid #7a9eec; 42 | padding: 10px 20px 10px 20px; 43 | } 44 | 45 | #notfound { 46 | background-color: #fbe3e4; 47 | border-top: 2px solid #fbc2c4; 48 | border-bottom: 2px solid #fbc2c4; 49 | padding: 0 20px 30px 20px; 50 | } 51 | 52 | #tasks { 53 | width: 500px; 54 | } 55 | 56 | #tasks li { 57 | padding: 5px 0 5px 0; 58 | border-bottom: 1px solid #bbbbbb; 59 | } 60 | 61 | #tasks li.last { 62 | border-bottom: none; 63 | } 64 | 65 | #tasks .name { 66 | width: 400px; 67 | text-align: left; 68 | display: inline-block; 69 | } 70 | 71 | #tasks .actions { 72 | width: 80px; 73 | text-align: right; 74 | display: inline-block; 75 | } -------------------------------------------------------------------------------- /docs/sample_applications/single_file_tasks_src/templates/layout.mako: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | 5 | 6 | 7 | Pyramid Task's List Tutorial 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | % if request.session.peek_flash(): 17 |
18 | <% flash = request.session.pop_flash() %> 19 | % for message in flash: 20 | ${message}
21 | % endfor 22 |
23 | % endif 24 | 25 |
26 | 27 | ${next.body()} 28 | 29 |
30 | 31 | 32 | -------------------------------------------------------------------------------- /docs/sample_applications/single_file_tasks_src/templates/list.mako: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | <%inherit file="layout.mako"/> 3 | 4 |

Task's List

5 | 6 |
    7 | % if tasks: 8 | % for task in tasks: 9 |
  • 10 | ${task['name']} 11 | 12 | [ close ] 13 | 14 |
  • 15 | % endfor 16 | % else: 17 |
  • There are no open tasks
  • 18 | % endif 19 |
  • 20 | Add a new task 21 |
  • 22 |
-------------------------------------------------------------------------------- /docs/sample_applications/single_file_tasks_src/templates/new.mako: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | <%inherit file="layout.mako"/> 3 | 4 |

Add a new task

5 | 6 |
7 | 8 | 9 |
10 | -------------------------------------------------------------------------------- /docs/sample_applications/single_file_tasks_src/templates/notfound.mako: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | <%inherit file="layout.mako"/> 3 | 4 |
5 |

404 - PAGE NOT FOUND

6 | The page you're looking for isn't here. 7 |
8 | -------------------------------------------------------------------------------- /docs/static_assets/bundling/.gitignore: -------------------------------------------------------------------------------- 1 | *.egg 2 | *.egg-info 3 | *.pyc 4 | *$py.class 5 | *~ 6 | .coverage 7 | coverage.xml 8 | build/ 9 | dist/ 10 | .tox/ 11 | nosetests.xml 12 | env*/ 13 | tmp/ 14 | Data.fs* 15 | *.sublime-project 16 | *.sublime-workspace 17 | .*.sw? 18 | .sw? 19 | .idea 20 | .DS_Store 21 | coverage 22 | test 23 | node_modules 24 | /static 25 | -------------------------------------------------------------------------------- /docs/static_assets/bundling/bundling_example/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source = bundling_example 3 | omit = bundling_example/test* 4 | -------------------------------------------------------------------------------- /docs/static_assets/bundling/bundling_example/.gitignore: -------------------------------------------------------------------------------- 1 | *.egg 2 | *.egg-info 3 | *.pyc 4 | *$py.class 5 | *~ 6 | .coverage 7 | coverage.xml 8 | build/ 9 | dist/ 10 | .tox/ 11 | nosetests.xml 12 | env*/ 13 | tmp/ 14 | Data.fs* 15 | *.sublime-project 16 | *.sublime-workspace 17 | .*.sw? 18 | .sw? 19 | .DS_Store 20 | coverage 21 | test 22 | -------------------------------------------------------------------------------- /docs/static_assets/bundling/bundling_example/CHANGES.txt: -------------------------------------------------------------------------------- 1 | 0.0 2 | --- 3 | 4 | - Initial version. 5 | -------------------------------------------------------------------------------- /docs/static_assets/bundling/bundling_example/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.txt *.ini *.cfg *.rst 2 | recursive-include bundling_example *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2 3 | -------------------------------------------------------------------------------- /docs/static_assets/bundling/bundling_example/README.txt: -------------------------------------------------------------------------------- 1 | bundling_example 2 | ================ 3 | 4 | Getting Started 5 | --------------- 6 | 7 | - Change directory into your newly created project. 8 | 9 | cd bundling_example 10 | 11 | - Create a Python virtual environment. 12 | 13 | python3 -m venv env 14 | 15 | - Upgrade packaging tools. 16 | 17 | env/bin/pip install --upgrade pip setuptools 18 | 19 | - Install the project in editable mode with its testing requirements. 20 | 21 | env/bin/pip install -e ".[testing]" 22 | 23 | - Run your project's tests. 24 | 25 | env/bin/pytest 26 | 27 | - Run your project. 28 | 29 | env/bin/pserve development.ini 30 | -------------------------------------------------------------------------------- /docs/static_assets/bundling/bundling_example/bundling_example/__init__.py: -------------------------------------------------------------------------------- 1 | from pyramid.config import Configurator 2 | 3 | 4 | def main(global_config, **settings): 5 | """ This function returns a Pyramid WSGI application. 6 | """ 7 | config = Configurator(settings=settings) 8 | config.include('pyramid_jinja2') 9 | config.include('.routes') 10 | config.scan() 11 | return config.make_wsgi_app() 12 | -------------------------------------------------------------------------------- /docs/static_assets/bundling/bundling_example/bundling_example/routes.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | 3 | 4 | def includeme(config): 5 | config.add_static_view('static', 'static', cache_max_age=3600) 6 | config.add_route('home', '/') 7 | # after default static view add bundled static support 8 | config.add_static_view( 9 | "static_bundled", "static_bundled", cache_max_age=1 10 | ) 11 | path = pathlib.Path(config.registry.settings["statics.dir"]) 12 | # create the directory if missing otherwise pyramid will not start 13 | path.mkdir(exist_ok=True) 14 | config.override_asset( 15 | to_override="bundling_example:static_bundled/", 16 | override_with=config.registry.settings["statics.dir"], 17 | ) 18 | -------------------------------------------------------------------------------- /docs/static_assets/bundling/bundling_example/bundling_example/scripts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pylons/pyramid_cookbook/35bbfad6637ea85adbf0e18d168b8ecc3d01707d/docs/static_assets/bundling/bundling_example/bundling_example/scripts/__init__.py -------------------------------------------------------------------------------- /docs/static_assets/bundling/bundling_example/bundling_example/scripts/build_static_assets.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import json 3 | import logging 4 | import os 5 | import pathlib 6 | import shutil 7 | import subprocess 8 | import sys 9 | 10 | import pkg_resources 11 | from pyramid.paster import bootstrap, setup_logging 12 | 13 | log = logging.getLogger(__name__) 14 | 15 | 16 | def build_assets(registry, *cmd_args, **cmd_kwargs): 17 | settings = registry.settings 18 | build_dir = settings["statics.build_dir"] 19 | try: 20 | shutil.rmtree(build_dir) 21 | except FileNotFoundError as exc: 22 | log.warning(exc) 23 | # your application frontend source code and configuration directory 24 | # usually the containing main package.json 25 | assets_path = os.path.abspath( 26 | pkg_resources.resource_filename("bundling_example", "../../frontend") 27 | ) 28 | # copy package static sources to temporary build dir 29 | shutil.copytree( 30 | assets_path, 31 | build_dir, 32 | ignore=shutil.ignore_patterns( 33 | "node_modules", "bower_components", "__pycache__" 34 | ), 35 | ) 36 | # configuration files/variables can be picked up by webpack/rollup/gulp 37 | os.environ["FRONTEND_ASSSET_ROOT_DIR"] = settings["statics.dir"] 38 | worker_config = {'frontendAssetRootDir': settings["statics.dir"]} 39 | worker_config_file = pathlib.Path(build_dir) / 'pyramid_config.json' 40 | 41 | with worker_config_file.open('w') as f: 42 | f.write(json.dumps(worker_config)) 43 | # your actual build commands to execute: 44 | 45 | # download all requirements 46 | subprocess.run(["yarn"], env=os.environ, cwd=build_dir, check=True) 47 | # run build process 48 | subprocess.run(["yarn", "build"], env=os.environ, cwd=build_dir, check=True) 49 | 50 | 51 | def parse_args(argv): 52 | parser = argparse.ArgumentParser() 53 | parser.add_argument("config_uri", help="Configuration file, e.g., development.ini") 54 | return parser.parse_args(argv[1:]) 55 | 56 | 57 | def main(argv=sys.argv): 58 | args = parse_args(argv) 59 | setup_logging(args.config_uri) 60 | env = bootstrap(args.config_uri) 61 | request = env["request"] 62 | build_assets(request.registry) 63 | -------------------------------------------------------------------------------- /docs/static_assets/bundling/bundling_example/bundling_example/static/pyramid-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pylons/pyramid_cookbook/35bbfad6637ea85adbf0e18d168b8ecc3d01707d/docs/static_assets/bundling/bundling_example/bundling_example/static/pyramid-16x16.png -------------------------------------------------------------------------------- /docs/static_assets/bundling/bundling_example/bundling_example/static/pyramid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pylons/pyramid_cookbook/35bbfad6637ea85adbf0e18d168b8ecc3d01707d/docs/static_assets/bundling/bundling_example/bundling_example/static/pyramid.png -------------------------------------------------------------------------------- /docs/static_assets/bundling/bundling_example/bundling_example/templates/404.jinja2: -------------------------------------------------------------------------------- 1 | {% extends "layout.jinja2" %} 2 | 3 | {% block content %} 4 |
5 |

Pyramid Starter project

6 |

404 Page Not Found

7 |
8 | {% endblock content %} 9 | -------------------------------------------------------------------------------- /docs/static_assets/bundling/bundling_example/bundling_example/templates/layout.jinja2: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Cookiecutter Starter project for the Pyramid Web Framework 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 27 | 28 | 29 | 30 | 31 |
32 |
33 |
34 |
35 | 36 |
37 |
38 | {% block content %} 39 |

No content

40 | {% endblock content %} 41 |
42 |
43 |
44 | 51 |
52 |
53 | 56 |
57 |
58 |
59 | 60 | 61 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /docs/static_assets/bundling/bundling_example/bundling_example/templates/mytemplate.jinja2: -------------------------------------------------------------------------------- 1 | {% extends "layout.jinja2" %} 2 | 3 | {% block content %} 4 |
5 |

Pyramid Starter project

6 |

Welcome to {{project}}, a Pyramid application generated by
Cookiecutter.

7 |
8 | {% endblock content %} 9 | -------------------------------------------------------------------------------- /docs/static_assets/bundling/bundling_example/bundling_example/tests.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from pyramid import testing 4 | 5 | 6 | class ViewTests(unittest.TestCase): 7 | def setUp(self): 8 | self.config = testing.setUp() 9 | 10 | def tearDown(self): 11 | testing.tearDown() 12 | 13 | def test_my_view(self): 14 | from .views.default import my_view 15 | request = testing.DummyRequest() 16 | info = my_view(request) 17 | self.assertEqual(info['project'], 'bundling_example') 18 | 19 | 20 | class FunctionalTests(unittest.TestCase): 21 | def setUp(self): 22 | from bundling_example import main 23 | additional_settings = { 24 | 'statics.dir':'statics', 25 | 'statics.build_dir':'static_build' 26 | } 27 | app = main({}, **additional_settings) 28 | from webtest import TestApp 29 | self.testapp = TestApp(app) 30 | 31 | def test_root(self): 32 | res = self.testapp.get('/', status=200) 33 | self.assertTrue(b'Pyramid' in res.body) 34 | -------------------------------------------------------------------------------- /docs/static_assets/bundling/bundling_example/bundling_example/views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pylons/pyramid_cookbook/35bbfad6637ea85adbf0e18d168b8ecc3d01707d/docs/static_assets/bundling/bundling_example/bundling_example/views/__init__.py -------------------------------------------------------------------------------- /docs/static_assets/bundling/bundling_example/bundling_example/views/default.py: -------------------------------------------------------------------------------- 1 | from pyramid.view import view_config 2 | 3 | 4 | @view_config(route_name='home', renderer='../templates/mytemplate.jinja2') 5 | def my_view(request): 6 | return {'project': 'bundling_example'} 7 | -------------------------------------------------------------------------------- /docs/static_assets/bundling/bundling_example/bundling_example/views/notfound.py: -------------------------------------------------------------------------------- 1 | from pyramid.view import notfound_view_config 2 | 3 | 4 | @notfound_view_config(renderer='../templates/404.jinja2') 5 | def notfound_view(request): 6 | request.response.status = 404 7 | return {} 8 | -------------------------------------------------------------------------------- /docs/static_assets/bundling/bundling_example/development.ini: -------------------------------------------------------------------------------- 1 | ### 2 | # app configuration 3 | # https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html 4 | ### 5 | 6 | [app:main] 7 | use = egg:bundling_example 8 | 9 | pyramid.reload_templates = true 10 | pyramid.debug_authorization = false 11 | pyramid.debug_notfound = false 12 | pyramid.debug_routematch = false 13 | pyramid.default_locale_name = en 14 | pyramid.includes = 15 | pyramid_debugtoolbar 16 | 17 | # build result directory 18 | statics.dir = %(here)s/static 19 | # intermediate directory for build process 20 | statics.build_dir = %(here)s/static_build 21 | 22 | # By default, the toolbar only appears for clients from IP addresses 23 | # '127.0.0.1' and '::1'. 24 | # debugtoolbar.hosts = 127.0.0.1 ::1 25 | 26 | ### 27 | # wsgi server configuration 28 | ### 29 | 30 | [server:main] 31 | use = egg:waitress#main 32 | listen = localhost:6543 33 | 34 | ### 35 | # logging configuration 36 | # https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html 37 | ### 38 | 39 | [loggers] 40 | keys = root, bundling_example 41 | 42 | [handlers] 43 | keys = console 44 | 45 | [formatters] 46 | keys = generic 47 | 48 | [logger_root] 49 | level = INFO 50 | handlers = console 51 | 52 | [logger_bundling_example] 53 | level = DEBUG 54 | handlers = 55 | qualname = bundling_example 56 | 57 | [handler_console] 58 | class = StreamHandler 59 | args = (sys.stderr,) 60 | level = NOTSET 61 | formatter = generic 62 | 63 | [formatter_generic] 64 | format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s 65 | -------------------------------------------------------------------------------- /docs/static_assets/bundling/bundling_example/production.ini: -------------------------------------------------------------------------------- 1 | ### 2 | # app configuration 3 | # https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html 4 | ### 5 | 6 | [app:main] 7 | use = egg:bundling_example 8 | 9 | pyramid.reload_templates = false 10 | pyramid.debug_authorization = false 11 | pyramid.debug_notfound = false 12 | pyramid.debug_routematch = false 13 | pyramid.default_locale_name = en 14 | 15 | # build result directory 16 | statics.dir = %(here)s/static 17 | # intermediate directory for build process 18 | statics.build_dir = %(here)s/static_build 19 | 20 | ### 21 | # wsgi server configuration 22 | ### 23 | 24 | [server:main] 25 | use = egg:waitress#main 26 | listen = *:6543 27 | 28 | ### 29 | # logging configuration 30 | # https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html 31 | ### 32 | 33 | [loggers] 34 | keys = root, bundling_example 35 | 36 | [handlers] 37 | keys = console 38 | 39 | [formatters] 40 | keys = generic 41 | 42 | [logger_root] 43 | level = WARN 44 | handlers = console 45 | 46 | [logger_bundling_example] 47 | level = WARN 48 | handlers = 49 | qualname = bundling_example 50 | 51 | [handler_console] 52 | class = StreamHandler 53 | args = (sys.stderr,) 54 | level = NOTSET 55 | formatter = generic 56 | 57 | [formatter_generic] 58 | format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s 59 | -------------------------------------------------------------------------------- /docs/static_assets/bundling/bundling_example/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | testpaths = bundling_example 3 | python_files = *.py 4 | -------------------------------------------------------------------------------- /docs/static_assets/bundling/bundling_example/setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from setuptools import setup, find_packages 4 | 5 | here = os.path.abspath(os.path.dirname(__file__)) 6 | with open(os.path.join(here, 'README.txt')) as f: 7 | README = f.read() 8 | with open(os.path.join(here, 'CHANGES.txt')) as f: 9 | CHANGES = f.read() 10 | 11 | requires = [ 12 | 'plaster_pastedeploy', 13 | 'pyramid', 14 | 'pyramid_jinja2', 15 | 'pyramid_debugtoolbar', 16 | 'waitress', 17 | ] 18 | 19 | tests_require = [ 20 | 'WebTest >= 1.3.1', # py3 compat 21 | 'pytest', 22 | 'pytest-cov', 23 | ] 24 | 25 | setup( 26 | name='bundling_example', 27 | version='0.0', 28 | description='bundling_example', 29 | long_description=README + '\n\n' + CHANGES, 30 | classifiers=[ 31 | 'Programming Language :: Python', 32 | 'Framework :: Pyramid', 33 | 'Topic :: Internet :: WWW/HTTP', 34 | 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application', 35 | ], 36 | author='', 37 | author_email='', 38 | url='', 39 | keywords='web pyramid pylons', 40 | packages=find_packages(), 41 | include_package_data=True, 42 | zip_safe=False, 43 | extras_require={ 44 | 'testing': tests_require, 45 | }, 46 | install_requires=requires, 47 | entry_points={ 48 | 'paste.app_factory': [ 49 | 'main = bundling_example:main', 50 | ], 51 | 'console_scripts': [ 52 | 'bundling_example_build_statics = bundling_example.scripts.build_static_assets:main', 53 | ] 54 | }, 55 | ) 56 | -------------------------------------------------------------------------------- /docs/static_assets/bundling/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "channelstream-landing-frontend", 3 | "version": "0.1.0", 4 | "description": "Frontend for Channelstream landing page", 5 | "main": "index.js", 6 | "license": "MIT", 7 | "scripts": { 8 | "build": "webpack --progress --mode=production", 9 | "dev": "webpack --watch --progress --mode=development" 10 | }, 11 | "dependencies": { 12 | "@polymer/lit-element": "0.6.0-dev.5", 13 | "@webcomponents/webcomponentsjs": "^2.0.2", 14 | "pwa-helpers": "^0.8.3", 15 | "redux": "^4.0.0", 16 | "webpack": "^4.0.0", 17 | "webpack-cli": "^3.1.0", 18 | "webpack-dev-server": "^3.1.6", 19 | "webpack-uglify-js-plugin": "^1.1.9", 20 | "copy-webpack-plugin": "^4.4.2", 21 | "css-loader": "^1.0.0", 22 | "mini-css-extract-plugin": "^0.4.0", 23 | "node-sass": "^4.1.1", 24 | "npm-check-updates": "^2.14.0", 25 | "polymer-webpack-loader": "^2.0.1", 26 | "sass-loader": "^7.0.1", 27 | "style-loader": "^0.22.1", 28 | "ts-loader": "^4.5.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /docs/static_assets/bundling/frontend/src/index.js: -------------------------------------------------------------------------------- 1 | alert('this is from bundle'); 2 | -------------------------------------------------------------------------------- /docs/static_assets/bundling/frontend/src/sass.js: -------------------------------------------------------------------------------- 1 | import './sass/main.scss'; 2 | -------------------------------------------------------------------------------- /docs/static_assets/bundling/frontend/src/sass/main.scss: -------------------------------------------------------------------------------- 1 | body { 2 | color: lime; 3 | } 4 | -------------------------------------------------------------------------------- /docs/static_assets/bundling/frontend/webpack.config.js: -------------------------------------------------------------------------------- 1 | require('style-loader'); 2 | require('css-loader'); 3 | var CopyWebpackPlugin = require('copy-webpack-plugin'); 4 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 5 | var path = require('path'); 6 | 7 | const projectName = 'channelstream_landing'; 8 | let devDestinationDir = path.join(__dirname, '..', 'static'); 9 | 10 | destinationRootDir = process.env.FRONTEND_ASSSET_ROOT_DIR || devDestinationDir; 11 | console.log('Root directory:', destinationRootDir); 12 | outputDir = path.resolve(destinationRootDir); 13 | 14 | module.exports = { 15 | // Tell Webpack which file kicks off our app. 16 | entry: { 17 | main: path.resolve(__dirname, 'src/index.js'), 18 | sass: path.resolve(__dirname, 'src/sass.js') 19 | }, 20 | output: { 21 | filename: 'bundle-[name].js', 22 | path: outputDir 23 | }, 24 | // Tell Webpack which directories to look in to resolve import statements. 25 | // Normally Webpack will look in node_modules by default but since we’re overriding 26 | // the property we’ll need to tell it to look there in addition to the 27 | // bower_components folder. 28 | resolve: { 29 | modules: [ 30 | path.resolve(__dirname, 'node_modules') 31 | ] 32 | }, 33 | // These rules tell Webpack how to process different module types. 34 | // Remember, *everything* is a module in Webpack. That includes 35 | // CSS, and (thanks to our loader) HTML. 36 | module: { 37 | rules: [ 38 | { 39 | test: /\.scss$/, 40 | use: [ 41 | MiniCssExtractPlugin.loader, 42 | "css-loader", // translates CSS into CommonJS 43 | "sass-loader" // compiles Sass to CSS 44 | ] 45 | } 46 | ] 47 | }, 48 | plugins: [ 49 | new MiniCssExtractPlugin({ 50 | // Options similar to the same options in webpackOptions.output 51 | // both options are optional 52 | filename: "css/main.css" 53 | }), 54 | // Polyfills 55 | // This plugin will copy files over to ‘./dist’ without transforming them. 56 | // That's important because the custom-elements-es5-adapter.js MUST 57 | // remain in ES2015. We’ll talk about this a bit later :) 58 | new CopyWebpackPlugin([ { 59 | from: '**/*.js', 60 | context: path.resolve(__dirname, 'node_modules/@webcomponents/webcomponentsjs'), 61 | to: path.join(outputDir, 'node_modules/@webcomponents/webcomponentsjs') 62 | }]) 63 | ] 64 | }; 65 | -------------------------------------------------------------------------------- /docs/static_assets/index.rst: -------------------------------------------------------------------------------- 1 | **************************** 2 | Static Assets (Static Files) 3 | **************************** 4 | 5 | .. toctree:: 6 | :maxdepth: 2 7 | 8 | serving-files 9 | uploading-files 10 | bundling-static-assets 11 | 12 | For more information on static assets, see the :ref:`pyramid:assets_chapter` 13 | section of the Pyramid documentation. 14 | -------------------------------------------------------------------------------- /docs/static_assets/uploading-files.rst: -------------------------------------------------------------------------------- 1 | =============== 2 | Uploading Files 3 | =============== 4 | 5 | There are two parts necessary for handling file uploads. The first is to 6 | make sure you have a form that's been setup correctly to accept files. This 7 | means adding ``enctype`` attribute to your ``form`` element with the value of 8 | ``multipart/form-data``. A very simple example would be a form that accepts 9 | an mp3 file. Notice we've setup the form as previously explained and also 10 | added an ``input`` element of the ``file`` type. 11 | 12 | .. code-block:: html 13 | :linenos: 14 | 15 |
17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | The second part is handling the file upload in your view callable (above, 25 | assumed to answer on ``/store_mp3_view``). The uploaded file is added to the 26 | request object as a ``cgi.FieldStorage`` object accessible through the 27 | ``request.POST`` multidict. The two properties we're interested in are the 28 | ``file`` and ``filename`` and we'll use those to write the file to disk: 29 | 30 | .. code-block:: python 31 | 32 | import os 33 | import shutil 34 | 35 | from pyramid.response import Response 36 | 37 | def store_mp3_view(request): 38 | # ``filename`` contains the name of the file in string format. 39 | # 40 | # WARNING: Internet Explorer is known to send an absolute file 41 | # *path* as the filename. This example is naive; it trusts 42 | # user input. 43 | filename = request.POST['mp3'].filename 44 | 45 | # ``input_file`` contains the actual file data which needs to be 46 | # stored somewhere. 47 | input_file = request.POST['mp3'].file 48 | 49 | # Using the filename like this without cleaning it is very 50 | # insecure so please keep that in mind when writing your own 51 | # file handling. 52 | file_path = os.path.join('/tmp', filename) 53 | with open(file_path, 'wb') as output_file: 54 | shutil.copyfileobj(input_file, output_file) 55 | 56 | return Response('OK') 57 | -------------------------------------------------------------------------------- /docs/templates/customrenderers.rst: -------------------------------------------------------------------------------- 1 | .. _customrenderers: 2 | 3 | Custom Renderers 4 | ---------------- 5 | 6 | Pyramid supports custom renderers, alongside the 7 | :ref:`default renderers ` shipped with Pyramid. 8 | 9 | Here's a basic comma-separated value (CSV) renderer to output a CSV file to 10 | the browser. Add the following to a ``renderers.py`` module in your 11 | application (or anywhere else you'd like to place such things): 12 | 13 | .. code-block:: python 14 | 15 | import csv 16 | try: 17 | from StringIO import StringIO # python 2 18 | except ImportError: 19 | from io import StringIO # python 3 20 | 21 | class CSVRenderer(object): 22 | def __init__(self, info): 23 | pass 24 | 25 | def __call__(self, value, system): 26 | """ Returns a plain CSV-encoded string with content-type 27 | ``text/csv``. The content-type may be overridden by 28 | setting ``request.response.content_type``.""" 29 | 30 | request = system.get('request') 31 | if request is not None: 32 | response = request.response 33 | ct = response.content_type 34 | if ct == response.default_content_type: 35 | response.content_type = 'text/csv' 36 | 37 | fout = StringIO() 38 | writer = csv.writer(fout, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL) 39 | 40 | writer.writerow(value.get('header', [])) 41 | writer.writerows(value.get('rows', [])) 42 | 43 | return fout.getvalue() 44 | 45 | Now you have a renderer. Let's register with our application's 46 | ``Configurator``: 47 | 48 | .. code-block:: python 49 | 50 | config.add_renderer('csv', 'myapp.renderers.CSVRenderer') 51 | 52 | Of course, modify the dotted-string to point to the module location you 53 | decided upon. To use the renderer, create a view: 54 | 55 | .. code-block:: python 56 | 57 | @view_config(route_name='data', renderer='csv') 58 | def my_view(request): 59 | query = DBSession.query(table).all() 60 | header = ['First Name', 'Last Name'] 61 | rows = [[item.first_name, item.last_name] for item in query] 62 | 63 | # override attributes of response 64 | filename = 'report.csv' 65 | request.response.content_disposition = 'attachment;filename=' + filename 66 | 67 | return { 68 | 'header': header, 69 | 'rows': rows, 70 | } 71 | 72 | def main(global_config, **settings): 73 | config = Configurator(settings=settings) 74 | config.add_route('data', '/data') 75 | config.scan() 76 | return config.make_wsgi_app() 77 | 78 | Query your database in your ``query`` variable, establish your ``headers`` and initialize 79 | ``rows``. 80 | 81 | Override attributes of response as required by your use case. We implement this aspect in view code to keep our custom renderer code focused to the task. 82 | 83 | Lastly, we pass ``headers`` and ``rows`` to the CSV renderer. 84 | 85 | For more information on how to add custom Renderers, see the following sections 86 | of the Pyramid documentation: 87 | 88 | - `Adding a new Renderer `_ 89 | - `Varying Attributes of Rendered Responses `_ 90 | -------------------------------------------------------------------------------- /docs/templates/index.rst: -------------------------------------------------------------------------------- 1 | Templates and Renderers 2 | %%%%%%%%%%%%%%%%%%%%%%% 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | templates 8 | mako_i18n 9 | chameleon_i18n 10 | customrenderers 11 | customrendererxlsx 12 | 13 | For more information on Templates and Renderers, see the following sections 14 | of the Pyramid documentation: 15 | 16 | - `Templates `_ 17 | - `Renderers `_ 18 | - `Internationalization and Localization 19 | `_ 20 | -------------------------------------------------------------------------------- /docs/testing/index.rst: -------------------------------------------------------------------------------- 1 | Testing 2 | %%%%%%% 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | testing_post_curl 8 | 9 | For more information on testing see the `Testing 10 | `_ 11 | section of the Pyramid documentation. 12 | 13 | For additional information on other testing packages see: 14 | 15 | - `WebTest `_ 16 | - `nose `_ 17 | -------------------------------------------------------------------------------- /docs/todo.rst: -------------------------------------------------------------------------------- 1 | TODO 2 | %%%% 3 | 4 | - Provide an example of using a newrequest subscriber to mutate the request, 5 | providing additional user data from a database based on the current 6 | authenticated userid. 7 | 8 | - Provide an example of adding a database connection to ``settings`` in 9 | __init__ and using it from a view. 10 | 11 | - Provide an example of a catchall 500 error view. 12 | 13 | - Redirecting to a URL with Parameters: 14 | 15 | .. code-block:: irc 16 | 17 | [22:04] How do I redirect to a url and set some GET params? 18 | some thing like return HTTPFound(location="whatever", params={ params here }) 19 | [22:05] return HTTPFound(location="whatever?a=1&b=2") 20 | [22:06] ok. and I would need to urlencode the entire string? 21 | [22:06] or is that handled automatically 22 | [22:07] its a url 23 | [22:07] like you'd type into the browser 24 | 25 | - Add an example of using a cascade to serve static assets from the root. 26 | 27 | - Explore static file return from handler action using wsgiapp2 + fileapp. 28 | 29 | - https://dannynavarro.net/2011/01/14/async-web-apps-with-pyramid/ 30 | 31 | - http://alexmarandon.com/articles/zodb_bfg_pyramid_notes/ 32 | 33 | - https://groups.google.com/forum/#!msg/pylons-devel/0QxHTgeswrw/yTWxlDU1WKsJ 34 | (pyramid_jinja2 i18n), also 35 | https://github.com/Pylons/pyramid_jinja2/pull/14 36 | 37 | - Simple asynchronous task queue: https://dannynavarro.net/2011/01/23/async-pyramid-example-done-right/ 38 | 39 | - `Installing Pyramid on Red Hat Enterprise Linux 6 40 | `_ (John Fulton). 41 | 42 | - Chameleon main template injected via BeforeRender. 43 | 44 | - Hybrid authorization: https://github.com/mmerickel/pyramid_auth_demo 45 | 46 | - http://whippleit.blogspot.com/2011/04/pyramid-on-google-app-engine-take-1-for.html 47 | 48 | - Custom events: https://dannynavarro.net/2011/06/12/using-custom-events-in-pyramid/ 49 | 50 | - TicTacToe and Long Polling With Pyramid: https://michael.merickel.org/2011/6/21/tictactoe-and-long-polling-with-pyramid/ 51 | 52 | - Jim Penny's rolodex tutorial: http://jpenny.im/ 53 | 54 | - Thorsten Lockert's formhelpers/Pyramid example: https://github.com/tholo/formhelpers2 55 | 56 | - The Python Ecosystem, an Introduction: http://mirnazim.org/writings/python-ecosystem-introduction/ 57 | 58 | - Outgrowing Pyramid Handlers: https://michael.merickel.org/2011/8/23/outgrowing-pyramid-handlers/ 59 | 60 | - Incorporate Custom Configuration (Google Analytics) into a Pyramid Application: https://russell.ballestrini.net/how-to-incorporate-custom-configuration-in-a-pyramid-application/ 61 | 62 | - Cookbook docs reorg 63 | 64 | - Move tutorials/overviews to tutorial project and replace with links 65 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/addcontent.rst: -------------------------------------------------------------------------------- 1 | ================================== 2 | 5: Adding Resources To Hierarchies 3 | ================================== 4 | 5 | Multiple views per type allowing addition of content anywhere in a resource 6 | tree. 7 | 8 | Background 9 | ========== 10 | 11 | We now have multiple kinds of things, but only one view per resource type. We 12 | need the ability to add things to containers, then view and edit resources. 13 | 14 | We will use the previously mentioned concept of named views. A name is a part 15 | of the URL that appears after the resource identifier. For example: 16 | 17 | .. code-block:: python 18 | 19 | @view_config(context=Folder, name='add_document') 20 | 21 | ...means that this URL:: 22 | 23 | http://localhost:6543/some_folder/add_document 24 | 25 | ...will match the view being configured. It's as if you have an object-oriented 26 | web with operations on resources represented by a URL. 27 | 28 | Goals 29 | ===== 30 | 31 | - Allow adding and editing content in a resource tree. 32 | 33 | - Create a simple form which POSTs data. 34 | 35 | - Create a view which takes the POST data, creates a resource, and redirects to 36 | the newly-added resource. 37 | 38 | - Create per-type named views. 39 | 40 | Steps 41 | ===== 42 | 43 | #. We are going to use the previous step as our starting point: 44 | 45 | .. code-block:: bash 46 | 47 | $ cd ..; cp -r typeviews addcontent; cd addcontent 48 | $ $VENV/bin/python setup.py develop 49 | 50 | 51 | #. Our views in ``addcontent/tutorial/views.py`` need type-specific 52 | registrations: 53 | 54 | .. literalinclude:: addcontent/tutorial/views.py 55 | :linenos: 56 | :emphasize-lines: 1-3,14,32-54 57 | 58 | #. Make a re-usable snippet in ``addcontent/tutorial/templates/addform.jinja2`` 59 | for adding content: 60 | 61 | .. literalinclude:: addcontent/tutorial/templates/addform.jinja2 62 | :language: jinja 63 | :linenos: 64 | 65 | #. Add this snippet to ``addcontent/tutorial/templates/root.jinja2``: 66 | 67 | .. literalinclude:: addcontent/tutorial/templates/root.jinja2 68 | :language: jinja 69 | :linenos: 70 | :emphasize-lines: 8-9 71 | 72 | #. Forms are needed in ``addcontent/tutorial/templates/folder.jinja2``: 73 | 74 | .. literalinclude:: addcontent/tutorial/templates/folder.jinja2 75 | :language: jinja 76 | :linenos: 77 | :emphasize-lines: 7-8 78 | 79 | #. ``$ $VENV/bin/nosetests`` should report running 4 tests. 80 | 81 | #. Run your Pyramid application with: 82 | 83 | .. code-block:: bash 84 | 85 | $ $VENV/bin/pserve development.ini --reload 86 | 87 | #. Open http://localhost:6543/ in your browser. 88 | 89 | Analysis 90 | ======== 91 | 92 | Our views now represent a richer system, where form data can be processed to 93 | modify content in the tree. We do this by attaching named views to resource 94 | types, giving them a natural system for object-oriented operations. 95 | 96 | To mimic uniqueness, we randomly choose a satisfactorily large number. For true 97 | uniqueness, we would also need to check that the number does not already exist 98 | at the same level of the resource tree. 99 | 100 | We'll start to address a couple of issues brought up in the Extra Credit below 101 | in the next step of this tutorial, :doc:`zodb`. 102 | 103 | Extra Credit 104 | ============ 105 | 106 | 1. What happens if you add folders and documents, then restart your app? 107 | 108 | 2. What happens if you remove the pseudo-random, pseudo-unique naming 109 | convention and replace it with a fixed value? 110 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/addcontent/development.ini: -------------------------------------------------------------------------------- 1 | [app:main] 2 | use = egg:tutorial 3 | pyramid.reload_templates = true 4 | pyramid.includes = 5 | pyramid_debugtoolbar 6 | 7 | [server:main] 8 | use = egg:pyramid#wsgiref 9 | host = 0.0.0.0 10 | port = 6543 11 | 12 | # Begin logging configuration 13 | 14 | [loggers] 15 | keys = root, tutorial 16 | 17 | [logger_tutorial] 18 | level = DEBUG 19 | handlers = 20 | qualname = tutorial 21 | 22 | [handlers] 23 | keys = console 24 | 25 | [formatters] 26 | keys = generic 27 | 28 | [logger_root] 29 | level = INFO 30 | handlers = console 31 | 32 | [handler_console] 33 | class = StreamHandler 34 | args = (sys.stderr,) 35 | level = NOTSET 36 | formatter = generic 37 | 38 | [formatter_generic] 39 | format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s 40 | 41 | # End logging configuration 42 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/addcontent/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | requires = [ 4 | 'pyramid', 5 | 'pyramid_jinja2', 6 | 'pyramid_debugtoolbar' 7 | ] 8 | 9 | setup(name='tutorial', 10 | install_requires=requires, 11 | entry_points="""\ 12 | [paste.app_factory] 13 | main = tutorial:main 14 | """, 15 | ) 16 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/addcontent/tutorial/__init__.py: -------------------------------------------------------------------------------- 1 | from pyramid.config import Configurator 2 | 3 | from .resources import bootstrap 4 | 5 | 6 | def main(global_config, **settings): 7 | config = Configurator(settings=settings, 8 | root_factory=bootstrap) 9 | config.include('pyramid_jinja2') 10 | config.scan('.views') 11 | return config.make_wsgi_app() 12 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/addcontent/tutorial/resources.py: -------------------------------------------------------------------------------- 1 | class Folder(dict): 2 | def __init__(self, name, parent, title): 3 | self.__name__ = name 4 | self.__parent__ = parent 5 | self.title = title 6 | 7 | 8 | class Root(Folder): 9 | pass 10 | 11 | 12 | class Document(object): 13 | def __init__(self, name, parent, title): 14 | self.__name__ = name 15 | self.__parent__ = parent 16 | self.title = title 17 | 18 | # Done outside bootstrap to persist from request to request 19 | root = Root('', None, 'My Site') 20 | 21 | 22 | def bootstrap(request): 23 | if not root.values(): 24 | # No values yet, let's make: 25 | # / 26 | # doc1 27 | # doc2 28 | # folder1/ 29 | # doc1 30 | doc1 = Document('doc1', root, 'Document 01') 31 | root['doc1'] = doc1 32 | doc2 = Document('doc2', root, 'Document 02') 33 | root['doc2'] = doc2 34 | folder1 = Folder('folder1', root, 'Folder 01') 35 | root['folder1'] = folder1 36 | 37 | # Only has to be unique in folder 38 | doc11 = Document('doc1', folder1, 'Document 01') 39 | folder1['doc1'] = doc11 40 | 41 | return root -------------------------------------------------------------------------------- /docs/traversal_tutorial/addcontent/tutorial/templates/addform.jinja2: -------------------------------------------------------------------------------- 1 |

2 |

5 |
6 | 8 |
9 | 10 |
11 |

12 |

13 |

16 |
17 | 19 |
20 | 21 |
22 |

-------------------------------------------------------------------------------- /docs/traversal_tutorial/addcontent/tutorial/templates/breadcrumbs.jinja2: -------------------------------------------------------------------------------- 1 | {% for p in view.parents %} 2 | 3 | {{ p.title }} >> 4 | 5 | {% endfor %} 6 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/addcontent/tutorial/templates/contents.jinja2: -------------------------------------------------------------------------------- 1 |

Contents

2 |
    3 | {% for child in context.values() %} 4 |
  • 5 | {{ child.title }} 6 |
  • 7 | {% endfor %} 8 |
9 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/addcontent/tutorial/templates/document.jinja2: -------------------------------------------------------------------------------- 1 | {% extends "templates/layout.jinja2" %} 2 | {% block content %} 3 | 4 |

{{ context.title }}

5 |

A document might have some body text.

6 | 7 | {% endblock content %} 8 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/addcontent/tutorial/templates/folder.jinja2: -------------------------------------------------------------------------------- 1 | {% extends "templates/layout.jinja2" %} 2 | {% block content %} 3 | 4 |

{{ context.title }}

5 | {% include "templates/contents.jinja2" %} 6 | 7 | {% include "templates/addform.jinja2" %} 8 | 9 | {% endblock content %} 10 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/addcontent/tutorial/templates/header.jinja2: -------------------------------------------------------------------------------- 1 | Tutorial -------------------------------------------------------------------------------- /docs/traversal_tutorial/addcontent/tutorial/templates/layout.jinja2: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ page_title }} 5 | 7 | 8 | 9 | 10 | 15 | 16 |
17 | 18 |
19 | {% include "templates/breadcrumbs.jinja2" %} 20 |
21 | 22 |

{{ page_title }}

23 | {% block content %} 24 | {% endblock content %} 25 | 26 |
27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/addcontent/tutorial/templates/root.jinja2: -------------------------------------------------------------------------------- 1 | {% extends "templates/layout.jinja2" %} 2 | {% block content %} 3 | 4 |

{{ context.title }}

5 |

The root might have some other text.

6 | {% include "templates/contents.jinja2" %} 7 | 8 | {% include "templates/addform.jinja2" %} 9 | 10 | {% endblock content %} 11 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/addcontent/tutorial/tests.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from pyramid.testing import DummyRequest 4 | from pyramid.testing import DummyResource 5 | 6 | 7 | class TutorialViewsUnitTests(unittest.TestCase): 8 | def _makeOne(self, context, request): 9 | from .views import TutorialViews 10 | 11 | inst = TutorialViews(context, request) 12 | return inst 13 | 14 | def test_site(self): 15 | request = DummyRequest() 16 | context = DummyResource() 17 | inst = self._makeOne(context, request) 18 | result = inst.root() 19 | self.assertIn('Root', result['page_title']) 20 | 21 | def test_folder_view(self): 22 | request = DummyRequest() 23 | context = DummyResource() 24 | inst = self._makeOne(context, request) 25 | result = inst.folder() 26 | self.assertIn('Folder', result['page_title']) 27 | 28 | def test_document_view(self): 29 | request = DummyRequest() 30 | context = DummyResource() 31 | inst = self._makeOne(context, request) 32 | result = inst.document() 33 | self.assertIn('Document', result['page_title']) 34 | 35 | 36 | class TutorialFunctionalTests(unittest.TestCase): 37 | def setUp(self): 38 | from tutorial import main 39 | 40 | app = main({}) 41 | from webtest import TestApp 42 | 43 | self.testapp = TestApp(app) 44 | 45 | def test_it(self): 46 | res = self.testapp.get('/', status=200) 47 | self.assertIn(b'Root', res.body) 48 | res = self.testapp.get('/folder1', status=200) 49 | self.assertIn(b'Folder', res.body) 50 | res = self.testapp.get('/doc1', status=200) 51 | self.assertIn(b'Document', res.body) 52 | res = self.testapp.get('/doc2', status=200) 53 | self.assertIn(b'Document', res.body) 54 | res = self.testapp.get('/folder1/doc1', status=200) 55 | self.assertIn(b'Document', res.body) -------------------------------------------------------------------------------- /docs/traversal_tutorial/addcontent/tutorial/views.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | 3 | from pyramid.httpexceptions import HTTPFound 4 | from pyramid.location import lineage 5 | from pyramid.view import view_config 6 | 7 | from .resources import ( 8 | Root, 9 | Folder, 10 | Document 11 | ) 12 | 13 | 14 | class TutorialViews(object): 15 | def __init__(self, context, request): 16 | self.context = context 17 | self.request = request 18 | self.parents = reversed(list(lineage(context))) 19 | 20 | @view_config(renderer='templates/root.jinja2', 21 | context=Root) 22 | def root(self): 23 | page_title = 'Quick Tutorial: Root' 24 | return dict(page_title=page_title) 25 | 26 | @view_config(renderer='templates/folder.jinja2', 27 | context=Folder) 28 | def folder(self): 29 | page_title = 'Quick Tutorial: Folder' 30 | return dict(page_title=page_title) 31 | 32 | @view_config(name='add_folder', context=Folder) 33 | def add_folder(self): 34 | # Make a new Folder 35 | title = self.request.POST['folder_title'] 36 | name = str(randint(0, 999999)) 37 | new_folder = Folder(name, self.context, title) 38 | self.context[name] = new_folder 39 | 40 | # Redirect to the new folder 41 | url = self.request.resource_url(new_folder) 42 | return HTTPFound(location=url) 43 | 44 | @view_config(name='add_document', context=Folder) 45 | def add_document(self): 46 | # Make a new Document 47 | title = self.request.POST['document_title'] 48 | name = str(randint(0, 999999)) 49 | new_document = Document(name, self.context, title) 50 | self.context[name] = new_document 51 | 52 | # Redirect to the new document 53 | url = self.request.resource_url(new_document) 54 | return HTTPFound(location=url) 55 | 56 | @view_config(renderer='templates/document.jinja2', 57 | context=Document) 58 | def document(self): 59 | page_title = 'Quick Tutorial: Document' 60 | return dict(page_title=page_title) 61 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/hierarchy.rst: -------------------------------------------------------------------------------- 1 | ======================== 2 | 3: Traversal Hierarchies 3 | ======================== 4 | 5 | Objects with subobjects and views, all via URLs. 6 | 7 | Background 8 | ========== 9 | 10 | In :doc:`siteroot` we took the simplest possible step: a root object with 11 | little need for the stitching together of a tree known as traversal. 12 | 13 | In this step we remain simple, but make a basic hierarchy:: 14 | 15 | / 16 | doc1 17 | doc2 18 | folder1/ 19 | doc1 20 | 21 | 22 | Objectives 23 | ========== 24 | 25 | - Use a multi-level nested hierarchy of Python objects. 26 | 27 | - Show how ``__name__`` and ``__parent__`` glue the hierarchy together. 28 | 29 | - Use objects which last between requests. 30 | 31 | Steps 32 | ===== 33 | 34 | #. We are going to use the previous step as our starting point: 35 | 36 | .. code-block:: bash 37 | 38 | $ cd ..; cp -r siteroot hierarchy; cd hierarchy 39 | $ $VENV/bin/python setup.py develop 40 | 41 | #. Provide a richer set of objects in ``hierarchy/tutorial/resources.py``: 42 | 43 | .. literalinclude:: hierarchy/tutorial/resources.py 44 | :linenos: 45 | 46 | #. Have ``hierarchy/tutorial/views.py`` show information about the resource 47 | tree: 48 | 49 | .. literalinclude:: hierarchy/tutorial/views.py 50 | :linenos: 51 | :emphasize-lines: 1,9 52 | 53 | #. Update the ``hierarchy/tutorial/templates/home.jinja2`` view template: 54 | 55 | .. literalinclude:: hierarchy/tutorial/templates/home.jinja2 56 | :language: jinja 57 | :linenos: 58 | :emphasize-lines: 4-12 59 | 60 | #. The ``hierarchy/tutorial/templates/breadcrumbs.jinja2`` template now has a 61 | hierarchy to show: 62 | 63 | .. literalinclude:: hierarchy/tutorial/templates/breadcrumbs.jinja2 64 | :language: jinja 65 | :linenos: 66 | 67 | #. Update the tests in ``hierarchy/tutorial/tests.py``: 68 | 69 | .. literalinclude:: hierarchy/tutorial/tests.py 70 | :linenos: 71 | :emphasize-lines: 8,13,26-28 72 | 73 | #. Now run the tests: 74 | 75 | .. code-block:: bash 76 | 77 | 78 | $ $VENV/bin/nosetests tutorial 79 | .. 80 | ---------------------------------------------------------------------- 81 | Ran 2 tests in 0.141s 82 | 83 | OK 84 | 85 | #. Run your Pyramid application with: 86 | 87 | .. code-block:: bash 88 | 89 | $ $VENV/bin/pserve development.ini --reload 90 | 91 | #. Open http://localhost:6543/ in your browser. 92 | 93 | Analysis 94 | ======== 95 | 96 | In this example we have to manage our tree by assigning ``__name__`` as an 97 | identifier on each child, and ``__parent__`` as a reference to the parent. The 98 | template used now shows different information based on the object URL to which 99 | you traversed. 100 | 101 | We also show that ``@view_config`` can set a "default" view on a context by 102 | omitting the ``@name`` attribute. Thus, if you visit 103 | ``http://localhost:6543/folder1/`` without providing anything after, the 104 | configured default view is used. 105 | 106 | Extra Credit 107 | ============ 108 | 109 | #. In ``resources.py``, we moved the instantiation of ``root`` out to global 110 | scope. Why? 111 | 112 | #. If you go to a resource that doesn't exist, will Pyramid handle it 113 | gracefully? 114 | 115 | #. If you ask for a default view on a resource and none is configured, will 116 | Pyramid handle it gracefully? 117 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/hierarchy/development.ini: -------------------------------------------------------------------------------- 1 | [app:main] 2 | use = egg:tutorial 3 | pyramid.reload_templates = true 4 | 5 | [server:main] 6 | use = egg:pyramid#wsgiref 7 | host = 0.0.0.0 8 | port = 6543 9 | 10 | # Begin logging configuration 11 | 12 | [loggers] 13 | keys = root, tutorial 14 | 15 | [logger_tutorial] 16 | level = DEBUG 17 | handlers = 18 | qualname = tutorial 19 | 20 | [handlers] 21 | keys = console 22 | 23 | [formatters] 24 | keys = generic 25 | 26 | [logger_root] 27 | level = INFO 28 | handlers = console 29 | 30 | [handler_console] 31 | class = StreamHandler 32 | args = (sys.stderr,) 33 | level = NOTSET 34 | formatter = generic 35 | 36 | [formatter_generic] 37 | format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s 38 | 39 | # End logging configuration 40 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/hierarchy/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | requires = [ 4 | 'pyramid', 5 | 'pyramid_jinja2', 6 | 'pyramid_debugtoolbar' 7 | ] 8 | 9 | setup(name='tutorial', 10 | install_requires=requires, 11 | entry_points="""\ 12 | [paste.app_factory] 13 | main = tutorial:main 14 | """, 15 | ) 16 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/hierarchy/tutorial/__init__.py: -------------------------------------------------------------------------------- 1 | from pyramid.config import Configurator 2 | 3 | from .resources import bootstrap 4 | 5 | 6 | def main(global_config, **settings): 7 | config = Configurator(settings=settings, 8 | root_factory=bootstrap) 9 | config.include('pyramid_jinja2') 10 | config.scan('.views') 11 | return config.make_wsgi_app() 12 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/hierarchy/tutorial/resources.py: -------------------------------------------------------------------------------- 1 | class Folder(dict): 2 | def __init__(self, name, parent, title): 3 | self.__name__ = name 4 | self.__parent__ = parent 5 | self.title = title 6 | 7 | 8 | class Root(Folder): 9 | pass 10 | 11 | 12 | class Document(object): 13 | def __init__(self, name, parent, title): 14 | self.__name__ = name 15 | self.__parent__ = parent 16 | self.title = title 17 | 18 | # Done outside bootstrap to persist from request to request 19 | root = Root('', None, 'My Site') 20 | 21 | 22 | def bootstrap(request): 23 | if not root.values(): 24 | # No values yet, let's make: 25 | # / 26 | # doc1 27 | # doc2 28 | # folder1/ 29 | # doc1 30 | doc1 = Document('doc1', root, 'Document 01') 31 | root['doc1'] = doc1 32 | doc2 = Document('doc2', root, 'Document 02') 33 | root['doc2'] = doc2 34 | folder1 = Folder('folder1', root, 'Folder 01') 35 | root['folder1'] = folder1 36 | 37 | # Only has to be unique in folder 38 | doc11 = Document('doc1', folder1, 'Document 01') 39 | folder1['doc1'] = doc11 40 | 41 | return root -------------------------------------------------------------------------------- /docs/traversal_tutorial/hierarchy/tutorial/templates/breadcrumbs.jinja2: -------------------------------------------------------------------------------- 1 | {% for p in view.parents %} 2 | 3 | {{ p.title }} 4 | >> 5 | {% endfor %} 6 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/hierarchy/tutorial/templates/header.jinja2: -------------------------------------------------------------------------------- 1 | Tutorial -------------------------------------------------------------------------------- /docs/traversal_tutorial/hierarchy/tutorial/templates/hello.jinja2: -------------------------------------------------------------------------------- 1 | {% extends "templates/layout.jinja2" %} 2 | {% block content %} 3 | 4 |

Welcome to {{ context.title }}. Visit 5 | home

6 | 7 | 8 | {% endblock content %} 9 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/hierarchy/tutorial/templates/home.jinja2: -------------------------------------------------------------------------------- 1 | {% extends "templates/layout.jinja2" %} 2 | {% block content %} 3 | 4 | 11 | 12 |

{{ context.title }}

13 | 14 |

Welcome to {{ context.title }}. Visit 15 | hello 16 |

17 | 18 | {% endblock content %} 19 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/hierarchy/tutorial/templates/layout.jinja2: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ page_title }} 5 | 7 | 8 | 9 | 10 | 15 | 16 |
17 | 18 |
19 | {% include "templates/breadcrumbs.jinja2" %} 20 |
21 | 22 |

{{ page_title }}

23 | {% block content %} 24 | {% endblock content %} 25 | 26 |
27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/hierarchy/tutorial/tests.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from pyramid.testing import DummyRequest 4 | from pyramid.testing import DummyResource 5 | 6 | 7 | class TutorialViewsUnitTests(unittest.TestCase): 8 | def test_home_view(self): 9 | from .views import TutorialViews 10 | 11 | request = DummyRequest() 12 | title = 'Dummy Context' 13 | context = DummyResource(title=title, __name__='dummy') 14 | inst = TutorialViews(context, request) 15 | result = inst.home() 16 | self.assertIn('Home', result['page_title']) 17 | 18 | 19 | class TutorialFunctionalTests(unittest.TestCase): 20 | def setUp(self): 21 | from tutorial import main 22 | app = main({}) 23 | from webtest import TestApp 24 | self.testapp = TestApp(app) 25 | 26 | def test_home(self): 27 | result = self.testapp.get('/', status=200) 28 | self.assertIn(b'Site Folder', result.body) 29 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/hierarchy/tutorial/views.py: -------------------------------------------------------------------------------- 1 | from pyramid.location import lineage 2 | from pyramid.view import view_config 3 | 4 | 5 | class TutorialViews: 6 | def __init__(self, context, request): 7 | self.context = context 8 | self.request = request 9 | self.parents = reversed(list(lineage(context))) 10 | 11 | @view_config(renderer='templates/home.jinja2') 12 | def home(self): 13 | page_title = 'Quick Tutorial: Home' 14 | return dict(page_title=page_title) 15 | 16 | @view_config(name='hello', renderer='templates/hello.jinja2') 17 | def hello(self): 18 | page_title = 'Quick Tutorial: Hello' 19 | return dict(page_title=page_title) 20 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/index.rst: -------------------------------------------------------------------------------- 1 | .. _traversal_tutorial: 2 | 3 | ================== 4 | Traversal Tutorial 5 | ================== 6 | 7 | Traversal is an alternate, object-oriented approach to mapping incoming web 8 | requests to objects and views in Pyramid. 9 | 10 | .. note:: 11 | 12 | This tutorial presumes you have gone through the Pyramid 13 | :ref:`quick_tutorial`. 14 | 15 | .. toctree:: 16 | 17 | requirements 18 | layout 19 | siteroot 20 | hierarchy 21 | typeviews 22 | addcontent 23 | zodb 24 | sqlroot 25 | sqladdcontent 26 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/layout/development.ini: -------------------------------------------------------------------------------- 1 | [app:main] 2 | use = egg:tutorial 3 | pyramid.reload_templates = true 4 | pyramid.includes = 5 | pyramid_debugtoolbar 6 | 7 | [server:main] 8 | use = egg:pyramid#wsgiref 9 | host = 0.0.0.0 10 | port = 6543 11 | 12 | # Begin logging configuration 13 | 14 | [loggers] 15 | keys = root, tutorial 16 | 17 | [logger_tutorial] 18 | level = DEBUG 19 | handlers = 20 | qualname = tutorial 21 | 22 | [handlers] 23 | keys = console 24 | 25 | [formatters] 26 | keys = generic 27 | 28 | [logger_root] 29 | level = INFO 30 | handlers = console 31 | 32 | [handler_console] 33 | class = StreamHandler 34 | args = (sys.stderr,) 35 | level = NOTSET 36 | formatter = generic 37 | 38 | [formatter_generic] 39 | format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s 40 | 41 | # End logging configuration 42 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/layout/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | requires = [ 4 | 'pyramid', 5 | 'pyramid_jinja2', 6 | 'pyramid_debugtoolbar' 7 | ] 8 | 9 | setup(name='tutorial', 10 | install_requires=requires, 11 | entry_points="""\ 12 | [paste.app_factory] 13 | main = tutorial:main 14 | """, 15 | ) 16 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/layout/tutorial/__init__.py: -------------------------------------------------------------------------------- 1 | from pyramid.config import Configurator 2 | 3 | 4 | def main(global_config, **settings): 5 | config = Configurator(settings=settings) 6 | config.include('pyramid_jinja2') 7 | config.scan('.views') 8 | return config.make_wsgi_app() 9 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/layout/tutorial/templates/breadcrumbs.jinja2: -------------------------------------------------------------------------------- 1 | 2 | Home >> 3 | 4 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/layout/tutorial/templates/header.jinja2: -------------------------------------------------------------------------------- 1 | Tutorial -------------------------------------------------------------------------------- /docs/traversal_tutorial/layout/tutorial/templates/layout.jinja2: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ page_title }} 5 | 7 | 8 | 9 | 10 | 15 | 16 |
17 | 18 |
19 | {% include "templates/breadcrumbs.jinja2" %} 20 |
21 | 22 |

{{ page_title }}

23 | {% block content %} 24 | {% endblock content %} 25 | 26 |
27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/layout/tutorial/templates/site.jinja2: -------------------------------------------------------------------------------- 1 | {% extends "templates/layout.jinja2" %} 2 | {% block content %} 3 | 4 |

Welcome to the site.

5 | 6 | {% endblock content %} 7 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/layout/tutorial/tests.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from pyramid.testing import DummyRequest 4 | 5 | 6 | class TutorialViewsUnitTests(unittest.TestCase): 7 | def _makeOne(self, request): 8 | from .views import TutorialViews 9 | inst = TutorialViews(request) 10 | return inst 11 | 12 | def test_site_view(self): 13 | request = DummyRequest() 14 | inst = self._makeOne(request) 15 | result = inst.site() 16 | self.assertIn('Site View', result['page_title']) 17 | 18 | 19 | class TutorialFunctionalTests(unittest.TestCase): 20 | def setUp(self): 21 | from tutorial import main 22 | app = main({}) 23 | from webtest import TestApp 24 | self.testapp = TestApp(app) 25 | 26 | def test_it(self): 27 | result = self.testapp.get('/hello', status=200) 28 | self.assertIn(b'Site View', result.body) 29 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/layout/tutorial/views.py: -------------------------------------------------------------------------------- 1 | from pyramid.view import view_config 2 | 3 | 4 | class TutorialViews(object): 5 | def __init__(self, request): 6 | self.request = request 7 | 8 | @view_config(name='hello', renderer='templates/site.jinja2') 9 | def site(self): 10 | page_title = 'Quick Tutorial: Site View' 11 | return dict(page_title=page_title) 12 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/requirements.rst: -------------------------------------------------------------------------------- 1 | .. _qtrav_requirements: 2 | 3 | ============ 4 | Requirements 5 | ============ 6 | 7 | Let's get our tutorial environment setup. Most of the setup work is in standard 8 | Python development practices: install Python, make an isolated environment, and 9 | setup packaging tools. 10 | 11 | The :ref:`Quick Tutorial of Pyramid ` has an excellent 12 | section covering the :ref:`installation and setup requirements 13 | `. Follow those instructions to get Python and 14 | Pyramid setup. 15 | 16 | For your workspace name, use ``quick_traversal`` in place of 17 | ``quick_tutorial``. -------------------------------------------------------------------------------- /docs/traversal_tutorial/siteroot/development.ini: -------------------------------------------------------------------------------- 1 | [app:main] 2 | use = egg:tutorial 3 | pyramid.reload_templates = true 4 | pyramid.includes = 5 | pyramid_debugtoolbar 6 | 7 | [server:main] 8 | use = egg:pyramid#wsgiref 9 | host = 0.0.0.0 10 | port = 6543 11 | 12 | # Begin logging configuration 13 | 14 | [loggers] 15 | keys = root, tutorial 16 | 17 | [logger_tutorial] 18 | level = DEBUG 19 | handlers = 20 | qualname = tutorial 21 | 22 | [handlers] 23 | keys = console 24 | 25 | [formatters] 26 | keys = generic 27 | 28 | [logger_root] 29 | level = INFO 30 | handlers = console 31 | 32 | [handler_console] 33 | class = StreamHandler 34 | args = (sys.stderr,) 35 | level = NOTSET 36 | formatter = generic 37 | 38 | [formatter_generic] 39 | format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s 40 | 41 | # End logging configuration 42 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/siteroot/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | requires = [ 4 | 'pyramid', 5 | 'pyramid_jinja2', 6 | 'pyramid_debugtoolbar' 7 | ] 8 | 9 | setup(name='tutorial', 10 | install_requires=requires, 11 | entry_points="""\ 12 | [paste.app_factory] 13 | main = tutorial:main 14 | """, 15 | ) 16 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/siteroot/tutorial/__init__.py: -------------------------------------------------------------------------------- 1 | from pyramid.config import Configurator 2 | 3 | from .resources import bootstrap 4 | 5 | 6 | def main(global_config, **settings): 7 | config = Configurator(settings=settings, 8 | root_factory=bootstrap) 9 | config.include('pyramid_jinja2') 10 | config.scan('.views') 11 | return config.make_wsgi_app() 12 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/siteroot/tutorial/resources.py: -------------------------------------------------------------------------------- 1 | class Root(dict): 2 | __name__ = '' 3 | __parent__ = None 4 | def __init__(self, title): 5 | self.title = title 6 | 7 | 8 | def bootstrap(request): 9 | root = Root('My Site') 10 | 11 | return root 12 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/siteroot/tutorial/templates/breadcrumbs.jinja2: -------------------------------------------------------------------------------- 1 | 2 | Home >> 3 | 4 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/siteroot/tutorial/templates/header.jinja2: -------------------------------------------------------------------------------- 1 | Tutorial -------------------------------------------------------------------------------- /docs/traversal_tutorial/siteroot/tutorial/templates/hello.jinja2: -------------------------------------------------------------------------------- 1 | {% extends "templates/layout.jinja2" %} 2 | {% block content %} 3 | 4 |

Welcome to {{ context.title }}. Visit 5 | home

6 | 7 | 8 | {% endblock content %} 9 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/siteroot/tutorial/templates/home.jinja2: -------------------------------------------------------------------------------- 1 | {% extends "templates/layout.jinja2" %} 2 | {% block content %} 3 | 4 |

Welcome to {{ context.title }}. Visit 5 | hello 6 |

7 | 8 | {% endblock content %} 9 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/siteroot/tutorial/templates/layout.jinja2: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ page_title }} 5 | 7 | 8 | 9 | 10 | 15 | 16 |
17 | 18 |
19 | {% include "templates/breadcrumbs.jinja2" %} 20 |
21 | 22 |

{{ page_title }}

23 | {% block content %} 24 | {% endblock content %} 25 | 26 |
27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/siteroot/tutorial/tests.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from pyramid.testing import DummyRequest 4 | from pyramid.testing import DummyResource 5 | 6 | 7 | class TutorialViewsUnitTests(unittest.TestCase): 8 | def test_home(self): 9 | from .views import TutorialViews 10 | 11 | request = DummyRequest() 12 | title = 'Dummy Context' 13 | context = DummyResource(title=title) 14 | inst = TutorialViews(context, request) 15 | result = inst.home() 16 | self.assertIn('Home', result['page_title']) 17 | 18 | 19 | class TutorialFunctionalTests(unittest.TestCase): 20 | def setUp(self): 21 | from tutorial import main 22 | app = main({}) 23 | from webtest import TestApp 24 | self.testapp = TestApp(app) 25 | 26 | def test_hello(self): 27 | result = self.testapp.get('/hello', status=200) 28 | self.assertIn(b'Quick Tutorial: Hello', result.body) 29 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/siteroot/tutorial/views.py: -------------------------------------------------------------------------------- 1 | from pyramid.view import view_config 2 | 3 | 4 | class TutorialViews: 5 | def __init__(self, context, request): 6 | self.context = context 7 | self.request = request 8 | 9 | @view_config(renderer='templates/home.jinja2') 10 | def home(self): 11 | page_title = 'Quick Tutorial: Home' 12 | return dict(page_title=page_title) 13 | 14 | @view_config(name='hello', renderer='templates/hello.jinja2') 15 | def hello(self): 16 | page_title = 'Quick Tutorial: Hello' 17 | return dict(page_title=page_title) 18 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/sqladdcontent/development.ini: -------------------------------------------------------------------------------- 1 | [app:main] 2 | use = egg:tutorial 3 | pyramid.reload_templates = true 4 | pyramid.includes = 5 | pyramid_debugtoolbar 6 | pyramid_tm 7 | sqlalchemy.url = sqlite:///%(here)s/sqltutorial.sqlite 8 | 9 | [server:main] 10 | use = egg:pyramid#wsgiref 11 | host = 0.0.0.0 12 | port = 6543 13 | 14 | # Begin logging configuration 15 | 16 | [loggers] 17 | keys = root, tutorial, sqlalchemy 18 | 19 | [logger_tutorial] 20 | level = DEBUG 21 | handlers = 22 | qualname = tutorial 23 | 24 | [logger_sqlalchemy] 25 | level = INFO 26 | handlers = 27 | qualname = sqlalchemy.engine 28 | 29 | [handlers] 30 | keys = console 31 | 32 | [formatters] 33 | keys = generic 34 | 35 | [logger_root] 36 | level = INFO 37 | handlers = console 38 | 39 | [handler_console] 40 | class = StreamHandler 41 | args = (sys.stderr,) 42 | level = NOTSET 43 | formatter = generic 44 | 45 | [formatter_generic] 46 | format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s 47 | 48 | # End logging configuration 49 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/sqladdcontent/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | requires = [ 4 | 'pyramid', 5 | 'pyramid_jinja2', 6 | 'pyramid_tm', 7 | 'sqlalchemy', 8 | 'pyramid_tm', 9 | 'zope.sqlalchemy', 10 | 'pyramid_debugtoolbar' 11 | ] 12 | 13 | setup(name='tutorial', 14 | install_requires=requires, 15 | entry_points="""\ 16 | [paste.app_factory] 17 | main = tutorial:main 18 | [console_scripts] 19 | initialize_tutorial_db = tutorial.initialize_db:main 20 | """, 21 | ) 22 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/sqladdcontent/tutorial/__init__.py: -------------------------------------------------------------------------------- 1 | from pyramid.config import Configurator 2 | 3 | from sqlalchemy import engine_from_config 4 | 5 | from .sqltraversal import ( 6 | DBSession, 7 | Base, 8 | root_factory, 9 | ) 10 | 11 | 12 | def main(global_config, **settings): 13 | engine = engine_from_config(settings, 'sqlalchemy.') 14 | DBSession.configure(bind=engine) 15 | Base.metadata.bind = engine 16 | 17 | config = Configurator(settings=settings, 18 | root_factory=root_factory) 19 | config.include('pyramid_jinja2') 20 | config.scan('.views') 21 | return config.make_wsgi_app() 22 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/sqladdcontent/tutorial/initialize_db.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import transaction 4 | 5 | from sqlalchemy import engine_from_config 6 | 7 | from pyramid.paster import ( 8 | get_appsettings, 9 | setup_logging, 10 | ) 11 | 12 | from .sqltraversal import ( 13 | DBSession, 14 | Node, 15 | Base, 16 | ) 17 | 18 | from .models import ( 19 | Document, 20 | Folder, 21 | ) 22 | 23 | 24 | def usage(argv): 25 | cmd = os.path.basename(argv[0]) 26 | print('usage: %s \n' 27 | '(example: "%s development.ini")' % (cmd, cmd)) 28 | sys.exit(1) 29 | 30 | 31 | def main(argv=sys.argv): 32 | if len(argv) != 2: 33 | usage(argv) 34 | config_uri = argv[1] 35 | setup_logging(config_uri) 36 | settings = get_appsettings(config_uri) 37 | engine = engine_from_config(settings, 'sqlalchemy.') 38 | DBSession.configure(bind=engine) 39 | Base.metadata.create_all(engine) 40 | 41 | with transaction.manager: 42 | root = Folder(name='', title='My SQLTraversal Root') 43 | DBSession.add(root) 44 | f1 = root['f1'] = Folder(title='Folder 1') 45 | f1['da'] = Document(title='Document A') 46 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/sqladdcontent/tutorial/models.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import ( 2 | Column, 3 | Integer, 4 | Text, 5 | ForeignKey, 6 | ) 7 | 8 | from .sqltraversal import Node 9 | 10 | 11 | class Folder(Node): 12 | __tablename__ = 'folder' 13 | id = Column(Integer, ForeignKey('node.id'), primary_key=True) 14 | title = Column(Text) 15 | 16 | 17 | class Document(Node): 18 | __tablename__ = 'document' 19 | id = Column(Integer, ForeignKey('node.id'), primary_key=True) 20 | title = Column(Text) 21 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/sqladdcontent/tutorial/sqltraversal.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import ( 2 | Column, 3 | Integer, 4 | Unicode, 5 | ForeignKey, 6 | String 7 | ) 8 | from sqlalchemy.ext.declarative import declarative_base 9 | from sqlalchemy.orm import ( 10 | scoped_session, 11 | sessionmaker, 12 | relationship, 13 | backref 14 | ) 15 | from sqlalchemy.orm.exc import NoResultFound 16 | from sqlalchemy.util import classproperty 17 | from zope.sqlalchemy import ZopeTransactionExtension 18 | 19 | DBSession = scoped_session( 20 | sessionmaker(extension=ZopeTransactionExtension())) 21 | Base = declarative_base() 22 | 23 | 24 | def u(s): 25 | # Backwards compatibility for Python 3 not having unicode() 26 | try: 27 | return unicode(s) 28 | except NameError: 29 | return str(s) 30 | 31 | 32 | def root_factory(request): 33 | return DBSession.query(Node).filter_by(parent_id=None).one() 34 | 35 | 36 | class Node(Base): 37 | __tablename__ = 'node' 38 | id = Column(Integer, primary_key=True) 39 | name = Column(Unicode(50), nullable=False) 40 | parent_id = Column(Integer, ForeignKey('node.id')) 41 | children = relationship("Node", 42 | backref=backref('parent', remote_side=[id]) 43 | ) 44 | type = Column(String(50)) 45 | 46 | @classproperty 47 | def __mapper_args__(cls): 48 | return dict( 49 | polymorphic_on='type', 50 | polymorphic_identity=cls.__name__.lower(), 51 | with_polymorphic='*', 52 | ) 53 | 54 | def __setitem__(self, key, node): 55 | node.name = u(key) 56 | if self.id is None: 57 | DBSession.flush() 58 | node.parent_id = self.id 59 | DBSession.add(node) 60 | DBSession.flush() 61 | 62 | def __getitem__(self, key): 63 | try: 64 | return DBSession.query(Node).filter_by( 65 | name=key, parent=self).one() 66 | except NoResultFound: 67 | raise KeyError(key) 68 | 69 | def values(self): 70 | return DBSession.query(Node).filter_by(parent=self) 71 | 72 | @property 73 | def __name__(self): 74 | return self.name 75 | 76 | @property 77 | def __parent__(self): 78 | return self.parent 79 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/sqladdcontent/tutorial/templates/addform.jinja2: -------------------------------------------------------------------------------- 1 |

2 |

5 |
6 | 8 |
9 | 10 |
11 |

12 |

13 |

16 |
17 | 19 |
20 | 21 |
22 |

-------------------------------------------------------------------------------- /docs/traversal_tutorial/sqladdcontent/tutorial/templates/breadcrumbs.jinja2: -------------------------------------------------------------------------------- 1 | {% for p in view.parents %} 2 | 3 | {{ p.title }} >> 4 | 5 | {% endfor %} 6 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/sqladdcontent/tutorial/templates/contents.jinja2: -------------------------------------------------------------------------------- 1 |

Contents

2 |
    3 | {% for child in context.values() %} 4 |
  • 5 | {{ child.title }} 6 |
  • 7 | {% endfor %} 8 |
9 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/sqladdcontent/tutorial/templates/document.jinja2: -------------------------------------------------------------------------------- 1 | {% extends "templates/layout.jinja2" %} 2 | {% block content %} 3 | 4 |

{{ context.title }}

5 |

A document might have some body text.

6 | 7 | {% endblock content %} 8 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/sqladdcontent/tutorial/templates/folder.jinja2: -------------------------------------------------------------------------------- 1 | {% extends "templates/layout.jinja2" %} 2 | {% block content %} 3 | 4 |

{{ context.title }}

5 | {% include "templates/contents.jinja2" %} 6 | 7 | {% include "templates/addform.jinja2" %} 8 | 9 | {% endblock content %} 10 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/sqladdcontent/tutorial/templates/header.jinja2: -------------------------------------------------------------------------------- 1 | Tutorial -------------------------------------------------------------------------------- /docs/traversal_tutorial/sqladdcontent/tutorial/templates/layout.jinja2: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ page_title }} 5 | 7 | 8 | 9 | 10 | 15 | 16 |
17 | 18 |
19 | {% include "templates/breadcrumbs.jinja2" %} 20 |
21 | 22 |

{{ page_title }}

23 | {% block content %} 24 | {% endblock content %} 25 | 26 |
27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/sqladdcontent/tutorial/templates/root.jinja2: -------------------------------------------------------------------------------- 1 | {% extends "templates/layout.jinja2" %} 2 | {% block content %} 3 | 4 |

{{ context.title }}

5 |

The root might have some other text.

6 | {% include "templates/contents.jinja2" %} 7 | 8 | {% include "templates/addform.jinja2" %} 9 | 10 | {% endblock content %} 11 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/sqladdcontent/tutorial/views.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | 3 | from pyramid.httpexceptions import HTTPFound 4 | from pyramid.location import lineage 5 | from pyramid.view import view_config 6 | 7 | from .models import ( 8 | Folder, 9 | Document 10 | ) 11 | 12 | 13 | class TutorialViews(object): 14 | def __init__(self, context, request): 15 | self.context = context 16 | self.request = request 17 | self.parents = reversed(list(lineage(context))) 18 | 19 | @view_config(renderer='templates/root.jinja2', 20 | context=Folder, custom_predicates=[lambda c, r: c is r.root]) 21 | def root(self): 22 | page_title = 'Quick Tutorial: Root' 23 | return dict(page_title=page_title) 24 | 25 | @view_config(renderer='templates/folder.jinja2', 26 | context=Folder) 27 | def folder(self): 28 | page_title = 'Quick Tutorial: Folder' 29 | return dict(page_title=page_title) 30 | 31 | @view_config(name='add_folder', context=Folder) 32 | def add_folder(self): 33 | # Make a new Folder 34 | title = self.request.POST['folder_title'] 35 | name = str(randint(0, 999999)) 36 | new_folder = self.context[name] = Folder(title=title) 37 | 38 | # Redirect to the new folder 39 | url = self.request.resource_url(new_folder) 40 | return HTTPFound(location=url) 41 | 42 | @view_config(name='add_document', context=Folder) 43 | def add_document(self): 44 | # Make a new Document 45 | title = self.request.POST['document_title'] 46 | name = str(randint(0, 999999)) 47 | new_document = self.context[name] = Document(title=title) 48 | 49 | # Redirect to the new document 50 | url = self.request.resource_url(new_document) 51 | return HTTPFound(location=url) 52 | 53 | @view_config(renderer='templates/document.jinja2', 54 | context=Document) 55 | def document(self): 56 | page_title = 'Quick Tutorial: Document' 57 | return dict(page_title=page_title) 58 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/sqlauthentication/development.ini: -------------------------------------------------------------------------------- 1 | [app:main] 2 | use = egg:tutorial 3 | pyramid.reload_templates = true 4 | pyramid.includes = 5 | pyramid_debugtoolbar 6 | pyramid_tm 7 | sqlalchemy.url = sqlite:///%(here)s/sqltutorial.sqlite 8 | tutorial.secret = 98zd 9 | 10 | [server:main] 11 | use = egg:pyramid#wsgiref 12 | host = 0.0.0.0 13 | port = 6543 14 | 15 | # Begin logging configuration 16 | 17 | [loggers] 18 | keys = root, tutorial, sqlalchemy 19 | 20 | [logger_tutorial] 21 | level = DEBUG 22 | handlers = 23 | qualname = tutorial 24 | 25 | [logger_sqlalchemy] 26 | level = INFO 27 | handlers = 28 | qualname = sqlalchemy.engine 29 | 30 | [handlers] 31 | keys = console 32 | 33 | [formatters] 34 | keys = generic 35 | 36 | [logger_root] 37 | level = INFO 38 | handlers = console 39 | 40 | [handler_console] 41 | class = StreamHandler 42 | args = (sys.stderr,) 43 | level = NOTSET 44 | formatter = generic 45 | 46 | [formatter_generic] 47 | format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s 48 | 49 | # End logging configuration 50 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/sqlauthentication/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | requires = [ 4 | 'pyramid', 5 | 'pyramid_jinja2', 6 | 'pyramid_tm', 7 | 'sqlalchemy', 8 | 'pyramid_tm', 9 | 'zope.sqlalchemy', 10 | 'pyramid_debugtoolbar' 11 | ] 12 | 13 | setup(name='tutorial', 14 | install_requires=requires, 15 | entry_points="""\ 16 | [paste.app_factory] 17 | main = tutorial:main 18 | [console_scripts] 19 | initialize_tutorial_db = tutorial.initialize_db:main 20 | """, 21 | ) 22 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/sqlauthentication/tutorial/__init__.py: -------------------------------------------------------------------------------- 1 | from pyramid.authentication import AuthTktAuthenticationPolicy 2 | from pyramid.authorization import ACLAuthorizationPolicy 3 | from pyramid.config import Configurator 4 | 5 | from sqlalchemy import engine_from_config 6 | 7 | from .models import ( 8 | DBSession, 9 | Base, 10 | root_factory 11 | ) 12 | from .security import groupfinder 13 | 14 | 15 | def main(global_config, **settings): 16 | engine = engine_from_config(settings, 'sqlalchemy.') 17 | DBSession.configure(bind=engine) 18 | Base.metadata.bind = engine 19 | 20 | config = Configurator(settings=settings, 21 | root_factory=root_factory) 22 | config.include('pyramid_jinja2') 23 | 24 | # Security policies 25 | authn_policy = AuthTktAuthenticationPolicy( 26 | settings['tutorial.secret'], callback=groupfinder, 27 | hashalg='sha512') 28 | authz_policy = ACLAuthorizationPolicy() 29 | config.set_authentication_policy(authn_policy) 30 | config.set_authorization_policy(authz_policy) 31 | 32 | config.scan('.views') 33 | return config.make_wsgi_app() 34 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/sqlauthentication/tutorial/initialize_db.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import transaction 4 | 5 | from sqlalchemy import engine_from_config 6 | 7 | from pyramid.paster import ( 8 | get_appsettings, 9 | setup_logging, 10 | ) 11 | 12 | from .models import ( 13 | DBSession, 14 | Document, 15 | Node, 16 | Folder, 17 | Root, 18 | Base, 19 | ) 20 | 21 | 22 | def usage(argv): 23 | cmd = os.path.basename(argv[0]) 24 | print('usage: %s \n' 25 | '(example: "%s development.ini")' % (cmd, cmd)) 26 | sys.exit(1) 27 | 28 | 29 | def main(argv=sys.argv): 30 | if len(argv) != 2: 31 | usage(argv) 32 | config_uri = argv[1] 33 | setup_logging(config_uri) 34 | settings = get_appsettings(config_uri) 35 | engine = engine_from_config(settings, 'sqlalchemy.') 36 | DBSession.configure(bind=engine) 37 | Base.metadata.create_all(engine) 38 | 39 | with transaction.manager: 40 | root = Root(name='', title='My SQLTraversal Root') 41 | DBSession.add(root) 42 | DBSession.flush() 43 | root = DBSession.query(Node).filter_by(name=u'').one() 44 | f1 = Folder(title='Folder 1') 45 | DBSession.add(f1) 46 | root['f1'] = f1 47 | da = Document(title='Document A') 48 | DBSession.add(da) 49 | f1['da'] = da -------------------------------------------------------------------------------- /docs/traversal_tutorial/sqlauthentication/tutorial/models.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import ( 2 | Column, 3 | Integer, 4 | Text, 5 | Unicode, 6 | ForeignKey, 7 | String 8 | ) 9 | 10 | from sqlalchemy.ext.declarative import declarative_base 11 | 12 | from sqlalchemy.orm import ( 13 | scoped_session, 14 | sessionmaker, 15 | relationship, 16 | backref 17 | ) 18 | 19 | from sqlalchemy.orm.exc import NoResultFound 20 | 21 | from zope.sqlalchemy import ZopeTransactionExtension 22 | 23 | DBSession = scoped_session( 24 | sessionmaker(extension=ZopeTransactionExtension())) 25 | Base = declarative_base() 26 | 27 | def u(s): 28 | # Backwards compatibility for Python 3 not having unicode() 29 | try: 30 | return unicode(s) 31 | except NameError: 32 | return str(s) 33 | 34 | class Node(Base): 35 | __tablename__ = 'node' 36 | id = Column(Integer, primary_key=True) 37 | name = Column(Unicode(50), nullable=False) 38 | parent_id = Column(Integer, ForeignKey('node.id')) 39 | children = relationship("Node", 40 | backref=backref('parent', remote_side=[id]) 41 | ) 42 | type = Column(String(50)) 43 | __mapper_args__ = dict( 44 | polymorphic_on=type, 45 | polymorphic_identity='node', 46 | with_polymorphic='*' 47 | ) 48 | 49 | 50 | def __setitem__(self, key, node): 51 | node.name = u(key) 52 | DBSession.add(node) 53 | DBSession.flush() 54 | node.parent_id = self.id 55 | 56 | def __getitem__(self, key): 57 | try: 58 | return DBSession.query(Node).filter_by( 59 | name=key, parent=self).one() 60 | except NoResultFound: 61 | raise KeyError(key) 62 | 63 | def values(self): 64 | return DBSession.query(Node).filter_by(parent=self) 65 | 66 | @property 67 | def __name__(self): 68 | return self.name 69 | 70 | @property 71 | def __parent__(self): 72 | return self.parent 73 | 74 | @property 75 | def is_empty(self): 76 | return self.values().count() == 0 77 | 78 | 79 | class Root(Node): 80 | __tablename__ = 'root' 81 | __mapper_args__ = dict( 82 | polymorphic_identity='root', 83 | with_polymorphic='*', 84 | ) 85 | id = Column(Integer, ForeignKey('node.id'), primary_key=True) 86 | title = Column(Text) 87 | 88 | 89 | class Folder(Node): 90 | __tablename__ = 'folder' 91 | __mapper_args__ = dict( 92 | polymorphic_identity='folder', 93 | with_polymorphic='*', 94 | ) 95 | id = Column(Integer, ForeignKey('node.id'), primary_key=True) 96 | title = Column(Text) 97 | 98 | 99 | class Document(Node): 100 | __tablename__ = 'document' 101 | id = Column(Integer, ForeignKey('node.id'), primary_key=True) 102 | __mapper_args__ = dict( 103 | polymorphic_identity='document', 104 | with_polymorphic='*', 105 | ) 106 | title = Column(Text) 107 | 108 | 109 | def root_factory(request): 110 | return DBSession.query(Root).one() 111 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/sqlauthentication/tutorial/security.py: -------------------------------------------------------------------------------- 1 | USERS = {'editor': 'editor', 2 | 'viewer': 'viewer'} 3 | GROUPS = {'editor': ['group:editors']} 4 | 5 | 6 | def groupfinder(userid, request): 7 | if userid in USERS: 8 | return GROUPS.get(userid, []) -------------------------------------------------------------------------------- /docs/traversal_tutorial/sqlauthentication/tutorial/templates/addform.jinja2: -------------------------------------------------------------------------------- 1 |

2 |

5 |
6 | 8 |
9 | 10 |
11 |

12 |

13 |

16 |
17 | 19 |
20 | 21 |
22 |

-------------------------------------------------------------------------------- /docs/traversal_tutorial/sqlauthentication/tutorial/templates/breadcrumbs.jinja2: -------------------------------------------------------------------------------- 1 | {% for p in view.parents %} 2 | 3 | {{ p.title }} 4 | >> 5 | {% endfor %} 6 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/sqlauthentication/tutorial/templates/contents.jinja2: -------------------------------------------------------------------------------- 1 |

Contents

2 |
    3 | {% for child in context.values() %} 4 |
  • 5 | {{ child.title }} 6 |
  • 7 | {% endfor %} 8 |
9 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/sqlauthentication/tutorial/templates/document.jinja2: -------------------------------------------------------------------------------- 1 | {% extends "templates/layout.jinja2" %} 2 | {% block content %} 3 | 4 |

{{ context.title }}

5 |

A document might have some body text.

6 | 7 | {% endblock content %} 8 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/sqlauthentication/tutorial/templates/folder.jinja2: -------------------------------------------------------------------------------- 1 | {% extends "templates/layout.jinja2" %} 2 | {% block content %} 3 | 4 |

{{ context.title }}

5 | {% include "templates/contents.jinja2" %} 6 | 7 | {% include "templates/addform.jinja2" %} 8 | 9 | {% endblock content %} 10 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/sqlauthentication/tutorial/templates/header.jinja2: -------------------------------------------------------------------------------- 1 |
2 | {% if view.logged_in %} 3 | {{ view.logged_in }} Logout 4 | {% else %} 5 | Login 6 | {% endif %} 7 |
8 | Tutorial -------------------------------------------------------------------------------- /docs/traversal_tutorial/sqlauthentication/tutorial/templates/layout.jinja2: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ page_title }} 5 | 7 | 8 | 9 | 10 | 15 | 16 |
17 | 18 |
19 | {% include "templates/breadcrumbs.jinja2" %} 20 |
21 | 22 |

{{ page_title }}

23 | {% block content %} 24 | {% endblock content %} 25 | 26 |
27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/sqlauthentication/tutorial/templates/login.jinja2: -------------------------------------------------------------------------------- 1 | {% extends "templates/layout.jinja2" %} 2 | {% block content %} 3 | 4 | {{ message }} 5 | 6 |
7 | 8 |
11 | 12 |
15 | 17 |
18 | {% endblock content %} 19 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/sqlauthentication/tutorial/templates/root.jinja2: -------------------------------------------------------------------------------- 1 | {% extends "templates/layout.jinja2" %} 2 | {% block content %} 3 | 4 |

{{ context.title }}

5 |

The root might have some other text.

6 | {% include "templates/contents.jinja2" %} 7 | 8 | {% include "templates/addform.jinja2" %} 9 | 10 | 11 | {% endblock content %} 12 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/sqlauthentication/tutorial/views.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | 3 | from pyramid.httpexceptions import HTTPFound 4 | from pyramid.location import lineage 5 | from pyramid.security import ( 6 | remember, 7 | forget, 8 | authenticated_userid 9 | ) 10 | from pyramid.view import view_config 11 | 12 | from .models import ( 13 | Root, 14 | Folder, 15 | Document 16 | ) 17 | from .security import USERS 18 | 19 | 20 | class TutorialViews(object): 21 | def __init__(self, context, request): 22 | self.context = context 23 | self.request = request 24 | self.parents = reversed(list(lineage(context))) 25 | self.logged_in = authenticated_userid(request) 26 | 27 | @view_config(renderer="templates/root.jinja2", 28 | context=Root) 29 | def root(self): 30 | page_title = 'Quick Tutorial: Root' 31 | return dict(page_title=page_title) 32 | 33 | @view_config(renderer="templates/folder.jinja2", 34 | context=Folder) 35 | def folder(self): 36 | page_title = 'Quick Tutorial: Folder' 37 | return dict(page_title=page_title) 38 | 39 | @view_config(name="add_folder", context=Root) 40 | @view_config(name="add_folder", context=Folder) 41 | def add_folder(self): 42 | # Make a new Folder 43 | title = self.request.POST['folder_title'] 44 | name = str(randint(0, 999999)) 45 | new_folder = Folder(title=title) 46 | self.context[name] = new_folder 47 | 48 | # Redirect to the new folder 49 | url = self.request.resource_url(new_folder) 50 | return HTTPFound(location=url) 51 | 52 | @view_config(name="add_document", context=Root) 53 | @view_config(name="add_document", context=Folder) 54 | def add_document(self): 55 | # Make a new Document 56 | title = self.request.POST['document_title'] 57 | name = str(randint(0, 999999)) 58 | new_document = Document(title=title) 59 | self.context[name] = new_document 60 | 61 | # Redirect to the new document 62 | url = self.request.resource_url(new_document) 63 | return HTTPFound(location=url) 64 | 65 | @view_config(renderer="templates/document.jinja2", 66 | context=Document) 67 | def document(self): 68 | page_title = 'Quick Tutorial: Document' 69 | return dict(page_title=page_title) 70 | 71 | @view_config(name='login', renderer='templates/login.jinja2') 72 | def login(self): 73 | request = self.request 74 | referrer = request.url 75 | message = '' 76 | login = '' 77 | password = '' 78 | if 'form.submitted' in request.params: 79 | login = request.params['login'] 80 | password = request.params['password'] 81 | if USERS.get(login) == password: 82 | headers = remember(request, login) 83 | return HTTPFound(location='/', 84 | headers=headers) 85 | message = 'Failed login' 86 | 87 | return dict( 88 | page_title='Login', 89 | message=message, 90 | url=request.application_url + '/login', 91 | login=login, 92 | password=password, 93 | ) 94 | 95 | @view_config(name='logout') 96 | def logout(self): 97 | request = self.request 98 | headers = forget(request) 99 | url = request.resource_url(request.root) 100 | return HTTPFound(location=url, 101 | headers=headers) -------------------------------------------------------------------------------- /docs/traversal_tutorial/sqlroot/development.ini: -------------------------------------------------------------------------------- 1 | [app:main] 2 | use = egg:tutorial 3 | pyramid.reload_templates = true 4 | pyramid.includes = 5 | pyramid_debugtoolbar 6 | pyramid_tm 7 | sqlalchemy.url = sqlite:///%(here)s/sqltutorial.sqlite 8 | 9 | [server:main] 10 | use = egg:pyramid#wsgiref 11 | host = 0.0.0.0 12 | port = 6543 13 | 14 | # Begin logging configuration 15 | 16 | [loggers] 17 | keys = root, tutorial, sqlalchemy 18 | 19 | [logger_tutorial] 20 | level = DEBUG 21 | handlers = 22 | qualname = tutorial 23 | 24 | [logger_sqlalchemy] 25 | level = INFO 26 | handlers = 27 | qualname = sqlalchemy.engine 28 | 29 | [handlers] 30 | keys = console 31 | 32 | [formatters] 33 | keys = generic 34 | 35 | [logger_root] 36 | level = INFO 37 | handlers = console 38 | 39 | [handler_console] 40 | class = StreamHandler 41 | args = (sys.stderr,) 42 | level = NOTSET 43 | formatter = generic 44 | 45 | [formatter_generic] 46 | format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s 47 | 48 | # End logging configuration 49 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/sqlroot/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | requires = [ 4 | 'pyramid', 5 | 'pyramid_jinja2', 6 | 'pyramid_tm', 7 | 'sqlalchemy', 8 | 'zope.sqlalchemy', 9 | 'pyramid_debugtoolbar' 10 | ] 11 | 12 | setup(name='tutorial', 13 | install_requires=requires, 14 | entry_points="""\ 15 | [paste.app_factory] 16 | main = tutorial:main 17 | [console_scripts] 18 | initialize_tutorial_db = tutorial.initialize_db:main 19 | """, 20 | ) 21 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/sqlroot/tutorial/__init__.py: -------------------------------------------------------------------------------- 1 | from pyramid.config import Configurator 2 | 3 | from sqlalchemy import engine_from_config 4 | 5 | from .models import ( 6 | DBSession, 7 | Base, 8 | root_factory 9 | ) 10 | 11 | 12 | def main(global_config, **settings): 13 | engine = engine_from_config(settings, 'sqlalchemy.') 14 | DBSession.configure(bind=engine) 15 | Base.metadata.bind = engine 16 | 17 | config = Configurator(settings=settings, 18 | root_factory=root_factory) 19 | config.include('pyramid_jinja2') 20 | config.scan('.views') 21 | return config.make_wsgi_app() 22 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/sqlroot/tutorial/initialize_db.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import transaction 4 | 5 | from sqlalchemy import engine_from_config 6 | 7 | from pyramid.paster import ( 8 | get_appsettings, 9 | setup_logging, 10 | ) 11 | 12 | from .models import ( 13 | DBSession, 14 | Root, 15 | Base, 16 | ) 17 | 18 | 19 | def usage(argv): 20 | cmd = os.path.basename(argv[0]) 21 | print('usage: %s \n' 22 | '(example: "%s development.ini")' % (cmd, cmd)) 23 | sys.exit(1) 24 | 25 | 26 | def main(argv=sys.argv): 27 | if len(argv) != 2: 28 | usage(argv) 29 | config_uri = argv[1] 30 | setup_logging(config_uri) 31 | settings = get_appsettings(config_uri) 32 | engine = engine_from_config(settings, 'sqlalchemy.') 33 | DBSession.configure(bind=engine) 34 | Base.metadata.create_all(engine) 35 | 36 | with transaction.manager: 37 | root = Root(title='My SQLTraversal Root') 38 | DBSession.add(root) -------------------------------------------------------------------------------- /docs/traversal_tutorial/sqlroot/tutorial/models.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import ( 2 | Column, 3 | Integer, 4 | Text, 5 | ) 6 | 7 | from sqlalchemy.ext.declarative import declarative_base 8 | 9 | from sqlalchemy.orm import ( 10 | scoped_session, 11 | sessionmaker, 12 | ) 13 | 14 | from zope.sqlalchemy import ZopeTransactionExtension 15 | 16 | DBSession = scoped_session( 17 | sessionmaker(extension=ZopeTransactionExtension())) 18 | Base = declarative_base() 19 | 20 | 21 | class Root(Base): 22 | __name__ = '' 23 | __parent__ = None 24 | __tablename__ = 'root' 25 | uid = Column(Integer, primary_key=True) 26 | title = Column(Text, unique=True) 27 | 28 | 29 | def root_factory(request): 30 | return DBSession.query(Root).one() -------------------------------------------------------------------------------- /docs/traversal_tutorial/sqlroot/tutorial/templates/breadcrumbs.jinja2: -------------------------------------------------------------------------------- 1 | 2 | Home >> 3 | 4 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/sqlroot/tutorial/templates/header.jinja2: -------------------------------------------------------------------------------- 1 | Tutorial -------------------------------------------------------------------------------- /docs/traversal_tutorial/sqlroot/tutorial/templates/hello.jinja2: -------------------------------------------------------------------------------- 1 | {% extends "templates/layout.jinja2" %} 2 | {% block content %} 3 | 4 |

Welcome to {{ context.title }}. Visit 5 | home

6 | 7 | 8 | {% endblock content %} 9 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/sqlroot/tutorial/templates/home.jinja2: -------------------------------------------------------------------------------- 1 | {% extends "templates/layout.jinja2" %} 2 | {% block content %} 3 | 4 |

Welcome to {{ context.title }}. Visit 5 | hello

6 | 7 | {% endblock content %} 8 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/sqlroot/tutorial/templates/layout.jinja2: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ page_title }} 5 | 7 | 8 | 9 | 10 | 15 | 16 |
17 | 18 |
19 | {% include "templates/breadcrumbs.jinja2" %} 20 |
21 | 22 |

{{ page_title }}

23 | {% block content %} 24 | {% endblock content %} 25 | 26 |
27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/sqlroot/tutorial/views.py: -------------------------------------------------------------------------------- 1 | from pyramid.view import view_config 2 | 3 | 4 | class TutorialViews: 5 | def __init__(self, context, request): 6 | self.context = context 7 | self.request = request 8 | 9 | @view_config(renderer='templates/home.jinja2') 10 | def home(self): 11 | page_title = 'Quick Tutorial: Home' 12 | return dict(page_title=page_title) 13 | 14 | @view_config(name='hello', renderer='templates/hello.jinja2') 15 | def hello(self): 16 | page_title = 'Quick Tutorial: Hello' 17 | return dict(page_title=page_title) 18 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/typeviews/development.ini: -------------------------------------------------------------------------------- 1 | [app:main] 2 | use = egg:tutorial 3 | pyramid.reload_templates = true 4 | pyramid.includes = 5 | pyramid_debugtoolbar 6 | 7 | [server:main] 8 | use = egg:pyramid#wsgiref 9 | host = 0.0.0.0 10 | port = 6543 11 | 12 | # Begin logging configuration 13 | 14 | [loggers] 15 | keys = root, tutorial 16 | 17 | [logger_tutorial] 18 | level = DEBUG 19 | handlers = 20 | qualname = tutorial 21 | 22 | [handlers] 23 | keys = console 24 | 25 | [formatters] 26 | keys = generic 27 | 28 | [logger_root] 29 | level = INFO 30 | handlers = console 31 | 32 | [handler_console] 33 | class = StreamHandler 34 | args = (sys.stderr,) 35 | level = NOTSET 36 | formatter = generic 37 | 38 | [formatter_generic] 39 | format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s 40 | 41 | # End logging configuration 42 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/typeviews/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | requires = [ 4 | 'pyramid', 5 | 'pyramid_jinja2', 6 | 'pyramid_debugtoolbar' 7 | ] 8 | 9 | setup(name='tutorial', 10 | install_requires=requires, 11 | entry_points="""\ 12 | [paste.app_factory] 13 | main = tutorial:main 14 | """, 15 | ) 16 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/typeviews/tutorial/__init__.py: -------------------------------------------------------------------------------- 1 | from pyramid.config import Configurator 2 | 3 | from .resources import bootstrap 4 | 5 | 6 | def main(global_config, **settings): 7 | config = Configurator(settings=settings, 8 | root_factory=bootstrap) 9 | config.include('pyramid_jinja2') 10 | config.scan('.views') 11 | return config.make_wsgi_app() 12 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/typeviews/tutorial/resources.py: -------------------------------------------------------------------------------- 1 | class Folder(dict): 2 | def __init__(self, name, parent, title): 3 | self.__name__ = name 4 | self.__parent__ = parent 5 | self.title = title 6 | 7 | 8 | class Root(Folder): 9 | pass 10 | 11 | 12 | class Document(object): 13 | def __init__(self, name, parent, title): 14 | self.__name__ = name 15 | self.__parent__ = parent 16 | self.title = title 17 | 18 | # Done outside bootstrap to persist from request to request 19 | root = Root('', None, 'My Site') 20 | 21 | 22 | def bootstrap(request): 23 | if not root.values(): 24 | # No values yet, let's make: 25 | # / 26 | # doc1 27 | # doc2 28 | # folder1/ 29 | # doc1 30 | doc1 = Document('doc1', root, 'Document 01') 31 | root['doc1'] = doc1 32 | doc2 = Document('doc2', root, 'Document 02') 33 | root['doc2'] = doc2 34 | folder1 = Folder('folder1', root, 'Folder 01') 35 | root['folder1'] = folder1 36 | 37 | # Only has to be unique in folder 38 | doc11 = Document('doc1', folder1, 'Document 01') 39 | folder1['doc1'] = doc11 40 | 41 | return root -------------------------------------------------------------------------------- /docs/traversal_tutorial/typeviews/tutorial/templates/breadcrumbs.jinja2: -------------------------------------------------------------------------------- 1 | {% for p in view.parents %} 2 | 3 | {{ p.title }} 4 | >> 5 | {% endfor %} 6 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/typeviews/tutorial/templates/contents.jinja2: -------------------------------------------------------------------------------- 1 |

Contents

2 |
    3 | {% for child in context.values() %} 4 |
  • 5 | {{ child.title }} 6 |
  • 7 | {% endfor %} 8 |
9 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/typeviews/tutorial/templates/document.jinja2: -------------------------------------------------------------------------------- 1 | {% extends "templates/layout.jinja2" %} 2 | {% block content %} 3 | 4 |

{{ context.title }}

5 |

A document might have some body text.

6 | 7 | {% endblock content %} 8 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/typeviews/tutorial/templates/folder.jinja2: -------------------------------------------------------------------------------- 1 | {% extends "templates/layout.jinja2" %} 2 | {% block content %} 3 | 4 |

{{ context.title }}

5 | {% include "templates/contents.jinja2" %} 6 | 7 | {% endblock content %} 8 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/typeviews/tutorial/templates/header.jinja2: -------------------------------------------------------------------------------- 1 | Tutorial -------------------------------------------------------------------------------- /docs/traversal_tutorial/typeviews/tutorial/templates/layout.jinja2: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ page_title }} 5 | 7 | 8 | 9 | 10 | 15 | 16 |
17 | 18 |
19 | {% include "templates/breadcrumbs.jinja2" %} 20 |
21 | 22 |

{{ page_title }}

23 | {% block content %} 24 | {% endblock content %} 25 | 26 |
27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/typeviews/tutorial/templates/root.jinja2: -------------------------------------------------------------------------------- 1 | {% extends "templates/layout.jinja2" %} 2 | {% block content %} 3 | 4 |

{{ context.title }}

5 |

The root might have some other text.

6 | {% include "templates/contents.jinja2" %} 7 | 8 | {% endblock content %} 9 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/typeviews/tutorial/tests.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from pyramid.testing import DummyRequest 4 | from pyramid.testing import DummyResource 5 | 6 | 7 | class TutorialViewsUnitTests(unittest.TestCase): 8 | def _makeOne(self, context, request): 9 | from .views import TutorialViews 10 | 11 | inst = TutorialViews(context, request) 12 | return inst 13 | 14 | def test_site(self): 15 | request = DummyRequest() 16 | context = DummyResource() 17 | inst = self._makeOne(context, request) 18 | result = inst.root() 19 | self.assertIn('Root', result['page_title']) 20 | 21 | def test_folder_view(self): 22 | request = DummyRequest() 23 | context = DummyResource() 24 | inst = self._makeOne(context, request) 25 | result = inst.folder() 26 | self.assertIn('Folder', result['page_title']) 27 | 28 | def test_document_view(self): 29 | request = DummyRequest() 30 | context = DummyResource() 31 | inst = self._makeOne(context, request) 32 | result = inst.document() 33 | self.assertIn('Document', result['page_title']) 34 | 35 | 36 | class TutorialFunctionalTests(unittest.TestCase): 37 | def setUp(self): 38 | from tutorial import main 39 | app = main({}) 40 | from webtest import TestApp 41 | self.testapp = TestApp(app) 42 | 43 | def test_it(self): 44 | res = self.testapp.get('/', status=200) 45 | self.assertIn(b'Root', res.body) 46 | res = self.testapp.get('/folder1', status=200) 47 | self.assertIn(b'Folder', res.body) 48 | res = self.testapp.get('/doc1', status=200) 49 | self.assertIn(b'Document', res.body) 50 | res = self.testapp.get('/doc2', status=200) 51 | self.assertIn(b'Document', res.body) 52 | res = self.testapp.get('/folder1/doc1', status=200) 53 | self.assertIn(b'Document', res.body) -------------------------------------------------------------------------------- /docs/traversal_tutorial/typeviews/tutorial/views.py: -------------------------------------------------------------------------------- 1 | from pyramid.location import lineage 2 | from pyramid.view import view_config 3 | 4 | from .resources import ( 5 | Root, 6 | Folder, 7 | Document 8 | ) 9 | 10 | 11 | class TutorialViews: 12 | def __init__(self, context, request): 13 | self.context = context 14 | self.request = request 15 | self.parents = reversed(list(lineage(context))) 16 | 17 | @view_config(renderer='templates/root.jinja2', 18 | context=Root) 19 | def root(self): 20 | page_title = 'Quick Tutorial: Root' 21 | return dict(page_title=page_title) 22 | 23 | @view_config(renderer='templates/folder.jinja2', 24 | context=Folder) 25 | def folder(self): 26 | page_title = 'Quick Tutorial: Folder' 27 | return dict(page_title=page_title) 28 | 29 | 30 | @view_config(renderer='templates/document.jinja2', 31 | context=Document) 32 | def document(self): 33 | page_title = 'Quick Tutorial: Document' 34 | return dict(page_title=page_title) 35 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/zodb/development.ini: -------------------------------------------------------------------------------- 1 | [app:main] 2 | use = egg:tutorial 3 | pyramid.reload_templates = true 4 | pyramid.includes = 5 | pyramid_debugtoolbar 6 | pyramid_zodbconn 7 | pyramid_tm 8 | zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 9 | 10 | [server:main] 11 | use = egg:pyramid#wsgiref 12 | host = 0.0.0.0 13 | port = 6543 14 | 15 | # Begin logging configuration 16 | 17 | [loggers] 18 | keys = root, tutorial 19 | 20 | [logger_tutorial] 21 | level = DEBUG 22 | handlers = 23 | qualname = tutorial 24 | 25 | [handlers] 26 | keys = console 27 | 28 | [formatters] 29 | keys = generic 30 | 31 | [logger_root] 32 | level = INFO 33 | handlers = console 34 | 35 | [handler_console] 36 | class = StreamHandler 37 | args = (sys.stderr,) 38 | level = NOTSET 39 | formatter = generic 40 | 41 | [formatter_generic] 42 | format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s 43 | 44 | # End logging configuration 45 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/zodb/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | requires = [ 4 | 'pyramid', 5 | 'pyramid_jinja2', 6 | 'ZODB3', 7 | 'pyramid_zodbconn', 8 | 'pyramid_tm', 9 | 'pyramid_debugtoolbar' 10 | ] 11 | 12 | setup(name='tutorial', 13 | install_requires=requires, 14 | entry_points="""\ 15 | [paste.app_factory] 16 | main = tutorial:main 17 | """, 18 | ) 19 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/zodb/tutorial/__init__.py: -------------------------------------------------------------------------------- 1 | from pyramid.config import Configurator 2 | from pyramid_zodbconn import get_connection 3 | 4 | from .resources import bootstrap 5 | 6 | 7 | def root_factory(request): 8 | conn = get_connection(request) 9 | return bootstrap(conn.root()) 10 | 11 | def main(global_config, **settings): 12 | config = Configurator(settings=settings, 13 | root_factory=root_factory) 14 | config.include('pyramid_jinja2') 15 | config.scan('.views') 16 | return config.make_wsgi_app() 17 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/zodb/tutorial/resources.py: -------------------------------------------------------------------------------- 1 | from persistent import Persistent 2 | from persistent.mapping import PersistentMapping 3 | import transaction 4 | 5 | 6 | class Folder(PersistentMapping): 7 | def __init__(self, title): 8 | PersistentMapping.__init__(self) 9 | self.title = title 10 | 11 | 12 | class Root(Folder): 13 | __name__ = None 14 | __parent__ = None 15 | 16 | 17 | class Document(Persistent): 18 | def __init__(self, title): 19 | Persistent.__init__(self) 20 | self.title = title 21 | 22 | 23 | def bootstrap(zodb_root): 24 | if not 'tutorial' in zodb_root: 25 | root = Root('My Site') 26 | zodb_root['tutorial'] = root 27 | transaction.commit() 28 | return zodb_root['tutorial'] 29 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/zodb/tutorial/templates/addform.jinja2: -------------------------------------------------------------------------------- 1 |

2 |

5 |
6 | 8 |
9 | 10 |
11 |

12 |

13 |

16 |
17 | 19 |
20 | 21 |
22 |

-------------------------------------------------------------------------------- /docs/traversal_tutorial/zodb/tutorial/templates/breadcrumbs.jinja2: -------------------------------------------------------------------------------- 1 | {% for p in view.parents %} 2 | 3 | {{ p.title }} 4 | >> 5 | {% endfor %} 6 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/zodb/tutorial/templates/contents.jinja2: -------------------------------------------------------------------------------- 1 |

Contents

2 |
    3 | {% for child in context.values() %} 4 |
  • 5 | {{ child.title }} 6 |
  • 7 | {% endfor %} 8 |
9 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/zodb/tutorial/templates/document.jinja2: -------------------------------------------------------------------------------- 1 | {% extends "templates/layout.jinja2" %} 2 | {% block content %} 3 | 4 |

{{ context.title }}

5 |

A document might have some body text.

6 | 7 | {% endblock content %} 8 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/zodb/tutorial/templates/folder.jinja2: -------------------------------------------------------------------------------- 1 | {% extends "templates/layout.jinja2" %} 2 | {% block content %} 3 | 4 |

{{ context.title }}

5 | {% include "templates/contents.jinja2" %} 6 | 7 | {% include "templates/addform.jinja2" %} 8 | 9 | {% endblock content %} 10 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/zodb/tutorial/templates/header.jinja2: -------------------------------------------------------------------------------- 1 | Tutorial -------------------------------------------------------------------------------- /docs/traversal_tutorial/zodb/tutorial/templates/layout.jinja2: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ page_title }} 5 | 7 | 8 | 9 | 10 | 15 | 16 |
17 | 18 |
19 | {% include "templates/breadcrumbs.jinja2" %} 20 |
21 | 22 |

{{ page_title }}

23 | {% block content %} 24 | {% endblock content %} 25 | 26 |
27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/zodb/tutorial/templates/root.jinja2: -------------------------------------------------------------------------------- 1 | {% extends "templates/layout.jinja2" %} 2 | {% block content %} 3 | 4 |

{{ context.title }}

5 |

The root might have some other text.

6 | {% include "templates/contents.jinja2" %} 7 | 8 | {% include "templates/addform.jinja2" %} 9 | 10 | {% endblock content %} 11 | -------------------------------------------------------------------------------- /docs/traversal_tutorial/zodb/tutorial/views.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | 3 | from pyramid.httpexceptions import HTTPFound 4 | from pyramid.location import lineage 5 | from pyramid.view import view_config 6 | 7 | from .resources import ( 8 | Root, 9 | Folder, 10 | Document 11 | ) 12 | 13 | 14 | class TutorialViews(object): 15 | def __init__(self, context, request): 16 | self.context = context 17 | self.request = request 18 | self.parents = reversed(list(lineage(context))) 19 | 20 | @view_config(renderer='templates/root.jinja2', 21 | context=Root) 22 | def root(self): 23 | page_title = 'Quick Tutorial: Root' 24 | return dict(page_title=page_title) 25 | 26 | @view_config(renderer='templates/folder.jinja2', 27 | context=Folder) 28 | def folder(self): 29 | page_title = 'Quick Tutorial: Folder' 30 | return dict(page_title=page_title) 31 | 32 | @view_config(name='add_folder', context=Folder) 33 | def add_folder(self): 34 | # Make a new Folder 35 | title = self.request.POST['folder_title'] 36 | name = str(randint(0, 999999)) 37 | new_folder = Folder(title) 38 | new_folder.__name__ = name 39 | new_folder.__parent__ = self.context 40 | self.context[name] = new_folder 41 | 42 | # Redirect to the new folder 43 | url = self.request.resource_url(new_folder) 44 | return HTTPFound(location=url) 45 | 46 | @view_config(name='add_document', context=Folder) 47 | def add_document(self): 48 | # Make a new Document 49 | title = self.request.POST['document_title'] 50 | name = str(randint(0, 999999)) 51 | new_document = Document(title) 52 | new_document.__name__ = name 53 | new_document.__parent__ = self.context 54 | self.context[name] = new_document 55 | 56 | # Redirect to the new document 57 | url = self.request.resource_url(new_document) 58 | return HTTPFound(location=url) 59 | 60 | @view_config(renderer='templates/document.jinja2', 61 | context=Document) 62 | def document(self): 63 | page_title = 'Quick Tutorial: Document' 64 | return dict(page_title=page_title) 65 | -------------------------------------------------------------------------------- /docs/views/chaining_decorators.rst: -------------------------------------------------------------------------------- 1 | Chaining Decorators 2 | %%%%%%%%%%%%%%%%%%% 3 | 4 | Pyramid has a ``decorator=`` argument to its view configuration. It accepts 5 | a single decorator that will wrap the *mapped* view callable represented by 6 | the view configuration. That means that, no matter what the signature and 7 | return value of the original view callable, the decorated view callable will 8 | receive two arguments: ``context`` and ``request`` and will return a response 9 | object:: 10 | 11 | # the decorator 12 | 13 | def decorator(view_callable): 14 | def inner(context, request): 15 | return view_callable(context, request) 16 | return inner 17 | 18 | # the view configuration 19 | 20 | @view_config(decorator=decorator, renderer='json') 21 | def myview(request): 22 | return {'a':1} 23 | 24 | But the ``decorator`` argument only takes a single decorator. What happens 25 | if you want to use more than one decorator? You can chain them together:: 26 | 27 | def combine(*decorators): 28 | def floo(view_callable): 29 | for decorator in decorators: 30 | view_callable = decorator(view_callable) 31 | return view_callable 32 | return floo 33 | 34 | def decorator1(view_callable): 35 | def inner(context, request): 36 | return view_callable(context, request) 37 | return inner 38 | 39 | def decorator2(view_callable): 40 | def inner(context, request): 41 | return view_callable(context, request) 42 | return inner 43 | 44 | def decorator3(view_callable): 45 | def inner(context, request): 46 | return view_callable(context, request) 47 | return inner 48 | 49 | alldecs = combine(decorator1, decorator2, decorator3) 50 | two_and_three = combine(decorator2, decorator3) 51 | one_and_three = combine(decorator1, decorator3) 52 | 53 | @view_config(decorator=alldecs, renderer='json') 54 | def myview(request): 55 | return {'a':1} 56 | -------------------------------------------------------------------------------- /docs/views/conditional_http.rst: -------------------------------------------------------------------------------- 1 | Conditional HTTP 2 | %%%%%%%%%%%%%%%% 3 | 4 | Pyramid requests and responses support conditional HTTP requests via the 5 | ``ETag`` and ``Last-Modified`` header. It is useful to enable this for an 6 | entire site to save on bandwidth for repeated requests. Enabling ``ETag`` 7 | support for an entire site can be done using a tween:: 8 | 9 | def conditional_http_tween_factory(handler, registry): 10 | def conditional_http_tween(request): 11 | response = handler(request) 12 | 13 | # If the Last-Modified header has been set, we want to enable the 14 | # conditional response processing. 15 | if response.last_modified is not None: 16 | response.conditional_response = True 17 | 18 | # We want to only enable the conditional machinery if either we 19 | # were given an explicit ETag header by the view or we have a 20 | # buffered response and can generate the ETag header ourself. 21 | if response.etag is not None: 22 | response.conditional_response = True 23 | elif (isinstance(response.app_iter, collections.abc.Sequence) and 24 | len(response.app_iter) == 1): 25 | response.conditional_response = True 26 | response.md5_etag() 27 | 28 | return response 29 | return conditional_http_tween 30 | 31 | The effect of this tween is that it will first check the response to determine 32 | if it already has a ``Last-Modified`` or ``ETag`` header set. If it does, then 33 | it will enable the conditional response processing. If the response does not 34 | have an ``ETag`` header set, then it will attempt to determine if the response 35 | is already loaded entirely into memory (to avoid loading what might be a very 36 | large object into memory). If it is already loaded into memory, then it will 37 | generate an ``ETag`` header from the MD5 digest of the response body, and 38 | again enable the conditional response processing. 39 | -------------------------------------------------------------------------------- /docs/views/index.rst: -------------------------------------------------------------------------------- 1 | Views 2 | %%%%% 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | chaining_decorators 8 | params_mapper 9 | conditional_http 10 | 11 | For more information on views, see the `Views 12 | `_ 13 | section of the Pyramid documentation. 14 | 15 | -------------------------------------------------------------------------------- /rtd.txt: -------------------------------------------------------------------------------- 1 | -e . 2 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [easy_install] 2 | zip_ok = false 3 | 4 | [aliases] 5 | dev = develop easy_install pyramid[testing] 6 | docs = develop easy_install pyramid[docs] 7 | 8 | [bdist_wheel] 9 | universal = 1 10 | 11 | [flake8] 12 | ignore = E301,E302,E731,E261,E123,E121,E128,E129,E125,W291,E501,W293,E303,W391,E266,E231,E201,E202,E127,E262,E265 13 | exclude = pyramid/tests/,pyramid/compat.py,pyramid/resource.py 14 | show-source = True 15 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # 3 | # Copyright (c) 2008-2015 Agendaless Consulting and Contributors. 4 | # All Rights Reserved. 5 | # 6 | # This software is subject to the provisions of the BSD-like license at 7 | # http://www.repoze.org/LICENSE.txt. A copy of the license should accompany 8 | # this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL 9 | # EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, 10 | # THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND 11 | # FITNESS FOR A PARTICULAR PURPOSE 12 | # 13 | ############################################################################## 14 | 15 | import os 16 | import sys 17 | 18 | from setuptools import setup, find_packages 19 | 20 | here = os.path.abspath(os.path.dirname(__file__)) 21 | try: 22 | with open(os.path.join(here, 'README.rst')) as f: 23 | README = f.read() 24 | except IOError: 25 | README = '' 26 | 27 | install_requires=[ 28 | 'setuptools', 29 | 'Sphinx >= 1.7.5', 30 | 'docutils', 31 | 'repoze.sphinx.autointerface', 32 | 'pylons-sphinx-themes', 33 | ] 34 | 35 | setup(name='pyramid_cookbook', 36 | version='0.2', 37 | description='The Pyramid Community Cookbook: topical, practical "recipes" of using Pyramid.', 38 | long_description=README + '\n\n', 39 | classifiers=[ 40 | "Development Status :: 6 - Mature", 41 | "Intended Audience :: Developers", 42 | "Programming Language :: Python", 43 | "Programming Language :: Python :: 2.7", 44 | "Programming Language :: Python :: 3", 45 | "Programming Language :: Python :: 3.4", 46 | "Programming Language :: Python :: 3.5", 47 | "Programming Language :: Python :: 3.6", 48 | "Programming Language :: Python :: Implementation :: CPython", 49 | "Programming Language :: Python :: Implementation :: PyPy", 50 | "Framework :: Pyramid", 51 | "Topic :: Internet :: WWW/HTTP", 52 | "Topic :: Internet :: WWW/HTTP :: WSGI", 53 | "License :: Repoze Public License", 54 | ], 55 | keywords='web wsgi pylons pyramid', 56 | author="Chris McDonough, Agendaless Consulting, and Steve Piercy", 57 | author_email="pylons-discuss@googlegroups.com", 58 | url="https://docs.pylonsproject.org/projects/pyramid-cookbook/en/latest/", 59 | license="BSD-derived (http://www.repoze.org/LICENSE.txt)", 60 | packages=find_packages(), 61 | include_package_data=True, 62 | zip_safe=False, 63 | install_requires = install_requires, 64 | ) 65 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = docs 3 | 4 | [testenv] 5 | # Most of these are defaults but if you specify any you can't fall back 6 | # to defaults for others. 7 | basepython = 8 | py36: python3.6 9 | py3: python3.6 10 | 11 | commands = 12 | pip install -q pyramid_cookbook 13 | 14 | [testenv:docs] 15 | basepython = python3.6 16 | whitelist_externals = make 17 | commands = 18 | pip install pyramid_cookbook 19 | make -C docs html BUILDDIR={envdir} "SPHINXOPTS=-W -E" 20 | --------------------------------------------------------------------------------