From 335200ed8f2b3fbd37fe8816749a9c2303082a53 Mon Sep 17 00:00:00 2001 From: cewing Date: Thu, 2 Jun 2016 17:16:29 -0700 Subject: be assertive in claims --- docs/narr/introduction.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst index de6ac408b..a387594d2 100644 --- a/docs/narr/introduction.rst +++ b/docs/narr/introduction.rst @@ -9,9 +9,8 @@ :app:`Pyramid` Introduction =========================== -:app:`Pyramid` is a general, open source, Python web application development -*framework*. Its primary goal is to make it easier for a Python developer to -create web applications. +:app:`Pyramid` is a Python web application *framework*. It is designed to make +creating web applications easier. It is open source. .. sidebar:: Frameworks vs. Libraries @@ -29,7 +28,7 @@ create web applications. framework provides a set of facilities that fits your application requirements. -Pyramid attempts to follow these design and engineering principles: +Pyramid follows these design and engineering principles: Simplicity :app:`Pyramid` takes a *"pay only for what you eat"* approach. You can get -- cgit v1.2.3 From 66b71518d01b2005c8070e58e7a7f37f2391fb0e Mon Sep 17 00:00:00 2001 From: cewing Date: Fri, 3 Jun 2016 14:00:38 -0700 Subject: clarify the framework sidebar --- docs/narr/introduction.rst | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst index a387594d2..e6658d953 100644 --- a/docs/narr/introduction.rst +++ b/docs/narr/introduction.rst @@ -12,21 +12,20 @@ :app:`Pyramid` is a Python web application *framework*. It is designed to make creating web applications easier. It is open source. -.. sidebar:: Frameworks vs. Libraries - - A *framework* differs from a *library* in one very important way: library - code is always *called* by code that you write, while a framework always - *calls* code that you write. Using a set of libraries to create an - application is usually easier than using a framework initially, because you - can choose to cede control to library code you have not authored very - selectively. But when you use a framework, you are required to cede a - greater portion of control to code you have not authored: code that resides - in the framework itself. You needn't use a framework at all to create a web - application using Python. A rich set of libraries already exists for the - platform. In practice, however, using a framework to create an application - is often more practical than rolling your own via a set of libraries if the - framework provides a set of facilities that fits your application - requirements. +.. sidebar:: What Is a Framework? + + A *framework* provides capabilities that developers can enhance or extend. A + web application framework provides many of the common needs of building web + applications allowing developers to concentrate only on the parts that are + specific to their application. + + Every framework makes choices about how a particular problem should be + solved. When developers choose to use a framework, they cede control over + the portions of their application that are provided by the framework. It is + possible to write a complete web application without any framework, by using + Python libraries. In practice, however, it is often more practical to use a + framework, so long as your chosen framework fits the requirements of your + application. Pyramid follows these design and engineering principles: -- cgit v1.2.3 From 9f2b004cd31fb4d949fb4e18f62bec7b82af58e6 Mon Sep 17 00:00:00 2001 From: cewing Date: Fri, 3 Jun 2016 15:06:42 -0700 Subject: simplify the statement of principles --- docs/narr/introduction.rst | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst index e6658d953..ac87f9ed4 100644 --- a/docs/narr/introduction.rst +++ b/docs/narr/introduction.rst @@ -30,31 +30,26 @@ creating web applications easier. It is open source. Pyramid follows these design and engineering principles: Simplicity - :app:`Pyramid` takes a *"pay only for what you eat"* approach. You can get - results even if you have only a partial understanding of :app:`Pyramid`. It - doesn't force you to use any particular technology to produce an application, - and we try to keep the core set of concepts that you need to understand to a - minimum. + :app:`Pyramid` is designed to be easy to use. You can get started even if you + don't understand it all. And when you're ready to do more, :app:`Pyramid` + will be there for you. Minimalism - :app:`Pyramid` tries to solve only the fundamental problems of creating a web - application: the mapping of URLs to code, templating, security, and serving - static assets. We consider these to be the core activities that are common to - nearly all web applications. + Out of the box, :app:`Pyramid` provides only the core tools needed for nearly + all web applications: mapping URLs to code, security, and serving static + assets (files like JavaScript and CSS). Additional tools provide templating, + database integration and more. But with :app:`Pyramid` you can *"pay only for + what you eat"*. Documentation - Pyramid's minimalism means that it is easier for us to maintain complete and - up-to-date documentation. It is our goal that no aspect of Pyramid is - undocumented. + :app:`Pyramid` is committed to comprehensive and up-to-date documentation. Speed - :app:`Pyramid` is designed to provide noticeably fast execution for common - tasks such as templating and simple response generation. + :app:`Pyramid` is designed to be noticeably fast. Reliability - :app:`Pyramid` is developed conservatively and tested exhaustively. Where - Pyramid source code is concerned, our motto is: "If it ain't tested, it's - broke". + :app:`Pyramid` is developed conservatively and tested exhaustively. Our motto + is: "If it ain't tested, it's broke". Openness As with Python, the Pyramid software is distributed under a `permissive open -- cgit v1.2.3 From f920852b93dd4d81dfe1dca12b72ac92140bf37c Mon Sep 17 00:00:00 2001 From: cewing Date: Fri, 3 Jun 2016 15:42:22 -0700 Subject: rewrite what makes pyramid unique --- docs/narr/introduction.rst | 54 +++++++++++++++++----------------------------- 1 file changed, 20 insertions(+), 34 deletions(-) diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst index ac87f9ed4..6e025f5f7 100644 --- a/docs/narr/introduction.rst +++ b/docs/narr/introduction.rst @@ -60,42 +60,28 @@ Openness What makes Pyramid unique ------------------------- -Understandably, people don't usually want to hear about squishy engineering -principles; they want to hear about concrete stuff that solves their problems. -With that in mind, what would make someone want to use Pyramid instead of one -of the many other web frameworks available today? What makes Pyramid unique? - -This is a hard question to answer because there are lots of excellent choices, -and it's actually quite hard to make a wrong choice, particularly in the Python -web framework market. But one reasonable answer is this: you can write very -small applications in Pyramid without needing to know a lot. "What?" you say. -"That can't possibly be a unique feature. Lots of other web frameworks let you -do that!" Well, you're right. But unlike many other systems, you can also -write very large applications in Pyramid if you learn a little more about it. +There are many tools available for web development. What would make someone +want to use Pyramid instead? What makes Pyramid unique? + +With Pyramid you can write very small applications without needing to know a +lot. And by learning a bit more, you can write very large applications too. Pyramid will allow you to become productive quickly, and will grow with you. It won't hold you back when your application is small, and it won't get in your -way when your application becomes large. "Well that's fine," you say. "Lots of -other frameworks let me write large apps, too." Absolutely. But other Python -web frameworks don't seamlessly let you do both. They seem to fall into two -non-overlapping categories: frameworks for "small apps" and frameworks for "big -apps". The "small app" frameworks typically sacrifice "big app" features, and -vice versa. - -We don't think it's a universally reasonable suggestion to write "small apps" -in a "small framework" and "big apps" in a "big framework". You can't really -know to what size every application will eventually grow. We don't really want -to have to rewrite a previously small application in another framework when it -gets "too big". We believe the current binary distinction between frameworks -for small and large applications is just false. A well-designed framework -should be able to be good at both. Pyramid strives to be that kind of -framework. - -To this end, Pyramid provides a set of features that combined are unique -amongst Python web frameworks. Lots of other frameworks contain some -combination of these features. Pyramid of course actually stole many of them -from those other frameworks. But Pyramid is the only one that has all of them -in one place, documented appropriately, and useful *à la carte* without -necessarily paying for the entire banquet. These are detailed below. +way when your application becomes large. Other application frameworks seem to +fall into two non-overlapping categories: those that support "small apps" and +those designed for "big apps". + +We don't believe you should have to make this choice. You can't really know how +large your application will become. You certainly shouldn't have to rewrite a +small application in another framework when it gets "too big". A well-designed +framework should be able to be good at both. Pyramid is that kind of framework. + +Pyramid provides a set of features that are unique among Python web frameworks. +Others may provide some, but only Pyramid provides them all, in one place, +fully documented, and useful *à la carte* without needing to pay for the whole +banquet. + +With Pyramid you get: Single-file applications ~~~~~~~~~~~~~~~~~~~~~~~~ -- cgit v1.2.3 From 02c5ba3a2cc09948ff49fd9f7c1bcba65050893a Mon Sep 17 00:00:00 2001 From: cewing Date: Fri, 3 Jun 2016 15:59:53 -0700 Subject: make title an action --- docs/narr/introduction.rst | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst index 6e025f5f7..bbe7df537 100644 --- a/docs/narr/introduction.rst +++ b/docs/narr/introduction.rst @@ -81,19 +81,15 @@ Others may provide some, but only Pyramid provides them all, in one place, fully documented, and useful *à la carte* without needing to pay for the whole banquet. -With Pyramid you get: -Single-file applications -~~~~~~~~~~~~~~~~~~~~~~~~ +Build single-file applications +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -You can write a Pyramid application that lives entirely in one Python file, not -unlike existing Python microframeworks. This is beneficial for one-off -prototyping, bug reproduction, and very small applications. These applications -are easy to understand because all the information about the application lives -in a single place, and you can deploy them without needing to understand much -about Python distributions and packaging. Pyramid isn't really marketed as a -microframework, but it allows you to do almost everything that frameworks that -are marketed as "micro" offer in very similar ways. +You can write a Pyramid application that lives entirely in one Python file. +Such an application is easy to understand since everything is in one place. It +is easy to deploy because you don't need to know much about Python packaging. +Pyramid allows you to do almost everything that so-called *microframeworks* can +in very similar ways. .. literalinclude:: helloworld.py -- cgit v1.2.3 From 6fcca68c34057200e7d7bebbdae678e4c0216dd6 Mon Sep 17 00:00:00 2001 From: cewing Date: Fri, 3 Jun 2016 16:18:34 -0700 Subject: fix decorator configuration section --- docs/narr/introduction.rst | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst index bbe7df537..41bc7ead3 100644 --- a/docs/narr/introduction.rst +++ b/docs/narr/introduction.rst @@ -97,13 +97,11 @@ in very similar ways. See also :ref:`firstapp_chapter`. -Decorator-based configuration -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Configure applications with decorators +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -If you like the idea of framework configuration statements living next to the -code it configures, so you don't have to constantly switch between files to -refer to framework configuration when adding new code, you can use Pyramid -decorators to localize the configuration. For example: +Pyramid allows you to keep your configuration right next to your code. That way +you don't have to switch files to see your configuration. For example: .. code-block:: python @@ -114,15 +112,11 @@ decorators to localize the configuration. For example: def fred_view(request): return Response('fred') -However, unlike some other systems, using decorators for Pyramid configuration -does not make your application difficult to extend, test, or reuse. The -:class:`~pyramid.view.view_config` decorator, for example, does not actually -*change* the input or output of the function it decorates, so testing it is a -"WYSIWYG" operation. You don't need to understand the framework to test your -own code. You just behave as if the decorator is not there. You can also -instruct Pyramid to ignore some decorators, or use completely imperative -configuration instead of decorators to add views. Pyramid decorators are inert -instead of eager. You detect and activate them with a :term:`scan`. +However, using Pyramid configuration decorators does not change your code. It +remains easy to extend, test or reuse. You can test your code as if the +decorators were not there. You can instruct the framework to ignore some +decorators. You can even use an imperative style to write your configuration, +skipping decorators entirely. Example: :ref:`mapping_views_using_a_decorator_section`. -- cgit v1.2.3 From dc7c27bdfbec0b22b650b9daf3e92f3b6000b4db Mon Sep 17 00:00:00 2001 From: cewing Date: Fri, 3 Jun 2016 16:20:21 -0700 Subject: fix generated urls section --- docs/narr/introduction.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst index 41bc7ead3..f13397edc 100644 --- a/docs/narr/introduction.rst +++ b/docs/narr/introduction.rst @@ -120,13 +120,13 @@ skipping decorators entirely. Example: :ref:`mapping_views_using_a_decorator_section`. -URL generation -~~~~~~~~~~~~~~ +Generate application URLs +~~~~~~~~~~~~~~~~~~~~~~~~~ -Pyramid is capable of generating URLs for resources, routes, and static assets. -Its URL generation APIs are easy to use and flexible. If you use Pyramid's -various APIs for generating URLs, you can change your configuration around -arbitrarily without fear of breaking a link on one of your web pages. +Dynamic web applications produce URLs that can change depending on what you are +viewing. Pyramid provides flexible, consistent, easy to use tools for generating +URLs. When you use these tools to write your application, you can change your +configuration without fear of breaking links in your web pages. Example: :ref:`generating_route_urls`. -- cgit v1.2.3 From 8c12559d800cde22d28a3cbc2ad5897c49768cb3 Mon Sep 17 00:00:00 2001 From: cewing Date: Fri, 3 Jun 2016 16:21:37 -0700 Subject: fix static assets section --- docs/narr/introduction.rst | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst index f13397edc..6a27cb715 100644 --- a/docs/narr/introduction.rst +++ b/docs/narr/introduction.rst @@ -130,16 +130,15 @@ configuration without fear of breaking links in your web pages. Example: :ref:`generating_route_urls`. -Static file serving +Serve static assets ~~~~~~~~~~~~~~~~~~~ -Pyramid is perfectly willing to serve static files itself. It won't make you -use some external web server to do that. You can even serve more than one set -of static files in a single Pyramid web application (e.g., ``/static`` and -``/static2``). You can optionally place your files on an external web server -and ask Pyramid to help you generate URLs to those files. This let's you use -Pyramid's internal file serving while doing development, and a faster static -file server in production, without changing any code. +Web applications often require JavaScript, CSS, images and other so-called +*static assets*. Pyramid provides flexible tools for serving these kinds of +files. You can serve them directly from Pyramid, or host them on an external +server or CDN (content delivery network). Either way, Pyramid can help you to +generate URLs so you can change where your files come from without changing any +code. Example: :ref:`static_assets_section`. -- cgit v1.2.3 From 0c4065080b6078c9d7495e28402718591f8e294b Mon Sep 17 00:00:00 2001 From: cewing Date: Fri, 3 Jun 2016 17:00:59 -0700 Subject: update the dynamic development section --- docs/narr/introduction.rst | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst index 6a27cb715..471b7a2ec 100644 --- a/docs/narr/introduction.rst +++ b/docs/narr/introduction.rst @@ -142,21 +142,19 @@ code. Example: :ref:`static_assets_section`. -Fully interactive development -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Develop interactively +~~~~~~~~~~~~~~~~~~~~~ + +Pyramid can automatically detect changes you make to template files and code, +so your changes are immediately available in your browser. You can debug using +plain old ``print()`` calls, which will display to your console. + +Pyramid has a debug toolbar that allows you to see information about how your +application is working right in your browser. See configuration, installed +packages, SQL queries, logging statements and more. -When developing a Pyramid application, several interactive features are -available. Pyramid can automatically utilize changed templates when rendering -pages and automatically restart the application to incorporate changed Python -code. Plain old ``print()`` calls used for debugging can display to a console. - -Pyramid's debug toolbar comes activated when you use a Pyramid scaffold to -render a project. This toolbar overlays your application in the browser, and -allows you access to framework data, such as the routes configured, the last -renderings performed, the current set of packages installed, SQLAlchemy queries -run, logging data, and various other facts. When an exception occurs, you can -use its interactive debugger to poke around right in your browser to try to -determine the cause of the exception. It's handy. +When your application has an error, an interactive debugger allows you to poke +around from your browser to find out what happened. Example: :ref:`debug_toolbar`. -- cgit v1.2.3 From 0027f5d5b2219aa483139390e31f23e63327fc55 Mon Sep 17 00:00:00 2001 From: cewing Date: Fri, 3 Jun 2016 17:01:45 -0700 Subject: update the debugging sections --- docs/narr/introduction.rst | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst index 471b7a2ec..114d013f2 100644 --- a/docs/narr/introduction.rst +++ b/docs/narr/introduction.rst @@ -158,22 +158,19 @@ around from your browser to find out what happened. Example: :ref:`debug_toolbar`. -Debugging settings -~~~~~~~~~~~~~~~~~~ - -Pyramid has debugging settings that allow you to print Pyramid runtime -information to the console when things aren't behaving as you're expecting. For -example, you can turn on ``debug_notfound``, which prints an informative -message to the console every time a URL does not match any view. You can turn -on ``debug_authorization``, which lets you know why a view execution was -allowed or denied by printing a message to the console. These features are -useful for those WTF moments. - -There are also a number of commands that you can invoke within a Pyramid -environment that allow you to introspect the configuration of your system. -``proutes`` shows all configured routes for an application in the order they'll -be evaluated for matching. ``pviews`` shows all configured views for any given -URL. These are also WTF-crushers in some circumstances. +Debug with power +~~~~~~~~~~~~~~~~ + +When things go wrong, Pyramid gives you powerful ways to fix the problem. + +You can configure Pyramid to print helpful information to the console. The +``debug_notfound`` setting shows information about URLs that aren't matched. +The ``debug_authorization`` setting provides helpful messages about why you +aren't allowed to do what you just tried. + +Pyramid also has command line tools to help you verify your configuration. You +can use ``proutes`` and ``pviews`` to inspect how URLs are connected to your +application code. Examples: :ref:`debug_authorization_section` and :ref:`command_line_chapter`. -- cgit v1.2.3 From f9822db7231bc4e52cadc467d60ea328def08d5b Mon Sep 17 00:00:00 2001 From: cewing Date: Fri, 3 Jun 2016 17:21:16 -0700 Subject: updated extensions section --- docs/narr/introduction.rst | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst index 114d013f2..2782267ae 100644 --- a/docs/narr/introduction.rst +++ b/docs/narr/introduction.rst @@ -174,14 +174,15 @@ application code. Examples: :ref:`debug_authorization_section` and :ref:`command_line_chapter`. -Add-ons -~~~~~~~ +Extend your application +~~~~~~~~~~~~~~~~~~~~~~~ + +Pyramid add-ons extend the core of the framework with useful abilities. There +are add-ons available for your favorite template language, SQL and NoSQL +databases, authentication services and much much more. -Pyramid has an extensive set of add-ons held to the same quality standards as -the Pyramid core itself. Add-ons are packages which provide functionality that -the Pyramid core doesn't. Add-on packages already exist which let you easily -send email, let you use the Jinja2 templating system, let you use XML-RPC or -JSON-RPC, let you integrate with jQuery Mobile, etc. +Supported Pyramid add-ons are held to the same demanding standards as the +framework itself. You will find them to be fully tested and well documented. Examples: https://trypyramid.com/resources-extending-pyramid.html -- cgit v1.2.3 From 42089ef5acc065dbb1fe826855936a415162b42b Mon Sep 17 00:00:00 2001 From: cewing Date: Fri, 3 Jun 2016 17:43:39 -0700 Subject: re-write the views section --- docs/narr/introduction.rst | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst index 2782267ae..2ede8328d 100644 --- a/docs/narr/introduction.rst +++ b/docs/narr/introduction.rst @@ -187,19 +187,15 @@ framework itself. You will find them to be fully tested and well documented. Examples: https://trypyramid.com/resources-extending-pyramid.html -Class-based and function-based views -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Pyramid has a structured, unified concept of a :term:`view callable`. View -callables can be functions, methods of classes, or even instances. When you -add a new view callable, you can choose to make it a function or a method of a -class. In either case Pyramid treats it largely the same way. You can change -your mind later and move code between methods of classes and functions. A -collection of similar view callables can be attached to a single class as -methods, if that floats your boat, and they can share initialization code as -necessary. All kinds of views are easy to understand and use, and operate -similarly. There is no phony distinction between them. They can be used for -the same purposes. +Write your views +~~~~~~~~~~~~~~~~ + +A fundamental task for any framework is to map URLs to code. In Pyramid, that +code is called a :term:`view callable`. View callables can be functions, class +methods or even callable class instances. You are free to choose the approach +that best fits your use case. Regardless of your choice, Pyramid treats them +the same. You can change your mind at any time without any penalty. There are +no artificial distinctions between the various approaches. Here's a view callable defined as a function: -- cgit v1.2.3 From 86ed4c13d31cae497cd7459798e57c1fa03e2548 Mon Sep 17 00:00:00 2001 From: cewing Date: Fri, 3 Jun 2016 17:44:22 -0700 Subject: emphasize that the views are yours --- docs/narr/introduction.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst index 2ede8328d..3f04327cc 100644 --- a/docs/narr/introduction.rst +++ b/docs/narr/introduction.rst @@ -187,8 +187,8 @@ framework itself. You will find them to be fully tested and well documented. Examples: https://trypyramid.com/resources-extending-pyramid.html -Write your views -~~~~~~~~~~~~~~~~ +Write *your* views +~~~~~~~~~~~~~~~~~~ A fundamental task for any framework is to map URLs to code. In Pyramid, that code is called a :term:`view callable`. View callables can be functions, class -- cgit v1.2.3 From 0967f59a084b5d0eee1de18a85cb0f2ed486e0b5 Mon Sep 17 00:00:00 2001 From: cewing Date: Fri, 3 Jun 2016 17:51:42 -0700 Subject: improve section title a bit more --- docs/narr/introduction.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst index 3f04327cc..c1f17dcfd 100644 --- a/docs/narr/introduction.rst +++ b/docs/narr/introduction.rst @@ -187,8 +187,8 @@ framework itself. You will find them to be fully tested and well documented. Examples: https://trypyramid.com/resources-extending-pyramid.html -Write *your* views -~~~~~~~~~~~~~~~~~~ +Write *your* views, *your* way +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A fundamental task for any framework is to map URLs to code. In Pyramid, that code is called a :term:`view callable`. View callables can be functions, class -- cgit v1.2.3 From ff14e5d4406a5fe9a4cc346be22cb36f45b6d844 Mon Sep 17 00:00:00 2001 From: cewing Date: Tue, 7 Jun 2016 10:39:23 -0700 Subject: fix up headline a bit --- docs/narr/introduction.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst index c1f17dcfd..41d7d384e 100644 --- a/docs/narr/introduction.rst +++ b/docs/narr/introduction.rst @@ -187,8 +187,8 @@ framework itself. You will find them to be fully tested and well documented. Examples: https://trypyramid.com/resources-extending-pyramid.html -Write *your* views, *your* way -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Write your views, *your* way +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A fundamental task for any framework is to map URLs to code. In Pyramid, that code is called a :term:`view callable`. View callables can be functions, class -- cgit v1.2.3 From 9b942a4bceb3efe98fbf5799d5e7aace33f9770c Mon Sep 17 00:00:00 2001 From: cewing Date: Tue, 7 Jun 2016 10:42:10 -0700 Subject: update asset specification section --- docs/narr/introduction.rst | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst index 41d7d384e..b9771c5f3 100644 --- a/docs/narr/introduction.rst +++ b/docs/narr/introduction.rst @@ -235,25 +235,26 @@ Here's a few views defined as methods of a class instead: .. _intro_asset_specs: -Asset specifications -~~~~~~~~~~~~~~~~~~~~ - -Asset specifications are strings that contain both a Python package name and a -file or directory name, e.g., ``MyPackage:static/index.html``. Use of these -specifications is omnipresent in Pyramid. An asset specification can refer to -a template, a translation directory, or any other package-bound static -resource. This makes a system built on Pyramid extensible because you don't -have to rely on globals ("*the* static directory") or lookup schemes ("*the* -ordered set of template directories") to address your files. You can move -files around as necessary, and include other packages that may not share your -system's templates or static files without encountering conflicts. - -Because asset specifications are used heavily in Pyramid, we've also provided a -way to allow users to override assets. Say you love a system that someone else -has created with Pyramid but you just need to change "that one template" to -make it all better. No need to fork the application. Just override the asset -specification for that template with your own inside a wrapper, and you're good -to go. +Find *your* static assets +~~~~~~~~~~~~~~~~~~~~~~~~~ + +In many web frameworks, the static assets required by an application are kept +in a globally shared location, "the *static* directory". Others use a lookup +scheme, like an ordered set of template directories. Both of these approaches +have problems when it comes to customization. + +Pyramid takes a different approach. Static assets are located using *asset +specifications*, strings that contain reference both to a Python package name +and a file or directory name, e.g. ``MyPackage:static/index.html``. These +specifications are used for templates, JavaScript and CSS, translation files, +and any other package-bound static resource. By using asset specifications, +Pyramid makes it easy to extend your application with other packages without +worrying about conflicts. + +What happens if another Pyramid package you are using provides an asset you +need to customize? Maybe that page template needs better HTML, or you want to +update some CSS. With asset specifications you can override the assets from +other packages using simple wrappers. Examples: :ref:`asset_specifications` and :ref:`overriding_assets_section`. -- cgit v1.2.3 From 5404f9cef8eacefa37a221327de6a7f66c5798eb Mon Sep 17 00:00:00 2001 From: cewing Date: Tue, 7 Jun 2016 11:01:36 -0700 Subject: update information about extensible templating --- docs/narr/introduction.rst | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst index b9771c5f3..53f68a2ef 100644 --- a/docs/narr/introduction.rst +++ b/docs/narr/introduction.rst @@ -258,19 +258,19 @@ other packages using simple wrappers. Examples: :ref:`asset_specifications` and :ref:`overriding_assets_section`. -Extensible templating -~~~~~~~~~~~~~~~~~~~~~ - -Pyramid has a structured API that allows for pluggability of "renderers". -Templating systems such as Mako, Genshi, Chameleon, and Jinja2 can be treated -as renderers. Renderer bindings for all of these templating systems already -exist for use in Pyramid. But if you'd rather use another, it's not a big -deal. Just copy the code from an existing renderer package, and plug in your -favorite templating system. You'll then be able to use that templating system -from within Pyramid just as you'd use one of the "built-in" templating systems. - -Pyramid does not make you use a single templating system exclusively. You can -use multiple templating systems, even in the same project. +Use *your* templates +~~~~~~~~~~~~~~~~~~~~ + +In Pyramid, the job of creating a ``Response`` belongs to a :term:`renderer`. +Any templating system--Mako, Genshi, Chameleon, Jinja2--can be a renderer. In +fact, packages exist for all of these systems. But if you'd rather use another, +a structured API exists allowing you to create a renderer using your favorite +templating system. You can use the templating system *you* understand, not one +required by the framework. + +What's more, Pyramid does not make you use a single templating system +exclusively. You can use multiple templating systems, even in the same +project. Example: :ref:`templates_used_directly`. -- cgit v1.2.3 From 1610c8fd3605f2ed481c37da27a1ce059419888a Mon Sep 17 00:00:00 2001 From: cewing Date: Tue, 7 Jun 2016 11:02:21 -0700 Subject: update section on returning dictionaries from views --- docs/narr/introduction.rst | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst index 53f68a2ef..112754b6a 100644 --- a/docs/narr/introduction.rst +++ b/docs/narr/introduction.rst @@ -274,16 +274,16 @@ project. Example: :ref:`templates_used_directly`. -Rendered views can return dictionaries -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Write testable views +~~~~~~~~~~~~~~~~~~~~ -If you use a :term:`renderer`, you don't have to return a special kind of -"webby" ``Response`` object from a view. Instead you can return a dictionary, -and Pyramid will take care of converting that dictionary to a Response using a -template on your behalf. This makes the view easier to test, because you don't -have to parse HTML in your tests. Instead just make an assertion that the view -returns "the right stuff" in the dictionary. You can write "real" unit tests -instead of functionally testing all of your views. +When you use a :term:`renderer` with your view callable, you are freed from +needing to return a "webby" ``Response`` object. Instead, your views can return +a simple Python dictionary. Pyramid will take care of rendering the information +in that dictionary to a ``Response`` on your behalf. As a result, your views +are more easily tested, since you don't need to parse HTML to evaluate the +results. Pyramid makes it a snap to write unit tests for your views, instead of +requiring you to use functional tests. .. index:: pair: renderer; explicitly calling @@ -291,7 +291,7 @@ instead of functionally testing all of your views. .. _example_render_to_response_call: -For example, instead of returning a ``Response`` object from a +For example, a typical web framework might return a ``Response`` object from a ``render_to_response`` call: .. code-block:: python @@ -303,7 +303,7 @@ For example, instead of returning a ``Response`` object from a return render_to_response('myapp:templates/mytemplate.pt', {'a':1}, request=request) -You can return a Python dictionary: +While you *can* do this in Pyramid, you can also return a Python dictionary: .. code-block:: python :linenos: @@ -314,13 +314,13 @@ You can return a Python dictionary: def myview(request): return {'a':1} -When this view callable is called by Pyramid, the ``{'a':1}`` dictionary will -be rendered to a response on your behalf. The string passed as ``renderer=`` -above is an :term:`asset specification`. It is in the form -``packagename:directoryname/filename.ext``. In this case, it refers to the -``mytemplate.pt`` file in the ``templates`` directory within the ``myapp`` -Python package. Asset specifications are omnipresent in Pyramid. See -:ref:`intro_asset_specs` for more information. +By configuring your view to use a renderer, you tell Pyramid to use the +``{'a':1}`` dictionary and the specified template to render a response on your +behalf. + +The string passed as ``renderer=`` above is an :term:`asset specification`. +Asset specifications are omnipresent in Pyramid. They allow for more reliable +customization. See :ref:`intro_asset_specs` for more information. Example: :ref:`renderers_chapter`. -- cgit v1.2.3 From 107390374a40d5fff686ebb084d1d9da5fc07eb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20HUBSCHER?= Date: Thu, 16 Feb 2017 17:16:35 +0100 Subject: Add a failing test. --- pyramid/tests/test_config/test_settings.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/pyramid/tests/test_config/test_settings.py b/pyramid/tests/test_config/test_settings.py index 2dbe9b1bb..d6fb8de67 100644 --- a/pyramid/tests/test_config/test_settings.py +++ b/pyramid/tests/test_config/test_settings.py @@ -1,5 +1,6 @@ import unittest + class TestSettingsConfiguratorMixin(unittest.TestCase): def _makeOne(self, *arg, **kw): from pyramid.config import Configurator @@ -63,6 +64,24 @@ class TestSettingsConfiguratorMixin(unittest.TestCase): settings = reg.getUtility(ISettings) self.assertEqual(settings['a'], 1) + def test_settings_parameter_dict_is_never_updated(self): + class ReadOnlyDict(dict): + def __readonly__(self, *args, **kwargs): + raise RuntimeError("Cannot modify ReadOnlyDict") + __setitem__ = __readonly__ + __delitem__ = __readonly__ + pop = __readonly__ + popitem = __readonly__ + clear = __readonly__ + update = __readonly__ + setdefault = __readonly__ + del __readonly__ + + initial = ReadOnlyDict() + config = self._makeOne(settings=initial) + config._set_settings({'a': '1'}) + + class TestSettings(unittest.TestCase): def _getTargetClass(self): -- cgit v1.2.3 From 0edf02b0452fe461570aec9fac34613e38deaad6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20HUBSCHER?= Date: Thu, 16 Feb 2017 17:49:58 +0100 Subject: Settings should not alter the initial dict. --- pyramid/config/settings.py | 1 + pyramid/tests/test_config/test_settings.py | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pyramid/config/settings.py b/pyramid/config/settings.py index 26eb48951..52b30db81 100644 --- a/pyramid/config/settings.py +++ b/pyramid/config/settings.py @@ -56,6 +56,7 @@ def Settings(d=None, _environ_=os.environ, **kw): keyword args).""" if d is None: d = {} + d = dict(d) d.update(**kw) eget = _environ_.get diff --git a/pyramid/tests/test_config/test_settings.py b/pyramid/tests/test_config/test_settings.py index d6fb8de67..202629358 100644 --- a/pyramid/tests/test_config/test_settings.py +++ b/pyramid/tests/test_config/test_settings.py @@ -12,12 +12,12 @@ class TestSettingsConfiguratorMixin(unittest.TestCase): settings = config._set_settings(None) self.assertTrue(settings) - def test__set_settings_uses_original_dict(self): + def test__set_settings_does_not_uses_original_dict(self): config = self._makeOne() dummy = {} result = config._set_settings(dummy) - self.assertTrue(dummy is result) - self.assertEqual(dummy['pyramid.debug_all'], False) + self.assertTrue(dummy is not result) + self.assertNotIn('pyramid.debug_all', dummy) def test__set_settings_as_dictwithvalues(self): config = self._makeOne() -- cgit v1.2.3 From 3c04c12d227bd08f248519691418f07070f9c587 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20HUBSCHER?= Date: Thu, 16 Feb 2017 18:09:05 +0100 Subject: It is actually a good thing that this line is not called. --- pyramid/tests/test_config/test_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyramid/tests/test_config/test_settings.py b/pyramid/tests/test_config/test_settings.py index 202629358..a3afd24e7 100644 --- a/pyramid/tests/test_config/test_settings.py +++ b/pyramid/tests/test_config/test_settings.py @@ -66,7 +66,7 @@ class TestSettingsConfiguratorMixin(unittest.TestCase): def test_settings_parameter_dict_is_never_updated(self): class ReadOnlyDict(dict): - def __readonly__(self, *args, **kwargs): + def __readonly__(self, *args, **kwargs): # pragma: no cover raise RuntimeError("Cannot modify ReadOnlyDict") __setitem__ = __readonly__ __delitem__ = __readonly__ -- cgit v1.2.3 From 9845f1aadc5c30b7387809a3baa3a2bcaaa1a597 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20HUBSCHER?= Date: Mon, 20 Feb 2017 11:01:34 +0100 Subject: Niceties. --- pyramid/scripts/pserve.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/pyramid/scripts/pserve.py b/pyramid/scripts/pserve.py index e2d97f5ec..b1b9cd009 100644 --- a/pyramid/scripts/pserve.py +++ b/pyramid/scripts/pserve.py @@ -35,10 +35,12 @@ from pyramid.scripts.common import setup_logging from pyramid.path import AssetResolver from pyramid.settings import aslist + def main(argv=sys.argv, quiet=False): command = PServeCommand(argv, quiet=quiet) return command.run() + class PServeCommand(object): description = """\ @@ -113,7 +115,6 @@ class PServeCommand(object): "passed here.", ) - ConfigParser = configparser.ConfigParser # testing loadapp = staticmethod(loadapp) # testing loadserver = staticmethod(loadserver) # testing @@ -126,7 +127,7 @@ class PServeCommand(object): self.args.verbose = 0 self.watch_files = [] - def out(self, msg): # pragma: no cover + def out(self, msg): # pragma: no cover if self.args.verbose > 0: print(msg) @@ -239,8 +240,9 @@ class PServeCommand(object): msg = '' self.out('Exiting%s (-v to see traceback)' % msg) + # For paste.deploy server instantiation (egg:pyramid#wsgiref) -def wsgiref_server_runner(wsgi_app, global_conf, **kw): # pragma: no cover +def wsgiref_server_runner(wsgi_app, global_conf, **kw): # pragma: no cover from wsgiref.simple_server import make_server host = kw.get('host', '0.0.0.0') port = int(kw.get('port', 8080)) @@ -248,13 +250,14 @@ def wsgiref_server_runner(wsgi_app, global_conf, **kw): # pragma: no cover print('Starting HTTP server on http://%s:%s' % (host, port)) server.serve_forever() + # For paste.deploy server instantiation (egg:pyramid#cherrypy) def cherrypy_server_runner( app, global_conf=None, host='127.0.0.1', port=None, ssl_pem=None, protocol_version=None, numthreads=None, server_name=None, max=None, request_queue_size=None, timeout=None - ): # pragma: no cover + ): # pragma: no cover """ Entry point for CherryPy's WSGI server @@ -361,5 +364,6 @@ def cherrypy_server_runner( return server -if __name__ == '__main__': # pragma: no cover + +if __name__ == '__main__': # pragma: no cover sys.exit(main() or 0) -- cgit v1.2.3 From d64e8d14c9cda3b95aac54a468f4e18cbb9c8a75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20HUBSCHER?= Date: Mon, 20 Feb 2017 11:03:11 +0100 Subject: Make sure PServeCommand kwargs are passed to the hupper worker. --- pyramid/scripts/pserve.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyramid/scripts/pserve.py b/pyramid/scripts/pserve.py index b1b9cd009..b89f9b982 100644 --- a/pyramid/scripts/pserve.py +++ b/pyramid/scripts/pserve.py @@ -125,6 +125,8 @@ class PServeCommand(object): self.args = self.parser.parse_args(argv[1:]) if quiet: self.args.verbose = 0 + if self.args.reload: + self.worker_kwargs = {'argv': argv, "quiet": quiet} self.watch_files = [] def out(self, msg): # pragma: no cover @@ -204,6 +206,7 @@ class PServeCommand(object): 'pyramid.scripts.pserve.main', reload_interval=int(self.args.reload_interval), verbose=self.args.verbose, + worker_kwargs=self.worker_kwargs ) return 0 -- cgit v1.2.3 From b2a749b34c1140613b3deaa2b55ab6c29dd343cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20HUBSCHER?= Date: Mon, 20 Feb 2017 11:31:38 +0100 Subject: Add test. --- pyramid/tests/test_scripts/test_pserve.py | 20 +++++++++++++++++++- setup.py | 3 ++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/pyramid/tests/test_scripts/test_pserve.py b/pyramid/tests/test_scripts/test_pserve.py index 18b0c84b6..f681eaedb 100644 --- a/pyramid/tests/test_scripts/test_pserve.py +++ b/pyramid/tests/test_scripts/test_pserve.py @@ -1,9 +1,11 @@ +import mock import os import unittest from pyramid.tests.test_scripts import dummy here = os.path.abspath(os.path.dirname(__file__)) + class TestPServeCommand(unittest.TestCase): def setUp(self): from pyramid.compat import NativeIO @@ -48,10 +50,11 @@ class TestPServeCommand(unittest.TestCase): inst.loadserver = self._get_server app = dummy.DummyApp() + def get_app(*args, **kwargs): app.global_conf = kwargs.get('global_conf', None) - inst.loadapp = get_app + inst.loadapp = get_app inst.run() self.assertEqual(app.global_conf, {'a': '1', 'b': '2'}) @@ -77,6 +80,21 @@ class TestPServeCommand(unittest.TestCase): os.path.abspath(os.path.join(here, '*.py')), ]) + def test_reload_call_hupper_with_correct_args(self): + with mock.patch('pyramid.scripts.pserve.hupper') as hupper_mock: + hupper_mock.is_active.side_effect = [False, True] + inst = self._makeOne('--reload', 'development.ini') + inst.loadserver = mock.MagicMock() + inst.loadapp = mock.MagicMock() + inst.run() + hupper_mock.start_reloader.assert_called_with( + 'pyramid.scripts.pserve.main', + reload_interval=1, + verbose=1, + worker_kwargs={'argv': ['pserve', '--reload', 'development.ini'], + 'quiet': False}) + + class Test_main(unittest.TestCase): def _callFUT(self, argv): from pyramid.scripts.pserve import main diff --git a/setup.py b/setup.py index d9fcec4c8..bd0e7f4d7 100644 --- a/setup.py +++ b/setup.py @@ -65,9 +65,10 @@ docs_extras = [ ] testing_extras = tests_require + [ + 'mock', 'nose', 'coverage', - 'virtualenv', # for scaffolding tests + 'virtualenv', # for scaffolding tests ] setup(name='pyramid', -- cgit v1.2.3 From 0bee841b9e9537a912b14017601de63e7efeabf1 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sat, 25 Feb 2017 17:23:50 -0600 Subject: add an IExecutionPolicy that can wrap the router --- docs/api/config.rst | 1 + docs/api/interfaces.rst | 3 ++ docs/glossary.rst | 4 +++ pyramid/config/factories.py | 25 +++++++++++++++ pyramid/interfaces.py | 43 +++++++++++++++++++++++++- pyramid/router.py | 46 +++++++++++++++++++++------- pyramid/tests/pkgs/subrequestapp/__init__.py | 4 ++- pyramid/tests/test_config/test_factories.py | 19 +++++++++++- pyramid/tests/test_integration.py | 2 +- pyramid/tests/test_router.py | 13 ++++++++ 10 files changed, 145 insertions(+), 15 deletions(-) diff --git a/docs/api/config.rst b/docs/api/config.rst index 62f138b76..c76d3d5ff 100644 --- a/docs/api/config.rst +++ b/docs/api/config.rst @@ -70,6 +70,7 @@ .. automethod:: add_subscriber_predicate .. automethod:: add_view_predicate .. automethod:: add_view_deriver + .. automethod:: set_execution_policy .. automethod:: set_request_factory .. automethod:: set_root_factory .. automethod:: set_session_factory diff --git a/docs/api/interfaces.rst b/docs/api/interfaces.rst index 521d65d2b..a212ba7a9 100644 --- a/docs/api/interfaces.rst +++ b/docs/api/interfaces.rst @@ -65,6 +65,9 @@ Other Interfaces .. autointerface:: IResponseFactory :members: + .. autointerface:: IRouter + :members: + .. autointerface:: IViewMapperFactory :members: diff --git a/docs/glossary.rst b/docs/glossary.rst index 0f299c169..3a55a9f8a 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -1154,3 +1154,7 @@ Glossary coverage A measurement of code coverage, usually expressed as a percentage of which lines of code have been executed over which lines are executable, typically run during test execution. + execution policy + A policy which wraps the :term:`router` by creating the request object + and sending it through the request pipeline. + See :class:`pyramid.config.Configurator.set_execution_policy`. diff --git a/pyramid/config/factories.py b/pyramid/config/factories.py index f0b6252ae..c8633cc47 100644 --- a/pyramid/config/factories.py +++ b/pyramid/config/factories.py @@ -3,6 +3,7 @@ from zope.interface import implementer from pyramid.interfaces import ( IDefaultRootFactory, + IExecutionPolicy, IRequestFactory, IResponseFactory, IRequestExtensions, @@ -10,6 +11,7 @@ from pyramid.interfaces import ( ISessionFactory, ) +from pyramid.router import default_execution_policy from pyramid.traversal import DefaultRootFactory from pyramid.util import ( @@ -231,6 +233,29 @@ class FactoriesConfiguratorMixin(object): 'set_request_propery() is deprecated as of Pyramid 1.5; use ' 'add_request_method() with the property=True argument instead') + @action_method + def set_execution_policy(self, policy): + """ + Override the :app:`Pyramid` :term:`execution policy` in the + current configuration. The ``policy`` argument must be an instance + of an :class:`pyramid.interfaces.IExecutionPolicy` or a + :term:`dotted Python name` that points at an instance of an + execution policy. + + """ + policy = self.maybe_dotted(policy) + if policy is None: + policy = default_execution_policy + + def register(): + self.registry.registerUtility(policy, IExecutionPolicy) + + intr = self.introspectable('execution policy', None, + self.object_description(policy), + 'execution policy') + intr['policy'] = policy + self.action(IExecutionPolicy, register, introspectables=(intr,)) + @implementer(IRequestExtensions) class _RequestExtensions(object): diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index 450cd9c24..bbb4754e4 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -682,7 +682,48 @@ class IRouter(Interface): registry = Attribute( """Component architecture registry local to this application.""") -class ISettings(Interface): + def make_request(environ): + """ + Create a new request object. + + This method initializes a new :class:`pyramid.interfaces.IRequest` + object using the application's + :class:`pyramid.interfaces.IRequestFactory`. + """ + + def invoke_request(request): + """ + Invoke the :app:`Pyramid` request pipeline. + + See :ref:`router_chapter` for information on the request pipeline. + """ + +class IExecutionPolicy(Interface): + def __call__(environ, router): + """ + This callable triggers the router to process a raw WSGI environ dict + into a response and controls the :app:`Pyramid` request pipeline. + + The ``environ`` is the raw WSGI environ. + + The ``router`` is an :class:`pyramid.interfaces.IRouter` object which + should be used to create a request object and send it into the + processing pipeline. + + The return value should be a :class:`pyramid.interfaces.IResponse` + object or an exception that will be handled by WSGI middleware. + + The default execution policy simple creates a request and sends it + through the pipeline: + + .. code-block:: python + + def simple_execution_policy(environ, router): + request = router.make_request(environ) + return router.invoke_request(request) + """ + +class ISettings(IDict): """ Runtime settings utility for pyramid; represents the deployment settings for the application. Implements a mapping interface.""" diff --git a/pyramid/router.py b/pyramid/router.py index fd11925e9..8b7b7b6bc 100644 --- a/pyramid/router.py +++ b/pyramid/router.py @@ -5,6 +5,7 @@ from zope.interface import ( from pyramid.interfaces import ( IDebugLogger, + IExecutionPolicy, IRequest, IRequestExtensions, IRootFactory, @@ -49,6 +50,8 @@ class Router(object): self.routes_mapper = q(IRoutesMapper) self.request_factory = q(IRequestFactory, default=Request) self.request_extensions = q(IRequestExtensions) + self.execution_policy = q( + IExecutionPolicy, default=default_execution_policy) self.orig_handle_request = self.handle_request tweens = q(ITweens) if tweens is not None: @@ -182,19 +185,36 @@ class Router(object): :term:`tween` in the tween stack closest to the request ingress. If ``use_tweens`` is ``False``, the request will be sent to the main router handler, and no tweens will be invoked. - + See the API for pyramid.request for complete documentation. """ + request.registry = self.registry + request.invoke_subrequest = self.invoke_subrequest + return self.invoke_request( + request, + _use_tweens=use_tweens, + _apply_extensions=True, + ) + + def make_request(self, environ): + request = self.request_factory(environ) + request.registry = self.registry + request.invoke_subrequest = self.invoke_subrequest + extensions = self.request_extensions + if extensions is not None: + apply_request_extensions(request, extensions=extensions) + return request + + def invoke_request(self, request, + _use_tweens=True, _apply_extensions=False): registry = self.registry has_listeners = self.registry.has_listeners notify = self.registry.notify - threadlocals = {'registry':registry, 'request':request} + threadlocals = {'registry': registry, 'request': request} manager = self.threadlocal_manager manager.push(threadlocals) - request.registry = registry - request.invoke_subrequest = self.invoke_subrequest - - if use_tweens: + + if _use_tweens: handle_request = self.handle_request else: handle_request = self.orig_handle_request @@ -203,7 +223,7 @@ class Router(object): try: extensions = self.request_extensions - if extensions is not None: + if _apply_extensions and extensions is not None: apply_request_extensions(request, extensions=extensions) response = handle_request(request) @@ -211,7 +231,7 @@ class Router(object): request._process_response_callbacks(response) has_listeners and notify(NewResponse(request, response)) - + return response finally: @@ -229,6 +249,10 @@ class Router(object): within the application registry; call ``start_response`` and return an iterable. """ - request = self.request_factory(environ) - response = self.invoke_subrequest(request, use_tweens=True) - return response(request.environ, start_response) + response = self.execution_policy(environ, self) + return response(environ, start_response) + + +def default_execution_policy(environ, router): + request = router.make_request(environ) + return router.invoke_request(request) diff --git a/pyramid/tests/pkgs/subrequestapp/__init__.py b/pyramid/tests/pkgs/subrequestapp/__init__.py index b8f44cd7f..e4b1d386a 100644 --- a/pyramid/tests/pkgs/subrequestapp/__init__.py +++ b/pyramid/tests/pkgs/subrequestapp/__init__.py @@ -7,7 +7,8 @@ def view_one(request): return response def view_two(request): - return 'This came from view_two' + # check that request.foo is valid for a subrequest + return 'This came from view_two, foo=%s' % (request.foo,) def view_three(request): subreq = Request.blank('/view_four') @@ -46,5 +47,6 @@ def main(): config.add_view(view_three, route_name='three') config.add_view(view_four, route_name='four') config.add_view(view_five, route_name='five') + config.add_request_method(lambda r: 'bar', 'foo', property=True) return config diff --git a/pyramid/tests/test_config/test_factories.py b/pyramid/tests/test_config/test_factories.py index 452d762f8..eb1f3534c 100644 --- a/pyramid/tests/test_config/test_factories.py +++ b/pyramid/tests/test_config/test_factories.py @@ -144,6 +144,24 @@ class TestFactoriesMixin(unittest.TestCase): self.assertRaises(ConfigurationError, get_bad_name) + def test_set_execution_policy(self): + from pyramid.interfaces import IExecutionPolicy + config = self._makeOne(autocommit=True) + def dummy_policy(environ, router): pass + config.set_execution_policy(dummy_policy) + registry = config.registry + result = registry.queryUtility(IExecutionPolicy) + self.assertEqual(result, dummy_policy) + + def test_set_execution_policy_to_None(self): + from pyramid.interfaces import IExecutionPolicy + from pyramid.router import default_execution_policy + config = self._makeOne(autocommit=True) + config.set_execution_policy(None) + registry = config.registry + result = registry.queryUtility(IExecutionPolicy) + self.assertEqual(result, default_execution_policy) + class TestDeprecatedFactoriesMixinMethods(unittest.TestCase): def setUp(self): from zope.deprecation import __show__ @@ -203,4 +221,3 @@ class TestDeprecatedFactoriesMixinMethods(unittest.TestCase): config.set_request_property(bar, name='bar') self.assertRaises(ConfigurationConflictError, config.commit) - diff --git a/pyramid/tests/test_integration.py b/pyramid/tests/test_integration.py index c2786c391..85c4466a4 100644 --- a/pyramid/tests/test_integration.py +++ b/pyramid/tests/test_integration.py @@ -610,7 +610,7 @@ class SubrequestAppTest(unittest.TestCase): def test_one(self): res = self.testapp.get('/view_one', status=200) - self.assertTrue(b'This came from view_two' in res.body) + self.assertTrue(b'This came from view_two, foo=bar' in res.body) def test_three(self): res = self.testapp.get('/view_three', status=500) diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py index 7aa42804c..a5da5c627 100644 --- a/pyramid/tests/test_router.py +++ b/pyramid/tests/test_router.py @@ -1271,6 +1271,19 @@ class TestRouter(unittest.TestCase): start_response = DummyStartResponse() self.assertRaises(PredicateMismatch, router, environ, start_response) + def test_custom_execution_policy(self): + from pyramid.interfaces import IExecutionPolicy + from pyramid.request import Request + from pyramid.response import Response + registry = self.config.registry + def dummy_policy(environ, router): + return Response(status=200, body=b'foo') + registry.registerUtility(dummy_policy, IExecutionPolicy) + router = self._makeOne() + resp = Request.blank('/').get_response(router) + self.assertEqual(resp.status_code, 200) + self.assertEqual(resp.body, b'foo') + class DummyPredicate(object): def __call__(self, info, request): return True -- cgit v1.2.3 From 0a0916f81e874dbd9c59fc806d5ff0685f1264b7 Mon Sep 17 00:00:00 2001 From: Bert JW Regeer Date: Sat, 25 Feb 2017 17:23:33 -0700 Subject: Add newline to make docs happy --- docs/glossary.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/glossary.rst b/docs/glossary.rst index 3a55a9f8a..0a46fac3b 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -1154,6 +1154,7 @@ Glossary coverage A measurement of code coverage, usually expressed as a percentage of which lines of code have been executed over which lines are executable, typically run during test execution. + execution policy A policy which wraps the :term:`router` by creating the request object and sending it through the request pipeline. -- cgit v1.2.3 From 1702daa1d312381a37b86ebf869cf4e1abe2c185 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20HUBSCHER?= Date: Mon, 27 Feb 2017 18:32:58 +0100 Subject: Rewrite test without mock. --- pyramid/tests/test_scripts/test_pserve.py | 37 ++++++++++++++++++++----------- setup.py | 1 - 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/pyramid/tests/test_scripts/test_pserve.py b/pyramid/tests/test_scripts/test_pserve.py index f681eaedb..18451df64 100644 --- a/pyramid/tests/test_scripts/test_pserve.py +++ b/pyramid/tests/test_scripts/test_pserve.py @@ -1,8 +1,8 @@ -import mock import os import unittest from pyramid.tests.test_scripts import dummy + here = os.path.abspath(os.path.dirname(__file__)) @@ -81,18 +81,29 @@ class TestPServeCommand(unittest.TestCase): ]) def test_reload_call_hupper_with_correct_args(self): - with mock.patch('pyramid.scripts.pserve.hupper') as hupper_mock: - hupper_mock.is_active.side_effect = [False, True] - inst = self._makeOne('--reload', 'development.ini') - inst.loadserver = mock.MagicMock() - inst.loadapp = mock.MagicMock() - inst.run() - hupper_mock.start_reloader.assert_called_with( - 'pyramid.scripts.pserve.main', - reload_interval=1, - verbose=1, - worker_kwargs={'argv': ['pserve', '--reload', 'development.ini'], - 'quiet': False}) + from pyramid.scripts import pserve + + class AttrDict(dict): + def __init__(self, *args, **kwargs): + super(AttrDict, self).__init__(*args, **kwargs) + self.__dict__ = self + + def dummy_start_reloader(*args, **kwargs): + dummy_start_reloader.args = args + dummy_start_reloader.kwargs = kwargs + + pserve.hupper = AttrDict(is_active=lambda: False, + start_reloader=dummy_start_reloader) + + inst = self._makeOne('--reload', 'development.ini') + inst.run() + + self.assertEquals(dummy_start_reloader.args, ('pyramid.scripts.pserve.main',)) + self.assertEquals(dummy_start_reloader.kwargs, { + 'reload_interval': 1, + 'verbose': 1, + 'worker_kwargs': {'argv': ['pserve', '--reload', 'development.ini'], + 'quiet': False}}) class Test_main(unittest.TestCase): diff --git a/setup.py b/setup.py index bd0e7f4d7..ab2170fec 100644 --- a/setup.py +++ b/setup.py @@ -65,7 +65,6 @@ docs_extras = [ ] testing_extras = tests_require + [ - 'mock', 'nose', 'coverage', 'virtualenv', # for scaffolding tests -- cgit v1.2.3 From 2cd38132e9d6b3506018ae892278d4b7da0d8119 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Tue, 28 Feb 2017 13:52:46 -0800 Subject: update pyramid-cookiecutter-starter prompts and reformat presentation of all cookiecutter prompts --- docs/narr/project.rst | 14 ++++++++++---- docs/quick_tour.rst | 24 ++++++++++++++++-------- docs/quick_tutorial/cookiecutters.rst | 13 ++++++++++--- docs/tutorials/modwsgi/index.rst | 15 +++++++++++++-- docs/tutorials/wiki/installation.rst | 9 +++++---- docs/tutorials/wiki2/installation.rst | 9 +++++---- 6 files changed, 59 insertions(+), 25 deletions(-) diff --git a/docs/narr/project.rst b/docs/narr/project.rst index f32fad370..525fdd501 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -89,10 +89,16 @@ On all platforms, generate a project using cookiecutter. If prompted for the first item, accept the default ``yes`` by hitting return. -#. ``You've cloned ~/.cookiecutters/pyramid-cookiecutter-starter before. Is it - okay to delete and re-clone it? [yes]:`` -#. ``project_name [Pyramid Scaffold]: myproject`` -#. ``repo_name [scaffold]: myproject`` +.. code-block:: text + + You've cloned ~/.cookiecutters/pyramid-cookiecutter-starter before. + Is it okay to delete and re-clone it? [yes]: yes + project_name [Pyramid Scaffold]: myproject + repo_name [scaffold]: myproject + Select template_language: + 1 - jinja2 + 2 - chameleon + Choose from 1, 2 [1]: 1 We then run through the following commands. diff --git a/docs/quick_tour.rst b/docs/quick_tour.rst index 053846276..fa9dfabad 100644 --- a/docs/quick_tour.rst +++ b/docs/quick_tour.rst @@ -514,10 +514,16 @@ Let's use the cookiecutter ``pyramid-cookiecutter-starter`` to create a starter If prompted for the first item, accept the default ``yes`` by hitting return. -#. ``You've cloned ~/.cookiecutters/pyramid-cookiecutter-starter before. Is it - okay to delete and re-clone it? [yes]:`` -#. ``project_name [Pyramid Scaffold]: hello_world`` -#. ``repo_name [scaffold]: hello_world`` +.. code-block:: text + + You've cloned ~/.cookiecutters/pyramid-cookiecutter-starter before. + Is it okay to delete and re-clone it? [yes]: yes + project_name [Pyramid Scaffold]: hello_world + repo_name [scaffold]: hello_world + Select template_language: + 1 - jinja2 + 2 - chameleon + Choose from 1, 2 [1]: 1 We then run through the following commands. @@ -863,10 +869,12 @@ Pyramid and SQLAlchemy are great friends. That friendship includes a cookiecutte If prompted for the first item, accept the default ``yes`` by hitting return. -#. ``You've cloned ~/.cookiecutters/pyramid-cookiecutter-alchemy before. Is it - okay to delete and re-clone it? [yes]:`` -#. ``project_name [Pyramid Scaffold]: sqla_demo`` -#. ``repo_name [scaffold]: sqla_demo`` +.. code-block:: text + + You've cloned ~/.cookiecutters/pyramid-cookiecutter-alchemy before. + Is it okay to delete and re-clone it? [yes]: yes + project_name [Pyramid Scaffold]: sqla_demo + repo_name [scaffold]: sqla_demo We then run through the following commands as before. diff --git a/docs/quick_tutorial/cookiecutters.rst b/docs/quick_tutorial/cookiecutters.rst index 8e7048f78..f7251618f 100644 --- a/docs/quick_tutorial/cookiecutters.rst +++ b/docs/quick_tutorial/cookiecutters.rst @@ -32,9 +32,16 @@ Steps If prompted for the first item, accept the default ``yes`` by hitting return. - #. ``You've cloned ~/.cookiecutters/pyramid-cookiecutter-starter before. Is it okay to delete and re-clone it? [yes]:`` - #. ``project_name [Pyramid Scaffold]: cc_starter`` - #. ``repo_name [scaffold]: cc_starter`` + .. code-block:: text + + You've cloned ~/.cookiecutters/pyramid-cookiecutter-starter before. + Is it okay to delete and re-clone it? [yes]: yes + project_name [Pyramid Scaffold]: cc_starter + repo_name [scaffold]: cc_starter + Select template_language: + 1 - jinja2 + 2 - chameleon + Choose from 1, 2 [1]: 1 #. We then run through the following commands. diff --git a/docs/tutorials/modwsgi/index.rst b/docs/tutorials/modwsgi/index.rst index 0c3b58bac..44e892a27 100644 --- a/docs/tutorials/modwsgi/index.rst +++ b/docs/tutorials/modwsgi/index.rst @@ -40,8 +40,19 @@ specific path information for commands and files. $ cd ~ $ cookiecutter https://github.com/Pylons/pyramid-cookiecutter-starter - project_name [Pyramid Scaffold]: myproject - repo_name [scaffold]: myproject + + If prompted for the first item, accept the default ``yes`` by hitting return. + + .. code-block:: text + + You've cloned ~/.cookiecutters/pyramid-cookiecutter-starter before. + Is it okay to delete and re-clone it? [yes]: yes + project_name [Pyramid Scaffold]: myproject + repo_name [scaffold]: myproject + Select template_language: + 1 - jinja2 + 2 - chameleon + Choose from 1, 2 [1]: 1 #. Create a :term:`virtual environment` which we'll use to install our application. It is important to use the same base Python interpreter diff --git a/docs/tutorials/wiki/installation.rst b/docs/tutorials/wiki/installation.rst index c735bdf9d..6be826395 100644 --- a/docs/tutorials/wiki/installation.rst +++ b/docs/tutorials/wiki/installation.rst @@ -45,11 +45,12 @@ On all operating systems ^^^^^^^^^^^^^^^^^^^^^^^^ If prompted for the first item, accept the default ``yes`` by hitting return. -#. ``You've cloned ~/.cookiecutters/pyramid-cookiecutter-zodb before. Is it - okay to delete and re-clone it? [yes]:`` -#. ``project_name [Pyramid Scaffold]: myproj`` -#. ``repo_name [scaffold]: tutorial`` +.. code-block:: text + You've cloned ~/.cookiecutters/pyramid-cookiecutter-zodb before. + Is it okay to delete and re-clone it? [yes]: yes + project_name [Pyramid Scaffold]: myproj + repo_name [scaffold]: tutorial Change directory into your newly created project ------------------------------------------------ diff --git a/docs/tutorials/wiki2/installation.rst b/docs/tutorials/wiki2/installation.rst index fd323fcfc..9eeb1711d 100644 --- a/docs/tutorials/wiki2/installation.rst +++ b/docs/tutorials/wiki2/installation.rst @@ -57,11 +57,12 @@ On all operating systems ^^^^^^^^^^^^^^^^^^^^^^^^ If prompted for the first item, accept the default ``yes`` by hitting return. -#. ``You've cloned ~/.cookiecutters/pyramid-cookiecutter-alchemy before. Is it - okay to delete and re-clone it? [yes]:`` -#. ``project_name [Pyramid Scaffold]: myproj`` -#. ``repo_name [scaffold]: tutorial`` +.. code-block:: text + You've cloned ~/.cookiecutters/pyramid-cookiecutter-alchemy before. + Is it okay to delete and re-clone it? [yes]: yes + project_name [Pyramid Scaffold]: myproj + repo_name [scaffold]: tutorial Change directory into your newly created project ------------------------------------------------ -- cgit v1.2.3 From 4c39718d8b5461f73b8520789a652874333f7c69 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Tue, 28 Feb 2017 20:40:11 -0600 Subject: add changelog for #2964 --- CHANGES.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 59a733bcd..0ae44e620 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -4,6 +4,12 @@ unreleased Features -------- +- Added an execution policy hook to the request pipeline. An execution + policy has the ability to control creation and execution of the request + objects before they enter rest of the pipeline. This means for a given + request that the policy may create more than one request for retry + purposes. See https://github.com/Pylons/pyramid/pull/2964 + Bug Fixes --------- -- cgit v1.2.3 From 495bc619668d9960118a48b2b77371df8cdc798d Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Tue, 28 Feb 2017 23:03:45 -0600 Subject: cache pip wheels in travis builds --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index b0e63ba97..ffc6caa72 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,6 +31,10 @@ install: script: - travis_retry tox +cache: + directories: + - $HOME/.cache/pip + notifications: email: - pyramid-checkins@lists.repoze.org -- cgit v1.2.3 From 0393f96a8ef8a9336f35816f9be6898e99014b94 Mon Sep 17 00:00:00 2001 From: Kirill Kuzminykh Date: Wed, 1 Mar 2017 11:52:25 +0300 Subject: Fixed several reference cycles to prevent memory leaks. Added simple test for detect memory leaks after application closing. --- pyramid/config/views.py | 2 +- pyramid/tests/test_config/test_views.py | 4 +++- pyramid/tests/test_integration.py | 26 ++++++++++++++++++++++++++ pyramid/tests/test_tweens.py | 4 ++++ pyramid/tweens.py | 2 +- pyramid/util.py | 9 ++++++--- 6 files changed, 41 insertions(+), 6 deletions(-) diff --git a/pyramid/config/views.py b/pyramid/config/views.py index 65c9da585..b6996b6d2 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -933,7 +933,7 @@ class ViewsConfiguratorMixin(object): if not exception_only and isexc: derived_view = runtime_exc_view(derived_view, derived_exc_view) - derived_view.__discriminator__ = lambda *arg: discriminator + derived_view.__discriminator__ = lambda *arg: view_intr.discriminator # __discriminator__ is used by superdynamic systems # that require it for introspection after manual view lookup; # see also MultiView.__discriminator__ diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py index 211632730..79631c6b8 100644 --- a/pyramid/tests/test_config/test_views.py +++ b/pyramid/tests/test_config/test_views.py @@ -13,6 +13,8 @@ from pyramid.compat import ( from pyramid.exceptions import ConfigurationError from pyramid.exceptions import ConfigurationExecutionError from pyramid.exceptions import ConfigurationConflictError +from pyramid.registry import undefer + class TestViewsConfigurationMixin(unittest.TestCase): def _makeOne(self, *arg, **kw): @@ -148,7 +150,7 @@ class TestViewsConfigurationMixin(unittest.TestCase): self.assertEqual(wrapper.__module__, view.__module__) self.assertEqual(wrapper.__name__, view.__name__) self.assertEqual(wrapper.__doc__, view.__doc__) - self.assertEqual(wrapper.__discriminator__(None, None).resolve()[0], + self.assertEqual(undefer(wrapper.__discriminator__(None, None))[0], 'view') def test_add_view_view_callable_dottedname(self): diff --git a/pyramid/tests/test_integration.py b/pyramid/tests/test_integration.py index 85c4466a4..09ea85e13 100644 --- a/pyramid/tests/test_integration.py +++ b/pyramid/tests/test_integration.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import datetime +import gc import locale import os import unittest @@ -741,3 +742,28 @@ def _assertBody(body, filename): data = data.replace(b'\r', b'') data = data.replace(b'\n', b'') assert(body == data) + + +class MemoryLeaksTest(unittest.TestCase): + + def tearDown(self): + import pyramid.config + pyramid.config.global_registries.empty() + + def get_gc_count(self): + last_collected = 0 + while True: + collected = gc.collect() + if collected == last_collected: + break + last_collected = collected + return len(gc.get_objects()) + + def test_memory_leaks(self): + from pyramid.config import Configurator + Configurator().make_wsgi_app() # Initialize all global objects + + initial_count = self.get_gc_count() + Configurator().make_wsgi_app() + current_count = self.get_gc_count() + self.assertEqual(current_count, initial_count) diff --git a/pyramid/tests/test_tweens.py b/pyramid/tests/test_tweens.py index c8eada34c..d2053e6b8 100644 --- a/pyramid/tests/test_tweens.py +++ b/pyramid/tests/test_tweens.py @@ -31,6 +31,7 @@ class Test_excview_tween_factory(unittest.TestCase): raise HTTPNotFound tween = self._makeOne(handler) request = Request.blank('/') + request.registry = self.config.registry result = tween(request) self.assertEqual(result.status, '404 Not Found') @@ -44,6 +45,7 @@ class Test_excview_tween_factory(unittest.TestCase): raise ValueError tween = self._makeOne(handler) request = Request.blank('/') + request.registry = self.config.registry result = tween(request) self.assertTrue(b'foo' in result.body) @@ -55,6 +57,7 @@ class Test_excview_tween_factory(unittest.TestCase): raise ValueError tween = self._makeOne(handler) request = Request.blank('/') + request.registry = self.config.registry request.method = 'POST' self.assertRaises(ValueError, lambda: tween(request)) @@ -64,6 +67,7 @@ class Test_excview_tween_factory(unittest.TestCase): raise ValueError tween = self._makeOne(handler) request = Request.blank('/') + request.registry = self.config.registry self.assertRaises(ValueError, lambda: tween(request)) class DummyRequest: diff --git a/pyramid/tweens.py b/pyramid/tweens.py index a842b1133..19daf9e5e 100644 --- a/pyramid/tweens.py +++ b/pyramid/tweens.py @@ -42,7 +42,7 @@ def excview_tween_factory(handler, registry): provides = providedBy(exc) try: response = _call_view( - registry, + request.registry, request, exc, provides, diff --git a/pyramid/util.py b/pyramid/util.py index 3337d410d..2827884a3 100644 --- a/pyramid/util.py +++ b/pyramid/util.py @@ -231,17 +231,20 @@ class WeakOrderedSet(object): self._order.remove(oid) self._order.append(oid) return - ref = weakref.ref(item, lambda x: self.remove(item)) + ref = weakref.ref(item, lambda x: self._remove_by_id(oid)) self._items[oid] = ref self._order.append(oid) - def remove(self, item): + def _remove_by_id(self, oid): """ Remove an item from the set.""" - oid = id(item) if oid in self._items: del self._items[oid] self._order.remove(oid) + def remove(self, item): + """ Remove an item from the set.""" + self._remove_by_id(id(item)) + def empty(self): """ Clear all objects from the set.""" self._items = {} -- cgit v1.2.3 From 5f012c616d8b593c3bf310e8115563f4e4c04971 Mon Sep 17 00:00:00 2001 From: Kirill Kuzminykh Date: Wed, 1 Mar 2017 12:00:21 +0300 Subject: Added a new contributor into the CONTRIBUTORS.txt --- CONTRIBUTORS.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index d5c178418..566e91195 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -292,3 +292,5 @@ Contributors - Mikko Ohtamaa, 2016/12/6 - Martin Frlin, 2016/12/7 + +- Kirill Kuzminykh, 2017/03/01 -- cgit v1.2.3 From b1cad5ee8c8bdf8dd5819e77366c36869d53edbb Mon Sep 17 00:00:00 2001 From: Kirill Kuzminykh Date: Wed, 1 Mar 2017 13:00:10 +0300 Subject: The memory leaks test skipped for platform 'pypy'. --- pyramid/tests/test_integration.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyramid/tests/test_integration.py b/pyramid/tests/test_integration.py index 09ea85e13..f23e54609 100644 --- a/pyramid/tests/test_integration.py +++ b/pyramid/tests/test_integration.py @@ -9,6 +9,7 @@ import unittest from pyramid.wsgi import wsgiapp from pyramid.view import view_config from pyramid.static import static_view +from pyramid.testing import skip_on from pyramid.compat import ( text_, url_quote, @@ -759,6 +760,7 @@ class MemoryLeaksTest(unittest.TestCase): last_collected = collected return len(gc.get_objects()) + @skip_on('pypy') def test_memory_leaks(self): from pyramid.config import Configurator Configurator().make_wsgi_app() # Initialize all global objects -- cgit v1.2.3 From 3ec0fca8285847c856e24bb052062480952fca73 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Wed, 1 Mar 2017 03:00:58 -0800 Subject: use correct directory name for cookiecutter generated README.txt (cherry picked from commit 40dd034) Refs: https://github.com/Pylons/pyramid-cookiecutter-starter/pull/22 --- docs/tutorials/wiki/src/authorization/README.txt | 2 +- docs/tutorials/wiki/src/basiclayout/README.txt | 2 +- docs/tutorials/wiki/src/installation/README.txt | 2 +- docs/tutorials/wiki/src/models/README.txt | 2 +- docs/tutorials/wiki/src/tests/README.txt | 2 +- docs/tutorials/wiki/src/views/README.txt | 2 +- docs/tutorials/wiki2/src/authentication/README.txt | 2 +- docs/tutorials/wiki2/src/authorization/README.txt | 2 +- docs/tutorials/wiki2/src/basiclayout/README.txt | 2 +- docs/tutorials/wiki2/src/installation/README.txt | 2 +- docs/tutorials/wiki2/src/models/README.txt | 2 +- docs/tutorials/wiki2/src/tests/README.txt | 2 +- docs/tutorials/wiki2/src/views/README.txt | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/tutorials/wiki/src/authorization/README.txt b/docs/tutorials/wiki/src/authorization/README.txt index bd67221cc..98683bf8c 100644 --- a/docs/tutorials/wiki/src/authorization/README.txt +++ b/docs/tutorials/wiki/src/authorization/README.txt @@ -6,7 +6,7 @@ Getting Started - Change directory into your newly created project. - cd myproj + cd tutorial - Create a Python virtual environment. diff --git a/docs/tutorials/wiki/src/basiclayout/README.txt b/docs/tutorials/wiki/src/basiclayout/README.txt index bd67221cc..98683bf8c 100644 --- a/docs/tutorials/wiki/src/basiclayout/README.txt +++ b/docs/tutorials/wiki/src/basiclayout/README.txt @@ -6,7 +6,7 @@ Getting Started - Change directory into your newly created project. - cd myproj + cd tutorial - Create a Python virtual environment. diff --git a/docs/tutorials/wiki/src/installation/README.txt b/docs/tutorials/wiki/src/installation/README.txt index bd67221cc..98683bf8c 100644 --- a/docs/tutorials/wiki/src/installation/README.txt +++ b/docs/tutorials/wiki/src/installation/README.txt @@ -6,7 +6,7 @@ Getting Started - Change directory into your newly created project. - cd myproj + cd tutorial - Create a Python virtual environment. diff --git a/docs/tutorials/wiki/src/models/README.txt b/docs/tutorials/wiki/src/models/README.txt index bd67221cc..98683bf8c 100644 --- a/docs/tutorials/wiki/src/models/README.txt +++ b/docs/tutorials/wiki/src/models/README.txt @@ -6,7 +6,7 @@ Getting Started - Change directory into your newly created project. - cd myproj + cd tutorial - Create a Python virtual environment. diff --git a/docs/tutorials/wiki/src/tests/README.txt b/docs/tutorials/wiki/src/tests/README.txt index bd67221cc..98683bf8c 100644 --- a/docs/tutorials/wiki/src/tests/README.txt +++ b/docs/tutorials/wiki/src/tests/README.txt @@ -6,7 +6,7 @@ Getting Started - Change directory into your newly created project. - cd myproj + cd tutorial - Create a Python virtual environment. diff --git a/docs/tutorials/wiki/src/views/README.txt b/docs/tutorials/wiki/src/views/README.txt index bd67221cc..98683bf8c 100644 --- a/docs/tutorials/wiki/src/views/README.txt +++ b/docs/tutorials/wiki/src/views/README.txt @@ -6,7 +6,7 @@ Getting Started - Change directory into your newly created project. - cd myproj + cd tutorial - Create a Python virtual environment. diff --git a/docs/tutorials/wiki2/src/authentication/README.txt b/docs/tutorials/wiki2/src/authentication/README.txt index 8466fd7b5..5e21b8aa4 100644 --- a/docs/tutorials/wiki2/src/authentication/README.txt +++ b/docs/tutorials/wiki2/src/authentication/README.txt @@ -6,7 +6,7 @@ Getting Started - Change directory into your newly created project. - cd myproj + cd tutorial - Create a Python virtual environment. diff --git a/docs/tutorials/wiki2/src/authorization/README.txt b/docs/tutorials/wiki2/src/authorization/README.txt index 8466fd7b5..5e21b8aa4 100644 --- a/docs/tutorials/wiki2/src/authorization/README.txt +++ b/docs/tutorials/wiki2/src/authorization/README.txt @@ -6,7 +6,7 @@ Getting Started - Change directory into your newly created project. - cd myproj + cd tutorial - Create a Python virtual environment. diff --git a/docs/tutorials/wiki2/src/basiclayout/README.txt b/docs/tutorials/wiki2/src/basiclayout/README.txt index 8466fd7b5..5e21b8aa4 100644 --- a/docs/tutorials/wiki2/src/basiclayout/README.txt +++ b/docs/tutorials/wiki2/src/basiclayout/README.txt @@ -6,7 +6,7 @@ Getting Started - Change directory into your newly created project. - cd myproj + cd tutorial - Create a Python virtual environment. diff --git a/docs/tutorials/wiki2/src/installation/README.txt b/docs/tutorials/wiki2/src/installation/README.txt index 8466fd7b5..5e21b8aa4 100644 --- a/docs/tutorials/wiki2/src/installation/README.txt +++ b/docs/tutorials/wiki2/src/installation/README.txt @@ -6,7 +6,7 @@ Getting Started - Change directory into your newly created project. - cd myproj + cd tutorial - Create a Python virtual environment. diff --git a/docs/tutorials/wiki2/src/models/README.txt b/docs/tutorials/wiki2/src/models/README.txt index 8466fd7b5..5e21b8aa4 100644 --- a/docs/tutorials/wiki2/src/models/README.txt +++ b/docs/tutorials/wiki2/src/models/README.txt @@ -6,7 +6,7 @@ Getting Started - Change directory into your newly created project. - cd myproj + cd tutorial - Create a Python virtual environment. diff --git a/docs/tutorials/wiki2/src/tests/README.txt b/docs/tutorials/wiki2/src/tests/README.txt index 8466fd7b5..5e21b8aa4 100644 --- a/docs/tutorials/wiki2/src/tests/README.txt +++ b/docs/tutorials/wiki2/src/tests/README.txt @@ -6,7 +6,7 @@ Getting Started - Change directory into your newly created project. - cd myproj + cd tutorial - Create a Python virtual environment. diff --git a/docs/tutorials/wiki2/src/views/README.txt b/docs/tutorials/wiki2/src/views/README.txt index 8466fd7b5..5e21b8aa4 100644 --- a/docs/tutorials/wiki2/src/views/README.txt +++ b/docs/tutorials/wiki2/src/views/README.txt @@ -6,7 +6,7 @@ Getting Started - Change directory into your newly created project. - cd myproj + cd tutorial - Create a Python virtual environment. -- cgit v1.2.3 From 71e92df9249274796224ae6d72ca83f0bca942d7 Mon Sep 17 00:00:00 2001 From: Kirill Kuzminykh Date: Thu, 2 Mar 2017 22:40:36 +0300 Subject: Reverted couple useless fixes of memory leaks. --- pyramid/config/views.py | 2 +- pyramid/registry.py | 4 +++- pyramid/tests/test_tweens.py | 4 ---- pyramid/tweens.py | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/pyramid/config/views.py b/pyramid/config/views.py index b6996b6d2..65c9da585 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -933,7 +933,7 @@ class ViewsConfiguratorMixin(object): if not exception_only and isexc: derived_view = runtime_exc_view(derived_view, derived_exc_view) - derived_view.__discriminator__ = lambda *arg: view_intr.discriminator + derived_view.__discriminator__ = lambda *arg: discriminator # __discriminator__ is used by superdynamic systems # that require it for introspection after manual view lookup; # see also MultiView.__discriminator__ diff --git a/pyramid/registry.py b/pyramid/registry.py index 20b3643e9..7589dfcac 100644 --- a/pyramid/registry.py +++ b/pyramid/registry.py @@ -276,7 +276,9 @@ class Deferred(object): @reify def value(self): - return self.func() + result = self.func() + del self.func + return result def resolve(self): return self.value diff --git a/pyramid/tests/test_tweens.py b/pyramid/tests/test_tweens.py index d2053e6b8..c8eada34c 100644 --- a/pyramid/tests/test_tweens.py +++ b/pyramid/tests/test_tweens.py @@ -31,7 +31,6 @@ class Test_excview_tween_factory(unittest.TestCase): raise HTTPNotFound tween = self._makeOne(handler) request = Request.blank('/') - request.registry = self.config.registry result = tween(request) self.assertEqual(result.status, '404 Not Found') @@ -45,7 +44,6 @@ class Test_excview_tween_factory(unittest.TestCase): raise ValueError tween = self._makeOne(handler) request = Request.blank('/') - request.registry = self.config.registry result = tween(request) self.assertTrue(b'foo' in result.body) @@ -57,7 +55,6 @@ class Test_excview_tween_factory(unittest.TestCase): raise ValueError tween = self._makeOne(handler) request = Request.blank('/') - request.registry = self.config.registry request.method = 'POST' self.assertRaises(ValueError, lambda: tween(request)) @@ -67,7 +64,6 @@ class Test_excview_tween_factory(unittest.TestCase): raise ValueError tween = self._makeOne(handler) request = Request.blank('/') - request.registry = self.config.registry self.assertRaises(ValueError, lambda: tween(request)) class DummyRequest: diff --git a/pyramid/tweens.py b/pyramid/tweens.py index 19daf9e5e..a842b1133 100644 --- a/pyramid/tweens.py +++ b/pyramid/tweens.py @@ -42,7 +42,7 @@ def excview_tween_factory(handler, registry): provides = providedBy(exc) try: response = _call_view( - request.registry, + registry, request, exc, provides, -- cgit v1.2.3 From 870a1d1f957d772cab0eb60bc1d1928da2ebb992 Mon Sep 17 00:00:00 2001 From: Kirill Kuzminykh Date: Thu, 2 Mar 2017 22:43:05 +0300 Subject: Reverted useless changes in tests. --- pyramid/tests/test_config/test_views.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py index 79631c6b8..211632730 100644 --- a/pyramid/tests/test_config/test_views.py +++ b/pyramid/tests/test_config/test_views.py @@ -13,8 +13,6 @@ from pyramid.compat import ( from pyramid.exceptions import ConfigurationError from pyramid.exceptions import ConfigurationExecutionError from pyramid.exceptions import ConfigurationConflictError -from pyramid.registry import undefer - class TestViewsConfigurationMixin(unittest.TestCase): def _makeOne(self, *arg, **kw): @@ -150,7 +148,7 @@ class TestViewsConfigurationMixin(unittest.TestCase): self.assertEqual(wrapper.__module__, view.__module__) self.assertEqual(wrapper.__name__, view.__name__) self.assertEqual(wrapper.__doc__, view.__doc__) - self.assertEqual(undefer(wrapper.__discriminator__(None, None))[0], + self.assertEqual(wrapper.__discriminator__(None, None).resolve()[0], 'view') def test_add_view_view_callable_dottedname(self): -- cgit v1.2.3 From 2798f253a907310643b1d2f8a7935c09d9582d49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20HUBSCHER?= Date: Fri, 3 Mar 2017 19:19:08 +0100 Subject: @mmerickel review. --- pyramid/tests/test_scripts/test_pserve.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/pyramid/tests/test_scripts/test_pserve.py b/pyramid/tests/test_scripts/test_pserve.py index 18451df64..8eb63b8d6 100644 --- a/pyramid/tests/test_scripts/test_pserve.py +++ b/pyramid/tests/test_scripts/test_pserve.py @@ -92,11 +92,15 @@ class TestPServeCommand(unittest.TestCase): dummy_start_reloader.args = args dummy_start_reloader.kwargs = kwargs - pserve.hupper = AttrDict(is_active=lambda: False, - start_reloader=dummy_start_reloader) - - inst = self._makeOne('--reload', 'development.ini') - inst.run() + orig_hupper = pserve.hupper + try: + pserve.hupper = AttrDict(is_active=lambda: False, + start_reloader=dummy_start_reloader) + + inst = self._makeOne('--reload', 'development.ini') + inst.run() + finally: + pserve.hupper = orig_hupper self.assertEquals(dummy_start_reloader.args, ('pyramid.scripts.pserve.main',)) self.assertEquals(dummy_start_reloader.kwargs, { -- cgit v1.2.3 From 1691556eaa4ea90150ac8639ef26707a27216b32 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Fri, 3 Mar 2017 19:36:41 -0600 Subject: changelog for #2967 --- CHANGES.txt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 0ae44e620..9c9acf3d0 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -15,7 +15,13 @@ Bug Fixes - HTTPException's accepts a detail kwarg that may be used to pass additional details to the exception. You may now pass objects so long as they have a - valid __str__ method. See https://github.com/Pylons/pyramid/pull/2951 + valid __str__ method. See https://github.com/Pylons/pyramid/pull/2951 + +- Fix a reference cycle causing memory leaks in which the registry + would keep a ``Configurator`` instance alive even after the configurator + was discarded. Another fix was also added for the ``global_registries`` + object in which the registry was stored in a closure preventing it from + being deallocated. See https://github.com/Pylons/pyramid/pull/2967 Deprecations ------------ -- cgit v1.2.3 From 38294e6dcabab1df45655949d4075c23b706bc2b Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sun, 5 Mar 2017 20:59:51 -0600 Subject: add changelog for #2962 --- CHANGES.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 9c9acf3d0..9056320c5 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -23,6 +23,11 @@ Bug Fixes object in which the registry was stored in a closure preventing it from being deallocated. See https://github.com/Pylons/pyramid/pull/2967 +- Fix a bug directly invoking ``pyramid.scripts.pserve.main`` with the + ``--reload`` option in which ``sys.argv`` is always used in the subprocess + instead of the supplied ``argv``. + See https://github.com/Pylons/pyramid/pull/2962 + Deprecations ------------ -- cgit v1.2.3 From cc0aeb84c897be5e77d32d1ba834fb8b4577ae29 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Fri, 10 Mar 2017 02:07:37 -0800 Subject: add Chameleon as option to pyramid-cookiecutter-starter --- docs/narr/project.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/narr/project.rst b/docs/narr/project.rst index 525fdd501..0f7cb4edf 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -52,7 +52,7 @@ Pyramid cookiecutters released under the Pylons Project differ from each other o These cookiecutters include: ``pyramid-cookiecutter-starter`` - :term:`URL dispatch` for routing and :term:`Jinja2` for templating + :term:`URL dispatch` for routing and either :term:`Jinja2` or :term:`Chameleon` for templating ``pyramid-cookiecutter-alchemy`` SQLite for persistent storage, :term:`SQLAlchemy` for an ORM, :term:`URL dispatch` for routing, and :term:`Jinja2` for templating. -- cgit v1.2.3 From c87f611352d8e0956dbb2b113b36ffda7590a34d Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Fri, 10 Mar 2017 04:01:09 -0800 Subject: add wikipedia to releasing --- RELEASING.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RELEASING.txt b/RELEASING.txt index c7a23309b..b9e5f4a6c 100644 --- a/RELEASING.txt +++ b/RELEASING.txt @@ -116,6 +116,8 @@ Marketing and communications - Edit `http://wiki.python.org/moin/WebFrameworks `_. +- Edit `https://en.wikipedia.org/wiki/Pylons_project `_. + - Announce to Twitter. ``` -- cgit v1.2.3 From 6c38f2b732cf2806bbd28efe77491ee0fe157855 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 12 Mar 2017 10:33:24 -0700 Subject: update twitter handle --- pyramid/scaffolds/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyramid/scaffolds/__init__.py b/pyramid/scaffolds/__init__.py index 719b4fe76..e06c5a930 100644 --- a/pyramid/scaffolds/__init__.py +++ b/pyramid/scaffolds/__init__.py @@ -37,7 +37,7 @@ class PyramidTemplate(Template): %(separator)s Tutorials: http://docs.pylonsproject.org/projects/pyramid_tutorials/en/latest/ Documentation: http://docs.pylonsproject.org/projects/pyramid/en/latest/ - Twitter: https://twitter.com/trypyramid + Twitter: https://twitter.com/PylonsProject Mailing List: https://groups.google.com/forum/#!forum/pylons-discuss Welcome to Pyramid. Sorry for the convenience. -- cgit v1.2.3 From 6204d8de5484bf7fa26dd41fd32ee3fd9a1047db Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Tue, 14 Mar 2017 02:56:00 -0700 Subject: add Mako to options for pyramid-cookiecutter-starter --- docs/narr/project.rst | 7 ++++--- docs/quick_tour.rst | 3 ++- docs/quick_tutorial/cookiecutters.rst | 3 ++- docs/tutorials/modwsgi/index.rst | 3 ++- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/narr/project.rst b/docs/narr/project.rst index 0f7cb4edf..ce7e90793 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -43,7 +43,7 @@ Pyramid cookiecutters released under the Pylons Project differ from each other o - the mechanism they use to map URLs to code (:term:`URL dispatch` or :term:`traversal`) -- templating libraries (:term:`Jinja2` or :term:`Chameleon`) +- templating libraries (:term:`Jinja2`, :term:`Chameleon`, or :term:`Mako`) * `pyramid-cookiecutter-starter `_ * `pyramid-cookiecutter-alchemy `_ @@ -52,7 +52,7 @@ Pyramid cookiecutters released under the Pylons Project differ from each other o These cookiecutters include: ``pyramid-cookiecutter-starter`` - :term:`URL dispatch` for routing and either :term:`Jinja2` or :term:`Chameleon` for templating + :term:`URL dispatch` for routing and either :term:`Jinja2`, :term:`Chameleon`, or :term:`Mako` for templating ``pyramid-cookiecutter-alchemy`` SQLite for persistent storage, :term:`SQLAlchemy` for an ORM, :term:`URL dispatch` for routing, and :term:`Jinja2` for templating. @@ -98,7 +98,8 @@ If prompted for the first item, accept the default ``yes`` by hitting return. Select template_language: 1 - jinja2 2 - chameleon - Choose from 1, 2 [1]: 1 + 3 - mako + Choose from 1, 2, 3 [1]: 1 We then run through the following commands. diff --git a/docs/quick_tour.rst b/docs/quick_tour.rst index fa9dfabad..02c3ff811 100644 --- a/docs/quick_tour.rst +++ b/docs/quick_tour.rst @@ -523,7 +523,8 @@ If prompted for the first item, accept the default ``yes`` by hitting return. Select template_language: 1 - jinja2 2 - chameleon - Choose from 1, 2 [1]: 1 + 3 - mako + Choose from 1, 2, 3 [1]: 1 We then run through the following commands. diff --git a/docs/quick_tutorial/cookiecutters.rst b/docs/quick_tutorial/cookiecutters.rst index f7251618f..edfd8cd69 100644 --- a/docs/quick_tutorial/cookiecutters.rst +++ b/docs/quick_tutorial/cookiecutters.rst @@ -41,7 +41,8 @@ Steps Select template_language: 1 - jinja2 2 - chameleon - Choose from 1, 2 [1]: 1 + 3 - mako + Choose from 1, 2, 3 [1]: 1 #. We then run through the following commands. diff --git a/docs/tutorials/modwsgi/index.rst b/docs/tutorials/modwsgi/index.rst index 44e892a27..690266586 100644 --- a/docs/tutorials/modwsgi/index.rst +++ b/docs/tutorials/modwsgi/index.rst @@ -52,7 +52,8 @@ specific path information for commands and files. Select template_language: 1 - jinja2 2 - chameleon - Choose from 1, 2 [1]: 1 + 3 - mako + Choose from 1, 2, 3 [1]: 1 #. Create a :term:`virtual environment` which we'll use to install our application. It is important to use the same base Python interpreter -- cgit v1.2.3 From 8b96cfeedd5aee88cfb1a744d480130853526a87 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Tue, 28 Mar 2017 01:10:36 -0500 Subject: add python_requires metadata --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index ab2170fec..29de8dcdf 100644 --- a/setup.py +++ b/setup.py @@ -98,6 +98,7 @@ setup(name='pyramid', packages=find_packages(), include_package_data=True, zip_safe=False, + python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*', install_requires=install_requires, extras_require={ 'testing': testing_extras, -- cgit v1.2.3 From 441503653073f465c0140f6f78f079526cf8efc2 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Tue, 28 Mar 2017 01:18:21 -0500 Subject: depend on python_requires in pip 9+ to check runtime versions - The python_requires checks work when installing a wheel as well, which these checks did not affect. --- setup.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/setup.py b/setup.py index 29de8dcdf..ff3e38874 100644 --- a/setup.py +++ b/setup.py @@ -13,21 +13,9 @@ ############################################################################## import os -import sys -import warnings from setuptools import setup, find_packages -py_version = sys.version_info[:2] - -if (3, 0) <= py_version < (3, 4): - warnings.warn( - 'On Python 3, Pyramid only supports Python 3.4 or better', - UserWarning, - ) -elif py_version < (2, 7): - raise RuntimeError('On Python 2, Pyramid requires Python 2.7 or better') - here = os.path.abspath(os.path.dirname(__file__)) try: with open(os.path.join(here, 'README.rst')) as f: -- cgit v1.2.3 From 50cebd7dea8a11188c383200f81e63a0b8b377ae Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Tue, 28 Mar 2017 02:19:47 -0500 Subject: require "open_url" setting in order to know what browser to open Parsing the port from the server section could be brought back but it would be a fallback that depends on finding a "port" variable in the [server:server_name] section of the config. --- pyramid/scripts/pserve.py | 49 ++++++++++++++++++------------- pyramid/tests/test_scripts/test_pserve.py | 4 +-- 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/pyramid/scripts/pserve.py b/pyramid/scripts/pserve.py index b89f9b982..caa73704a 100644 --- a/pyramid/scripts/pserve.py +++ b/pyramid/scripts/pserve.py @@ -22,10 +22,6 @@ from paste.deploy import ( loadapp, loadserver, ) -from paste.deploy.loadwsgi import ( - SERVER, - loadcontext, -) from pyramid.compat import PY2 from pyramid.compat import configparser @@ -87,7 +83,9 @@ class PServeCommand(object): '-b', '--browser', dest='browser', action='store_true', - help="Open a web browser to server url") + help=("Open a web browser to the server url. The server url is " + "determined from the 'open_url' setting in the 'pserve' " + "section of the configuration file.")) parser.add_argument( '-v', '--verbose', default=default_verbosity, @@ -119,6 +117,8 @@ class PServeCommand(object): loadapp = staticmethod(loadapp) # testing loadserver = staticmethod(loadserver) # testing + open_url = None + _scheme_re = re.compile(r'^[a-z][a-z]+:', re.I) def __init__(self, argv, quiet=False): @@ -127,7 +127,7 @@ class PServeCommand(object): self.args.verbose = 0 if self.args.reload: self.worker_kwargs = {'argv': argv, "quiet": quiet} - self.watch_files = [] + self.watch_files = set() def out(self, msg): # pragma: no cover if self.args.verbose > 0: @@ -150,7 +150,7 @@ class PServeCommand(object): try: items = dict(config.items('pserve')) except configparser.NoSectionError: - return + items = {} watch_files = aslist(items.get('watch_files', ''), flatten=False) @@ -161,7 +161,11 @@ class PServeCommand(object): file = resolver.resolve(file).abspath() elif not os.path.isabs(file): file = os.path.join(here, file) - self.watch_files.append(os.path.abspath(file)) + self.watch_files.add(os.path.abspath(file)) + + open_url = items.get('open_url') + if open_url: + self.open_url = open_url def run(self): # pragma: no cover if not self.args.config_uri: @@ -188,16 +192,21 @@ class PServeCommand(object): # do not open the browser on each reload so check hupper first if self.args.browser and not hupper.is_active(): - def open_browser(): - context = loadcontext( - SERVER, app_spec, name=server_name, relative_to=base, - global_conf=vars) - url = 'http://127.0.0.1:{port}/'.format(**context.config()) - time.sleep(1) - webbrowser.open(url) - t = threading.Thread(target=open_browser) - t.setDaemon(True) - t.start() + self.pserve_file_config(config_path, global_conf=vars) + url = self.open_url + if not url: + self.out('WARNING: could not determine the server\'s url to ' + 'open the browser. To fix this set the "open_url" ' + 'setting in the [pserve] section of the ' + 'configuration file.') + + else: + def open_browser(): + time.sleep(1) + webbrowser.open(url) + t = threading.Thread(target=open_browser) + t.setDaemon(True) + t.start() if self.args.reload and not hupper.is_active(): if self.args.verbose > 1: @@ -213,11 +222,11 @@ class PServeCommand(object): if config_path: setup_logging(config_path, global_conf=vars) self.pserve_file_config(config_path, global_conf=vars) - self.watch_files.append(config_path) + self.watch_files.add(config_path) if hupper.is_active(): reloader = hupper.get_reloader() - reloader.watch_files(self.watch_files) + reloader.watch_files(list(self.watch_files)) server = self.loadserver( server_spec, name=server_name, relative_to=base, global_conf=vars) diff --git a/pyramid/tests/test_scripts/test_pserve.py b/pyramid/tests/test_scripts/test_pserve.py index 8eb63b8d6..bb3303a10 100644 --- a/pyramid/tests/test_scripts/test_pserve.py +++ b/pyramid/tests/test_scripts/test_pserve.py @@ -74,11 +74,11 @@ class TestPServeCommand(unittest.TestCase): 'a': '1', 'here': os.path.abspath('/base'), }) - self.assertEqual(inst.watch_files, [ + self.assertEqual(inst.watch_files, set([ os.path.abspath('/base/foo'), os.path.abspath('/baz'), os.path.abspath(os.path.join(here, '*.py')), - ]) + ])) def test_reload_call_hupper_with_correct_args(self): from pyramid.scripts import pserve -- cgit v1.2.3 From 248669dbaedc4848e627c449e4e43928628b86be Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Tue, 28 Mar 2017 02:48:52 -0500 Subject: support opening the browser via pserve.open_url config setting --- pyramid/scripts/pserve.py | 29 ++++++++++++++++++++++++++++- pyramid/tests/test_scripts/test_pserve.py | 26 ++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/pyramid/scripts/pserve.py b/pyramid/scripts/pserve.py index caa73704a..c469dde04 100644 --- a/pyramid/scripts/pserve.py +++ b/pyramid/scripts/pserve.py @@ -150,7 +150,7 @@ class PServeCommand(object): try: items = dict(config.items('pserve')) except configparser.NoSectionError: - items = {} + return watch_files = aslist(items.get('watch_files', ''), flatten=False) @@ -163,10 +163,31 @@ class PServeCommand(object): file = os.path.join(here, file) self.watch_files.add(os.path.abspath(file)) + # attempt to determine the url of the server open_url = items.get('open_url') if open_url: self.open_url = open_url + def _guess_server_url(self, filename, server_name, + global_conf=None): # pragma: no cover + server_name = server_name or 'main' + here = os.path.abspath(os.path.dirname(filename)) + defaults = {} + if global_conf: + defaults.update(global_conf) + defaults['here'] = here + + config = self.ConfigParser(defaults=defaults) + config.optionxform = str + config.read(filename) + try: + items = dict(config.items('server:' + server_name)) + except configparser.NoSectionError: + return + + if 'port' in items: + return 'http://127.0.0.1:{port}'.format(**items) + def run(self): # pragma: no cover if not self.args.config_uri: self.out('You must give a config file') @@ -194,6 +215,12 @@ class PServeCommand(object): if self.args.browser and not hupper.is_active(): self.pserve_file_config(config_path, global_conf=vars) url = self.open_url + + # do not guess the url if the server is sourced from a different + # location than the config_path + if not url and server_spec == app_spec: + url = self._guess_server_url(config_path, server_name, vars) + if not url: self.out('WARNING: could not determine the server\'s url to ' 'open the browser. To fix this set the "open_url" ' diff --git a/pyramid/tests/test_scripts/test_pserve.py b/pyramid/tests/test_scripts/test_pserve.py index bb3303a10..d5578b3ea 100644 --- a/pyramid/tests/test_scripts/test_pserve.py +++ b/pyramid/tests/test_scripts/test_pserve.py @@ -80,6 +80,32 @@ class TestPServeCommand(unittest.TestCase): os.path.abspath(os.path.join(here, '*.py')), ])) + def test_config_file_finds_open_url(self): + inst = self._makeOne('development.ini') + self.config_factory.items = [( + 'open_url', 'http://127.0.0.1:8080/', + )] + inst.pserve_file_config('/base/path.ini', global_conf={'a': '1'}) + self.assertEqual(self.config_factory.defaults, { + 'a': '1', + 'here': os.path.abspath('/base'), + }) + self.assertEqual(inst.open_url, 'http://127.0.0.1:8080/') + + def test__guess_server_url(self): + inst = self._makeOne('development.ini') + self.config_factory.items = [( + 'port', '8080', + )] + url = inst._guess_server_url( + '/base/path.ini', 'main', global_conf={'a': '1'}) + self.assertEqual(self.config_factory.defaults, { + 'a': '1', + 'here': os.path.abspath('/base'), + }) + self.assertEqual(self.config_factory.parser.section, 'server:main') + self.assertEqual(url, 'http://127.0.0.1:8080') + def test_reload_call_hupper_with_correct_args(self): from pyramid.scripts import pserve -- cgit v1.2.3 From 839dbff79f43a8f76d2be9edd95f78308a316deb Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Tue, 28 Mar 2017 20:57:04 -0500 Subject: changelog for #2984 --- CHANGES.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 9056320c5..7676a69f9 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -10,6 +10,14 @@ Features request that the policy may create more than one request for retry purposes. See https://github.com/Pylons/pyramid/pull/2964 +- Support an ``open_url`` config setting in the ``pserve`` section of the + config file. This url is used to open a web browser when ``pserve --browser`` + is invoked. When this setting is unavailable the ``pserve`` script will + attempt to guess the port the server is using from the + ``server:`` section of the config file but there is no + requirement that the server is being run in this format so it may fail. + See https://github.com/Pylons/pyramid/pull/2984 + Bug Fixes --------- -- cgit v1.2.3 From 14be695bd7d187e162145a28ac07fe341dae3208 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Tue, 28 Mar 2017 01:29:33 -0500 Subject: rewrite low-level pyramid config functions to use plaster --- docs/api/paster.rst | 6 +- pyramid/paster.py | 59 +++++------- pyramid/scripts/common.py | 27 +----- pyramid/tests/test_paster.py | 180 +++++++++++++----------------------- pyramid/tests/test_scripts/dummy.py | 41 ++++++++ setup.py | 2 + 6 files changed, 137 insertions(+), 178 deletions(-) diff --git a/docs/api/paster.rst b/docs/api/paster.rst index 27bc81a1f..f0784d0f8 100644 --- a/docs/api/paster.rst +++ b/docs/api/paster.rst @@ -7,8 +7,8 @@ .. autofunction:: bootstrap - .. autofunction:: get_app(config_uri, name=None, options=None) + .. autofunction:: get_app - .. autofunction:: get_appsettings(config_uri, name=None, options=None) + .. autofunction:: get_appsettings - .. autofunction:: setup_logging(config_uri, global_conf=None) + .. autofunction:: setup_logging diff --git a/pyramid/paster.py b/pyramid/paster.py index 5429a7860..f7544f0c5 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -1,14 +1,17 @@ -import os +from pyramid.scripting import prepare +from pyramid.scripts.common import get_config_loader -from paste.deploy import ( - loadapp, - appconfig, - ) +def setup_logging(config_uri, global_conf=None): + """ + Set up Python logging with the filename specified via ``config_uri`` + (a string in the form ``filename#sectionname``). -from pyramid.scripting import prepare -from pyramid.scripts.common import setup_logging # noqa, api + Extra defaults can optionally be specified as a dict in ``global_conf``. + """ + loader = get_config_loader(config_uri) + loader.setup_logging(global_conf) -def get_app(config_uri, name=None, options=None, loadapp=loadapp): +def get_app(config_uri, name=None, options=None): """ Return the WSGI application named ``name`` in the PasteDeploy config file specified by ``config_uri``. @@ -18,20 +21,13 @@ def get_app(config_uri, name=None, options=None, loadapp=loadapp): If the ``name`` is None, this will attempt to parse the name from the ``config_uri`` string expecting the format ``inifile#name``. - If no name is found, the name will default to "main".""" - path, section = _getpathsec(config_uri, name) - config_name = 'config:%s' % path - here_dir = os.getcwd() + If no name is found, the name will default to "main". - app = loadapp( - config_name, - name=section, - relative_to=here_dir, - global_conf=options) - - return app + """ + loader = get_config_loader(config_uri) + return loader.get_wsgi_app(name, options) -def get_appsettings(config_uri, name=None, options=None, appconfig=appconfig): +def get_appsettings(config_uri, name=None, options=None): """ Return a dictionary representing the key/value pairs in an ``app`` section within the file represented by ``config_uri``. @@ -41,24 +37,11 @@ def get_appsettings(config_uri, name=None, options=None, appconfig=appconfig): If the ``name`` is None, this will attempt to parse the name from the ``config_uri`` string expecting the format ``inifile#name``. - If no name is found, the name will default to "main".""" - path, section = _getpathsec(config_uri, name) - config_name = 'config:%s' % path - here_dir = os.getcwd() - return appconfig( - config_name, - name=section, - relative_to=here_dir, - global_conf=options) - -def _getpathsec(config_uri, name): - if '#' in config_uri: - path, section = config_uri.split('#', 1) - else: - path, section = config_uri, 'main' - if name: - section = name - return path, section + If no name is found, the name will default to "main". + + """ + loader = get_config_loader(config_uri) + return loader.get_wsgi_app_settings(name, options) def bootstrap(config_uri, request=None, options=None): """ Load a WSGI application from the PasteDeploy config file specified diff --git a/pyramid/scripts/common.py b/pyramid/scripts/common.py index fc141f6e2..f4b8027db 100644 --- a/pyramid/scripts/common.py +++ b/pyramid/scripts/common.py @@ -1,6 +1,4 @@ -import os -from pyramid.compat import configparser -from logging.config import fileConfig +import plaster def parse_vars(args): """ @@ -17,26 +15,9 @@ def parse_vars(args): result[name] = value return result -def setup_logging(config_uri, global_conf=None, - fileConfig=fileConfig, - configparser=configparser): +def get_config_loader(config_uri): """ - Set up logging via :func:`logging.config.fileConfig` with the filename - specified via ``config_uri`` (a string in the form - ``filename#sectionname``). + Find a ``plaster.ILoader`` object supporting the "wsgi" protocol. - ConfigParser defaults are specified for the special ``__file__`` - and ``here`` variables, similar to PasteDeploy config loading. - Extra defaults can optionally be specified as a dict in ``global_conf``. """ - path = config_uri.split('#', 1)[0] - parser = configparser.ConfigParser() - parser.read([path]) - if parser.has_section('loggers'): - config_file = os.path.abspath(path) - full_global_conf = dict( - __file__=config_file, - here=os.path.dirname(config_file)) - if global_conf: - full_global_conf.update(global_conf) - return fileConfig(config_file, full_global_conf) + return plaster.get_loader(config_uri, protocols=['wsgi']) diff --git a/pyramid/tests/test_paster.py b/pyramid/tests/test_paster.py index 22a5cde3d..fc0cf20a5 100644 --- a/pyramid/tests/test_paster.py +++ b/pyramid/tests/test_paster.py @@ -1,58 +1,32 @@ import os import unittest +from pyramid.tests.test_scripts.dummy import DummyLoader here = os.path.dirname(__file__) class Test_get_app(unittest.TestCase): - def _callFUT(self, config_file, section_name, **kw): - from pyramid.paster import get_app - return get_app(config_file, section_name, **kw) + def _callFUT(self, config_file, section_name, options=None, _loader=None): + import pyramid.paster + old_loader = pyramid.paster.get_config_loader + try: + if _loader is not None: + pyramid.paster.get_config_loader = _loader + return pyramid.paster.get_app(config_file, section_name, + options=options) + finally: + pyramid.paster.get_config_loader = old_loader def test_it(self): app = DummyApp() - loadapp = DummyLoadWSGI(app) - result = self._callFUT('/foo/bar/myapp.ini', 'myapp', loadapp=loadapp) - self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') - self.assertEqual(loadapp.section_name, 'myapp') - self.assertEqual(loadapp.relative_to, os.getcwd()) - self.assertEqual(result, app) - - def test_it_with_hash(self): - app = DummyApp() - loadapp = DummyLoadWSGI(app) - result = self._callFUT( - '/foo/bar/myapp.ini#myapp', None, loadapp=loadapp - ) - self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') - self.assertEqual(loadapp.section_name, 'myapp') - self.assertEqual(loadapp.relative_to, os.getcwd()) - self.assertEqual(result, app) - - def test_it_with_hash_and_name_override(self): - app = DummyApp() - loadapp = DummyLoadWSGI(app) - result = self._callFUT( - '/foo/bar/myapp.ini#myapp', 'yourapp', loadapp=loadapp - ) - self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') - self.assertEqual(loadapp.section_name, 'yourapp') - self.assertEqual(loadapp.relative_to, os.getcwd()) - self.assertEqual(result, app) - - def test_it_with_options(self): - app = DummyApp() - loadapp = DummyLoadWSGI(app) - options = {'a':1} + loader = DummyLoader(app=app) result = self._callFUT( - '/foo/bar/myapp.ini#myapp', - 'yourapp', - loadapp=loadapp, - options=options, - ) - self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') - self.assertEqual(loadapp.section_name, 'yourapp') - self.assertEqual(loadapp.relative_to, os.getcwd()) - self.assertEqual(loadapp.kw, {'global_conf':options}) + '/foo/bar/myapp.ini', 'myapp', options={'a': 'b'}, + _loader=loader) + self.assertEqual(loader.uri, '/foo/bar/myapp.ini') + self.assertEqual(len(loader.calls), 1) + self.assertEqual(loader.calls[0]['op'], 'app') + self.assertEqual(loader.calls[0]['name'], 'myapp') + self.assertEqual(loader.calls[0]['defaults'], {'a': 'b'}) self.assertEqual(result, app) def test_it_with_dummyapp_requiring_options(self): @@ -63,38 +37,28 @@ class Test_get_app(unittest.TestCase): self.assertEqual(app.settings['foo'], 'baz') class Test_get_appsettings(unittest.TestCase): - def _callFUT(self, config_file, section_name, **kw): - from pyramid.paster import get_appsettings - return get_appsettings(config_file, section_name, **kw) + def _callFUT(self, config_file, section_name, options=None, _loader=None): + import pyramid.paster + old_loader = pyramid.paster.get_config_loader + try: + if _loader is not None: + pyramid.paster.get_config_loader = _loader + return pyramid.paster.get_appsettings(config_file, section_name, + options=options) + finally: + pyramid.paster.get_config_loader = old_loader def test_it(self): - values = {'a':1} - appconfig = DummyLoadWSGI(values) - result = self._callFUT('/foo/bar/myapp.ini', 'myapp', - appconfig=appconfig) - self.assertEqual(appconfig.config_name, 'config:/foo/bar/myapp.ini') - self.assertEqual(appconfig.section_name, 'myapp') - self.assertEqual(appconfig.relative_to, os.getcwd()) - self.assertEqual(result, values) - - def test_it_with_hash(self): - values = {'a':1} - appconfig = DummyLoadWSGI(values) - result = self._callFUT('/foo/bar/myapp.ini#myapp', None, - appconfig=appconfig) - self.assertEqual(appconfig.config_name, 'config:/foo/bar/myapp.ini') - self.assertEqual(appconfig.section_name, 'myapp') - self.assertEqual(appconfig.relative_to, os.getcwd()) - self.assertEqual(result, values) - - def test_it_with_hash_and_name_override(self): - values = {'a':1} - appconfig = DummyLoadWSGI(values) - result = self._callFUT('/foo/bar/myapp.ini#myapp', 'yourapp', - appconfig=appconfig) - self.assertEqual(appconfig.config_name, 'config:/foo/bar/myapp.ini') - self.assertEqual(appconfig.section_name, 'yourapp') - self.assertEqual(appconfig.relative_to, os.getcwd()) + values = {'a': 1} + loader = DummyLoader(app_settings=values) + result = self._callFUT( + '/foo/bar/myapp.ini', 'myapp', options={'a': 'b'}, + _loader=loader) + self.assertEqual(loader.uri, '/foo/bar/myapp.ini') + self.assertEqual(len(loader.calls), 1) + self.assertEqual(loader.calls[0]['op'], 'app_settings') + self.assertEqual(loader.calls[0]['name'], 'myapp') + self.assertEqual(loader.calls[0]['defaults'], {'a': 'b'}) self.assertEqual(result, values) def test_it_with_dummyapp_requiring_options(self): @@ -105,40 +69,39 @@ class Test_get_appsettings(unittest.TestCase): self.assertEqual(result['foo'], 'baz') class Test_setup_logging(unittest.TestCase): - def _callFUT(self, config_file, global_conf=None): - from pyramid.paster import setup_logging - dummy_cp = DummyConfigParserModule - return setup_logging( - config_uri=config_file, - global_conf=global_conf, - fileConfig=self.fileConfig, - configparser=dummy_cp, - ) + def _callFUT(self, config_file, global_conf=None, _loader=None): + import pyramid.paster + old_loader = pyramid.paster.get_config_loader + try: + if _loader is not None: + pyramid.paster.get_config_loader = _loader + return pyramid.paster.setup_logging(config_file, global_conf) + finally: + pyramid.paster.get_config_loader = old_loader def test_it_no_global_conf(self): - config_file, dict = self._callFUT('/abc') - # os.path.abspath is a sop to Windows - self.assertEqual(config_file, os.path.abspath('/abc')) - self.assertEqual(dict['__file__'], os.path.abspath('/abc')) - self.assertEqual(dict['here'], os.path.abspath('/')) + loader = DummyLoader() + self._callFUT('/abc', _loader=loader) + self.assertEqual(loader.uri, '/abc') + self.assertEqual(len(loader.calls), 1) + self.assertEqual(loader.calls[0]['op'], 'logging') + self.assertEqual(loader.calls[0]['defaults'], None) def test_it_global_conf_empty(self): - config_file, dict = self._callFUT('/abc', global_conf={}) - # os.path.abspath is a sop to Windows - self.assertEqual(config_file, os.path.abspath('/abc')) - self.assertEqual(dict['__file__'], os.path.abspath('/abc')) - self.assertEqual(dict['here'], os.path.abspath('/')) + loader = DummyLoader() + self._callFUT('/abc', global_conf={}, _loader=loader) + self.assertEqual(loader.uri, '/abc') + self.assertEqual(len(loader.calls), 1) + self.assertEqual(loader.calls[0]['op'], 'logging') + self.assertEqual(loader.calls[0]['defaults'], {}) def test_it_global_conf_not_empty(self): - config_file, dict = self._callFUT('/abc', global_conf={'key': 'val'}) - # os.path.abspath is a sop to Windows - self.assertEqual(config_file, os.path.abspath('/abc')) - self.assertEqual(dict['__file__'], os.path.abspath('/abc')) - self.assertEqual(dict['here'], os.path.abspath('/')) - self.assertEqual(dict['key'], 'val') - - def fileConfig(self, config_file, dict): - return config_file, dict + loader = DummyLoader() + self._callFUT('/abc', global_conf={'key': 'val'}, _loader=loader) + self.assertEqual(loader.uri, '/abc') + self.assertEqual(len(loader.calls), 1) + self.assertEqual(loader.calls[0]['op'], 'logging') + self.assertEqual(loader.calls[0]['defaults'], {'key': 'val'}) class Test_bootstrap(unittest.TestCase): def _callFUT(self, config_uri, request=None): @@ -187,17 +150,6 @@ class DummyRegistry(object): dummy_registry = DummyRegistry() -class DummyLoadWSGI: - def __init__(self, result): - self.result = result - - def __call__(self, config_name, name=None, relative_to=None, **kw): - self.config_name = config_name - self.section_name = name - self.relative_to = relative_to - self.kw = kw - return self.result - class DummyApp: def __init__(self): self.registry = dummy_registry diff --git a/pyramid/tests/test_scripts/dummy.py b/pyramid/tests/test_scripts/dummy.py index ced09d0b0..b904adfbd 100644 --- a/pyramid/tests/test_scripts/dummy.py +++ b/pyramid/tests/test_scripts/dummy.py @@ -162,3 +162,44 @@ class DummyPkgResources(object): def iter_entry_points(self, name): return self.entry_points + + +class dummy_setup_logging(object): + def __call__(self, config_uri, global_conf): + self.config_uri = config_uri + self.global_conf = global_conf + + +class DummyLoader(object): + def __init__(self, settings=None, app_settings=None, app=None): + if not settings: + settings = {} + if not app_settings: + app_settings = {} + self.settings = settings + self.app_settings = app_settings + self.app = app + self.calls = [] + + def __call__(self, uri): + self.uri = uri + return self + + def add_call(self, op, name, defaults): + self.calls.append({'op': op, 'name': name, 'defaults': defaults}) + + def get_settings(self, name=None, defaults=None): + self.add_call('settings', name, defaults) + return self.result + + def get_wsgi_app(self, name=None, defaults=None): + self.add_call('app', name, defaults) + return self.app + + def get_wsgi_app_settings(self, name=None, defaults=None): + self.add_call('app_settings', name, defaults) + return self.app_settings + + def setup_logging(self, defaults): + self.add_call('logging', None, defaults) + self.defaults = defaults diff --git a/setup.py b/setup.py index ff3e38874..046341f13 100644 --- a/setup.py +++ b/setup.py @@ -34,10 +34,12 @@ install_requires = [ 'venusian >= 1.0a3', # ``ignore`` 'translationstring >= 0.4', # py3 compat 'PasteDeploy >= 1.5.0', # py3 compat + 'plaster', 'hupper', ] tests_require = [ + 'plaster_pastedeploy', 'WebTest >= 1.3.1', # py3 compat 'zope.component >= 4.0', # py3 compat ] -- cgit v1.2.3 From 6787903d36d2f011f921f7e61b502b1db94e833a Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 29 Mar 2017 00:04:53 -0500 Subject: update prequest, proutes, ptweens and pviews Also ensure that each script is invoking setup_logging. --- pyramid/scripts/prequest.py | 19 ++++---- pyramid/scripts/proutes.py | 35 +++++++------- pyramid/scripts/ptweens.py | 8 +++- pyramid/scripts/pviews.py | 10 ++-- pyramid/tests/test_scripts/dummy.py | 4 +- pyramid/tests/test_scripts/test_prequest.py | 62 +++++------------------- pyramid/tests/test_scripts/test_proutes.py | 74 ++++++++++++++--------------- pyramid/tests/test_scripts/test_ptweens.py | 3 +- pyramid/tests/test_scripts/test_pviews.py | 3 +- 9 files changed, 93 insertions(+), 125 deletions(-) diff --git a/pyramid/scripts/prequest.py b/pyramid/scripts/prequest.py index 66feff624..f0681afd7 100644 --- a/pyramid/scripts/prequest.py +++ b/pyramid/scripts/prequest.py @@ -5,9 +5,8 @@ import textwrap from pyramid.compat import url_unquote from pyramid.request import Request -from pyramid.paster import get_app +from pyramid.scripts.common import get_config_loader from pyramid.scripts.common import parse_vars -from pyramid.scripts.common import setup_logging def main(argv=sys.argv, quiet=False): command = PRequestCommand(argv, quiet) @@ -110,7 +109,7 @@ class PRequestCommand(object): "passed here.", ) - get_app = staticmethod(get_app) + _get_config_loader = staticmethod(get_config_loader) stdin = sys.stdin def __init__(self, argv, quiet=False): @@ -121,17 +120,18 @@ class PRequestCommand(object): if not self.quiet: print(msg) - def configure_logging(self, app_spec): - setup_logging(app_spec) - def run(self): if not self.args.config_uri or not self.args.path_info: self.out('You must provide at least two arguments') return 2 - app_spec = self.args.config_uri + config_uri = self.args.config_uri + config_vars = parse_vars(self.args.config_vars) path = self.args.path_info - self.configure_logging(app_spec) + loader = self._get_config_loader(config_uri) + loader.setup_logging(config_vars) + + app = loader.get_wsgi_app(self.args.app_name, config_vars) if not path.startswith('/'): path = '/' + path @@ -158,9 +158,6 @@ class PRequestCommand(object): name, value = item.split(':', 1) headers[name] = value.strip() - app = self.get_app(app_spec, self.args.app_name, - options=parse_vars(self.args.config_vars)) - request_method = (self.args.method or 'GET').upper() environ = { diff --git a/pyramid/scripts/proutes.py b/pyramid/scripts/proutes.py index 80c8238a2..69d61ae8f 100644 --- a/pyramid/scripts/proutes.py +++ b/pyramid/scripts/proutes.py @@ -7,10 +7,11 @@ import re from zope.interface import Interface from pyramid.paster import bootstrap -from pyramid.compat import (string_types, configparser) +from pyramid.compat import string_types from pyramid.interfaces import IRouteRequest from pyramid.config import not_ +from pyramid.scripts.common import get_config_loader from pyramid.scripts.common import parse_vars from pyramid.static import static_view from pyramid.view import _find_views @@ -175,7 +176,6 @@ def get_route_data(route, registry): (route.name, route_intr['external_url'], UNKNOWN_KEY, ANY_KEY) ] - route_request_methods = route_intr['request_methods'] view_intr = registry.introspector.related(route_intr) @@ -245,9 +245,9 @@ class PRoutesCommand(object): will be assumed. Example: 'proutes myapp.ini'. """ - bootstrap = (bootstrap,) + bootstrap = staticmethod(bootstrap) # testing + get_config_loader = staticmethod(get_config_loader) # testing stdout = sys.stdout - ConfigParser = configparser.ConfigParser # testing parser = argparse.ArgumentParser( description=textwrap.dedent(description), formatter_class=argparse.RawDescriptionHelpFormatter, @@ -308,18 +308,12 @@ class PRoutesCommand(object): return True - def proutes_file_config(self, filename): - config = self.ConfigParser() - config.read(filename) - try: - items = config.items('proutes') - for k, v in items: - if 'format' == k: - cols = re.split(r'[,|\s\n]+', v) - self.column_format = [x.strip() for x in cols] - - except configparser.NoSectionError: - return + def proutes_file_config(self, loader, global_conf=None): + settings = loader.get_settings('proutes', global_conf) + format = settings.get('format') + if format: + cols = re.split(r'[,|\s\n]+', format) + self.column_format = [x.strip() for x in cols] def out(self, msg): # pragma: no cover if not self.quiet: @@ -336,12 +330,15 @@ class PRoutesCommand(object): return 2 config_uri = self.args.config_uri - env = self.bootstrap[0](config_uri, options=parse_vars(self.args.config_vars)) + config_vars = parse_vars(self.args.config_vars) + loader = self.get_config_loader(config_uri) + loader.setup_logging(config_vars) + self.proutes_file_config(loader, config_vars) + + env = self.bootstrap(config_uri, options=config_vars) registry = env['registry'] mapper = self._get_mapper(registry) - self.proutes_file_config(config_uri) - if self.args.format: columns = self.args.format.split(',') self.column_format = [x.strip() for x in columns] diff --git a/pyramid/scripts/ptweens.py b/pyramid/scripts/ptweens.py index 5ca77e52a..d5cbebe12 100644 --- a/pyramid/scripts/ptweens.py +++ b/pyramid/scripts/ptweens.py @@ -7,6 +7,7 @@ from pyramid.interfaces import ITweens from pyramid.tweens import MAIN from pyramid.tweens import INGRESS from pyramid.paster import bootstrap +from pyramid.paster import setup_logging from pyramid.scripts.common import parse_vars def main(argv=sys.argv, quiet=False): @@ -47,7 +48,8 @@ class PTweensCommand(object): ) stdout = sys.stdout - bootstrap = (bootstrap,) # testing + bootstrap = staticmethod(bootstrap) # testing + setup_logging = staticmethod(setup_logging) # testing def __init__(self, argv, quiet=False): self.quiet = quiet @@ -76,7 +78,9 @@ class PTweensCommand(object): self.out('Requires a config file argument') return 2 config_uri = self.args.config_uri - env = self.bootstrap[0](config_uri, options=parse_vars(self.args.config_vars)) + config_vars = parse_vars(self.args.config_vars) + self.setup_logging(config_uri, global_conf=config_vars) + env = self.bootstrap(config_uri, options=config_vars) registry = env['registry'] tweens = self._get_tweens(registry) if tweens is not None: diff --git a/pyramid/scripts/pviews.py b/pyramid/scripts/pviews.py index 4d3312917..c0df2f078 100644 --- a/pyramid/scripts/pviews.py +++ b/pyramid/scripts/pviews.py @@ -4,6 +4,7 @@ import textwrap from pyramid.interfaces import IMultiView from pyramid.paster import bootstrap +from pyramid.paster import setup_logging from pyramid.request import Request from pyramid.scripts.common import parse_vars from pyramid.view import _find_views @@ -51,7 +52,8 @@ class PViewsCommand(object): ) - bootstrap = (bootstrap,) # testing + bootstrap = staticmethod(bootstrap) # testing + setup_logging = staticmethod(setup_logging) # testing def __init__(self, argv, quiet=False): self.quiet = quiet @@ -252,13 +254,15 @@ class PViewsCommand(object): self.out('Command requires a config file arg and a url arg') return 2 config_uri = self.args.config_uri + config_vars = parse_vars(self.args.config_vars) url = self.args.url + self.setup_logging(config_uri, global_conf=config_vars) + if not url.startswith('/'): url = '/%s' % url request = Request.blank(url) - env = self.bootstrap[0](config_uri, options=parse_vars(self.args.config_vars), - request=request) + env = self.bootstrap(config_uri, options=config_vars, request=request) view = self._find_view(request) self.out('') self.out("URL = %s" % url) diff --git a/pyramid/tests/test_scripts/dummy.py b/pyramid/tests/test_scripts/dummy.py index b904adfbd..18810f8f3 100644 --- a/pyramid/tests/test_scripts/dummy.py +++ b/pyramid/tests/test_scripts/dummy.py @@ -167,7 +167,7 @@ class DummyPkgResources(object): class dummy_setup_logging(object): def __call__(self, config_uri, global_conf): self.config_uri = config_uri - self.global_conf = global_conf + self.defaults = global_conf class DummyLoader(object): @@ -190,7 +190,7 @@ class DummyLoader(object): def get_settings(self, name=None, defaults=None): self.add_call('settings', name, defaults) - return self.result + return self.settings.get(name, {}) def get_wsgi_app(self, name=None, defaults=None): self.add_call('app', name, defaults) diff --git a/pyramid/tests/test_scripts/test_prequest.py b/pyramid/tests/test_scripts/test_prequest.py index 45db0dbaf..ec5119270 100644 --- a/pyramid/tests/test_scripts/test_prequest.py +++ b/pyramid/tests/test_scripts/test_prequest.py @@ -1,4 +1,5 @@ import unittest +from pyramid.tests.test_scripts import dummy class TestPRequestCommand(unittest.TestCase): def _getTargetClass(self): @@ -7,23 +8,17 @@ class TestPRequestCommand(unittest.TestCase): def _makeOne(self, argv, headers=None): cmd = self._getTargetClass()(argv) - cmd.get_app = self.get_app - self._headers = headers or [] - self._out = [] - cmd.out = self.out - return cmd - - def get_app(self, spec, app_name=None, options=None): - self._spec = spec - self._app_name = app_name - self._options = options or {} def helloworld(environ, start_request): self._environ = environ self._path_info = environ['PATH_INFO'] - start_request('200 OK', self._headers) + start_request('200 OK', headers or []) return [b'abc'] - return helloworld + self.loader = dummy.DummyLoader(app=helloworld) + self._out = [] + cmd._get_config_loader = self.loader + cmd.out = self.out + return cmd def out(self, msg): self._out.append(msg) @@ -38,8 +33,10 @@ class TestPRequestCommand(unittest.TestCase): [('Content-Type', 'text/html; charset=UTF-8')]) command.run() self.assertEqual(self._path_info, '/') - self.assertEqual(self._spec, 'development.ini') - self.assertEqual(self._app_name, None) + self.assertEqual(self.loader.uri, 'development.ini') + self.assertEqual(self.loader.calls[0]['op'], 'logging') + self.assertEqual(self.loader.calls[1]['op'], 'app') + self.assertEqual(self.loader.calls[1]['name'], None) self.assertEqual(self._out, ['abc']) def test_command_path_doesnt_start_with_slash(self): @@ -47,8 +44,7 @@ class TestPRequestCommand(unittest.TestCase): [('Content-Type', 'text/html; charset=UTF-8')]) command.run() self.assertEqual(self._path_info, '/abc') - self.assertEqual(self._spec, 'development.ini') - self.assertEqual(self._app_name, None) + self.assertEqual(self.loader.uri, 'development.ini') self.assertEqual(self._out, ['abc']) def test_command_has_bad_config_header(self): @@ -67,8 +63,6 @@ class TestPRequestCommand(unittest.TestCase): command.run() self.assertEqual(self._environ['HTTP_NAME'], 'value') self.assertEqual(self._path_info, '/') - self.assertEqual(self._spec, 'development.ini') - self.assertEqual(self._app_name, None) self.assertEqual(self._out, ['abc']) def test_command_w_basic_auth(self): @@ -81,8 +75,6 @@ class TestPRequestCommand(unittest.TestCase): self.assertEqual(self._environ['HTTP_AUTHORIZATION'], 'Basic dXNlcjpwYXNzd29yZA==') self.assertEqual(self._path_info, '/') - self.assertEqual(self._spec, 'development.ini') - self.assertEqual(self._app_name, None) self.assertEqual(self._out, ['abc']) def test_command_has_content_type_header_var(self): @@ -92,8 +84,6 @@ class TestPRequestCommand(unittest.TestCase): command.run() self.assertEqual(self._environ['CONTENT_TYPE'], 'app/foo') self.assertEqual(self._path_info, '/') - self.assertEqual(self._spec, 'development.ini') - self.assertEqual(self._app_name, None) self.assertEqual(self._out, ['abc']) def test_command_has_multiple_header_vars(self): @@ -109,8 +99,6 @@ class TestPRequestCommand(unittest.TestCase): self.assertEqual(self._environ['HTTP_NAME'], 'value') self.assertEqual(self._environ['HTTP_NAME2'], 'value2') self.assertEqual(self._path_info, '/') - self.assertEqual(self._spec, 'development.ini') - self.assertEqual(self._app_name, None) self.assertEqual(self._out, ['abc']) def test_command_method_get(self): @@ -119,8 +107,6 @@ class TestPRequestCommand(unittest.TestCase): command.run() self.assertEqual(self._environ['REQUEST_METHOD'], 'GET') self.assertEqual(self._path_info, '/') - self.assertEqual(self._spec, 'development.ini') - self.assertEqual(self._app_name, None) self.assertEqual(self._out, ['abc']) def test_command_method_post(self): @@ -134,8 +120,6 @@ class TestPRequestCommand(unittest.TestCase): self.assertEqual(self._environ['CONTENT_LENGTH'], '-1') self.assertEqual(self._environ['wsgi.input'], stdin) self.assertEqual(self._path_info, '/') - self.assertEqual(self._spec, 'development.ini') - self.assertEqual(self._app_name, None) self.assertEqual(self._out, ['abc']) def test_command_method_put(self): @@ -149,8 +133,6 @@ class TestPRequestCommand(unittest.TestCase): self.assertEqual(self._environ['CONTENT_LENGTH'], '-1') self.assertEqual(self._environ['wsgi.input'], stdin) self.assertEqual(self._path_info, '/') - self.assertEqual(self._spec, 'development.ini') - self.assertEqual(self._app_name, None) self.assertEqual(self._out, ['abc']) def test_command_method_patch(self): @@ -164,8 +146,6 @@ class TestPRequestCommand(unittest.TestCase): self.assertEqual(self._environ['CONTENT_LENGTH'], '-1') self.assertEqual(self._environ['wsgi.input'], stdin) self.assertEqual(self._path_info, '/') - self.assertEqual(self._spec, 'development.ini') - self.assertEqual(self._app_name, None) self.assertEqual(self._out, ['abc']) def test_command_method_propfind(self): @@ -178,8 +158,6 @@ class TestPRequestCommand(unittest.TestCase): command.run() self.assertEqual(self._environ['REQUEST_METHOD'], 'PROPFIND') self.assertEqual(self._path_info, '/') - self.assertEqual(self._spec, 'development.ini') - self.assertEqual(self._app_name, None) self.assertEqual(self._out, ['abc']) def test_command_method_options(self): @@ -192,8 +170,6 @@ class TestPRequestCommand(unittest.TestCase): command.run() self.assertEqual(self._environ['REQUEST_METHOD'], 'OPTIONS') self.assertEqual(self._path_info, '/') - self.assertEqual(self._spec, 'development.ini') - self.assertEqual(self._app_name, None) self.assertEqual(self._out, ['abc']) def test_command_with_query_string(self): @@ -202,8 +178,6 @@ class TestPRequestCommand(unittest.TestCase): command.run() self.assertEqual(self._environ['QUERY_STRING'], 'a=1&b=2&c') self.assertEqual(self._path_info, '/abc') - self.assertEqual(self._spec, 'development.ini') - self.assertEqual(self._app_name, None) self.assertEqual(self._out, ['abc']) def test_command_display_headers(self): @@ -212,8 +186,6 @@ class TestPRequestCommand(unittest.TestCase): [('Content-Type', 'text/html; charset=UTF-8')]) command.run() self.assertEqual(self._path_info, '/') - self.assertEqual(self._spec, 'development.ini') - self.assertEqual(self._app_name, None) self.assertEqual( self._out, ['200 OK', 'Content-Type: text/html; charset=UTF-8', 'abc']) @@ -223,21 +195,13 @@ class TestPRequestCommand(unittest.TestCase): headers=[('Content-Type', 'image/jpeg')]) command.run() self.assertEqual(self._path_info, '/') - self.assertEqual(self._spec, 'development.ini') - self.assertEqual(self._app_name, None) self.assertEqual(self._out, [b'abc']) def test_command_method_configures_logging(self): command = self._makeOne(['', 'development.ini', '/']) - called_args = [] - - def configure_logging(app_spec): - called_args.append(app_spec) - - command.configure_logging = configure_logging command.run() - self.assertEqual(called_args, ['development.ini']) + self.assertEqual(self.loader.calls[0]['op'], 'logging') class Test_main(unittest.TestCase): diff --git a/pyramid/tests/test_scripts/test_proutes.py b/pyramid/tests/test_scripts/test_proutes.py index 74293a112..fab5e163e 100644 --- a/pyramid/tests/test_scripts/test_proutes.py +++ b/pyramid/tests/test_scripts/test_proutes.py @@ -19,7 +19,8 @@ class TestPRoutesCommand(unittest.TestCase): def _makeOne(self): cmd = self._getTargetClass()([]) - cmd.bootstrap = (dummy.DummyBootstrap(),) + cmd.bootstrap = dummy.DummyBootstrap() + cmd.get_config_loader = dummy.DummyLoader() cmd.args.config_uri = '/foo/bar/myapp.ini#myapp' return cmd @@ -37,14 +38,15 @@ class TestPRoutesCommand(unittest.TestCase): def test_good_args(self): cmd = self._getTargetClass()([]) - cmd.bootstrap = (dummy.DummyBootstrap(),) + cmd.bootstrap = dummy.DummyBootstrap() + cmd.get_config_loader = dummy.DummyLoader() cmd.args.config_uri = '/foo/bar/myapp.ini#myapp' cmd.args.config_args = ('a=1',) route = dummy.DummyRoute('a', '/a') mapper = dummy.DummyMapper(route) cmd._get_mapper = lambda *arg: mapper registry = self._makeRegistry() - cmd.bootstrap = (dummy.DummyBootstrap(registry=registry),) + cmd.bootstrap = dummy.DummyBootstrap(registry=registry) L = [] cmd.out = lambda msg: L.append(msg) cmd.run() @@ -52,7 +54,8 @@ class TestPRoutesCommand(unittest.TestCase): def test_bad_args(self): cmd = self._getTargetClass()([]) - cmd.bootstrap = (dummy.DummyBootstrap(),) + cmd.bootstrap = dummy.DummyBootstrap() + cmd.get_config_loader = dummy.DummyLoader() cmd.args.config_uri = '/foo/bar/myapp.ini#myapp' cmd.args.config_vars = ('a',) route = dummy.DummyRoute('a', '/a') @@ -86,7 +89,7 @@ class TestPRoutesCommand(unittest.TestCase): mapper = dummy.DummyMapper(route) command._get_mapper = lambda *arg: mapper registry = self._makeRegistry() - command.bootstrap = (dummy.DummyBootstrap(registry=registry),) + command.bootstrap = dummy.DummyBootstrap(registry=registry) L = [] command.out = L.append @@ -103,7 +106,7 @@ class TestPRoutesCommand(unittest.TestCase): L = [] command.out = L.append registry = self._makeRegistry() - command.bootstrap = (dummy.DummyBootstrap(registry=registry),) + command.bootstrap = dummy.DummyBootstrap(registry=registry) result = command.run() self.assertEqual(result, 0) self.assertEqual(len(L), 3) @@ -124,7 +127,7 @@ class TestPRoutesCommand(unittest.TestCase): command._get_mapper = lambda *arg: mapper L = [] command.out = L.append - command.bootstrap = (dummy.DummyBootstrap(registry=registry),) + command.bootstrap = dummy.DummyBootstrap(registry=registry) result = command.run() self.assertEqual(result, 0) self.assertEqual(len(L), 3) @@ -150,7 +153,7 @@ class TestPRoutesCommand(unittest.TestCase): command._get_mapper = lambda *arg: mapper L = [] command.out = L.append - command.bootstrap = (dummy.DummyBootstrap(registry=registry),) + command.bootstrap = dummy.DummyBootstrap(registry=registry) result = command.run() self.assertEqual(result, 0) self.assertEqual(len(L), 3) @@ -190,7 +193,7 @@ class TestPRoutesCommand(unittest.TestCase): command._get_mapper = lambda *arg: mapper L = [] command.out = L.append - command.bootstrap = (dummy.DummyBootstrap(registry=registry),) + command.bootstrap = dummy.DummyBootstrap(registry=registry) result = command.run() self.assertEqual(result, 0) self.assertEqual(len(L), 3) @@ -218,7 +221,7 @@ class TestPRoutesCommand(unittest.TestCase): command = self._makeOne() L = [] command.out = L.append - command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) + command.bootstrap = dummy.DummyBootstrap(registry=config.registry) result = command.run() self.assertEqual(result, 0) self.assertEqual(len(L), 3) @@ -252,7 +255,7 @@ class TestPRoutesCommand(unittest.TestCase): command._get_mapper = lambda *arg: mapper L = [] command.out = L.append - command.bootstrap = (dummy.DummyBootstrap(registry=registry),) + command.bootstrap = dummy.DummyBootstrap(registry=registry) result = command.run() self.assertEqual(result, 0) self.assertEqual(len(L), 3) @@ -288,7 +291,7 @@ class TestPRoutesCommand(unittest.TestCase): command._get_mapper = lambda *arg: mapper L = [] command.out = L.append - command.bootstrap = (dummy.DummyBootstrap(registry=registry),) + command.bootstrap = dummy.DummyBootstrap(registry=registry) result = command.run() self.assertEqual(result, 0) self.assertEqual(len(L), 3) @@ -327,7 +330,7 @@ class TestPRoutesCommand(unittest.TestCase): command = self._makeOne() L = [] command.out = L.append - command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) + command.bootstrap = dummy.DummyBootstrap(registry=config.registry) result = command.run() self.assertEqual(result, 0) self.assertEqual(len(L), 3) @@ -354,7 +357,7 @@ class TestPRoutesCommand(unittest.TestCase): command = self._makeOne() L = [] command.out = L.append - command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) + command.bootstrap = dummy.DummyBootstrap(registry=config.registry) result = command.run() self.assertEqual(result, 0) self.assertEqual(len(L), 3) @@ -382,7 +385,7 @@ class TestPRoutesCommand(unittest.TestCase): command = self._makeOne() L = [] command.out = L.append - command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) + command.bootstrap = dummy.DummyBootstrap(registry=config.registry) result = command.run() self.assertEqual(result, 0) self.assertEqual(len(L), 3) @@ -410,7 +413,7 @@ class TestPRoutesCommand(unittest.TestCase): command = self._makeOne() L = [] command.out = L.append - command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) + command.bootstrap = dummy.DummyBootstrap(registry=config.registry) result = command.run() self.assertEqual(result, 0) self.assertEqual(len(L), 3) @@ -436,7 +439,7 @@ class TestPRoutesCommand(unittest.TestCase): command = self._makeOne() L = [] command.out = L.append - command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) + command.bootstrap = dummy.DummyBootstrap(registry=config.registry) result = command.run() self.assertEqual(result, 0) self.assertEqual(len(L), 5) @@ -461,7 +464,7 @@ class TestPRoutesCommand(unittest.TestCase): command = self._makeOne() L = [] command.out = L.append - command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) + command.bootstrap = dummy.DummyBootstrap(registry=config.registry) result = command.run() self.assertEqual(result, 0) self.assertEqual(len(L), 3) @@ -491,7 +494,7 @@ class TestPRoutesCommand(unittest.TestCase): command = self._makeOne() L = [] command.out = L.append - command.bootstrap = (dummy.DummyBootstrap(registry=config2.registry),) + command.bootstrap = dummy.DummyBootstrap(registry=config2.registry) result = command.run() self.assertEqual(result, 0) self.assertEqual(len(L), 3) @@ -521,7 +524,7 @@ class TestPRoutesCommand(unittest.TestCase): command = self._makeOne() L = [] command.out = L.append - command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) + command.bootstrap = dummy.DummyBootstrap(registry=config.registry) result = command.run() self.assertEqual(result, 0) self.assertEqual(len(L), 3) @@ -551,7 +554,7 @@ class TestPRoutesCommand(unittest.TestCase): command = self._makeOne() L = [] command.out = L.append - command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) + command.bootstrap = dummy.DummyBootstrap(registry=config.registry) result = command.run() self.assertEqual(result, 0) self.assertEqual(len(L), 3) @@ -592,7 +595,7 @@ class TestPRoutesCommand(unittest.TestCase): L = [] command.out = L.append - command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) + command.bootstrap = dummy.DummyBootstrap(registry=config.registry) result = command.run() self.assertEqual(result, 0) self.assertEqual(len(L), 3) @@ -624,7 +627,7 @@ class TestPRoutesCommand(unittest.TestCase): command.args.format = 'method,name' L = [] command.out = L.append - command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) + command.bootstrap = dummy.DummyBootstrap(registry=config.registry) result = command.run() self.assertEqual(result, 0) self.assertEqual(len(L), 3) @@ -654,7 +657,7 @@ class TestPRoutesCommand(unittest.TestCase): command.args.format = 'predicates,name,pattern' L = [] command.out = L.append - command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) + command.bootstrap = dummy.DummyBootstrap(registry=config.registry) expected = ( "You provided invalid formats ['predicates'], " "Available formats are ['name', 'pattern', 'view', 'method']" @@ -682,10 +685,9 @@ class TestPRoutesCommand(unittest.TestCase): L = [] command.out = L.append - command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) - config_factory = dummy.DummyConfigParserFactory() - command.ConfigParser = config_factory - config_factory.items = [('format', 'method\nname')] + command.bootstrap = dummy.DummyBootstrap(registry=config.registry) + command.get_config_loader = dummy.DummyLoader( + {'proutes': {'format': 'method\nname'}}) result = command.run() self.assertEqual(result, 0) @@ -715,10 +717,9 @@ class TestPRoutesCommand(unittest.TestCase): L = [] command.out = L.append - command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) - config_factory = dummy.DummyConfigParserFactory() - command.ConfigParser = config_factory - config_factory.items = [('format', 'method name')] + command.bootstrap = dummy.DummyBootstrap(registry=config.registry) + command.get_config_loader = dummy.DummyLoader( + {'proutes': {'format': 'method name'}}) result = command.run() self.assertEqual(result, 0) @@ -748,10 +749,9 @@ class TestPRoutesCommand(unittest.TestCase): L = [] command.out = L.append - command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) - config_factory = dummy.DummyConfigParserFactory() - command.ConfigParser = config_factory - config_factory.items = [('format', 'method,name')] + command.bootstrap = dummy.DummyBootstrap(registry=config.registry) + command.get_config_loader = dummy.DummyLoader( + {'proutes': {'format': 'method,name'}}) result = command.run() self.assertEqual(result, 0) @@ -771,7 +771,7 @@ class TestPRoutesCommand(unittest.TestCase): command = self._makeOne() L = [] command.out = L.append - command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) + command.bootstrap = dummy.DummyBootstrap(registry=config.registry) result = command.run() self.assertEqual(result, 0) self.assertEqual(len(L), 3) diff --git a/pyramid/tests/test_scripts/test_ptweens.py b/pyramid/tests/test_scripts/test_ptweens.py index f63069fed..6907b858d 100644 --- a/pyramid/tests/test_scripts/test_ptweens.py +++ b/pyramid/tests/test_scripts/test_ptweens.py @@ -8,7 +8,8 @@ class TestPTweensCommand(unittest.TestCase): def _makeOne(self): cmd = self._getTargetClass()([]) - cmd.bootstrap = (dummy.DummyBootstrap(),) + cmd.bootstrap = dummy.DummyBootstrap() + cmd.setup_logging = dummy.dummy_setup_logging() cmd.args.config_uri = '/foo/bar/myapp.ini#myapp' return cmd diff --git a/pyramid/tests/test_scripts/test_pviews.py b/pyramid/tests/test_scripts/test_pviews.py index 7bdab5804..6ec9defbd 100644 --- a/pyramid/tests/test_scripts/test_pviews.py +++ b/pyramid/tests/test_scripts/test_pviews.py @@ -8,7 +8,8 @@ class TestPViewsCommand(unittest.TestCase): def _makeOne(self, registry=None): cmd = self._getTargetClass()([]) - cmd.bootstrap = (dummy.DummyBootstrap(registry=registry),) + cmd.bootstrap = dummy.DummyBootstrap(registry=registry) + cmd.setup_logging = dummy.dummy_setup_logging() cmd.args.config_uri = '/foo/bar/myapp.ini#myapp' return cmd -- cgit v1.2.3 From e1d1af88e314fe59d9197182f8c2b56ecdcbd115 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 29 Mar 2017 00:37:09 -0500 Subject: update pshell --- pyramid/scripts/pshell.py | 31 +++++++---------- pyramid/tests/test_scripts/test_pshell.py | 55 ++++++++----------------------- 2 files changed, 25 insertions(+), 61 deletions(-) diff --git a/pyramid/scripts/pshell.py b/pyramid/scripts/pshell.py index 83e640c32..bb201dbc2 100644 --- a/pyramid/scripts/pshell.py +++ b/pyramid/scripts/pshell.py @@ -5,15 +5,14 @@ import sys import textwrap import pkg_resources -from pyramid.compat import configparser from pyramid.compat import exec_ from pyramid.util import DottedNameResolver from pyramid.paster import bootstrap from pyramid.settings import aslist +from pyramid.scripts.common import get_config_loader from pyramid.scripts.common import parse_vars -from pyramid.scripts.common import setup_logging def main(argv=sys.argv, quiet=False): command = PShellCommand(argv, quiet) @@ -41,7 +40,8 @@ class PShellCommand(object): than one Pyramid application within it, the loader will use the last one. """ - bootstrap = (bootstrap,) # for testing + bootstrap = staticmethod(bootstrap) # for testing + get_config_loader = staticmethod(get_config_loader) # for testing pkg_resources = pkg_resources # for testing parser = argparse.ArgumentParser( @@ -78,7 +78,6 @@ class PShellCommand(object): "passed here.", ) - ConfigParser = configparser.ConfigParser # testing default_runner = python_shell_runner # testing loaded_objects = {} @@ -91,20 +90,13 @@ class PShellCommand(object): self.quiet = quiet self.args = self.parser.parse_args(argv[1:]) - def pshell_file_config(self, filename): - config = self.ConfigParser() - config.optionxform = str - config.read(filename) - try: - items = config.items('pshell') - except configparser.NoSectionError: - return - + def pshell_file_config(self, loader, defaults): + settings = loader.get_settings('pshell', defaults) resolver = DottedNameResolver(None) self.loaded_objects = {} self.object_help = {} self.setup = None - for k, v in items: + for k, v in settings.items(): if k == 'setup': self.setup = v elif k == 'default_shell': @@ -124,13 +116,12 @@ class PShellCommand(object): self.out('Requires a config file argument') return 2 config_uri = self.args.config_uri - config_file = config_uri.split('#', 1)[0] - setup_logging(config_file) - self.pshell_file_config(config_file) + config_vars = parse_vars(self.args.config_vars) + loader = self.get_config_loader(config_uri) + loader.setup_logging(config_vars) + self.pshell_file_config(loader, config_vars) - # bootstrap the environ - env = self.bootstrap[0](config_uri, - options=parse_vars(self.args.config_vars)) + env = self.bootstrap(config_uri, options=config_vars) # remove the closer from the env self.closer = env.pop('closer') diff --git a/pyramid/tests/test_scripts/test_pshell.py b/pyramid/tests/test_scripts/test_pshell.py index 303964b2b..ca9eb7af2 100644 --- a/pyramid/tests/test_scripts/test_pshell.py +++ b/pyramid/tests/test_scripts/test_pshell.py @@ -8,16 +8,16 @@ class TestPShellCommand(unittest.TestCase): from pyramid.scripts.pshell import PShellCommand return PShellCommand - def _makeOne(self, patch_bootstrap=True, patch_config=True, + def _makeOne(self, patch_bootstrap=True, patch_loader=True, patch_args=True, patch_options=True): cmd = self._getTargetClass()([]) if patch_bootstrap: self.bootstrap = dummy.DummyBootstrap() - cmd.bootstrap = (self.bootstrap,) - if patch_config: - self.config_factory = dummy.DummyConfigParserFactory() - cmd.ConfigParser = self.config_factory + cmd.bootstrap = self.bootstrap + if patch_loader: + self.loader = dummy.DummyLoader() + cmd.get_config_loader = self.loader if patch_args: class Args(object): pass self.args = Args() @@ -46,9 +46,6 @@ class TestPShellCommand(unittest.TestCase): command.default_runner = shell command.run() - self.assertTrue(self.config_factory.parser) - self.assertEqual(self.config_factory.parser.filename, - '/foo/bar/myapp.ini') self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp') self.assertEqual(shell.env, { 'app':self.bootstrap.app, 'root':self.bootstrap.root, @@ -79,9 +76,6 @@ class TestPShellCommand(unittest.TestCase): self.assertEqual( out_calls, ['could not find a shell named "unknown_python_shell"'] ) - self.assertTrue(self.config_factory.parser) - self.assertEqual(self.config_factory.parser.filename, - '/foo/bar/myapp.ini') self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp') self.assertTrue(self.bootstrap.closer.called) @@ -100,9 +94,6 @@ class TestPShellCommand(unittest.TestCase): command.args.python_shell = 'ipython' command.run() - self.assertTrue(self.config_factory.parser) - self.assertEqual(self.config_factory.parser.filename, - '/foo/bar/myapp.ini') self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp') self.assertEqual(shell.env, { 'app':self.bootstrap.app, 'root':self.bootstrap.root, @@ -199,12 +190,9 @@ class TestPShellCommand(unittest.TestCase): command = self._makeOne() model = dummy.Dummy() user = dummy.Dummy() - self.config_factory.items = [('m', model), ('User', user)] + self.loader.settings = {'pshell': {'m': model, 'User': user}} shell = dummy.DummyShell() command.run(shell) - self.assertTrue(self.config_factory.parser) - self.assertEqual(self.config_factory.parser.filename, - '/foo/bar/myapp.ini') self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp') self.assertEqual(shell.env, { 'app':self.bootstrap.app, 'root':self.bootstrap.root, @@ -223,12 +211,9 @@ class TestPShellCommand(unittest.TestCase): env['a'] = 1 env['root'] = 'root override' env['none'] = None - self.config_factory.items = [('setup', setup)] + self.loader.settings = {'pshell': {'setup': setup}} shell = dummy.DummyShell() command.run(shell) - self.assertTrue(self.config_factory.parser) - self.assertEqual(self.config_factory.parser.filename, - '/foo/bar/myapp.ini') self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp') self.assertEqual(shell.env, { 'app':self.bootstrap.app, 'root':'root override', @@ -252,12 +237,9 @@ class TestPShellCommand(unittest.TestCase): 'python': dshell, } ) - self.config_factory.items = [ - ('default_shell', 'bpython python\nipython')] + self.loader.settings = {'pshell': { + 'default_shell': 'bpython python\nipython'}} command.run() - self.assertTrue(self.config_factory.parser) - self.assertEqual(self.config_factory.parser.filename, - '/foo/bar/myapp.ini') self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp') self.assertTrue(dshell.called) @@ -268,12 +250,9 @@ class TestPShellCommand(unittest.TestCase): env['a'] = 1 env['m'] = 'model override' env['root'] = 'root override' - self.config_factory.items = [('setup', setup), ('m', model)] + self.loader.settings = {'pshell': {'setup': setup, 'm': model}} shell = dummy.DummyShell() command.run(shell) - self.assertTrue(self.config_factory.parser) - self.assertEqual(self.config_factory.parser.filename, - '/foo/bar/myapp.ini') self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp') self.assertEqual(shell.env, { 'app':self.bootstrap.app, 'root':'root override', @@ -291,14 +270,10 @@ class TestPShellCommand(unittest.TestCase): env['a'] = 1 env['root'] = 'root override' model = dummy.Dummy() - self.config_factory.items = [('setup', 'abc'), - ('m', model)] + self.loader.settings = {'pshell': {'setup': 'abc', 'm': model}} command.args.setup = setup shell = dummy.DummyShell() command.run(shell) - self.assertTrue(self.config_factory.parser) - self.assertEqual(self.config_factory.parser.filename, - '/foo/bar/myapp.ini') self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp') self.assertEqual(shell.env, { 'app':self.bootstrap.app, 'root':'root override', @@ -313,13 +288,11 @@ class TestPShellCommand(unittest.TestCase): def test_command_custom_section_override(self): command = self._makeOne() dummy_ = dummy.Dummy() - self.config_factory.items = [('app', dummy_), ('root', dummy_), - ('registry', dummy_), ('request', dummy_)] + self.loader.settings = {'pshell': { + 'app': dummy_, 'root': dummy_, 'registry': dummy_, + 'request': dummy_}} shell = dummy.DummyShell() command.run(shell) - self.assertTrue(self.config_factory.parser) - self.assertEqual(self.config_factory.parser.filename, - '/foo/bar/myapp.ini') self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp') self.assertEqual(shell.env, { 'app':dummy_, 'root':dummy_, 'registry':dummy_, 'request':dummy_, -- cgit v1.2.3 From 3e489b740b1836c81af240cba579245ab18177da Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 29 Mar 2017 22:46:02 -0500 Subject: update pserve --- pyramid/scripts/pserve.py | 103 ++++++++++------------------ pyramid/tests/test_paster.py | 26 +++---- pyramid/tests/test_scripts/dummy.py | 33 +++------ pyramid/tests/test_scripts/test_prequest.py | 4 +- pyramid/tests/test_scripts/test_pserve.py | 69 ++++++++----------- 5 files changed, 80 insertions(+), 155 deletions(-) diff --git a/pyramid/scripts/pserve.py b/pyramid/scripts/pserve.py index c469dde04..f7d094980 100644 --- a/pyramid/scripts/pserve.py +++ b/pyramid/scripts/pserve.py @@ -18,16 +18,11 @@ import time import webbrowser import hupper -from paste.deploy import ( - loadapp, - loadserver, -) from pyramid.compat import PY2 -from pyramid.compat import configparser +from pyramid.scripts.common import get_config_loader from pyramid.scripts.common import parse_vars -from pyramid.scripts.common import setup_logging from pyramid.path import AssetResolver from pyramid.settings import aslist @@ -113,9 +108,7 @@ class PServeCommand(object): "passed here.", ) - ConfigParser = configparser.ConfigParser # testing - loadapp = staticmethod(loadapp) # testing - loadserver = staticmethod(loadserver) # testing + _get_config_loader = staticmethod(get_config_loader) # for testing open_url = None @@ -133,26 +126,14 @@ class PServeCommand(object): if self.args.verbose > 0: print(msg) - def get_config_vars(self): - restvars = self.args.config_vars - return parse_vars(restvars) + def get_config_path(self, loader): + return os.path.abspath(loader.uri.path) - def pserve_file_config(self, filename, global_conf=None): - here = os.path.abspath(os.path.dirname(filename)) - defaults = {} - if global_conf: - defaults.update(global_conf) - defaults['here'] = here - - config = self.ConfigParser(defaults=defaults) - config.optionxform = str - config.read(filename) - try: - items = dict(config.items('pserve')) - except configparser.NoSectionError: - return - - watch_files = aslist(items.get('watch_files', ''), flatten=False) + def pserve_file_config(self, loader, global_conf=None): + settings = loader.get_settings('pserve', global_conf) + config_path = self.get_config_path(loader) + here = os.path.dirname(config_path) + watch_files = aslist(settings.get('watch_files', ''), flatten=False) # track file paths relative to the ini file resolver = AssetResolver(package=None) @@ -164,45 +145,30 @@ class PServeCommand(object): self.watch_files.add(os.path.abspath(file)) # attempt to determine the url of the server - open_url = items.get('open_url') + open_url = settings.get('open_url') if open_url: self.open_url = open_url - def _guess_server_url(self, filename, server_name, - global_conf=None): # pragma: no cover + def guess_server_url(self, loader, server_name, global_conf=None): server_name = server_name or 'main' - here = os.path.abspath(os.path.dirname(filename)) - defaults = {} - if global_conf: - defaults.update(global_conf) - defaults['here'] = here - - config = self.ConfigParser(defaults=defaults) - config.optionxform = str - config.read(filename) - try: - items = dict(config.items('server:' + server_name)) - except configparser.NoSectionError: - return - - if 'port' in items: - return 'http://127.0.0.1:{port}'.format(**items) + settings = loader.get_settings('server:' + server_name, global_conf) + if 'port' in settings: + return 'http://127.0.0.1:{port}'.format(**settings) def run(self): # pragma: no cover if not self.args.config_uri: self.out('You must give a config file') return 2 + config_uri = self.args.config_uri + config_vars = parse_vars(self.args.config_vars) app_spec = self.args.config_uri - - vars = self.get_config_vars() app_name = self.args.app_name - base = os.getcwd() - if not self._scheme_re.search(app_spec): - config_path = os.path.join(base, app_spec) - app_spec = 'config:' + app_spec - else: - config_path = None + loader = self._get_config_loader(config_uri) + loader.setup_logging(config_vars) + + self.pserve_file_config(loader, global_conf=config_vars) + server_name = self.args.server_name if self.args.server: server_spec = 'egg:pyramid' @@ -211,15 +177,17 @@ class PServeCommand(object): else: server_spec = app_spec + server_loader = loader + if server_spec != app_spec: + server_loader = self.get_config_loader(server_spec) + # do not open the browser on each reload so check hupper first if self.args.browser and not hupper.is_active(): - self.pserve_file_config(config_path, global_conf=vars) url = self.open_url - # do not guess the url if the server is sourced from a different - # location than the config_path - if not url and server_spec == app_spec: - url = self._guess_server_url(config_path, server_name, vars) + if not url: + url = self.guess_server_url( + server_loader, server_name, config_vars) if not url: self.out('WARNING: could not determine the server\'s url to ' @@ -246,20 +214,19 @@ class PServeCommand(object): ) return 0 - if config_path: - setup_logging(config_path, global_conf=vars) - self.pserve_file_config(config_path, global_conf=vars) - self.watch_files.add(config_path) + config_path = self.get_config_path(loader) + self.watch_files.add(config_path) + + server_path = self.get_config_path(server_loader) + self.watch_files.add(server_path) if hupper.is_active(): reloader = hupper.get_reloader() reloader.watch_files(list(self.watch_files)) - server = self.loadserver( - server_spec, name=server_name, relative_to=base, global_conf=vars) + server = server_loader.get_wsgi_server(server_name, config_vars) - app = self.loadapp( - app_spec, name=app_name, relative_to=base, global_conf=vars) + app = loader.get_wsgi_app(app_name, config_vars) if self.args.verbose > 0: if hasattr(os, 'getpid'): diff --git a/pyramid/tests/test_paster.py b/pyramid/tests/test_paster.py index fc0cf20a5..784458647 100644 --- a/pyramid/tests/test_paster.py +++ b/pyramid/tests/test_paster.py @@ -22,7 +22,7 @@ class Test_get_app(unittest.TestCase): result = self._callFUT( '/foo/bar/myapp.ini', 'myapp', options={'a': 'b'}, _loader=loader) - self.assertEqual(loader.uri, '/foo/bar/myapp.ini') + self.assertEqual(loader.uri.path, '/foo/bar/myapp.ini') self.assertEqual(len(loader.calls), 1) self.assertEqual(loader.calls[0]['op'], 'app') self.assertEqual(loader.calls[0]['name'], 'myapp') @@ -54,7 +54,7 @@ class Test_get_appsettings(unittest.TestCase): result = self._callFUT( '/foo/bar/myapp.ini', 'myapp', options={'a': 'b'}, _loader=loader) - self.assertEqual(loader.uri, '/foo/bar/myapp.ini') + self.assertEqual(loader.uri.path, '/foo/bar/myapp.ini') self.assertEqual(len(loader.calls), 1) self.assertEqual(loader.calls[0]['op'], 'app_settings') self.assertEqual(loader.calls[0]['name'], 'myapp') @@ -81,24 +81,24 @@ class Test_setup_logging(unittest.TestCase): def test_it_no_global_conf(self): loader = DummyLoader() - self._callFUT('/abc', _loader=loader) - self.assertEqual(loader.uri, '/abc') + self._callFUT('/abc.ini', _loader=loader) + self.assertEqual(loader.uri.path, '/abc.ini') self.assertEqual(len(loader.calls), 1) self.assertEqual(loader.calls[0]['op'], 'logging') self.assertEqual(loader.calls[0]['defaults'], None) def test_it_global_conf_empty(self): loader = DummyLoader() - self._callFUT('/abc', global_conf={}, _loader=loader) - self.assertEqual(loader.uri, '/abc') + self._callFUT('/abc.ini', global_conf={}, _loader=loader) + self.assertEqual(loader.uri.path, '/abc.ini') self.assertEqual(len(loader.calls), 1) self.assertEqual(loader.calls[0]['op'], 'logging') self.assertEqual(loader.calls[0]['defaults'], {}) def test_it_global_conf_not_empty(self): loader = DummyLoader() - self._callFUT('/abc', global_conf={'key': 'val'}, _loader=loader) - self.assertEqual(loader.uri, '/abc') + self._callFUT('/abc.ini', global_conf={'key': 'val'}, _loader=loader) + self.assertEqual(loader.uri.path, '/abc.ini') self.assertEqual(len(loader.calls), 1) self.assertEqual(loader.calls[0]['op'], 'logging') self.assertEqual(loader.calls[0]['defaults'], {'key': 'val'}) @@ -166,13 +166,3 @@ class DummyRequest: def __init__(self, environ): self.environ = environ self.matchdict = {} - -class DummyConfigParser(object): - def read(self, x): - pass - - def has_section(self, name): - return True - -class DummyConfigParserModule(object): - ConfigParser = DummyConfigParser diff --git a/pyramid/tests/test_scripts/dummy.py b/pyramid/tests/test_scripts/dummy.py index 18810f8f3..2d2b0549f 100644 --- a/pyramid/tests/test_scripts/dummy.py +++ b/pyramid/tests/test_scripts/dummy.py @@ -81,29 +81,6 @@ class DummyMultiView(object): self.views = [(None, view, None) for view in views] self.__request_attrs__ = attrs -class DummyConfigParser(object): - def __init__(self, result, defaults=None): - self.result = result - self.defaults = defaults - - def read(self, filename): - self.filename = filename - - def items(self, section): - self.section = section - if self.result is None: - from pyramid.compat import configparser - raise configparser.NoSectionError(section) - return self.result - -class DummyConfigParserFactory(object): - items = None - - def __call__(self, defaults=None): - self.defaults = defaults - self.parser = DummyConfigParser(self.items, defaults) - return self.parser - class DummyCloser(object): def __call__(self): self.called = True @@ -171,7 +148,7 @@ class dummy_setup_logging(object): class DummyLoader(object): - def __init__(self, settings=None, app_settings=None, app=None): + def __init__(self, settings=None, app_settings=None, app=None, server=None): if not settings: settings = {} if not app_settings: @@ -179,10 +156,12 @@ class DummyLoader(object): self.settings = settings self.app_settings = app_settings self.app = app + self.server = server self.calls = [] def __call__(self, uri): - self.uri = uri + import plaster + self.uri = plaster.parse_uri(uri) return self def add_call(self, op, name, defaults): @@ -200,6 +179,10 @@ class DummyLoader(object): self.add_call('app_settings', name, defaults) return self.app_settings + def get_wsgi_server(self, name=None, defaults=None): + self.add_call('server', name, defaults) + return self.server + def setup_logging(self, defaults): self.add_call('logging', None, defaults) self.defaults = defaults diff --git a/pyramid/tests/test_scripts/test_prequest.py b/pyramid/tests/test_scripts/test_prequest.py index ec5119270..75d5cc198 100644 --- a/pyramid/tests/test_scripts/test_prequest.py +++ b/pyramid/tests/test_scripts/test_prequest.py @@ -33,7 +33,7 @@ class TestPRequestCommand(unittest.TestCase): [('Content-Type', 'text/html; charset=UTF-8')]) command.run() self.assertEqual(self._path_info, '/') - self.assertEqual(self.loader.uri, 'development.ini') + self.assertEqual(self.loader.uri.path, 'development.ini') self.assertEqual(self.loader.calls[0]['op'], 'logging') self.assertEqual(self.loader.calls[1]['op'], 'app') self.assertEqual(self.loader.calls[1]['name'], None) @@ -44,7 +44,7 @@ class TestPRequestCommand(unittest.TestCase): [('Content-Type', 'text/html; charset=UTF-8')]) command.run() self.assertEqual(self._path_info, '/abc') - self.assertEqual(self.loader.uri, 'development.ini') + self.assertEqual(self.loader.uri.path, 'development.ini') self.assertEqual(self._out, ['abc']) def test_command_has_bad_config_header(self): diff --git a/pyramid/tests/test_scripts/test_pserve.py b/pyramid/tests/test_scripts/test_pserve.py index d5578b3ea..485cf38cb 100644 --- a/pyramid/tests/test_scripts/test_pserve.py +++ b/pyramid/tests/test_scripts/test_pserve.py @@ -10,16 +10,10 @@ class TestPServeCommand(unittest.TestCase): def setUp(self): from pyramid.compat import NativeIO self.out_ = NativeIO() - self.config_factory = dummy.DummyConfigParserFactory() def out(self, msg): self.out_.write(msg) - def _get_server(*args, **kwargs): - def server(app): - return '' - return server - def _getTargetClass(self): from pyramid.scripts.pserve import PServeCommand return PServeCommand @@ -29,7 +23,8 @@ class TestPServeCommand(unittest.TestCase): effargs.extend(args) cmd = self._getTargetClass()(effargs) cmd.out = self.out - cmd.ConfigParser = self.config_factory + self.loader = dummy.DummyLoader() + cmd._get_config_loader = self.loader return cmd def test_run_no_args(self): @@ -38,41 +33,33 @@ class TestPServeCommand(unittest.TestCase): self.assertEqual(result, 2) self.assertEqual(self.out_.getvalue(), 'You must give a config file') - def test_config_vars_no_command(self): - inst = self._makeOne() - inst.args.config_uri = 'foo' - inst.args.config_vars = ['a=1', 'b=2'] - result = inst.get_config_vars() - self.assertEqual(result, {'a': '1', 'b': '2'}) - def test_parse_vars_good(self): inst = self._makeOne('development.ini', 'a=1', 'b=2') - inst.loadserver = self._get_server - app = dummy.DummyApp() - def get_app(*args, **kwargs): - app.global_conf = kwargs.get('global_conf', None) + def get_app(name, global_conf): + app.name = name + app.global_conf = global_conf + return app + self.loader.get_wsgi_app = get_app + self.loader.server = lambda x: x - inst.loadapp = get_app inst.run() self.assertEqual(app.global_conf, {'a': '1', 'b': '2'}) def test_parse_vars_bad(self): inst = self._makeOne('development.ini', 'a') - inst.loadserver = self._get_server self.assertRaises(ValueError, inst.run) def test_config_file_finds_watch_files(self): inst = self._makeOne('development.ini') - self.config_factory.items = [( - 'watch_files', - 'foo\n/baz\npyramid.tests.test_scripts:*.py', - )] - inst.pserve_file_config('/base/path.ini', global_conf={'a': '1'}) - self.assertEqual(self.config_factory.defaults, { + loader = self.loader('/base/path.ini') + loader.settings = {'pserve': { + 'watch_files': 'foo\n/baz\npyramid.tests.test_scripts:*.py', + }} + inst.pserve_file_config(loader, global_conf={'a': '1'}) + self.assertEqual(loader.calls[0]['defaults'], { 'a': '1', - 'here': os.path.abspath('/base'), }) self.assertEqual(inst.watch_files, set([ os.path.abspath('/base/foo'), @@ -82,28 +69,26 @@ class TestPServeCommand(unittest.TestCase): def test_config_file_finds_open_url(self): inst = self._makeOne('development.ini') - self.config_factory.items = [( - 'open_url', 'http://127.0.0.1:8080/', - )] - inst.pserve_file_config('/base/path.ini', global_conf={'a': '1'}) - self.assertEqual(self.config_factory.defaults, { + loader = self.loader('/base/path.ini') + loader.settings = {'pserve': { + 'open_url': 'http://127.0.0.1:8080/', + }} + inst.pserve_file_config(loader, global_conf={'a': '1'}) + self.assertEqual(loader.calls[0]['defaults'], { 'a': '1', - 'here': os.path.abspath('/base'), }) self.assertEqual(inst.open_url, 'http://127.0.0.1:8080/') - def test__guess_server_url(self): + def test_guess_server_url(self): inst = self._makeOne('development.ini') - self.config_factory.items = [( - 'port', '8080', - )] - url = inst._guess_server_url( - '/base/path.ini', 'main', global_conf={'a': '1'}) - self.assertEqual(self.config_factory.defaults, { + loader = self.loader('/base/path.ini') + loader.settings = {'server:foo': { + 'port': '8080', + }} + url = inst.guess_server_url(loader, 'foo', global_conf={'a': '1'}) + self.assertEqual(loader.calls[0]['defaults'], { 'a': '1', - 'here': os.path.abspath('/base'), }) - self.assertEqual(self.config_factory.parser.section, 'server:main') self.assertEqual(url, 'http://127.0.0.1:8080') def test_reload_call_hupper_with_correct_args(self): -- cgit v1.2.3 From db8aeae4c79b0bec3b8c28f5b060a75e453e1f98 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Thu, 30 Mar 2017 01:53:03 -0500 Subject: depend on plaster_pastedeploy --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 046341f13..3048428aa 100644 --- a/setup.py +++ b/setup.py @@ -35,11 +35,11 @@ install_requires = [ 'translationstring >= 0.4', # py3 compat 'PasteDeploy >= 1.5.0', # py3 compat 'plaster', + 'plaster_pastedeploy', 'hupper', ] tests_require = [ - 'plaster_pastedeploy', 'WebTest >= 1.3.1', # py3 compat 'zope.component >= 4.0', # py3 compat ] -- cgit v1.2.3 From fa8a9dcb11a7e2ec81fa041fd0292d7ce8e2138a Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Fri, 31 Mar 2017 00:09:51 -0500 Subject: add changelog for #2985 --- CHANGES.txt | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 7676a69f9..c617adf95 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,14 +1,32 @@ unreleased ========== -Features --------- +Major Features +-------------- + +- The file format used by all ``p*`` command line scripts such as ``pserve`` + and ``pshell``, as well as the ``pyramid.paster.bootstrap`` function + is now replaceable thanks to a new dependency on + `plaster `. + + For now, Pyramid is still shipping with integrated support for the + PasteDeploy INI format by depending on the ``plaster_pastedeploy`` binding. + + See https://github.com/Pylons/pyramid/pull/2985 - Added an execution policy hook to the request pipeline. An execution policy has the ability to control creation and execution of the request - objects before they enter rest of the pipeline. This means for a given - request that the policy may create more than one request for retry - purposes. See https://github.com/Pylons/pyramid/pull/2964 + objects before they enter rest of the pipeline. This means for a single + request environ the policy may create more than one request object. + + The first library to use this feature is + `pyramid_retry + `. + + See https://github.com/Pylons/pyramid/pull/2964 + +Features +-------- - Support an ``open_url`` config setting in the ``pserve`` section of the config file. This url is used to open a web browser when ``pserve --browser`` -- cgit v1.2.3 From f454b80b0f6e6442fa27e48b7e1e38c5a7cbef03 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Fri, 31 Mar 2017 01:49:42 -0500 Subject: add some simple notes about plaster in the narrative docs --- CHANGES.txt | 6 +++--- docs/glossary.rst | 8 ++++++++ docs/narr/paste.rst | 12 ++++++------ docs/narr/startup.rst | 9 ++++++++- 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index c617adf95..59196626d 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -7,7 +7,7 @@ Major Features - The file format used by all ``p*`` command line scripts such as ``pserve`` and ``pshell``, as well as the ``pyramid.paster.bootstrap`` function is now replaceable thanks to a new dependency on - `plaster `. + `plaster `_. For now, Pyramid is still shipping with integrated support for the PasteDeploy INI format by depending on the ``plaster_pastedeploy`` binding. @@ -16,12 +16,12 @@ Major Features - Added an execution policy hook to the request pipeline. An execution policy has the ability to control creation and execution of the request - objects before they enter rest of the pipeline. This means for a single + objects before they enter the rest of the pipeline. This means for a single request environ the policy may create more than one request object. The first library to use this feature is `pyramid_retry - `. + `_. See https://github.com/Pylons/pyramid/pull/2964 diff --git a/docs/glossary.rst b/docs/glossary.rst index 0a46fac3b..8f7ea70a1 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -366,6 +366,14 @@ Glossary :term:`WSGI` components together declaratively within an ``.ini`` file. It was developed by Ian Bicking. + plaster + `plaster `_ is + a library used by :app:`Pyramid` which acts as an abstraction between + command-line scripts and the file format used to load the :term:`WSGI` + components and application settings. By default :app:`Pyramid` ships + with the ``plaster_pastedeploy`` library installed which provides + integrated support for loading a :term:`PasteDeploy` INI file. + Chameleon `chameleon `_ is an attribute language template compiler which supports the :term:`ZPT` diff --git a/docs/narr/paste.rst b/docs/narr/paste.rst index 2d4e76e24..26cb1bfa5 100644 --- a/docs/narr/paste.rst +++ b/docs/narr/paste.rst @@ -26,12 +26,7 @@ documentation, see http://pythonpaste.org/deploy/. PasteDeploy ----------- -:term:`PasteDeploy` is the system that Pyramid uses to allow :term:`deployment -settings` to be specified using an ``.ini`` configuration file format. It also -allows the ``pserve`` command to work. Its configuration format provides a -convenient place to define application :term:`deployment settings` and WSGI -server settings, and its server runner allows you to stop and start a Pyramid -application easily. +:term:`plaster` is the system that Pyramid uses to load settings from configuration files. The most common format for these files is an ``.ini`` format structured in a way defined by :term:`PasteDeploy`. The format supports mechanisms to define WSGI app :term:`deployment settings`, WSGI server settings and logging. This allows the ``pserve`` command to work, allowing you to stop and start a Pyramid application easily. .. _pastedeploy_entry_points: @@ -96,3 +91,8 @@ applications, servers, and :term:`middleware` defined within the configuration file. The values in a ``[DEFAULT]`` section will be passed to your application's ``main`` function as ``global_config`` (see the reference to the ``main`` function in :ref:`init_py`). + +Alternative Configuration File Formats +-------------------------------------- + +It is possible to use different file formats with :app:`Pyramid` if you do not like :term:`PasteDeploy`. Under the hood all command-line scripts such as ``pserve`` and ``pshell`` pass the ``config_uri`` (e.g. ``development.ini`` or ``production.ini``) to the :term:`plaster` library which performs a lookup for an appropriate parser. For ``.ini`` files it uses PasteDeploy but you can register your own configuration formats that plaster will find instead. diff --git a/docs/narr/startup.rst b/docs/narr/startup.rst index cf4612602..29a75cba2 100644 --- a/docs/narr/startup.rst +++ b/docs/narr/startup.rst @@ -38,7 +38,14 @@ Here's a high-level time-ordered overview of what happens when you press begin to run and serve an application using the information contained within the ``development.ini`` file. -#. The framework finds a section named either ``[app:main]``, +#. ``pserve`` passes the ``development.ini`` path to :term:`plaster` which + finds an available configuration loader that recognizes the ``ini`` format. + +#. :term:`plaster` finds the ``plaster_pastedeploy`` library which binds + the :term:`PasteDeploy` library and returns a parser that can understand + the format. + +#. The :term:`PasteDeploy` finds a section named either ``[app:main]``, ``[pipeline:main]``, or ``[composite:main]`` in the ``.ini`` file. This section represents the configuration of a :term:`WSGI` application that will be served. If you're using a simple application (e.g., ``[app:main]``), the -- cgit v1.2.3 From a857ebbc65c0491f27d39c7a6c1ba485c99fee5d Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sun, 2 Apr 2017 12:48:22 -0500 Subject: add a failing test checking whether the threadlocal registry is active during config.include --- pyramid/tests/test_config/test_init.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pyramid/tests/test_config/test_init.py b/pyramid/tests/test_config/test_init.py index 0d5413d16..53c601537 100644 --- a/pyramid/tests/test_config/test_init.py +++ b/pyramid/tests/test_config/test_init.py @@ -817,6 +817,16 @@ pyramid.tests.test_config.dummy_include2""", self.assertEqual(results['root_package'], tests) self.assertEqual(results['package'], test_config) + def test_include_threadlocals_active(self): + from pyramid.tests import test_config + from pyramid.threadlocal import get_current_registry + stack = [] + def include(config): + stack.append(get_current_registry()) + config = self._makeOne() + config.include(include) + self.assertTrue(stack[0] is config.registry) + def test_action_branching_kw_is_None(self): config = self._makeOne(autocommit=True) self.assertEqual(config.action('discrim'), None) -- cgit v1.2.3 From f5ff7e0dd3a5b8bc1fd6978e361c9ab8391b6960 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sun, 2 Apr 2017 12:50:48 -0500 Subject: push the threadlocal registry while config.include executes --- pyramid/config/__init__.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pyramid/config/__init__.py b/pyramid/config/__init__.py index 304d3a85e..6c661aa59 100644 --- a/pyramid/config/__init__.py +++ b/pyramid/config/__init__.py @@ -753,6 +753,11 @@ class Configurator( .. versionadded:: 1.2 The ``route_prefix`` parameter. + .. versionchanged:: 1.9 + The included function is wrapped with a call to + :meth:`pyramid.config.Configurator.begin` and + :meth:`pyramid.config.Configurator.end` while it is executed. + """ # """ <-- emacs @@ -802,7 +807,11 @@ class Configurator( ) configurator.basepath = os.path.dirname(sourcefile) configurator.includepath = self.includepath + (spec,) - c(configurator) + self.begin() + try: + c(configurator) + finally: + self.end() def add_directive(self, name, directive, action_wrap=True): """ -- cgit v1.2.3 From bd1cce29c9d5e618c4682e7c5a37ea574aee9817 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sun, 2 Apr 2017 12:58:58 -0500 Subject: add changelog for #2989 --- CHANGES.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 7676a69f9..c8a87f625 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -18,6 +18,12 @@ Features requirement that the server is being run in this format so it may fail. See https://github.com/Pylons/pyramid/pull/2984 +- The threadlocals are now available inside any function invoked via + ``config.include``. This means the only config-time code that cannot rely + on threadlocals is code executed from non-actions inside the main. This + can be alleviated by invoking ``config.begin()`` and ``config.end()`` + appropriately. See https://github.com/Pylons/pyramid/pull/2989 + Bug Fixes --------- -- cgit v1.2.3 From 134ef7e8d17842e286528aeb8d2936579ed81053 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Fri, 16 Dec 2016 00:48:52 -0600 Subject: turn the Configurator into a context manager fixes #2872 --- README.rst | 8 +++---- pyramid/config/__init__.py | 40 ++++++++++++++++++++++++++++++++-- pyramid/tests/test_config/test_init.py | 16 ++++++++++++++ 3 files changed, 58 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index f05dd8bbf..302590fe1 100644 --- a/README.rst +++ b/README.rst @@ -31,10 +31,10 @@ and deployment more fun, more predictable, and more productive. return Response('Hello %(name)s!' % request.matchdict) if __name__ == '__main__': - config = Configurator() - config.add_route('hello', '/hello/{name}') - config.add_view(hello_world, route_name='hello') - app = config.make_wsgi_app() + with Configurator() as config: + config.add_route('hello', '/hello/{name}') + config.add_view(hello_world, route_name='hello') + app = config.make_wsgi_app() server = make_server('0.0.0.0', 8080, app) server.serve_forever() diff --git a/pyramid/config/__init__.py b/pyramid/config/__init__.py index 6c661aa59..bcd4b3904 100644 --- a/pyramid/config/__init__.py +++ b/pyramid/config/__init__.py @@ -110,6 +110,17 @@ class Configurator( A Configurator is used to configure a :app:`Pyramid` :term:`application registry`. + The Configurator lifecycle can be managed by using a context manager to + automatically handle calling :meth:`pyramid.config.Configurator.begin` and + :meth:`pyramid.config.Configurator.end` as well as + :meth:`pyramid.config.Configurator.commit`. + + .. code-block:: python + + with Configurator(settings=settings) as config: + config.add_route('home', '/') + app = config.make_wsgi_app() + If the ``registry`` argument is not ``None``, it must be an instance of the :class:`pyramid.registry.Registry` class representing the registry to configure. If ``registry`` is ``None``, the @@ -265,6 +276,11 @@ class Configurator( .. versionadded:: 1.6 The ``root_package`` argument. The ``response_factory`` argument. + + .. versionadded:: 1.9 + The ability to use the configurator as a context manager with the + ``with``-statement to make threadlocal configuration available for + further configuration with an implicit commit. """ manager = manager # for testing injection venusian = venusian # for testing injection @@ -646,12 +662,22 @@ class Configurator( _ctx = action_state # bw compat def commit(self): - """ Commit any pending configuration actions. If a configuration + """ + Commit any pending configuration actions. If a configuration conflict is detected in the pending configuration actions, this method will raise a :exc:`ConfigurationConflictError`; within the traceback of this error will be information about the source of the conflict, usually including file names and line numbers of the cause of the - configuration conflicts.""" + configuration conflicts. + + .. warning:: + You should think very carefully before manually invoking + ``commit()``. Especially not as part of any reusable configuration + methods. Normally it should only be done by an application author at + the end of configuration in order to override certain aspects of an + addon. + + """ self.begin() try: self.action_state.execute_actions(introspector=self.introspector) @@ -933,6 +959,16 @@ class Configurator( """ return self.manager.pop() + def __enter__(self): + self.begin() + return self + + def __exit__(self, exc_type, exc_value, exc_traceback): + self.end() + + if exc_value is None: + self.commit() + # this is *not* an action method (uses caller_package) def scan(self, package=None, categories=None, onerror=None, ignore=None, **kw): diff --git a/pyramid/tests/test_config/test_init.py b/pyramid/tests/test_config/test_init.py index 53c601537..ab584cc3d 100644 --- a/pyramid/tests/test_config/test_init.py +++ b/pyramid/tests/test_config/test_init.py @@ -141,6 +141,22 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(manager.pushed, pushed) self.assertEqual(manager.popped, True) + def test_context_manager(self): + from pyramid.config import Configurator + config = Configurator() + manager = DummyThreadLocalManager() + config.manager = manager + view = lambda r: None + with config as ctx: + self.assertTrue(config is ctx) + self.assertEqual(manager.pushed, + {'registry': config.registry, 'request': None}) + self.assertFalse(manager.popped) + config.add_view(view) + self.assertTrue(manager.popped) + config.add_view(view) # did not raise a conflict because of commit + config.commit() + def test_ctor_with_package_registry(self): import sys from pyramid.config import Configurator -- cgit v1.2.3 From 4c583e349a33937a217f1f09cfe01acbbf5edc38 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 8 Apr 2017 14:22:33 -0700 Subject: grammar fix --- docs/narr/hooks.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index 63279027a..81c8e81d7 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -1569,7 +1569,7 @@ event type. def __call__(self, event): return event.request.path.startswith(self.val) -Once you've created a subscriber predicate, it may registered via +Once you've created a subscriber predicate, it may be registered via :meth:`pyramid.config.Configurator.add_subscriber_predicate`. For example: .. code-block:: python -- cgit v1.2.3 From 1bd681193feef31d032c13e7022bc2d65d9e0a21 Mon Sep 17 00:00:00 2001 From: Jeremy Chen Date: Mon, 10 Apr 2017 14:55:46 +1000 Subject: replace deprecated cgi.escape() with html.escape() As suggested by https://docs.python.org/3.6/library/cgi.html cgi.escape() Deprecated since version 3.2: This function is unsafe because quote is false by default, and therefore deprecated. Use html.escape() instead. --- docs/tutorials/wiki2/src/views/tutorial/views/default.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/tutorials/wiki2/src/views/tutorial/views/default.py b/docs/tutorials/wiki2/src/views/tutorial/views/default.py index bb6300b75..0a05b33e6 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/views/default.py +++ b/docs/tutorials/wiki2/src/views/tutorial/views/default.py @@ -1,4 +1,4 @@ -import cgi +import html import re from docutils.core import publish_parts @@ -31,10 +31,10 @@ def view_page(request): exists = request.dbsession.query(Page).filter_by(name=word).all() if exists: view_url = request.route_url('view_page', pagename=word) - return '%s' % (view_url, cgi.escape(word)) + return '%s' % (view_url, html.escape(word)) else: add_url = request.route_url('add_page', pagename=word) - return '%s' % (add_url, cgi.escape(word)) + return '%s' % (add_url, html.escape(word)) content = publish_parts(page.data, writer_name='html')['html_body'] content = wikiwords.sub(add_link, content) -- cgit v1.2.3 From b9ee2a673a27ba045ac031b94b7d1cc9196844cf Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Mon, 10 Apr 2017 02:41:23 -0700 Subject: add execution policy to pyramid request processing diagrams --- docs/_static/pyramid_request_processing.graffle | 244 ++++++++++++++++++------ docs/_static/pyramid_request_processing.png | Bin 130688 -> 135179 bytes docs/_static/pyramid_request_processing.svg | 2 +- 3 files changed, 184 insertions(+), 62 deletions(-) diff --git a/docs/_static/pyramid_request_processing.graffle b/docs/_static/pyramid_request_processing.graffle index 56e4e13f2..5ccbf4ea4 100644 --- a/docs/_static/pyramid_request_processing.graffle +++ b/docs/_static/pyramid_request_processing.graffle @@ -58,9 +58,129 @@ 8 GraphicsList + + Class + LineGraphic + ControlPoints + + {0, 6.9840087890625} + {0, -9} + + FontInfo + + Color + + w + 0 + + Font + Helvetica + Size + 12 + + Head + + ID + 169378 + Info + 2 + + ID + 169517 + Layer + 0 + Points + + {155.00000381469727, 109.82143211364746} + {155.00000254313238, 133.68303707668386} + + Style + + stroke + + Bezier + + Color + + b + 0.0980392 + g + 0.0980392 + r + 0.0980392 + + HeadArrow + SharpArrow + Legacy + + LineType + 1 + TailArrow + 0 + + + Tail + + ID + 169516 + + + + Bounds + {{102.16667175292969, 87.276790618896484}, {105.66666412353516, 22.544641494750977}} + Class + ShapedGraphic + ID + 169516 + Layer + 0 + Magnets + + {0, 1} + {0, -1} + {1, 0} + {-1, 0} + + Shape + Rectangle + Style + + fill + + Color + + b + 0.999208 + g + 0.811343 + r + 0.644457 + + + shadow + + Draws + NO + ShadowVector + {2, 2} + + + Text + + Text + {\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400 +\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc + +\f0\fs20 \cf0 execution policy} + VerticalPad + 0 + + Bounds - {{238.74999618530273, 294.65604172230951}, {105.66668701171875, 18.656048080136394}} + {{238.74999618530273, 325.15604172230951}, {105.66668701171875, 18.656048080136394}} Class ShapedGraphic FontInfo @@ -146,8 +266,8 @@ 0 Points - {154.9999760464211, 209.11365574251681} - {239.8333613077798, 209.14732074737549} + {154.99997604642331, 241.21656873817005} + {239.8333613077798, 240.64732074737549} Style @@ -182,7 +302,7 @@ Bounds - {{239.83336130777977, 197.875}, {105.66666412353516, 22.544641494750977}} + {{239.83336130777977, 229.375}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID @@ -259,8 +379,8 @@ 0 Points - {344.41668319702148, 411.88506673894034} - {375.5, 411.77232108797347} + {344.41668319702148, 442.38506673894034} + {375.5, 442.27232108797347} Style @@ -317,8 +437,8 @@ 0 Points - {155.00000254313238, 459.27667544230695} - {238.5002713470962, 456.52468399152298} + {155.00000254313238, 489.77667544230695} + {238.5002713470962, 487.02468399152298} Style @@ -377,8 +497,8 @@ 0 Points - {155.00000254313238, 482.12574895537085} - {238.52297468463752, 508.35839132916635} + {155.00000254313238, 512.6257489553708} + {238.52297468463752, 538.85839132916635} Style @@ -413,7 +533,7 @@ Bounds - {{238.74999618530273, 275.99999999999994}, {105.75002924601222, 18.656048080136394}} + {{238.74999618530273, 306.49999999999994}, {105.75002924601222, 18.656048080136394}} Class ShapedGraphic FontInfo @@ -475,7 +595,7 @@ Bounds - {{238.74999618530273, 421.15071036499205}, {105.66668701171875, 18.656048080136394}} + {{238.74999618530273, 451.65071036499205}, {105.66668701171875, 18.656048080136394}} Class ShapedGraphic FontInfo @@ -537,7 +657,7 @@ Bounds - {{238.74999618530273, 312.65604172230951}, {105.66668701171875, 18.656048080136394}} + {{238.74999618530273, 343.15604172230951}, {105.66668701171875, 18.656048080136394}} Class ShapedGraphic FontInfo @@ -599,7 +719,7 @@ Bounds - {{238.74999618530273, 402.55704269887212}, {105.66668701171875, 18.656048080136394}} + {{238.74999618530273, 433.05704269887212}, {105.66668701171875, 18.656048080136394}} Class ShapedGraphic FontInfo @@ -659,7 +779,7 @@ Bounds - {{238.74999618530273, 383.90099016834085}, {105.66668701171875, 18.656048080136394}} + {{238.74999618530273, 414.40099016834085}, {105.66668701171875, 18.656048080136394}} Class ShapedGraphic FontInfo @@ -719,7 +839,7 @@ Bounds - {{238.74999618530273, 350.36561209044055}, {105.66668701171875, 33.089282989501953}} + {{238.74999618530273, 380.86561209044055}, {105.66668701171875, 33.089282989501953}} Class ShapedGraphic FontInfo @@ -779,7 +899,7 @@ Bounds - {{238.74999618530273, 331.26348241170439}, {105.66668701171875, 18.656048080136394}} + {{238.74999618530273, 361.76348241170439}, {105.66668701171875, 18.656048080136394}} Class ShapedGraphic FontInfo @@ -865,8 +985,8 @@ 0 Points - {155.00000254313238, 470.25295298442387} - {238.33861159880226, 482.4262543949045} + {155.00000254313238, 500.75295298442387} + {238.33861159880226, 512.9262543949045} Style @@ -901,7 +1021,7 @@ Bounds - {{238.83336130777977, 471.22620192028251}, {105.66666412353516, 22.544641494750977}} + {{238.83336130777977, 501.72620192028251}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID @@ -980,8 +1100,8 @@ 0 Points - {154.99998733539806, 128.68025330008533} - {239.83340199788393, 128.59152244387357} + {154.99998724282403, 165.00131810958592} + {239.83340199788393, 164.59152244387357} Style @@ -1016,7 +1136,7 @@ Bounds - {{239.83340199788395, 117.31920169649808}, {105.66666412353516, 22.544641494750977}} + {{239.83340199788395, 153.31920169649808}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID @@ -1074,7 +1194,7 @@ Bounds - {{102.1666056315114, 148.28868579864499}, {105.66669464111328, 33.08929443359375}} + {{102.1666056315114, 181.78868579864499}, {105.66669464111328, 33.08929443359375}} Class ShapedGraphic ID @@ -1125,7 +1245,7 @@ Bounds - {{102.1666056315114, 181.37798023223874}, {105.66669464111328, 17.244049072265625}} + {{102.1666056315114, 214.87798023223874}, {105.66669464111328, 17.244049072265625}} Class ShapedGraphic ID @@ -1193,7 +1313,7 @@ Bounds - {{102.16666158040482, 272}, {105.66666412353516, 33.08929443359375}} + {{102.16666158040482, 302.5}, {105.66666412353516, 33.08929443359375}} Class ShapedGraphic ID @@ -1244,7 +1364,7 @@ Bounds - {{102.16666158040482, 305.08929443359375}, {105.66666412353516, 17.244049072265625}} + {{102.16666158040482, 335.58929443359375}, {105.66666412353516, 17.244049072265625}} Class ShapedGraphic ID @@ -1321,8 +1441,8 @@ 0 Points - {238.74999618530282, 439.80675844512831} - {207.66666666666765, 385.656005859375} + {238.74999618530282, 470.30675844512831} + {207.66666666666765, 416.156005859375} Style @@ -1371,8 +1491,8 @@ 0 Points - {239.25039065750093, 276.57837549845181} - {207.66666666666777, 353.07514659563753} + {239.25039065750093, 307.07837549845181} + {207.66666666666777, 383.57514659563753} Style @@ -1436,8 +1556,8 @@ 0 Points - {155.00000254313238, 386.66442959065108} - {155.00000254313238, 422.21209462483216} + {155.00000254313238, 417.16442959065108} + {155.00000254313238, 452.71209462483216} Style @@ -1472,7 +1592,7 @@ Bounds - {{102.16667048136482, 353.07514659563753}, {105.66666412353516, 33.089282989501953}} + {{102.16667048136482, 383.57514659563753}, {105.66666412353516, 33.089282989501953}} Class ShapedGraphic ID @@ -1555,8 +1675,8 @@ 0 Points - {154.9999936421724, 258.44082431579938} - {238.8333613077798, 258.45536063967575} + {154.9999936421724, 288.94082431579938} + {238.8333613077798, 288.95536063967575} Style @@ -1969,7 +2089,7 @@ Bounds - {{375.5, 400.5}, {105.66666412353516, 22.544642175946908}} + {{375.5, 431}, {105.66666412353516, 22.544642175946908}} Class ShapedGraphic ID @@ -2053,8 +2173,8 @@ 0 Points - {155.00000170434049, 119.22767858295661} - {154.99995295206804, 148.28868579864499} + {155.00000157307935, 156.72767858475962} + {154.99995295206804, 181.78868579864499} Style @@ -2110,7 +2230,9 @@ Head ID - 169378 + 169516 + Info + 2 ID 169385 @@ -2119,7 +2241,7 @@ Points {155.00000254313238, 67.727678571434836} - {155.00000254313238, 96.18303707668386} + {155.00000381469727, 87.276790618896484} Style @@ -2156,7 +2278,7 @@ Bounds - {{102.16667048136482, 509.6179466247504}, {105.66666412353516, 22.544641494750977}} + {{102.16667048136482, 540.1179466247504}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID @@ -2209,7 +2331,7 @@ Bounds - {{239, 497.23589324949899}, {105.66666412353516, 22.544641494750977}} + {{239, 527.73589324949899}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID @@ -2262,7 +2384,7 @@ Bounds - {{239, 445.23589324949717}, {105.66666412353516, 22.544641494750977}} + {{239, 475.73589324949717}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID @@ -2315,7 +2437,7 @@ Bounds - {{102.16667048136482, 422.21209462483216}, {105.66666412353516, 22.544641494750977}} + {{102.16667048136482, 452.71209462483216}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID @@ -2368,7 +2490,7 @@ Bounds - {{238.83336130777977, 247.18303989230026}, {105.66666412353516, 22.544641494750977}} + {{238.83336130777977, 277.68303989230026}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID @@ -2421,7 +2543,7 @@ Bounds - {{102.16667048136482, 222.18303707668389}, {105.66666412353516, 22.544641494750977}} + {{102.16667048136482, 252.68303707668389}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID @@ -2474,7 +2596,7 @@ Bounds - {{102.16667048136482, 96.18303707668386}, {105.66666412353516, 22.544641494750977}} + {{102.16667048136482, 133.68303707668386}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID @@ -2609,8 +2731,8 @@ 0 Points - {154.99995295206804, 198.62202930450437} - {155.00000254313238, 222.18303707668389} + {154.99995295206804, 232.12202930450437} + {155.00000254313238, 252.68303707668389} Style @@ -2678,8 +2800,8 @@ 0 Points - {154.9999936421724, 245.22767856643924} - {154.9999936421724, 272} + {154.9999936421724, 275.72767856643924} + {154.9999936421724, 302.5} Style @@ -2743,8 +2865,8 @@ 0 Points - {154.9999936421724, 322.33334350585938} - {155.00000254313238, 353.07514659563753} + {154.9999936421724, 352.83334350585938} + {155.00000254313238, 383.57514659563753} Style @@ -2812,8 +2934,8 @@ 0 Points - {155.00000254313238, 444.75673611958314} - {155.00000254313238, 509.6179466247504} + {155.00000254313238, 475.25673611958314} + {155.00000254313238, 540.1179466247504} Style @@ -9868,7 +9990,7 @@ MasterSheets ModificationDate - 2016-04-13 08:32:47 +0000 + 2017-04-10 09:33:14 +0000 Modifier Steve Piercy NotesVisible @@ -9949,7 +10071,7 @@ Frame - {{35, 93}, {2284, 1325}} + {{35, 93}, {1632, 1325}} ListView OutlineWidth @@ -9963,15 +10085,15 @@ SidebarWidth 163 VisibleRegion - {{110.125, 77.875}, {239.125, 146.375}} + {{-27, 33.5}, {630.5, 593}} Zoom - 8 + 2 ZoomValues Request Processing + 2 8 - 4 diff --git a/docs/_static/pyramid_request_processing.png b/docs/_static/pyramid_request_processing.png index 2f44f4824..d62b172b0 100644 Binary files a/docs/_static/pyramid_request_processing.png and b/docs/_static/pyramid_request_processing.png differ diff --git a/docs/_static/pyramid_request_processing.svg b/docs/_static/pyramid_request_processing.svg index 03f6d56fa..38fa7348d 100644 --- a/docs/_static/pyramid_request_processing.svg +++ b/docs/_static/pyramid_request_processing.svg @@ -1,3 +1,3 @@ -2016-04-13 08:32ZRequest Processingno exceptionsmiddleware ingress tween ingresstraversalContextFoundtween egressresponse callbacksfinished callbacksmiddleware egressBeforeRenderRequest ProcessingLegendeventcallbackview deriverexternal process (middleware, tween)internal processview pipelinepredicatesview lookuproute predicatesURL dispatchNewRequestNewResponseview mapper ingressviewview mapper egressresponse adapterdecorators ingressdecorators egressauthorizationBeforeTraversalCSRF checks +2017-04-10 09:33ZRequest Processingno exceptionsmiddleware ingress tween ingresstraversalContextFoundtween egressresponse callbacksfinished callbacksmiddleware egressBeforeRenderRequest ProcessingLegendeventcallbackview deriverexternal process (middleware, tween)internal processview pipelinepredicatesview lookuproute predicatesURL dispatchNewRequestNewResponseview mapper ingressviewview mapper egressresponse adapterdecorators ingressdecorators egressauthorizationBeforeTraversalCSRF checksexecution policy -- cgit v1.2.3 From a2c7c7a49bceeaaab2853e7e73c3671979d4c9ed Mon Sep 17 00:00:00 2001 From: Matthew Wilkes Date: Mon, 5 Dec 2016 12:16:26 +0100 Subject: Create a new ICSRF implementation for getting CSRF tokens, split out from the session machinery. Adds configuration of this to the csrf_options configurator commands. Make the default implementation a fallback to the old one. Documentation patches for new best practices given updates CSRF implementation. --- CHANGES.txt | 12 ++ docs/api/csrf.rst | 18 ++ docs/api/interfaces.rst | 3 + docs/api/session.rst | 4 - docs/narr/security.rst | 191 +++++++++++++++++++++ docs/narr/sessions.rst | 175 ------------------- pyramid/config/security.py | 16 ++ pyramid/config/views.py | 14 +- pyramid/csrf.py | 286 ++++++++++++++++++++++++++++++++ pyramid/interfaces.py | 29 +++- pyramid/predicates.py | 2 +- pyramid/renderers.py | 1 - pyramid/session.py | 155 +---------------- pyramid/tests/test_config/test_views.py | 2 +- pyramid/tests/test_csrf.py | 172 +++++++++++++++++++ pyramid/tests/test_session.py | 4 +- pyramid/viewderivers.py | 2 +- 17 files changed, 731 insertions(+), 355 deletions(-) create mode 100644 docs/api/csrf.rst create mode 100644 pyramid/csrf.py create mode 100644 pyramid/tests/test_csrf.py diff --git a/CHANGES.txt b/CHANGES.txt index c8a87f625..9d6264688 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -24,6 +24,14 @@ Features can be alleviated by invoking ``config.begin()`` and ``config.end()`` appropriately. See https://github.com/Pylons/pyramid/pull/2989 +- A new CSRF implementation, :class:`pyramid.csrf.SessionCSRF` has been added, + which deleagates all CSRF generation to the current session, following the + old API for this. A ``get_csrf_token()`` method is now available in template + global scope, to make it easy for template developers to get the current CSRF + token without adding it to Python code. + See https://github.com/Pylons/pyramid/pull/2854 + + Bug Fixes --------- @@ -50,3 +58,7 @@ Backward Incompatibilities Documentation Changes --------------------- + +- Retrieving CSRF token from the session has been deprecated, in favor of + equivalent methods in :mod:`pyramid.csrf`. + See https://github.com/Pylons/pyramid/pull/2854 diff --git a/docs/api/csrf.rst b/docs/api/csrf.rst new file mode 100644 index 000000000..3125bdac9 --- /dev/null +++ b/docs/api/csrf.rst @@ -0,0 +1,18 @@ +.. _csrf_module: + +:mod:`pyramid.csrf` +------------------- + +.. automodule:: pyramid.csrf + + .. autofunction:: get_csrf_token + + .. autofunction:: new_csrf_token + + .. autoclass:: SessionCSRF + :members: + + .. autofunction:: check_csrf_origin + + .. autofunction:: check_csrf_token + diff --git a/docs/api/interfaces.rst b/docs/api/interfaces.rst index a212ba7a9..2ca472616 100644 --- a/docs/api/interfaces.rst +++ b/docs/api/interfaces.rst @@ -44,6 +44,9 @@ Other Interfaces .. autointerface:: IRoutePregenerator :members: + .. autointerface:: ICSRF + :members: + .. autointerface:: ISession :members: diff --git a/docs/api/session.rst b/docs/api/session.rst index 56c4f52d7..53bae7c52 100644 --- a/docs/api/session.rst +++ b/docs/api/session.rst @@ -9,10 +9,6 @@ .. autofunction:: signed_deserialize - .. autofunction:: check_csrf_origin - - .. autofunction:: check_csrf_token - .. autofunction:: SignedCookieSessionFactory .. autofunction:: UnencryptedCookieSessionFactoryConfig diff --git a/docs/narr/security.rst b/docs/narr/security.rst index 77e7fd707..b4fb3b8a8 100644 --- a/docs/narr/security.rst +++ b/docs/narr/security.rst @@ -765,3 +765,194 @@ which would allow the attacker to control the content of the payload. Re-using a secret across two different subsystems might drop the security of signing to zero. Keys should not be re-used across different contexts where an attacker has the possibility of providing a chosen plaintext. + +Preventing Cross-Site Request Forgery Attacks +--------------------------------------------- + +`Cross-site request forgery +`_ attacks are a +phenomenon whereby a user who is logged in to your website might inadvertantly +load a URL because it is linked from, or embedded in, an attacker's website. +If the URL is one that may modify or delete data, the consequences can be dire. + +You can avoid most of these attacks by issuing a unique token to the browser +and then requiring that it be present in all potentially unsafe requests. +:app:`Pyramid` sessions provide facilities to create and check CSRF tokens. + +To use CSRF tokens, you must first enable a :term:`session factory` as +described in :ref:`using_the_default_session_factory` or +:ref:`using_alternate_session_factories`. + +.. index:: + single: csrf.get_csrf_token + +Using the ``csrf.get_csrf_token`` Method +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To get the current CSRF token, use the +:data:`pyramid.csrf.get_csrf_token` method. + +.. code-block:: python + + from pyramid.csrf import get_csrf_token + token = get_csrf_token(request) + +The ``get_csrf_token()`` method accepts a single argument: the request. It +returns a CSRF *token* string. If ``get_csrf_token()`` or ``new_csrf_token()`` +was invoked previously for this user, then the existing token will be returned. +If no CSRF token previously existed for this user, then a new token will be set +into the session and returned. The newly created token will be opaque and +randomized. + + +Using the ``get_csrf_token`` global in templates +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Templates have a ``get_csrf_token()`` method inserted into their globals, which +allows you to get the current token without modifying the view code. This +method takes no arguments and returns a CSRF token string. You can use the +returned token as the value of a hidden field in a form that posts to a method +that requires elevated privileges, or supply it as a request header in AJAX +requests. + +For example, include the CSRF token as a hidden field: + +.. code-block:: html + +
+ + +
+ +Or include it as a header in a jQuery AJAX request: + +.. code-block:: javascript + + var csrfToken = "${get_csrf_token()}"; + $.ajax({ + type: "POST", + url: "/myview", + headers: { 'X-CSRF-Token': csrfToken } + }).done(function() { + alert("Deleted"); + }); + +The handler for the URL that receives the request should then require that the +correct CSRF token is supplied. + +.. index:: + single: csrf.new_csrf_token + +Using the ``csrf.new_csrf_token`` Method +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To explicitly create a new CSRF token, use the ``csrf.new_csrf_token()`` +method. This differs only from ``csrf.get_csrf_token()`` inasmuch as it +clears any existing CSRF token, creates a new CSRF token, sets the token into +the user, and returns the token. + +.. code-block:: python + + from pyramid.csrf import get_csrf_token + token = new_csrf_token() + +.. note:: + + It is not possible to force a new CSRF token from a template. If you + want to regenerate your CSRF token then do it in the view code and return + the new token as part of the context. + +Checking CSRF Tokens Manually +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In request handling code, you can check the presence and validity of a CSRF +token with :func:`pyramid.session.check_csrf_token`. If the token is valid, it +will return ``True``, otherwise it will raise ``HTTPBadRequest``. Optionally, +you can specify ``raises=False`` to have the check return ``False`` instead of +raising an exception. + +By default, it checks for a POST parameter named ``csrf_token`` or a header +named ``X-CSRF-Token``. + +.. code-block:: python + + from pyramid.session import check_csrf_token + + def myview(request): + # Require CSRF Token + check_csrf_token(request) + + # ... + +.. _auto_csrf_checking: + +Checking CSRF Tokens Automatically +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 1.7 + +:app:`Pyramid` supports automatically checking CSRF tokens on requests with an +unsafe method as defined by RFC2616. Any other request may be checked manually. +This feature can be turned on globally for an application using the +:meth:`pyramid.config.Configurator.set_default_csrf_options` directive. +For example: + +.. code-block:: python + + from pyramid.config import Configurator + + config = Configurator() + config.set_default_csrf_options(require_csrf=True) + +CSRF checking may be explicitly enabled or disabled on a per-view basis using +the ``require_csrf`` view option. A value of ``True`` or ``False`` will +override the default set by ``set_default_csrf_options``. For example: + +.. code-block:: python + + @view_config(route_name='hello', require_csrf=False) + def myview(request): + # ... + +When CSRF checking is active, the token and header used to find the +supplied CSRF token will be ``csrf_token`` and ``X-CSRF-Token``, respectively, +unless otherwise overridden by ``set_default_csrf_options``. The token is +checked against the value in ``request.POST`` which is the submitted form body. +If this value is not present, then the header will be checked. + +In addition to token based CSRF checks, if the request is using HTTPS then the +automatic CSRF checking will also check the referrer of the request to ensure +that it matches one of the trusted origins. By default the only trusted origin +is the current host, however additional origins may be configured by setting +``pyramid.csrf_trusted_origins`` to a list of domain names (and ports if they +are non standard). If a host in the list of domains starts with a ``.`` then +that will allow all subdomains as well as the domain without the ``.``. + +If CSRF checks fail then a :class:`pyramid.exceptions.BadCSRFToken` or +:class:`pyramid.exceptions.BadCSRFOrigin` exception will be raised. This +exception may be caught and handled by an :term:`exception view` but, by +default, will result in a ``400 Bad Request`` response being sent to the +client. + +Checking CSRF Tokens with a View Predicate +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 1.7 + Use the ``require_csrf`` option or read :ref:`auto_csrf_checking` instead + to have :class:`pyramid.exceptions.BadCSRFToken` exceptions raised. + +A convenient way to require a valid CSRF token for a particular view is to +include ``check_csrf=True`` as a view predicate. See +:meth:`pyramid.config.Configurator.add_view`. + +.. code-block:: python + + @view_config(request_method='POST', check_csrf=True, ...) + def myview(request): + ... + +.. note:: + A mismatch of a CSRF token is treated like any other predicate miss, and the + predicate system, when it doesn't find a view, raises ``HTTPNotFound`` + instead of ``HTTPBadRequest``, so ``check_csrf=True`` behavior is different + from calling :func:`pyramid.session.check_csrf_token`. diff --git a/docs/narr/sessions.rst b/docs/narr/sessions.rst index 5b24201a9..90b5f4585 100644 --- a/docs/narr/sessions.rst +++ b/docs/narr/sessions.rst @@ -321,178 +321,3 @@ flash storage. single: preventing cross-site request forgery attacks single: cross-site request forgery attacks, prevention -Preventing Cross-Site Request Forgery Attacks ---------------------------------------------- - -`Cross-site request forgery -`_ attacks are a -phenomenon whereby a user who is logged in to your website might inadvertantly -load a URL because it is linked from, or embedded in, an attacker's website. -If the URL is one that may modify or delete data, the consequences can be dire. - -You can avoid most of these attacks by issuing a unique token to the browser -and then requiring that it be present in all potentially unsafe requests. -:app:`Pyramid` sessions provide facilities to create and check CSRF tokens. - -To use CSRF tokens, you must first enable a :term:`session factory` as -described in :ref:`using_the_default_session_factory` or -:ref:`using_alternate_session_factories`. - -.. index:: - single: session.get_csrf_token - -Using the ``session.get_csrf_token`` Method -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To get the current CSRF token from the session, use the -``session.get_csrf_token()`` method. - -.. code-block:: python - - token = request.session.get_csrf_token() - -The ``session.get_csrf_token()`` method accepts no arguments. It returns a -CSRF *token* string. If ``session.get_csrf_token()`` or -``session.new_csrf_token()`` was invoked previously for this session, then the -existing token will be returned. If no CSRF token previously existed for this -session, then a new token will be set into the session and returned. The newly -created token will be opaque and randomized. - -You can use the returned token as the value of a hidden field in a form that -posts to a method that requires elevated privileges, or supply it as a request -header in AJAX requests. - -For example, include the CSRF token as a hidden field: - -.. code-block:: html - -
- - -
- -Or include it as a header in a jQuery AJAX request: - -.. code-block:: javascript - - var csrfToken = ${request.session.get_csrf_token()}; - $.ajax({ - type: "POST", - url: "/myview", - headers: { 'X-CSRF-Token': csrfToken } - }).done(function() { - alert("Deleted"); - }); - -The handler for the URL that receives the request should then require that the -correct CSRF token is supplied. - -.. index:: - single: session.new_csrf_token - -Using the ``session.new_csrf_token`` Method -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To explicitly create a new CSRF token, use the ``session.new_csrf_token()`` -method. This differs only from ``session.get_csrf_token()`` inasmuch as it -clears any existing CSRF token, creates a new CSRF token, sets the token into -the session, and returns the token. - -.. code-block:: python - - token = request.session.new_csrf_token() - -Checking CSRF Tokens Manually -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -In request handling code, you can check the presence and validity of a CSRF -token with :func:`pyramid.session.check_csrf_token`. If the token is valid, it -will return ``True``, otherwise it will raise ``HTTPBadRequest``. Optionally, -you can specify ``raises=False`` to have the check return ``False`` instead of -raising an exception. - -By default, it checks for a POST parameter named ``csrf_token`` or a header -named ``X-CSRF-Token``. - -.. code-block:: python - - from pyramid.session import check_csrf_token - - def myview(request): - # Require CSRF Token - check_csrf_token(request) - - # ... - -.. _auto_csrf_checking: - -Checking CSRF Tokens Automatically -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. versionadded:: 1.7 - -:app:`Pyramid` supports automatically checking CSRF tokens on requests with an -unsafe method as defined by RFC2616. Any other request may be checked manually. -This feature can be turned on globally for an application using the -:meth:`pyramid.config.Configurator.set_default_csrf_options` directive. -For example: - -.. code-block:: python - - from pyramid.config import Configurator - - config = Configurator() - config.set_default_csrf_options(require_csrf=True) - -CSRF checking may be explicitly enabled or disabled on a per-view basis using -the ``require_csrf`` view option. A value of ``True`` or ``False`` will -override the default set by ``set_default_csrf_options``. For example: - -.. code-block:: python - - @view_config(route_name='hello', require_csrf=False) - def myview(request): - # ... - -When CSRF checking is active, the token and header used to find the -supplied CSRF token will be ``csrf_token`` and ``X-CSRF-Token``, respectively, -unless otherwise overridden by ``set_default_csrf_options``. The token is -checked against the value in ``request.POST`` which is the submitted form body. -If this value is not present, then the header will be checked. - -In addition to token based CSRF checks, if the request is using HTTPS then the -automatic CSRF checking will also check the referrer of the request to ensure -that it matches one of the trusted origins. By default the only trusted origin -is the current host, however additional origins may be configured by setting -``pyramid.csrf_trusted_origins`` to a list of domain names (and ports if they -are non standard). If a host in the list of domains starts with a ``.`` then -that will allow all subdomains as well as the domain without the ``.``. - -If CSRF checks fail then a :class:`pyramid.exceptions.BadCSRFToken` or -:class:`pyramid.exceptions.BadCSRFOrigin` exception will be raised. This -exception may be caught and handled by an :term:`exception view` but, by -default, will result in a ``400 Bad Request`` response being sent to the -client. - -Checking CSRF Tokens with a View Predicate -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. deprecated:: 1.7 - Use the ``require_csrf`` option or read :ref:`auto_csrf_checking` instead - to have :class:`pyramid.exceptions.BadCSRFToken` exceptions raised. - -A convenient way to require a valid CSRF token for a particular view is to -include ``check_csrf=True`` as a view predicate. See -:meth:`pyramid.config.Configurator.add_view`. - -.. code-block:: python - - @view_config(request_method='POST', check_csrf=True, ...) - def myview(request): - ... - -.. note:: - A mismatch of a CSRF token is treated like any other predicate miss, and the - predicate system, when it doesn't find a view, raises ``HTTPNotFound`` - instead of ``HTTPBadRequest``, so ``check_csrf=True`` behavior is different - from calling :func:`pyramid.session.check_csrf_token`. diff --git a/pyramid/config/security.py b/pyramid/config/security.py index 33593376b..102a61e0c 100644 --- a/pyramid/config/security.py +++ b/pyramid/config/security.py @@ -3,16 +3,21 @@ from zope.interface import implementer from pyramid.interfaces import ( IAuthorizationPolicy, IAuthenticationPolicy, + ICSRFPolicy, IDefaultCSRFOptions, IDefaultPermission, PHASE1_CONFIG, PHASE2_CONFIG, ) +from pyramid.csrf import csrf_token_template_global +from pyramid.csrf import SessionCSRF +from pyramid.events import BeforeRender from pyramid.exceptions import ConfigurationError from pyramid.util import action_method from pyramid.util import as_sorted_tuple + class SecurityConfiguratorMixin(object): @action_method def set_authentication_policy(self, policy): @@ -165,6 +170,7 @@ class SecurityConfiguratorMixin(object): @action_method def set_default_csrf_options( self, + implementation=None, require_csrf=True, token='csrf_token', header='X-CSRF-Token', @@ -174,6 +180,10 @@ class SecurityConfiguratorMixin(object): """ Set the default CSRF options used by subsequent view registrations. + ``implementation`` is a class that implements the + :meth:`pyramid.interfaces.ICSRFPolicy` interface that will be used for all + CSRF functionality. Default: :class:`pyramid.csrf.SessionCSRF`. + ``require_csrf`` controls whether CSRF checks will be automatically enabled on each view in the application. This value is used as the fallback when ``require_csrf`` is left at the default of ``None`` on @@ -207,7 +217,10 @@ class SecurityConfiguratorMixin(object): options = DefaultCSRFOptions( require_csrf, token, header, safe_methods, callback, ) + if implementation is None: + implementation = SessionCSRF() def register(): + self.registry.registerUtility(implementation, ICSRFPolicy) self.registry.registerUtility(options, IDefaultCSRFOptions) intr = self.introspectable('default csrf view options', None, @@ -218,9 +231,12 @@ class SecurityConfiguratorMixin(object): intr['header'] = header intr['safe_methods'] = as_sorted_tuple(safe_methods) intr['callback'] = callback + + self.add_subscriber(csrf_token_template_global, [BeforeRender]) self.action(IDefaultCSRFOptions, register, order=PHASE1_CONFIG, introspectables=(intr,)) + @implementer(IDefaultCSRFOptions) class DefaultCSRFOptions(object): def __init__(self, require_csrf, token, header, safe_methods, callback): diff --git a/pyramid/config/views.py b/pyramid/config/views.py index 65c9da585..7a383be44 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -641,16 +641,14 @@ class ViewsConfiguratorMixin(object): 'check name'. If the value provided is ``True``, ``csrf_token`` will be used as the check name. - If CSRF checking is performed, the checked value will be the value - of ``request.params[check_name]``. This value will be compared - against the value of ``request.session.get_csrf_token()``, and the - check will pass if these two values are the same. If the check - passes, the associated view will be permitted to execute. If the + If CSRF checking is performed, the checked value will be the value of + ``request.params[check_name]``. This value will be compared against + the value of ``impl.get_csrf_token()`` (where ``impl`` is an + implementation of :meth:`pyramid.interfaces.ICSRFPolicy`), and the + check will pass if these two values are the same. If the check + passes, the associated view will be permitted to execute. If the check fails, the associated view will not be permitted to execute. - Note that using this feature requires a :term:`session factory` to - have been configured. - .. versionadded:: 1.4a2 physical_path diff --git a/pyramid/csrf.py b/pyramid/csrf.py new file mode 100644 index 000000000..c373079a4 --- /dev/null +++ b/pyramid/csrf.py @@ -0,0 +1,286 @@ +from functools import partial +import uuid + +from zope.interface import implementer + +from pyramid.compat import ( + urlparse, + bytes_ +) +from pyramid.exceptions import ( + BadCSRFOrigin, + BadCSRFToken, +) +from pyramid.interfaces import ICSRFPolicy +from pyramid.settings import aslist +from pyramid.util import ( + is_same_domain, + strings_differ +) + + +@implementer(ICSRFPolicy) +class SessionCSRF(object): + """ The default CSRF implementation, which mimics the behavior from older + versions of Pyramid. The ``new_csrf_token`` and ``get_csrf_token`` methods + are indirected to the underlying session implementation. + + Note that using this CSRF implementation requires that + a :term:`session factory` is configured. + + .. versionadded :: 1.8a1 + """ + def new_csrf_token(self, request): + """ Sets a new CSRF token into the session and returns it. """ + return request.session.new_csrf_token() + + def get_csrf_token(self, request): + """ Returns the currently active CSRF token from the session, generating + a new one if needed.""" + return request.session.get_csrf_token() + + def check_csrf_token(self, request, supplied_token): + """ Returns True if supplied_token is the same value as get_csrf_token + returns for this request. """ + expected = self.get_csrf_token(request) + return not strings_differ( + bytes_(expected, 'ascii'), + bytes_(supplied_token, 'ascii'), + ) + +@implementer(ICSRFPolicy) +class CookieCSRF(object): + """ An alternative CSRF implementation that stores its information in + unauthenticated cookies, known as the 'Double Submit Cookie' method in the + OWASP CSRF guidelines. This gives some additional flexibility with regards + to scalingas the tokens can be generated and verified by a front-end server. + + .. versionadded :: 1.8a1 + """ + + def __init__(self, cookie_name='csrf_token', secure=False, httponly=False, + domain=None, path='/'): + self.cookie_name = cookie_name + self.secure = secure + self.httponly = httponly + self.domain = domain + self.path = path + + def new_csrf_token(self, request): + """ Sets a new CSRF token into the request and returns it. """ + token = uuid.uuid4().hex + def set_cookie(request, response): + response.set_cookie( + self.cookie_name, + token, + httponly=self.httponly, + secure=self.secure, + domain=self.domain, + path=self.path, + overwrite=True, + ) + request.add_response_callback(set_cookie) + return token + + def get_csrf_token(self, request): + """ Returns the currently active CSRF token by checking the cookies + sent with the current request.""" + token = request.cookies.get(self.cookie_name) + if not token: + token = self.new_csrf_token(request) + return token + + def check_csrf_token(self, request, supplied_token): + """ Returns True if supplied_token is the same value as get_csrf_token + returns for this request. """ + expected = self.get_csrf_token(request) + return not strings_differ( + bytes_(expected, 'ascii'), + bytes_(supplied_token, 'ascii'), + ) + + +def csrf_token_template_global(event): + request = event.get('request', None) + try: + registry = request.registry + except AttributeError: + return + else: + csrf = registry.getUtility(ICSRFPolicy) + if csrf is not None: + event['get_csrf_token'] = partial(csrf.get_csrf_token, request) + + +def get_csrf_token(request): + """ Get the currently active CSRF token for the request passed, generating + a new one using ``new_csrf_token(request)`` if one does not exist. This + calls the equivalent method in the chosen CSRF protection implementation. + + .. versionadded :: 1.8a1 + """ + registry = request.registry + csrf = registry.getUtility(ICSRFPolicy) + if csrf is not None: + return csrf.get_csrf_token(request) + + +def new_csrf_token(request): + """ Generate a new CSRF token for the request passed and persist it in an + implementation defined manner. This calls the equivalent method in the + chosen CSRF protection implementation. + + .. versionadded :: 1.8a1 + """ + registry = request.registry + csrf = registry.getUtility(ICSRFPolicy) + if csrf is not None: + return csrf.new_csrf_token(request) + + +def check_csrf_token(request, + token='csrf_token', + header='X-CSRF-Token', + raises=True): + """ Check the CSRF token returned by the :meth:`pyramid.interfaces.ICSRFPolicy` + implementation against the value in ``request.POST.get(token)`` (if a POST + request) or ``request.headers.get(header)``. If a ``token`` keyword is not + supplied to this function, the string ``csrf_token`` will be used to look + up the token in ``request.POST``. If a ``header`` keyword is not supplied + to this function, the string ``X-CSRF-Token`` will be used to look up the + token in ``request.headers``. + + If the value supplied by post or by header doesn't match the value supplied + by ``impl.get_csrf_token()`` (where ``impl`` is an implementation of + :meth:`pyramid.interfaces.ICSRFPolicy`), and ``raises`` is ``True``, this + function will raise an :exc:`pyramid.exceptions.BadCSRFToken` exception. If + the values differ and ``raises`` is ``False``, this function will return + ``False``. If the CSRF check is successful, this function will return + ``True`` unconditionally. + + See :ref:`auto_csrf_checking` for information about how to secure your + application automatically against CSRF attacks. + + .. versionadded:: 1.4a2 + + .. versionchanged:: 1.7a1 + A CSRF token passed in the query string of the request is no longer + considered valid. It must be passed in either the request body or + a header. + + .. versionchanged:: 1.8a1 + Moved from pyramid.session to pyramid.csrf + """ + supplied_token = "" + # We first check the headers for a csrf token, as that is significantly + # cheaper than checking the POST body + if header is not None: + supplied_token = request.headers.get(header, "") + + # If this is a POST/PUT/etc request, then we'll check the body to see if it + # has a token. We explicitly use request.POST here because CSRF tokens + # should never appear in an URL as doing so is a security issue. We also + # explicitly check for request.POST here as we do not support sending form + # encoded data over anything but a request.POST. + if supplied_token == "" and token is not None: + supplied_token = request.POST.get(token, "") + + policy = request.registry.getUtility(ICSRFPolicy) + if not policy.check_csrf_token(request, supplied_token): + if raises: + raise BadCSRFToken('check_csrf_token(): Invalid token') + return False + return True + + +def check_csrf_origin(request, trusted_origins=None, raises=True): + """ + Check the Origin of the request to see if it is a cross site request or + not. + + If the value supplied by the Origin or Referer header isn't one of the + trusted origins and ``raises`` is ``True``, this function will raise a + :exc:`pyramid.exceptions.BadCSRFOrigin` exception but if ``raises`` is + ``False`` this function will return ``False`` instead. If the CSRF origin + checks are successful this function will return ``True`` unconditionally. + + Additional trusted origins may be added by passing a list of domain (and + ports if nonstandard like `['example.com', 'dev.example.com:8080']`) in + with the ``trusted_origins`` parameter. If ``trusted_origins`` is ``None`` + (the default) this list of additional domains will be pulled from the + ``pyramid.csrf_trusted_origins`` setting. + + Note that this function will do nothing if request.scheme is not https. + + .. versionadded:: 1.7 + + .. versionchanged:: 1.8a1 + Moved from pyramid.session to pyramid.csrf + """ + def _fail(reason): + if raises: + raise BadCSRFOrigin(reason) + else: + return False + + if request.scheme == "https": + # Suppose user visits http://example.com/ + # An active network attacker (man-in-the-middle, MITM) sends a + # POST form that targets https://example.com/detonate-bomb/ and + # submits it via JavaScript. + # + # The attacker will need to provide a CSRF cookie and token, but + # that's no problem for a MITM when we cannot make any assumptions + # about what kind of session storage is being used. So the MITM can + # circumvent the CSRF protection. This is true for any HTTP connection, + # but anyone using HTTPS expects better! For this reason, for + # https://example.com/ we need additional protection that treats + # http://example.com/ as completely untrusted. Under HTTPS, + # Barth et al. found that the Referer header is missing for + # same-domain requests in only about 0.2% of cases or less, so + # we can use strict Referer checking. + + # Determine the origin of this request + origin = request.headers.get("Origin") + if origin is None: + origin = request.referrer + + # Fail if we were not able to locate an origin at all + if not origin: + return _fail("Origin checking failed - no Origin or Referer.") + + # Parse our origin so we we can extract the required information from + # it. + originp = urlparse.urlparse(origin) + + # Ensure that our Referer is also secure. + if originp.scheme != "https": + return _fail( + "Referer checking failed - Referer is insecure while host is " + "secure." + ) + + # Determine which origins we trust, which by default will include the + # current origin. + if trusted_origins is None: + trusted_origins = aslist( + request.registry.settings.get( + "pyramid.csrf_trusted_origins", []) + ) + + if request.host_port not in set(["80", "443"]): + trusted_origins.append("{0.domain}:{0.host_port}".format(request)) + else: + trusted_origins.append(request.domain) + + # Actually check to see if the request's origin matches any of our + # trusted origins. + if not any(is_same_domain(originp.netloc, host) + for host in trusted_origins): + reason = ( + "Referer checking failed - {0} does not match any trusted " + "origins." + ) + return _fail(reason.format(origin)) + + return True diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index bbb4754e4..f58ee8b58 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -981,19 +981,30 @@ class ISession(IDict): :meth:`pyramid.interfaces.ISession.flash` """ - def new_csrf_token(): - """ Create and set into the session a new, random cross-site request - forgery protection token. Return the token. It will be a string.""" - def get_csrf_token(): - """ Return a random cross-site request forgery protection token. It - will be a string. If a token was previously added to the session via +class ICSRFPolicy(Interface): + """ An object that offers the ability to verify CSRF tokens and generate + new ones""" + + def new_csrf_token(request): + """ Create and return a new, random cross-site request forgery + protection token. Return the token. It will be a string.""" + + def get_csrf_token(request): + """ Return a cross-site request forgery protection token. It + will be a string. If a token was previously set for this user via ``new_csrf_token``, that token will be returned. If no CSRF token - was previously set into the session, ``new_csrf_token`` will be - called, which will create and set a token, and this token will be - returned. + was previously set, ``new_csrf_token`` will be called, which will + create and set a token, and this token will be returned. """ + def check_csrf_token(request, supplied_token): + """ Returns a boolean that represents if supplied_token is a valid CSRF + token for this request. Comparing strings for equality must be done + using :func:`pyramid.utils.strings_differ` to avoid timing attacks. + """ + + class IIntrospector(Interface): def get(category_name, discriminator, default=None): """ Get the IIntrospectable related to the category_name and the diff --git a/pyramid/predicates.py b/pyramid/predicates.py index 7c3a778ca..3d7bb1b4b 100644 --- a/pyramid/predicates.py +++ b/pyramid/predicates.py @@ -4,7 +4,7 @@ from pyramid.exceptions import ConfigurationError from pyramid.compat import is_nonstr_iter -from pyramid.session import check_csrf_token +from pyramid.csrf import check_csrf_token from pyramid.traversal import ( find_interface, traversal_path, diff --git a/pyramid/renderers.py b/pyramid/renderers.py index 47705d5d9..7d667ba7b 100644 --- a/pyramid/renderers.py +++ b/pyramid/renderers.py @@ -447,7 +447,6 @@ class RendererHelper(object): registry = self.registry registry.notify(system_values) - result = renderer(value, system_values) return result diff --git a/pyramid/session.py b/pyramid/session.py index 47b80f617..b1ad25410 100644 --- a/pyramid/session.py +++ b/pyramid/session.py @@ -16,19 +16,11 @@ from pyramid.compat import ( text_, bytes_, native_, - urlparse, ) -from pyramid.exceptions import ( - BadCSRFOrigin, - BadCSRFToken, -) from pyramid.interfaces import ISession -from pyramid.settings import aslist -from pyramid.util import ( - is_same_domain, - strings_differ, -) +from pyramid.util import strings_differ + def manage_accessed(wrapped): """ Decorator which causes a cookie to be renewed when an accessor @@ -109,149 +101,6 @@ def signed_deserialize(serialized, secret, hmac=hmac): return pickle.loads(pickled) -def check_csrf_origin(request, trusted_origins=None, raises=True): - """ - Check the Origin of the request to see if it is a cross site request or - not. - - If the value supplied by the Origin or Referer header isn't one of the - trusted origins and ``raises`` is ``True``, this function will raise a - :exc:`pyramid.exceptions.BadCSRFOrigin` exception but if ``raises`` is - ``False`` this function will return ``False`` instead. If the CSRF origin - checks are successful this function will return ``True`` unconditionally. - - Additional trusted origins may be added by passing a list of domain (and - ports if nonstandard like `['example.com', 'dev.example.com:8080']`) in - with the ``trusted_origins`` parameter. If ``trusted_origins`` is ``None`` - (the default) this list of additional domains will be pulled from the - ``pyramid.csrf_trusted_origins`` setting. - - Note that this function will do nothing if request.scheme is not https. - - .. versionadded:: 1.7 - """ - def _fail(reason): - if raises: - raise BadCSRFOrigin(reason) - else: - return False - - if request.scheme == "https": - # Suppose user visits http://example.com/ - # An active network attacker (man-in-the-middle, MITM) sends a - # POST form that targets https://example.com/detonate-bomb/ and - # submits it via JavaScript. - # - # The attacker will need to provide a CSRF cookie and token, but - # that's no problem for a MITM when we cannot make any assumptions - # about what kind of session storage is being used. So the MITM can - # circumvent the CSRF protection. This is true for any HTTP connection, - # but anyone using HTTPS expects better! For this reason, for - # https://example.com/ we need additional protection that treats - # http://example.com/ as completely untrusted. Under HTTPS, - # Barth et al. found that the Referer header is missing for - # same-domain requests in only about 0.2% of cases or less, so - # we can use strict Referer checking. - - # Determine the origin of this request - origin = request.headers.get("Origin") - if origin is None: - origin = request.referrer - - # Fail if we were not able to locate an origin at all - if not origin: - return _fail("Origin checking failed - no Origin or Referer.") - - # Parse our origin so we we can extract the required information from - # it. - originp = urlparse.urlparse(origin) - - # Ensure that our Referer is also secure. - if originp.scheme != "https": - return _fail( - "Referer checking failed - Referer is insecure while host is " - "secure." - ) - - # Determine which origins we trust, which by default will include the - # current origin. - if trusted_origins is None: - trusted_origins = aslist( - request.registry.settings.get( - "pyramid.csrf_trusted_origins", []) - ) - - if request.host_port not in set(["80", "443"]): - trusted_origins.append("{0.domain}:{0.host_port}".format(request)) - else: - trusted_origins.append(request.domain) - - # Actually check to see if the request's origin matches any of our - # trusted origins. - if not any(is_same_domain(originp.netloc, host) - for host in trusted_origins): - reason = ( - "Referer checking failed - {0} does not match any trusted " - "origins." - ) - return _fail(reason.format(origin)) - - return True - - -def check_csrf_token(request, - token='csrf_token', - header='X-CSRF-Token', - raises=True): - """ Check the CSRF token in the request's session against the value in - ``request.POST.get(token)`` (if a POST request) or - ``request.headers.get(header)``. If a ``token`` keyword is not supplied to - this function, the string ``csrf_token`` will be used to look up the token - in ``request.POST``. If a ``header`` keyword is not supplied to this - function, the string ``X-CSRF-Token`` will be used to look up the token in - ``request.headers``. - - If the value supplied by post or by header doesn't match the value - supplied by ``request.session.get_csrf_token()``, and ``raises`` is - ``True``, this function will raise an - :exc:`pyramid.exceptions.BadCSRFToken` exception. - If the values differ and ``raises`` is ``False``, this function will - return ``False``. If the CSRF check is successful, this function will - return ``True`` unconditionally. - - Note that using this function requires that a :term:`session factory` is - configured. - - See :ref:`auto_csrf_checking` for information about how to secure your - application automatically against CSRF attacks. - - .. versionadded:: 1.4a2 - - .. versionchanged:: 1.7a1 - A CSRF token passed in the query string of the request is no longer - considered valid. It must be passed in either the request body or - a header. - """ - supplied_token = "" - # If this is a POST/PUT/etc request, then we'll check the body to see if it - # has a token. We explicitly use request.POST here because CSRF tokens - # should never appear in an URL as doing so is a security issue. We also - # explicitly check for request.POST here as we do not support sending form - # encoded data over anything but a request.POST. - if token is not None: - supplied_token = request.POST.get(token, "") - - # If we were unable to locate a CSRF token in a request body, then we'll - # check to see if there are any headers that have a value for us. - if supplied_token == "" and header is not None: - supplied_token = request.headers.get(header, "") - - expected_token = request.session.get_csrf_token() - if strings_differ(bytes_(expected_token), bytes_(supplied_token)): - if raises: - raise BadCSRFToken('check_csrf_token(): Invalid token') - return False - return True class PickleSerializer(object): """ A serializer that uses the pickle protocol to dump Python diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py index 211632730..45495f1fa 100644 --- a/pyramid/tests/test_config/test_views.py +++ b/pyramid/tests/test_config/test_views.py @@ -2373,7 +2373,7 @@ class TestViewsConfigurationMixin(unittest.TestCase): view = lambda r: 'OK' config.set_default_csrf_options(require_csrf=True) config.add_view(view, context=Exception, renderer=null_renderer) - view_intr = introspector.introspectables[1] + view_intr = introspector.introspectables[-1] self.assertTrue(view_intr.type_name, 'view') self.assertEqual(view_intr['callable'], view) derived_view = view_intr['derived_callable'] diff --git a/pyramid/tests/test_csrf.py b/pyramid/tests/test_csrf.py new file mode 100644 index 000000000..a74d2a07b --- /dev/null +++ b/pyramid/tests/test_csrf.py @@ -0,0 +1,172 @@ +import unittest + +from pyramid.config import Configurator +from pyramid.csrf import CookieCSRF, SessionCSRF, get_csrf_token, new_csrf_token +from pyramid.events import BeforeRender +from pyramid.interfaces import ICSRFPolicy +from pyramid.tests.test_view import BaseTest as ViewBaseTest + + +class CSRFTokenTests(ViewBaseTest, unittest.TestCase): + class DummyCSRF(object): + def new_csrf_token(self, request): + return 'e5e9e30a08b34ff9842ff7d2b958c14b' + + def get_csrf_token(self, request): + return '02821185e4c94269bdc38e6eeae0a2f8' + + def test_csrf_token_passed_to_template(self): + config = Configurator() + config.set_default_csrf_options(implementation=self.DummyCSRF) + config.commit() + + request = self._makeRequest() + request.registry = config.registry + + before = BeforeRender({'request': request}, {}) + config.registry.notify(before) + self.assertIn('get_csrf_token', before) + self.assertEqual( + before['get_csrf_token'](), + '02821185e4c94269bdc38e6eeae0a2f8' + ) + + def test_simple_api_for_tokens_from_python(self): + config = Configurator() + config.set_default_csrf_options(implementation=self.DummyCSRF) + config.commit() + + request = self._makeRequest() + request.registry = config.registry + self.assertEqual( + get_csrf_token(request), + '02821185e4c94269bdc38e6eeae0a2f8' + ) + self.assertEqual( + new_csrf_token(request), + 'e5e9e30a08b34ff9842ff7d2b958c14b' + ) + + +class SessionCSRFTests(unittest.TestCase): + class MockSession(object): + def new_csrf_token(self): + return 'e5e9e30a08b34ff9842ff7d2b958c14b' + + def get_csrf_token(self): + return '02821185e4c94269bdc38e6eeae0a2f8' + + def test_session_csrf_implementation_delegates_to_session(self): + config = Configurator() + config.set_default_csrf_options(implementation=SessionCSRF) + config.commit() + + request = DummyRequest(config.registry, session=self.MockSession()) + self.assertEqual( + config.registry.getUtility(ICSRFPolicy).get_csrf_token(request), + '02821185e4c94269bdc38e6eeae0a2f8' + ) + self.assertEqual( + config.registry.getUtility(ICSRFPolicy).new_csrf_token(request), + 'e5e9e30a08b34ff9842ff7d2b958c14b' + ) + + +class CookieCSRFTests(unittest.TestCase): + + def test_get_cookie_csrf_with_no_existing_cookie_sets_cookies(self): + config = Configurator() + config.set_default_csrf_options(implementation=CookieCSRF()) + config.commit() + + response = MockResponse() + request = DummyRequest(config.registry, response=response) + + token = config.registry.getUtility(ICSRFPolicy).get_csrf_token(request) + self.assertEqual( + response.called_args, + ('csrf_token', token), + ) + self.assertEqual( + response.called_kwargs, + { + 'secure': False, + 'httponly': False, + 'domain': None, + 'path': '/', + 'overwrite': True + } + ) + + def test_existing_cookie_csrf_does_not_set_cookie(self): + config = Configurator() + config.set_default_csrf_options(implementation=CookieCSRF()) + config.commit() + + response = MockResponse() + request = DummyRequest(config.registry, response=response) + request.cookies = {'csrf_token': 'e6f325fee5974f3da4315a8ccf4513d2'} + + token = config.registry.getUtility(ICSRFPolicy).get_csrf_token(request) + self.assertEqual( + token, + 'e6f325fee5974f3da4315a8ccf4513d2' + ) + self.assertEqual( + response.called_args, + (), + ) + self.assertEqual( + response.called_kwargs, + {} + ) + + def test_new_cookie_csrf_with_existing_cookie_sets_cookies(self): + config = Configurator() + config.set_default_csrf_options(implementation=CookieCSRF()) + config.commit() + + response = MockResponse() + request = DummyRequest(config.registry, response=response) + request.cookies = {'csrf_token': 'e6f325fee5974f3da4315a8ccf4513d2'} + + token = config.registry.getUtility(ICSRFPolicy).new_csrf_token(request) + self.assertEqual( + response.called_args, + ('csrf_token', token), + ) + self.assertEqual( + response.called_kwargs, + { + 'secure': False, + 'httponly': False, + 'domain': None, + 'path': '/', + 'overwrite': True + } + ) + + +class DummyRequest(object): + registry = None + session = None + cookies = {} + + def __init__(self, registry, session=None, response=None): + self.registry = registry + self.session = session + self.response = response + + def add_response_callback(self, callback): + callback(self, self.response) + + +class MockResponse(object): + def __init__(self): + self.called_args = () + self.called_kwargs = {} + + def set_cookie(self, *args, **kwargs): + self.called_args = args + self.called_kwargs = kwargs + return diff --git a/pyramid/tests/test_session.py b/pyramid/tests/test_session.py index 3a308d08b..b51dccecc 100644 --- a/pyramid/tests/test_session.py +++ b/pyramid/tests/test_session.py @@ -661,7 +661,7 @@ class Test_signed_deserialize(unittest.TestCase): class Test_check_csrf_token(unittest.TestCase): def _callFUT(self, *args, **kwargs): - from ..session import check_csrf_token + from ..csrf import check_csrf_token return check_csrf_token(*args, **kwargs) def test_success_token(self): @@ -709,7 +709,7 @@ class Test_check_csrf_token(unittest.TestCase): class Test_check_csrf_origin(unittest.TestCase): def _callFUT(self, *args, **kwargs): - from ..session import check_csrf_origin + from ..csrf import check_csrf_origin return check_csrf_origin(*args, **kwargs) def test_success_with_http(self): diff --git a/pyramid/viewderivers.py b/pyramid/viewderivers.py index 4eb0ce704..d2869b162 100644 --- a/pyramid/viewderivers.py +++ b/pyramid/viewderivers.py @@ -6,7 +6,7 @@ from zope.interface import ( ) from pyramid.security import NO_PERMISSION_REQUIRED -from pyramid.session import ( +from pyramid.csrf import ( check_csrf_origin, check_csrf_token, ) -- cgit v1.2.3 From 313c251497f6cdb3e5ca961a8092a2356aa502fc Mon Sep 17 00:00:00 2001 From: Jure Cerjak Date: Mon, 5 Dec 2016 16:06:08 +0100 Subject: Fix tests and documentation in various places, and feedback following review regarding naming of variables and code cleanup. --- docs/api/csrf.rst | 10 +- docs/api/interfaces.rst | 2 +- docs/narr/security.rst | 34 ++- docs/narr/sessions.rst | 4 +- pyramid/config/views.py | 2 +- pyramid/csrf.py | 35 ++- pyramid/tests/test_config/test_views.py | 1 + pyramid/tests/test_csrf.py | 367 +++++++++++++++++++++++++++----- pyramid/tests/test_session.py | 138 ------------ pyramid/tests/test_viewderivers.py | 1 + 10 files changed, 369 insertions(+), 225 deletions(-) diff --git a/docs/api/csrf.rst b/docs/api/csrf.rst index 3125bdac9..89fb0c4b2 100644 --- a/docs/api/csrf.rst +++ b/docs/api/csrf.rst @@ -5,14 +5,16 @@ .. automodule:: pyramid.csrf + .. autoclass:: SessionCSRF + :members: + + .. autoclass:: CookieCSRF + :members: + .. autofunction:: get_csrf_token .. autofunction:: new_csrf_token - .. autoclass:: SessionCSRF - :members: - .. autofunction:: check_csrf_origin .. autofunction:: check_csrf_token - diff --git a/docs/api/interfaces.rst b/docs/api/interfaces.rst index 2ca472616..b88209a36 100644 --- a/docs/api/interfaces.rst +++ b/docs/api/interfaces.rst @@ -44,7 +44,7 @@ Other Interfaces .. autointerface:: IRoutePregenerator :members: - .. autointerface:: ICSRF + .. autointerface:: ICSRFPolicy :members: .. autointerface:: ISession diff --git a/docs/narr/security.rst b/docs/narr/security.rst index b4fb3b8a8..6962a0fe3 100644 --- a/docs/narr/security.rst +++ b/docs/narr/security.rst @@ -146,7 +146,7 @@ For example, the following view declaration protects the view named # config is an instance of pyramid.config.Configurator config.add_view('mypackage.views.blog_entry_add_view', - name='add_entry.html', + name='add_entry.html', context='mypackage.resources.Blog', permission='add') @@ -725,7 +725,7 @@ object that implements the following interface: """ Return ``True`` if any of the ``principals`` is allowed the ``permission`` in the current ``context``, else return ``False`` """ - + def principals_allowed_by_permission(self, context, permission): """ Return a set of principal identifiers allowed by the ``permission`` in ``context``. This behavior is optional; if you @@ -777,11 +777,27 @@ If the URL is one that may modify or delete data, the consequences can be dire. You can avoid most of these attacks by issuing a unique token to the browser and then requiring that it be present in all potentially unsafe requests. -:app:`Pyramid` sessions provide facilities to create and check CSRF tokens. +:app:`Pyramid` provides facilities to create and check CSRF tokens. + +By default :app:`Pyramid` comes with a session-based CSRF implementation +:class:`pyramid.csrf.SessionCSRF`. To use it, you must first enable +a :term:`session factory` as described in +:ref:`using_the_default_session_factory` or +:ref:`using_alternate_session_factories`. Alternatively, you can use +a cookie-based implementation :class:`pyramid.csrf.CookieCSRF` which gives +some additional flexibility as it does not require a session for each user. +You can also define your own implementation of +:class:`pyramid.interfaces.ICSRFPolicy` and register it with the +:meth:`pyramid.config.Configurator.set_default_csrf_options` directive. -To use CSRF tokens, you must first enable a :term:`session factory` as -described in :ref:`using_the_default_session_factory` or -:ref:`using_alternate_session_factories`. +For example: + +.. code-block:: python + + from pyramid.config import Configurator + + config = Configurator() + config.set_default_csrf_options(implementation=MyCustomCSRFPolicy()) .. index:: single: csrf.get_csrf_token @@ -866,7 +882,7 @@ Checking CSRF Tokens Manually ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In request handling code, you can check the presence and validity of a CSRF -token with :func:`pyramid.session.check_csrf_token`. If the token is valid, it +token with :func:`pyramid.csrf.check_csrf_token`. If the token is valid, it will return ``True``, otherwise it will raise ``HTTPBadRequest``. Optionally, you can specify ``raises=False`` to have the check return ``False`` instead of raising an exception. @@ -876,7 +892,7 @@ named ``X-CSRF-Token``. .. code-block:: python - from pyramid.session import check_csrf_token + from pyramid.csrf import check_csrf_token def myview(request): # Require CSRF Token @@ -955,4 +971,4 @@ include ``check_csrf=True`` as a view predicate. See A mismatch of a CSRF token is treated like any other predicate miss, and the predicate system, when it doesn't find a view, raises ``HTTPNotFound`` instead of ``HTTPBadRequest``, so ``check_csrf=True`` behavior is different - from calling :func:`pyramid.session.check_csrf_token`. + from calling :func:`pyramid.csrf.check_csrf_token`. diff --git a/docs/narr/sessions.rst b/docs/narr/sessions.rst index 90b5f4585..86fe2a139 100644 --- a/docs/narr/sessions.rst +++ b/docs/narr/sessions.rst @@ -12,8 +12,7 @@ application. This chapter describes how to configure sessions, what session implementations :app:`Pyramid` provides out of the box, how to store and retrieve data from -sessions, and two session-specific features: flash messages, and cross-site -request forgery attack prevention. +sessions, and a session-specific feature: flash messages. .. index:: single: session factory (default) @@ -320,4 +319,3 @@ flash storage. .. index:: single: preventing cross-site request forgery attacks single: cross-site request forgery attacks, prevention - diff --git a/pyramid/config/views.py b/pyramid/config/views.py index 7a383be44..4ebd014de 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -643,7 +643,7 @@ class ViewsConfiguratorMixin(object): If CSRF checking is performed, the checked value will be the value of ``request.params[check_name]``. This value will be compared against - the value of ``impl.get_csrf_token()`` (where ``impl`` is an + the value of ``policy.get_csrf_token()`` (where ``policy`` is an implementation of :meth:`pyramid.interfaces.ICSRFPolicy`), and the check will pass if these two values are the same. If the check passes, the associated view will be permitted to execute. If the diff --git a/pyramid/csrf.py b/pyramid/csrf.py index c373079a4..7adbc9fee 100644 --- a/pyramid/csrf.py +++ b/pyramid/csrf.py @@ -53,11 +53,12 @@ class CookieCSRF(object): """ An alternative CSRF implementation that stores its information in unauthenticated cookies, known as the 'Double Submit Cookie' method in the OWASP CSRF guidelines. This gives some additional flexibility with regards - to scalingas the tokens can be generated and verified by a front-end server. - + to scaling as the tokens can be generated and verified by a front-end + server. + .. versionadded :: 1.8a1 """ - + def __init__(self, cookie_name='csrf_token', secure=False, httponly=False, domain=None, path='/'): self.cookie_name = cookie_name @@ -108,8 +109,7 @@ def csrf_token_template_global(event): return else: csrf = registry.getUtility(ICSRFPolicy) - if csrf is not None: - event['get_csrf_token'] = partial(csrf.get_csrf_token, request) + event['get_csrf_token'] = partial(csrf.get_csrf_token, request) def get_csrf_token(request): @@ -121,8 +121,7 @@ def get_csrf_token(request): """ registry = request.registry csrf = registry.getUtility(ICSRFPolicy) - if csrf is not None: - return csrf.get_csrf_token(request) + return csrf.get_csrf_token(request) def new_csrf_token(request): @@ -134,25 +133,25 @@ def new_csrf_token(request): """ registry = request.registry csrf = registry.getUtility(ICSRFPolicy) - if csrf is not None: - return csrf.new_csrf_token(request) + return csrf.new_csrf_token(request) def check_csrf_token(request, token='csrf_token', header='X-CSRF-Token', raises=True): - """ Check the CSRF token returned by the :meth:`pyramid.interfaces.ICSRFPolicy` - implementation against the value in ``request.POST.get(token)`` (if a POST - request) or ``request.headers.get(header)``. If a ``token`` keyword is not - supplied to this function, the string ``csrf_token`` will be used to look - up the token in ``request.POST``. If a ``header`` keyword is not supplied - to this function, the string ``X-CSRF-Token`` will be used to look up the - token in ``request.headers``. + """ Check the CSRF token returned by the + :class:`pyramid.interfaces.ICSRFPolicy` implementation against the value in + ``request.POST.get(token)`` (if a POST request) or + ``request.headers.get(header)``. If a ``token`` keyword is not supplied to + this function, the string ``csrf_token`` will be used to look up the token + in ``request.POST``. If a ``header`` keyword is not supplied to this + function, the string ``X-CSRF-Token`` will be used to look up the token in + ``request.headers``. If the value supplied by post or by header doesn't match the value supplied - by ``impl.get_csrf_token()`` (where ``impl`` is an implementation of - :meth:`pyramid.interfaces.ICSRFPolicy`), and ``raises`` is ``True``, this + by ``policy.get_csrf_token()`` (where ``policy`` is an implementation of + :class:`pyramid.interfaces.ICSRFPolicy`), and ``raises`` is ``True``, this function will raise an :exc:`pyramid.exceptions.BadCSRFToken` exception. If the values differ and ``raises`` is ``False``, this function will return ``False``. If the CSRF check is successful, this function will return diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py index 45495f1fa..0816d9958 100644 --- a/pyramid/tests/test_config/test_views.py +++ b/pyramid/tests/test_config/test_views.py @@ -18,6 +18,7 @@ class TestViewsConfigurationMixin(unittest.TestCase): def _makeOne(self, *arg, **kw): from pyramid.config import Configurator config = Configurator(*arg, **kw) + config.set_default_csrf_options(require_csrf=False) return config def _getViewCallable(self, config, ctx_iface=None, exc_iface=None, diff --git a/pyramid/tests/test_csrf.py b/pyramid/tests/test_csrf.py index a74d2a07b..1b3f3fc3b 100644 --- a/pyramid/tests/test_csrf.py +++ b/pyramid/tests/test_csrf.py @@ -1,54 +1,109 @@ import unittest +from zope.interface.interfaces import ComponentLookupError + +from pyramid import testing from pyramid.config import Configurator -from pyramid.csrf import CookieCSRF, SessionCSRF, get_csrf_token, new_csrf_token from pyramid.events import BeforeRender -from pyramid.interfaces import ICSRFPolicy -from pyramid.tests.test_view import BaseTest as ViewBaseTest -class CSRFTokenTests(ViewBaseTest, unittest.TestCase): - class DummyCSRF(object): - def new_csrf_token(self, request): - return 'e5e9e30a08b34ff9842ff7d2b958c14b' +class Test_get_csrf_token(unittest.TestCase): + def setUp(self): + self.config = testing.setUp() - def get_csrf_token(self, request): - return '02821185e4c94269bdc38e6eeae0a2f8' + def _callFUT(self, *args, **kwargs): + from pyramid.csrf import get_csrf_token + return get_csrf_token(*args, **kwargs) + + def test_no_csrf_utility_registered(self): + request = testing.DummyRequest() + + with self.assertRaises(ComponentLookupError): + self._callFUT(request) + + def test_success(self): + self.config.set_default_csrf_options(implementation=DummyCSRF()) + request = testing.DummyRequest() + + csrf_token = self._callFUT(request) + + self.assertEquals(csrf_token, '02821185e4c94269bdc38e6eeae0a2f8') + + +class Test_new_csrf_token(unittest.TestCase): + def setUp(self): + self.config = testing.setUp() + + def _callFUT(self, *args, **kwargs): + from pyramid.csrf import new_csrf_token + return new_csrf_token(*args, **kwargs) + + def test_no_csrf_utility_registered(self): + request = testing.DummyRequest() + + with self.assertRaises(ComponentLookupError): + self._callFUT(request) + + def test_success(self): + self.config.set_default_csrf_options(implementation=DummyCSRF()) + request = testing.DummyRequest() + + csrf_token = self._callFUT(request) + + self.assertEquals(csrf_token, 'e5e9e30a08b34ff9842ff7d2b958c14b') + + +class Test_csrf_token_template_global(unittest.TestCase): + def setUp(self): + self.config = testing.setUp() + + def _callFUT(self, *args, **kwargs): + from pyramid.csrf import csrf_token_template_global + return csrf_token_template_global(*args, **kwargs) + + def test_event_is_missing_request(self): + event = BeforeRender({}, {}) + + self._callFUT(event) + + self.assertNotIn('get_csrf_token', event) + + def test_request_is_missing_registry(self): + request = DummyRequest(registry=None) + del request.registry + del request.__class__.registry + event = BeforeRender({'request': request}, {}) + + self._callFUT(event) + + self.assertNotIn('get_csrf_token', event) + + def test_csrf_utility_not_registered(self): + request = testing.DummyRequest() + event = BeforeRender({'request': request}, {}) + + with self.assertRaises(ComponentLookupError): + self._callFUT(event) def test_csrf_token_passed_to_template(self): config = Configurator() - config.set_default_csrf_options(implementation=self.DummyCSRF) + config.set_default_csrf_options(implementation=DummyCSRF()) config.commit() - request = self._makeRequest() + request = testing.DummyRequest() request.registry = config.registry before = BeforeRender({'request': request}, {}) config.registry.notify(before) + self.assertIn('get_csrf_token', before) self.assertEqual( before['get_csrf_token'](), '02821185e4c94269bdc38e6eeae0a2f8' ) - def test_simple_api_for_tokens_from_python(self): - config = Configurator() - config.set_default_csrf_options(implementation=self.DummyCSRF) - config.commit() - - request = self._makeRequest() - request.registry = config.registry - self.assertEqual( - get_csrf_token(request), - '02821185e4c94269bdc38e6eeae0a2f8' - ) - self.assertEqual( - new_csrf_token(request), - 'e5e9e30a08b34ff9842ff7d2b958c14b' - ) - -class SessionCSRFTests(unittest.TestCase): +class TestSessionCSRF(unittest.TestCase): class MockSession(object): def new_csrf_token(self): return 'e5e9e30a08b34ff9842ff7d2b958c14b' @@ -56,33 +111,75 @@ class SessionCSRFTests(unittest.TestCase): def get_csrf_token(self): return '02821185e4c94269bdc38e6eeae0a2f8' - def test_session_csrf_implementation_delegates_to_session(self): + def _makeOne(self): + from pyramid.csrf import SessionCSRF + return SessionCSRF() + + def test_register_session_csrf_policy(self): + from pyramid.csrf import SessionCSRF + from pyramid.interfaces import ICSRFPolicy + config = Configurator() - config.set_default_csrf_options(implementation=SessionCSRF) + config.set_default_csrf_options(implementation=self._makeOne()) config.commit() - request = DummyRequest(config.registry, session=self.MockSession()) + policy = config.registry.queryUtility(ICSRFPolicy) + + self.assertTrue(isinstance(policy, SessionCSRF)) + + def test_session_csrf_implementation_delegates_to_session(self): + policy = self._makeOne() + request = DummyRequest(session=self.MockSession()) + self.assertEqual( - config.registry.getUtility(ICSRFPolicy).get_csrf_token(request), + policy.get_csrf_token(request), '02821185e4c94269bdc38e6eeae0a2f8' ) self.assertEqual( - config.registry.getUtility(ICSRFPolicy).new_csrf_token(request), + policy.new_csrf_token(request), 'e5e9e30a08b34ff9842ff7d2b958c14b' ) + def test_verifying_token_invalid(self): + policy = self._makeOne() + request = DummyRequest(session=self.MockSession()) -class CookieCSRFTests(unittest.TestCase): + result = policy.check_csrf_token(request, 'invalid-token') + self.assertFalse(result) + + def test_verifying_token_valid(self): + policy = self._makeOne() + request = DummyRequest(session=self.MockSession()) + + result = policy.check_csrf_token( + request, '02821185e4c94269bdc38e6eeae0a2f8') + self.assertTrue(result) + + +class TestCookieCSRF(unittest.TestCase): + def _makeOne(self): + from pyramid.csrf import CookieCSRF + return CookieCSRF() + + def test_register_cookie_csrf_policy(self): + from pyramid.csrf import CookieCSRF + from pyramid.interfaces import ICSRFPolicy - def test_get_cookie_csrf_with_no_existing_cookie_sets_cookies(self): config = Configurator() - config.set_default_csrf_options(implementation=CookieCSRF()) + config.set_default_csrf_options(implementation=self._makeOne()) config.commit() + policy = config.registry.queryUtility(ICSRFPolicy) + + self.assertTrue(isinstance(policy, CookieCSRF)) + + def test_get_cookie_csrf_with_no_existing_cookie_sets_cookies(self): response = MockResponse() - request = DummyRequest(config.registry, response=response) + request = DummyRequest(response=response) + + policy = self._makeOne() + token = policy.get_csrf_token(request) - token = config.registry.getUtility(ICSRFPolicy).get_csrf_token(request) self.assertEqual( response.called_args, ('csrf_token', token), @@ -99,15 +196,13 @@ class CookieCSRFTests(unittest.TestCase): ) def test_existing_cookie_csrf_does_not_set_cookie(self): - config = Configurator() - config.set_default_csrf_options(implementation=CookieCSRF()) - config.commit() - response = MockResponse() - request = DummyRequest(config.registry, response=response) + request = DummyRequest(response=response) request.cookies = {'csrf_token': 'e6f325fee5974f3da4315a8ccf4513d2'} - token = config.registry.getUtility(ICSRFPolicy).get_csrf_token(request) + policy = self._makeOne() + token = policy.get_csrf_token(request) + self.assertEqual( token, 'e6f325fee5974f3da4315a8ccf4513d2' @@ -122,15 +217,13 @@ class CookieCSRFTests(unittest.TestCase): ) def test_new_cookie_csrf_with_existing_cookie_sets_cookies(self): - config = Configurator() - config.set_default_csrf_options(implementation=CookieCSRF()) - config.commit() - response = MockResponse() - request = DummyRequest(config.registry, response=response) + request = DummyRequest(response=response) request.cookies = {'csrf_token': 'e6f325fee5974f3da4315a8ccf4513d2'} - token = config.registry.getUtility(ICSRFPolicy).new_csrf_token(request) + policy = self._makeOne() + token = policy.new_csrf_token(request) + self.assertEqual( response.called_args, ('csrf_token', token), @@ -146,13 +239,177 @@ class CookieCSRFTests(unittest.TestCase): } ) + def test_verifying_token_invalid_token(self): + response = MockResponse() + request = DummyRequest(response=response) + request.cookies = {'csrf_token': 'e6f325fee5974f3da4315a8ccf4513d2'} + + policy = self._makeOne() + self.assertFalse( + policy.check_csrf_token(request, 'invalid-token') + ) + + def test_verifying_token_against_existing_cookie(self): + response = MockResponse() + request = DummyRequest(response=response) + request.cookies = {'csrf_token': 'e6f325fee5974f3da4315a8ccf4513d2'} + + policy = self._makeOne() + self.assertTrue( + policy.check_csrf_token(request, 'e6f325fee5974f3da4315a8ccf4513d2') + ) + + +class Test_check_csrf_token(unittest.TestCase): + def setUp(self): + self.config = testing.setUp() + + # set up CSRF (this will also register SessionCSRF policy) + self.config.set_default_csrf_options(require_csrf=False) + + def _callFUT(self, *args, **kwargs): + from ..csrf import check_csrf_token + return check_csrf_token(*args, **kwargs) + + def test_success_token(self): + request = testing.DummyRequest() + request.method = "POST" + request.POST = {'csrf_token': request.session.get_csrf_token()} + self.assertEqual(self._callFUT(request, token='csrf_token'), True) + + def test_success_header(self): + request = testing.DummyRequest() + request.headers['X-CSRF-Token'] = request.session.get_csrf_token() + self.assertEqual(self._callFUT(request, header='X-CSRF-Token'), True) + + def test_success_default_token(self): + request = testing.DummyRequest() + request.method = "POST" + request.POST = {'csrf_token': request.session.get_csrf_token()} + self.assertEqual(self._callFUT(request), True) + + def test_success_default_header(self): + request = testing.DummyRequest() + request.headers['X-CSRF-Token'] = request.session.get_csrf_token() + self.assertEqual(self._callFUT(request), True) + + def test_failure_raises(self): + from pyramid.exceptions import BadCSRFToken + request = testing.DummyRequest() + self.assertRaises(BadCSRFToken, self._callFUT, request, + 'csrf_token') + + def test_failure_no_raises(self): + request = testing.DummyRequest() + result = self._callFUT(request, 'csrf_token', raises=False) + self.assertEqual(result, False) + + def test_token_differing_types(self): + from pyramid.compat import text_ + request = testing.DummyRequest() + request.method = "POST" + request.session['_csrft_'] = text_('foo') + request.POST = {'csrf_token': b'foo'} + self.assertEqual(self._callFUT(request, token='csrf_token'), True) + + +class Test_check_csrf_origin(unittest.TestCase): + def _callFUT(self, *args, **kwargs): + from ..csrf import check_csrf_origin + return check_csrf_origin(*args, **kwargs) + + def test_success_with_http(self): + request = testing.DummyRequest() + request.scheme = "http" + self.assertTrue(self._callFUT(request)) + + def test_success_with_https_and_referrer(self): + request = testing.DummyRequest() + request.scheme = "https" + request.host = "example.com" + request.host_port = "443" + request.referrer = "https://example.com/login/" + request.registry.settings = {} + self.assertTrue(self._callFUT(request)) + + def test_success_with_https_and_origin(self): + request = testing.DummyRequest() + request.scheme = "https" + request.host = "example.com" + request.host_port = "443" + request.headers = {"Origin": "https://example.com/"} + request.referrer = "https://not-example.com/" + request.registry.settings = {} + self.assertTrue(self._callFUT(request)) + + def test_success_with_additional_trusted_host(self): + request = testing.DummyRequest() + request.scheme = "https" + request.host = "example.com" + request.host_port = "443" + request.referrer = "https://not-example.com/login/" + request.registry.settings = { + "pyramid.csrf_trusted_origins": ["not-example.com"], + } + self.assertTrue(self._callFUT(request)) + + def test_success_with_nonstandard_port(self): + request = testing.DummyRequest() + request.scheme = "https" + request.host = "example.com:8080" + request.host_port = "8080" + request.referrer = "https://example.com:8080/login/" + request.registry.settings = {} + self.assertTrue(self._callFUT(request)) + + def test_fails_with_wrong_host(self): + from pyramid.exceptions import BadCSRFOrigin + request = testing.DummyRequest() + request.scheme = "https" + request.host = "example.com" + request.host_port = "443" + request.referrer = "https://not-example.com/login/" + request.registry.settings = {} + self.assertRaises(BadCSRFOrigin, self._callFUT, request) + self.assertFalse(self._callFUT(request, raises=False)) + + def test_fails_with_no_origin(self): + from pyramid.exceptions import BadCSRFOrigin + request = testing.DummyRequest() + request.scheme = "https" + request.referrer = None + self.assertRaises(BadCSRFOrigin, self._callFUT, request) + self.assertFalse(self._callFUT(request, raises=False)) + + def test_fails_when_http_to_https(self): + from pyramid.exceptions import BadCSRFOrigin + request = testing.DummyRequest() + request.scheme = "https" + request.host = "example.com" + request.host_port = "443" + request.referrer = "http://example.com/evil/" + request.registry.settings = {} + self.assertRaises(BadCSRFOrigin, self._callFUT, request) + self.assertFalse(self._callFUT(request, raises=False)) + + def test_fails_with_nonstandard_port(self): + from pyramid.exceptions import BadCSRFOrigin + request = testing.DummyRequest() + request.scheme = "https" + request.host = "example.com:8080" + request.host_port = "8080" + request.referrer = "https://example.com/login/" + request.registry.settings = {} + self.assertRaises(BadCSRFOrigin, self._callFUT, request) + self.assertFalse(self._callFUT(request, raises=False)) + class DummyRequest(object): registry = None session = None cookies = {} - def __init__(self, registry, session=None, response=None): + def __init__(self, registry=None, session=None, response=None): self.registry = registry self.session = session self.response = response @@ -170,3 +427,11 @@ class MockResponse(object): self.called_args = args self.called_kwargs = kwargs return + + +class DummyCSRF(object): + def new_csrf_token(self, request): + return 'e5e9e30a08b34ff9842ff7d2b958c14b' + + def get_csrf_token(self, request): + return '02821185e4c94269bdc38e6eeae0a2f8' diff --git a/pyramid/tests/test_session.py b/pyramid/tests/test_session.py index b51dccecc..ade602799 100644 --- a/pyramid/tests/test_session.py +++ b/pyramid/tests/test_session.py @@ -659,144 +659,6 @@ class Test_signed_deserialize(unittest.TestCase): result = self._callFUT(serialized, secret.decode('latin-1')) self.assertEqual(result, '123') -class Test_check_csrf_token(unittest.TestCase): - def _callFUT(self, *args, **kwargs): - from ..csrf import check_csrf_token - return check_csrf_token(*args, **kwargs) - - def test_success_token(self): - request = testing.DummyRequest() - request.method = "POST" - request.POST = {'csrf_token': request.session.get_csrf_token()} - self.assertEqual(self._callFUT(request, token='csrf_token'), True) - - def test_success_header(self): - request = testing.DummyRequest() - request.headers['X-CSRF-Token'] = request.session.get_csrf_token() - self.assertEqual(self._callFUT(request, header='X-CSRF-Token'), True) - - def test_success_default_token(self): - request = testing.DummyRequest() - request.method = "POST" - request.POST = {'csrf_token': request.session.get_csrf_token()} - self.assertEqual(self._callFUT(request), True) - - def test_success_default_header(self): - request = testing.DummyRequest() - request.headers['X-CSRF-Token'] = request.session.get_csrf_token() - self.assertEqual(self._callFUT(request), True) - - def test_failure_raises(self): - from pyramid.exceptions import BadCSRFToken - request = testing.DummyRequest() - self.assertRaises(BadCSRFToken, self._callFUT, request, - 'csrf_token') - - def test_failure_no_raises(self): - request = testing.DummyRequest() - result = self._callFUT(request, 'csrf_token', raises=False) - self.assertEqual(result, False) - - def test_token_differing_types(self): - from pyramid.compat import text_ - request = testing.DummyRequest() - request.method = "POST" - request.session['_csrft_'] = text_('foo') - request.POST = {'csrf_token': b'foo'} - self.assertEqual(self._callFUT(request, token='csrf_token'), True) - - -class Test_check_csrf_origin(unittest.TestCase): - - def _callFUT(self, *args, **kwargs): - from ..csrf import check_csrf_origin - return check_csrf_origin(*args, **kwargs) - - def test_success_with_http(self): - request = testing.DummyRequest() - request.scheme = "http" - self.assertTrue(self._callFUT(request)) - - def test_success_with_https_and_referrer(self): - request = testing.DummyRequest() - request.scheme = "https" - request.host = "example.com" - request.host_port = "443" - request.referrer = "https://example.com/login/" - request.registry.settings = {} - self.assertTrue(self._callFUT(request)) - - def test_success_with_https_and_origin(self): - request = testing.DummyRequest() - request.scheme = "https" - request.host = "example.com" - request.host_port = "443" - request.headers = {"Origin": "https://example.com/"} - request.referrer = "https://not-example.com/" - request.registry.settings = {} - self.assertTrue(self._callFUT(request)) - - def test_success_with_additional_trusted_host(self): - request = testing.DummyRequest() - request.scheme = "https" - request.host = "example.com" - request.host_port = "443" - request.referrer = "https://not-example.com/login/" - request.registry.settings = { - "pyramid.csrf_trusted_origins": ["not-example.com"], - } - self.assertTrue(self._callFUT(request)) - - def test_success_with_nonstandard_port(self): - request = testing.DummyRequest() - request.scheme = "https" - request.host = "example.com:8080" - request.host_port = "8080" - request.referrer = "https://example.com:8080/login/" - request.registry.settings = {} - self.assertTrue(self._callFUT(request)) - - def test_fails_with_wrong_host(self): - from pyramid.exceptions import BadCSRFOrigin - request = testing.DummyRequest() - request.scheme = "https" - request.host = "example.com" - request.host_port = "443" - request.referrer = "https://not-example.com/login/" - request.registry.settings = {} - self.assertRaises(BadCSRFOrigin, self._callFUT, request) - self.assertFalse(self._callFUT(request, raises=False)) - - def test_fails_with_no_origin(self): - from pyramid.exceptions import BadCSRFOrigin - request = testing.DummyRequest() - request.scheme = "https" - request.referrer = None - self.assertRaises(BadCSRFOrigin, self._callFUT, request) - self.assertFalse(self._callFUT(request, raises=False)) - - def test_fails_when_http_to_https(self): - from pyramid.exceptions import BadCSRFOrigin - request = testing.DummyRequest() - request.scheme = "https" - request.host = "example.com" - request.host_port = "443" - request.referrer = "http://example.com/evil/" - request.registry.settings = {} - self.assertRaises(BadCSRFOrigin, self._callFUT, request) - self.assertFalse(self._callFUT(request, raises=False)) - - def test_fails_with_nonstandard_port(self): - from pyramid.exceptions import BadCSRFOrigin - request = testing.DummyRequest() - request.scheme = "https" - request.host = "example.com:8080" - request.host_port = "8080" - request.referrer = "https://example.com/login/" - request.registry.settings = {} - self.assertRaises(BadCSRFOrigin, self._callFUT, request) - self.assertFalse(self._callFUT(request, raises=False)) - class DummySerializer(object): def dumps(self, value): diff --git a/pyramid/tests/test_viewderivers.py b/pyramid/tests/test_viewderivers.py index 51d0bd367..6b81cc1e5 100644 --- a/pyramid/tests/test_viewderivers.py +++ b/pyramid/tests/test_viewderivers.py @@ -12,6 +12,7 @@ class TestDeriveView(unittest.TestCase): def setUp(self): self.config = testing.setUp() + self.config.set_default_csrf_options(require_csrf=False) def tearDown(self): self.config = None -- cgit v1.2.3 From 8f60e2c397a4c781d3ac2dc7fcff9321cdb16a42 Mon Sep 17 00:00:00 2001 From: Jure Cerjak Date: Wed, 7 Dec 2016 11:00:18 +0100 Subject: add to contributors list --- CONTRIBUTORS.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 566e91195..750f6d29f 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -291,6 +291,8 @@ Contributors - Mikko Ohtamaa, 2016/12/6 +- Jure Cerjak, 2016/12/7 + - Martin Frlin, 2016/12/7 - Kirill Kuzminykh, 2017/03/01 -- cgit v1.2.3 From fe0d223ad08bcab724d216b3a877b690c5795f73 Mon Sep 17 00:00:00 2001 From: Matthew Wilkes Date: Fri, 9 Dec 2016 11:25:03 +0100 Subject: Rename implementation to ICSRFStoragePolicy --- docs/api/interfaces.rst | 2 +- docs/narr/security.rst | 2 +- pyramid/config/security.py | 6 +++--- pyramid/config/views.py | 2 +- pyramid/csrf.py | 18 +++++++++--------- pyramid/interfaces.py | 2 +- pyramid/tests/test_csrf.py | 8 ++++---- 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/docs/api/interfaces.rst b/docs/api/interfaces.rst index b88209a36..e542a6be0 100644 --- a/docs/api/interfaces.rst +++ b/docs/api/interfaces.rst @@ -44,7 +44,7 @@ Other Interfaces .. autointerface:: IRoutePregenerator :members: - .. autointerface:: ICSRFPolicy + .. autointerface:: ICSRFStoragePolicy :members: .. autointerface:: ISession diff --git a/docs/narr/security.rst b/docs/narr/security.rst index 6962a0fe3..04c236e0b 100644 --- a/docs/narr/security.rst +++ b/docs/narr/security.rst @@ -787,7 +787,7 @@ a :term:`session factory` as described in a cookie-based implementation :class:`pyramid.csrf.CookieCSRF` which gives some additional flexibility as it does not require a session for each user. You can also define your own implementation of -:class:`pyramid.interfaces.ICSRFPolicy` and register it with the +:class:`pyramid.interfaces.ICSRFStoragePolicy` and register it with the :meth:`pyramid.config.Configurator.set_default_csrf_options` directive. For example: diff --git a/pyramid/config/security.py b/pyramid/config/security.py index 102a61e0c..c8becce1f 100644 --- a/pyramid/config/security.py +++ b/pyramid/config/security.py @@ -3,7 +3,7 @@ from zope.interface import implementer from pyramid.interfaces import ( IAuthorizationPolicy, IAuthenticationPolicy, - ICSRFPolicy, + ICSRFStoragePolicy, IDefaultCSRFOptions, IDefaultPermission, PHASE1_CONFIG, @@ -181,7 +181,7 @@ class SecurityConfiguratorMixin(object): Set the default CSRF options used by subsequent view registrations. ``implementation`` is a class that implements the - :meth:`pyramid.interfaces.ICSRFPolicy` interface that will be used for all + :meth:`pyramid.interfaces.ICSRFStoragePolicy` interface that will be used for all CSRF functionality. Default: :class:`pyramid.csrf.SessionCSRF`. ``require_csrf`` controls whether CSRF checks will be automatically @@ -220,7 +220,7 @@ class SecurityConfiguratorMixin(object): if implementation is None: implementation = SessionCSRF() def register(): - self.registry.registerUtility(implementation, ICSRFPolicy) + self.registry.registerUtility(implementation, ICSRFStoragePolicy) self.registry.registerUtility(options, IDefaultCSRFOptions) intr = self.introspectable('default csrf view options', None, diff --git a/pyramid/config/views.py b/pyramid/config/views.py index 4ebd014de..e037f7706 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -644,7 +644,7 @@ class ViewsConfiguratorMixin(object): If CSRF checking is performed, the checked value will be the value of ``request.params[check_name]``. This value will be compared against the value of ``policy.get_csrf_token()`` (where ``policy`` is an - implementation of :meth:`pyramid.interfaces.ICSRFPolicy`), and the + implementation of :meth:`pyramid.interfaces.ICSRFStoragePolicy`), and the check will pass if these two values are the same. If the check passes, the associated view will be permitted to execute. If the check fails, the associated view will not be permitted to execute. diff --git a/pyramid/csrf.py b/pyramid/csrf.py index 7adbc9fee..b2788a764 100644 --- a/pyramid/csrf.py +++ b/pyramid/csrf.py @@ -11,7 +11,7 @@ from pyramid.exceptions import ( BadCSRFOrigin, BadCSRFToken, ) -from pyramid.interfaces import ICSRFPolicy +from pyramid.interfaces import ICSRFStoragePolicy from pyramid.settings import aslist from pyramid.util import ( is_same_domain, @@ -19,7 +19,7 @@ from pyramid.util import ( ) -@implementer(ICSRFPolicy) +@implementer(ICSRFStoragePolicy) class SessionCSRF(object): """ The default CSRF implementation, which mimics the behavior from older versions of Pyramid. The ``new_csrf_token`` and ``get_csrf_token`` methods @@ -48,7 +48,7 @@ class SessionCSRF(object): bytes_(supplied_token, 'ascii'), ) -@implementer(ICSRFPolicy) +@implementer(ICSRFStoragePolicy) class CookieCSRF(object): """ An alternative CSRF implementation that stores its information in unauthenticated cookies, known as the 'Double Submit Cookie' method in the @@ -108,7 +108,7 @@ def csrf_token_template_global(event): except AttributeError: return else: - csrf = registry.getUtility(ICSRFPolicy) + csrf = registry.getUtility(ICSRFStoragePolicy) event['get_csrf_token'] = partial(csrf.get_csrf_token, request) @@ -120,7 +120,7 @@ def get_csrf_token(request): .. versionadded :: 1.8a1 """ registry = request.registry - csrf = registry.getUtility(ICSRFPolicy) + csrf = registry.getUtility(ICSRFStoragePolicy) return csrf.get_csrf_token(request) @@ -132,7 +132,7 @@ def new_csrf_token(request): .. versionadded :: 1.8a1 """ registry = request.registry - csrf = registry.getUtility(ICSRFPolicy) + csrf = registry.getUtility(ICSRFStoragePolicy) return csrf.new_csrf_token(request) @@ -141,7 +141,7 @@ def check_csrf_token(request, header='X-CSRF-Token', raises=True): """ Check the CSRF token returned by the - :class:`pyramid.interfaces.ICSRFPolicy` implementation against the value in + :class:`pyramid.interfaces.ICSRFStoragePolicy` implementation against the value in ``request.POST.get(token)`` (if a POST request) or ``request.headers.get(header)``. If a ``token`` keyword is not supplied to this function, the string ``csrf_token`` will be used to look up the token @@ -151,7 +151,7 @@ def check_csrf_token(request, If the value supplied by post or by header doesn't match the value supplied by ``policy.get_csrf_token()`` (where ``policy`` is an implementation of - :class:`pyramid.interfaces.ICSRFPolicy`), and ``raises`` is ``True``, this + :class:`pyramid.interfaces.ICSRFStoragePolicy`), and ``raises`` is ``True``, this function will raise an :exc:`pyramid.exceptions.BadCSRFToken` exception. If the values differ and ``raises`` is ``False``, this function will return ``False``. If the CSRF check is successful, this function will return @@ -184,7 +184,7 @@ def check_csrf_token(request, if supplied_token == "" and token is not None: supplied_token = request.POST.get(token, "") - policy = request.registry.getUtility(ICSRFPolicy) + policy = request.registry.getUtility(ICSRFStoragePolicy) if not policy.check_csrf_token(request, supplied_token): if raises: raise BadCSRFToken('check_csrf_token(): Invalid token') diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index f58ee8b58..aab5647a1 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -982,7 +982,7 @@ class ISession(IDict): """ -class ICSRFPolicy(Interface): +class ICSRFStoragePolicy(Interface): """ An object that offers the ability to verify CSRF tokens and generate new ones""" diff --git a/pyramid/tests/test_csrf.py b/pyramid/tests/test_csrf.py index 1b3f3fc3b..8866f3601 100644 --- a/pyramid/tests/test_csrf.py +++ b/pyramid/tests/test_csrf.py @@ -117,13 +117,13 @@ class TestSessionCSRF(unittest.TestCase): def test_register_session_csrf_policy(self): from pyramid.csrf import SessionCSRF - from pyramid.interfaces import ICSRFPolicy + from pyramid.interfaces import ICSRFStoragePolicy config = Configurator() config.set_default_csrf_options(implementation=self._makeOne()) config.commit() - policy = config.registry.queryUtility(ICSRFPolicy) + policy = config.registry.queryUtility(ICSRFStoragePolicy) self.assertTrue(isinstance(policy, SessionCSRF)) @@ -163,13 +163,13 @@ class TestCookieCSRF(unittest.TestCase): def test_register_cookie_csrf_policy(self): from pyramid.csrf import CookieCSRF - from pyramid.interfaces import ICSRFPolicy + from pyramid.interfaces import ICSRFStoragePolicy config = Configurator() config.set_default_csrf_options(implementation=self._makeOne()) config.commit() - policy = config.registry.queryUtility(ICSRFPolicy) + policy = config.registry.queryUtility(ICSRFStoragePolicy) self.assertTrue(isinstance(policy, CookieCSRF)) -- cgit v1.2.3 From f6d63a41d37b0647c49e53bb54f009f7da4d5079 Mon Sep 17 00:00:00 2001 From: Matthew Wilkes Date: Fri, 9 Dec 2016 12:00:17 +0100 Subject: Fix a bug where people that didn't configure CSRF protection but did configure a session and set explicit checks would see an exception --- pyramid/csrf.py | 8 +++++++- pyramid/tests/test_csrf.py | 26 ++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/pyramid/csrf.py b/pyramid/csrf.py index b2788a764..f282eb569 100644 --- a/pyramid/csrf.py +++ b/pyramid/csrf.py @@ -184,7 +184,13 @@ def check_csrf_token(request, if supplied_token == "" and token is not None: supplied_token = request.POST.get(token, "") - policy = request.registry.getUtility(ICSRFStoragePolicy) + policy = request.registry.queryUtility(ICSRFStoragePolicy) + if policy is None: + # There is no policy set, but we are trying to validate a CSRF token + # This means explicit validation has been asked for without configuring + # the CSRF implementation. Fall back to SessionCSRF as that is the + # default + policy = SessionCSRF() if not policy.check_csrf_token(request, supplied_token): if raises: raise BadCSRFToken('check_csrf_token(): Invalid token') diff --git a/pyramid/tests/test_csrf.py b/pyramid/tests/test_csrf.py index 8866f3601..3994a31d4 100644 --- a/pyramid/tests/test_csrf.py +++ b/pyramid/tests/test_csrf.py @@ -313,6 +313,32 @@ class Test_check_csrf_token(unittest.TestCase): self.assertEqual(self._callFUT(request, token='csrf_token'), True) +class Test_check_csrf_token_without_defaults_configured(unittest.TestCase): + def setUp(self): + self.config = testing.setUp() + + def _callFUT(self, *args, **kwargs): + from ..csrf import check_csrf_token + return check_csrf_token(*args, **kwargs) + + def test_success_token(self): + request = testing.DummyRequest() + request.method = "POST" + request.POST = {'csrf_token': request.session.get_csrf_token()} + self.assertEqual(self._callFUT(request, token='csrf_token'), True) + + def test_failure_raises(self): + from pyramid.exceptions import BadCSRFToken + request = testing.DummyRequest() + self.assertRaises(BadCSRFToken, self._callFUT, request, + 'csrf_token') + + def test_failure_no_raises(self): + request = testing.DummyRequest() + result = self._callFUT(request, 'csrf_token', raises=False) + self.assertEqual(result, False) + + class Test_check_csrf_origin(unittest.TestCase): def _callFUT(self, *args, **kwargs): from ..csrf import check_csrf_origin -- cgit v1.2.3 From 7c0f098641fda4207ea6fa50c58b289926038697 Mon Sep 17 00:00:00 2001 From: Matthew Wilkes Date: Wed, 12 Apr 2017 11:57:56 +0100 Subject: Use the webob CookieProfile in the Cookie implementation, rename some implemenations based on feedback, split CSRF implementation and option configuration and make the csrf token function exposed as a system default rather than a renderer event. --- docs/api/config.rst | 1 + docs/api/csrf.rst | 4 +- docs/narr/extconfig.rst | 1 + docs/narr/security.rst | 8 +-- pyramid/config/__init__.py | 1 + pyramid/config/security.py | 31 ++++++---- pyramid/csrf.py | 52 +++++++---------- pyramid/renderers.py | 4 ++ pyramid/tests/test_csrf.py | 126 +++++++--------------------------------- pyramid/tests/test_renderers.py | 8 +++ 10 files changed, 84 insertions(+), 152 deletions(-) diff --git a/docs/api/config.rst b/docs/api/config.rst index c76d3d5ff..a785b64ad 100644 --- a/docs/api/config.rst +++ b/docs/api/config.rst @@ -37,6 +37,7 @@ .. automethod:: set_authentication_policy .. automethod:: set_authorization_policy .. automethod:: set_default_csrf_options + .. automethod:: set_csrf_storage_policy .. automethod:: set_default_permission .. automethod:: add_permission diff --git a/docs/api/csrf.rst b/docs/api/csrf.rst index 89fb0c4b2..f890ee660 100644 --- a/docs/api/csrf.rst +++ b/docs/api/csrf.rst @@ -5,10 +5,10 @@ .. automodule:: pyramid.csrf - .. autoclass:: SessionCSRF + .. autoclass:: SessionCSRFStoragePolicy :members: - .. autoclass:: CookieCSRF + .. autoclass:: CookieCSRFStoragePolicy :members: .. autofunction:: get_csrf_token diff --git a/docs/narr/extconfig.rst b/docs/narr/extconfig.rst index 4009ec1dc..c20685cbf 100644 --- a/docs/narr/extconfig.rst +++ b/docs/narr/extconfig.rst @@ -263,6 +263,7 @@ Pre-defined Phases - :meth:`pyramid.config.Configurator.override_asset` - :meth:`pyramid.config.Configurator.set_authorization_policy` - :meth:`pyramid.config.Configurator.set_default_csrf_options` +- :meth:`pyramid.config.Configurator.set_csrf_storage_policy` - :meth:`pyramid.config.Configurator.set_default_permission` - :meth:`pyramid.config.Configurator.set_view_mapper` diff --git a/docs/narr/security.rst b/docs/narr/security.rst index 04c236e0b..e67f7b98c 100644 --- a/docs/narr/security.rst +++ b/docs/narr/security.rst @@ -780,15 +780,15 @@ and then requiring that it be present in all potentially unsafe requests. :app:`Pyramid` provides facilities to create and check CSRF tokens. By default :app:`Pyramid` comes with a session-based CSRF implementation -:class:`pyramid.csrf.SessionCSRF`. To use it, you must first enable +:class:`pyramid.csrf.SessionCSRFStoragePolicy`. To use it, you must first enable a :term:`session factory` as described in :ref:`using_the_default_session_factory` or :ref:`using_alternate_session_factories`. Alternatively, you can use -a cookie-based implementation :class:`pyramid.csrf.CookieCSRF` which gives +a cookie-based implementation :class:`pyramid.csrf.CookieCSRFStoragePolicy` which gives some additional flexibility as it does not require a session for each user. You can also define your own implementation of :class:`pyramid.interfaces.ICSRFStoragePolicy` and register it with the -:meth:`pyramid.config.Configurator.set_default_csrf_options` directive. +:meth:`pyramid.config.Configurator.set_csrf_storage_policy` directive. For example: @@ -797,7 +797,7 @@ For example: from pyramid.config import Configurator config = Configurator() - config.set_default_csrf_options(implementation=MyCustomCSRFPolicy()) + config.set_csrf_storage_policy(MyCustomCSRFPolicy()) .. index:: single: csrf.get_csrf_token diff --git a/pyramid/config/__init__.py b/pyramid/config/__init__.py index 6c661aa59..b05effbde 100644 --- a/pyramid/config/__init__.py +++ b/pyramid/config/__init__.py @@ -380,6 +380,7 @@ class Configurator( self.add_default_view_derivers() self.add_default_route_predicates() self.add_default_tweens() + self.add_default_security() if exceptionresponse_view is not None: exceptionresponse_view = self.maybe_dotted(exceptionresponse_view) diff --git a/pyramid/config/security.py b/pyramid/config/security.py index c8becce1f..0b565e322 100644 --- a/pyramid/config/security.py +++ b/pyramid/config/security.py @@ -10,15 +10,17 @@ from pyramid.interfaces import ( PHASE2_CONFIG, ) -from pyramid.csrf import csrf_token_template_global -from pyramid.csrf import SessionCSRF -from pyramid.events import BeforeRender +from pyramid.csrf import SessionCSRFStoragePolicy from pyramid.exceptions import ConfigurationError from pyramid.util import action_method from pyramid.util import as_sorted_tuple class SecurityConfiguratorMixin(object): + + def add_default_security(self): + self.set_csrf_storage_policy(SessionCSRFStoragePolicy()) + @action_method def set_authentication_policy(self, policy): """ Override the :app:`Pyramid` :term:`authentication policy` in the @@ -170,7 +172,6 @@ class SecurityConfiguratorMixin(object): @action_method def set_default_csrf_options( self, - implementation=None, require_csrf=True, token='csrf_token', header='X-CSRF-Token', @@ -180,10 +181,6 @@ class SecurityConfiguratorMixin(object): """ Set the default CSRF options used by subsequent view registrations. - ``implementation`` is a class that implements the - :meth:`pyramid.interfaces.ICSRFStoragePolicy` interface that will be used for all - CSRF functionality. Default: :class:`pyramid.csrf.SessionCSRF`. - ``require_csrf`` controls whether CSRF checks will be automatically enabled on each view in the application. This value is used as the fallback when ``require_csrf`` is left at the default of ``None`` on @@ -217,10 +214,7 @@ class SecurityConfiguratorMixin(object): options = DefaultCSRFOptions( require_csrf, token, header, safe_methods, callback, ) - if implementation is None: - implementation = SessionCSRF() def register(): - self.registry.registerUtility(implementation, ICSRFStoragePolicy) self.registry.registerUtility(options, IDefaultCSRFOptions) intr = self.introspectable('default csrf view options', None, @@ -232,10 +226,23 @@ class SecurityConfiguratorMixin(object): intr['safe_methods'] = as_sorted_tuple(safe_methods) intr['callback'] = callback - self.add_subscriber(csrf_token_template_global, [BeforeRender]) self.action(IDefaultCSRFOptions, register, order=PHASE1_CONFIG, introspectables=(intr,)) + @action_method + def set_csrf_storage_policy(self, policy): + """ + Set the CSRF storage policy used by subsequent view registrations. + + ``policy`` is a class that implements the + :meth:`pyramid.interfaces.ICSRFStoragePolicy` interface that will be used for all + CSRF functionality. + """ + def register(): + self.registry.registerUtility(policy, ICSRFStoragePolicy) + + self.action(ICSRFStoragePolicy, register, order=PHASE1_CONFIG) + @implementer(IDefaultCSRFOptions) class DefaultCSRFOptions(object): diff --git a/pyramid/csrf.py b/pyramid/csrf.py index f282eb569..4c5a73940 100644 --- a/pyramid/csrf.py +++ b/pyramid/csrf.py @@ -1,8 +1,11 @@ -from functools import partial import uuid +from webob.cookies import CookieProfile from zope.interface import implementer + +from pyramid.authentication import _SimpleSerializer + from pyramid.compat import ( urlparse, bytes_ @@ -20,7 +23,7 @@ from pyramid.util import ( @implementer(ICSRFStoragePolicy) -class SessionCSRF(object): +class SessionCSRFStoragePolicy(object): """ The default CSRF implementation, which mimics the behavior from older versions of Pyramid. The ``new_csrf_token`` and ``get_csrf_token`` methods are indirected to the underlying session implementation. @@ -49,7 +52,7 @@ class SessionCSRF(object): ) @implementer(ICSRFStoragePolicy) -class CookieCSRF(object): +class CookieCSRFStoragePolicy(object): """ An alternative CSRF implementation that stores its information in unauthenticated cookies, known as the 'Double Submit Cookie' method in the OWASP CSRF guidelines. This gives some additional flexibility with regards @@ -60,25 +63,25 @@ class CookieCSRF(object): """ def __init__(self, cookie_name='csrf_token', secure=False, httponly=False, - domain=None, path='/'): - self.cookie_name = cookie_name - self.secure = secure - self.httponly = httponly + domain=None, max_age=None, path='/'): + serializer = _SimpleSerializer() + self.cookie_profile = CookieProfile( + cookie_name=cookie_name, + secure=secure, + max_age=max_age, + httponly=httponly, + path=path, + serializer=serializer + ) self.domain = domain - self.path = path def new_csrf_token(self, request): """ Sets a new CSRF token into the request and returns it. """ token = uuid.uuid4().hex def set_cookie(request, response): - response.set_cookie( - self.cookie_name, + self.cookie_profile.set_cookies( + response, token, - httponly=self.httponly, - secure=self.secure, - domain=self.domain, - path=self.path, - overwrite=True, ) request.add_response_callback(set_cookie) return token @@ -86,7 +89,8 @@ class CookieCSRF(object): def get_csrf_token(self, request): """ Returns the currently active CSRF token by checking the cookies sent with the current request.""" - token = request.cookies.get(self.cookie_name) + bound_cookies = self.cookie_profile.bind(request) + token = bound_cookies.get_value() if not token: token = self.new_csrf_token(request) return token @@ -100,18 +104,6 @@ class CookieCSRF(object): bytes_(supplied_token, 'ascii'), ) - -def csrf_token_template_global(event): - request = event.get('request', None) - try: - registry = request.registry - except AttributeError: - return - else: - csrf = registry.getUtility(ICSRFStoragePolicy) - event['get_csrf_token'] = partial(csrf.get_csrf_token, request) - - def get_csrf_token(request): """ Get the currently active CSRF token for the request passed, generating a new one using ``new_csrf_token(request)`` if one does not exist. This @@ -188,9 +180,9 @@ def check_csrf_token(request, if policy is None: # There is no policy set, but we are trying to validate a CSRF token # This means explicit validation has been asked for without configuring - # the CSRF implementation. Fall back to SessionCSRF as that is the + # the CSRF implementation. Fall back to SessionCSRFStoragePolicy as that is the # default - policy = SessionCSRF() + policy = SessionCSRFStoragePolicy() if not policy.check_csrf_token(request, supplied_token): if raises: raise BadCSRFToken('check_csrf_token(): Invalid token') diff --git a/pyramid/renderers.py b/pyramid/renderers.py index 7d667ba7b..6019f50fb 100644 --- a/pyramid/renderers.py +++ b/pyramid/renderers.py @@ -1,3 +1,4 @@ +from functools import partial import json import os import re @@ -19,6 +20,7 @@ from pyramid.compat import ( text_type, ) +from pyramid.csrf import get_csrf_token from pyramid.decorator import reify from pyramid.events import BeforeRender @@ -428,6 +430,7 @@ class RendererHelper(object): 'context':context, 'request':request, 'req':request, + 'get_csrf_token':partial(get_csrf_token, request), } return self.render_to_response(response, system, request=request) @@ -441,6 +444,7 @@ class RendererHelper(object): 'context':getattr(request, 'context', None), 'request':request, 'req':request, + 'get_csrf_token':partial(get_csrf_token, request), } system_values = BeforeRender(system_values, value) diff --git a/pyramid/tests/test_csrf.py b/pyramid/tests/test_csrf.py index 3994a31d4..e6ae05eec 100644 --- a/pyramid/tests/test_csrf.py +++ b/pyramid/tests/test_csrf.py @@ -22,7 +22,7 @@ class Test_get_csrf_token(unittest.TestCase): self._callFUT(request) def test_success(self): - self.config.set_default_csrf_options(implementation=DummyCSRF()) + self.config.set_csrf_storage_policy(DummyCSRF()) request = testing.DummyRequest() csrf_token = self._callFUT(request) @@ -45,7 +45,7 @@ class Test_new_csrf_token(unittest.TestCase): self._callFUT(request) def test_success(self): - self.config.set_default_csrf_options(implementation=DummyCSRF()) + self.config.set_csrf_storage_policy(DummyCSRF()) request = testing.DummyRequest() csrf_token = self._callFUT(request) @@ -53,57 +53,7 @@ class Test_new_csrf_token(unittest.TestCase): self.assertEquals(csrf_token, 'e5e9e30a08b34ff9842ff7d2b958c14b') -class Test_csrf_token_template_global(unittest.TestCase): - def setUp(self): - self.config = testing.setUp() - - def _callFUT(self, *args, **kwargs): - from pyramid.csrf import csrf_token_template_global - return csrf_token_template_global(*args, **kwargs) - - def test_event_is_missing_request(self): - event = BeforeRender({}, {}) - - self._callFUT(event) - - self.assertNotIn('get_csrf_token', event) - - def test_request_is_missing_registry(self): - request = DummyRequest(registry=None) - del request.registry - del request.__class__.registry - event = BeforeRender({'request': request}, {}) - - self._callFUT(event) - - self.assertNotIn('get_csrf_token', event) - - def test_csrf_utility_not_registered(self): - request = testing.DummyRequest() - event = BeforeRender({'request': request}, {}) - - with self.assertRaises(ComponentLookupError): - self._callFUT(event) - - def test_csrf_token_passed_to_template(self): - config = Configurator() - config.set_default_csrf_options(implementation=DummyCSRF()) - config.commit() - - request = testing.DummyRequest() - request.registry = config.registry - - before = BeforeRender({'request': request}, {}) - config.registry.notify(before) - - self.assertIn('get_csrf_token', before) - self.assertEqual( - before['get_csrf_token'](), - '02821185e4c94269bdc38e6eeae0a2f8' - ) - - -class TestSessionCSRF(unittest.TestCase): +class TestSessionCSRFStoragePolicy(unittest.TestCase): class MockSession(object): def new_csrf_token(self): return 'e5e9e30a08b34ff9842ff7d2b958c14b' @@ -112,20 +62,20 @@ class TestSessionCSRF(unittest.TestCase): return '02821185e4c94269bdc38e6eeae0a2f8' def _makeOne(self): - from pyramid.csrf import SessionCSRF - return SessionCSRF() + from pyramid.csrf import SessionCSRFStoragePolicy + return SessionCSRFStoragePolicy() def test_register_session_csrf_policy(self): - from pyramid.csrf import SessionCSRF + from pyramid.csrf import SessionCSRFStoragePolicy from pyramid.interfaces import ICSRFStoragePolicy config = Configurator() - config.set_default_csrf_options(implementation=self._makeOne()) + config.set_csrf_storage_policy(self._makeOne()) config.commit() policy = config.registry.queryUtility(ICSRFStoragePolicy) - self.assertTrue(isinstance(policy, SessionCSRF)) + self.assertTrue(isinstance(policy, SessionCSRFStoragePolicy)) def test_session_csrf_implementation_delegates_to_session(self): policy = self._makeOne() @@ -156,22 +106,22 @@ class TestSessionCSRF(unittest.TestCase): self.assertTrue(result) -class TestCookieCSRF(unittest.TestCase): +class TestCookieCSRFStoragePolicy(unittest.TestCase): def _makeOne(self): - from pyramid.csrf import CookieCSRF - return CookieCSRF() + from pyramid.csrf import CookieCSRFStoragePolicy + return CookieCSRFStoragePolicy() def test_register_cookie_csrf_policy(self): - from pyramid.csrf import CookieCSRF + from pyramid.csrf import CookieCSRFStoragePolicy from pyramid.interfaces import ICSRFStoragePolicy config = Configurator() - config.set_default_csrf_options(implementation=self._makeOne()) + config.set_csrf_storage_policy(self._makeOne()) config.commit() policy = config.registry.queryUtility(ICSRFStoragePolicy) - self.assertTrue(isinstance(policy, CookieCSRF)) + self.assertTrue(isinstance(policy, CookieCSRFStoragePolicy)) def test_get_cookie_csrf_with_no_existing_cookie_sets_cookies(self): response = MockResponse() @@ -179,20 +129,9 @@ class TestCookieCSRF(unittest.TestCase): policy = self._makeOne() token = policy.get_csrf_token(request) - self.assertEqual( - response.called_args, - ('csrf_token', token), - ) - self.assertEqual( - response.called_kwargs, - { - 'secure': False, - 'httponly': False, - 'domain': None, - 'path': '/', - 'overwrite': True - } + response.headerlist, + [('Set-Cookie', 'csrf_token={}; Path=/'.format(token))] ) def test_existing_cookie_csrf_does_not_set_cookie(self): @@ -208,12 +147,8 @@ class TestCookieCSRF(unittest.TestCase): 'e6f325fee5974f3da4315a8ccf4513d2' ) self.assertEqual( - response.called_args, - (), - ) - self.assertEqual( - response.called_kwargs, - {} + response.headerlist, + [], ) def test_new_cookie_csrf_with_existing_cookie_sets_cookies(self): @@ -223,20 +158,9 @@ class TestCookieCSRF(unittest.TestCase): policy = self._makeOne() token = policy.new_csrf_token(request) - - self.assertEqual( - response.called_args, - ('csrf_token', token), - ) self.assertEqual( - response.called_kwargs, - { - 'secure': False, - 'httponly': False, - 'domain': None, - 'path': '/', - 'overwrite': True - } + response.headerlist, + [('Set-Cookie', 'csrf_token={}; Path=/'.format(token))] ) def test_verifying_token_invalid_token(self): @@ -264,7 +188,7 @@ class Test_check_csrf_token(unittest.TestCase): def setUp(self): self.config = testing.setUp() - # set up CSRF (this will also register SessionCSRF policy) + # set up CSRF (this will also register SessionCSRFStoragePolicy policy) self.config.set_default_csrf_options(require_csrf=False) def _callFUT(self, *args, **kwargs): @@ -446,13 +370,7 @@ class DummyRequest(object): class MockResponse(object): def __init__(self): - self.called_args = () - self.called_kwargs = {} - - def set_cookie(self, *args, **kwargs): - self.called_args = args - self.called_kwargs = kwargs - return + self.headerlist = [] class DummyCSRF(object): diff --git a/pyramid/tests/test_renderers.py b/pyramid/tests/test_renderers.py index 65bfa5582..86d8b582a 100644 --- a/pyramid/tests/test_renderers.py +++ b/pyramid/tests/test_renderers.py @@ -203,6 +203,7 @@ class TestRendererHelper(unittest.TestCase): self.assertEqual(helper.get_renderer(), factory.respond) def test_render_view(self): + import pyramid.csrf self._registerRendererFactory() self._registerResponseFactory() request = Dummy() @@ -212,6 +213,9 @@ class TestRendererHelper(unittest.TestCase): request = testing.DummyRequest() response = 'response' response = helper.render_view(request, response, view, context) + get_csrf = response.app_iter[1].pop('get_csrf_token') + self.assertEqual(get_csrf.args, (request, )) + self.assertEqual(get_csrf.func, pyramid.csrf.get_csrf_token) self.assertEqual(response.app_iter[0], 'response') self.assertEqual(response.app_iter[1], {'renderer_info': helper, @@ -242,12 +246,16 @@ class TestRendererHelper(unittest.TestCase): self.assertEqual(reg.event.__class__.__name__, 'BeforeRender') def test_render_system_values_is_None(self): + import pyramid.csrf self._registerRendererFactory() request = Dummy() context = Dummy() request.context = context helper = self._makeOne('loo.foo') result = helper.render('values', None, request=request) + get_csrf = result[1].pop('get_csrf_token') + self.assertEqual(get_csrf.args, (request, )) + self.assertEqual(get_csrf.func, pyramid.csrf.get_csrf_token) system = {'request':request, 'context':context, 'renderer_name':'loo.foo', -- cgit v1.2.3 From 8597552cbf9b49366c7d2a27c123d140877a1bab Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 12 Apr 2017 21:58:26 -0500 Subject: docs syntax fix --- pyramid/config/security.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyramid/config/security.py b/pyramid/config/security.py index 33593376b..6864ebb23 100644 --- a/pyramid/config/security.py +++ b/pyramid/config/security.py @@ -200,7 +200,7 @@ class SecurityConfiguratorMixin(object): are not subject to CSRF attacks. For example, if a request is authenticated using the ``Authorization`` header instead of a cookie, this may return ``False`` for that request so that clients do not - need to send the ``X-CSRF-Token` header. The callback is only tested + need to send the ``X-CSRF-Token`` header. The callback is only tested for non-safe methods as defined by ``safe_methods``. """ -- cgit v1.2.3 From 66585f47e2ccd36be6a3cd5679ccf60161d9db05 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 12 Apr 2017 22:37:56 -0500 Subject: forward port some history changes from 1.8-branch --- HISTORY.txt | 6 ++++++ docs/whatsnew-1.8.rst | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/HISTORY.txt b/HISTORY.txt index c10747af4..c69d9514e 100644 --- a/HISTORY.txt +++ b/HISTORY.txt @@ -198,6 +198,12 @@ Features See https://github.com/Pylons/pyramid/pull/2873 +- Added a new ``callback`` option to ``config.set_default_csrf_options`` which + can be used to determine per-request whether CSRF checking should be enabled + to allow for a mix authentication methods. Only cookie-based methods + generally require CSRF checking. + See https://github.com/Pylons/pyramid/pull/2778 + Bug Fixes --------- diff --git a/docs/whatsnew-1.8.rst b/docs/whatsnew-1.8.rst index adc60b34b..ff16c1a4b 100644 --- a/docs/whatsnew-1.8.rst +++ b/docs/whatsnew-1.8.rst @@ -114,6 +114,13 @@ Minor Feature Additions later calls to place translation directories at a higher priority then earlier calls. See https://github.com/Pylons/pyramid/pull/2902 +- Added a new ``callback`` option to + :meth:`pyramid.config.Configurator.set_default_csrf_options`` which + can be used to determine per-request whether CSRF checking should be enabled + to allow for a mix authentication methods. Only cookie-based methods + generally require CSRF checking. + See https://github.com/Pylons/pyramid/pull/2778 + Backwards Incompatibilities --------------------------- -- cgit v1.2.3 From 2078f2cf5188491581cf1c362a583af2b2cbc2fe Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 12 Apr 2017 22:39:13 -0500 Subject: add version tags on set_default_csrf_options --- pyramid/config/security.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyramid/config/security.py b/pyramid/config/security.py index 6864ebb23..1d4bbe890 100644 --- a/pyramid/config/security.py +++ b/pyramid/config/security.py @@ -203,6 +203,11 @@ class SecurityConfiguratorMixin(object): need to send the ``X-CSRF-Token`` header. The callback is only tested for non-safe methods as defined by ``safe_methods``. + .. versionadded:: 1.7 + + .. versionchanged:: 1.8 + Added the ``callback`` option. + """ options = DefaultCSRFOptions( require_csrf, token, header, safe_methods, callback, -- cgit v1.2.3 From 67ac6c8718df02505882d08d254d7a4ab9423d18 Mon Sep 17 00:00:00 2001 From: Jeremy Chen Date: Sat, 15 Apr 2017 19:23:58 +1000 Subject: Update default.py --- docs/tutorials/wiki2/src/views/tutorial/views/default.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/tutorials/wiki2/src/views/tutorial/views/default.py b/docs/tutorials/wiki2/src/views/tutorial/views/default.py index 0a05b33e6..3b95e0f59 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/views/default.py +++ b/docs/tutorials/wiki2/src/views/tutorial/views/default.py @@ -1,4 +1,4 @@ -import html +from pyramid.compat import escape import re from docutils.core import publish_parts @@ -31,10 +31,10 @@ def view_page(request): exists = request.dbsession.query(Page).filter_by(name=word).all() if exists: view_url = request.route_url('view_page', pagename=word) - return '%s' % (view_url, html.escape(word)) + return '%s' % (view_url, escape(word)) else: add_url = request.route_url('add_page', pagename=word) - return '%s' % (add_url, html.escape(word)) + return '%s' % (add_url, escape(word)) content = publish_parts(page.data, writer_name='html')['html_body'] content = wikiwords.sub(add_link, content) -- cgit v1.2.3 From 4b743ad895e914d31b75d446118d219e36435711 Mon Sep 17 00:00:00 2001 From: Jeremy Chen Date: Sat, 15 Apr 2017 19:25:46 +1000 Subject: Update default.py --- docs/tutorials/wiki2/src/authentication/tutorial/views/default.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/views/default.py b/docs/tutorials/wiki2/src/authentication/tutorial/views/default.py index 1b071434c..2d058d874 100644 --- a/docs/tutorials/wiki2/src/authentication/tutorial/views/default.py +++ b/docs/tutorials/wiki2/src/authentication/tutorial/views/default.py @@ -1,4 +1,4 @@ -import cgi +from pyramid.compat import escape import re from docutils.core import publish_parts @@ -32,10 +32,10 @@ def view_page(request): exists = request.dbsession.query(Page).filter_by(name=word).all() if exists: view_url = request.route_url('view_page', pagename=word) - return '%s' % (view_url, cgi.escape(word)) + return '%s' % (view_url, escape(word)) else: add_url = request.route_url('add_page', pagename=word) - return '%s' % (add_url, cgi.escape(word)) + return '%s' % (add_url, escape(word)) content = publish_parts(page.data, writer_name='html')['html_body'] content = wikiwords.sub(add_link, content) -- cgit v1.2.3 From edf56847ab136c0fc358309e584edd15357c5848 Mon Sep 17 00:00:00 2001 From: Jeremy Chen Date: Sat, 15 Apr 2017 19:27:24 +1000 Subject: Update default.py --- docs/tutorials/wiki2/src/authorization/tutorial/views/default.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/views/default.py b/docs/tutorials/wiki2/src/authorization/tutorial/views/default.py index 9358993ea..65c12ed3b 100644 --- a/docs/tutorials/wiki2/src/authorization/tutorial/views/default.py +++ b/docs/tutorials/wiki2/src/authorization/tutorial/views/default.py @@ -1,4 +1,4 @@ -import cgi +from pyramid.compat import escape import re from docutils.core import publish_parts @@ -25,10 +25,10 @@ def view_page(request): exists = request.dbsession.query(Page).filter_by(name=word).all() if exists: view_url = request.route_url('view_page', pagename=word) - return '%s' % (view_url, cgi.escape(word)) + return '%s' % (view_url, escape(word)) else: add_url = request.route_url('add_page', pagename=word) - return '%s' % (add_url, cgi.escape(word)) + return '%s' % (add_url, escape(word)) content = publish_parts(page.data, writer_name='html')['html_body'] content = wikiwords.sub(add_link, content) -- cgit v1.2.3 From 9d961de6cef714391683e24d4616d0db2a9e931d Mon Sep 17 00:00:00 2001 From: Jeremy Chen Date: Sat, 15 Apr 2017 19:28:27 +1000 Subject: Update default.py --- docs/tutorials/wiki2/src/tests/tutorial/views/default.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/tutorials/wiki2/src/tests/tutorial/views/default.py b/docs/tutorials/wiki2/src/tests/tutorial/views/default.py index 9358993ea..65c12ed3b 100644 --- a/docs/tutorials/wiki2/src/tests/tutorial/views/default.py +++ b/docs/tutorials/wiki2/src/tests/tutorial/views/default.py @@ -1,4 +1,4 @@ -import cgi +from pyramid.compat import escape import re from docutils.core import publish_parts @@ -25,10 +25,10 @@ def view_page(request): exists = request.dbsession.query(Page).filter_by(name=word).all() if exists: view_url = request.route_url('view_page', pagename=word) - return '%s' % (view_url, cgi.escape(word)) + return '%s' % (view_url, escape(word)) else: add_url = request.route_url('add_page', pagename=word) - return '%s' % (add_url, cgi.escape(word)) + return '%s' % (add_url, escape(word)) content = publish_parts(page.data, writer_name='html')['html_body'] content = wikiwords.sub(add_link, content) -- cgit v1.2.3 From de07193b25c3d03ea6829dac9002cca9bea17aa0 Mon Sep 17 00:00:00 2001 From: Ira Lun Date: Sat, 15 Apr 2017 22:57:31 +0100 Subject: Fix a typo in a comment. --- pyramid/config/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyramid/config/views.py b/pyramid/config/views.py index 65c9da585..dd8e9e787 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -1032,7 +1032,7 @@ class ViewsConfiguratorMixin(object): # XXX we could try to be more efficient here and register # a non-secured view for a multiview if none of the - # multiview's consituent views have a permission + # multiview's constituent views have a permission # associated with them, but this code is getting pretty # rough already if is_multiview: -- cgit v1.2.3 From 6d120e5f740f3b9a3f9ffd52da5c748b2f06cbca Mon Sep 17 00:00:00 2001 From: Aleph Melo Date: Sun, 16 Apr 2017 16:50:45 -0300 Subject: Fix #2927 - Change to listen = localhost:6543. --- docs/narr/myproject/development.ini | 2 +- docs/narr/startup.rst | 2 +- docs/quick_tour.rst | 2 +- docs/quick_tour/logging/development.ini | 2 +- docs/quick_tour/package/development.ini | 2 +- docs/quick_tour/sessions/development.ini | 2 +- docs/quick_tour/sqla_demo/development.ini | 2 +- docs/quick_tutorial/cookiecutters/development.ini | 2 +- docs/tutorials/wiki/src/authorization/development.ini | 2 +- docs/tutorials/wiki/src/basiclayout/development.ini | 2 +- docs/tutorials/wiki/src/installation/development.ini | 2 +- docs/tutorials/wiki/src/models/development.ini | 2 +- docs/tutorials/wiki/src/tests/development.ini | 2 +- docs/tutorials/wiki/src/views/development.ini | 2 +- docs/tutorials/wiki2/src/authentication/development.ini | 2 +- docs/tutorials/wiki2/src/authorization/development.ini | 2 +- docs/tutorials/wiki2/src/basiclayout/development.ini | 2 +- docs/tutorials/wiki2/src/installation/development.ini | 2 +- docs/tutorials/wiki2/src/models/development.ini | 2 +- docs/tutorials/wiki2/src/tests/development.ini | 2 +- docs/tutorials/wiki2/src/views/development.ini | 2 +- pyramid/scaffolds/alchemy/development.ini_tmpl | 2 +- pyramid/scaffolds/starter/development.ini_tmpl | 2 +- pyramid/scaffolds/zodb/development.ini_tmpl | 2 +- 24 files changed, 24 insertions(+), 24 deletions(-) diff --git a/docs/narr/myproject/development.ini b/docs/narr/myproject/development.ini index 5d110805a..20a8a4868 100644 --- a/docs/narr/myproject/development.ini +++ b/docs/narr/myproject/development.ini @@ -24,7 +24,7 @@ pyramid.includes = [server:main] use = egg:waitress#main -listen = 127.0.0.1:6543 [::1]:6543 +listen = localhost:6543 ### # logging configuration diff --git a/docs/narr/startup.rst b/docs/narr/startup.rst index cf4612602..08747fa89 100644 --- a/docs/narr/startup.rst +++ b/docs/narr/startup.rst @@ -132,7 +132,7 @@ Here's a high-level time-ordered overview of what happens when you press #. ``pserve`` starts the WSGI *server* defined within the ``[server:main]`` section. In our case, this is the Waitress server (``use = egg:waitress#main``), and it will listen on all interfaces on port 6543 - for both IPv4 and IPv6 (``listen = 127.0.0.1:6543 [::1]:6543``). The server + for both IPv4 and IPv6 (``listen = localhost:6543``). The server code itself is what prints ``serving on http://127.0.0.1:6543``. The server serves the application, and the application is running, waiting to receive requests. diff --git a/docs/quick_tour.rst b/docs/quick_tour.rst index 02c3ff811..cd1598a1c 100644 --- a/docs/quick_tour.rst +++ b/docs/quick_tour.rst @@ -618,7 +618,7 @@ We have a few decisions made for us in this configuration: #. *Choice of web server:* ``use = egg:waitress#main`` tells ``pserve`` to use the ``waitress`` server. -#. *Interfaces:* ``listen = 127.0.0.1:6543 [::1]:6543`` tells ``waitress`` to listen on all interfaces on port 6543 for both IPv4 and IPv6. +#. *Interfaces:* ``listen = localhost:6543`` tells ``waitress`` to listen on all interfaces on port 6543 for both IPv4 and IPv6. Additionally the ``development.ini`` generated by this cookiecutter wired up Python's standard logging. We'll now see in the console, for example, a log on diff --git a/docs/quick_tour/logging/development.ini b/docs/quick_tour/logging/development.ini index 1f19e373d..b0210cbad 100644 --- a/docs/quick_tour/logging/development.ini +++ b/docs/quick_tour/logging/development.ini @@ -24,7 +24,7 @@ pyramid.includes = [server:main] use = egg:waitress#main -listen = 127.0.0.1:6543 [::1]:6543 +listen = localhost:6543 ### # logging configuration diff --git a/docs/quick_tour/package/development.ini b/docs/quick_tour/package/development.ini index 1f19e373d..b0210cbad 100644 --- a/docs/quick_tour/package/development.ini +++ b/docs/quick_tour/package/development.ini @@ -24,7 +24,7 @@ pyramid.includes = [server:main] use = egg:waitress#main -listen = 127.0.0.1:6543 [::1]:6543 +listen = localhost:6543 ### # logging configuration diff --git a/docs/quick_tour/sessions/development.ini b/docs/quick_tour/sessions/development.ini index 1f19e373d..b0210cbad 100644 --- a/docs/quick_tour/sessions/development.ini +++ b/docs/quick_tour/sessions/development.ini @@ -24,7 +24,7 @@ pyramid.includes = [server:main] use = egg:waitress#main -listen = 127.0.0.1:6543 [::1]:6543 +listen = localhost:6543 ### # logging configuration diff --git a/docs/quick_tour/sqla_demo/development.ini b/docs/quick_tour/sqla_demo/development.ini index 17b57fd0d..8d45a0975 100644 --- a/docs/quick_tour/sqla_demo/development.ini +++ b/docs/quick_tour/sqla_demo/development.ini @@ -26,7 +26,7 @@ sqlalchemy.url = sqlite:///%(here)s/sqla_demo.sqlite [server:main] use = egg:waitress#main -listen = 127.0.0.1:6543 [::1]:6543 +listen = localhost:6543 ### # logging configuration diff --git a/docs/quick_tutorial/cookiecutters/development.ini b/docs/quick_tutorial/cookiecutters/development.ini index 86b54b51d..a5093fb52 100644 --- a/docs/quick_tutorial/cookiecutters/development.ini +++ b/docs/quick_tutorial/cookiecutters/development.ini @@ -24,7 +24,7 @@ pyramid.includes = [server:main] use = egg:waitress#main -listen = 127.0.0.1:6543 [::1]:6543 +listen = localhost:6543 ### # logging configuration diff --git a/docs/tutorials/wiki/src/authorization/development.ini b/docs/tutorials/wiki/src/authorization/development.ini index 82c8cf3a1..74e7457d6 100644 --- a/docs/tutorials/wiki/src/authorization/development.ini +++ b/docs/tutorials/wiki/src/authorization/development.ini @@ -26,7 +26,7 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 [server:main] use = egg:waitress#main -listen = 127.0.0.1:6543 [::1]:6543 +listen = localhost:6543 ### # logging configuration diff --git a/docs/tutorials/wiki/src/basiclayout/development.ini b/docs/tutorials/wiki/src/basiclayout/development.ini index 82c8cf3a1..74e7457d6 100644 --- a/docs/tutorials/wiki/src/basiclayout/development.ini +++ b/docs/tutorials/wiki/src/basiclayout/development.ini @@ -26,7 +26,7 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 [server:main] use = egg:waitress#main -listen = 127.0.0.1:6543 [::1]:6543 +listen = localhost:6543 ### # logging configuration diff --git a/docs/tutorials/wiki/src/installation/development.ini b/docs/tutorials/wiki/src/installation/development.ini index 82c8cf3a1..74e7457d6 100644 --- a/docs/tutorials/wiki/src/installation/development.ini +++ b/docs/tutorials/wiki/src/installation/development.ini @@ -26,7 +26,7 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 [server:main] use = egg:waitress#main -listen = 127.0.0.1:6543 [::1]:6543 +listen = localhost:6543 ### # logging configuration diff --git a/docs/tutorials/wiki/src/models/development.ini b/docs/tutorials/wiki/src/models/development.ini index 82c8cf3a1..74e7457d6 100644 --- a/docs/tutorials/wiki/src/models/development.ini +++ b/docs/tutorials/wiki/src/models/development.ini @@ -26,7 +26,7 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 [server:main] use = egg:waitress#main -listen = 127.0.0.1:6543 [::1]:6543 +listen = localhost:6543 ### # logging configuration diff --git a/docs/tutorials/wiki/src/tests/development.ini b/docs/tutorials/wiki/src/tests/development.ini index 82c8cf3a1..74e7457d6 100644 --- a/docs/tutorials/wiki/src/tests/development.ini +++ b/docs/tutorials/wiki/src/tests/development.ini @@ -26,7 +26,7 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 [server:main] use = egg:waitress#main -listen = 127.0.0.1:6543 [::1]:6543 +listen = localhost:6543 ### # logging configuration diff --git a/docs/tutorials/wiki/src/views/development.ini b/docs/tutorials/wiki/src/views/development.ini index 82c8cf3a1..74e7457d6 100644 --- a/docs/tutorials/wiki/src/views/development.ini +++ b/docs/tutorials/wiki/src/views/development.ini @@ -26,7 +26,7 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 [server:main] use = egg:waitress#main -listen = 127.0.0.1:6543 [::1]:6543 +listen = localhost:6543 ### # logging configuration diff --git a/docs/tutorials/wiki2/src/authentication/development.ini b/docs/tutorials/wiki2/src/authentication/development.ini index 1e08d1bce..0786c1f66 100644 --- a/docs/tutorials/wiki2/src/authentication/development.ini +++ b/docs/tutorials/wiki2/src/authentication/development.ini @@ -28,7 +28,7 @@ auth.secret = seekrit [server:main] use = egg:waitress#main -listen = 127.0.0.1:6543 [::1]:6543 +listen = localhost:6543 ### # logging configuration diff --git a/docs/tutorials/wiki2/src/authorization/development.ini b/docs/tutorials/wiki2/src/authorization/development.ini index 1e08d1bce..0786c1f66 100644 --- a/docs/tutorials/wiki2/src/authorization/development.ini +++ b/docs/tutorials/wiki2/src/authorization/development.ini @@ -28,7 +28,7 @@ auth.secret = seekrit [server:main] use = egg:waitress#main -listen = 127.0.0.1:6543 [::1]:6543 +listen = localhost:6543 ### # logging configuration diff --git a/docs/tutorials/wiki2/src/basiclayout/development.ini b/docs/tutorials/wiki2/src/basiclayout/development.ini index e9f6d8d3f..be80882a5 100644 --- a/docs/tutorials/wiki2/src/basiclayout/development.ini +++ b/docs/tutorials/wiki2/src/basiclayout/development.ini @@ -26,7 +26,7 @@ sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite [server:main] use = egg:waitress#main -listen = 127.0.0.1:6543 [::1]:6543 +listen = localhost:6543 ### # logging configuration diff --git a/docs/tutorials/wiki2/src/installation/development.ini b/docs/tutorials/wiki2/src/installation/development.ini index e9f6d8d3f..be80882a5 100644 --- a/docs/tutorials/wiki2/src/installation/development.ini +++ b/docs/tutorials/wiki2/src/installation/development.ini @@ -26,7 +26,7 @@ sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite [server:main] use = egg:waitress#main -listen = 127.0.0.1:6543 [::1]:6543 +listen = localhost:6543 ### # logging configuration diff --git a/docs/tutorials/wiki2/src/models/development.ini b/docs/tutorials/wiki2/src/models/development.ini index e9f6d8d3f..be80882a5 100644 --- a/docs/tutorials/wiki2/src/models/development.ini +++ b/docs/tutorials/wiki2/src/models/development.ini @@ -26,7 +26,7 @@ sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite [server:main] use = egg:waitress#main -listen = 127.0.0.1:6543 [::1]:6543 +listen = localhost:6543 ### # logging configuration diff --git a/docs/tutorials/wiki2/src/tests/development.ini b/docs/tutorials/wiki2/src/tests/development.ini index 1e08d1bce..0786c1f66 100644 --- a/docs/tutorials/wiki2/src/tests/development.ini +++ b/docs/tutorials/wiki2/src/tests/development.ini @@ -28,7 +28,7 @@ auth.secret = seekrit [server:main] use = egg:waitress#main -listen = 127.0.0.1:6543 [::1]:6543 +listen = localhost:6543 ### # logging configuration diff --git a/docs/tutorials/wiki2/src/views/development.ini b/docs/tutorials/wiki2/src/views/development.ini index e9f6d8d3f..be80882a5 100644 --- a/docs/tutorials/wiki2/src/views/development.ini +++ b/docs/tutorials/wiki2/src/views/development.ini @@ -26,7 +26,7 @@ sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite [server:main] use = egg:waitress#main -listen = 127.0.0.1:6543 [::1]:6543 +listen = localhost:6543 ### # logging configuration diff --git a/pyramid/scaffolds/alchemy/development.ini_tmpl b/pyramid/scaffolds/alchemy/development.ini_tmpl index 64ac5ab6c..6efde1d82 100644 --- a/pyramid/scaffolds/alchemy/development.ini_tmpl +++ b/pyramid/scaffolds/alchemy/development.ini_tmpl @@ -26,7 +26,7 @@ sqlalchemy.url = sqlite:///%(here)s/{{project}}.sqlite [server:main] use = egg:waitress#main -listen = 127.0.0.1:6543 [::1]:6543 +listen = localhost:6543 ### # logging configuration diff --git a/pyramid/scaffolds/starter/development.ini_tmpl b/pyramid/scaffolds/starter/development.ini_tmpl index de58ea63e..79302bd0a 100644 --- a/pyramid/scaffolds/starter/development.ini_tmpl +++ b/pyramid/scaffolds/starter/development.ini_tmpl @@ -24,7 +24,7 @@ pyramid.includes = [server:main] use = egg:waitress#main -listen = 127.0.0.1:6543 [::1]:6543 +listen = localhost:6543 ### # logging configuration diff --git a/pyramid/scaffolds/zodb/development.ini_tmpl b/pyramid/scaffolds/zodb/development.ini_tmpl index a155590f8..453b87e49 100644 --- a/pyramid/scaffolds/zodb/development.ini_tmpl +++ b/pyramid/scaffolds/zodb/development.ini_tmpl @@ -29,7 +29,7 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 [server:main] use = egg:waitress#main -listen = 127.0.0.1:6543 [::1]:6543 +listen = localhost:6543 ### # logging configuration -- cgit v1.2.3 From 6dbdeb344579522829007c64cfbe535d2f2ece1b Mon Sep 17 00:00:00 2001 From: Aleph Melo Date: Sun, 16 Apr 2017 17:00:06 -0300 Subject: Add a new contributor. --- CONTRIBUTORS.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 566e91195..1a6fc24ac 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -294,3 +294,5 @@ Contributors - Martin Frlin, 2016/12/7 - Kirill Kuzminykh, 2017/03/01 + +- Aleph Melo, 2017/04/16 \ No newline at end of file -- cgit v1.2.3 From 999bdae76694649b36d963b58cc1f3f91fa35ed7 Mon Sep 17 00:00:00 2001 From: Ira Lun Date: Sun, 16 Apr 2017 22:46:04 +0100 Subject: Fix typo in comment. --- pyramid/config/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyramid/config/views.py b/pyramid/config/views.py index dd8e9e787..2433ccfef 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -972,7 +972,7 @@ class ViewsConfiguratorMixin(object): def register_view(classifier, request_iface, derived_view): # A multiviews is a set of views which are registered for # exactly the same context type/request type/name triad. Each - # consituent view in a multiview differs only by the + # constituent view in a multiview differs only by the # predicates which it possesses. # To find a previously registered view for a context -- cgit v1.2.3 From 8197f18c5ed634625c749db674d7bdf97d1013ef Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Mon, 17 Apr 2017 12:29:55 -0700 Subject: fix rst syntax for index entries --- docs/narr/logging.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/narr/logging.rst b/docs/narr/logging.rst index 87682158b..9cc5b4ed8 100644 --- a/docs/narr/logging.rst +++ b/docs/narr/logging.rst @@ -16,7 +16,7 @@ to send log messages to loggers that you've configured. cookiecutter which does not create these files, the configuration information in this chapter may not be applicable. -.. index: +.. index:: pair: settings; logging pair: .ini; logging pair: logging; configuration -- cgit v1.2.3 From 4375bc690f31c488610e1bd72c2f1ea18b295121 Mon Sep 17 00:00:00 2001 From: Jeremy Chen Date: Wed, 19 Apr 2017 20:32:39 +1000 Subject: Update CONTRIBUTORS.txt --- CONTRIBUTORS.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 566e91195..3fe2c2d58 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -294,3 +294,5 @@ Contributors - Martin Frlin, 2016/12/7 - Kirill Kuzminykh, 2017/03/01 + +- Jeremy(Ching-Rui) Chen, 2017/04/19 -- cgit v1.2.3 From 6ff6fa265cb48a48daa61247bb1a068852ad13c0 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 23 Apr 2017 23:59:48 -0700 Subject: update user prompt for cookiecutter repo_name - refs: https://github.com/Pylons/pyramid-cookiecutter-starter/pull/27#issuecomment-296507821 --- docs/narr/project.rst | 2 +- docs/quick_tour.rst | 4 ++-- docs/quick_tutorial/cookiecutters.rst | 2 +- docs/tutorials/modwsgi/index.rst | 2 +- docs/tutorials/wiki/installation.rst | 2 +- docs/tutorials/wiki2/installation.rst | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/narr/project.rst b/docs/narr/project.rst index ce7e90793..9c44d4f16 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -94,7 +94,7 @@ If prompted for the first item, accept the default ``yes`` by hitting return. You've cloned ~/.cookiecutters/pyramid-cookiecutter-starter before. Is it okay to delete and re-clone it? [yes]: yes project_name [Pyramid Scaffold]: myproject - repo_name [scaffold]: myproject + repo_name [myproject]: myproject Select template_language: 1 - jinja2 2 - chameleon diff --git a/docs/quick_tour.rst b/docs/quick_tour.rst index 02c3ff811..571dfb356 100644 --- a/docs/quick_tour.rst +++ b/docs/quick_tour.rst @@ -519,7 +519,7 @@ If prompted for the first item, accept the default ``yes`` by hitting return. You've cloned ~/.cookiecutters/pyramid-cookiecutter-starter before. Is it okay to delete and re-clone it? [yes]: yes project_name [Pyramid Scaffold]: hello_world - repo_name [scaffold]: hello_world + repo_name [hello_world]: hello_world Select template_language: 1 - jinja2 2 - chameleon @@ -875,7 +875,7 @@ If prompted for the first item, accept the default ``yes`` by hitting return. You've cloned ~/.cookiecutters/pyramid-cookiecutter-alchemy before. Is it okay to delete and re-clone it? [yes]: yes project_name [Pyramid Scaffold]: sqla_demo - repo_name [scaffold]: sqla_demo + repo_name [sqla_demo]: sqla_demo We then run through the following commands as before. diff --git a/docs/quick_tutorial/cookiecutters.rst b/docs/quick_tutorial/cookiecutters.rst index edfd8cd69..337a5c535 100644 --- a/docs/quick_tutorial/cookiecutters.rst +++ b/docs/quick_tutorial/cookiecutters.rst @@ -37,7 +37,7 @@ Steps You've cloned ~/.cookiecutters/pyramid-cookiecutter-starter before. Is it okay to delete and re-clone it? [yes]: yes project_name [Pyramid Scaffold]: cc_starter - repo_name [scaffold]: cc_starter + repo_name [cc_starter]: cc_starter Select template_language: 1 - jinja2 2 - chameleon diff --git a/docs/tutorials/modwsgi/index.rst b/docs/tutorials/modwsgi/index.rst index 690266586..170f2ebc8 100644 --- a/docs/tutorials/modwsgi/index.rst +++ b/docs/tutorials/modwsgi/index.rst @@ -48,7 +48,7 @@ specific path information for commands and files. You've cloned ~/.cookiecutters/pyramid-cookiecutter-starter before. Is it okay to delete and re-clone it? [yes]: yes project_name [Pyramid Scaffold]: myproject - repo_name [scaffold]: myproject + repo_name [myproject]: myproject Select template_language: 1 - jinja2 2 - chameleon diff --git a/docs/tutorials/wiki/installation.rst b/docs/tutorials/wiki/installation.rst index 6be826395..de057b1cc 100644 --- a/docs/tutorials/wiki/installation.rst +++ b/docs/tutorials/wiki/installation.rst @@ -50,7 +50,7 @@ If prompted for the first item, accept the default ``yes`` by hitting return. You've cloned ~/.cookiecutters/pyramid-cookiecutter-zodb before. Is it okay to delete and re-clone it? [yes]: yes project_name [Pyramid Scaffold]: myproj - repo_name [scaffold]: tutorial + repo_name [myproj]: tutorial Change directory into your newly created project ------------------------------------------------ diff --git a/docs/tutorials/wiki2/installation.rst b/docs/tutorials/wiki2/installation.rst index 9eeb1711d..c61d4360d 100644 --- a/docs/tutorials/wiki2/installation.rst +++ b/docs/tutorials/wiki2/installation.rst @@ -62,7 +62,7 @@ If prompted for the first item, accept the default ``yes`` by hitting return. You've cloned ~/.cookiecutters/pyramid-cookiecutter-alchemy before. Is it okay to delete and re-clone it? [yes]: yes project_name [Pyramid Scaffold]: myproj - repo_name [scaffold]: tutorial + repo_name [myproj]: tutorial Change directory into your newly created project ------------------------------------------------ -- cgit v1.2.3 From 2ded2fc216b4caaf0d97813413943e0838b6eaaa Mon Sep 17 00:00:00 2001 From: Matthew Wilkes Date: Wed, 26 Apr 2017 15:41:47 +0100 Subject: Apply drafting changes to documentation. --- CHANGES.txt | 2 +- docs/glossary.rst | 5 +++++ docs/narr/security.rst | 4 ++++ docs/narr/sessions.rst | 4 ---- pyramid/config/security.py | 2 +- pyramid/config/views.py | 6 ++++++ pyramid/csrf.py | 43 ++++++++++++++++++++++--------------------- pyramid/interfaces.py | 4 ++-- 8 files changed, 41 insertions(+), 29 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 9d6264688..762550053 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -25,7 +25,7 @@ Features appropriately. See https://github.com/Pylons/pyramid/pull/2989 - A new CSRF implementation, :class:`pyramid.csrf.SessionCSRF` has been added, - which deleagates all CSRF generation to the current session, following the + which delegates all CSRF generation to the current session, following the old API for this. A ``get_csrf_token()`` method is now available in template global scope, to make it easy for template developers to get the current CSRF token without adding it to Python code. diff --git a/docs/glossary.rst b/docs/glossary.rst index 0a46fac3b..0cf96f488 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -891,6 +891,11 @@ Glossary :meth:`pyramid.config.Configurator.set_session_factory` for more information. + CSRF storage policy + A utility that implements :class:`pyramid.interfaces.ICSRFStoragePolicy` + which is responsible for allocating CSRF tokens to a user and verifying + that a provided token is acceptable. + Mako `Mako `_ is a template language which refines the familiar ideas of componentized layout and inheritance diff --git a/docs/narr/security.rst b/docs/narr/security.rst index e67f7b98c..86e5c1ef4 100644 --- a/docs/narr/security.rst +++ b/docs/narr/security.rst @@ -766,6 +766,10 @@ a secret across two different subsystems might drop the security of signing to zero. Keys should not be re-used across different contexts where an attacker has the possibility of providing a chosen plaintext. +.. index:: + single: preventing cross-site request forgery attacks + single: cross-site request forgery attacks, prevention + Preventing Cross-Site Request Forgery Attacks --------------------------------------------- diff --git a/docs/narr/sessions.rst b/docs/narr/sessions.rst index 86fe2a139..7e2469d54 100644 --- a/docs/narr/sessions.rst +++ b/docs/narr/sessions.rst @@ -315,7 +315,3 @@ flash storage. ['info message'] >>> request.session.peek_flash() [] - -.. index:: - single: preventing cross-site request forgery attacks - single: cross-site request forgery attacks, prevention diff --git a/pyramid/config/security.py b/pyramid/config/security.py index 0b565e322..6f5b36d3a 100644 --- a/pyramid/config/security.py +++ b/pyramid/config/security.py @@ -232,7 +232,7 @@ class SecurityConfiguratorMixin(object): @action_method def set_csrf_storage_policy(self, policy): """ - Set the CSRF storage policy used by subsequent view registrations. + Set the :term:`CSRF storage policy` used by subsequent view registrations. ``policy`` is a class that implements the :meth:`pyramid.interfaces.ICSRFStoragePolicy` interface that will be used for all diff --git a/pyramid/config/views.py b/pyramid/config/views.py index e037f7706..2fc243fac 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -651,6 +651,12 @@ class ViewsConfiguratorMixin(object): .. versionadded:: 1.4a2 + .. versionchanged:: 1.9 + This feature requires either a :term:`session factory` to have been + configured, or a :term:`CSRF storage policy` other than the default + to be in use. + + physical_path If specified, this value should be a string or a tuple representing diff --git a/pyramid/csrf.py b/pyramid/csrf.py index 4c5a73940..ffc7b5fe3 100644 --- a/pyramid/csrf.py +++ b/pyramid/csrf.py @@ -31,7 +31,7 @@ class SessionCSRFStoragePolicy(object): Note that using this CSRF implementation requires that a :term:`session factory` is configured. - .. versionadded :: 1.8a1 + .. versionadded :: 1.9 """ def new_csrf_token(self, request): """ Sets a new CSRF token into the session and returns it. """ @@ -43,8 +43,8 @@ class SessionCSRFStoragePolicy(object): return request.session.get_csrf_token() def check_csrf_token(self, request, supplied_token): - """ Returns True if supplied_token is the same value as get_csrf_token - returns for this request. """ + """ Returns ``True`` if ``supplied_token is`` the same value as + ``get_csrf_token(request)``.""" expected = self.get_csrf_token(request) return not strings_differ( bytes_(expected, 'ascii'), @@ -55,11 +55,11 @@ class SessionCSRFStoragePolicy(object): class CookieCSRFStoragePolicy(object): """ An alternative CSRF implementation that stores its information in unauthenticated cookies, known as the 'Double Submit Cookie' method in the - OWASP CSRF guidelines. This gives some additional flexibility with regards - to scaling as the tokens can be generated and verified by a front-end - server. + `OWASP CSRF guidelines `_. + This gives some additional flexibility with regards to scaling as the tokens + can be generated and verified by a front-end server. - .. versionadded :: 1.8a1 + .. versionadded :: 1.9 """ def __init__(self, cookie_name='csrf_token', secure=False, httponly=False, @@ -96,8 +96,8 @@ class CookieCSRFStoragePolicy(object): return token def check_csrf_token(self, request, supplied_token): - """ Returns True if supplied_token is the same value as get_csrf_token - returns for this request. """ + """ Returns ``True`` if ``supplied_token is`` the same value as + ``get_csrf_token(request)``.""" expected = self.get_csrf_token(request) return not strings_differ( bytes_(expected, 'ascii'), @@ -109,7 +109,7 @@ def get_csrf_token(request): a new one using ``new_csrf_token(request)`` if one does not exist. This calls the equivalent method in the chosen CSRF protection implementation. - .. versionadded :: 1.8a1 + .. versionadded :: 1.9 """ registry = request.registry csrf = registry.getUtility(ICSRFStoragePolicy) @@ -121,7 +121,7 @@ def new_csrf_token(request): implementation defined manner. This calls the equivalent method in the chosen CSRF protection implementation. - .. versionadded :: 1.8a1 + .. versionadded :: 1.9 """ registry = request.registry csrf = registry.getUtility(ICSRFStoragePolicy) @@ -159,8 +159,8 @@ def check_csrf_token(request, considered valid. It must be passed in either the request body or a header. - .. versionchanged:: 1.8a1 - Moved from pyramid.session to pyramid.csrf + .. versionchanged:: 1.9 + Moved from :mod:`pyramid.session` to :mod:`pyramid.csrf` """ supplied_token = "" # We first check the headers for a csrf token, as that is significantly @@ -192,27 +192,28 @@ def check_csrf_token(request, def check_csrf_origin(request, trusted_origins=None, raises=True): """ - Check the Origin of the request to see if it is a cross site request or + Check the ``Origin`` of the request to see if it is a cross site request or not. - If the value supplied by the Origin or Referer header isn't one of the + If the value supplied by the ``Origin`` or ``Referer`` header isn't one of the trusted origins and ``raises`` is ``True``, this function will raise a - :exc:`pyramid.exceptions.BadCSRFOrigin` exception but if ``raises`` is - ``False`` this function will return ``False`` instead. If the CSRF origin + :exc:`pyramid.exceptions.BadCSRFOrigin` exception, but if ``raises`` is + ``False``, this function will return ``False`` instead. If the CSRF origin checks are successful this function will return ``True`` unconditionally. Additional trusted origins may be added by passing a list of domain (and - ports if nonstandard like `['example.com', 'dev.example.com:8080']`) in + ports if nonstandard like ``['example.com', 'dev.example.com:8080']``) in with the ``trusted_origins`` parameter. If ``trusted_origins`` is ``None`` (the default) this list of additional domains will be pulled from the ``pyramid.csrf_trusted_origins`` setting. - Note that this function will do nothing if request.scheme is not https. + Note that this function will do nothing if ``request.scheme`` is not + ``https``. .. versionadded:: 1.7 - .. versionchanged:: 1.8a1 - Moved from pyramid.session to pyramid.csrf + .. versionchanged:: 1.9 + Moved from :mod:`pyramid.session` to :mod:`pyramid.csrf` """ def _fail(reason): if raises: diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index aab5647a1..c3b6b164d 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -999,8 +999,8 @@ class ICSRFStoragePolicy(Interface): """ def check_csrf_token(request, supplied_token): - """ Returns a boolean that represents if supplied_token is a valid CSRF - token for this request. Comparing strings for equality must be done + """ Returns a boolean that represents if ``supplied_token`` is a valid + CSRF token for this request. Comparing strings for equality must be done using :func:`pyramid.utils.strings_differ` to avoid timing attacks. """ -- cgit v1.2.3 From 4b3603ad2f4850605c45e1b7bf4f077584303641 Mon Sep 17 00:00:00 2001 From: Matthew Wilkes Date: Wed, 26 Apr 2017 15:43:18 +0100 Subject: Move CSRF storage policy registration out of PHASE_1 config and simplify tests given previous improvements to CSRF. --- docs/narr/extconfig.rst | 1 - pyramid/config/security.py | 2 +- pyramid/csrf.py | 6 ------ pyramid/testing.py | 1 + pyramid/tests/test_csrf.py | 14 +++++--------- 5 files changed, 7 insertions(+), 17 deletions(-) diff --git a/docs/narr/extconfig.rst b/docs/narr/extconfig.rst index c20685cbf..4009ec1dc 100644 --- a/docs/narr/extconfig.rst +++ b/docs/narr/extconfig.rst @@ -263,7 +263,6 @@ Pre-defined Phases - :meth:`pyramid.config.Configurator.override_asset` - :meth:`pyramid.config.Configurator.set_authorization_policy` - :meth:`pyramid.config.Configurator.set_default_csrf_options` -- :meth:`pyramid.config.Configurator.set_csrf_storage_policy` - :meth:`pyramid.config.Configurator.set_default_permission` - :meth:`pyramid.config.Configurator.set_view_mapper` diff --git a/pyramid/config/security.py b/pyramid/config/security.py index 6f5b36d3a..9d59ca78e 100644 --- a/pyramid/config/security.py +++ b/pyramid/config/security.py @@ -241,7 +241,7 @@ class SecurityConfiguratorMixin(object): def register(): self.registry.registerUtility(policy, ICSRFStoragePolicy) - self.action(ICSRFStoragePolicy, register, order=PHASE1_CONFIG) + self.action(ICSRFStoragePolicy, register) @implementer(IDefaultCSRFOptions) diff --git a/pyramid/csrf.py b/pyramid/csrf.py index ffc7b5fe3..5d183bb57 100644 --- a/pyramid/csrf.py +++ b/pyramid/csrf.py @@ -177,12 +177,6 @@ def check_csrf_token(request, supplied_token = request.POST.get(token, "") policy = request.registry.queryUtility(ICSRFStoragePolicy) - if policy is None: - # There is no policy set, but we are trying to validate a CSRF token - # This means explicit validation has been asked for without configuring - # the CSRF implementation. Fall back to SessionCSRFStoragePolicy as that is the - # default - policy = SessionCSRFStoragePolicy() if not policy.check_csrf_token(request, supplied_token): if raises: raise BadCSRFToken('check_csrf_token(): Invalid token') diff --git a/pyramid/testing.py b/pyramid/testing.py index 877b351db..69b30e83f 100644 --- a/pyramid/testing.py +++ b/pyramid/testing.py @@ -479,6 +479,7 @@ def setUp(registry=None, request=None, hook_zca=True, autocommit=True, config.add_default_view_derivers() config.add_default_route_predicates() config.add_default_tweens() + config.add_default_security() config.commit() global have_zca try: diff --git a/pyramid/tests/test_csrf.py b/pyramid/tests/test_csrf.py index e6ae05eec..fcb6333ee 100644 --- a/pyramid/tests/test_csrf.py +++ b/pyramid/tests/test_csrf.py @@ -15,11 +15,9 @@ class Test_get_csrf_token(unittest.TestCase): from pyramid.csrf import get_csrf_token return get_csrf_token(*args, **kwargs) - def test_no_csrf_utility_registered(self): + def test_no_override_csrf_utility_registered(self): request = testing.DummyRequest() - - with self.assertRaises(ComponentLookupError): - self._callFUT(request) + self._callFUT(request) def test_success(self): self.config.set_csrf_storage_policy(DummyCSRF()) @@ -38,11 +36,9 @@ class Test_new_csrf_token(unittest.TestCase): from pyramid.csrf import new_csrf_token return new_csrf_token(*args, **kwargs) - def test_no_csrf_utility_registered(self): + def test_no_override_csrf_utility_registered(self): request = testing.DummyRequest() - - with self.assertRaises(ComponentLookupError): - self._callFUT(request) + self._callFUT(request) def test_success(self): self.config.set_csrf_storage_policy(DummyCSRF()) @@ -188,7 +184,7 @@ class Test_check_csrf_token(unittest.TestCase): def setUp(self): self.config = testing.setUp() - # set up CSRF (this will also register SessionCSRFStoragePolicy policy) + # set up CSRF self.config.set_default_csrf_options(require_csrf=False) def _callFUT(self, *args, **kwargs): -- cgit v1.2.3 From de299eb1ca359a2f13b109e57cff97098fbe00ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20HUBSCHER?= Date: Thu, 27 Apr 2017 10:00:28 +0200 Subject: Fix underlined title. --- docs/narr/myproject/README.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/narr/myproject/README.txt b/docs/narr/myproject/README.txt index 41ef0ff91..2ffc0acba 100644 --- a/docs/narr/myproject/README.txt +++ b/docs/narr/myproject/README.txt @@ -1,5 +1,5 @@ MyProject -=============================== +========= Getting Started --------------- -- cgit v1.2.3 From 68f673ff520c4bdffac796c9965936ec57916c72 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Fri, 28 Apr 2017 00:09:20 -0700 Subject: update cookiecutter README.txt throughout docs - https://github.com/Pylons/pyramid-cookiecutter-starter/pull/28 - https://github.com/Pylons/pyramid-cookiecutter-zodb/pull/7 - https://github.com/Pylons/pyramid-cookiecutter-alchemy/pull/8 --- docs/quick_tour/logging/README.txt | 2 +- docs/quick_tour/package/README.txt | 2 +- docs/quick_tour/sessions/README.txt | 2 +- docs/quick_tour/sqla_demo/README.txt | 2 +- docs/quick_tutorial/cookiecutters/README.txt | 2 +- docs/tutorials/wiki/src/authorization/README.txt | 2 +- docs/tutorials/wiki/src/basiclayout/README.txt | 2 +- docs/tutorials/wiki/src/installation/README.txt | 2 +- docs/tutorials/wiki/src/models/README.txt | 2 +- docs/tutorials/wiki/src/tests/README.txt | 2 +- docs/tutorials/wiki/src/views/README.txt | 2 +- docs/tutorials/wiki2/src/authentication/README.txt | 2 +- docs/tutorials/wiki2/src/authorization/README.txt | 2 +- docs/tutorials/wiki2/src/basiclayout/README.txt | 2 +- docs/tutorials/wiki2/src/installation/README.txt | 2 +- docs/tutorials/wiki2/src/models/README.txt | 2 +- docs/tutorials/wiki2/src/tests/README.txt | 2 +- docs/tutorials/wiki2/src/views/README.txt | 2 +- 18 files changed, 18 insertions(+), 18 deletions(-) diff --git a/docs/quick_tour/logging/README.txt b/docs/quick_tour/logging/README.txt index fb7bde0a7..ff70a1354 100644 --- a/docs/quick_tour/logging/README.txt +++ b/docs/quick_tour/logging/README.txt @@ -1,5 +1,5 @@ hello_world -=============================== +=========== Getting Started --------------- diff --git a/docs/quick_tour/package/README.txt b/docs/quick_tour/package/README.txt index fb7bde0a7..ff70a1354 100644 --- a/docs/quick_tour/package/README.txt +++ b/docs/quick_tour/package/README.txt @@ -1,5 +1,5 @@ hello_world -=============================== +=========== Getting Started --------------- diff --git a/docs/quick_tour/sessions/README.txt b/docs/quick_tour/sessions/README.txt index fb7bde0a7..ff70a1354 100644 --- a/docs/quick_tour/sessions/README.txt +++ b/docs/quick_tour/sessions/README.txt @@ -1,5 +1,5 @@ hello_world -=============================== +=========== Getting Started --------------- diff --git a/docs/quick_tour/sqla_demo/README.txt b/docs/quick_tour/sqla_demo/README.txt index 1659e47ab..27bbff5a7 100644 --- a/docs/quick_tour/sqla_demo/README.txt +++ b/docs/quick_tour/sqla_demo/README.txt @@ -1,5 +1,5 @@ sqla_demo -=============================== +========= Getting Started --------------- diff --git a/docs/quick_tutorial/cookiecutters/README.txt b/docs/quick_tutorial/cookiecutters/README.txt index 4b1f31bf3..55c5dcec6 100644 --- a/docs/quick_tutorial/cookiecutters/README.txt +++ b/docs/quick_tutorial/cookiecutters/README.txt @@ -1,5 +1,5 @@ cc_starter -=============================== +========== Getting Started --------------- diff --git a/docs/tutorials/wiki/src/authorization/README.txt b/docs/tutorials/wiki/src/authorization/README.txt index 98683bf8c..5ec53bf9d 100644 --- a/docs/tutorials/wiki/src/authorization/README.txt +++ b/docs/tutorials/wiki/src/authorization/README.txt @@ -1,5 +1,5 @@ myproj -=============================== +====== Getting Started --------------- diff --git a/docs/tutorials/wiki/src/basiclayout/README.txt b/docs/tutorials/wiki/src/basiclayout/README.txt index 98683bf8c..5ec53bf9d 100644 --- a/docs/tutorials/wiki/src/basiclayout/README.txt +++ b/docs/tutorials/wiki/src/basiclayout/README.txt @@ -1,5 +1,5 @@ myproj -=============================== +====== Getting Started --------------- diff --git a/docs/tutorials/wiki/src/installation/README.txt b/docs/tutorials/wiki/src/installation/README.txt index 98683bf8c..5ec53bf9d 100644 --- a/docs/tutorials/wiki/src/installation/README.txt +++ b/docs/tutorials/wiki/src/installation/README.txt @@ -1,5 +1,5 @@ myproj -=============================== +====== Getting Started --------------- diff --git a/docs/tutorials/wiki/src/models/README.txt b/docs/tutorials/wiki/src/models/README.txt index 98683bf8c..5ec53bf9d 100644 --- a/docs/tutorials/wiki/src/models/README.txt +++ b/docs/tutorials/wiki/src/models/README.txt @@ -1,5 +1,5 @@ myproj -=============================== +====== Getting Started --------------- diff --git a/docs/tutorials/wiki/src/tests/README.txt b/docs/tutorials/wiki/src/tests/README.txt index 98683bf8c..5ec53bf9d 100644 --- a/docs/tutorials/wiki/src/tests/README.txt +++ b/docs/tutorials/wiki/src/tests/README.txt @@ -1,5 +1,5 @@ myproj -=============================== +====== Getting Started --------------- diff --git a/docs/tutorials/wiki/src/views/README.txt b/docs/tutorials/wiki/src/views/README.txt index 98683bf8c..5ec53bf9d 100644 --- a/docs/tutorials/wiki/src/views/README.txt +++ b/docs/tutorials/wiki/src/views/README.txt @@ -1,5 +1,5 @@ myproj -=============================== +====== Getting Started --------------- diff --git a/docs/tutorials/wiki2/src/authentication/README.txt b/docs/tutorials/wiki2/src/authentication/README.txt index 5e21b8aa4..81102a869 100644 --- a/docs/tutorials/wiki2/src/authentication/README.txt +++ b/docs/tutorials/wiki2/src/authentication/README.txt @@ -1,5 +1,5 @@ myproj -=============================== +====== Getting Started --------------- diff --git a/docs/tutorials/wiki2/src/authorization/README.txt b/docs/tutorials/wiki2/src/authorization/README.txt index 5e21b8aa4..81102a869 100644 --- a/docs/tutorials/wiki2/src/authorization/README.txt +++ b/docs/tutorials/wiki2/src/authorization/README.txt @@ -1,5 +1,5 @@ myproj -=============================== +====== Getting Started --------------- diff --git a/docs/tutorials/wiki2/src/basiclayout/README.txt b/docs/tutorials/wiki2/src/basiclayout/README.txt index 5e21b8aa4..81102a869 100644 --- a/docs/tutorials/wiki2/src/basiclayout/README.txt +++ b/docs/tutorials/wiki2/src/basiclayout/README.txt @@ -1,5 +1,5 @@ myproj -=============================== +====== Getting Started --------------- diff --git a/docs/tutorials/wiki2/src/installation/README.txt b/docs/tutorials/wiki2/src/installation/README.txt index 5e21b8aa4..81102a869 100644 --- a/docs/tutorials/wiki2/src/installation/README.txt +++ b/docs/tutorials/wiki2/src/installation/README.txt @@ -1,5 +1,5 @@ myproj -=============================== +====== Getting Started --------------- diff --git a/docs/tutorials/wiki2/src/models/README.txt b/docs/tutorials/wiki2/src/models/README.txt index 5e21b8aa4..81102a869 100644 --- a/docs/tutorials/wiki2/src/models/README.txt +++ b/docs/tutorials/wiki2/src/models/README.txt @@ -1,5 +1,5 @@ myproj -=============================== +====== Getting Started --------------- diff --git a/docs/tutorials/wiki2/src/tests/README.txt b/docs/tutorials/wiki2/src/tests/README.txt index 5e21b8aa4..81102a869 100644 --- a/docs/tutorials/wiki2/src/tests/README.txt +++ b/docs/tutorials/wiki2/src/tests/README.txt @@ -1,5 +1,5 @@ myproj -=============================== +====== Getting Started --------------- diff --git a/docs/tutorials/wiki2/src/views/README.txt b/docs/tutorials/wiki2/src/views/README.txt index 5e21b8aa4..81102a869 100644 --- a/docs/tutorials/wiki2/src/views/README.txt +++ b/docs/tutorials/wiki2/src/views/README.txt @@ -1,5 +1,5 @@ myproj -=============================== +====== Getting Started --------------- -- cgit v1.2.3 From 682a9b9df6f42f8261daa077f04b47b65bf00c34 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sat, 29 Apr 2017 01:43:38 -0500 Subject: final cleanup of csrf decoupling in #2854 - Renamed `SessionCSRFStoragePolicy` to `LegacySessionCSRFStoragePolicy` for the version that uses the legacy `ISession.get_csrf_token` and `ISession.new_csrf_token` apis and set that as the default. - Added new `SessionCSRFStoragePolicy` that stores data in the session similar to how the `SessionAuthenticationPolicy` works. - `CookieCSRFStoragePolicy` did not properly return the newly generated token from `get_csrf_token` after calling `new_csrf_token`. It needed to cache the new value since the response callback does not affect the current request. - `CookieCSRFStoragePolicy` was not forwarding the `domain` value to the `CookieProfile` causing that setting to be ignored. - Removed `check_csrf_token` from the `ICSRFStoragePolicy` interface to simplify implementations of storage policies. - Added an introspectable item for the configured storage policy so that it appears on the debugtoolbar. - Added a change note on `ISession` that it no longer required the csrf methods. - Leave deprecated shims in ``pyramid.session`` for ``check_csrf_origin`` and ``check_csrf_token``. --- CHANGES.txt | 13 +++--- docs/api/csrf.rst | 3 ++ docs/narr/security.rst | 1 + docs/narr/templates.rst | 4 ++ pyramid/config/security.py | 20 ++++++--- pyramid/csrf.py | 109 +++++++++++++++++++++++++++++---------------- pyramid/interfaces.py | 30 ++++++++----- pyramid/session.py | 14 ++++++ pyramid/tests/test_csrf.py | 108 ++++++++++++++++++++++---------------------- pyramid/tests/test_util.py | 6 ++- 10 files changed, 190 insertions(+), 118 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 762550053..7d70abbb8 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -24,12 +24,13 @@ Features can be alleviated by invoking ``config.begin()`` and ``config.end()`` appropriately. See https://github.com/Pylons/pyramid/pull/2989 -- A new CSRF implementation, :class:`pyramid.csrf.SessionCSRF` has been added, - which delegates all CSRF generation to the current session, following the - old API for this. A ``get_csrf_token()`` method is now available in template - global scope, to make it easy for template developers to get the current CSRF - token without adding it to Python code. - See https://github.com/Pylons/pyramid/pull/2854 +- A new CSRF implementation, ``pyramid.csrf.SessionCSRFStoragePolicy``, + has been added which delegates all CSRF generation to the current session, + following the old API for this. A ``pyramid.csrf.get_csrf_token()`` api is now + available in template global scope, to make it easy for template developers + to get the current CSRF token without adding it to Python code. + See https://github.com/Pylons/pyramid/pull/2854 and + https://github.com/Pylons/pyramid/pull/3019 Bug Fixes diff --git a/docs/api/csrf.rst b/docs/api/csrf.rst index f890ee660..38501546e 100644 --- a/docs/api/csrf.rst +++ b/docs/api/csrf.rst @@ -5,6 +5,9 @@ .. automodule:: pyramid.csrf + .. autoclass:: LegacySessionCSRFStoragePolicy + :members: + .. autoclass:: SessionCSRFStoragePolicy :members: diff --git a/docs/narr/security.rst b/docs/narr/security.rst index 86e5c1ef4..ddf496b69 100644 --- a/docs/narr/security.rst +++ b/docs/narr/security.rst @@ -824,6 +824,7 @@ If no CSRF token previously existed for this user, then a new token will be set into the session and returned. The newly created token will be opaque and randomized. +.. _get_csrf_token_in_templates: Using the ``get_csrf_token`` global in templates ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/narr/templates.rst b/docs/narr/templates.rst index 6b3b5fcce..4eadbd2f0 100644 --- a/docs/narr/templates.rst +++ b/docs/narr/templates.rst @@ -228,6 +228,10 @@ These values are provided to the template: provided if the template is rendered as the result of a ``renderer=`` argument to the view configuration being used. +``get_csrf_token()`` + A convenience function to access the current CSRF token. See + :ref:`get_csrf_token_in_templates` for more information. + ``renderer_name`` The renderer name used to perform the rendering, e.g., ``mypackage:templates/foo.pt``. diff --git a/pyramid/config/security.py b/pyramid/config/security.py index 9d59ca78e..8e4c908d3 100644 --- a/pyramid/config/security.py +++ b/pyramid/config/security.py @@ -10,7 +10,7 @@ from pyramid.interfaces import ( PHASE2_CONFIG, ) -from pyramid.csrf import SessionCSRFStoragePolicy +from pyramid.csrf import LegacySessionCSRFStoragePolicy from pyramid.exceptions import ConfigurationError from pyramid.util import action_method from pyramid.util import as_sorted_tuple @@ -19,7 +19,7 @@ from pyramid.util import as_sorted_tuple class SecurityConfiguratorMixin(object): def add_default_security(self): - self.set_csrf_storage_policy(SessionCSRFStoragePolicy()) + self.set_csrf_storage_policy(LegacySessionCSRFStoragePolicy()) @action_method def set_authentication_policy(self, policy): @@ -232,16 +232,22 @@ class SecurityConfiguratorMixin(object): @action_method def set_csrf_storage_policy(self, policy): """ - Set the :term:`CSRF storage policy` used by subsequent view registrations. + Set the :term:`CSRF storage policy` used by subsequent view + registrations. ``policy`` is a class that implements the - :meth:`pyramid.interfaces.ICSRFStoragePolicy` interface that will be used for all - CSRF functionality. + :meth:`pyramid.interfaces.ICSRFStoragePolicy` interface and defines + how to generate and persist CSRF tokens. + """ def register(): self.registry.registerUtility(policy, ICSRFStoragePolicy) - - self.action(ICSRFStoragePolicy, register) + intr = self.introspectable('csrf storage policy', + None, + policy, + 'csrf storage policy') + intr['policy'] = policy + self.action(ICSRFStoragePolicy, register, introspectables=(intr,)) @implementer(IDefaultCSRFOptions) diff --git a/pyramid/csrf.py b/pyramid/csrf.py index 5d183bb57..1910e4ec8 100644 --- a/pyramid/csrf.py +++ b/pyramid/csrf.py @@ -7,8 +7,9 @@ from zope.interface import implementer from pyramid.authentication import _SimpleSerializer from pyramid.compat import ( + bytes_, urlparse, - bytes_ + text_, ) from pyramid.exceptions import ( BadCSRFOrigin, @@ -23,44 +24,79 @@ from pyramid.util import ( @implementer(ICSRFStoragePolicy) -class SessionCSRFStoragePolicy(object): - """ The default CSRF implementation, which mimics the behavior from older - versions of Pyramid. The ``new_csrf_token`` and ``get_csrf_token`` methods - are indirected to the underlying session implementation. +class LegacySessionCSRFStoragePolicy(object): + """ A CSRF storage policy that defers control of CSRF storage to the + session. + + This policy maintains compatibility with legacy ISession implementations + that know how to manage CSRF tokens themselves via + ``ISession.new_csrf_token`` and ``ISession.get_csrf_token``. Note that using this CSRF implementation requires that a :term:`session factory` is configured. - .. versionadded :: 1.9 + .. versionadded:: 1.9 + """ def new_csrf_token(self, request): """ Sets a new CSRF token into the session and returns it. """ return request.session.new_csrf_token() def get_csrf_token(self, request): - """ Returns the currently active CSRF token from the session, generating - a new one if needed.""" + """ Returns the currently active CSRF token from the session, + generating a new one if needed.""" return request.session.get_csrf_token() - def check_csrf_token(self, request, supplied_token): - """ Returns ``True`` if ``supplied_token is`` the same value as - ``get_csrf_token(request)``.""" - expected = self.get_csrf_token(request) - return not strings_differ( - bytes_(expected, 'ascii'), - bytes_(supplied_token, 'ascii'), - ) + +@implementer(ICSRFStoragePolicy) +class SessionCSRFStoragePolicy(object): + """ A CSRF storage policy that persists the CSRF token in the session. + + Note that using this CSRF implementation requires that + a :term:`session factory` is configured. + + ``key`` + + The session key where the CSRF token will be stored. + Default: `_csrft_`. + + .. versionadded:: 1.9 + + """ + _token_factory = staticmethod(lambda: text_(uuid.uuid4().hex)) + + def __init__(self, key='_csrft_'): + self.key = key + + def new_csrf_token(self, request): + """ Sets a new CSRF token into the session and returns it. """ + token = self._token_factory() + request.session[self.key] = token + return token + + def get_csrf_token(self, request): + """ Returns the currently active CSRF token from the session, + generating a new one if needed.""" + token = request.session.get(self.key, None) + if not token: + token = self.new_csrf_token(request) + return token + @implementer(ICSRFStoragePolicy) class CookieCSRFStoragePolicy(object): """ An alternative CSRF implementation that stores its information in unauthenticated cookies, known as the 'Double Submit Cookie' method in the - `OWASP CSRF guidelines `_. - This gives some additional flexibility with regards to scaling as the tokens - can be generated and verified by a front-end server. + `OWASP CSRF guidelines `_. This gives some additional flexibility with + regards to scaling as the tokens can be generated and verified by a + front-end server. + + .. versionadded:: 1.9 - .. versionadded :: 1.9 """ + _token_factory = staticmethod(lambda: text_(uuid.uuid4().hex)) def __init__(self, cookie_name='csrf_token', secure=False, httponly=False, domain=None, max_age=None, path='/'): @@ -71,13 +107,15 @@ class CookieCSRFStoragePolicy(object): max_age=max_age, httponly=httponly, path=path, + domains=[domain], serializer=serializer ) - self.domain = domain + self.cookie_name = cookie_name def new_csrf_token(self, request): """ Sets a new CSRF token into the request and returns it. """ - token = uuid.uuid4().hex + token = self._token_factory() + request.cookies[self.cookie_name] = token def set_cookie(request, response): self.cookie_profile.set_cookies( response, @@ -95,14 +133,6 @@ class CookieCSRFStoragePolicy(object): token = self.new_csrf_token(request) return token - def check_csrf_token(self, request, supplied_token): - """ Returns ``True`` if ``supplied_token is`` the same value as - ``get_csrf_token(request)``.""" - expected = self.get_csrf_token(request) - return not strings_differ( - bytes_(expected, 'ascii'), - bytes_(supplied_token, 'ascii'), - ) def get_csrf_token(request): """ Get the currently active CSRF token for the request passed, generating @@ -133,8 +163,8 @@ def check_csrf_token(request, header='X-CSRF-Token', raises=True): """ Check the CSRF token returned by the - :class:`pyramid.interfaces.ICSRFStoragePolicy` implementation against the value in - ``request.POST.get(token)`` (if a POST request) or + :class:`pyramid.interfaces.ICSRFStoragePolicy` implementation against the + value in ``request.POST.get(token)`` (if a POST request) or ``request.headers.get(header)``. If a ``token`` keyword is not supplied to this function, the string ``csrf_token`` will be used to look up the token in ``request.POST``. If a ``header`` keyword is not supplied to this @@ -143,11 +173,12 @@ def check_csrf_token(request, If the value supplied by post or by header doesn't match the value supplied by ``policy.get_csrf_token()`` (where ``policy`` is an implementation of - :class:`pyramid.interfaces.ICSRFStoragePolicy`), and ``raises`` is ``True``, this - function will raise an :exc:`pyramid.exceptions.BadCSRFToken` exception. If - the values differ and ``raises`` is ``False``, this function will return - ``False``. If the CSRF check is successful, this function will return - ``True`` unconditionally. + :class:`pyramid.interfaces.ICSRFStoragePolicy`), and ``raises`` is + ``True``, this function will raise an + :exc:`pyramid.exceptions.BadCSRFToken` exception. If the values differ + and ``raises`` is ``False``, this function will return ``False``. If the + CSRF check is successful, this function will return ``True`` + unconditionally. See :ref:`auto_csrf_checking` for information about how to secure your application automatically against CSRF attacks. @@ -176,8 +207,8 @@ def check_csrf_token(request, if supplied_token == "" and token is not None: supplied_token = request.POST.get(token, "") - policy = request.registry.queryUtility(ICSRFStoragePolicy) - if not policy.check_csrf_token(request, supplied_token): + expected_token = get_csrf_token(request) + if strings_differ(bytes_(expected_token), bytes_(supplied_token)): if raises: raise BadCSRFToken('check_csrf_token(): Invalid token') return False diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index c3b6b164d..853e8fcdd 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -927,6 +927,13 @@ class ISession(IDict): usually accessed via ``request.session``. Keys and values of a session must be pickleable. + + .. versionchanged:: 1.9 + + Sessions are no longer required to implement ``get_csrf_token`` and + ``new_csrf_token``. CSRF token support was moved to the pluggable + :class:`pyramid.interfaces.ICSRFStoragePolicy` configuration hook. + """ # attributes @@ -984,24 +991,23 @@ class ISession(IDict): class ICSRFStoragePolicy(Interface): """ An object that offers the ability to verify CSRF tokens and generate - new ones""" + new ones.""" def new_csrf_token(request): - """ Create and return a new, random cross-site request forgery - protection token. Return the token. It will be a string.""" + """ Create and return a new, random cross-site request forgery + protection token. The token will be an ascii-compatible unicode + string. + + """ def get_csrf_token(request): """ Return a cross-site request forgery protection token. It - will be a string. If a token was previously set for this user via - ``new_csrf_token``, that token will be returned. If no CSRF token - was previously set, ``new_csrf_token`` will be called, which will - create and set a token, and this token will be returned. - """ + will be an ascii-compatible unicode string. If a token was previously + set for this user via ``new_csrf_token``, that token will be returned. + If no CSRF token was previously set, ``new_csrf_token`` will be + called, which will create and set a token, and this token will be + returned. - def check_csrf_token(request, supplied_token): - """ Returns a boolean that represents if ``supplied_token`` is a valid - CSRF token for this request. Comparing strings for equality must be done - using :func:`pyramid.utils.strings_differ` to avoid timing attacks. """ diff --git a/pyramid/session.py b/pyramid/session.py index b1ad25410..33119343b 100644 --- a/pyramid/session.py +++ b/pyramid/session.py @@ -17,6 +17,10 @@ from pyramid.compat import ( bytes_, native_, ) +from pyramid.csrf import ( + check_csrf_origin, + check_csrf_token, +) from pyramid.interfaces import ISession from pyramid.util import strings_differ @@ -608,3 +612,13 @@ def SignedCookieSessionFactory( reissue_time=reissue_time, set_on_exception=set_on_exception, ) + +check_csrf_origin = check_csrf_origin # api +deprecated('check_csrf_origin', + 'pyramid.session.check_csrf_origin is deprecated as of Pyramid ' + '1.9. Use pyramid.csrf.check_csrf_origin instead.') + +check_csrf_token = check_csrf_token # api +deprecated('check_csrf_token', + 'pyramid.session.check_csrf_token is deprecated as of Pyramid ' + '1.9. Use pyramid.csrf.check_csrf_token instead.') diff --git a/pyramid/tests/test_csrf.py b/pyramid/tests/test_csrf.py index fcb6333ee..cd7ba2951 100644 --- a/pyramid/tests/test_csrf.py +++ b/pyramid/tests/test_csrf.py @@ -49,7 +49,7 @@ class Test_new_csrf_token(unittest.TestCase): self.assertEquals(csrf_token, 'e5e9e30a08b34ff9842ff7d2b958c14b') -class TestSessionCSRFStoragePolicy(unittest.TestCase): +class TestLegacySessionCSRFStoragePolicy(unittest.TestCase): class MockSession(object): def new_csrf_token(self): return 'e5e9e30a08b34ff9842ff7d2b958c14b' @@ -58,11 +58,11 @@ class TestSessionCSRFStoragePolicy(unittest.TestCase): return '02821185e4c94269bdc38e6eeae0a2f8' def _makeOne(self): - from pyramid.csrf import SessionCSRFStoragePolicy - return SessionCSRFStoragePolicy() + from pyramid.csrf import LegacySessionCSRFStoragePolicy + return LegacySessionCSRFStoragePolicy() def test_register_session_csrf_policy(self): - from pyramid.csrf import SessionCSRFStoragePolicy + from pyramid.csrf import LegacySessionCSRFStoragePolicy from pyramid.interfaces import ICSRFStoragePolicy config = Configurator() @@ -71,7 +71,7 @@ class TestSessionCSRFStoragePolicy(unittest.TestCase): policy = config.registry.queryUtility(ICSRFStoragePolicy) - self.assertTrue(isinstance(policy, SessionCSRFStoragePolicy)) + self.assertTrue(isinstance(policy, LegacySessionCSRFStoragePolicy)) def test_session_csrf_implementation_delegates_to_session(self): policy = self._makeOne() @@ -86,26 +86,46 @@ class TestSessionCSRFStoragePolicy(unittest.TestCase): 'e5e9e30a08b34ff9842ff7d2b958c14b' ) - def test_verifying_token_invalid(self): + +class TestSessionCSRFStoragePolicy(unittest.TestCase): + def _makeOne(self, **kw): + from pyramid.csrf import SessionCSRFStoragePolicy + return SessionCSRFStoragePolicy(**kw) + + def test_register_session_csrf_policy(self): + from pyramid.csrf import SessionCSRFStoragePolicy + from pyramid.interfaces import ICSRFStoragePolicy + + config = Configurator() + config.set_csrf_storage_policy(self._makeOne()) + config.commit() + + policy = config.registry.queryUtility(ICSRFStoragePolicy) + + self.assertTrue(isinstance(policy, SessionCSRFStoragePolicy)) + + def test_it_creates_a_new_token(self): + request = DummyRequest(session={}) + policy = self._makeOne() - request = DummyRequest(session=self.MockSession()) + policy._token_factory = lambda: 'foo' + self.assertEqual(policy.get_csrf_token(request), 'foo') - result = policy.check_csrf_token(request, 'invalid-token') - self.assertFalse(result) + def test_get_csrf_token_returns_the_new_token(self): + request = DummyRequest(session={'_csrft_': 'foo'}) - def test_verifying_token_valid(self): policy = self._makeOne() - request = DummyRequest(session=self.MockSession()) + self.assertEqual(policy.get_csrf_token(request), 'foo') - result = policy.check_csrf_token( - request, '02821185e4c94269bdc38e6eeae0a2f8') - self.assertTrue(result) + token = policy.new_csrf_token(request) + self.assertNotEqual(token, 'foo') + self.assertEqual(token, policy.get_csrf_token(request)) class TestCookieCSRFStoragePolicy(unittest.TestCase): - def _makeOne(self): + def _makeOne(self, **kw): from pyramid.csrf import CookieCSRFStoragePolicy - return CookieCSRFStoragePolicy() + return CookieCSRFStoragePolicy(**kw) def test_register_cookie_csrf_policy(self): from pyramid.csrf import CookieCSRFStoragePolicy @@ -121,18 +141,18 @@ class TestCookieCSRFStoragePolicy(unittest.TestCase): def test_get_cookie_csrf_with_no_existing_cookie_sets_cookies(self): response = MockResponse() - request = DummyRequest(response=response) + request = DummyRequest() policy = self._makeOne() token = policy.get_csrf_token(request) + request.response_callback(request, response) self.assertEqual( response.headerlist, [('Set-Cookie', 'csrf_token={}; Path=/'.format(token))] ) def test_existing_cookie_csrf_does_not_set_cookie(self): - response = MockResponse() - request = DummyRequest(response=response) + request = DummyRequest() request.cookies = {'csrf_token': 'e6f325fee5974f3da4315a8ccf4513d2'} policy = self._makeOne() @@ -142,42 +162,32 @@ class TestCookieCSRFStoragePolicy(unittest.TestCase): token, 'e6f325fee5974f3da4315a8ccf4513d2' ) - self.assertEqual( - response.headerlist, - [], - ) + self.assertIsNone(request.response_callback) def test_new_cookie_csrf_with_existing_cookie_sets_cookies(self): - response = MockResponse() - request = DummyRequest(response=response) + request = DummyRequest() request.cookies = {'csrf_token': 'e6f325fee5974f3da4315a8ccf4513d2'} policy = self._makeOne() token = policy.new_csrf_token(request) + + response = MockResponse() + request.response_callback(request, response) self.assertEqual( response.headerlist, [('Set-Cookie', 'csrf_token={}; Path=/'.format(token))] ) - def test_verifying_token_invalid_token(self): - response = MockResponse() - request = DummyRequest(response=response) - request.cookies = {'csrf_token': 'e6f325fee5974f3da4315a8ccf4513d2'} + def test_get_csrf_token_returns_the_new_token(self): + request = DummyRequest() + request.cookies = {'csrf_token': 'foo'} policy = self._makeOne() - self.assertFalse( - policy.check_csrf_token(request, 'invalid-token') - ) - - def test_verifying_token_against_existing_cookie(self): - response = MockResponse() - request = DummyRequest(response=response) - request.cookies = {'csrf_token': 'e6f325fee5974f3da4315a8ccf4513d2'} + self.assertEqual(policy.get_csrf_token(request), 'foo') - policy = self._makeOne() - self.assertTrue( - policy.check_csrf_token(request, 'e6f325fee5974f3da4315a8ccf4513d2') - ) + token = policy.new_csrf_token(request) + self.assertNotEqual(token, 'foo') + self.assertEqual(token, policy.get_csrf_token(request)) class Test_check_csrf_token(unittest.TestCase): @@ -224,14 +234,6 @@ class Test_check_csrf_token(unittest.TestCase): result = self._callFUT(request, 'csrf_token', raises=False) self.assertEqual(result, False) - def test_token_differing_types(self): - from pyramid.compat import text_ - request = testing.DummyRequest() - request.method = "POST" - request.session['_csrft_'] = text_('foo') - request.POST = {'csrf_token': b'foo'} - self.assertEqual(self._callFUT(request, token='csrf_token'), True) - class Test_check_csrf_token_without_defaults_configured(unittest.TestCase): def setUp(self): @@ -353,15 +355,15 @@ class Test_check_csrf_origin(unittest.TestCase): class DummyRequest(object): registry = None session = None - cookies = {} + response_callback = None - def __init__(self, registry=None, session=None, response=None): + def __init__(self, registry=None, session=None): self.registry = registry self.session = session - self.response = response + self.cookies = {} def add_response_callback(self, callback): - callback(self, self.response) + self.response_callback = callback class MockResponse(object): diff --git a/pyramid/tests/test_util.py b/pyramid/tests/test_util.py index bbf6103f4..d64f0a73f 100644 --- a/pyramid/tests/test_util.py +++ b/pyramid/tests/test_util.py @@ -369,12 +369,16 @@ class Test_strings_differ(unittest.TestCase): from pyramid.util import strings_differ return strings_differ(*args, **kw) - def test_it(self): + def test_it_bytes(self): self.assertFalse(self._callFUT(b'foo', b'foo')) self.assertTrue(self._callFUT(b'123', b'345')) self.assertTrue(self._callFUT(b'1234', b'123')) self.assertTrue(self._callFUT(b'123', b'1234')) + def test_it_native_str(self): + self.assertFalse(self._callFUT('123', '123')) + self.assertTrue(self._callFUT('123', '1234')) + def test_it_with_internal_comparator(self): result = self._callFUT(b'foo', b'foo', compare_digest=None) self.assertFalse(result) -- cgit v1.2.3 From 87af11c5e33b8c03d57a8b571f0b152efe866af1 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sat, 29 Apr 2017 21:48:49 -0500 Subject: add changelog for #2874 --- CHANGES.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index c8a87f625..8868e6ff7 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -24,6 +24,12 @@ Features can be alleviated by invoking ``config.begin()`` and ``config.end()`` appropriately. See https://github.com/Pylons/pyramid/pull/2989 +- The ``pyramid.config.Configurator`` can now be used as a context manager + which will automatically push/pop threadlocals (similar to + ``config.begin()`` and ``config.end()``). It will also automatically perform + a ``config.commit()`` and thus it is only recommended to be used at the + top-level of your app. See https://github.com/Pylons/pyramid/pull/2874 + Bug Fixes --------- -- cgit v1.2.3 From 3f14d63c009ae7f101b7aeb4525bab2dfe66fa11 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sun, 30 Apr 2017 02:00:48 -0500 Subject: restore the ``ICSRFStoragePolicy.check_csrf_token`` api --- pyramid/csrf.py | 35 ++++++++++--- pyramid/interfaces.py | 10 ++++ pyramid/tests/test_csrf.py | 121 +++++++++++++++++++++++++++------------------ 3 files changed, 113 insertions(+), 53 deletions(-) diff --git a/pyramid/csrf.py b/pyramid/csrf.py index 1910e4ec8..c8f097777 100644 --- a/pyramid/csrf.py +++ b/pyramid/csrf.py @@ -47,6 +47,12 @@ class LegacySessionCSRFStoragePolicy(object): generating a new one if needed.""" return request.session.get_csrf_token() + def check_csrf_token(self, request, supplied_token): + """ Returns ``True`` if the ``supplied_token`` is valid.""" + expected_token = self.get_csrf_token(request) + return not strings_differ( + bytes_(expected_token), bytes_(supplied_token)) + @implementer(ICSRFStoragePolicy) class SessionCSRFStoragePolicy(object): @@ -82,6 +88,12 @@ class SessionCSRFStoragePolicy(object): token = self.new_csrf_token(request) return token + def check_csrf_token(self, request, supplied_token): + """ Returns ``True`` if the ``supplied_token`` is valid.""" + expected_token = self.get_csrf_token(request) + return not strings_differ( + bytes_(expected_token), bytes_(supplied_token)) + @implementer(ICSRFStoragePolicy) class CookieCSRFStoragePolicy(object): @@ -133,6 +145,12 @@ class CookieCSRFStoragePolicy(object): token = self.new_csrf_token(request) return token + def check_csrf_token(self, request, supplied_token): + """ Returns ``True`` if the ``supplied_token`` is valid.""" + expected_token = self.get_csrf_token(request) + return not strings_differ( + bytes_(expected_token), bytes_(supplied_token)) + def get_csrf_token(request): """ Get the currently active CSRF token for the request passed, generating @@ -140,6 +158,7 @@ def get_csrf_token(request): calls the equivalent method in the chosen CSRF protection implementation. .. versionadded :: 1.9 + """ registry = request.registry csrf = registry.getUtility(ICSRFStoragePolicy) @@ -152,6 +171,7 @@ def new_csrf_token(request): chosen CSRF protection implementation. .. versionadded :: 1.9 + """ registry = request.registry csrf = registry.getUtility(ICSRFStoragePolicy) @@ -171,9 +191,8 @@ def check_csrf_token(request, function, the string ``X-CSRF-Token`` will be used to look up the token in ``request.headers``. - If the value supplied by post or by header doesn't match the value supplied - by ``policy.get_csrf_token()`` (where ``policy`` is an implementation of - :class:`pyramid.interfaces.ICSRFStoragePolicy`), and ``raises`` is + If the value supplied by post or by header cannot be verified by the + :class:`pyramid.interfaces.ICSRFStoragePolicy`, and ``raises`` is ``True``, this function will raise an :exc:`pyramid.exceptions.BadCSRFToken` exception. If the values differ and ``raises`` is ``False``, this function will return ``False``. If the @@ -191,7 +210,10 @@ def check_csrf_token(request, a header. .. versionchanged:: 1.9 - Moved from :mod:`pyramid.session` to :mod:`pyramid.csrf` + Moved from :mod:`pyramid.session` to :mod:`pyramid.csrf` and updated + to use the configured :class:`pyramid.interfaces.ICSRFStoragePolicy` to + verify the CSRF token. + """ supplied_token = "" # We first check the headers for a csrf token, as that is significantly @@ -207,8 +229,8 @@ def check_csrf_token(request, if supplied_token == "" and token is not None: supplied_token = request.POST.get(token, "") - expected_token = get_csrf_token(request) - if strings_differ(bytes_(expected_token), bytes_(supplied_token)): + policy = request.registry.getUtility(ICSRFStoragePolicy) + if not policy.check_csrf_token(request, text_(supplied_token)): if raises: raise BadCSRFToken('check_csrf_token(): Invalid token') return False @@ -239,6 +261,7 @@ def check_csrf_origin(request, trusted_origins=None, raises=True): .. versionchanged:: 1.9 Moved from :mod:`pyramid.session` to :mod:`pyramid.csrf` + """ def _fail(reason): if raises: diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index 853e8fcdd..ab83813c8 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -1010,6 +1010,16 @@ class ICSRFStoragePolicy(Interface): """ + def check_csrf_token(request, token): + """ Determine if the supplied ``token`` is valid. Most implementations + should simply compare the ``token`` to the current value of + ``get_csrf_token`` but it is possible to verify the token using + any mechanism necessary using this method. + + Returns ``True`` if the ``token`` is valid, otherwise ``False``. + + """ + class IIntrospector(Interface): def get(category_name, discriminator, default=None): diff --git a/pyramid/tests/test_csrf.py b/pyramid/tests/test_csrf.py index cd7ba2951..f01780ad8 100644 --- a/pyramid/tests/test_csrf.py +++ b/pyramid/tests/test_csrf.py @@ -1,61 +1,20 @@ import unittest -from zope.interface.interfaces import ComponentLookupError - from pyramid import testing from pyramid.config import Configurator -from pyramid.events import BeforeRender - - -class Test_get_csrf_token(unittest.TestCase): - def setUp(self): - self.config = testing.setUp() - - def _callFUT(self, *args, **kwargs): - from pyramid.csrf import get_csrf_token - return get_csrf_token(*args, **kwargs) - - def test_no_override_csrf_utility_registered(self): - request = testing.DummyRequest() - self._callFUT(request) - - def test_success(self): - self.config.set_csrf_storage_policy(DummyCSRF()) - request = testing.DummyRequest() - - csrf_token = self._callFUT(request) - - self.assertEquals(csrf_token, '02821185e4c94269bdc38e6eeae0a2f8') - - -class Test_new_csrf_token(unittest.TestCase): - def setUp(self): - self.config = testing.setUp() - - def _callFUT(self, *args, **kwargs): - from pyramid.csrf import new_csrf_token - return new_csrf_token(*args, **kwargs) - - def test_no_override_csrf_utility_registered(self): - request = testing.DummyRequest() - self._callFUT(request) - - def test_success(self): - self.config.set_csrf_storage_policy(DummyCSRF()) - request = testing.DummyRequest() - - csrf_token = self._callFUT(request) - - self.assertEquals(csrf_token, 'e5e9e30a08b34ff9842ff7d2b958c14b') class TestLegacySessionCSRFStoragePolicy(unittest.TestCase): class MockSession(object): + def __init__(self, current_token='02821185e4c94269bdc38e6eeae0a2f8'): + self.current_token = current_token + def new_csrf_token(self): - return 'e5e9e30a08b34ff9842ff7d2b958c14b' + self.current_token = 'e5e9e30a08b34ff9842ff7d2b958c14b' + return self.current_token def get_csrf_token(self): - return '02821185e4c94269bdc38e6eeae0a2f8' + return self.current_token def _makeOne(self): from pyramid.csrf import LegacySessionCSRFStoragePolicy @@ -86,6 +45,13 @@ class TestLegacySessionCSRFStoragePolicy(unittest.TestCase): 'e5e9e30a08b34ff9842ff7d2b958c14b' ) + def test_check_csrf_token(self): + request = DummyRequest(session=self.MockSession('foo')) + + policy = self._makeOne() + self.assertTrue(policy.check_csrf_token(request, 'foo')) + self.assertFalse(policy.check_csrf_token(request, 'bar')) + class TestSessionCSRFStoragePolicy(unittest.TestCase): def _makeOne(self, **kw): @@ -121,6 +87,16 @@ class TestSessionCSRFStoragePolicy(unittest.TestCase): self.assertNotEqual(token, 'foo') self.assertEqual(token, policy.get_csrf_token(request)) + def test_check_csrf_token(self): + request = DummyRequest(session={}) + + policy = self._makeOne() + self.assertFalse(policy.check_csrf_token(request, 'foo')) + + request.session = {'_csrft_': 'foo'} + self.assertTrue(policy.check_csrf_token(request, 'foo')) + self.assertFalse(policy.check_csrf_token(request, 'bar')) + class TestCookieCSRFStoragePolicy(unittest.TestCase): def _makeOne(self, **kw): @@ -189,6 +165,57 @@ class TestCookieCSRFStoragePolicy(unittest.TestCase): self.assertNotEqual(token, 'foo') self.assertEqual(token, policy.get_csrf_token(request)) + def test_check_csrf_token(self): + request = DummyRequest() + + policy = self._makeOne() + self.assertFalse(policy.check_csrf_token(request, 'foo')) + + request.cookies = {'csrf_token': 'foo'} + self.assertTrue(policy.check_csrf_token(request, 'foo')) + self.assertFalse(policy.check_csrf_token(request, 'bar')) + +class Test_get_csrf_token(unittest.TestCase): + def setUp(self): + self.config = testing.setUp() + + def _callFUT(self, *args, **kwargs): + from pyramid.csrf import get_csrf_token + return get_csrf_token(*args, **kwargs) + + def test_no_override_csrf_utility_registered(self): + request = testing.DummyRequest() + self._callFUT(request) + + def test_success(self): + self.config.set_csrf_storage_policy(DummyCSRF()) + request = testing.DummyRequest() + + csrf_token = self._callFUT(request) + + self.assertEquals(csrf_token, '02821185e4c94269bdc38e6eeae0a2f8') + + +class Test_new_csrf_token(unittest.TestCase): + def setUp(self): + self.config = testing.setUp() + + def _callFUT(self, *args, **kwargs): + from pyramid.csrf import new_csrf_token + return new_csrf_token(*args, **kwargs) + + def test_no_override_csrf_utility_registered(self): + request = testing.DummyRequest() + self._callFUT(request) + + def test_success(self): + self.config.set_csrf_storage_policy(DummyCSRF()) + request = testing.DummyRequest() + + csrf_token = self._callFUT(request) + + self.assertEquals(csrf_token, 'e5e9e30a08b34ff9842ff7d2b958c14b') + class Test_check_csrf_token(unittest.TestCase): def setUp(self): -- cgit v1.2.3 From 69828b5aa35ed3cf19941a0771c82418a0733b7e Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 30 Apr 2017 16:37:21 -0700 Subject: standardize "non-standard" --- docs/narr/security.rst | 2 +- pyramid/csrf.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/narr/security.rst b/docs/narr/security.rst index ddf496b69..3a6bfa5e5 100644 --- a/docs/narr/security.rst +++ b/docs/narr/security.rst @@ -946,7 +946,7 @@ automatic CSRF checking will also check the referrer of the request to ensure that it matches one of the trusted origins. By default the only trusted origin is the current host, however additional origins may be configured by setting ``pyramid.csrf_trusted_origins`` to a list of domain names (and ports if they -are non standard). If a host in the list of domains starts with a ``.`` then +are non-standard). If a host in the list of domains starts with a ``.`` then that will allow all subdomains as well as the domain without the ``.``. If CSRF checks fail then a :class:`pyramid.exceptions.BadCSRFToken` or diff --git a/pyramid/csrf.py b/pyramid/csrf.py index c8f097777..7c836e5ad 100644 --- a/pyramid/csrf.py +++ b/pyramid/csrf.py @@ -249,7 +249,7 @@ def check_csrf_origin(request, trusted_origins=None, raises=True): checks are successful this function will return ``True`` unconditionally. Additional trusted origins may be added by passing a list of domain (and - ports if nonstandard like ``['example.com', 'dev.example.com:8080']``) in + ports if non-standard like ``['example.com', 'dev.example.com:8080']``) in with the ``trusted_origins`` parameter. If ``trusted_origins`` is ``None`` (the default) this list of additional domains will be pulled from the ``pyramid.csrf_trusted_origins`` setting. -- cgit v1.2.3 From 6419a30f2322157a1faf3fce5bec5122a2ca69fa Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sun, 30 Apr 2017 19:08:50 -0500 Subject: improve csrf changelog docs --- CHANGES.txt | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 075d3ffd9..719fbd495 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -11,6 +11,7 @@ Major Features For now, Pyramid is still shipping with integrated support for the PasteDeploy INI format by depending on the ``plaster_pastedeploy`` binding. + This may change in the future. See https://github.com/Pylons/pyramid/pull/2985 @@ -42,11 +43,26 @@ Features can be alleviated by invoking ``config.begin()`` and ``config.end()`` appropriately. See https://github.com/Pylons/pyramid/pull/2989 -- A new CSRF implementation, ``pyramid.csrf.SessionCSRFStoragePolicy``, - has been added which delegates all CSRF generation to the current session, - following the old API for this. A ``pyramid.csrf.get_csrf_token()`` api is now - available in template global scope, to make it easy for template developers - to get the current CSRF token without adding it to Python code. +- CSRF support has been refactored out of sessions and into its own + independent API in the ``pyramid.csrf`` module. It supports a pluggable + ``pyramid.interfaces.ICSRFStoragePolicy`` which can be used to define your + own mechanism for generating and validating CSRF tokens. By default, + Pyramid continues to use the ``pyramid.csrf.LegacySessionCSRFStoragePolicy`` + that uses the ``request.session.get_csrf_token`` and + ``request.session.new_csrf_token`` APIs under the hood to preserve + compatibility. Two new policies are shipped as well, + ``pyramid.csrf.SessionCSRFStoragePolicy`` and + ``pyramid.csrf.CookieCSRFStoragePolicy`` which will store the CSRF tokens + in the session and in a standalone cookie, respectively. The storage policy + can be changed by using the new + ``pyramid.config.Configurator.set_csrf_storage_policy`` config directive. + + CSRF tokens should be used via the new ``pyramid.csrf.get_csrf_token``, + ``pyramid.csrf.new_csrf_token`` and ``pyramid.csrf.check_csrf_token`` APIs + in order to continue working if the storage policy is changed. Also, the + ``pyramid.csrf.get_csrf_token`` function is injected into templates to be + used conveniently in UI code. + See https://github.com/Pylons/pyramid/pull/2854 and https://github.com/Pylons/pyramid/pull/3019 -- cgit v1.2.3 From 9028c99445d4c0a7ac24aaa84a6db499397e691a Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sun, 30 Apr 2017 19:09:17 -0500 Subject: move csrf changes to the "major features" section --- CHANGES.txt | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 719fbd495..bcfcc3107 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -26,23 +26,6 @@ Major Features See https://github.com/Pylons/pyramid/pull/2964 -Features --------- - -- Support an ``open_url`` config setting in the ``pserve`` section of the - config file. This url is used to open a web browser when ``pserve --browser`` - is invoked. When this setting is unavailable the ``pserve`` script will - attempt to guess the port the server is using from the - ``server:`` section of the config file but there is no - requirement that the server is being run in this format so it may fail. - See https://github.com/Pylons/pyramid/pull/2984 - -- The threadlocals are now available inside any function invoked via - ``config.include``. This means the only config-time code that cannot rely - on threadlocals is code executed from non-actions inside the main. This - can be alleviated by invoking ``config.begin()`` and ``config.end()`` - appropriately. See https://github.com/Pylons/pyramid/pull/2989 - - CSRF support has been refactored out of sessions and into its own independent API in the ``pyramid.csrf`` module. It supports a pluggable ``pyramid.interfaces.ICSRFStoragePolicy`` which can be used to define your @@ -66,6 +49,23 @@ Features See https://github.com/Pylons/pyramid/pull/2854 and https://github.com/Pylons/pyramid/pull/3019 +Features +-------- + +- Support an ``open_url`` config setting in the ``pserve`` section of the + config file. This url is used to open a web browser when ``pserve --browser`` + is invoked. When this setting is unavailable the ``pserve`` script will + attempt to guess the port the server is using from the + ``server:`` section of the config file but there is no + requirement that the server is being run in this format so it may fail. + See https://github.com/Pylons/pyramid/pull/2984 + +- The threadlocals are now available inside any function invoked via + ``config.include``. This means the only config-time code that cannot rely + on threadlocals is code executed from non-actions inside the main. This + can be alleviated by invoking ``config.begin()`` and ``config.end()`` + appropriately. See https://github.com/Pylons/pyramid/pull/2989 + - The ``pyramid.config.Configurator`` can now be used as a context manager which will automatically push/pop threadlocals (similar to ``config.begin()`` and ``config.end()``). It will also automatically perform -- cgit v1.2.3 From 847fb70980aca38b0dc415e2b433618d7e42ac8d Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sun, 30 Apr 2017 19:10:12 -0500 Subject: improve flow of changes for configurator threadlocals --- CHANGES.txt | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index bcfcc3107..e30f185f0 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -60,18 +60,19 @@ Features requirement that the server is being run in this format so it may fail. See https://github.com/Pylons/pyramid/pull/2984 -- The threadlocals are now available inside any function invoked via - ``config.include``. This means the only config-time code that cannot rely - on threadlocals is code executed from non-actions inside the main. This - can be alleviated by invoking ``config.begin()`` and ``config.end()`` - appropriately. See https://github.com/Pylons/pyramid/pull/2989 - - The ``pyramid.config.Configurator`` can now be used as a context manager which will automatically push/pop threadlocals (similar to ``config.begin()`` and ``config.end()``). It will also automatically perform a ``config.commit()`` and thus it is only recommended to be used at the top-level of your app. See https://github.com/Pylons/pyramid/pull/2874 +- The threadlocals are now available inside any function invoked via + ``config.include``. This means the only config-time code that cannot rely + on threadlocals is code executed from non-actions inside the main. This + can be alleviated by invoking ``config.begin()`` and ``config.end()`` + appropriately or using the new context manager feature of the configurator. + See https://github.com/Pylons/pyramid/pull/2989 + Bug Fixes --------- -- cgit v1.2.3 From 2b9b6cab969eab9b1976a1a9a29ed2e44e92ca6d Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 1 May 2017 21:10:30 -0500 Subject: update changelog and add whatsnew-1.9 --- CHANGES.txt | 27 ++++++++++++++++++--------- docs/index.rst | 1 + 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index e30f185f0..861dfa684 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -49,8 +49,8 @@ Major Features See https://github.com/Pylons/pyramid/pull/2854 and https://github.com/Pylons/pyramid/pull/3019 -Features --------- +Minor Features +-------------- - Support an ``open_url`` config setting in the ``pserve`` section of the config file. This url is used to open a web browser when ``pserve --browser`` @@ -94,12 +94,21 @@ Bug Fixes Deprecations ------------ -Backward Incompatibilities --------------------------- +- Pyramid currently depends on ``plaster_pastedeploy`` to simplify the + transition to ``plaster`` by maintaining integrated support for INI files. + This dependency on ``plaster_pastedeploy`` should be considered subject to + Pyramid's deprecation policy and is subject to removal in the future. + Applications should depend on the appropriate plaster binding to satisfy + their needs. + +- Retrieving CSRF token from the session has been deprecated in favor of + equivalent methods in the ``pyramid.csrf`` module. The CSRF methods + (``ISession.get_csrf_token`` and ``ISession.new_csrf_token``) are no longer + required on the ``ISession`` interface except when using the default + ``pyramid.csrf.LegacySessionCSRFStoragePolicy``. -Documentation Changes ---------------------- + Also, ``pyramid.session.check_csrf_token`` is now located at + ``pyramid.csrf.check_csrf_token``. -- Retrieving CSRF token from the session has been deprecated, in favor of - equivalent methods in :mod:`pyramid.csrf`. - See https://github.com/Pylons/pyramid/pull/2854 + See https://github.com/Pylons/pyramid/pull/2854 and + https://github.com/Pylons/pyramid/pull/3019 diff --git a/docs/index.rst b/docs/index.rst index ed5b458ea..7d3393548 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -185,6 +185,7 @@ Change History .. toctree:: :maxdepth: 1 + whatsnew-1.9 whatsnew-1.8 whatsnew-1.7 whatsnew-1.6 -- cgit v1.2.3 From 4245b85e8041c87b9eb7ebd60707813d05d7e004 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 1 May 2017 21:30:09 -0500 Subject: really add whatsnew-1.9 --- docs/whatsnew-1.9.rst | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 docs/whatsnew-1.9.rst diff --git a/docs/whatsnew-1.9.rst b/docs/whatsnew-1.9.rst new file mode 100644 index 000000000..7ceefbf49 --- /dev/null +++ b/docs/whatsnew-1.9.rst @@ -0,0 +1,49 @@ +What's New in Pyramid 1.9 +========================= + +This article explains the new features in :app:`Pyramid` version 1.9 as compared to its predecessor, :app:`Pyramid` 1.8. It also documents backwards incompatibilities between the two versions and deprecations added to :app:`Pyramid` 1.9, as well as software dependency changes and notable documentation additions. + +Major Feature Additions +----------------------- + +- The file format used by all ``p*`` command line scripts such as ``pserve`` and ``pshell``, as well as the :func:`pyramid.paster.bootstrap` function is now replaceable thanks to a new dependency on `plaster `_. + + For now, Pyramid is still shipping with integrated support for the PasteDeploy INI format by depending on the ``plaster_pastedeploy`` binding library. This may change in the future so it is recommended for applications to start depending on the appropriate plaster binding for their needs. + + See https://github.com/Pylons/pyramid/pull/2985 + +- Added an :term:`execution policy` hook to the request pipeline. An execution policy has the ability to control creation and execution of the request objects before they enter the rest of the pipeline. This means for a single request environ the policy may create more than one request object. + + The execution policy can be replaced using the new :meth:`pyramid.config.Configurator.set_execution_policy` config directive. + + The first library to use this feature is `pyramid_retry `_. + + See https://github.com/Pylons/pyramid/pull/2964 + +- CSRF support has been refactored out of sessions and into its own independent API in the :mod:`pyramid.csrf` module. It supports a pluggable :class:`pyramid.interfaces.ICSRFStoragePolicy` which can be used to define your own mechanism for generating and validating CSRF tokens. By default, Pyramid continues to use the :class:`pyramid.csrf.LegacySessionCSRFStoragePolicy` that uses the ``request.session.get_csrf_token`` and ``request.session.new_csrf_token`` APIs under the hood to preserve compatibility with older Pyramid applications. Two new policies are shipped as well, :class:`pyramid.csrf.SessionCSRFStoragePolicy` and :class:`pyramid.csrf.CookieCSRFStoragePolicy` which will store the CSRF tokens in the session and in a standalone cookie, respectively. The storage policy can be changed by using the new :meth:`pyramid.config.Configurator.set_csrf_storage_policy` config directive. + + CSRF tokens should be used via the new :func:`pyramid.csrf.get_csrf_token`, :func:`pyramid.csrf.new_csrf_token` and :func:`pyramid.csrf.check_csrf_token`` APIs in order to continue working if the storage policy is changed. Also, the :func:`pyramid.csrf.get_csrf_token` function is now injected into templates to be used conveniently in UI code. + + See https://github.com/Pylons/pyramid/pull/2854 and https://github.com/Pylons/pyramid/pull/3019 + +Minor Feature Additions +----------------------- + +- Support an ``open_url`` config setting in the ``pserve`` section of the config file. This url is used to open a web browser when ``pserve --browser`` is invoked. When this setting is unavailable the ``pserve`` script will attempt to guess the port the server is using from the ``server:`` section of the config file but there is no requirement that the server is being run in this format so it may fail. See https://github.com/Pylons/pyramid/pull/2984 + +- The :class:`pyramid.config.Configurator` can now be used as a context manager which will automatically push/pop threadlocals (similar to :meth:`pyramid.config.Configurator.begin` and `pyramid.config.Configurator.end`). It will also automatically perform a :meth:`pyramid.config.Configurator.commit` at the end and thus it is only recommended to be used at the top-level of your app. See https://github.com/Pylons/pyramid/pull/2874 + +- The threadlocals are now available inside any function invoked via :meth:`pyramid.config.Configurator.include`. This means the only config-time code that cannot rely on threadlocals is code executed from non-actions inside the main. This can be alleviated by invoking :meth:`pyramid.config.Configurator.begin` and :meth:`pyramid.config.Configurator.end` appropriately or using the new context manager feature of the configurator. See https://github.com/Pylons/pyramid/pull/2989 + +Deprecations +------------ + +- Pyramid currently depends on ``plaster_pastedeploy`` to simplify the transition to ``plaster`` by maintaining integrated support for INI files. This dependency on ``plaster_pastedeploy`` should be considered subject to Pyramid's deprecation policy and is subject to removal in the future. Applications should depend on the appropriate plaster binding to satisfy their needs. + +- Retrieving CSRF token from the session has been deprecated in favor of equivalent methods in the :mod:`pyramid.csrf` module. The CSRF methods (``ISession.get_csrf_token`` and ``ISession.new_csrf_token``) are no longer required on the :class:`pyramid.interfaces.ISession` interface except when using the default :class:`pyramid.csrf.LegacySessionCSRFStoragePolicy`. + + Also, ``pyramid.session.check_csrf_token`` is now located at + :func:`pyramid.csrf.check_csrf_token`. + + See https://github.com/Pylons/pyramid/pull/2854 and + https://github.com/Pylons/pyramid/pull/3019 -- cgit v1.2.3 From 33f1c247272e58993003777677c7c6a7a4f5e38c Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 1 May 2017 21:33:36 -0500 Subject: switch readme to 1.9-branch badges --- README.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 302590fe1..5f42115df 100644 --- a/README.rst +++ b/README.rst @@ -1,16 +1,16 @@ Pyramid ======= -.. image:: https://travis-ci.org/Pylons/pyramid.png?branch=master +.. image:: https://travis-ci.org/Pylons/pyramid.png?branch=1.9-branch :target: https://travis-ci.org/Pylons/pyramid :alt: master Travis CI Status -.. image:: https://readthedocs.org/projects/pyramid/badge/?version=master - :target: http://docs.pylonsproject.org/projects/pyramid/en/master/ +.. image:: https://readthedocs.org/projects/pyramid/badge/?version=1.9-branch + :target: http://docs.pylonsproject.org/projects/pyramid/en/1.9-branch/ :alt: Master Documentation Status -.. image:: https://readthedocs.org/projects/pyramid/badge/?version=latest - :target: http://docs.pylonsproject.org/projects/pyramid/en/latest/ +.. image:: https://readthedocs.org/projects/pyramid/badge/?version=1.9-branch + :target: http://docs.pylonsproject.org/projects/pyramid/en/1.9-branch/ :alt: Latest Documentation Status .. image:: https://img.shields.io/badge/irc-freenode-blue.svg -- cgit v1.2.3 From 156c7fa32d6bf6e7786967920ca89403b0eb16fd Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 1 May 2017 21:38:08 -0500 Subject: link to 1.9-branch in contributing --- contributing.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contributing.md b/contributing.md index 9deeee035..82f60e7b8 100644 --- a/contributing.md +++ b/contributing.md @@ -26,6 +26,8 @@ listed below. * [master](https://github.com/Pylons/pyramid/) - The branch on which further development takes place. The default branch on GitHub. +* [1.9-branch](https://github.com/Pylons/pyramid/tree/1.9-branch) - The branch + classified as "alpha". * [1.8-branch](https://github.com/Pylons/pyramid/tree/1.8-branch) - The branch classified as "stable" or "latest". * [1.7-branch](https://github.com/Pylons/pyramid/tree/1.7-branch) - The oldest -- cgit v1.2.3 From fdd77da6231fa9286c3f6fa494ae0731570e0134 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 1 May 2017 21:42:54 -0500 Subject: link to plaster_pastedeploy --- CHANGES.txt | 5 +++-- docs/whatsnew-1.9.rst | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 861dfa684..a6cac805f 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -10,8 +10,9 @@ Major Features `plaster `_. For now, Pyramid is still shipping with integrated support for the - PasteDeploy INI format by depending on the ``plaster_pastedeploy`` binding. - This may change in the future. + PasteDeploy INI format by depending on the + `plaster_pastedeploy `_. - For now, Pyramid is still shipping with integrated support for the PasteDeploy INI format by depending on the ``plaster_pastedeploy`` binding library. This may change in the future so it is recommended for applications to start depending on the appropriate plaster binding for their needs. + For now, Pyramid is still shipping with integrated support for the PasteDeploy INI format by depending on the `plaster_pastedeploy Date: Mon, 1 May 2017 21:49:28 -0500 Subject: fix rst syntax --- docs/whatsnew-1.9.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/whatsnew-1.9.rst b/docs/whatsnew-1.9.rst index 291f731ed..dd5ab894d 100644 --- a/docs/whatsnew-1.9.rst +++ b/docs/whatsnew-1.9.rst @@ -31,7 +31,7 @@ Minor Feature Additions - Support an ``open_url`` config setting in the ``pserve`` section of the config file. This url is used to open a web browser when ``pserve --browser`` is invoked. When this setting is unavailable the ``pserve`` script will attempt to guess the port the server is using from the ``server:`` section of the config file but there is no requirement that the server is being run in this format so it may fail. See https://github.com/Pylons/pyramid/pull/2984 -- The :class:`pyramid.config.Configurator` can now be used as a context manager which will automatically push/pop threadlocals (similar to :meth:`pyramid.config.Configurator.begin` and `pyramid.config.Configurator.end`). It will also automatically perform a :meth:`pyramid.config.Configurator.commit` at the end and thus it is only recommended to be used at the top-level of your app. See https://github.com/Pylons/pyramid/pull/2874 +- The :class:`pyramid.config.Configurator` can now be used as a context manager which will automatically push/pop threadlocals (similar to :meth:`pyramid.config.Configurator.begin` and :meth:`pyramid.config.Configurator.end`). It will also automatically perform a :meth:`pyramid.config.Configurator.commit` at the end and thus it is only recommended to be used at the top-level of your app. See https://github.com/Pylons/pyramid/pull/2874 - The threadlocals are now available inside any function invoked via :meth:`pyramid.config.Configurator.include`. This means the only config-time code that cannot rely on threadlocals is code executed from non-actions inside the main. This can be alleviated by invoking :meth:`pyramid.config.Configurator.begin` and :meth:`pyramid.config.Configurator.end` appropriately or using the new context manager feature of the configurator. See https://github.com/Pylons/pyramid/pull/2989 -- cgit v1.2.3 From 7850884719c94c0721748b5458504cb8a9d242c8 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 1 May 2017 21:54:55 -0500 Subject: add changelog for #2993 --- CHANGES.txt | 6 ++++++ docs/whatsnew-1.9.rst | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index a6cac805f..70a2ff922 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -113,3 +113,9 @@ Deprecations See https://github.com/Pylons/pyramid/pull/2854 and https://github.com/Pylons/pyramid/pull/3019 + +Documentation Changes +--------------------- + +- Added the execution policy to the routing diagram in the Request Processing + chatper. See https://github.com/Pylons/pyramid/pull/2993 diff --git a/docs/whatsnew-1.9.rst b/docs/whatsnew-1.9.rst index dd5ab894d..e57ed254d 100644 --- a/docs/whatsnew-1.9.rst +++ b/docs/whatsnew-1.9.rst @@ -47,3 +47,9 @@ Deprecations See https://github.com/Pylons/pyramid/pull/2854 and https://github.com/Pylons/pyramid/pull/3019 + +Documentation Enhancements +-------------------------- + +- Added the :term:`execution policy` to the routing diagram in + :ref:`router_chapter`. See https://github.com/Pylons/pyramid/pull/2993 -- cgit v1.2.3 From 2aebc688c6a81b1baef01791e1cf3c9907c7c3ee Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 1 May 2017 22:07:55 -0500 Subject: line length fixes in whatsnew-1.9 --- CHANGES.txt | 2 +- docs/whatsnew-1.9.rst | 11 ++++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 70a2ff922..85931d7f1 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -98,7 +98,7 @@ Deprecations - Pyramid currently depends on ``plaster_pastedeploy`` to simplify the transition to ``plaster`` by maintaining integrated support for INI files. This dependency on ``plaster_pastedeploy`` should be considered subject to - Pyramid's deprecation policy and is subject to removal in the future. + Pyramid's deprecation policy and may be removed in the future. Applications should depend on the appropriate plaster binding to satisfy their needs. diff --git a/docs/whatsnew-1.9.rst b/docs/whatsnew-1.9.rst index e57ed254d..5f9e0e011 100644 --- a/docs/whatsnew-1.9.rst +++ b/docs/whatsnew-1.9.rst @@ -38,18 +38,15 @@ Minor Feature Additions Deprecations ------------ -- Pyramid currently depends on ``plaster_pastedeploy`` to simplify the transition to ``plaster`` by maintaining integrated support for INI files. This dependency on ``plaster_pastedeploy`` should be considered subject to Pyramid's deprecation policy and is subject to removal in the future. Applications should depend on the appropriate plaster binding to satisfy their needs. +- Pyramid currently depends on ``plaster_pastedeploy`` to simplify the transition to ``plaster`` by maintaining integrated support for INI files. This dependency on ``plaster_pastedeploy`` should be considered subject to Pyramid's deprecation policy and may be removed in the future. Applications should depend on the appropriate plaster binding to satisfy their needs. - Retrieving CSRF token from the session has been deprecated in favor of equivalent methods in the :mod:`pyramid.csrf` module. The CSRF methods (``ISession.get_csrf_token`` and ``ISession.new_csrf_token``) are no longer required on the :class:`pyramid.interfaces.ISession` interface except when using the default :class:`pyramid.csrf.LegacySessionCSRFStoragePolicy`. - Also, ``pyramid.session.check_csrf_token`` is now located at - :func:`pyramid.csrf.check_csrf_token`. + Also, ``pyramid.session.check_csrf_token`` is now located at :func:`pyramid.csrf.check_csrf_token`. - See https://github.com/Pylons/pyramid/pull/2854 and - https://github.com/Pylons/pyramid/pull/3019 + See https://github.com/Pylons/pyramid/pull/2854 and https://github.com/Pylons/pyramid/pull/3019 Documentation Enhancements -------------------------- -- Added the :term:`execution policy` to the routing diagram in - :ref:`router_chapter`. See https://github.com/Pylons/pyramid/pull/2993 +- Added the :term:`execution policy` to the routing diagram in :ref:`router_chapter`. See https://github.com/Pylons/pyramid/pull/2993 -- cgit v1.2.3 From 840508d2a104d1e729bbb1e91f50947da6988133 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 1 May 2017 22:51:18 -0500 Subject: typo --- CHANGES.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 85931d7f1..d77a5e49b 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -118,4 +118,4 @@ Documentation Changes --------------------- - Added the execution policy to the routing diagram in the Request Processing - chatper. See https://github.com/Pylons/pyramid/pull/2993 + chapter. See https://github.com/Pylons/pyramid/pull/2993 -- cgit v1.2.3 From 2ea4b00b1055864a1f10dd9c4c8a0b903b25080c Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 1 May 2017 22:51:44 -0500 Subject: add the license file to the wheel's dist-info --- setup.cfg | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.cfg b/setup.cfg index 9a241ddf5..6f1f33760 100644 --- a/setup.cfg +++ b/setup.cfg @@ -13,6 +13,9 @@ docs = develop easy_install pyramid[docs] [bdist_wheel] universal = 1 +[metadata] +license_file = LICENSE.txt + [flake8] ignore = # E121: continuation line under-indented for hanging indent -- cgit v1.2.3 From c84904381f664511060b39d0937fbe76efa22f25 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 1 May 2017 22:51:58 -0500 Subject: prep 1.9a1 --- CHANGES.txt | 4 ++-- setup.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index d77a5e49b..0513fd3c9 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,5 @@ -unreleased -========== +1.9a1 (2017-05-01) +================== Major Features -------------- diff --git a/setup.py b/setup.py index 3048428aa..6aac12ff8 100644 --- a/setup.py +++ b/setup.py @@ -61,7 +61,7 @@ testing_extras = tests_require + [ ] setup(name='pyramid', - version='1.9.dev0', + version='1.9a1', description='The Pyramid Web Framework, a Pylons project', long_description=README + '\n\n' + CHANGES, classifiers=[ -- cgit v1.2.3 From c273cd0471afe365d9bd8a793a81897a9e713aab Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 1 May 2017 22:57:19 -0500 Subject: fix url syntax --- docs/whatsnew-1.9.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/whatsnew-1.9.rst b/docs/whatsnew-1.9.rst index 5f9e0e011..b1a406a74 100644 --- a/docs/whatsnew-1.9.rst +++ b/docs/whatsnew-1.9.rst @@ -8,7 +8,7 @@ Major Feature Additions - The file format used by all ``p*`` command line scripts such as ``pserve`` and ``pshell``, as well as the :func:`pyramid.paster.bootstrap` function is now replaceable thanks to a new dependency on `plaster `_. - For now, Pyramid is still shipping with integrated support for the PasteDeploy INI format by depending on the `plaster_pastedeploy `_ binding library. This may change in the future so it is recommended for applications to start depending on the appropriate plaster binding for their needs. See https://github.com/Pylons/pyramid/pull/2985 -- cgit v1.2.3 From fbfd8191cee8536078cc01cd2256378ba0711f22 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 1 May 2017 23:05:53 -0500 Subject: fix url syntax yet again --- CHANGES.txt | 2 +- docs/conf.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 0513fd3c9..2378ec883 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -11,7 +11,7 @@ Major Features For now, Pyramid is still shipping with integrated support for the PasteDeploy INI format by depending on the - `plaster_pastedeploy `_ binding library. This may change in the future. See https://github.com/Pylons/pyramid/pull/2985 diff --git a/docs/conf.py b/docs/conf.py index df58064e5..f09ae325b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -67,6 +67,7 @@ intersphinx_mapping = { 'cookiecutter': ('https://cookiecutter.readthedocs.io/en/latest/', None), 'deform': ('http://docs.pylonsproject.org/projects/deform/en/latest', None), 'jinja2': ('http://docs.pylonsproject.org/projects/pyramid-jinja2/en/latest/', None), + 'plaster': ('http://docs.pylonsproject.org/projects/plaster/en/latest/', None), 'pylonswebframework': ('http://docs.pylonsproject.org/projects/pylons-webframework/en/latest/', None), 'python': ('https://docs.python.org/3', None), 'pytest': ('http://pytest.org/latest/', None), -- cgit v1.2.3 From 216e105262f23f7469a72265d94ed0181ec04bc3 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Tue, 2 May 2017 00:32:30 -0700 Subject: kill off pylonsrtd --- RELEASING.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/RELEASING.txt b/RELEASING.txt index b9e5f4a6c..cd38b9871 100644 --- a/RELEASING.txt +++ b/RELEASING.txt @@ -111,8 +111,6 @@ Marketing and communications - Edit Pylons/trypyramid.com/src/templates/resources.html for major releases only. -- Edit Pylons/pylonsrtd/pylonsrtd/docs/pyramid.rst for major releases only. - - Edit `http://wiki.python.org/moin/WebFrameworks `_. -- cgit v1.2.3 From 7d1e0646d701b35d61ca8800341c07afac31bb86 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Tue, 2 May 2017 00:34:24 -0700 Subject: add more events for updating trypyramid.com --- RELEASING.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RELEASING.txt b/RELEASING.txt index cd38b9871..58ebb2fb3 100644 --- a/RELEASING.txt +++ b/RELEASING.txt @@ -108,8 +108,8 @@ Update previous version (final releases only) Marketing and communications ---------------------------- -- Edit Pylons/trypyramid.com/src/templates/resources.html for major releases - only. +- Edit Pylons/trypyramid.com/src/templates/resources.html for major releases, + pre-releases, and once pre-releases are final. - Edit `http://wiki.python.org/moin/WebFrameworks `_. -- cgit v1.2.3 From 7936210eb8f6da693fcab9ed04dfed277874eeb0 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Tue, 2 May 2017 21:43:49 -0500 Subject: clean request.exception if the excview fails to handle the error request.exception is only not None if the response was generated by the excview fixes #3027 --- pyramid/tests/test_tweens.py | 17 ++++++++- pyramid/tweens.py | 86 +++++++++++++++++++++++--------------------- 2 files changed, 61 insertions(+), 42 deletions(-) diff --git a/pyramid/tests/test_tweens.py b/pyramid/tests/test_tweens.py index c8eada34c..2e74ad7cf 100644 --- a/pyramid/tests/test_tweens.py +++ b/pyramid/tests/test_tweens.py @@ -22,6 +22,8 @@ class Test_excview_tween_factory(unittest.TestCase): request = DummyRequest() result = tween(request) self.assertTrue(result is dummy_response) + self.assertIsNone(request.exception) + self.assertIsNone(request.exc_info) def test_it_catches_notfound(self): from pyramid.request import Request @@ -31,8 +33,11 @@ class Test_excview_tween_factory(unittest.TestCase): raise HTTPNotFound tween = self._makeOne(handler) request = Request.blank('/') + request.registry = self.config.registry result = tween(request) self.assertEqual(result.status, '404 Not Found') + self.assertIsInstance(request.exception, HTTPNotFound) + self.assertEqual(request.exception, request.exc_info[1]) def test_it_catches_with_predicate(self): from pyramid.request import Request @@ -44,8 +49,11 @@ class Test_excview_tween_factory(unittest.TestCase): raise ValueError tween = self._makeOne(handler) request = Request.blank('/') + request.registry = self.config.registry result = tween(request) self.assertTrue(b'foo' in result.body) + self.assertIsInstance(request.exception, ValueError) + self.assertEqual(request.exception, request.exc_info[1]) def test_it_reraises_on_mismatch(self): from pyramid.request import Request @@ -55,8 +63,11 @@ class Test_excview_tween_factory(unittest.TestCase): raise ValueError tween = self._makeOne(handler) request = Request.blank('/') + request.registry = self.config.registry request.method = 'POST' self.assertRaises(ValueError, lambda: tween(request)) + self.assertIsNone(request.exception) + self.assertIsNone(request.exc_info) def test_it_reraises_on_no_match(self): from pyramid.request import Request @@ -64,10 +75,14 @@ class Test_excview_tween_factory(unittest.TestCase): raise ValueError tween = self._makeOne(handler) request = Request.blank('/') + request.registry = self.config.registry self.assertRaises(ValueError, lambda: tween(request)) + self.assertIsNone(request.exception) + self.assertIsNone(request.exc_info) class DummyRequest: - pass + exception = None + exc_info = None class DummyResponse: pass diff --git a/pyramid/tweens.py b/pyramid/tweens.py index a842b1133..673429b06 100644 --- a/pyramid/tweens.py +++ b/pyramid/tweens.py @@ -10,6 +10,50 @@ from pyramid.interfaces import ( from zope.interface import providedBy from pyramid.view import _call_view +def _error_handler(request, exc): + # NOTE: we do not need to delete exc_info because this function + # should never be in the call stack of the exception + exc_info = sys.exc_info() + + attrs = request.__dict__ + attrs['exc_info'] = exc_info + attrs['exception'] = exc + # clear old generated request.response, if any; it may + # have been mutated by the view, and its state is not + # sane (e.g. caching headers) + if 'response' in attrs: + del attrs['response'] + # we use .get instead of .__getitem__ below due to + # https://github.com/Pylons/pyramid/issues/700 + request_iface = attrs.get('request_iface', IRequest) + provides = providedBy(exc) + try: + response = _call_view( + request.registry, + request, + exc, + provides, + '', + view_classifier=IExceptionViewClassifier, + request_iface=request_iface.combined + ) + + # if views matched but did not pass predicates then treat the + # same as not finding any matching views + except PredicateMismatch: + response = None + + # re-raise the original exception as no exception views were + # able to handle the error + if response is None: + if 'exception' in attrs: + del attrs['exception'] + if 'exc_info' in attrs: + del attrs['exc_info'] + reraise(*exc_info) + + return response + def excview_tween_factory(handler, registry): """ A :term:`tween` factory which produces a tween that catches an exception raised by downstream tweens (or the main Pyramid request @@ -17,50 +61,10 @@ def excview_tween_factory(handler, registry): :term:`exception view`.""" def excview_tween(request): - attrs = request.__dict__ try: response = handler(request) except Exception as exc: - # WARNING: do not assign the result of sys.exc_info() to a local - # var here, doing so will cause a leak. We used to actually - # explicitly delete both "exception" and "exc_info" from ``attrs`` - # in a ``finally:`` clause below, but now we do not because these - # attributes are useful to upstream tweens. This actually still - # apparently causes a reference cycle, but it is broken - # successfully by the garbage collector (see - # https://github.com/Pylons/pyramid/issues/1223). - attrs['exc_info'] = sys.exc_info() - attrs['exception'] = exc - # clear old generated request.response, if any; it may - # have been mutated by the view, and its state is not - # sane (e.g. caching headers) - if 'response' in attrs: - del attrs['response'] - # we use .get instead of .__getitem__ below due to - # https://github.com/Pylons/pyramid/issues/700 - request_iface = attrs.get('request_iface', IRequest) - provides = providedBy(exc) - try: - response = _call_view( - registry, - request, - exc, - provides, - '', - view_classifier=IExceptionViewClassifier, - request_iface=request_iface.combined - ) - - # if views matched but did not pass predicates, squash the error - # and re-raise the original exception - except PredicateMismatch: - response = None - - # re-raise the original exception as no exception views were - # able to handle the error - if response is None: - reraise(*attrs['exc_info']) - + response = _error_handler(request, exc) return response return excview_tween -- cgit v1.2.3 From 3213e20f58d1a0b339e9d5bf9378ec54593624c7 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 3 May 2017 14:05:19 -0500 Subject: add changelog for #3029 --- CHANGES.txt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 2378ec883..b299ed6e9 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -114,6 +114,23 @@ Deprecations See https://github.com/Pylons/pyramid/pull/2854 and https://github.com/Pylons/pyramid/pull/3019 +Backward Incompatibilities +-------------------------- + +- ``request.exception`` and ``request.exc_info`` will only be set if the + response was generated by the EXCVIEW tween. This is to avoid any confusion + where a response was generated elsewhere in the pipeline and not in + direct relation to the original exception. If anyone upstream wants to + catch and render responses for exceptions they should set + ``request.exception`` and ``request.exc_info`` themselves to indicate + the exception that was squashed when generating the response. + + This is a very minor incompatibility. Most tweens right now would give + priority to the raised exception and ignore ``request.exception``. This + change just improves and clarifies that bookkeeping by trying to be + more clear about the relationship between the response and its squashed + exception. See https://github.com/Pylons/pyramid/pull/3029 + Documentation Changes --------------------- -- cgit v1.2.3 From 3b886e6f39b1c89e78566bce2edacef9bee6d177 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 3 May 2017 22:59:52 -0500 Subject: normalize excview tween to use ``request.invoke_exception_view`` ``request.exception`` and ``request.exc_info`` are set to the exception used to render the response but they are reset to their original values if no response could be rendered minor incompatibility in that ``request.response`` is restored after the excview tween but should not be an issue because a response is returned thus request.response should be ignored by anyone who cares. --- pyramid/tests/test_view.py | 6 +++--- pyramid/tweens.py | 47 +++++----------------------------------------- pyramid/view.py | 21 ++++++++++++++++----- 3 files changed, 24 insertions(+), 50 deletions(-) diff --git a/pyramid/tests/test_view.py b/pyramid/tests/test_view.py index cab42cf48..2061515b3 100644 --- a/pyramid/tests/test_view.py +++ b/pyramid/tests/test_view.py @@ -778,11 +778,11 @@ class TestViewMethodsMixin(unittest.TestCase): orig_response = request.response = DummyResponse(b'foo') try: raise RuntimeError - except RuntimeError: + except RuntimeError as ex: response = request.invoke_exception_view() self.assertEqual(response.app_iter, [b'bar']) - self.assertTrue(request.exception is orig_exc) - self.assertTrue(request.exc_info is orig_exc_info) + self.assertTrue(request.exception is ex) + self.assertTrue(request.exc_info[1] is ex) self.assertTrue(request.response is orig_response) else: # pragma: no cover self.fail() diff --git a/pyramid/tweens.py b/pyramid/tweens.py index 673429b06..c9b3bd4b8 100644 --- a/pyramid/tweens.py +++ b/pyramid/tweens.py @@ -1,55 +1,18 @@ import sys from pyramid.compat import reraise -from pyramid.exceptions import PredicateMismatch -from pyramid.interfaces import ( - IExceptionViewClassifier, - IRequest, - ) - -from zope.interface import providedBy -from pyramid.view import _call_view +from pyramid.httpexceptions import HTTPNotFound def _error_handler(request, exc): # NOTE: we do not need to delete exc_info because this function # should never be in the call stack of the exception exc_info = sys.exc_info() - attrs = request.__dict__ - attrs['exc_info'] = exc_info - attrs['exception'] = exc - # clear old generated request.response, if any; it may - # have been mutated by the view, and its state is not - # sane (e.g. caching headers) - if 'response' in attrs: - del attrs['response'] - # we use .get instead of .__getitem__ below due to - # https://github.com/Pylons/pyramid/issues/700 - request_iface = attrs.get('request_iface', IRequest) - provides = providedBy(exc) try: - response = _call_view( - request.registry, - request, - exc, - provides, - '', - view_classifier=IExceptionViewClassifier, - request_iface=request_iface.combined - ) - - # if views matched but did not pass predicates then treat the - # same as not finding any matching views - except PredicateMismatch: - response = None - - # re-raise the original exception as no exception views were - # able to handle the error - if response is None: - if 'exception' in attrs: - del attrs['exception'] - if 'exc_info' in attrs: - del attrs['exc_info'] + response = request.invoke_exception_view(exc_info) + except HTTPNotFound: + # re-raise the original exception as no exception views were + # able to handle the error reraise(*exc_info) return response diff --git a/pyramid/view.py b/pyramid/view.py index 498bdde45..3ce984209 100644 --- a/pyramid/view.py +++ b/pyramid/view.py @@ -657,8 +657,14 @@ class ViewMethodsMixin(object): This method returns a :term:`response` object or raises :class:`pyramid.httpexceptions.HTTPNotFound` if a matching view cannot - be found.""" + be found. + If a response is generated then ``request.exception`` and + ``request.exc_info`` will be left at the values used to render the + response. Otherwise the previous values for ``request.exception`` and + ``request.exc_info`` will be restored. + + """ if request is None: request = self registry = getattr(request, 'registry', None) @@ -673,7 +679,7 @@ class ViewMethodsMixin(object): # clear old generated request.response, if any; it may # have been mutated by the view, and its state is not # sane (e.g. caching headers) - with hide_attrs(request, 'exception', 'exc_info', 'response'): + with hide_attrs(request, 'response', 'exc_info', 'exception'): attrs['exception'] = exc attrs['exc_info'] = exc_info # we use .get instead of .__getitem__ below due to @@ -690,6 +696,11 @@ class ViewMethodsMixin(object): secure=secure, request_iface=request_iface.combined, ) - if response is None: - raise HTTPNotFound - return response + + if response is None: + raise HTTPNotFound + + # successful response, overwrite exception/exc_info + attrs['exception'] = exc + attrs['exc_info'] = exc_info + return response -- cgit v1.2.3 From e2e51b35303e69b5028a84026837095b1bfe6f79 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Thu, 4 May 2017 00:23:18 -0500 Subject: add changelog for #3031 --- CHANGES.txt | 7 ++++++- pyramid/tweens.py | 13 ++++++++++++- pyramid/view.py | 5 +++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index b299ed6e9..80b5003c1 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -125,11 +125,16 @@ Backward Incompatibilities ``request.exception`` and ``request.exc_info`` themselves to indicate the exception that was squashed when generating the response. + Similar behavior occurs with ``request.invoke_exception_view`` in which + the exception properties are set to reflect the exception if a response + is successfully generated by the method. + This is a very minor incompatibility. Most tweens right now would give priority to the raised exception and ignore ``request.exception``. This change just improves and clarifies that bookkeeping by trying to be more clear about the relationship between the response and its squashed - exception. See https://github.com/Pylons/pyramid/pull/3029 + exception. See https://github.com/Pylons/pyramid/pull/3029 and + https://github.com/Pylons/pyramid/pull/3031 Documentation Changes --------------------- diff --git a/pyramid/tweens.py b/pyramid/tweens.py index c9b3bd4b8..740b6961c 100644 --- a/pyramid/tweens.py +++ b/pyramid/tweens.py @@ -21,7 +21,18 @@ def excview_tween_factory(handler, registry): """ A :term:`tween` factory which produces a tween that catches an exception raised by downstream tweens (or the main Pyramid request handler) and, if possible, converts it into a Response using an - :term:`exception view`.""" + :term:`exception view`. + + .. versionchanged:: 1.9 + The ``request.response`` will be remain unchanged even if the tween + handles an exception. Previously it was deleted after handling an + exception. + + Also, ``request.exception`` and ``request.exc_info`` are only set if + the tween handles an exception and returns a response otherwise they + are left at their original values. + + """ def excview_tween(request): try: diff --git a/pyramid/view.py b/pyramid/view.py index 3ce984209..0c1b8cd97 100644 --- a/pyramid/view.py +++ b/pyramid/view.py @@ -664,6 +664,11 @@ class ViewMethodsMixin(object): response. Otherwise the previous values for ``request.exception`` and ``request.exc_info`` will be restored. + .. versionchanged:: 1.9 + The ``request.exception`` and ``request.exc_info`` properties will + reflect the exception used to render the response where previously + they were reset to the values prior to invoking the method. + """ if request is None: request = self -- cgit v1.2.3 From ab8c57811d904377416c2786670ecf0e81d8ca33 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Thu, 4 May 2017 00:26:20 -0500 Subject: add incompatibilities to whatsnew --- docs/whatsnew-1.9.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/whatsnew-1.9.rst b/docs/whatsnew-1.9.rst index b1a406a74..f49258662 100644 --- a/docs/whatsnew-1.9.rst +++ b/docs/whatsnew-1.9.rst @@ -46,6 +46,29 @@ Deprecations See https://github.com/Pylons/pyramid/pull/2854 and https://github.com/Pylons/pyramid/pull/3019 +Backward Incompatibilities +-------------------------- + +- ``request.exception`` and ``request.exc_info`` will only be set if the + response was generated by the EXCVIEW tween. This is to avoid any confusion + where a response was generated elsewhere in the pipeline and not in + direct relation to the original exception. If anyone upstream wants to + catch and render responses for exceptions they should set + ``request.exception`` and ``request.exc_info`` themselves to indicate + the exception that was squashed when generating the response. + + Similar behavior occurs with + :meth:`pyramid.request.Request.invoke_exception_view` in which + the exception properties are set to reflect the exception if a response + is successfully generated by the method. + + This is a very minor incompatibility. Most tweens right now would give + priority to the raised exception and ignore ``request.exception``. This + change just improves and clarifies that bookkeeping by trying to be + more clear about the relationship between the response and its squashed + exception. See https://github.com/Pylons/pyramid/pull/3029 and + https://github.com/Pylons/pyramid/pull/3031 + Documentation Enhancements -------------------------- -- cgit v1.2.3 From d1745247edae01ef934acf5bb206d29952a99dbf Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Thu, 4 May 2017 00:27:18 -0500 Subject: line length --- docs/whatsnew-1.9.rst | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/docs/whatsnew-1.9.rst b/docs/whatsnew-1.9.rst index f49258662..0ba29625c 100644 --- a/docs/whatsnew-1.9.rst +++ b/docs/whatsnew-1.9.rst @@ -49,25 +49,11 @@ Deprecations Backward Incompatibilities -------------------------- -- ``request.exception`` and ``request.exc_info`` will only be set if the - response was generated by the EXCVIEW tween. This is to avoid any confusion - where a response was generated elsewhere in the pipeline and not in - direct relation to the original exception. If anyone upstream wants to - catch and render responses for exceptions they should set - ``request.exception`` and ``request.exc_info`` themselves to indicate - the exception that was squashed when generating the response. - - Similar behavior occurs with - :meth:`pyramid.request.Request.invoke_exception_view` in which - the exception properties are set to reflect the exception if a response - is successfully generated by the method. - - This is a very minor incompatibility. Most tweens right now would give - priority to the raised exception and ignore ``request.exception``. This - change just improves and clarifies that bookkeeping by trying to be - more clear about the relationship between the response and its squashed - exception. See https://github.com/Pylons/pyramid/pull/3029 and - https://github.com/Pylons/pyramid/pull/3031 +- ``request.exception`` and ``request.exc_info`` will only be set if the response was generated by the EXCVIEW tween. This is to avoid any confusion where a response was generated elsewhere in the pipeline and not in direct relation to the original exception. If anyone upstream wants to catch and render responses for exceptions they should set ``request.exception`` and ``request.exc_info`` themselves to indicate the exception that was squashed when generating the response. + + Similar behavior occurs with :meth:`pyramid.request.Request.invoke_exception_view` in which the exception properties are set to reflect the exception if a response is successfully generated by the method. + + This is a very minor incompatibility. Most tweens right now would give priority to the raised exception and ignore ``request.exception``. This change just improves and clarifies that bookkeeping by trying to be more clear about the relationship between the response and its squashed exception. See https://github.com/Pylons/pyramid/pull/3029 and https://github.com/Pylons/pyramid/pull/3031 Documentation Enhancements -------------------------- -- cgit v1.2.3 From 203101c06c61a80d1023f1d21302eceae5f66e81 Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Sat, 6 May 2017 20:15:40 -0400 Subject: Update url.py | Refactor parse_url_overrides Refactor parse_url_overrides: * pop values with default if key is missing * change conditionals to test for truth * prevent throwing an exception if passing keyword with default value * test if anchor starts with '#' before blindly adding it --- pyramid/url.py | 42 +++++++++++++++--------------------------- 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/pyramid/url.py b/pyramid/url.py index 9634f61da..7b88c385c 100644 --- a/pyramid/url.py +++ b/pyramid/url.py @@ -34,40 +34,28 @@ ANCHOR_SAFE = QUERY_SAFE def parse_url_overrides(kw): """Parse special arguments passed when generating urls. - The supplied dictionary is mutated, popping arguments as necessary. - Returns a 6-tuple of the format ``(app_url, scheme, host, port, - qs, anchor)``. + The supplied dictionary is mutated when we pop arguments. + Returns a 6-tuple of the format: + + ``(app_url, scheme, host, port, qs, anchor)``. """ - anchor = '' - qs = '' - app_url = None - host = None - scheme = None - port = None - - if '_query' in kw: - query = kw.pop('_query') + app_url = kw.pop('_app_url', None) + scheme = kw.pop('_scheme', None) + host = kw.pop('_host', None) + port = kw.pop('_port', None) + qs = query = kw.pop('_query', '') + anchor = kw.pop('_anchor', '') + + if query: if isinstance(query, string_types): qs = '?' + url_quote(query, QUERY_SAFE) elif query: qs = '?' + urlencode(query, doseq=True) - if '_anchor' in kw: - anchor = kw.pop('_anchor') + if anchor: anchor = url_quote(anchor, ANCHOR_SAFE) - anchor = '#' + anchor - - if '_app_url' in kw: - app_url = kw.pop('_app_url') - - if '_host' in kw: - host = kw.pop('_host') - - if '_scheme' in kw: - scheme = kw.pop('_scheme') - - if '_port' in kw: - port = kw.pop('_port') + if anchor.startswith('#') == False: + anchor = '#' + anchor return app_url, scheme, host, port, qs, anchor -- cgit v1.2.3 From 707e22afe530367645a25044a8ea4a1b8d803cd4 Mon Sep 17 00:00:00 2001 From: russellballestrini Date: Sat, 6 May 2017 20:37:06 -0400 Subject: make adjustments to make tests pass. modified: pyramid/url.py --- pyramid/url.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyramid/url.py b/pyramid/url.py index 7b88c385c..7371acaa3 100644 --- a/pyramid/url.py +++ b/pyramid/url.py @@ -43,13 +43,14 @@ def parse_url_overrides(kw): scheme = kw.pop('_scheme', None) host = kw.pop('_host', None) port = kw.pop('_port', None) - qs = query = kw.pop('_query', '') + query = kw.pop('_query', '') anchor = kw.pop('_anchor', '') + qs = '' if query: if isinstance(query, string_types): qs = '?' + url_quote(query, QUERY_SAFE) - elif query: + else: qs = '?' + urlencode(query, doseq=True) if anchor: -- cgit v1.2.3 From 666e8a72aad017633c9c2ca8c2fe0dcfe54a2b29 Mon Sep 17 00:00:00 2001 From: russellballestrini Date: Sat, 6 May 2017 20:39:27 -0400 Subject: modified: CONTRIBUTORS.txt --- CONTRIBUTORS.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index ca1f56f51..b8fb081ec 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -298,3 +298,5 @@ Contributors - Kirill Kuzminykh, 2017/03/01 - Jeremy(Ching-Rui) Chen, 2017/04/19 + +- Russell Ballestrini, 2017/05/06 -- cgit v1.2.3 From 03e2626a6b33584e2b4e1c7c446b4f311c0fb350 Mon Sep 17 00:00:00 2001 From: russellballestrini Date: Sat, 6 May 2017 21:29:30 -0400 Subject: pep8 fix modified: pyramid/url.py --- pyramid/url.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyramid/url.py b/pyramid/url.py index 7371acaa3..cbfbfc553 100644 --- a/pyramid/url.py +++ b/pyramid/url.py @@ -55,7 +55,7 @@ def parse_url_overrides(kw): if anchor: anchor = url_quote(anchor, ANCHOR_SAFE) - if anchor.startswith('#') == False: + if not anchor.startswith('#'): anchor = '#' + anchor return app_url, scheme, host, port, qs, anchor -- cgit v1.2.3 From 1fc7eefc4ac9f5ea3d22d7a108cd3da1e73cbcfa Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Tue, 9 May 2017 01:41:12 -0500 Subject: fix changelog, added #3031 and #3029 to the wrong release version --- CHANGES.txt | 47 +++++++++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 80b5003c1..51a1e457d 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,28 @@ +1.9a2 (2017-05-09) +================== + +Backward Incompatibilities +-------------------------- + +- ``request.exception`` and ``request.exc_info`` will only be set if the + response was generated by the EXCVIEW tween. This is to avoid any confusion + where a response was generated elsewhere in the pipeline and not in + direct relation to the original exception. If anyone upstream wants to + catch and render responses for exceptions they should set + ``request.exception`` and ``request.exc_info`` themselves to indicate + the exception that was squashed when generating the response. + + Similar behavior occurs with ``request.invoke_exception_view`` in which + the exception properties are set to reflect the exception if a response + is successfully generated by the method. + + This is a very minor incompatibility. Most tweens right now would give + priority to the raised exception and ignore ``request.exception``. This + change just improves and clarifies that bookkeeping by trying to be + more clear about the relationship between the response and its squashed + exception. See https://github.com/Pylons/pyramid/pull/3029 and + https://github.com/Pylons/pyramid/pull/3031 + 1.9a1 (2017-05-01) ================== @@ -114,28 +139,6 @@ Deprecations See https://github.com/Pylons/pyramid/pull/2854 and https://github.com/Pylons/pyramid/pull/3019 -Backward Incompatibilities --------------------------- - -- ``request.exception`` and ``request.exc_info`` will only be set if the - response was generated by the EXCVIEW tween. This is to avoid any confusion - where a response was generated elsewhere in the pipeline and not in - direct relation to the original exception. If anyone upstream wants to - catch and render responses for exceptions they should set - ``request.exception`` and ``request.exc_info`` themselves to indicate - the exception that was squashed when generating the response. - - Similar behavior occurs with ``request.invoke_exception_view`` in which - the exception properties are set to reflect the exception if a response - is successfully generated by the method. - - This is a very minor incompatibility. Most tweens right now would give - priority to the raised exception and ignore ``request.exception``. This - change just improves and clarifies that bookkeeping by trying to be - more clear about the relationship between the response and its squashed - exception. See https://github.com/Pylons/pyramid/pull/3029 and - https://github.com/Pylons/pyramid/pull/3031 - Documentation Changes --------------------- -- cgit v1.2.3 From 607367ace939488f787d918c60275a90d5505dbd Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Tue, 9 May 2017 01:42:09 -0500 Subject: prep 1.9a2 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6aac12ff8..03416efe7 100644 --- a/setup.py +++ b/setup.py @@ -61,7 +61,7 @@ testing_extras = tests_require + [ ] setup(name='pyramid', - version='1.9a1', + version='1.9a2', description='The Pyramid Web Framework, a Pylons project', long_description=README + '\n\n' + CHANGES, classifiers=[ -- cgit v1.2.3 From a7402ad57c6bf4803286b61fd9560d8b192826b6 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Tue, 9 May 2017 14:15:01 -0400 Subject: Pytest changed their URL structure --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index f09ae325b..0fdfa7c9a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -70,7 +70,7 @@ intersphinx_mapping = { 'plaster': ('http://docs.pylonsproject.org/projects/plaster/en/latest/', None), 'pylonswebframework': ('http://docs.pylonsproject.org/projects/pylons-webframework/en/latest/', None), 'python': ('https://docs.python.org/3', None), - 'pytest': ('http://pytest.org/latest/', None), + 'pytest': ('http://pytest.org/en/latest/', None), 'sphinx': ('http://www.sphinx-doc.org/en/latest', None), 'sqla': ('http://docs.sqlalchemy.org/en/latest', None), 'tm': ('http://docs.pylonsproject.org/projects/pyramid-tm/en/latest/', None), -- cgit v1.2.3 From 0b92dfed800117595ef00fb2847c5db9970f4cac Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Tue, 9 May 2017 12:00:33 -0700 Subject: use new TLD for pytest-cov --- docs/quick_tutorial/unit_testing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quick_tutorial/unit_testing.rst b/docs/quick_tutorial/unit_testing.rst index 7c85d5289..002c62fde 100644 --- a/docs/quick_tutorial/unit_testing.rst +++ b/docs/quick_tutorial/unit_testing.rst @@ -29,7 +29,7 @@ broken the code. As you're writing your code, you might find this more convenient than changing to your browser constantly and clicking reload. We'll also leave discussion of `pytest-cov -`_ for another section. +`_ for another section. Objectives -- cgit v1.2.3 From f46c7944b70e2204529216655bfdfac1b72e646b Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Fri, 12 May 2017 01:18:17 -0700 Subject: use https --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 0fdfa7c9a..e63019c63 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -70,7 +70,7 @@ intersphinx_mapping = { 'plaster': ('http://docs.pylonsproject.org/projects/plaster/en/latest/', None), 'pylonswebframework': ('http://docs.pylonsproject.org/projects/pylons-webframework/en/latest/', None), 'python': ('https://docs.python.org/3', None), - 'pytest': ('http://pytest.org/en/latest/', None), + 'pytest': ('https://pytest.org/en/latest/', None), 'sphinx': ('http://www.sphinx-doc.org/en/latest', None), 'sqla': ('http://docs.sqlalchemy.org/en/latest', None), 'tm': ('http://docs.pylonsproject.org/projects/pyramid-tm/en/latest/', None), -- cgit v1.2.3 From b57460e673b4837f9bb587ecb9b6deb1761dcfe6 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Fri, 12 May 2017 21:06:08 -0700 Subject: update narrative docs to align with source code - per https://github.com/Pylons/pyramid/pull/3000#issuecomment-294565854 --- docs/narr/project.rst | 6 +++--- docs/narr/startup.rst | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/narr/project.rst b/docs/narr/project.rst index ce7e90793..ad27290f3 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -332,7 +332,7 @@ Access is restricted such that only a browser running on the same machine as Pyramid will be able to access your Pyramid application. However, if you want to open access to other machines on the same network, then edit the ``development.ini`` file, and replace the ``listen`` value in the -``[server:main]`` section, changing it from ``127.0.0.1:6543 [::1]:6543`` to ``*:6543`` +``[server:main]`` section, changing it from ``localhost:6543`` to ``*:6543`` (this is equivalent to ``0.0.0.0:6543 [::]:6543``). For example: .. code-block:: ini @@ -356,8 +356,8 @@ IPv6. ``[::]`` means the same as ``0.0.0.0`` but for IPv6 protocol. You can change the port on which the server runs on by changing the same portion of the ``development.ini`` file. For example, you can change the -``listen = 127.0.0.1:6543 [::1]:6543`` line in the ``development.ini`` file's ``[server:main]`` -section to ``listen = 127:0.0.1:8080 [::1]:8080`` to run the server on port 8080 instead of port 6543. +``listen = localhost:6543`` line in the ``development.ini`` file's ``[server:main]`` +section to ``listen = localhost:8080`` to run the server on port 8080 instead of port 6543. You can shut down a server started this way by pressing ``Ctrl-C`` (or ``Ctrl-Break`` on Windows). diff --git a/docs/narr/startup.rst b/docs/narr/startup.rst index 08747fa89..27a2f1919 100644 --- a/docs/narr/startup.rst +++ b/docs/narr/startup.rst @@ -10,12 +10,12 @@ you'll see something much like this show up on the console: $ $VENV/bin/pserve development.ini Starting server in PID 16305. - Serving on http://127.0.0.1:6543 - Serving on http://[::1]:6543 + Serving on http://localhost:6543 + Serving on http://localhost:6543 This chapter explains what happens between the time you press the "Return" key -on your keyboard after typing ``pserve development.ini`` and the time the line -``serving on http://127.0.0.1:6543`` is output to your console. +on your keyboard after typing ``pserve development.ini`` and the time the lines +``Serving on http://localhost:6543`` are output to your console. .. index:: single: startup process @@ -133,7 +133,7 @@ Here's a high-level time-ordered overview of what happens when you press section. In our case, this is the Waitress server (``use = egg:waitress#main``), and it will listen on all interfaces on port 6543 for both IPv4 and IPv6 (``listen = localhost:6543``). The server - code itself is what prints ``serving on http://127.0.0.1:6543``. The server + code itself is what prints ``Serving on http://localhost:6543``. The server serves the application, and the application is running, waiting to receive requests. .. seealso:: -- cgit v1.2.3 From b5444950d84c507f26764a16021db6e01d0461e3 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Fri, 12 May 2017 21:22:39 -0700 Subject: resolve conflicts --- CONTRIBUTORS.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 1a6fc24ac..542b85f8e 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -295,4 +295,6 @@ Contributors - Kirill Kuzminykh, 2017/03/01 -- Aleph Melo, 2017/04/16 \ No newline at end of file +- Aleph Melo, 2017/04/16 + +- Jeremy(Ching-Rui) Chen, 2017/04/19 -- cgit v1.2.3 From b730ae3a4706df6e5cf39a91b23ec67225fc63db Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Wed, 17 May 2017 04:01:27 -0700 Subject: remove bad path from python executable - Closes #3044 --- docs/quick_tour.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quick_tour.rst b/docs/quick_tour.rst index de896939a..1265012ab 100644 --- a/docs/quick_tour.rst +++ b/docs/quick_tour.rst @@ -46,7 +46,7 @@ For Windows: # set an environment variable to where you want your virtual environment c:\\> set VENV=c:\\env # create the virtual environment - c:\\> %VENV%\\Scripts\\python -m venv %VENV% + c:\\> python -m venv %VENV% # install pyramid c:\\> %VENV%\\Scripts\\pip install pyramid # or for a specific released version -- cgit v1.2.3 From ad6b57f351799c44eb539cf622ed197ea85f9dbd Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Wed, 17 May 2017 04:24:44 -0700 Subject: adjust emphasize-lines range --- docs/tutorials/wiki/authorization.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst index d580e7816..0ba734f83 100644 --- a/docs/tutorials/wiki/authorization.rst +++ b/docs/tutorials/wiki/authorization.rst @@ -233,7 +233,7 @@ Add the following import statements to the head of .. literalinclude:: src/authorization/tutorial/views.py :lines: 6-17 - :emphasize-lines: 1-14 + :emphasize-lines: 1-12 :language: python All the highlighted lines need to be added or edited. -- cgit v1.2.3 From 7c680930d09d20bfa05249e01553e6488e61f1ca Mon Sep 17 00:00:00 2001 From: cewing Date: Mon, 22 May 2017 10:56:05 -0700 Subject: updates to narrative docs introduction, fixing for clarity and concision --- docs/narr/introduction.rst | 665 +++++++++++---------------------------------- 1 file changed, 159 insertions(+), 506 deletions(-) diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst index 40d9c14a8..adf955a97 100644 --- a/docs/narr/introduction.rst +++ b/docs/narr/introduction.rst @@ -55,6 +55,63 @@ Openness As with Python, the Pyramid software is distributed under a `permissive open source license `_. +.. _why_pyramid: + +Why Pyramid? +------------ + +In a world filled with web frameworks, why should you choose Pyramid? + +Modern +~~~~~~ + +Pyramid is fully compatible with Python 3. If you develop a Pyramid application +today, you can rest assured that you'll be able to use the most modern features +of your favorite language. And in the years to come, you'll continue to be +working on a framework that is up-to-date and forward-looking. + +Tested +~~~~~~ + +Untested code is broken by design. The Pyramid community has a strong testing +culture and our framework reflects that. Every release of Pyramid has 100% +statement coverage [#]_ and 95% decision/condition coverage. [#]_ It is +automatically tested using `Travis `_ and +`Jenkins `_ on Python 2.7, +Python 3.4, Python 3.5, and PyPy after each commit to its GitHub repository. +`Official Pyramid add-ons `_ +are held to a similar testing standard. + +We still find bugs in Pyramid, but we've noticed we find a lot fewer of them +while working on projects with a solid testing regime. + +.. [#] as measured by `coverage `_ +.. [#] as measured by `instrumental `_ + +Documented +~~~~~~~~~~ + +The Pyramid documentation is comprehensive. We strive to keep our narrative +documentation both complete and friendly to newcomers. We also maintain a +:ref:`cookbook ` of recipes, demonstrations of +common scenarios you might face. And contributions in the form of improvements +to our documentation are always appreciated. + +Supported +~~~~~~~~~ + +You can get help quickly with Pyramid. It's our goal that no Pyramid question +go unanswered. Whether you ask a question on IRC, on the Pylons-discuss mailing +list, or on StackOverflow, you're likely to get a reasonably prompt response. + +Pyramid is also a welcoming, friendly space for newcomers. We don't tolerate +"support trolls" or those who enjoy berating fellow users in our support +channels. We try to keep it well-lit and new-user-friendly. + +Example: Visit irc\://freenode.net#pyramid (the ``#pyramid`` channel on +irc.freenode.net in an IRC client) or the pylons-discuss maillist at +https://groups.google.com/forum/#!forum/pylons-discuss. + .. _what_makes_pyramid_unique: What makes Pyramid unique @@ -324,537 +381,131 @@ customization. See :ref:`intro_asset_specs` for more information. Example: :ref:`renderers_chapter`. -Event system -~~~~~~~~~~~~ - -Pyramid emits *events* during its request processing lifecycle. You can -subscribe any number of listeners to these events. For example, to be notified -of a new request, you can subscribe to the ``NewRequest`` event. To be -notified that a template is about to be rendered, you can subscribe to the -``BeforeRender`` event, and so forth. Using an event publishing system as a -framework notification feature instead of hardcoded hook points tends to make -systems based on that framework less brittle. - -You can also use Pyramid's event system to send your *own* events. For -example, if you'd like to create a system that is itself a framework, and may -want to notify subscribers that a document has just been indexed, you can -create your own event type (``DocumentIndexed`` perhaps) and send the event via -Pyramid. Users of this framework can then subscribe to your event like they'd -subscribe to the events that are normally sent by Pyramid itself. - -Example: :ref:`events_chapter` and :ref:`event_types`. - -Built-in internationalization -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Pyramid ships with internationalization-related features in its core: -localization, pluralization, and creating message catalogs from source files -and templates. Pyramid allows for a plurality of message catalogs via the use -of translation domains. You can create a system that has its own translations -without conflict with other translations in other domains. - -Example: :ref:`i18n_chapter`. - -HTTP caching -~~~~~~~~~~~~ - -Pyramid provides an easy way to associate views with HTTP caching policies. You -can just tell Pyramid to configure your view with an ``http_cache`` statement, -and it will take care of the rest:: - - @view_config(http_cache=3600) # 60 minutes - def myview(request): .... - -Pyramid will add appropriate ``Cache-Control`` and ``Expires`` headers to -responses generated when this view is invoked. - -See the :meth:`~pyramid.config.Configurator.add_view` method's ``http_cache`` -documentation for more information. - -Sessions -~~~~~~~~ - -Pyramid has built-in HTTP sessioning. This allows you to associate data with -otherwise anonymous users between requests. Lots of systems do this. But -Pyramid also allows you to plug in your own sessioning system by creating some -code that adheres to a documented interface. Currently there is a binding -package for the third-party Redis sessioning system that does exactly this. But -if you have a specialized need (perhaps you want to store your session data in -MongoDB), you can. You can even switch between implementations without -changing your application code. - -Example: :ref:`sessions_chapter`. - -Speed -~~~~~ - -The Pyramid core is, as far as we can tell, at least marginally faster than any -other existing Python web framework. It has been engineered from the ground up -for speed. It only does as much work as absolutely necessary when you ask it -to get a job done. Extraneous function calls and suboptimal algorithms in its -core codepaths are avoided. It is feasible to get, for example, between 3500 -and 4000 requests per second from a simple Pyramid view on commodity dual-core -laptop hardware and an appropriate WSGI server (mod_wsgi or gunicorn). In any -case, performance statistics are largely useless without requirements and -goals, but if you need speed, Pyramid will almost certainly never be your -application's bottleneck; at least no more than Python will be a bottleneck. - -Example: http://blog.curiasolutions.com/pages/the-great-web-framework-shootout.html - -Exception views -~~~~~~~~~~~~~~~ - -Exceptions happen. Rather than deal with exceptions that might present -themselves to a user in production in an ad-hoc way, Pyramid allows you to -register an :term:`exception view`. Exception views are like regular Pyramid -views, but they're only invoked when an exception "bubbles up" to Pyramid -itself. For example, you might register an exception view for the -:exc:`Exception` exception, which will catch *all* exceptions, and present a -pretty "well, this is embarrassing" page. Or you might choose to register an -exception view for only specific kinds of application-specific exceptions, such -as an exception that happens when a file is not found, or an exception that -happens when an action cannot be performed because the user doesn't have -permission to do something. In the former case, you can show a pretty "Not -Found" page; in the latter case you might show a login form. - -Example: :ref:`exception_views`. - -No singletons -~~~~~~~~~~~~~ - -Pyramid is written in such a way that it requires your application to have -exactly zero "singleton" data structures. Or put another way, Pyramid doesn't -require you to construct any "mutable globals". Or put even another different -way, an import of a Pyramid application needn't have any "import-time side -effects". This is esoteric-sounding, but if you've ever tried to cope with -parameterizing a Django ``settings.py`` file for multiple installations of the -same application, or if you've ever needed to monkey-patch some framework -fixture so that it behaves properly for your use case, or if you've ever wanted -to deploy your system using an asynchronous server, you'll end up appreciating -this feature. It just won't be a problem. You can even run multiple copies of -a similar but not identically configured Pyramid application within the same -Python process. This is good for shared hosting environments, where RAM is at -a premium. - -View predicates and many views per route -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Unlike many other systems, Pyramid allows you to associate more than one view -per route. For example, you can create a route with the pattern ``/items`` and -when the route is matched, you can shuffle off the request to one view if the -request method is GET, another view if the request method is POST, etc. A -system known as "view predicates" allows for this. Request method matching is -the most basic thing you can do with a view predicate. You can also associate -views with other request parameters, such as the elements in the query string, -the Accept header, whether the request is an XHR request or not, and lots of -other things. This feature allows you to keep your individual views clean. -They won't need much conditional logic, so they'll be easier to test. - -Example: :ref:`view_configuration_parameters`. - -Transaction management -~~~~~~~~~~~~~~~~~~~~~~ - -Pyramid's :term:`scaffold` system renders projects that include a *transaction -management* system, stolen from Zope. When you use this transaction management -system, you cease being responsible for committing your data anymore. Instead -Pyramid takes care of committing: it commits at the end of a request or aborts -if there's an exception. Why is that a good thing? Having a centralized place -for transaction management is a great thing. If, instead of managing your -transactions in a centralized place, you sprinkle ``session.commit`` calls in -your application logic itself, you can wind up in a bad place. Wherever you -manually commit data to your database, it's likely that some of your other code -is going to run *after* your commit. If that code goes on to do other important -things after that commit, and an error happens in the later code, you can -easily wind up with inconsistent data if you're not extremely careful. Some -data will have been written to the database that probably should not have. -Having a centralized commit point saves you from needing to think about this; -it's great for lazy people who also care about data integrity. Either the -request completes successfully, and all changes are committed, or it does not, -and all changes are aborted. - -Pyramid's transaction management system allows you to synchronize commits -between multiple databases. It also allows you to do things like conditionally -send email if a transaction commits, but otherwise keep quiet. - -Example: :ref:`bfg_sql_wiki_tutorial` (note the lack of commit statements -anywhere in application code). - -Configuration conflict detection -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -When a system is small, it's reasonably easy to keep it all in your head. But -when systems grow large, you may have hundreds or thousands of configuration -statements which add a view, add a route, and so forth. - -Pyramid's configuration system keeps track of your configuration statements. If -you accidentally add two that are identical, or Pyramid can't make sense out of -what it would mean to have both statements active at the same time, it will -complain loudly at startup time. It's not dumb though. It will automatically -resolve conflicting configuration statements on its own if you use the -configuration :meth:`~pyramid.config.Configurator.include` system. "More local" -statements are preferred over "less local" ones. This allows you to -intelligently factor large systems into smaller ones. - -Example: :ref:`conflict_detection`. - -Configuration extensibility -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Unlike other systems, Pyramid provides a structured "include" mechanism (see -:meth:`~pyramid.config.Configurator.include`) that allows you to combine -applications from multiple Python packages. All the configuration statements -that can be performed in your "main" Pyramid application can also be performed -by included packages, including the addition of views, routes, subscribers, and -even authentication and authorization policies. You can even extend or override -an existing application by including another application's configuration in -your own, overriding or adding new views and routes to it. This has the -potential to allow you to create a big application out of many other smaller -ones. For example, if you want to reuse an existing application that already -has a bunch of routes, you can just use the ``include`` statement with a -``route_prefix``. The new application will live within your application at an -URL prefix. It's not a big deal, and requires little up-front engineering -effort. - -For example: - -.. code-block:: python - :linenos: - - from pyramid.config import Configurator - - if __name__ == '__main__': - config = Configurator() - config.include('pyramid_jinja2') - config.include('pyramid_exclog') - config.include('some.other.guys.package', route_prefix='/someotherguy') - -.. seealso:: - - See also :ref:`including_configuration` and - :ref:`building_an_extensible_app`. - -Flexible authentication and authorization -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Pyramid includes a flexible, pluggable authentication and authorization system. -No matter where your user data is stored, or what scheme you'd like to use to -permit your users to access your data, you can use a predefined Pyramid -plugpoint to plug in your custom authentication and authorization code. If you -want to change these schemes later, you can just change it in one place rather -than everywhere in your code. It also ships with prebuilt well-tested -authentication and authorization schemes out of the box. But what if you don't -want to use Pyramid's built-in system? You don't have to. You can just write -your own bespoke security code as you would in any other system. - -Example: :ref:`enabling_authorization_policy`. - -Traversal -~~~~~~~~~ - -:term:`Traversal` is a concept stolen from :term:`Zope`. It allows you to -create a tree of resources, each of which can be addressed by one or more URLs. -Each of those resources can have one or more *views* associated with it. If -your data isn't naturally treelike, or you're unwilling to create a treelike -representation of your data, you aren't going to find traversal very useful. -However, traversal is absolutely fantastic for sites that need to be -arbitrarily extensible. It's a lot easier to add a node to a tree than it is to -shoehorn a route into an ordered list of other routes, or to create another -entire instance of an application to service a department and glue code to -allow disparate apps to share data. It's a great fit for sites that naturally -lend themselves to changing departmental hierarchies, such as content -management systems and document management systems. Traversal also lends -itself well to systems that require very granular security ("Bob can edit -*this* document" as opposed to "Bob can edit documents"). - -Examples: :ref:`hello_traversal_chapter` and -:ref:`much_ado_about_traversal_chapter`. - -Tweens -~~~~~~ - -Pyramid has a sort of internal WSGI-middleware-ish pipeline that can be hooked -by arbitrary add-ons named "tweens". The debug toolbar is a "tween", and the -``pyramid_tm`` transaction manager is also. Tweens are more useful than WSGI -:term:`middleware` in some circumstances because they run in the context of -Pyramid itself, meaning you have access to templates and other renderers, a -"real" request object, and other niceties. - -Example: :ref:`registering_tweens`. - -View response adapters -~~~~~~~~~~~~~~~~~~~~~~ - -A lot is made of the aesthetics of what *kinds* of objects you're allowed to -return from view callables in various frameworks. In a previous section in -this document, we showed you that, if you use a :term:`renderer`, you can -usually return a dictionary from a view callable instead of a full-on -:term:`Response` object. But some frameworks allow you to return strings or -tuples from view callables. When frameworks allow for this, code looks -slightly prettier, because fewer imports need to be done, and there is less -code. For example, compare this: - -.. code-block:: python - :linenos: - - def aview(request): - return "Hello world!" - -To this: - -.. code-block:: python - :linenos: - - from pyramid.response import Response - - def aview(request): - return Response("Hello world!") - -The former is "prettier", right? - -Out of the box, if you define the former view callable (the one that simply -returns a string) in Pyramid, when it is executed, Pyramid will raise an -exception. This is because "explicit is better than implicit", in most cases, -and by default Pyramid wants you to return a :term:`Response` object from a -view callable. This is because there's usually a heck of a lot more to a -response object than just its body. But if you're the kind of person who -values such aesthetics, we have an easy way to allow for this sort of thing: - -.. code-block:: python - :linenos: - - from pyramid.config import Configurator - from pyramid.response import Response - - def string_response_adapter(s): - response = Response(s) - response.content_type = 'text/html' - return response - - if __name__ == '__main__': - config = Configurator() - config.add_response_adapter(string_response_adapter, basestring) - -Do that once in your Pyramid application at startup. Now you can return -strings from any of your view callables, e.g.: - -.. code-block:: python - :linenos: - - def helloview(request): - return "Hello world!" - - def goodbyeview(request): - return "Goodbye world!" - -Oh noes! What if you want to indicate a custom content type? And a custom -status code? No fear: - -.. code-block:: python - :linenos: - - from pyramid.config import Configurator - - def tuple_response_adapter(val): - status_int, content_type, body = val - response = Response(body) - response.content_type = content_type - response.status_int = status_int - return response - - def string_response_adapter(body): - response = Response(body) - response.content_type = 'text/html' - response.status_int = 200 - return response +Use events to coordinate +~~~~~~~~~~~~~~~~~~~~~~~~ - if __name__ == '__main__': - config = Configurator() - config.add_response_adapter(string_response_adapter, basestring) - config.add_response_adapter(tuple_response_adapter, tuple) +When writing web applications, it is often important to have your code run at a +specific point in the lifecycle of a request. In Pyramid, you can accomplish +this using *subscribers* and *events*. -Once this is done, both of these view callables will work: +For example, you might have a job that needs to be done each time your +application handles a new request. Pyramid emits a ``NewRequest`` event at this +point in the request handling lifecycle. You can register your code as a +subscriber to this event using a clear, declarative style: .. code-block:: python - :linenos: - def aview(request): - return "Hello world!" - - def anotherview(request): - return (403, 'text/plain', "Forbidden") + from pyramid.events import NewRequest + from pyramid.events import subscriber -Pyramid defaults to explicit behavior, because it's the most generally useful, -but provides hooks that allow you to adapt the framework to localized aesthetic -desires. + @subscriber(NewRequest) + def my_job(event): + do_something(event.request) -.. seealso:: +Pyramid's event system can be extended as well. If you need, you can create +events of your own and send them using Pyramid's event system. Then anyone +working with your application can subscribe to your events and coordinate their +code with yours. - See also :ref:`using_iresponse`. - -"Global" response object -~~~~~~~~~~~~~~~~~~~~~~~~ - -"Constructing these response objects in my view callables is such a chore! And -I'm way too lazy to register a response adapter, as per the prior section," you -say. Fine. Be that way: - -.. code-block:: python - :linenos: - - def aview(request): - response = request.response - response.body = 'Hello world!' - response.content_type = 'text/plain' - return response - -.. seealso:: - - See also :ref:`request_response_attr`. +Example: :ref:`events_chapter` and :ref:`event_types`. -Automating repetitive configuration -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Build international applications +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Does Pyramid's configurator allow you to do something, but you're a little -adventurous and just want it a little less verbose? Or you'd like to offer up -some handy configuration feature to other Pyramid users without requiring that -we change Pyramid? You can extend Pyramid's :term:`Configurator` with your own -directives. For example, let's say you find yourself calling -:meth:`pyramid.config.Configurator.add_view` repetitively. Usually you can -take the boring away by using existing shortcuts, but let's say that this is a -case where there is no such shortcut: +Pyramid ships with features that allow you to write applications for +international audiences. You can mark text in your source files and templates +and build catalogs of messages to be translated. You can translate these +catalogs into other languages. Users may then indicate their preference, and +see your application in their language. -.. code-block:: python - :linenos: +Many systems which offer internationalization suffer from a common problem. A +message in your code may have the same text as one in some other package. +Messages can conflict with each-other, leading to translation errors. Pyramid +solves this problem by using translation *domains*. Each application can have +its own translation domain. Messages in one domain cannot conflict with +messages in another. Problem solved. - from pyramid.config import Configurator +Example: :ref:`i18n_chapter`. - config = Configurator() - config.add_route('xhr_route', '/xhr/{id}') - config.add_view('my.package.GET_view', route_name='xhr_route', - xhr=True, permission='view', request_method='GET') - config.add_view('my.package.POST_view', route_name='xhr_route', - xhr=True, permission='view', request_method='POST') - config.add_view('my.package.HEAD_view', route_name='xhr_route', - xhr=True, permission='view', request_method='HEAD') +Build efficient applications +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Pretty tedious right? You can add a directive to the Pyramid configurator to -automate some of the tedium away: +Views in dynamic web applications can be expensive or slow to build. Pyramid +allows you to save the results of such a view by *caching* the rendered +response. Indicate in configuration that you want a view to be cached:: -.. code-block:: python - :linenos: + @view_config(http_cache=3600) # 60 minutes + def myview(request): ... - from pyramid.config import Configurator +Pyramid will automatically add the appropriate ``Cache-Control`` and +``Expires`` headers to the response it creates. - def add_protected_xhr_views(config, module): - module = config.maybe_dotted(module) - for method in ('GET', 'POST', 'HEAD'): - view = getattr(module, 'xhr_%s_view' % method, None) - if view is not None: - config.add_view(view, route_name='xhr_route', xhr=True, - permission='view', request_method=method) +See the :meth:`~pyramid.config.Configurator.add_view` method's ``http_cache`` +documentation for more information. - config = Configurator() - config.add_directive('add_protected_xhr_views', add_protected_xhr_views) +Build fast applications +~~~~~~~~~~~~~~~~~~~~~~~ -Once that's done, you can call the directive you've just added as a method of -the Configurator object: +The Pyramid core is fast. It has been engineered from the ground up for speed. +It only does as much work as absolutely necessary when you ask it to get a job +done. If you need speed from your application, Pyramid is the right choice for +you. -.. code-block:: python - :linenos: +Example: http://blog.curiasolutions.com/pages/the-great-web-framework-shootout.html - config.add_route('xhr_route', '/xhr/{id}') - config.add_protected_xhr_views('my.package') +Store session data +~~~~~~~~~~~~~~~~~~ -Your previously repetitive configuration lines have now morphed into one line. +HTTP is a *stateless* protocol. No request can have knowledge of any other +request. But it is often desireable to associate data with a particular user. +Think of a shopping cart that remembers the items you have added to it even as +you move through the shopping site finding other items to add. -You can share your configuration code with others this way, too, by packaging -it up and calling :meth:`~pyramid.config.Configurator.add_directive` from -within a function called when another user uses the -:meth:`~pyramid.config.Configurator.include` method against your code. +Pyramid allows you to use *sessions* to solve this problem. Many other +frameworks also support sessions. But Pyramid allows you to plug in your own +custom sessioning system. So long as your system conforms to a documented +interface, you can drop it in in place of the provided system. -.. seealso:: +Currently there is a binding package for the third-party Redis sessioning +system that does exactly this. But if you have a specialized need (perhaps you +want to store your session data in MongoDB), you can. You can even switch +between implementations without changing your application code. - See also :ref:`add_directive`. +Example: :ref:`sessions_chapter`. -Programmatic introspection +Handle problems with grace ~~~~~~~~~~~~~~~~~~~~~~~~~~ -If you're building a large system that other users may plug code into, it's -useful to be able to get an enumeration of what code they plugged in *at -application runtime*. For example, you might want to show them a set of tabs -at the top of the screen based on an enumeration of views they registered. +Mistakes happen. Problems crop up. No-one writes bug-free code. Pyramid +provides a way to handle the exceptions your code encounters. An +:term:`exception view` is a special kind of view which is automatically called +when an particular exception type "bubbles up" without being handled by your +application. -This is possible using Pyramid's :term:`introspector`. +For example, you might register an exception view for the :exc:`Exception` +exception type, which will catch *all* exceptions, and present a pretty "well, +this is embarrassing" page. Or you might choose to register an exception view +for only certain application-specific exceptions. You can make a one for when a +file is not found, or when the user doesn't have permission to do something. In +the former case, you can show a pretty "Not Found" page; in the latter case you +might show a login form. -Here's an example of using Pyramid's introspector from within a view callable: +Example: :ref:`exception_views`. -.. code-block:: python - :linenos: +And much, much more... +~~~~~~~~~~~~~~~~~~~~~~ - from pyramid.view import view_config - from pyramid.response import Response +Pyramid has been built with a number of other sophisticated design features +that make it adaptable. Read more about them below. - @view_config(route_name='bar') - def show_current_route_pattern(request): - introspector = request.registry.introspector - route_name = request.matched_route.name - route_intr = introspector.get('routes', route_name) - return Response(str(route_intr['pattern'])) +.. toctree:: + :maxdepth: 2 -.. seealso:: + advfeatures - See also :ref:`using_introspection`. -Python 3 compatibility -~~~~~~~~~~~~~~~~~~~~~~ -Pyramid and most of its add-ons are Python 3 compatible. If you develop a -Pyramid application today, you won't need to worry that five years from now -you'll be backwatered because there are language features you'd like to use but -your framework doesn't support newer Python versions. - -Testing -~~~~~~~ - -Every release of Pyramid has 100% statement coverage via unit and integration -tests, as measured by the ``coverage`` tool available on PyPI. It also has -greater than 95% decision/condition coverage as measured by the -``instrumental`` tool available on PyPI. It is automatically tested by Travis, -and Jenkins on Python 2.7, Python 3.4, Python 3.5, and PyPy -after each commit to its GitHub repository. Official Pyramid add-ons are held -to a similar testing standard. We still find bugs in Pyramid and its official -add-ons, but we've noticed we find a lot more of them while working on other -projects that don't have a good testing regime. - -Travis: https://travis-ci.org/Pylons/pyramid -Jenkins: http://jenkins.pylonsproject.org/job/pyramid/ - -Support -~~~~~~~ - -It's our goal that no Pyramid question go unanswered. Whether you ask a -question on IRC, on the Pylons-discuss mailing list, or on StackOverflow, -you're likely to get a reasonably prompt response. We don't tolerate "support -trolls" or other people who seem to get their rocks off by berating fellow -users in our various official support channels. We try to keep it well-lit and -new-user-friendly. -Example: Visit irc\://freenode.net#pyramid (the ``#pyramid`` channel on -irc.freenode.net in an IRC client) or the pylons-discuss maillist at -https://groups.google.com/forum/#!forum/pylons-discuss. - -Documentation -~~~~~~~~~~~~~ - -It's a constant struggle, but we try to maintain a balance between completeness -and new-user-friendliness in the official narrative Pyramid documentation -(concrete suggestions for improvement are always appreciated, by the way). We -also maintain a "cookbook" of recipes, which are usually demonstrations of -common integration scenarios too specific to add to the official narrative -docs. In any case, the Pyramid documentation is comprehensive. - -Example: The :ref:`Pyramid Community Cookbook `. .. index:: single: Pylons Project @@ -907,6 +558,21 @@ The concept of :term:`view` is used by :app:`Pyramid` mostly as it would be by Django. :app:`Pyramid` has a documentation culture more like Django's than like Zope's. + +.. sidebar:: You Say :app:`Pyramid` is MVC, but Where's the Controller? + + The :app:`Pyramid` authors believe that the MVC pattern just doesn't really + fit the web very well. In a :app:`Pyramid` application, there is a resource + tree which represents the site structure, and views which tend to present + the data stored in the resource tree and a user-defined "domain model". + However, no facility provided *by the framework* actually necessarily maps + to the concept of a "controller" or "model". So if you had to give it some + acronym, I guess you'd say :app:`Pyramid` is actually an "RV" framework + rather than an "MVC" framework. "MVC", however, is close enough as a + general classification moniker for purposes of comparison with other web + frameworks. + + Like :term:`Pylons` version 1.0, but unlike :term:`Zope`, a :app:`Pyramid` application developer may use completely imperative code to perform common framework configuration tasks such as adding a view or a route. In Zope, @@ -931,16 +597,3 @@ frameworks named `model-view-controller `_ frameworks. Insofar as this term has been claimed to represent a class of web frameworks, :app:`Pyramid` also generally fits into this class. - -.. sidebar:: You Say :app:`Pyramid` is MVC, but Where's the Controller? - - The :app:`Pyramid` authors believe that the MVC pattern just doesn't really - fit the web very well. In a :app:`Pyramid` application, there is a resource - tree which represents the site structure, and views which tend to present - the data stored in the resource tree and a user-defined "domain model". - However, no facility provided *by the framework* actually necessarily maps - to the concept of a "controller" or "model". So if you had to give it some - acronym, I guess you'd say :app:`Pyramid` is actually an "RV" framework - rather than an "MVC" framework. "MVC", however, is close enough as a - general classification moniker for purposes of comparison with other web - frameworks. -- cgit v1.2.3 From 26c90db176cb109bbb5b1fb094827be2df273dae Mon Sep 17 00:00:00 2001 From: cewing Date: Mon, 22 May 2017 12:10:51 -0700 Subject: move more esoteric framework features into a separate file to remove complexity from the intro doc. --- docs/narr/advfeatures.rst | 393 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 393 insertions(+) create mode 100644 docs/narr/advfeatures.rst diff --git a/docs/narr/advfeatures.rst b/docs/narr/advfeatures.rst new file mode 100644 index 000000000..4365b1855 --- /dev/null +++ b/docs/narr/advfeatures.rst @@ -0,0 +1,393 @@ +Advanced :app:`Pyramid` Design Features +======================================= + +Pyramid has been built from the ground up to avoid the problems that other +frameworks can suffer. + + +No singletons +~~~~~~~~~~~~~ + +Pyramid is written in such a way that it requires your application to have +exactly zero "singleton" data structures. Or put another way, Pyramid doesn't +require you to construct any "mutable globals". Or put even another different +way, an import of a Pyramid application needn't have any "import-time side +effects". This is esoteric-sounding, but if you've ever tried to cope with +parameterizing a Django ``settings.py`` file for multiple installations of the +same application, or if you've ever needed to monkey-patch some framework +fixture so that it behaves properly for your use case, or if you've ever wanted +to deploy your system using an asynchronous server, you'll end up appreciating +this feature. It just won't be a problem. You can even run multiple copies of +a similar but not identically configured Pyramid application within the same +Python process. This is good for shared hosting environments, where RAM is at +a premium. + +View predicates and many views per route +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Unlike many other systems, Pyramid allows you to associate more than one view +per route. For example, you can create a route with the pattern ``/items`` and +when the route is matched, you can shuffle off the request to one view if the +request method is GET, another view if the request method is POST, etc. A +system known as "view predicates" allows for this. Request method matching is +the most basic thing you can do with a view predicate. You can also associate +views with other request parameters, such as the elements in the query string, +the Accept header, whether the request is an XHR request or not, and lots of +other things. This feature allows you to keep your individual views clean. +They won't need much conditional logic, so they'll be easier to test. + +Example: :ref:`view_configuration_parameters`. + +Transaction management +~~~~~~~~~~~~~~~~~~~~~~ + +Pyramid's :term:`scaffold` system renders projects that include a *transaction +management* system, stolen from Zope. When you use this transaction management +system, you cease being responsible for committing your data anymore. Instead +Pyramid takes care of committing: it commits at the end of a request or aborts +if there's an exception. Why is that a good thing? Having a centralized place +for transaction management is a great thing. If, instead of managing your +transactions in a centralized place, you sprinkle ``session.commit`` calls in +your application logic itself, you can wind up in a bad place. Wherever you +manually commit data to your database, it's likely that some of your other code +is going to run *after* your commit. If that code goes on to do other important +things after that commit, and an error happens in the later code, you can +easily wind up with inconsistent data if you're not extremely careful. Some +data will have been written to the database that probably should not have. +Having a centralized commit point saves you from needing to think about this; +it's great for lazy people who also care about data integrity. Either the +request completes successfully, and all changes are committed, or it does not, +and all changes are aborted. + +Pyramid's transaction management system allows you to synchronize commits +between multiple databases. It also allows you to do things like conditionally +send email if a transaction commits, but otherwise keep quiet. + +Example: :ref:`bfg_sql_wiki_tutorial` (note the lack of commit statements +anywhere in application code). + +Configuration conflict detection +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When a system is small, it's reasonably easy to keep it all in your head. But +when systems grow large, you may have hundreds or thousands of configuration +statements which add a view, add a route, and so forth. + +Pyramid's configuration system keeps track of your configuration statements. If +you accidentally add two that are identical, or Pyramid can't make sense out of +what it would mean to have both statements active at the same time, it will +complain loudly at startup time. It's not dumb though. It will automatically +resolve conflicting configuration statements on its own if you use the +configuration :meth:`~pyramid.config.Configurator.include` system. "More local" +statements are preferred over "less local" ones. This allows you to +intelligently factor large systems into smaller ones. + +Example: :ref:`conflict_detection`. + +Configuration extensibility +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Unlike other systems, Pyramid provides a structured "include" mechanism (see +:meth:`~pyramid.config.Configurator.include`) that allows you to combine +applications from multiple Python packages. All the configuration statements +that can be performed in your "main" Pyramid application can also be performed +by included packages, including the addition of views, routes, subscribers, and +even authentication and authorization policies. You can even extend or override +an existing application by including another application's configuration in +your own, overriding or adding new views and routes to it. This has the +potential to allow you to create a big application out of many other smaller +ones. For example, if you want to reuse an existing application that already +has a bunch of routes, you can just use the ``include`` statement with a +``route_prefix``. The new application will live within your application at an +URL prefix. It's not a big deal, and requires little up-front engineering +effort. + +For example: + +.. code-block:: python + :linenos: + + from pyramid.config import Configurator + + if __name__ == '__main__': + config = Configurator() + config.include('pyramid_jinja2') + config.include('pyramid_exclog') + config.include('some.other.package', route_prefix='/somethingelse') + +.. seealso:: + + See also :ref:`including_configuration` and + :ref:`building_an_extensible_app`. + +Flexible authentication and authorization +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Pyramid includes a flexible, pluggable authentication and authorization system. +No matter where your user data is stored, or what scheme you'd like to use to +permit your users to access your data, you can use a predefined Pyramid +plugpoint to plug in your custom authentication and authorization code. If you +want to change these schemes later, you can just change it in one place rather +than everywhere in your code. It also ships with prebuilt well-tested +authentication and authorization schemes out of the box. But what if you don't +want to use Pyramid's built-in system? You don't have to. You can just write +your own bespoke security code as you would in any other system. + +Example: :ref:`enabling_authorization_policy`. + +Traversal +~~~~~~~~~ + +:term:`Traversal` is a concept stolen from :term:`Zope`. It allows you to +create a tree of resources, each of which can be addressed by one or more URLs. +Each of those resources can have one or more *views* associated with it. If +your data isn't naturally treelike, or you're unwilling to create a treelike +representation of your data, you aren't going to find traversal very useful. +However, traversal is absolutely fantastic for sites that need to be +arbitrarily extensible. It's a lot easier to add a node to a tree than it is to +shoehorn a route into an ordered list of other routes, or to create another +entire instance of an application to service a department and glue code to +allow disparate apps to share data. It's a great fit for sites that naturally +lend themselves to changing departmental hierarchies, such as content +management systems and document management systems. Traversal also lends +itself well to systems that require very granular security ("Bob can edit +*this* document" as opposed to "Bob can edit documents"). + +Examples: :ref:`hello_traversal_chapter` and +:ref:`much_ado_about_traversal_chapter`. + +Tweens +~~~~~~ + +Pyramid has a sort of internal WSGI-middleware-ish pipeline that can be hooked +by arbitrary add-ons named "tweens". The debug toolbar is a "tween", and the +``pyramid_tm`` transaction manager is also. Tweens are more useful than WSGI +:term:`middleware` in some circumstances because they run in the context of +Pyramid itself, meaning you have access to templates and other renderers, a +"real" request object, and other niceties. + +Example: :ref:`registering_tweens`. + +View response adapters +~~~~~~~~~~~~~~~~~~~~~~ + +A lot is made of the aesthetics of what *kinds* of objects you're allowed to +return from view callables in various frameworks. In a previous section in +this document, we showed you that, if you use a :term:`renderer`, you can +usually return a dictionary from a view callable instead of a full-on +:term:`Response` object. But some frameworks allow you to return strings or +tuples from view callables. When frameworks allow for this, code looks +slightly prettier, because fewer imports need to be done, and there is less +code. For example, compare this: + +.. code-block:: python + :linenos: + + def aview(request): + return "Hello world!" + +To this: + +.. code-block:: python + :linenos: + + from pyramid.response import Response + + def aview(request): + return Response("Hello world!") + +The former is "prettier", right? + +Out of the box, if you define the former view callable (the one that simply +returns a string) in Pyramid, when it is executed, Pyramid will raise an +exception. This is because "explicit is better than implicit", in most cases, +and by default Pyramid wants you to return a :term:`Response` object from a +view callable. This is because there's usually a heck of a lot more to a +response object than just its body. But if you're the kind of person who +values such aesthetics, we have an easy way to allow for this sort of thing: + +.. code-block:: python + :linenos: + + from pyramid.config import Configurator + from pyramid.response import Response + + def string_response_adapter(s): + response = Response(s) + response.content_type = 'text/html' + return response + + if __name__ == '__main__': + config = Configurator() + config.add_response_adapter(string_response_adapter, basestring) + +Do that once in your Pyramid application at startup. Now you can return +strings from any of your view callables, e.g.: + +.. code-block:: python + :linenos: + + def helloview(request): + return "Hello world!" + + def goodbyeview(request): + return "Goodbye world!" + +Oh noes! What if you want to indicate a custom content type? And a custom +status code? No fear: + +.. code-block:: python + :linenos: + + from pyramid.config import Configurator + + def tuple_response_adapter(val): + status_int, content_type, body = val + response = Response(body) + response.content_type = content_type + response.status_int = status_int + return response + + def string_response_adapter(body): + response = Response(body) + response.content_type = 'text/html' + response.status_int = 200 + return response + + if __name__ == '__main__': + config = Configurator() + config.add_response_adapter(string_response_adapter, basestring) + config.add_response_adapter(tuple_response_adapter, tuple) + +Once this is done, both of these view callables will work: + +.. code-block:: python + :linenos: + + def aview(request): + return "Hello world!" + + def anotherview(request): + return (403, 'text/plain', "Forbidden") + +Pyramid defaults to explicit behavior, because it's the most generally useful, +but provides hooks that allow you to adapt the framework to localized aesthetic +desires. + +.. seealso:: + + See also :ref:`using_iresponse`. + +"Global" response object +~~~~~~~~~~~~~~~~~~~~~~~~ + +"Constructing these response objects in my view callables is such a chore! And +I'm way too lazy to register a response adapter, as per the prior section," you +say. Fine. Be that way: + +.. code-block:: python + :linenos: + + def aview(request): + response = request.response + response.body = 'Hello world!' + response.content_type = 'text/plain' + return response + +.. seealso:: + + See also :ref:`request_response_attr`. + +Automating repetitive configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Does Pyramid's configurator allow you to do something, but you're a little +adventurous and just want it a little less verbose? Or you'd like to offer up +some handy configuration feature to other Pyramid users without requiring that +we change Pyramid? You can extend Pyramid's :term:`Configurator` with your own +directives. For example, let's say you find yourself calling +:meth:`pyramid.config.Configurator.add_view` repetitively. Usually you can +take the boring away by using existing shortcuts, but let's say that this is a +case where there is no such shortcut: + +.. code-block:: python + :linenos: + + from pyramid.config import Configurator + + config = Configurator() + config.add_route('xhr_route', '/xhr/{id}') + config.add_view('my.package.GET_view', route_name='xhr_route', + xhr=True, permission='view', request_method='GET') + config.add_view('my.package.POST_view', route_name='xhr_route', + xhr=True, permission='view', request_method='POST') + config.add_view('my.package.HEAD_view', route_name='xhr_route', + xhr=True, permission='view', request_method='HEAD') + +Pretty tedious right? You can add a directive to the Pyramid configurator to +automate some of the tedium away: + +.. code-block:: python + :linenos: + + from pyramid.config import Configurator + + def add_protected_xhr_views(config, module): + module = config.maybe_dotted(module) + for method in ('GET', 'POST', 'HEAD'): + view = getattr(module, 'xhr_%s_view' % method, None) + if view is not None: + config.add_view(view, route_name='xhr_route', xhr=True, + permission='view', request_method=method) + + config = Configurator() + config.add_directive('add_protected_xhr_views', add_protected_xhr_views) + +Once that's done, you can call the directive you've just added as a method of +the Configurator object: + +.. code-block:: python + :linenos: + + config.add_route('xhr_route', '/xhr/{id}') + config.add_protected_xhr_views('my.package') + +Your previously repetitive configuration lines have now morphed into one line. + +You can share your configuration code with others this way, too, by packaging +it up and calling :meth:`~pyramid.config.Configurator.add_directive` from +within a function called when another user uses the +:meth:`~pyramid.config.Configurator.include` method against your code. + +.. seealso:: + + See also :ref:`add_directive`. + +Programmatic introspection +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you're building a large system that other users may plug code into, it's +useful to be able to get an enumeration of what code they plugged in *at +application runtime*. For example, you might want to show them a set of tabs +at the top of the screen based on an enumeration of views they registered. + +This is possible using Pyramid's :term:`introspector`. + +Here's an example of using Pyramid's introspector from within a view callable: + +.. code-block:: python + :linenos: + + from pyramid.view import view_config + from pyramid.response import Response + + @view_config(route_name='bar') + def show_current_route_pattern(request): + introspector = request.registry.introspector + route_name = request.matched_route.name + route_intr = introspector.get('routes', route_name) + return Response(str(route_intr['pattern'])) + +.. seealso:: + + See also :ref:`using_introspection`. \ No newline at end of file -- cgit v1.2.3 From f47d226dc4c390a476f2b1fabe8e56d458c971ae Mon Sep 17 00:00:00 2001 From: cewing Date: Mon, 22 May 2017 14:02:08 -0700 Subject: simplify the section comparing pyramid with other web frameworks also remove the sidebar about MVC, in favor of a simpler statement of belief in the MVC paragraph. --- docs/narr/introduction.rst | 76 ++++++++++++---------------------------------- 1 file changed, 19 insertions(+), 57 deletions(-) diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst index 5eda0fcf4..63bc164fb 100644 --- a/docs/narr/introduction.rst +++ b/docs/narr/introduction.rst @@ -517,7 +517,7 @@ includes details about how :app:`Pyramid` relates to the Pylons Project. single: MVC :app:`Pyramid` and Other Web Frameworks ------------------------------------------- +--------------------------------------- The first release of Pyramid's predecessor (named :mod:`repoze.bfg`) was made in July of 2008. At the end of 2010, we changed the name of :mod:`repoze.bfg` @@ -528,63 +528,25 @@ November of that year. :term:`Django`. As a result, :app:`Pyramid` borrows several concepts and features from each, combining them into a unique web framework. -Many features of :app:`Pyramid` trace their origins back to :term:`Zope`. Like -Zope applications, :app:`Pyramid` applications can be easily extended. If you -obey certain constraints, the application you produce can be reused, modified, -re-integrated, or extended by third-party developers without forking the -original application. The concepts of :term:`traversal` and declarative -security in :app:`Pyramid` were pioneered first in Zope. - -The :app:`Pyramid` concept of :term:`URL dispatch` is inspired by the -:term:`Routes` system used by :term:`Pylons` version 1.0. Like Pylons version -1.0, :app:`Pyramid` is mostly policy-free. It makes no assertions about which -database you should use. Pyramid no longer has built-in templating facilities -as of version 1.5a2, but instead officially supports bindings for templating -languages, including Chameleon, Jinja2, and Mako. In essence, it only supplies -a mechanism to map URLs to :term:`view` code, along with a set of conventions -for calling those views. You are free to use third-party components that fit -your needs in your applications. - -The concept of :term:`view` is used by :app:`Pyramid` mostly as it would be by -Django. :app:`Pyramid` has a documentation culture more like Django's than -like Zope's. - - -.. sidebar:: You Say :app:`Pyramid` is MVC, but Where's the Controller? - - The :app:`Pyramid` authors believe that the MVC pattern just doesn't really - fit the web very well. In a :app:`Pyramid` application, there is a resource - tree which represents the site structure, and views which tend to present - the data stored in the resource tree and a user-defined "domain model". - However, no facility provided *by the framework* actually necessarily maps - to the concept of a "controller" or "model". So if you had to give it some - acronym, I guess you'd say :app:`Pyramid` is actually an "RV" framework - rather than an "MVC" framework. "MVC", however, is close enough as a - general classification moniker for purposes of comparison with other web - frameworks. - - -Like :term:`Pylons` version 1.0, but unlike :term:`Zope`, a :app:`Pyramid` -application developer may use completely imperative code to perform common -framework configuration tasks such as adding a view or a route. In Zope, -:term:`ZCML` is typically required for similar purposes. In :term:`Grok`, a -Zope-based web framework, :term:`decorator` objects and class-level -declarations are used for this purpose. Out of the box, Pyramid supports -imperative and decorator-based configuration. :term:`ZCML` may be used via an -add-on package named ``pyramid_zcml``. - -Also unlike :term:`Zope` and other "full-stack" frameworks such as -:term:`Django`, :app:`Pyramid` makes no assumptions about which persistence -mechanisms you should use to build an application. Zope applications are -typically reliant on :term:`ZODB`. :app:`Pyramid` allows you to build -:term:`ZODB` applications, but it has no reliance on the ZODB software. -Likewise, :term:`Django` tends to assume that you want to store your -application's data in a relational database. :app:`Pyramid` makes no such -assumption, allowing you to use a relational database, and neither encouraging -nor discouraging the decision. +Similar to :term:`Zope`, :app:`Pyramid` applications may easily be extended. If +you work within the constraints of the framework, you can produce applications +that can be reused, modified or extended without needing to modify the original +application code. :app:`Pyramid` also inherits the concepts of :term:`traversal` +and declarative security from Zope. + +Similar to :term:`Pylons` version 1.0, :app:`Pyramid` is largely free of +policy. It makes no assertions about which database or template system you +should use. You are free to use whatever third-party components fit the needs +of your specific application. :app:`Pyramid` also inherits its approach to +:term:`URL dispatch` from Pylons. + +Similar to :term:`Django`, :app:`Pyramid` values extensive documentation. In +addition, the concept of a :term:`view` is used by :app:`Pyramid` much as it +would be by Django. Other Python web frameworks advertise themselves as members of a class of web frameworks named `model-view-controller `_ -frameworks. Insofar as this term has been claimed to represent a class of web -frameworks, :app:`Pyramid` also generally fits into this class. +frameworks. The authors of :app:`Pyramid` do not believe that the MVC pattern +fits the web particularly well. However, if this abstraction works for you, +:app:`Pyramid` also generally fits into this class. -- cgit v1.2.3 From aafaf73655c11ca1d6645d85e1754be6996540dd Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Mon, 22 May 2017 14:38:53 -0700 Subject: Add test for closest predicate error message --- pyramid/tests/test_config/test_util.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pyramid/tests/test_config/test_util.py b/pyramid/tests/test_config/test_util.py index 398b6fba8..bb86a1f56 100644 --- a/pyramid/tests/test_config/test_util.py +++ b/pyramid/tests/test_config/test_util.py @@ -365,6 +365,16 @@ class TestPredicateList(unittest.TestCase): from pyramid.exceptions import ConfigurationError self.assertRaises(ConfigurationError, self._callFUT, unknown=1) + def test_predicate_close_matches(self): + from pyramid.exceptions import ConfigurationError + with self.assertRaises(ConfigurationError) as context: + self._callFUT(method='GET') + expected_msg = ( + "Unknown predicate values: {'method': 'GET'} " + "(did you mean request_method)" + ) + self.assertEqual(context.exception.args[0], expected_msg) + def test_notted(self): from pyramid.config import not_ from pyramid.testing import DummyRequest -- cgit v1.2.3 From 08422ee6340cbcd225dcfc26c7c0aa3165449a58 Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Mon, 22 May 2017 14:40:44 -0700 Subject: Fix #1603, add closest predicate name in error message --- pyramid/config/util.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/pyramid/config/util.py b/pyramid/config/util.py index 67bba9593..d09d37d94 100644 --- a/pyramid/config/util.py +++ b/pyramid/config/util.py @@ -1,3 +1,4 @@ +from difflib import get_close_matches from hashlib import md5 import inspect @@ -36,7 +37,7 @@ class not_(object): config.add_view( 'mypackage.views.my_view', - route_name='ok', + route_name='ok', request_method=not_('POST') ) @@ -69,7 +70,7 @@ class Notted(object): # if the underlying predicate doesnt return a value, it's not really # a predicate, it's just something pretending to be a predicate, # so dont update the hash - if val: + if val: val = '!' + val return val @@ -90,7 +91,7 @@ class Notted(object): # over = before class PredicateList(object): - + def __init__(self): self.sorter = TopologicalSorter() self.last_added = None @@ -152,7 +153,15 @@ class PredicateList(object): weights.append(1 << n + 1) preds.append(pred) if kw: - raise ConfigurationError('Unknown predicate values: %r' % (kw,)) + closest = [] + names = [ name for name, _ in ordered ] + for name in kw: + closest.extend(get_close_matches(name, names, 3)) + + raise ConfigurationError( + 'Unknown predicate values: %r (did you mean %s)' + % (kw, ','.join(closest)) + ) # A "order" is computed for the predicate list. An order is # a scoring. # -- cgit v1.2.3 From 96dd805d55c5576778580ff52e978ff8aebc3a04 Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Mon, 22 May 2017 14:54:46 -0700 Subject: =?UTF-8?q?Load=20difflib=20on-demand=20so=20that=20it=20won?= =?UTF-8?q?=E2=80=99t=20take=20message=20proactively?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyramid/config/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyramid/config/util.py b/pyramid/config/util.py index d09d37d94..63f06ff9b 100644 --- a/pyramid/config/util.py +++ b/pyramid/config/util.py @@ -1,4 +1,3 @@ -from difflib import get_close_matches from hashlib import md5 import inspect @@ -153,6 +152,7 @@ class PredicateList(object): weights.append(1 << n + 1) preds.append(pred) if kw: + from difflib import get_close_matches closest = [] names = [ name for name, _ in ordered ] for name in kw: -- cgit v1.2.3 From 37d887705400e0c7631ec26afb972ebe9bfe35d5 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 22 May 2017 15:13:18 -0700 Subject: add changelog for #3054 --- CHANGES.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 51a1e457d..1a7cfaf0b 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,10 @@ +unreleased +========== + +- Add an informative error message when unknown predicates are supplied. The + new message suggests alternatives based on the list of known predicates. + See https://github.com/Pylons/pyramid/pull/3054 + 1.9a2 (2017-05-09) ================== -- cgit v1.2.3 From 3b340c9922f9bc1ea42e729c086c946b4cdfaf83 Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Mon, 22 May 2017 16:11:48 -0700 Subject: Fix #2548, add SRI has for script tags --- docs/narr/myproject/myproject/templates/layout.jinja2 | 8 ++++---- pyramid/scaffolds/alchemy/+package+/templates/layout.jinja2_tmpl | 8 ++++---- pyramid/scaffolds/starter/+package+/templates/layout.jinja2_tmpl | 8 ++++---- pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl | 8 ++++---- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/narr/myproject/myproject/templates/layout.jinja2 b/docs/narr/myproject/myproject/templates/layout.jinja2 index bfac9e64e..820758fea 100644 --- a/docs/narr/myproject/myproject/templates/layout.jinja2 +++ b/docs/narr/myproject/myproject/templates/layout.jinja2 @@ -18,8 +18,8 @@ @@ -58,7 +58,7 @@ - - + + diff --git a/pyramid/scaffolds/alchemy/+package+/templates/layout.jinja2_tmpl b/pyramid/scaffolds/alchemy/+package+/templates/layout.jinja2_tmpl index b5cfdc94d..6f6946ba2 100644 --- a/pyramid/scaffolds/alchemy/+package+/templates/layout.jinja2_tmpl +++ b/pyramid/scaffolds/alchemy/+package+/templates/layout.jinja2_tmpl @@ -18,8 +18,8 @@ @@ -60,7 +60,7 @@ - - + + diff --git a/pyramid/scaffolds/starter/+package+/templates/layout.jinja2_tmpl b/pyramid/scaffolds/starter/+package+/templates/layout.jinja2_tmpl index a784c741b..f3c27e3f0 100644 --- a/pyramid/scaffolds/starter/+package+/templates/layout.jinja2_tmpl +++ b/pyramid/scaffolds/starter/+package+/templates/layout.jinja2_tmpl @@ -18,8 +18,8 @@ @@ -60,7 +60,7 @@ - - + + diff --git a/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl b/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl index 72b480249..4b24ee2df 100644 --- a/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl +++ b/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl @@ -18,8 +18,8 @@ @@ -61,7 +61,7 @@ - - + + -- cgit v1.2.3 From 52ff50f75d265a4c7d04db538c26b61ab05677c6 Mon Sep 17 00:00:00 2001 From: cewing Date: Mon, 22 May 2017 16:38:53 -0700 Subject: Updating wording in the advanced features doc to make it more accessible --- docs/narr/advfeatures.rst | 352 +++++++++++++++++++++++----------------------- 1 file changed, 179 insertions(+), 173 deletions(-) diff --git a/docs/narr/advfeatures.rst b/docs/narr/advfeatures.rst index 4365b1855..5c45cf61a 100644 --- a/docs/narr/advfeatures.rst +++ b/docs/narr/advfeatures.rst @@ -5,104 +5,123 @@ Pyramid has been built from the ground up to avoid the problems that other frameworks can suffer. -No singletons -~~~~~~~~~~~~~ - -Pyramid is written in such a way that it requires your application to have -exactly zero "singleton" data structures. Or put another way, Pyramid doesn't -require you to construct any "mutable globals". Or put even another different -way, an import of a Pyramid application needn't have any "import-time side -effects". This is esoteric-sounding, but if you've ever tried to cope with -parameterizing a Django ``settings.py`` file for multiple installations of the -same application, or if you've ever needed to monkey-patch some framework -fixture so that it behaves properly for your use case, or if you've ever wanted -to deploy your system using an asynchronous server, you'll end up appreciating -this feature. It just won't be a problem. You can even run multiple copies of -a similar but not identically configured Pyramid application within the same -Python process. This is good for shared hosting environments, where RAM is at -a premium. - -View predicates and many views per route -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +You Don't Need Singletons +------------------------- + +Have you ever struggled with parametrizing Django's ``settings.py`` file for +multiple installations of the same Django application? Have you ever needed to +monkey-patch a framework fixture to get it to behave properly for your +use-case? Have you ever tried to deploy your application using an asynchronous +server and failed? + +All these problems are symptoms of "mutable global state", also known as +"import-time side effects" and arise from the use of "singleton" data structures. + +:app:`Pyramid` is written so that you don't run into these types of problems. +It is even possible to run multiple copies of the *same* :app:`Pyramid` +application configured differently within a single Python process. This makes +running Pyramid in shared hosting environments a snap. + +Simplify your View Code with Predicates +--------------------------------------- + +How many times have you found yourself beginning the logic of your view code +with something like this:: + + if request.user.is_authenticated: + # do one thing + else: + # do something else Unlike many other systems, Pyramid allows you to associate more than one view -per route. For example, you can create a route with the pattern ``/items`` and -when the route is matched, you can shuffle off the request to one view if the -request method is GET, another view if the request method is POST, etc. A -system known as "view predicates" allows for this. Request method matching is -the most basic thing you can do with a view predicate. You can also associate -views with other request parameters, such as the elements in the query string, -the Accept header, whether the request is an XHR request or not, and lots of -other things. This feature allows you to keep your individual views clean. -They won't need much conditional logic, so they'll be easier to test. +with a single route. For example, you can create a route with the pattern +``/items`` and when the route is matched, you can send the request to one view +if the request method is GET, another view if the request method is POST, and +so on. + +:app:`Pyramid` uses a system of "view predicates" to allow this. Matching the +request method is one basic thing you can do with a view predicate. You can +also associate views with other request parameters, such as elements in the +query string, the Accept header, whether the request is an XHR request or not, +and lots of other things. + +For our example above, you can do this instead:: + + @view_config(route_name="items", effective_principals=pyramid.security.Authenticated) + def auth_view(request): + # do one thing + + @view_config(route_name="items") + def anon_view(request): + # do something else + +This approach allows you to develop view code that is simpler, more easily +understandable, and more directly testable. Example: :ref:`view_configuration_parameters`. -Transaction management -~~~~~~~~~~~~~~~~~~~~~~ +Stop Worrying About Transactions +-------------------------------- Pyramid's :term:`scaffold` system renders projects that include a *transaction -management* system, stolen from Zope. When you use this transaction management -system, you cease being responsible for committing your data anymore. Instead -Pyramid takes care of committing: it commits at the end of a request or aborts -if there's an exception. Why is that a good thing? Having a centralized place -for transaction management is a great thing. If, instead of managing your -transactions in a centralized place, you sprinkle ``session.commit`` calls in -your application logic itself, you can wind up in a bad place. Wherever you -manually commit data to your database, it's likely that some of your other code -is going to run *after* your commit. If that code goes on to do other important -things after that commit, and an error happens in the later code, you can -easily wind up with inconsistent data if you're not extremely careful. Some -data will have been written to the database that probably should not have. -Having a centralized commit point saves you from needing to think about this; -it's great for lazy people who also care about data integrity. Either the -request completes successfully, and all changes are committed, or it does not, -and all changes are aborted. - -Pyramid's transaction management system allows you to synchronize commits -between multiple databases. It also allows you to do things like conditionally -send email if a transaction commits, but otherwise keep quiet. +management* system. When you use this system, you can stop worrying about when +to commit your changes, :app:`Pyramid` handles it for you. The system will +commit at the end of a request or aborts if there was an exception. + +Why is that a good thing? Imagine a situation where you manually commit a +change to your persistence layer. It's very likely that other framework code +will run *after* your changes are done. If an error happens in that other code, +you can easily wind up with inconsistent data if you're not extremely careful. + +Using transaction management saves you from needing to think about this. Either +a request completes successfully, and all changes are committed, or it does +not, and all changes are aborted. + +Pyramid's transaction management is extendable, so you can synchronize commits +between multiple databases, or databases of different kinds. It also allows you +to do things like conditionally send email if a transaction commits, but +otherwise keep quiet. Example: :ref:`bfg_sql_wiki_tutorial` (note the lack of commit statements anywhere in application code). -Configuration conflict detection -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Stop Worrying About Configuration +--------------------------------- When a system is small, it's reasonably easy to keep it all in your head. But -when systems grow large, you may have hundreds or thousands of configuration -statements which add a view, add a route, and so forth. +as systems grow large, configuration grows more complex. Your app may grow to +have hundreds or even thousands of configuration statements. -Pyramid's configuration system keeps track of your configuration statements. If -you accidentally add two that are identical, or Pyramid can't make sense out of +Pyramid's configuration system keeps track of your configuration. If you +accidentally add two that are identical, or Pyramid can't make sense out of what it would mean to have both statements active at the same time, it will -complain loudly at startup time. It's not dumb though. It will automatically -resolve conflicting configuration statements on its own if you use the -configuration :meth:`~pyramid.config.Configurator.include` system. "More local" -statements are preferred over "less local" ones. This allows you to -intelligently factor large systems into smaller ones. +complain loudly at startup time. + +Pyramid's configuration system is not dumb though. If you use the confugration +:meth:`~pyramid.config.Configurator.include` system, it can automatically +resolve conflicts on its own. "More local" statements are preferred over "less +local" ones. So you can intelligently factor large systems into smaller ones. Example: :ref:`conflict_detection`. -Configuration extensibility -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Unlike other systems, Pyramid provides a structured "include" mechanism (see -:meth:`~pyramid.config.Configurator.include`) that allows you to combine -applications from multiple Python packages. All the configuration statements -that can be performed in your "main" Pyramid application can also be performed -by included packages, including the addition of views, routes, subscribers, and -even authentication and authorization policies. You can even extend or override -an existing application by including another application's configuration in -your own, overriding or adding new views and routes to it. This has the -potential to allow you to create a big application out of many other smaller -ones. For example, if you want to reuse an existing application that already -has a bunch of routes, you can just use the ``include`` statement with a -``route_prefix``. The new application will live within your application at an -URL prefix. It's not a big deal, and requires little up-front engineering -effort. - -For example: +Compose Powerful Apps From Simple Parts +---------------------------------------- + +Speaking of the :app:`Pyramid` structured "include" mechanism (see +:meth:`~pyramid.config.Configurator.include`), it allows you to compose complex +applications from multiple, simple Python packages. All the configuration +statements that can be performed in your "main" Pyramid application can also be +used in included packages. You can add views, routes, and subscribers, and even +set authentication and authorization policies. + +If you need, you can extend or override the configuration of an existing +application by including its configuration in your own and then modifying it. + + +For example, if you want to reuse an existing application that already has a +bunch of routes, you can just use the ``include`` statement with a +``route_prefix``. All the routes of that application will be availabe, prefixed +as you requested: .. code-block:: python :linenos: @@ -120,91 +139,79 @@ For example: See also :ref:`including_configuration` and :ref:`building_an_extensible_app`. -Flexible authentication and authorization -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Authenticate Users Your Way +--------------------------- + +:app:`Pyramid` ships with prebuilt well-tested authentication and authorization +schemes out of the box. Using a scheme is a matter of configuration. So if you +need to change approaches later, you need only update your configuration. -Pyramid includes a flexible, pluggable authentication and authorization system. -No matter where your user data is stored, or what scheme you'd like to use to -permit your users to access your data, you can use a predefined Pyramid -plugpoint to plug in your custom authentication and authorization code. If you -want to change these schemes later, you can just change it in one place rather -than everywhere in your code. It also ships with prebuilt well-tested -authentication and authorization schemes out of the box. But what if you don't -want to use Pyramid's built-in system? You don't have to. You can just write -your own bespoke security code as you would in any other system. +In addition, the system that handles authentication an authorization is +flexible and pluggable. If you want to use another security add-on, or define +your own, you can. And again, you need only update your application +configuration to make the change. Example: :ref:`enabling_authorization_policy`. -Traversal -~~~~~~~~~ - -:term:`Traversal` is a concept stolen from :term:`Zope`. It allows you to -create a tree of resources, each of which can be addressed by one or more URLs. -Each of those resources can have one or more *views* associated with it. If -your data isn't naturally treelike, or you're unwilling to create a treelike -representation of your data, you aren't going to find traversal very useful. -However, traversal is absolutely fantastic for sites that need to be -arbitrarily extensible. It's a lot easier to add a node to a tree than it is to -shoehorn a route into an ordered list of other routes, or to create another -entire instance of an application to service a department and glue code to -allow disparate apps to share data. It's a great fit for sites that naturally -lend themselves to changing departmental hierarchies, such as content -management systems and document management systems. Traversal also lends -itself well to systems that require very granular security ("Bob can edit -*this* document" as opposed to "Bob can edit documents"). +Build Trees of Resources +------------------------ + +:app:`Pyramid` supports :term:`Traversal`, a way of mapping URLs to a concrete +tree of resources. If your application naturally consists of an arbitrary +heirarchy of different types of content (like a CMS or a Document Management +System), traversal is for you. If you have a requirement for a highly granular +security model ("Jane can edit documents in *this* folder, but not *that* +one"), traversal can be a powerful approach. Examples: :ref:`hello_traversal_chapter` and :ref:`much_ado_about_traversal_chapter`. -Tweens -~~~~~~ +Take Action on Each Request with Tweens +--------------------------------------- + +Pyramid has a system for applying arbitrary actions to each request or response +called *tweens*. The system is similar in concept to WSGI :term:`middleware`, +but can be more useful since they run in the Pyramid context, and have access +to templates, request objects, and other niceties. -Pyramid has a sort of internal WSGI-middleware-ish pipeline that can be hooked -by arbitrary add-ons named "tweens". The debug toolbar is a "tween", and the -``pyramid_tm`` transaction manager is also. Tweens are more useful than WSGI -:term:`middleware` in some circumstances because they run in the context of -Pyramid itself, meaning you have access to templates and other renderers, a -"real" request object, and other niceties. +The Pyramid debug toolbar is a "tween", as is the ``pyramid_tm`` transaction +manager. Example: :ref:`registering_tweens`. -View response adapters -~~~~~~~~~~~~~~~~~~~~~~ +Return What You Want From Your Views +------------------------------------ -A lot is made of the aesthetics of what *kinds* of objects you're allowed to -return from view callables in various frameworks. In a previous section in -this document, we showed you that, if you use a :term:`renderer`, you can -usually return a dictionary from a view callable instead of a full-on -:term:`Response` object. But some frameworks allow you to return strings or -tuples from view callables. When frameworks allow for this, code looks -slightly prettier, because fewer imports need to be done, and there is less -code. For example, compare this: +We have shown before (in the :doc:`introduction`) how using a :term:`renderer` +allows you to return simple Python dictionaries from your view code. But some +frameworks allow you to return strings or tuples from view callables. +When frameworks allow for this, code looks slightly prettier, because there are +fewer imports, and less code. For example, compare this: .. code-block:: python :linenos: + from pyramid.response import Response + def aview(request): - return "Hello world!" + return Response("Hello world!") To this: .. code-block:: python :linenos: - from pyramid.response import Response - def aview(request): - return Response("Hello world!") + return "Hello world!" -The former is "prettier", right? +Nicer to look at, right? -Out of the box, if you define the former view callable (the one that simply -returns a string) in Pyramid, when it is executed, Pyramid will raise an -exception. This is because "explicit is better than implicit", in most cases, -and by default Pyramid wants you to return a :term:`Response` object from a -view callable. This is because there's usually a heck of a lot more to a -response object than just its body. But if you're the kind of person who -values such aesthetics, we have an easy way to allow for this sort of thing: +Out of the box, Pyramid will raise an exception if you try to run the second +example above. After all, a view should return a response, and "explicit is +better than implicit". + +But if you're a developer who likes the aesthetics of simplicity, Pyramid +provides an way to support this sort of thing, the *response adapter*: .. code-block:: python :linenos: @@ -217,12 +224,13 @@ values such aesthetics, we have an easy way to allow for this sort of thing: response.content_type = 'text/html' return response +A new response adapter is registered in configuration: + if __name__ == '__main__': config = Configurator() config.add_response_adapter(string_response_adapter, basestring) -Do that once in your Pyramid application at startup. Now you can return -strings from any of your view callables, e.g.: +With that, you may return strings from any of your view callables, e.g.: .. code-block:: python :linenos: @@ -233,8 +241,8 @@ strings from any of your view callables, e.g.: def goodbyeview(request): return "Goodbye world!" -Oh noes! What if you want to indicate a custom content type? And a custom -status code? No fear: +You can even use a response adapter to allow for custom content types and +return codes: .. code-block:: python :linenos: @@ -259,7 +267,7 @@ status code? No fear: config.add_response_adapter(string_response_adapter, basestring) config.add_response_adapter(tuple_response_adapter, tuple) -Once this is done, both of these view callables will work: +With this, both of these views will work as expected: .. code-block:: python :linenos: @@ -270,20 +278,17 @@ Once this is done, both of these view callables will work: def anotherview(request): return (403, 'text/plain', "Forbidden") -Pyramid defaults to explicit behavior, because it's the most generally useful, -but provides hooks that allow you to adapt the framework to localized aesthetic -desires. - .. seealso:: See also :ref:`using_iresponse`. -"Global" response object -~~~~~~~~~~~~~~~~~~~~~~~~ +Use Global Response Objects +--------------------------- -"Constructing these response objects in my view callables is such a chore! And -I'm way too lazy to register a response adapter, as per the prior section," you -say. Fine. Be that way: +Views have to return responses. But constructing them in view code is a chore. +And perhaps registering a response adapter as shown above is just too much +work. :app:`Pyramid` provides a global response object as well. You can just +use it directly, if you prefer: .. code-block:: python :linenos: @@ -298,17 +303,18 @@ say. Fine. Be that way: See also :ref:`request_response_attr`. -Automating repetitive configuration -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Extend Configuration +-------------------- + +Perhaps the :app:`Pyramid` configurator's syntax feels a bit verbose to you. Or +possibly you would like to add a feature to configuration without asking the +core developers to change Pyramid itself? -Does Pyramid's configurator allow you to do something, but you're a little -adventurous and just want it a little less verbose? Or you'd like to offer up -some handy configuration feature to other Pyramid users without requiring that -we change Pyramid? You can extend Pyramid's :term:`Configurator` with your own -directives. For example, let's say you find yourself calling -:meth:`pyramid.config.Configurator.add_view` repetitively. Usually you can -take the boring away by using existing shortcuts, but let's say that this is a -case where there is no such shortcut: +You can extend Pyramid's :term:`Configurator` with your own directives. For +example, let's say you find yourself calling +:meth:`pyramid.config.Configurator.add_view` repetitively. Usually you can get +rid of the boring with existing shortcuts, but let's say that this is a case +where there is no such shortcut: .. code-block:: python :linenos: @@ -352,26 +358,26 @@ the Configurator object: config.add_route('xhr_route', '/xhr/{id}') config.add_protected_xhr_views('my.package') -Your previously repetitive configuration lines have now morphed into one line. +Much better! -You can share your configuration code with others this way, too, by packaging -it up and calling :meth:`~pyramid.config.Configurator.add_directive` from -within a function called when another user uses the +You can share your configuration code with others, too. Package it up and call +:meth:`~pyramid.config.Configurator.add_directive` from within a function +called when another user uses the :meth:`~pyramid.config.Configurator.include` method against your code. .. seealso:: See also :ref:`add_directive`. -Programmatic introspection -~~~~~~~~~~~~~~~~~~~~~~~~~~ +Introspect Your Application +--------------------------- -If you're building a large system that other users may plug code into, it's -useful to be able to get an enumeration of what code they plugged in *at -application runtime*. For example, you might want to show them a set of tabs -at the top of the screen based on an enumeration of views they registered. +If you're building a large, pluggalbe system, it's useful to be able to get a +list of what has been plugged in *at application runtime*. For example, you +might want to show users a set of tabs at the top of the screen based on a list +of the views they registered. -This is possible using Pyramid's :term:`introspector`. +:app:`Pyramid` provides an :term:`introspector` for just this purpose. Here's an example of using Pyramid's introspector from within a view callable: -- cgit v1.2.3 From c5538ea907990a19994c9d9acda92e602a8769c4 Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Mon, 22 May 2017 16:54:17 -0700 Subject: Also replace script tags appear everywhere --- docs/quick_tour/logging/hello_world/templates/layout.jinja2 | 8 ++++---- docs/quick_tour/package/hello_world/templates/layout.jinja2 | 8 ++++---- docs/quick_tour/sessions/hello_world/templates/layout.jinja2 | 8 ++++---- docs/quick_tour/sqla_demo/sqla_demo/templates/layout.jinja2 | 8 ++++---- .../cookiecutters/cc_starter/templates/layout.jinja2 | 8 ++++---- docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt | 8 ++++---- docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt | 8 ++++---- docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt | 8 ++++---- .../wiki/src/basiclayout/tutorial/templates/mytemplate.pt | 8 ++++---- .../wiki/src/installation/tutorial/templates/mytemplate.pt | 8 ++++---- docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt | 8 ++++---- docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt | 8 ++++---- docs/tutorials/wiki/src/tests/tutorial/templates/login.pt | 8 ++++---- docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt | 8 ++++---- docs/tutorials/wiki/src/tests/tutorial/templates/view.pt | 8 ++++---- docs/tutorials/wiki/src/views/tutorial/templates/edit.pt | 8 ++++---- docs/tutorials/wiki/src/views/tutorial/templates/view.pt | 8 ++++---- .../wiki2/src/authentication/tutorial/templates/layout.jinja2 | 8 ++++---- .../wiki2/src/authorization/tutorial/templates/layout.jinja2 | 8 ++++---- .../wiki2/src/basiclayout/tutorial/templates/layout.jinja2 | 8 ++++---- .../wiki2/src/installation/tutorial/templates/layout.jinja2 | 8 ++++---- docs/tutorials/wiki2/src/models/tutorial/templates/layout.jinja2 | 8 ++++---- docs/tutorials/wiki2/src/tests/tutorial/templates/layout.jinja2 | 8 ++++---- docs/tutorials/wiki2/src/views/tutorial/templates/layout.jinja2 | 8 ++++---- 24 files changed, 96 insertions(+), 96 deletions(-) diff --git a/docs/quick_tour/logging/hello_world/templates/layout.jinja2 b/docs/quick_tour/logging/hello_world/templates/layout.jinja2 index 916127267..c82cac915 100644 --- a/docs/quick_tour/logging/hello_world/templates/layout.jinja2 +++ b/docs/quick_tour/logging/hello_world/templates/layout.jinja2 @@ -18,8 +18,8 @@ @@ -58,7 +58,7 @@ - - + + diff --git a/docs/quick_tour/package/hello_world/templates/layout.jinja2 b/docs/quick_tour/package/hello_world/templates/layout.jinja2 index 916127267..c82cac915 100644 --- a/docs/quick_tour/package/hello_world/templates/layout.jinja2 +++ b/docs/quick_tour/package/hello_world/templates/layout.jinja2 @@ -18,8 +18,8 @@ @@ -58,7 +58,7 @@ - - + + diff --git a/docs/quick_tour/sessions/hello_world/templates/layout.jinja2 b/docs/quick_tour/sessions/hello_world/templates/layout.jinja2 index 916127267..c82cac915 100644 --- a/docs/quick_tour/sessions/hello_world/templates/layout.jinja2 +++ b/docs/quick_tour/sessions/hello_world/templates/layout.jinja2 @@ -18,8 +18,8 @@ @@ -58,7 +58,7 @@ - - + + diff --git a/docs/quick_tour/sqla_demo/sqla_demo/templates/layout.jinja2 b/docs/quick_tour/sqla_demo/sqla_demo/templates/layout.jinja2 index 4607eb11f..b84b3ec0e 100644 --- a/docs/quick_tour/sqla_demo/sqla_demo/templates/layout.jinja2 +++ b/docs/quick_tour/sqla_demo/sqla_demo/templates/layout.jinja2 @@ -18,8 +18,8 @@ @@ -58,7 +58,7 @@ - - + + diff --git a/docs/quick_tutorial/cookiecutters/cc_starter/templates/layout.jinja2 b/docs/quick_tutorial/cookiecutters/cc_starter/templates/layout.jinja2 index 20da74879..3aed0a123 100644 --- a/docs/quick_tutorial/cookiecutters/cc_starter/templates/layout.jinja2 +++ b/docs/quick_tutorial/cookiecutters/cc_starter/templates/layout.jinja2 @@ -18,8 +18,8 @@ @@ -58,7 +58,7 @@ - - + + diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt index 19adc5932..6073c706c 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt +++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt @@ -19,8 +19,8 @@ @@ -67,7 +67,7 @@ - - + + diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt index 02f7038fe..66b8cf685 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt +++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt @@ -19,8 +19,8 @@ @@ -69,7 +69,7 @@ - - + + diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt index 17a715b50..f6a234d8f 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt +++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt @@ -19,8 +19,8 @@ @@ -67,7 +67,7 @@ - - + + diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt index 3ac122711..4ffc0eb22 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt @@ -18,8 +18,8 @@ @@ -59,7 +59,7 @@ - - + + diff --git a/docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt index 3ac122711..4ffc0eb22 100644 --- a/docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt @@ -18,8 +18,8 @@ @@ -59,7 +59,7 @@ - - + + diff --git a/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt index 3ac122711..4ffc0eb22 100644 --- a/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt @@ -18,8 +18,8 @@ @@ -59,7 +59,7 @@ - - + + diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt index 19adc5932..6073c706c 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt +++ b/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt @@ -19,8 +19,8 @@ @@ -67,7 +67,7 @@ - - + + diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt index 02f7038fe..66b8cf685 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt +++ b/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt @@ -19,8 +19,8 @@ @@ -69,7 +69,7 @@ - - + + diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt index f8cbe2e2c..6c3809250 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt @@ -18,8 +18,8 @@ @@ -61,7 +61,7 @@ - - + + diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt index 17a715b50..f6a234d8f 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt +++ b/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt @@ -19,8 +19,8 @@ @@ -67,7 +67,7 @@ - - + + diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt b/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt index b23f45d56..71a15d48c 100644 --- a/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt +++ b/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt @@ -19,8 +19,8 @@ @@ -63,7 +63,7 @@ - - + + diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/view.pt b/docs/tutorials/wiki/src/views/tutorial/templates/view.pt index 5caaef4af..e91f0b8d5 100644 --- a/docs/tutorials/wiki/src/views/tutorial/templates/view.pt +++ b/docs/tutorials/wiki/src/views/tutorial/templates/view.pt @@ -19,8 +19,8 @@ @@ -64,7 +64,7 @@ - - + + diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/templates/layout.jinja2 b/docs/tutorials/wiki2/src/authentication/tutorial/templates/layout.jinja2 index 44d14304e..bd5185800 100644 --- a/docs/tutorials/wiki2/src/authentication/tutorial/templates/layout.jinja2 +++ b/docs/tutorials/wiki2/src/authentication/tutorial/templates/layout.jinja2 @@ -18,8 +18,8 @@ @@ -58,7 +58,7 @@ - - + + diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/templates/layout.jinja2 b/docs/tutorials/wiki2/src/authorization/tutorial/templates/layout.jinja2 index 44d14304e..bd5185800 100644 --- a/docs/tutorials/wiki2/src/authorization/tutorial/templates/layout.jinja2 +++ b/docs/tutorials/wiki2/src/authorization/tutorial/templates/layout.jinja2 @@ -18,8 +18,8 @@ @@ -58,7 +58,7 @@ - - + + diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/layout.jinja2 b/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/layout.jinja2 index 1f658c834..e29413cf9 100644 --- a/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/layout.jinja2 +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/layout.jinja2 @@ -18,8 +18,8 @@ @@ -58,7 +58,7 @@ - - + + diff --git a/docs/tutorials/wiki2/src/installation/tutorial/templates/layout.jinja2 b/docs/tutorials/wiki2/src/installation/tutorial/templates/layout.jinja2 index 1f658c834..e29413cf9 100644 --- a/docs/tutorials/wiki2/src/installation/tutorial/templates/layout.jinja2 +++ b/docs/tutorials/wiki2/src/installation/tutorial/templates/layout.jinja2 @@ -18,8 +18,8 @@ @@ -58,7 +58,7 @@ - - + + diff --git a/docs/tutorials/wiki2/src/models/tutorial/templates/layout.jinja2 b/docs/tutorials/wiki2/src/models/tutorial/templates/layout.jinja2 index 1f658c834..e29413cf9 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/templates/layout.jinja2 +++ b/docs/tutorials/wiki2/src/models/tutorial/templates/layout.jinja2 @@ -18,8 +18,8 @@ @@ -58,7 +58,7 @@ - - + + diff --git a/docs/tutorials/wiki2/src/tests/tutorial/templates/layout.jinja2 b/docs/tutorials/wiki2/src/tests/tutorial/templates/layout.jinja2 index 44d14304e..bd5185800 100644 --- a/docs/tutorials/wiki2/src/tests/tutorial/templates/layout.jinja2 +++ b/docs/tutorials/wiki2/src/tests/tutorial/templates/layout.jinja2 @@ -18,8 +18,8 @@ @@ -58,7 +58,7 @@ - - + + diff --git a/docs/tutorials/wiki2/src/views/tutorial/templates/layout.jinja2 b/docs/tutorials/wiki2/src/views/tutorial/templates/layout.jinja2 index 7575de8a7..f2c4b50f2 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/templates/layout.jinja2 +++ b/docs/tutorials/wiki2/src/views/tutorial/templates/layout.jinja2 @@ -18,8 +18,8 @@ @@ -49,7 +49,7 @@ - - + + -- cgit v1.2.3 From 50d216a549bc848f411769690d722d367c91fdb4 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Mon, 22 May 2017 17:49:58 -0700 Subject: add change note --- CHANGES.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 1a7cfaf0b..c21508a70 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -5,6 +5,10 @@ unreleased new message suggests alternatives based on the list of known predicates. See https://github.com/Pylons/pyramid/pull/3054 +- Added integrity attributes for JavaScripts in cookiecutters, scaffolds, and + resulting source files in tutorials. + See https://github.com/Pylons/pyramid/issues/2548 + 1.9a2 (2017-05-09) ================== -- cgit v1.2.3 From ed7bad6862f5b495e3c6b55007822813d021248b Mon Sep 17 00:00:00 2001 From: Chris Morales Date: Tue, 23 May 2017 11:57:47 -0700 Subject: pyramid_tm.explicit_manager set in the configuration. --- pyramid/scaffolds/alchemy/+package+/models/__init__.py_tmpl | 1 + pyramid/scaffolds/zodb/+package+/__init__.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/pyramid/scaffolds/alchemy/+package+/models/__init__.py_tmpl b/pyramid/scaffolds/alchemy/+package+/models/__init__.py_tmpl index f626d1ef0..521816ce7 100644 --- a/pyramid/scaffolds/alchemy/+package+/models/__init__.py_tmpl +++ b/pyramid/scaffolds/alchemy/+package+/models/__init__.py_tmpl @@ -57,6 +57,7 @@ def includeme(config): """ settings = config.get_settings() + settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' # use pyramid_tm to hook the transaction lifecycle to the request config.include('pyramid_tm') diff --git a/pyramid/scaffolds/zodb/+package+/__init__.py b/pyramid/scaffolds/zodb/+package+/__init__.py index f2a86df47..a956d0faf 100644 --- a/pyramid/scaffolds/zodb/+package+/__init__.py +++ b/pyramid/scaffolds/zodb/+package+/__init__.py @@ -12,6 +12,8 @@ def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ config = Configurator(root_factory=root_factory, settings=settings) + settings = config.get_settings() + settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' config.include('pyramid_chameleon') config.add_static_view('static', 'static', cache_max_age=3600) config.scan() -- cgit v1.2.3 From 07e0e15fdafb28843a92cd03681a07aa652008a9 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Tue, 23 May 2017 12:50:58 -0700 Subject: allow the execution policy to perform a last-ditch effort to render an exception view --- pyramid/router.py | 14 ++++++++++++-- pyramid/tests/test_router.py | 27 +++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/pyramid/router.py b/pyramid/router.py index 8b7b7b6bc..7f3f9fbea 100644 --- a/pyramid/router.py +++ b/pyramid/router.py @@ -1,3 +1,4 @@ +import sys from zope.interface import ( implementer, providedBy, @@ -24,6 +25,7 @@ from pyramid.events import ( BeforeTraversal, ) +from pyramid.compat import reraise from pyramid.httpexceptions import HTTPNotFound from pyramid.request import Request from pyramid.view import _call_view @@ -252,7 +254,15 @@ class Router(object): response = self.execution_policy(environ, self) return response(environ, start_response) - def default_execution_policy(environ, router): request = router.make_request(environ) - return router.invoke_request(request) + try: + return router.invoke_request(request) + except Exception: + exc_info = sys.exc_info() + try: + return request.invoke_exception_view(exc_info) + except HTTPNotFound: + reraise(*exc_info) + finally: + del exc_info # avoid local ref cycle diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py index a5da5c627..bd023824c 100644 --- a/pyramid/tests/test_router.py +++ b/pyramid/tests/test_router.py @@ -1284,6 +1284,33 @@ class TestRouter(unittest.TestCase): self.assertEqual(resp.status_code, 200) self.assertEqual(resp.body, b'foo') + def test_execution_policy_handles_exception(self): + from pyramid.interfaces import IViewClassifier + from pyramid.interfaces import IExceptionViewClassifier + from pyramid.interfaces import IRequest + class Exception1(Exception): + pass + class Exception2(Exception): + pass + req_iface = self._registerRouteRequest('foo') + self._connectRoute('foo', 'archives/:action/:article', None) + view = DummyView(DummyResponse(), raise_exception=Exception1) + self._registerView(view, '', IViewClassifier, req_iface, None) + exception_view1 = DummyView(DummyResponse(), + raise_exception=Exception2) + self._registerView(exception_view1, '', IExceptionViewClassifier, + IRequest, Exception1) + response = DummyResponse() + response.app_iter = ["Hello, world"] + exception_view2 = DummyView(response) + self._registerView(exception_view2, '', IExceptionViewClassifier, + IRequest, Exception2) + environ = self._makeEnviron(PATH_INFO='/archives/action1/article1') + start_response = DummyStartResponse() + router = self._makeOne() + result = router(environ, start_response) + self.assertEqual(result, ["Hello, world"]) + class DummyPredicate(object): def __call__(self, info, request): return True -- cgit v1.2.3 From dda9fa85a8132ae3a18ff0b09e902ee6057430d1 Mon Sep 17 00:00:00 2001 From: Bert JW Regeer Date: Tue, 23 May 2017 13:53:10 -0700 Subject: When invoking an exception view, push the new threadlocals This way when calling the threadlocal get_current_request() you get the same request object as the one that was passed to the view. --- pyramid/tests/test_view.py | 22 ++++++++++++++++++++++ pyramid/threadlocal.py | 2 +- pyramid/view.py | 40 ++++++++++++++++++++++++++++------------ 3 files changed, 51 insertions(+), 13 deletions(-) diff --git a/pyramid/tests/test_view.py b/pyramid/tests/test_view.py index 2061515b3..9f02b1352 100644 --- a/pyramid/tests/test_view.py +++ b/pyramid/tests/test_view.py @@ -790,6 +790,8 @@ class TestViewMethodsMixin(unittest.TestCase): def test_it_supports_alternate_requests(self): def exc_view(exc, request): self.assertTrue(request is other_req) + from pyramid.threadlocal import get_current_request + self.assertTrue(get_current_request() is other_req) return DummyResponse(b'foo') self.config.add_view(exc_view, context=RuntimeError) request = self._makeOne() @@ -816,6 +818,26 @@ class TestViewMethodsMixin(unittest.TestCase): else: # pragma: no cover self.fail() + def test_it_raises_if_no_registry(self): + def exc_view(exc, request): + return DummyResponse(b'foo') + self.config.add_view(exc_view, context=RuntimeError) + request = self._makeOne() + del request.registry + from pyramid.threadlocal import manager + manager.push({'registry': None, 'request': request}) + try: + raise RuntimeError + except RuntimeError: + try: + request.invoke_exception_view() + except RuntimeError as e: + self.assertEqual(e.args[0], "Unable to retrieve registry") + else: # pragma: no cover + self.fail() + finally: + manager.pop() + def test_it_supports_alternate_exc_info(self): def exc_view(exc, request): self.assertTrue(request.exc_info is exc_info) diff --git a/pyramid/threadlocal.py b/pyramid/threadlocal.py index 638f7b7b0..9429fe953 100644 --- a/pyramid/threadlocal.py +++ b/pyramid/threadlocal.py @@ -31,7 +31,7 @@ class ThreadLocalManager(threading.local): self.stack[:] = [] def defaults(): - return {'request':None, 'registry':global_registry} + return {'request': None, 'registry': global_registry} manager = ThreadLocalManager(default=defaults) diff --git a/pyramid/view.py b/pyramid/view.py index 0c1b8cd97..14c8c029e 100644 --- a/pyramid/view.py +++ b/pyramid/view.py @@ -28,7 +28,11 @@ from pyramid.httpexceptions import ( default_exceptionresponse_view, ) -from pyramid.threadlocal import get_current_registry +from pyramid.threadlocal import ( + get_current_registry, + manager, + ) + from pyramid.util import hide_attrs _marker = object() @@ -675,8 +679,13 @@ class ViewMethodsMixin(object): registry = getattr(request, 'registry', None) if registry is None: registry = get_current_registry() + + if registry is None: + raise RuntimeError("Unable to retrieve registry") + if exc_info is None: exc_info = sys.exc_info() + exc = exc_info[1] attrs = request.__dict__ context_iface = providedBy(exc) @@ -690,17 +699,24 @@ class ViewMethodsMixin(object): # we use .get instead of .__getitem__ below due to # https://github.com/Pylons/pyramid/issues/700 request_iface = attrs.get('request_iface', IRequest) - response = _call_view( - registry, - request, - exc, - context_iface, - '', - view_types=None, - view_classifier=IExceptionViewClassifier, - secure=secure, - request_iface=request_iface.combined, - ) + + try: + if request is not self: + manager.push({'request': request, 'registry': registry}) + + response = _call_view( + registry, + request, + exc, + context_iface, + '', + view_types=None, + view_classifier=IExceptionViewClassifier, + secure=secure, + request_iface=request_iface.combined, + ) + finally: + manager.pop() if response is None: raise HTTPNotFound -- cgit v1.2.3 From 3bb3c6e18bc94856b8c08907dfea5d1ce8217754 Mon Sep 17 00:00:00 2001 From: Bert JW Regeer Date: Tue, 23 May 2017 13:59:55 -0700 Subject: Make coverage happy again --- pyramid/tests/test_view.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/pyramid/tests/test_view.py b/pyramid/tests/test_view.py index 9f02b1352..a9ce2234d 100644 --- a/pyramid/tests/test_view.py +++ b/pyramid/tests/test_view.py @@ -819,9 +819,6 @@ class TestViewMethodsMixin(unittest.TestCase): self.fail() def test_it_raises_if_no_registry(self): - def exc_view(exc, request): - return DummyResponse(b'foo') - self.config.add_view(exc_view, context=RuntimeError) request = self._makeOne() del request.registry from pyramid.threadlocal import manager -- cgit v1.2.3 From d8388838022aa5bafe78c8ba6949bc52c2968799 Mon Sep 17 00:00:00 2001 From: Chris Morales Date: Tue, 23 May 2017 15:01:16 -0700 Subject: updated references for the models that have references for the config.settings --- docs/quick_tour/sqla_demo/sqla_demo/models/__init__.py | 2 +- docs/tutorials/wiki2/src/authentication/tutorial/models/__init__.py | 1 + docs/tutorials/wiki2/src/basiclayout/tutorial/models/__init__.py | 1 + docs/tutorials/wiki2/src/installation/tutorial/models/__init__.py | 1 + docs/tutorials/wiki2/src/models/tutorial/models/__init__.py | 1 + docs/tutorials/wiki2/src/tests/tutorial/models/__init__.py | 1 + docs/tutorials/wiki2/src/views/tutorial/models/__init__.py | 1 + 7 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/quick_tour/sqla_demo/sqla_demo/models/__init__.py b/docs/quick_tour/sqla_demo/sqla_demo/models/__init__.py index 339326758..e6eb98fbd 100644 --- a/docs/quick_tour/sqla_demo/sqla_demo/models/__init__.py +++ b/docs/quick_tour/sqla_demo/sqla_demo/models/__init__.py @@ -57,7 +57,7 @@ def includeme(config): """ settings = config.get_settings() - + settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' # use pyramid_tm to hook the transaction lifecycle to the request config.include('pyramid_tm') diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/authentication/tutorial/models/__init__.py index 8147052ad..cd8347ccd 100644 --- a/docs/tutorials/wiki2/src/authentication/tutorial/models/__init__.py +++ b/docs/tutorials/wiki2/src/authentication/tutorial/models/__init__.py @@ -58,6 +58,7 @@ def includeme(config): """ settings = config.get_settings() + settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' # use pyramid_tm to hook the transaction lifecycle to the request config.include('pyramid_tm') diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/models/__init__.py index 5ca037787..ae575691c 100644 --- a/docs/tutorials/wiki2/src/basiclayout/tutorial/models/__init__.py +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/models/__init__.py @@ -57,6 +57,7 @@ def includeme(config): """ settings = config.get_settings() + settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' # use pyramid_tm to hook the transaction lifecycle to the request config.include('pyramid_tm') diff --git a/docs/tutorials/wiki2/src/installation/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/installation/tutorial/models/__init__.py index 5ca037787..ae575691c 100644 --- a/docs/tutorials/wiki2/src/installation/tutorial/models/__init__.py +++ b/docs/tutorials/wiki2/src/installation/tutorial/models/__init__.py @@ -57,6 +57,7 @@ def includeme(config): """ settings = config.get_settings() + settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' # use pyramid_tm to hook the transaction lifecycle to the request config.include('pyramid_tm') diff --git a/docs/tutorials/wiki2/src/models/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/models/tutorial/models/__init__.py index 8147052ad..cd8347ccd 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/models/__init__.py +++ b/docs/tutorials/wiki2/src/models/tutorial/models/__init__.py @@ -58,6 +58,7 @@ def includeme(config): """ settings = config.get_settings() + settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' # use pyramid_tm to hook the transaction lifecycle to the request config.include('pyramid_tm') diff --git a/docs/tutorials/wiki2/src/tests/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/tests/tutorial/models/__init__.py index 8147052ad..cd8347ccd 100644 --- a/docs/tutorials/wiki2/src/tests/tutorial/models/__init__.py +++ b/docs/tutorials/wiki2/src/tests/tutorial/models/__init__.py @@ -58,6 +58,7 @@ def includeme(config): """ settings = config.get_settings() + settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' # use pyramid_tm to hook the transaction lifecycle to the request config.include('pyramid_tm') diff --git a/docs/tutorials/wiki2/src/views/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/views/tutorial/models/__init__.py index 8147052ad..cd8347ccd 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/models/__init__.py +++ b/docs/tutorials/wiki2/src/views/tutorial/models/__init__.py @@ -58,6 +58,7 @@ def includeme(config): """ settings = config.get_settings() + settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' # use pyramid_tm to hook the transaction lifecycle to the request config.include('pyramid_tm') -- cgit v1.2.3 From 81fa04cc4e59ce01bb0abbabfefb8c25cf43eb78 Mon Sep 17 00:00:00 2001 From: Chris Morales Date: Tue, 23 May 2017 17:53:09 -0700 Subject: updated the emphasis in the tutorial docs for the definingmodels.rst --- docs/tutorials/wiki2/definingmodels.rst | 2 +- docs/tutorials/wiki2/src/models/tutorial/__init__.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/tutorials/wiki2/definingmodels.rst b/docs/tutorials/wiki2/definingmodels.rst index 5cebb943c..801b56eb4 100644 --- a/docs/tutorials/wiki2/definingmodels.rst +++ b/docs/tutorials/wiki2/definingmodels.rst @@ -153,7 +153,7 @@ the following: .. literalinclude:: src/models/tutorial/models/__init__.py :linenos: :language: py - :emphasize-lines: 8,9 + :emphasize-lines: 10,11 Here we align our imports with the names of the models, ``Page`` and ``User``. diff --git a/docs/tutorials/wiki2/src/models/tutorial/__init__.py b/docs/tutorials/wiki2/src/models/tutorial/__init__.py index 4dab44823..7654fc808 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/__init__.py +++ b/docs/tutorials/wiki2/src/models/tutorial/__init__.py @@ -5,6 +5,8 @@ def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ config = Configurator(settings=settings) + settings = config.get_settings() + settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' config.include('pyramid_jinja2') config.include('.models') config.include('.routes') -- cgit v1.2.3 From f20a018167a19d17527d40c027e6f9045749f065 Mon Sep 17 00:00:00 2001 From: cewing Date: Tue, 23 May 2017 20:21:07 -0700 Subject: fixes per code review, Thanks @stevepiercy. --- docs/glossary.rst | 33 +++- docs/narr/advanced-features.rst | 418 ++++++++++++++++++++++++++++++++++++++++ docs/narr/advfeatures.rst | 399 -------------------------------------- docs/narr/introduction.rst | 2 +- 4 files changed, 451 insertions(+), 401 deletions(-) create mode 100644 docs/narr/advanced-features.rst delete mode 100644 docs/narr/advfeatures.rst diff --git a/docs/glossary.rst b/docs/glossary.rst index 2e5276554..9031ede04 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -751,7 +751,7 @@ Glossary :ref:`Venusian` is a library which allows framework authors to defer decorator actions. Instead of taking actions when a function (or class) decorator is executed - at import time, the action usually taken by the decorator is + at :term:`import time`, the action usually taken by the decorator is deferred until a separate "scan" phase. :app:`Pyramid` relies on Venusian to provide a basis for its :term:`scan` feature. @@ -1172,3 +1172,34 @@ Glossary A policy which wraps the :term:`router` by creating the request object and sending it through the request pipeline. See :class:`pyramid.config.Configurator.set_execution_policy`. + + singleton + A singleton is a class which will only ever have one instance. + As there is only one, it is shared by all other code. + This makes it an example of :term:`global state`. + + Using a singleton is `considered a poor design choice. `_ + As :term:`mutable` global state, it can be changed by any other code, + and so the values it represents cannot be reasoned about or tested properly. + + global state + A set of values that are available to the entirety of a program. + + mutable + In Python, a value is mutable if it can be changed *in place*. + The Python ``list`` and ``dict`` types are mutable. + When a value is added to or removed from an instance of either, the original object remains. + The opposite of mutable is :term:`immutable`. + + immutable + In Python, a value is immutable if it cannot be changed. + The Python ``str``, ``int``, and ``tuple`` data types are all ``immutable``. + + import time + In Python, the moment when a module is referred to in an ``import`` statement. + At this moment, all statements in that module at the module scope (at the left margin) are executed. + It is a bad design decision to put statements in a Python module that have :term:`side effect`\ s at import time. + + side effect + A statement or function has a side effect when it changes a value outside its own scope. + Put another way, if one can observe the change made by a function from outside that function, it has a side effect. diff --git a/docs/narr/advanced-features.rst b/docs/narr/advanced-features.rst new file mode 100644 index 000000000..a97d4f3b1 --- /dev/null +++ b/docs/narr/advanced-features.rst @@ -0,0 +1,418 @@ +Advanced :app:`Pyramid` Design Features +======================================= + +Pyramid has been built from the ground up to avoid the problems that other +frameworks can suffer. + + +You Don't Need Singletons +------------------------- + +Have you ever struggled with parameterizing Django's ``settings.py`` file for +multiple installations of the same Django application? Have you ever needed to +monkey-patch a framework fixture to get it to behave properly for your +use case? Have you ever tried to deploy your application using an asynchronous +server and failed? + +All these problems are symptoms of :term:`mutable` :term:`global state`, also +known as :term:`import time` :term:`side effect`\ s and arise from the use of +:term:`singleton` data structures. + +:app:`Pyramid` is written so that you don't run into these types of problems. +It is even possible to run multiple copies of the *same* :app:`Pyramid` +application configured differently within a single Python process. This makes +running :app:`Pyramid` in shared hosting environments a snap. + +Simplify your View Code with Predicates +--------------------------------------- + +How many times have you found yourself beginning the logic of your view code +with something like this: + +.. code-block:: python + + if request.user.is_authenticated: + # do one thing + else: + # do something else + +Unlike many other systems, :app:`Pyramid` allows you to associate more than one view +with a single route. For example, you can create a route with the pattern +``/items`` and when the route is matched, you can send the request to one view +if the request method is GET, another view if the request method is POST, and +so on. + +:app:`Pyramid` uses a system of :term:`view predicate`\ s to allow this. +Matching the request method is one basic thing you can do with a +:term:`view predicate`. You can also associate views with other request +parameters, such as elements in the query string, the Accept header, whether +the request is an AJAX (XHR) request or not, and lots of other things. + +For our example above, you can do this instead: + +.. code-block:: python + + @view_config(route_name="items", effective_principals=pyramid.security.Authenticated) + def auth_view(request): + # do one thing + + @view_config(route_name="items") + def anon_view(request): + # do something else + +This approach allows you to develop view code that is simpler, more easily +understandable, and more directly testable. + +.. seealso:: + + See also :ref:`view_configuration_parameters`. + +Stop Worrying About Transactions +-------------------------------- + +:app:`Pyramid`\ 's :term:`cookiecutter`\ s render projects that include a *transaction +management* system. When you use this system, you can stop worrying about when +to commit your changes, :app:`Pyramid` handles it for you. The system will +commit at the end of a request or abort if there was an exception. + +Why is that a good thing? Imagine a situation where you manually commit a +change to your persistence layer. It's very likely that other framework code +will run *after* your changes are done. If an error happens in that other code, +you can easily wind up with inconsistent data if you're not extremely careful. + +Using transaction management saves you from needing to think about this. Either +a request completes successfully and all changes are committed, or it does +not and all changes are aborted. + +Pyramid's transaction management is extendable, so you can synchronize commits +between multiple databases or databases of different kinds. It also allows you +to do things like conditionally send email if a transaction is committed, but +otherwise keep quiet. + +.. seealso:: + + See also :ref:`bfg_sql_wiki_tutorial` (note the lack of commit statements + anywhere in application code). + +Stop Worrying About Configuration +--------------------------------- + +When a system is small, it's reasonably easy to keep it all in your head. But +as systems grow large, configuration grows more complex. Your app may grow to +have hundreds or even thousands of configuration statements. + +:app:`Pyramid`\ 's configuration system keeps track of each of your statements. If you +accidentally add two that are identical, or :app:`Pyramid` can't make sense out of +what it would mean to have both statements active at the same time, it will +complain loudly at startup time. + +:app:`Pyramid`\ 's configuration system is not dumb though. If you use the +:meth:`~pyramid.config.Configurator.include` system, it can automatically +resolve conflicts on its own. More local statements are preferred over less +local ones. So you can intelligently factor large systems into smaller ones. + +.. seealso:: + + See also :ref:`conflict_detection`. + +Compose Powerful Apps From Simple Parts +---------------------------------------- + +Speaking of the :app:`Pyramid` structured "include" mechanism (see +:meth:`~pyramid.config.Configurator.include`), it allows you to compose complex +applications from multiple, simple Python packages. All the configuration +statements that can be performed in your main :app:`Pyramid` application can also be +used in included packages. You can add views, routes, and subscribers, and even +set authentication and authorization policies. + +If you need, you can extend or override the configuration of an existing +application by including its configuration in your own and then modifying it. + + +For example, if you want to reuse an existing application that already has a +bunch of routes, you can just use the ``include`` statement with a +``route_prefix``. All the routes of that application will be availabe, prefixed +as you requested: + +.. code-block:: python + :linenos: + + from pyramid.config import Configurator + + if __name__ == '__main__': + config = Configurator() + config.include('pyramid_jinja2') + config.include('pyramid_exclog') + config.include('some.other.package', route_prefix='/somethingelse') + +.. seealso:: + + See also :ref:`including_configuration` and + :ref:`building_an_extensible_app`. + +Authenticate Users Your Way +--------------------------- + +:app:`Pyramid` ships with prebuilt, well-tested authentication and authorization +schemes out of the box. Using a scheme is a matter of configuration. So if you +need to change approaches later, you need only update your configuration. + +In addition, the system that handles authentication and authorization is +flexible and pluggable. If you want to use another security add-on, or define +your own, you can. And again, you need only update your application +configuration to make the change. + +.. seealso:: + + See also :ref:`enabling_authorization_policy`. + +Build Trees of Resources +------------------------ + +:app:`Pyramid` supports :term:`traversal`, a way of mapping URLs to a concrete +:term:`resource tree`. If your application naturally consists of an arbitrary +heirarchy of different types of content (like a CMS or a Document Management +System), traversal is for you. If you have a requirement for a highly granular +security model ("Jane can edit documents in *this* folder, but not *that* +one"), traversal can be a powerful approach. + +.. seealso:: + + See also :ref:`hello_traversal_chapter` and + :ref:`much_ado_about_traversal_chapter`. + +Take Action on Each Request with Tweens +--------------------------------------- + +:app:`Pyramid` has a system for applying an arbitrary action to each request or +response called a :term:`tween`. The system is similar in concept to WSGI +:term:`middleware`, but can be more useful since :term:`tween`\ s run in the +:app:`Pyramid` context, and have access to templates, request objects, and +other niceties. + +The :app:`Pyramid` debug toolbar is a :term:`tween`, as is the ``pyramid_tm`` +transaction manager. + +.. seealso:: + + See also :ref:`registering_tweens`. + +Return What You Want From Your Views +------------------------------------ + +We have shown elsewhere (in the :doc:`introduction`) how using a :term:`renderer` +allows you to return simple Python dictionaries from your view code. But some +frameworks allow you to return strings or tuples from view callables. +When frameworks allow for this, code looks slightly prettier because there are +fewer imports and less code. For example, compare this: + +.. code-block:: python + :linenos: + + from pyramid.response import Response + + def aview(request): + return Response("Hello world!") + +To this: + +.. code-block:: python + :linenos: + + def aview(request): + return "Hello world!" + +Nicer to look at, right? + +Out of the box, :app:`Pyramid` will raise an exception if you try to run the +second example above. After all, a view should return a response, and "explicit +is better than implicit". + +But if you're a developer who likes the aesthetics of simplicity, +:app:`Pyramid` provides an way to support this sort of thing, the +:term:`response adapter`\ : + +.. code-block:: python + :linenos: + + from pyramid.config import Configurator + from pyramid.response import Response + + def string_response_adapter(s): + response = Response(s) + response.content_type = 'text/html' + return response + +A new response adapter is registered in configuration: + + if __name__ == '__main__': + config = Configurator() + config.add_response_adapter(string_response_adapter, basestring) + +With that, you may return strings from any of your view callables, e.g.: + +.. code-block:: python + :linenos: + + def helloview(request): + return "Hello world!" + + def goodbyeview(request): + return "Goodbye world!" + +You can even use a :term:`response adapter` to allow for custom content types +and return codes: + +.. code-block:: python + :linenos: + + from pyramid.config import Configurator + + def tuple_response_adapter(val): + status_int, content_type, body = val + response = Response(body) + response.content_type = content_type + response.status_int = status_int + return response + + def string_response_adapter(body): + response = Response(body) + response.content_type = 'text/html' + response.status_int = 200 + return response + + if __name__ == '__main__': + config = Configurator() + config.add_response_adapter(string_response_adapter, basestring) + config.add_response_adapter(tuple_response_adapter, tuple) + +With this, both of these views will work as expected: + +.. code-block:: python + :linenos: + + def aview(request): + return "Hello world!" + + def anotherview(request): + return (403, 'text/plain', "Forbidden") + +.. seealso:: + + See also :ref:`using_iresponse`. + +Use Global Response Objects +--------------------------- + +Views have to return responses. But constructing them in view code is a chore. +And perhaps registering a :term:`response adapter` as shown above is just too +much work. :app:`Pyramid` provides a global response object as well. You can +use it directly, if you prefer: + +.. code-block:: python + :linenos: + + def aview(request): + response = request.response + response.body = 'Hello world!' + response.content_type = 'text/plain' + return response + +.. seealso:: + + See also :ref:`request_response_attr`. + +Extend Configuration +-------------------- + +Perhaps the :app:`Pyramid` configurator's syntax feels a bit verbose to you. Or +possibly you would like to add a feature to configuration without asking the +core developers to change :app:`Pyramid` itself? + +You can extend :app:`Pyramid`\ 's :term:`configurator` with your own +directives. For example, let's say you find yourself calling +:meth:`pyramid.config.Configurator.add_view` repetitively. Usually you can get +rid of the boring with existing shortcuts, but let's say that this is a case +where there is no such shortcut: + +.. code-block:: python + :linenos: + + from pyramid.config import Configurator + + config = Configurator() + config.add_route('xhr_route', '/xhr/{id}') + config.add_view('my.package.GET_view', route_name='xhr_route', + xhr=True, permission='view', request_method='GET') + config.add_view('my.package.POST_view', route_name='xhr_route', + xhr=True, permission='view', request_method='POST') + config.add_view('my.package.HEAD_view', route_name='xhr_route', + xhr=True, permission='view', request_method='HEAD') + +Pretty tedious right? You can add a directive to the :app:`Pyramid` +:term:`configurator` to automate some of the tedium away: + +.. code-block:: python + :linenos: + + from pyramid.config import Configurator + + def add_protected_xhr_views(config, module): + module = config.maybe_dotted(module) + for method in ('GET', 'POST', 'HEAD'): + view = getattr(module, 'xhr_%s_view' % method, None) + if view is not None: + config.add_view(view, route_name='xhr_route', xhr=True, + permission='view', request_method=method) + + config = Configurator() + config.add_directive('add_protected_xhr_views', add_protected_xhr_views) + +Once that's done, you can call the directive you've just added as a method of +the :term:`configurator` object: + +.. code-block:: python + :linenos: + + config.add_route('xhr_route', '/xhr/{id}') + config.add_protected_xhr_views('my.package') + +Much better! + +You can share your configuration code with others, too. Package it up and call +:meth:`~pyramid.config.Configurator.add_directive` from within a function +called when another user uses the +:meth:`~pyramid.config.Configurator.include` method against your code. + +.. seealso:: + + See also :ref:`add_directive`. + +Introspect Your Application +--------------------------- + +If you're building a large, pluggalbe system, it's useful to be able to get a +list of what has been plugged in *at application runtime*. For example, you +might want to show users a set of tabs at the top of the screen based on a list +of the views they registered. + +:app:`Pyramid` provides an :term:`introspector` for just this purpose. + +Here's an example of using Pyramid's introspector from within a view callable: + +.. code-block:: python + :linenos: + + from pyramid.view import view_config + from pyramid.response import Response + + @view_config(route_name='bar') + def show_current_route_pattern(request): + introspector = request.registry.introspector + route_name = request.matched_route.name + route_intr = introspector.get('routes', route_name) + return Response(str(route_intr['pattern'])) + +.. seealso:: + + See also :ref:`using_introspection`. \ No newline at end of file diff --git a/docs/narr/advfeatures.rst b/docs/narr/advfeatures.rst deleted file mode 100644 index 5c45cf61a..000000000 --- a/docs/narr/advfeatures.rst +++ /dev/null @@ -1,399 +0,0 @@ -Advanced :app:`Pyramid` Design Features -======================================= - -Pyramid has been built from the ground up to avoid the problems that other -frameworks can suffer. - - -You Don't Need Singletons -------------------------- - -Have you ever struggled with parametrizing Django's ``settings.py`` file for -multiple installations of the same Django application? Have you ever needed to -monkey-patch a framework fixture to get it to behave properly for your -use-case? Have you ever tried to deploy your application using an asynchronous -server and failed? - -All these problems are symptoms of "mutable global state", also known as -"import-time side effects" and arise from the use of "singleton" data structures. - -:app:`Pyramid` is written so that you don't run into these types of problems. -It is even possible to run multiple copies of the *same* :app:`Pyramid` -application configured differently within a single Python process. This makes -running Pyramid in shared hosting environments a snap. - -Simplify your View Code with Predicates ---------------------------------------- - -How many times have you found yourself beginning the logic of your view code -with something like this:: - - if request.user.is_authenticated: - # do one thing - else: - # do something else - -Unlike many other systems, Pyramid allows you to associate more than one view -with a single route. For example, you can create a route with the pattern -``/items`` and when the route is matched, you can send the request to one view -if the request method is GET, another view if the request method is POST, and -so on. - -:app:`Pyramid` uses a system of "view predicates" to allow this. Matching the -request method is one basic thing you can do with a view predicate. You can -also associate views with other request parameters, such as elements in the -query string, the Accept header, whether the request is an XHR request or not, -and lots of other things. - -For our example above, you can do this instead:: - - @view_config(route_name="items", effective_principals=pyramid.security.Authenticated) - def auth_view(request): - # do one thing - - @view_config(route_name="items") - def anon_view(request): - # do something else - -This approach allows you to develop view code that is simpler, more easily -understandable, and more directly testable. - -Example: :ref:`view_configuration_parameters`. - -Stop Worrying About Transactions --------------------------------- - -Pyramid's :term:`scaffold` system renders projects that include a *transaction -management* system. When you use this system, you can stop worrying about when -to commit your changes, :app:`Pyramid` handles it for you. The system will -commit at the end of a request or aborts if there was an exception. - -Why is that a good thing? Imagine a situation where you manually commit a -change to your persistence layer. It's very likely that other framework code -will run *after* your changes are done. If an error happens in that other code, -you can easily wind up with inconsistent data if you're not extremely careful. - -Using transaction management saves you from needing to think about this. Either -a request completes successfully, and all changes are committed, or it does -not, and all changes are aborted. - -Pyramid's transaction management is extendable, so you can synchronize commits -between multiple databases, or databases of different kinds. It also allows you -to do things like conditionally send email if a transaction commits, but -otherwise keep quiet. - -Example: :ref:`bfg_sql_wiki_tutorial` (note the lack of commit statements -anywhere in application code). - -Stop Worrying About Configuration ---------------------------------- - -When a system is small, it's reasonably easy to keep it all in your head. But -as systems grow large, configuration grows more complex. Your app may grow to -have hundreds or even thousands of configuration statements. - -Pyramid's configuration system keeps track of your configuration. If you -accidentally add two that are identical, or Pyramid can't make sense out of -what it would mean to have both statements active at the same time, it will -complain loudly at startup time. - -Pyramid's configuration system is not dumb though. If you use the confugration -:meth:`~pyramid.config.Configurator.include` system, it can automatically -resolve conflicts on its own. "More local" statements are preferred over "less -local" ones. So you can intelligently factor large systems into smaller ones. - -Example: :ref:`conflict_detection`. - -Compose Powerful Apps From Simple Parts ----------------------------------------- - -Speaking of the :app:`Pyramid` structured "include" mechanism (see -:meth:`~pyramid.config.Configurator.include`), it allows you to compose complex -applications from multiple, simple Python packages. All the configuration -statements that can be performed in your "main" Pyramid application can also be -used in included packages. You can add views, routes, and subscribers, and even -set authentication and authorization policies. - -If you need, you can extend or override the configuration of an existing -application by including its configuration in your own and then modifying it. - - -For example, if you want to reuse an existing application that already has a -bunch of routes, you can just use the ``include`` statement with a -``route_prefix``. All the routes of that application will be availabe, prefixed -as you requested: - -.. code-block:: python - :linenos: - - from pyramid.config import Configurator - - if __name__ == '__main__': - config = Configurator() - config.include('pyramid_jinja2') - config.include('pyramid_exclog') - config.include('some.other.package', route_prefix='/somethingelse') - -.. seealso:: - - See also :ref:`including_configuration` and - :ref:`building_an_extensible_app`. - -Authenticate Users Your Way ---------------------------- - -:app:`Pyramid` ships with prebuilt well-tested authentication and authorization -schemes out of the box. Using a scheme is a matter of configuration. So if you -need to change approaches later, you need only update your configuration. - -In addition, the system that handles authentication an authorization is -flexible and pluggable. If you want to use another security add-on, or define -your own, you can. And again, you need only update your application -configuration to make the change. - -Example: :ref:`enabling_authorization_policy`. - -Build Trees of Resources ------------------------- - -:app:`Pyramid` supports :term:`Traversal`, a way of mapping URLs to a concrete -tree of resources. If your application naturally consists of an arbitrary -heirarchy of different types of content (like a CMS or a Document Management -System), traversal is for you. If you have a requirement for a highly granular -security model ("Jane can edit documents in *this* folder, but not *that* -one"), traversal can be a powerful approach. - -Examples: :ref:`hello_traversal_chapter` and -:ref:`much_ado_about_traversal_chapter`. - -Take Action on Each Request with Tweens ---------------------------------------- - -Pyramid has a system for applying arbitrary actions to each request or response -called *tweens*. The system is similar in concept to WSGI :term:`middleware`, -but can be more useful since they run in the Pyramid context, and have access -to templates, request objects, and other niceties. - -The Pyramid debug toolbar is a "tween", as is the ``pyramid_tm`` transaction -manager. - -Example: :ref:`registering_tweens`. - -Return What You Want From Your Views ------------------------------------- - -We have shown before (in the :doc:`introduction`) how using a :term:`renderer` -allows you to return simple Python dictionaries from your view code. But some -frameworks allow you to return strings or tuples from view callables. -When frameworks allow for this, code looks slightly prettier, because there are -fewer imports, and less code. For example, compare this: - -.. code-block:: python - :linenos: - - from pyramid.response import Response - - def aview(request): - return Response("Hello world!") - -To this: - -.. code-block:: python - :linenos: - - def aview(request): - return "Hello world!" - -Nicer to look at, right? - -Out of the box, Pyramid will raise an exception if you try to run the second -example above. After all, a view should return a response, and "explicit is -better than implicit". - -But if you're a developer who likes the aesthetics of simplicity, Pyramid -provides an way to support this sort of thing, the *response adapter*: - -.. code-block:: python - :linenos: - - from pyramid.config import Configurator - from pyramid.response import Response - - def string_response_adapter(s): - response = Response(s) - response.content_type = 'text/html' - return response - -A new response adapter is registered in configuration: - - if __name__ == '__main__': - config = Configurator() - config.add_response_adapter(string_response_adapter, basestring) - -With that, you may return strings from any of your view callables, e.g.: - -.. code-block:: python - :linenos: - - def helloview(request): - return "Hello world!" - - def goodbyeview(request): - return "Goodbye world!" - -You can even use a response adapter to allow for custom content types and -return codes: - -.. code-block:: python - :linenos: - - from pyramid.config import Configurator - - def tuple_response_adapter(val): - status_int, content_type, body = val - response = Response(body) - response.content_type = content_type - response.status_int = status_int - return response - - def string_response_adapter(body): - response = Response(body) - response.content_type = 'text/html' - response.status_int = 200 - return response - - if __name__ == '__main__': - config = Configurator() - config.add_response_adapter(string_response_adapter, basestring) - config.add_response_adapter(tuple_response_adapter, tuple) - -With this, both of these views will work as expected: - -.. code-block:: python - :linenos: - - def aview(request): - return "Hello world!" - - def anotherview(request): - return (403, 'text/plain', "Forbidden") - -.. seealso:: - - See also :ref:`using_iresponse`. - -Use Global Response Objects ---------------------------- - -Views have to return responses. But constructing them in view code is a chore. -And perhaps registering a response adapter as shown above is just too much -work. :app:`Pyramid` provides a global response object as well. You can just -use it directly, if you prefer: - -.. code-block:: python - :linenos: - - def aview(request): - response = request.response - response.body = 'Hello world!' - response.content_type = 'text/plain' - return response - -.. seealso:: - - See also :ref:`request_response_attr`. - -Extend Configuration --------------------- - -Perhaps the :app:`Pyramid` configurator's syntax feels a bit verbose to you. Or -possibly you would like to add a feature to configuration without asking the -core developers to change Pyramid itself? - -You can extend Pyramid's :term:`Configurator` with your own directives. For -example, let's say you find yourself calling -:meth:`pyramid.config.Configurator.add_view` repetitively. Usually you can get -rid of the boring with existing shortcuts, but let's say that this is a case -where there is no such shortcut: - -.. code-block:: python - :linenos: - - from pyramid.config import Configurator - - config = Configurator() - config.add_route('xhr_route', '/xhr/{id}') - config.add_view('my.package.GET_view', route_name='xhr_route', - xhr=True, permission='view', request_method='GET') - config.add_view('my.package.POST_view', route_name='xhr_route', - xhr=True, permission='view', request_method='POST') - config.add_view('my.package.HEAD_view', route_name='xhr_route', - xhr=True, permission='view', request_method='HEAD') - -Pretty tedious right? You can add a directive to the Pyramid configurator to -automate some of the tedium away: - -.. code-block:: python - :linenos: - - from pyramid.config import Configurator - - def add_protected_xhr_views(config, module): - module = config.maybe_dotted(module) - for method in ('GET', 'POST', 'HEAD'): - view = getattr(module, 'xhr_%s_view' % method, None) - if view is not None: - config.add_view(view, route_name='xhr_route', xhr=True, - permission='view', request_method=method) - - config = Configurator() - config.add_directive('add_protected_xhr_views', add_protected_xhr_views) - -Once that's done, you can call the directive you've just added as a method of -the Configurator object: - -.. code-block:: python - :linenos: - - config.add_route('xhr_route', '/xhr/{id}') - config.add_protected_xhr_views('my.package') - -Much better! - -You can share your configuration code with others, too. Package it up and call -:meth:`~pyramid.config.Configurator.add_directive` from within a function -called when another user uses the -:meth:`~pyramid.config.Configurator.include` method against your code. - -.. seealso:: - - See also :ref:`add_directive`. - -Introspect Your Application ---------------------------- - -If you're building a large, pluggalbe system, it's useful to be able to get a -list of what has been plugged in *at application runtime*. For example, you -might want to show users a set of tabs at the top of the screen based on a list -of the views they registered. - -:app:`Pyramid` provides an :term:`introspector` for just this purpose. - -Here's an example of using Pyramid's introspector from within a view callable: - -.. code-block:: python - :linenos: - - from pyramid.view import view_config - from pyramid.response import Response - - @view_config(route_name='bar') - def show_current_route_pattern(request): - introspector = request.registry.introspector - route_name = request.matched_route.name - route_intr = introspector.get('routes', route_name) - return Response(str(route_intr['pattern'])) - -.. seealso:: - - See also :ref:`using_introspection`. \ No newline at end of file diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst index 63bc164fb..a8d417250 100644 --- a/docs/narr/introduction.rst +++ b/docs/narr/introduction.rst @@ -492,7 +492,7 @@ that make it adaptable. Read more about them below. .. toctree:: :maxdepth: 2 - advfeatures + advanced-features -- cgit v1.2.3 From 44c621a5b8320848933024280dc491dec844c184 Mon Sep 17 00:00:00 2001 From: cewing Date: Tue, 23 May 2017 21:07:42 -0700 Subject: finish polishing the advanced configuration doc per code review --- docs/narr/advanced-features.rst | 44 +++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/docs/narr/advanced-features.rst b/docs/narr/advanced-features.rst index a97d4f3b1..35841631f 100644 --- a/docs/narr/advanced-features.rst +++ b/docs/narr/advanced-features.rst @@ -245,6 +245,8 @@ But if you're a developer who likes the aesthetics of simplicity, A new response adapter is registered in configuration: +.. code-block:: python + if __name__ == '__main__': config = Configurator() config.add_response_adapter(string_response_adapter, basestring) @@ -325,15 +327,14 @@ use it directly, if you prefer: Extend Configuration -------------------- -Perhaps the :app:`Pyramid` configurator's syntax feels a bit verbose to you. Or -possibly you would like to add a feature to configuration without asking the -core developers to change :app:`Pyramid` itself? +Perhaps the :app:`Pyramid` configurator's syntax feels a bit verbose to you. +Or possibly you would like to add a feature to configuration +without asking the core developers to change :app:`Pyramid` itself? -You can extend :app:`Pyramid`\ 's :term:`configurator` with your own -directives. For example, let's say you find yourself calling -:meth:`pyramid.config.Configurator.add_view` repetitively. Usually you can get -rid of the boring with existing shortcuts, but let's say that this is a case -where there is no such shortcut: +You can extend :app:`Pyramid`\ 's :term:`configurator` with your own directives. +For example, let's say you find yourself calling :meth:`pyramid.config.Configurator.add_view` repetitively. +Usually you can get rid of the boring with existing shortcuts, +but let's say that this is a case where there is no such shortcut: .. code-block:: python :linenos: @@ -349,8 +350,8 @@ where there is no such shortcut: config.add_view('my.package.HEAD_view', route_name='xhr_route', xhr=True, permission='view', request_method='HEAD') -Pretty tedious right? You can add a directive to the :app:`Pyramid` -:term:`configurator` to automate some of the tedium away: +Pretty tedious right? +You can add a directive to the :app:`Pyramid` :term:`configurator` to automate some of the tedium away: .. code-block:: python :linenos: @@ -368,8 +369,8 @@ Pretty tedious right? You can add a directive to the :app:`Pyramid` config = Configurator() config.add_directive('add_protected_xhr_views', add_protected_xhr_views) -Once that's done, you can call the directive you've just added as a method of -the :term:`configurator` object: +Once that's done, +you can call the directive you've just added as a method of the :term:`configurator` object: .. code-block:: python :linenos: @@ -379,10 +380,11 @@ the :term:`configurator` object: Much better! -You can share your configuration code with others, too. Package it up and call -:meth:`~pyramid.config.Configurator.add_directive` from within a function -called when another user uses the -:meth:`~pyramid.config.Configurator.include` method against your code. +You can share your configuration code with others, too. +Add your code to a Python package. +Put the call to :meth:`~pyramid.config.Configurator.add_directive` in a function. +When other programmers install your package, +they'll be able to use your configuration by passing your function to a call to :meth:`~pyramid.config.Configurator.include`. .. seealso:: @@ -391,14 +393,14 @@ called when another user uses the Introspect Your Application --------------------------- -If you're building a large, pluggalbe system, it's useful to be able to get a -list of what has been plugged in *at application runtime*. For example, you -might want to show users a set of tabs at the top of the screen based on a list -of the views they registered. +If you're building a large, pluggable system, +it's useful to be able to get a list of what has been plugged in *at application runtime*. +For example, you might want to show users a set of tabs at the top of the screen +based on a list of the views they registered. :app:`Pyramid` provides an :term:`introspector` for just this purpose. -Here's an example of using Pyramid's introspector from within a view callable: +Here's an example of using :app:`Pyramid`\ 's :term:`introspector` from within a view: .. code-block:: python :linenos: -- cgit v1.2.3 From 5c6e8dfc9e9d91468b2a570eb763d146b8272d76 Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Wed, 24 May 2017 01:24:35 -0700 Subject: Add myself in the contributors list --- CONTRIBUTORS.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 2e49a193a..cbee08d0d 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -300,3 +300,5 @@ Contributors - Aleph Melo, 2017/04/16 - Jeremy(Ching-Rui) Chen, 2017/04/19 + +- Fang-Pen Lin, 2017/05/22 -- cgit v1.2.3 From 7c20d8ca305c8cdcc72fd721054b9ed2b783f02d Mon Sep 17 00:00:00 2001 From: Chris Morales Date: Wed, 24 May 2017 14:51:25 -0700 Subject: updated documentation showing the pyramid_tm.explicit_manager being set --- docs/tutorials/wiki2/src/authorization/tutorial/models/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/authorization/tutorial/models/__init__.py index 8147052ad..cd8347ccd 100644 --- a/docs/tutorials/wiki2/src/authorization/tutorial/models/__init__.py +++ b/docs/tutorials/wiki2/src/authorization/tutorial/models/__init__.py @@ -58,6 +58,7 @@ def includeme(config): """ settings = config.get_settings() + settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' # use pyramid_tm to hook the transaction lifecycle to the request config.include('pyramid_tm') -- cgit v1.2.3 From e20ed7d9271d4e824a08d23be1ed942db0756a86 Mon Sep 17 00:00:00 2001 From: cewing Date: Thu, 25 May 2017 09:05:35 -0700 Subject: fix code indentation and unify style for all code blocks, per CR --- docs/narr/advanced-features.rst | 165 ++++++++++++++++++++-------------------- 1 file changed, 84 insertions(+), 81 deletions(-) diff --git a/docs/narr/advanced-features.rst b/docs/narr/advanced-features.rst index 35841631f..ae28ba41b 100644 --- a/docs/narr/advanced-features.rst +++ b/docs/narr/advanced-features.rst @@ -30,6 +30,7 @@ How many times have you found yourself beginning the logic of your view code with something like this: .. code-block:: python + :linenos: if request.user.is_authenticated: # do one thing @@ -51,6 +52,7 @@ the request is an AJAX (XHR) request or not, and lots of other things. For our example above, you can do this instead: .. code-block:: python + :linenos: @view_config(route_name="items", effective_principals=pyramid.security.Authenticated) def auth_view(request): @@ -135,15 +137,15 @@ bunch of routes, you can just use the ``include`` statement with a as you requested: .. code-block:: python - :linenos: + :linenos: - from pyramid.config import Configurator + from pyramid.config import Configurator - if __name__ == '__main__': - config = Configurator() - config.include('pyramid_jinja2') - config.include('pyramid_exclog') - config.include('some.other.package', route_prefix='/somethingelse') + if __name__ == '__main__': + config = Configurator() + config.include('pyramid_jinja2') + config.include('pyramid_exclog') + config.include('some.other.package', route_prefix='/somethingelse') .. seealso:: @@ -207,20 +209,20 @@ When frameworks allow for this, code looks slightly prettier because there are fewer imports and less code. For example, compare this: .. code-block:: python - :linenos: + :linenos: - from pyramid.response import Response + from pyramid.response import Response - def aview(request): - return Response("Hello world!") + def aview(request): + return Response("Hello world!") To this: .. code-block:: python - :linenos: + :linenos: - def aview(request): - return "Hello world!" + def aview(request): + return "Hello world!" Nicer to look at, right? @@ -233,71 +235,72 @@ But if you're a developer who likes the aesthetics of simplicity, :term:`response adapter`\ : .. code-block:: python - :linenos: + :linenos: - from pyramid.config import Configurator - from pyramid.response import Response + from pyramid.config import Configurator + from pyramid.response import Response - def string_response_adapter(s): - response = Response(s) - response.content_type = 'text/html' - return response + def string_response_adapter(s): + response = Response(s) + response.content_type = 'text/html' + return response A new response adapter is registered in configuration: .. code-block:: python + :linenos: - if __name__ == '__main__': - config = Configurator() - config.add_response_adapter(string_response_adapter, basestring) + if __name__ == '__main__': + config = Configurator() + config.add_response_adapter(string_response_adapter, basestring) With that, you may return strings from any of your view callables, e.g.: .. code-block:: python - :linenos: + :linenos: - def helloview(request): - return "Hello world!" + def helloview(request): + return "Hello world!" - def goodbyeview(request): - return "Goodbye world!" + def goodbyeview(request): + return "Goodbye world!" You can even use a :term:`response adapter` to allow for custom content types and return codes: .. code-block:: python - :linenos: + :linenos: - from pyramid.config import Configurator + from pyramid.config import Configurator - def tuple_response_adapter(val): - status_int, content_type, body = val - response = Response(body) - response.content_type = content_type - response.status_int = status_int - return response + def tuple_response_adapter(val): + status_int, content_type, body = val + response = Response(body) + response.content_type = content_type + response.status_int = status_int + return response - def string_response_adapter(body): - response = Response(body) - response.content_type = 'text/html' - response.status_int = 200 - return response + def string_response_adapter(body): + response = Response(body) + response.content_type = 'text/html' + response.status_int = 200 + return response - if __name__ == '__main__': - config = Configurator() - config.add_response_adapter(string_response_adapter, basestring) - config.add_response_adapter(tuple_response_adapter, tuple) + if __name__ == '__main__': + config = Configurator() + config.add_response_adapter(string_response_adapter, basestring) + config.add_response_adapter(tuple_response_adapter, tuple) With this, both of these views will work as expected: .. code-block:: python - :linenos: + :linenos: - def aview(request): - return "Hello world!" + def aview(request): + return "Hello world!" - def anotherview(request): - return (403, 'text/plain', "Forbidden") + def anotherview(request): + return (403, 'text/plain', "Forbidden") .. seealso:: @@ -312,13 +315,13 @@ much work. :app:`Pyramid` provides a global response object as well. You can use it directly, if you prefer: .. code-block:: python - :linenos: + :linenos: - def aview(request): - response = request.response - response.body = 'Hello world!' - response.content_type = 'text/plain' - return response + def aview(request): + response = request.response + response.body = 'Hello world!' + response.content_type = 'text/plain' + return response .. seealso:: @@ -337,46 +340,46 @@ Usually you can get rid of the boring with existing shortcuts, but let's say that this is a case where there is no such shortcut: .. code-block:: python - :linenos: + :linenos: - from pyramid.config import Configurator + from pyramid.config import Configurator - config = Configurator() - config.add_route('xhr_route', '/xhr/{id}') - config.add_view('my.package.GET_view', route_name='xhr_route', - xhr=True, permission='view', request_method='GET') - config.add_view('my.package.POST_view', route_name='xhr_route', - xhr=True, permission='view', request_method='POST') - config.add_view('my.package.HEAD_view', route_name='xhr_route', - xhr=True, permission='view', request_method='HEAD') + config = Configurator() + config.add_route('xhr_route', '/xhr/{id}') + config.add_view('my.package.GET_view', route_name='xhr_route', + xhr=True, permission='view', request_method='GET') + config.add_view('my.package.POST_view', route_name='xhr_route', + xhr=True, permission='view', request_method='POST') + config.add_view('my.package.HEAD_view', route_name='xhr_route', + xhr=True, permission='view', request_method='HEAD') Pretty tedious right? You can add a directive to the :app:`Pyramid` :term:`configurator` to automate some of the tedium away: .. code-block:: python - :linenos: + :linenos: - from pyramid.config import Configurator + from pyramid.config import Configurator - def add_protected_xhr_views(config, module): - module = config.maybe_dotted(module) - for method in ('GET', 'POST', 'HEAD'): - view = getattr(module, 'xhr_%s_view' % method, None) - if view is not None: - config.add_view(view, route_name='xhr_route', xhr=True, - permission='view', request_method=method) + def add_protected_xhr_views(config, module): + module = config.maybe_dotted(module) + for method in ('GET', 'POST', 'HEAD'): + view = getattr(module, 'xhr_%s_view' % method, None) + if view is not None: + config.add_view(view, route_name='xhr_route', xhr=True, + permission='view', request_method=method) - config = Configurator() - config.add_directive('add_protected_xhr_views', add_protected_xhr_views) + config = Configurator() + config.add_directive('add_protected_xhr_views', add_protected_xhr_views) Once that's done, you can call the directive you've just added as a method of the :term:`configurator` object: .. code-block:: python - :linenos: + :linenos: - config.add_route('xhr_route', '/xhr/{id}') - config.add_protected_xhr_views('my.package') + config.add_route('xhr_route', '/xhr/{id}') + config.add_protected_xhr_views('my.package') Much better! -- cgit v1.2.3 From 981a9df80716ca622324d653117b9c7c3ada70c2 Mon Sep 17 00:00:00 2001 From: cewing Date: Thu, 25 May 2017 09:05:55 -0700 Subject: fix more style issues per CR --- docs/narr/introduction.rst | 82 +++++++++++++++++++++++++++------------------- 1 file changed, 48 insertions(+), 34 deletions(-) diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst index a8d417250..19b8fcdb8 100644 --- a/docs/narr/introduction.rst +++ b/docs/narr/introduction.rst @@ -73,44 +73,47 @@ working on a framework that is up-to-date and forward-looking. Tested ~~~~~~ -Untested code is broken by design. The Pyramid community has a strong testing -culture and our framework reflects that. Every release of Pyramid has 100% -statement coverage [#]_ and 95% decision/condition coverage. [#]_ It is -automatically tested using `Travis `_ and -`Jenkins `_ on Python 2.7, -Python 3.4, Python 3.5, and PyPy after each commit to its GitHub repository. +Untested code is broken by design. +The Pyramid community has a strong testing culture and our framework reflects that. +Every release of Pyramid has 100% statement coverage (as measured by `coverage `_) +and 95% decision/condition coverage. (as measured by `instrumental `_) +It is automatically tested using `Travis `_ +and `Jenkins `_ +on supported versions of Python after each commit to its GitHub repository. `Official Pyramid add-ons `_ are held to a similar testing standard. We still find bugs in Pyramid, but we've noticed we find a lot fewer of them while working on projects with a solid testing regime. -.. [#] as measured by `coverage `_ -.. [#] as measured by `instrumental `_ - Documented ~~~~~~~~~~ -The Pyramid documentation is comprehensive. We strive to keep our narrative -documentation both complete and friendly to newcomers. We also maintain a -:ref:`cookbook ` of recipes, demonstrations of -common scenarios you might face. And contributions in the form of improvements -to our documentation are always appreciated. +The Pyramid documentation is comprehensive. +We strive to keep our narrative documentation both complete and friendly to newcomers. +We also maintain a :ref:`cookbook ` of recipes, +demonstrations of common scenarios you might face. +Contributions in the form of improvements to our documentation are always appreciated. +And we always welcome improvements to our `official tutorials `_ +as well as new contributions to our `community maintained tutorials `_. Supported ~~~~~~~~~ -You can get help quickly with Pyramid. It's our goal that no Pyramid question -go unanswered. Whether you ask a question on IRC, on the Pylons-discuss mailing -list, or on StackOverflow, you're likely to get a reasonably prompt response. +You can get help quickly with :app:`Pyramid`. +It's our goal that no :app:`Pyramid` question go unanswered. +Whether you ask a question on IRC, on the Pylons-discuss mailing list, or on StackOverflow, +you're likely to get a reasonably prompt response. + +:app:`Pyramid` is also a welcoming, friendly space for newcomers. +We don't tolerate "support trolls" or those who enjoy berating fellow users in our support channels. +We try to keep it well-lit and new-user-friendly. -Pyramid is also a welcoming, friendly space for newcomers. We don't tolerate -"support trolls" or those who enjoy berating fellow users in our support -channels. We try to keep it well-lit and new-user-friendly. +.. seealso:: -Example: Visit irc\://freenode.net#pyramid (the ``#pyramid`` channel on -irc.freenode.net in an IRC client) or the pylons-discuss maillist at -https://groups.google.com/forum/#!forum/pylons-discuss. + See also our `#pyramid IRC channel `_, + our `pylons-discuss mailing list `_, + and :ref:`support-and-development`. .. _what_makes_pyramid_unique: @@ -135,8 +138,7 @@ framework should be able to be good at both. Pyramid is that kind of framework. Pyramid provides a set of features that are unique among Python web frameworks. Others may provide some, but only Pyramid provides them all, in one place, -fully documented, and useful *à la carte* without needing to pay for the whole -banquet. +fully documented, and *à la carte* without needing to pay for the whole banquet. Build single-file applications @@ -170,12 +172,14 @@ you don't have to switch files to see your configuration. For example: return Response('fred') However, using Pyramid configuration decorators does not change your code. It -remains easy to extend, test or reuse. You can test your code as if the +remains easy to extend, test, or reuse. You can test your code as if the decorators were not there. You can instruct the framework to ignore some decorators. You can even use an imperative style to write your configuration, skipping decorators entirely. -Example: :ref:`mapping_views_using_a_decorator_section`. +.. seealso:: + + See also :ref:`mapping_views_using_a_decorator_section`. Generate application URLs ~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -185,7 +189,9 @@ viewing. Pyramid provides flexible, consistent, easy to use tools for generating URLs. When you use these tools to write your application, you can change your configuration without fear of breaking links in your web pages. -Example: :ref:`generating_route_urls`. +.. seealso:: + + See also :ref:`generating_route_urls`. Serve static assets ~~~~~~~~~~~~~~~~~~~ @@ -197,7 +203,9 @@ server or CDN (content delivery network). Either way, Pyramid can help you to generate URLs so you can change where your files come from without changing any code. -Example: :ref:`static_assets_section`. +.. seealso:: + + See also :ref:`static_assets_section`. Develop interactively ~~~~~~~~~~~~~~~~~~~~~ @@ -216,7 +224,9 @@ around from your browser to find out what happened. To use the Pyramid debug toolbar, build your project with a Pyramid :term:`cookiecutter`. -Example: :ref:`debug_toolbar`. +.. seealso:: + + See also :ref:`debug_toolbar`. Debug with power ~~~~~~~~~~~~~~~~ @@ -232,20 +242,24 @@ Pyramid also has command line tools to help you verify your configuration. You can use ``proutes`` and ``pviews`` to inspect how URLs are connected to your application code. -Examples: :ref:`debug_authorization_section` and :ref:`command_line_chapter`. +.. seealso:: + + See also :ref:`debug_authorization_section`, :ref:`command_line_chapter`, + and :doc:`../pscripts/index` Extend your application ~~~~~~~~~~~~~~~~~~~~~~~ Pyramid add-ons extend the core of the framework with useful abilities. There are add-ons available for your favorite template language, SQL and NoSQL -databases, authentication services and much much more. +databases, authentication services and more. Supported Pyramid add-ons are held to the same demanding standards as the framework itself. You will find them to be fully tested and well documented. -Examples: -https://trypyramid.com/resources-extending-pyramid.html +.. seealso:: + + See also https://trypyramid.com/resources-extending-pyramid.html Write your views, *your* way ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- cgit v1.2.3 From 7dbc4a7a4b480365461ce882670070c932b3fcd9 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Thu, 1 Jun 2017 11:41:41 -0700 Subject: clarify badges for releasing --- README.rst | 12 ++++-------- RELEASING.txt | 4 ++-- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/README.rst b/README.rst index 5f42115df..4125dac01 100644 --- a/README.rst +++ b/README.rst @@ -1,17 +1,13 @@ Pyramid ======= -.. image:: https://travis-ci.org/Pylons/pyramid.png?branch=1.9-branch +.. image:: https://travis-ci.org/Pylons/pyramid.png?branch=master :target: https://travis-ci.org/Pylons/pyramid :alt: master Travis CI Status -.. image:: https://readthedocs.org/projects/pyramid/badge/?version=1.9-branch - :target: http://docs.pylonsproject.org/projects/pyramid/en/1.9-branch/ - :alt: Master Documentation Status - -.. image:: https://readthedocs.org/projects/pyramid/badge/?version=1.9-branch - :target: http://docs.pylonsproject.org/projects/pyramid/en/1.9-branch/ - :alt: Latest Documentation Status +.. image:: https://readthedocs.org/projects/pyramid/badge/?version=master + :target: http://docs.pylonsproject.org/projects/pyramid/en/master/ + :alt: master Documentation Status .. image:: https://img.shields.io/badge/irc-freenode-blue.svg :target: https://webchat.freenode.net/?channels=pyramid diff --git a/RELEASING.txt b/RELEASING.txt index 58ebb2fb3..24bbb3e77 100644 --- a/RELEASING.txt +++ b/RELEASING.txt @@ -48,8 +48,8 @@ Prepare new release branch include a link under "Bug Fix Releases" to the minor feature changes in CHANGES.txt. -- Update README.rst to use correct versions of badges and URLs according to - each branch and context, i.e., RTD "latest" == GitHub/Travis "1.x-branch". +- Update README.rst to use correct versions of badges, URLs, and ALT option + according to each branch and context. - Update whatsnew-X.X.rst in docs to point at change log entries for individual releases if applicable. -- cgit v1.2.3 From 93c94e102f5b6d732fb06ca8d18383154e2c9636 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Thu, 1 Jun 2017 12:11:58 -0700 Subject: use the version for the mid-release cycle - update releasing.txt accordingly --- README.rst | 10 +++++----- RELEASING.txt | 5 ++++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index 4125dac01..0429c36b5 100644 --- a/README.rst +++ b/README.rst @@ -1,13 +1,13 @@ Pyramid ======= -.. image:: https://travis-ci.org/Pylons/pyramid.png?branch=master +.. image:: https://travis-ci.org/Pylons/pyramid.png?branch=1.9-branch :target: https://travis-ci.org/Pylons/pyramid - :alt: master Travis CI Status + :alt: 1.9-branch Travis CI Status -.. image:: https://readthedocs.org/projects/pyramid/badge/?version=master - :target: http://docs.pylonsproject.org/projects/pyramid/en/master/ - :alt: master Documentation Status +.. image:: https://readthedocs.org/projects/pyramid/badge/?version=1.9-branch + :target: http://docs.pylonsproject.org/projects/pyramid/en/1.9-branch/ + :alt: 1.9-branch Documentation Status .. image:: https://img.shields.io/badge/irc-freenode-blue.svg :target: https://webchat.freenode.net/?channels=pyramid diff --git a/RELEASING.txt b/RELEASING.txt index 24bbb3e77..f780a607b 100644 --- a/RELEASING.txt +++ b/RELEASING.txt @@ -49,7 +49,7 @@ Prepare new release branch changes in CHANGES.txt. - Update README.rst to use correct versions of badges, URLs, and ALT option - according to each branch and context. + according to the new release branch name. - Update whatsnew-X.X.rst in docs to point at change log entries for individual releases if applicable. @@ -96,6 +96,9 @@ Prepare master for further development (major releases only) - Change setup.py version to the next version number. +- Update README.rst to use correct versions of badges, URLs, and ALT option + for "master" instead of the major release version. + Update previous version (final releases only) --------------------------------------------- -- cgit v1.2.3 From d179ce929d800fb5a8a43e9fece625cdd2eba25f Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Thu, 1 Jun 2017 18:10:25 -0700 Subject: use shortcut for github URL; update RELEASING.txt - refs: #3042 --- CHANGES.txt | 4 ++++ RELEASING.txt | 8 ++++++++ docs/narr/project.rst | 2 +- docs/quick_tour.rst | 4 ++-- docs/quick_tutorial/cookiecutters.rst | 2 +- docs/tutorials/modwsgi/index.rst | 2 +- docs/tutorials/wiki/installation.rst | 4 ++-- docs/tutorials/wiki2/installation.rst | 4 ++-- 8 files changed, 21 insertions(+), 9 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index c21508a70..fc1d5ae25 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -9,6 +9,10 @@ unreleased resulting source files in tutorials. See https://github.com/Pylons/pyramid/issues/2548 +- Update RELEASING.txt for updating cookiecutters. Change cookiecutter URLs to + use shortcut. + See: https://github.com/Pylons/pyramid/issues/3042 + 1.9a2 (2017-05-09) ================== diff --git a/RELEASING.txt b/RELEASING.txt index f780a607b..cb619dc8d 100644 --- a/RELEASING.txt +++ b/RELEASING.txt @@ -38,6 +38,9 @@ Prepare new release branch $ ./scaffoldtests.sh +- For each ``pyramid-cookiecutter-*``, make a new branch off "master" with the + same name to align with the new Pyramid release branch name. + - Ensure all features of the release are documented (audit CHANGES.txt or communicate with contributors). @@ -102,6 +105,11 @@ Prepare master for further development (major releases only) Update previous version (final releases only) --------------------------------------------- +- In the docs, update the ``cookiecutter`` command by appending the previous + branch name for checkout, for example, ``cookiecutter + gh:Pylons/pyramid-cookiecutter-starter --checkout 1.8-branch``. A search for + ``cookiecutter gh:Pylons/pyramid-cookiecutter-`` should return all instances. + - In docs/conf.py, update values under html_theme_options for in_progress and outdated. Uncomment the sections to enable pylons_sphinx_latesturl. diff --git a/docs/narr/project.rst b/docs/narr/project.rst index a150afc6b..924f0b696 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -85,7 +85,7 @@ On all platforms, generate a project using cookiecutter. .. code-block:: bash - $ cookiecutter https://github.com/Pylons/pyramid-cookiecutter-starter + $ cookiecutter gh:Pylons/pyramid-cookiecutter-starter If prompted for the first item, accept the default ``yes`` by hitting return. diff --git a/docs/quick_tour.rst b/docs/quick_tour.rst index 1265012ab..d6ecd20c9 100644 --- a/docs/quick_tour.rst +++ b/docs/quick_tour.rst @@ -510,7 +510,7 @@ Let's use the cookiecutter ``pyramid-cookiecutter-starter`` to create a starter .. code-block:: bash - $ $VENV/bin/cookiecutter https://github.com/Pylons/pyramid-cookiecutter-starter + $ $VENV/bin/cookiecutter gh:Pylons/pyramid-cookiecutter-starter If prompted for the first item, accept the default ``yes`` by hitting return. @@ -866,7 +866,7 @@ Pyramid and SQLAlchemy are great friends. That friendship includes a cookiecutte .. code-block:: bash $ cd ~ - $ env/bin/cookiecutter https://github.com/Pylons/pyramid-cookiecutter-alchemy + $ env/bin/cookiecutter gh:Pylons/pyramid-cookiecutter-alchemy If prompted for the first item, accept the default ``yes`` by hitting return. diff --git a/docs/quick_tutorial/cookiecutters.rst b/docs/quick_tutorial/cookiecutters.rst index 337a5c535..36ec700f0 100644 --- a/docs/quick_tutorial/cookiecutters.rst +++ b/docs/quick_tutorial/cookiecutters.rst @@ -28,7 +28,7 @@ Steps .. code-block:: bash - $ $VENV/bin/cookiecutter https://github.com/Pylons/pyramid-cookiecutter-starter + $ $VENV/bin/cookiecutter gh:Pylons/pyramid-cookiecutter-starter If prompted for the first item, accept the default ``yes`` by hitting return. diff --git a/docs/tutorials/modwsgi/index.rst b/docs/tutorials/modwsgi/index.rst index 170f2ebc8..10cf8a478 100644 --- a/docs/tutorials/modwsgi/index.rst +++ b/docs/tutorials/modwsgi/index.rst @@ -39,7 +39,7 @@ specific path information for commands and files. .. code-block:: bash $ cd ~ - $ cookiecutter https://github.com/Pylons/pyramid-cookiecutter-starter + $ cookiecutter gh:Pylons/pyramid-cookiecutter-starter If prompted for the first item, accept the default ``yes`` by hitting return. diff --git a/docs/tutorials/wiki/installation.rst b/docs/tutorials/wiki/installation.rst index de057b1cc..570ba7f6c 100644 --- a/docs/tutorials/wiki/installation.rst +++ b/docs/tutorials/wiki/installation.rst @@ -31,7 +31,7 @@ On UNIX .. code-block:: bash $ cd ~ - $ cookiecutter https://github.com/Pylons/pyramid-cookiecutter-zodb + $ cookiecutter gh:Pylons/pyramid-cookiecutter-zodb On Windows ^^^^^^^^^^ @@ -39,7 +39,7 @@ On Windows .. code-block:: doscon c:\> cd \ - c:\> cookiecutter https://github.com/Pylons/pyramid-cookiecutter-zodb + c:\> cookiecutter gh:Pylons/pyramid-cookiecutter-zodb On all operating systems ^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/tutorials/wiki2/installation.rst b/docs/tutorials/wiki2/installation.rst index c61d4360d..3cd04a940 100644 --- a/docs/tutorials/wiki2/installation.rst +++ b/docs/tutorials/wiki2/installation.rst @@ -43,7 +43,7 @@ On UNIX .. code-block:: bash $ cd ~ - $ cookiecutter https://github.com/Pylons/pyramid-cookiecutter-alchemy + $ cookiecutter gh:Pylons/pyramid-cookiecutter-alchemy On Windows ^^^^^^^^^^ @@ -51,7 +51,7 @@ On Windows .. code-block:: doscon c:\> cd \ - c:\> cookiecutter https://github.com/Pylons/pyramid-cookiecutter-alchemy + c:\> cookiecutter gh:Pylons/pyramid-cookiecutter-alchemy On all operating systems ^^^^^^^^^^^^^^^^^^^^^^^^ -- cgit v1.2.3 From 64cf0e5f9e4f4f56a377bd33202c2232c14e2eaf Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Fri, 2 Jun 2017 12:54:32 -0700 Subject: Move docs update to Prepare new release branch --- RELEASING.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/RELEASING.txt b/RELEASING.txt index cb619dc8d..29d999522 100644 --- a/RELEASING.txt +++ b/RELEASING.txt @@ -41,6 +41,11 @@ Prepare new release branch - For each ``pyramid-cookiecutter-*``, make a new branch off "master" with the same name to align with the new Pyramid release branch name. +- In the docs, update the ``cookiecutter`` command with the new branch name, + for example, ``cookiecutter gh:Pylons/pyramid-cookiecutter-starter --checkout + x.y-branch``. A search for ``cookiecutter gh:Pylons/pyramid-cookiecutter-`` + should return all instances to be updated. + - Ensure all features of the release are documented (audit CHANGES.txt or communicate with contributors). @@ -105,11 +110,6 @@ Prepare master for further development (major releases only) Update previous version (final releases only) --------------------------------------------- -- In the docs, update the ``cookiecutter`` command by appending the previous - branch name for checkout, for example, ``cookiecutter - gh:Pylons/pyramid-cookiecutter-starter --checkout 1.8-branch``. A search for - ``cookiecutter gh:Pylons/pyramid-cookiecutter-`` should return all instances. - - In docs/conf.py, update values under html_theme_options for in_progress and outdated. Uncomment the sections to enable pylons_sphinx_latesturl. -- cgit v1.2.3 From c3bb231f4bb36fd30cdf740607c3faeb5b4a1ee5 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Fri, 2 Jun 2017 12:56:14 -0700 Subject: append ` --checkout master` to cookie cutter command --- docs/narr/project.rst | 2 +- docs/quick_tour.rst | 4 ++-- docs/quick_tutorial/cookiecutters.rst | 2 +- docs/tutorials/modwsgi/index.rst | 2 +- docs/tutorials/wiki/installation.rst | 4 ++-- docs/tutorials/wiki2/installation.rst | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/narr/project.rst b/docs/narr/project.rst index 924f0b696..5863926c9 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -85,7 +85,7 @@ On all platforms, generate a project using cookiecutter. .. code-block:: bash - $ cookiecutter gh:Pylons/pyramid-cookiecutter-starter + $ cookiecutter gh:Pylons/pyramid-cookiecutter-starter --checkout master If prompted for the first item, accept the default ``yes`` by hitting return. diff --git a/docs/quick_tour.rst b/docs/quick_tour.rst index d6ecd20c9..2ada97ac3 100644 --- a/docs/quick_tour.rst +++ b/docs/quick_tour.rst @@ -510,7 +510,7 @@ Let's use the cookiecutter ``pyramid-cookiecutter-starter`` to create a starter .. code-block:: bash - $ $VENV/bin/cookiecutter gh:Pylons/pyramid-cookiecutter-starter + $ $VENV/bin/cookiecutter gh:Pylons/pyramid-cookiecutter-starter --checkout master If prompted for the first item, accept the default ``yes`` by hitting return. @@ -866,7 +866,7 @@ Pyramid and SQLAlchemy are great friends. That friendship includes a cookiecutte .. code-block:: bash $ cd ~ - $ env/bin/cookiecutter gh:Pylons/pyramid-cookiecutter-alchemy + $ env/bin/cookiecutter gh:Pylons/pyramid-cookiecutter-alchemy --checkout master If prompted for the first item, accept the default ``yes`` by hitting return. diff --git a/docs/quick_tutorial/cookiecutters.rst b/docs/quick_tutorial/cookiecutters.rst index 36ec700f0..f8568206d 100644 --- a/docs/quick_tutorial/cookiecutters.rst +++ b/docs/quick_tutorial/cookiecutters.rst @@ -28,7 +28,7 @@ Steps .. code-block:: bash - $ $VENV/bin/cookiecutter gh:Pylons/pyramid-cookiecutter-starter + $ $VENV/bin/cookiecutter gh:Pylons/pyramid-cookiecutter-starter --checkout master If prompted for the first item, accept the default ``yes`` by hitting return. diff --git a/docs/tutorials/modwsgi/index.rst b/docs/tutorials/modwsgi/index.rst index 10cf8a478..a409284cc 100644 --- a/docs/tutorials/modwsgi/index.rst +++ b/docs/tutorials/modwsgi/index.rst @@ -39,7 +39,7 @@ specific path information for commands and files. .. code-block:: bash $ cd ~ - $ cookiecutter gh:Pylons/pyramid-cookiecutter-starter + $ cookiecutter gh:Pylons/pyramid-cookiecutter-starter --checkout master If prompted for the first item, accept the default ``yes`` by hitting return. diff --git a/docs/tutorials/wiki/installation.rst b/docs/tutorials/wiki/installation.rst index 570ba7f6c..668fee098 100644 --- a/docs/tutorials/wiki/installation.rst +++ b/docs/tutorials/wiki/installation.rst @@ -31,7 +31,7 @@ On UNIX .. code-block:: bash $ cd ~ - $ cookiecutter gh:Pylons/pyramid-cookiecutter-zodb + $ cookiecutter gh:Pylons/pyramid-cookiecutter-zodb --checkout master On Windows ^^^^^^^^^^ @@ -39,7 +39,7 @@ On Windows .. code-block:: doscon c:\> cd \ - c:\> cookiecutter gh:Pylons/pyramid-cookiecutter-zodb + c:\> cookiecutter gh:Pylons/pyramid-cookiecutter-zodb --checkout master On all operating systems ^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/tutorials/wiki2/installation.rst b/docs/tutorials/wiki2/installation.rst index 3cd04a940..6c6c5d786 100644 --- a/docs/tutorials/wiki2/installation.rst +++ b/docs/tutorials/wiki2/installation.rst @@ -43,7 +43,7 @@ On UNIX .. code-block:: bash $ cd ~ - $ cookiecutter gh:Pylons/pyramid-cookiecutter-alchemy + $ cookiecutter gh:Pylons/pyramid-cookiecutter-alchemy --checkout master On Windows ^^^^^^^^^^ @@ -51,7 +51,7 @@ On Windows .. code-block:: doscon c:\> cd \ - c:\> cookiecutter gh:Pylons/pyramid-cookiecutter-alchemy + c:\> cookiecutter gh:Pylons/pyramid-cookiecutter-alchemy --checkout master On all operating systems ^^^^^^^^^^^^^^^^^^^^^^^^ -- cgit v1.2.3 From 1aa283fac740cf5ca7e6c9a02c6cc1366e328fe8 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Fri, 2 Jun 2017 13:05:39 -0700 Subject: mid-release cycle will be the death of me --- docs/narr/project.rst | 2 +- docs/quick_tour.rst | 4 ++-- docs/quick_tutorial/cookiecutters.rst | 2 +- docs/tutorials/modwsgi/index.rst | 2 +- docs/tutorials/wiki/installation.rst | 4 ++-- docs/tutorials/wiki2/installation.rst | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/narr/project.rst b/docs/narr/project.rst index 5863926c9..f542eae86 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -85,7 +85,7 @@ On all platforms, generate a project using cookiecutter. .. code-block:: bash - $ cookiecutter gh:Pylons/pyramid-cookiecutter-starter --checkout master + $ cookiecutter gh:Pylons/pyramid-cookiecutter-starter --checkout 1.9-branch If prompted for the first item, accept the default ``yes`` by hitting return. diff --git a/docs/quick_tour.rst b/docs/quick_tour.rst index 2ada97ac3..f3a0a27b8 100644 --- a/docs/quick_tour.rst +++ b/docs/quick_tour.rst @@ -510,7 +510,7 @@ Let's use the cookiecutter ``pyramid-cookiecutter-starter`` to create a starter .. code-block:: bash - $ $VENV/bin/cookiecutter gh:Pylons/pyramid-cookiecutter-starter --checkout master + $ $VENV/bin/cookiecutter gh:Pylons/pyramid-cookiecutter-starter --checkout 1.9-branch If prompted for the first item, accept the default ``yes`` by hitting return. @@ -866,7 +866,7 @@ Pyramid and SQLAlchemy are great friends. That friendship includes a cookiecutte .. code-block:: bash $ cd ~ - $ env/bin/cookiecutter gh:Pylons/pyramid-cookiecutter-alchemy --checkout master + $ env/bin/cookiecutter gh:Pylons/pyramid-cookiecutter-alchemy --checkout 1.9-branch If prompted for the first item, accept the default ``yes`` by hitting return. diff --git a/docs/quick_tutorial/cookiecutters.rst b/docs/quick_tutorial/cookiecutters.rst index f8568206d..0f2a24816 100644 --- a/docs/quick_tutorial/cookiecutters.rst +++ b/docs/quick_tutorial/cookiecutters.rst @@ -28,7 +28,7 @@ Steps .. code-block:: bash - $ $VENV/bin/cookiecutter gh:Pylons/pyramid-cookiecutter-starter --checkout master + $ $VENV/bin/cookiecutter gh:Pylons/pyramid-cookiecutter-starter --checkout 1.9-branch If prompted for the first item, accept the default ``yes`` by hitting return. diff --git a/docs/tutorials/modwsgi/index.rst b/docs/tutorials/modwsgi/index.rst index a409284cc..8df432434 100644 --- a/docs/tutorials/modwsgi/index.rst +++ b/docs/tutorials/modwsgi/index.rst @@ -39,7 +39,7 @@ specific path information for commands and files. .. code-block:: bash $ cd ~ - $ cookiecutter gh:Pylons/pyramid-cookiecutter-starter --checkout master + $ cookiecutter gh:Pylons/pyramid-cookiecutter-starter --checkout 1.9-branch If prompted for the first item, accept the default ``yes`` by hitting return. diff --git a/docs/tutorials/wiki/installation.rst b/docs/tutorials/wiki/installation.rst index 668fee098..3e7434bd7 100644 --- a/docs/tutorials/wiki/installation.rst +++ b/docs/tutorials/wiki/installation.rst @@ -31,7 +31,7 @@ On UNIX .. code-block:: bash $ cd ~ - $ cookiecutter gh:Pylons/pyramid-cookiecutter-zodb --checkout master + $ cookiecutter gh:Pylons/pyramid-cookiecutter-zodb --checkout 1.9-branch On Windows ^^^^^^^^^^ @@ -39,7 +39,7 @@ On Windows .. code-block:: doscon c:\> cd \ - c:\> cookiecutter gh:Pylons/pyramid-cookiecutter-zodb --checkout master + c:\> cookiecutter gh:Pylons/pyramid-cookiecutter-zodb --checkout 1.9-branch On all operating systems ^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/tutorials/wiki2/installation.rst b/docs/tutorials/wiki2/installation.rst index 6c6c5d786..56197900c 100644 --- a/docs/tutorials/wiki2/installation.rst +++ b/docs/tutorials/wiki2/installation.rst @@ -43,7 +43,7 @@ On UNIX .. code-block:: bash $ cd ~ - $ cookiecutter gh:Pylons/pyramid-cookiecutter-alchemy --checkout master + $ cookiecutter gh:Pylons/pyramid-cookiecutter-alchemy --checkout 1.9-branch On Windows ^^^^^^^^^^ @@ -51,7 +51,7 @@ On Windows .. code-block:: doscon c:\> cd \ - c:\> cookiecutter gh:Pylons/pyramid-cookiecutter-alchemy --checkout master + c:\> cookiecutter gh:Pylons/pyramid-cookiecutter-alchemy --checkout 1.9-branch On all operating systems ^^^^^^^^^^^^^^^^^^^^^^^^ -- cgit v1.2.3 From cb3b05f43fd297fc8e3556cb8c9d59017229e519 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Fri, 2 Jun 2017 13:15:57 -0700 Subject: Add cookiecutter step for master branch upon release of new branch --- RELEASING.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/RELEASING.txt b/RELEASING.txt index 29d999522..9f7db457e 100644 --- a/RELEASING.txt +++ b/RELEASING.txt @@ -107,6 +107,11 @@ Prepare master for further development (major releases only) - Update README.rst to use correct versions of badges, URLs, and ALT option for "master" instead of the major release version. +- In the docs, update the ``cookiecutter`` command with ``master``, + for example, ``cookiecutter gh:Pylons/pyramid-cookiecutter-starter --checkout + master``. A search for ``cookiecutter gh:Pylons/pyramid-cookiecutter-`` + should return all instances to be updated. + Update previous version (final releases only) --------------------------------------------- -- cgit v1.2.3 From f42ab136cd5d2c98c34b101d458750f638380d08 Mon Sep 17 00:00:00 2001 From: cewing Date: Sat, 3 Jun 2017 16:06:38 -0700 Subject: use str in deference to Py3 style over Py2 --- docs/narr/advanced-features.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/narr/advanced-features.rst b/docs/narr/advanced-features.rst index ae28ba41b..9ed0cc712 100644 --- a/docs/narr/advanced-features.rst +++ b/docs/narr/advanced-features.rst @@ -252,7 +252,7 @@ A new response adapter is registered in configuration: if __name__ == '__main__': config = Configurator() - config.add_response_adapter(string_response_adapter, basestring) + config.add_response_adapter(string_response_adapter, str) With that, you may return strings from any of your view callables, e.g.: @@ -288,7 +288,7 @@ and return codes: if __name__ == '__main__': config = Configurator() - config.add_response_adapter(string_response_adapter, basestring) + config.add_response_adapter(string_response_adapter, str) config.add_response_adapter(tuple_response_adapter, tuple) With this, both of these views will work as expected: -- cgit v1.2.3 From a419bcd2b1fabf2fcf551edd714236a990d89b36 Mon Sep 17 00:00:00 2001 From: cewing Date: Sat, 3 Jun 2017 16:08:00 -0700 Subject: more fixes for CR --- docs/narr/introduction.rst | 75 +++++++++++++++++++++++++--------------------- 1 file changed, 41 insertions(+), 34 deletions(-) diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst index 19b8fcdb8..4f9574ec6 100644 --- a/docs/narr/introduction.rst +++ b/docs/narr/introduction.rst @@ -73,15 +73,17 @@ working on a framework that is up-to-date and forward-looking. Tested ~~~~~~ -Untested code is broken by design. -The Pyramid community has a strong testing culture and our framework reflects that. -Every release of Pyramid has 100% statement coverage (as measured by `coverage `_) -and 95% decision/condition coverage. (as measured by `instrumental `_) -It is automatically tested using `Travis `_ -and `Jenkins `_ -on supported versions of Python after each commit to its GitHub repository. -`Official Pyramid add-ons `_ -are held to a similar testing standard. +Untested code is broken by design. The Pyramid community has a strong testing +culture and our framework reflects that. Every release of Pyramid has 100% +statement coverage (as measured by `coverage +`_) and 95% decision/condition coverage. (as +measured by `instrumental +`_) It is +automatically tested using `Travis `_ and +`Jenkins `_ on supported +versions of Python after each commit to its GitHub repository. `Official +Pyramid add-ons `_ are +held to a similar testing standard. We still find bugs in Pyramid, but we've noticed we find a lot fewer of them while working on projects with a solid testing regime. @@ -89,25 +91,27 @@ while working on projects with a solid testing regime. Documented ~~~~~~~~~~ -The Pyramid documentation is comprehensive. -We strive to keep our narrative documentation both complete and friendly to newcomers. -We also maintain a :ref:`cookbook ` of recipes, -demonstrations of common scenarios you might face. -Contributions in the form of improvements to our documentation are always appreciated. -And we always welcome improvements to our `official tutorials `_ -as well as new contributions to our `community maintained tutorials `_. +The Pyramid documentation is comprehensive. We strive to keep our narrative +documentation both complete and friendly to newcomers. We also maintain a +:ref:`cookbook ` of recipes, demonstrations of +common scenarios you might face. Contributions in the form of improvements to +our documentation are always appreciated. And we always welcome improvements to +our `official tutorials +`_ as well +as new contributions to our `community maintained tutorials +`_. Supported ~~~~~~~~~ -You can get help quickly with :app:`Pyramid`. -It's our goal that no :app:`Pyramid` question go unanswered. -Whether you ask a question on IRC, on the Pylons-discuss mailing list, or on StackOverflow, -you're likely to get a reasonably prompt response. +You can get help quickly with :app:`Pyramid`. It's our goal that no +:app:`Pyramid` question go unanswered. Whether you ask a question on IRC, on +the Pylons-discuss mailing list, or on StackOverflow, you're likely to get a +reasonably prompt response. -:app:`Pyramid` is also a welcoming, friendly space for newcomers. -We don't tolerate "support trolls" or those who enjoy berating fellow users in our support channels. -We try to keep it well-lit and new-user-friendly. +:app:`Pyramid` is also a welcoming, friendly space for newcomers. We don't +tolerate "support trolls" or those who enjoy berating fellow users in our +support channels. We try to keep it well-lit and new-user-friendly. .. seealso:: @@ -336,9 +340,9 @@ Use *your* templates ~~~~~~~~~~~~~~~~~~~~ In Pyramid, the job of creating a ``Response`` belongs to a :term:`renderer`. -Any templating system--Mako, Genshi, Chameleon, Jinja2--can be a renderer. In -fact, packages exist for all of these systems. But if you'd rather use another, -a structured API exists allowing you to create a renderer using your favorite +Any templating system—Mako, Chameleon, Jinja2—can be a renderer. In fact, +packages exist for all of these systems. But if you'd rather use another, a +structured API exists allowing you to create a renderer using your favorite templating system. You can use the templating system *you* understand, not one required by the framework. @@ -352,7 +356,7 @@ Write testable views ~~~~~~~~~~~~~~~~~~~~ When you use a :term:`renderer` with your view callable, you are freed from -needing to return a "webby" ``Response`` object. Instead, your views can return +needing to return a "webby" ``Response`` object. Instead your views can return a simple Python dictionary. Pyramid will take care of rendering the information in that dictionary to a ``Response`` on your behalf. As a result, your views are more easily tested, since you don't need to parse HTML to evaluate the @@ -398,8 +402,8 @@ customization. See :ref:`intro_asset_specs` for more information. Example: :ref:`renderers_chapter`. -Use events to coordinate -~~~~~~~~~~~~~~~~~~~~~~~~ +Use events to coordinate actions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When writing web applications, it is often important to have your code run at a specific point in the lifecycle of a request. In Pyramid, you can accomplish @@ -441,10 +445,13 @@ Build efficient applications ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Pyramid provides an easy way to *cache* the results of slow or expensive views. -You can indicate in view configuration that you want a view to be cached:: +You can indicate in view configuration that you want a view to be cached: + +.. code-block:: python @view_config(http_cache=3600) # 60 minutes - def myview(request): ... + def myview(request): + # ... Pyramid will automatically add the appropriate ``Cache-Control`` and ``Expires`` headers to the response it creates. @@ -544,9 +551,9 @@ features from each, combining them into a unique web framework. Similar to :term:`Zope`, :app:`Pyramid` applications may easily be extended. If you work within the constraints of the framework, you can produce applications -that can be reused, modified or extended without needing to modify the original -application code. :app:`Pyramid` also inherits the concepts of :term:`traversal` -and declarative security from Zope. +that can be reused, modified, or extended without needing to modify the +original application code. :app:`Pyramid` also inherits the concepts of +:term:`traversal` and declarative security from Zope. Similar to :term:`Pylons` version 1.0, :app:`Pyramid` is largely free of policy. It makes no assertions about which database or template system you -- cgit v1.2.3 From 794fd355156224b9ce93651837a311dbf6ac7040 Mon Sep 17 00:00:00 2001 From: cewing Date: Sat, 3 Jun 2017 16:29:01 -0700 Subject: finish all app references for Pyramid and refold line lengths --- docs/narr/advanced-features.rst | 96 +++++++------- docs/narr/introduction.rst | 277 ++++++++++++++++++++-------------------- 2 files changed, 192 insertions(+), 181 deletions(-) diff --git a/docs/narr/advanced-features.rst b/docs/narr/advanced-features.rst index 9ed0cc712..63bc5d3e7 100644 --- a/docs/narr/advanced-features.rst +++ b/docs/narr/advanced-features.rst @@ -1,9 +1,8 @@ Advanced :app:`Pyramid` Design Features ======================================= -Pyramid has been built from the ground up to avoid the problems that other -frameworks can suffer. - +:app:`Pyramid` has been built from the ground up to avoid the problems +that other frameworks can suffer. You Don't Need Singletons ------------------------- @@ -37,8 +36,8 @@ with something like this: else: # do something else -Unlike many other systems, :app:`Pyramid` allows you to associate more than one view -with a single route. For example, you can create a route with the pattern +Unlike many other systems, :app:`Pyramid` allows you to associate more than one +view with a single route. For example, you can create a route with the pattern ``/items`` and when the route is matched, you can send the request to one view if the request method is GET, another view if the request method is POST, and so on. @@ -72,10 +71,11 @@ understandable, and more directly testable. Stop Worrying About Transactions -------------------------------- -:app:`Pyramid`\ 's :term:`cookiecutter`\ s render projects that include a *transaction -management* system. When you use this system, you can stop worrying about when -to commit your changes, :app:`Pyramid` handles it for you. The system will -commit at the end of a request or abort if there was an exception. +:app:`Pyramid`\ 's :term:`cookiecutter`\ s render projects that include a +*transaction management* system. When you use this system, you can stop +worrying about when to commit your changes, :app:`Pyramid` handles it for you. +The system will commit at the end of a request or abort if there was an +exception. Why is that a good thing? Imagine a situation where you manually commit a change to your persistence layer. It's very likely that other framework code @@ -86,10 +86,10 @@ Using transaction management saves you from needing to think about this. Either a request completes successfully and all changes are committed, or it does not and all changes are aborted. -Pyramid's transaction management is extendable, so you can synchronize commits -between multiple databases or databases of different kinds. It also allows you -to do things like conditionally send email if a transaction is committed, but -otherwise keep quiet. +:app:`Pyramid`\ 's transaction management is extendable, so you can synchronize +commits between multiple databases or databases of different kinds. It also +allows you to do things like conditionally send email if a transaction is +committed, but otherwise keep quiet. .. seealso:: @@ -103,10 +103,10 @@ When a system is small, it's reasonably easy to keep it all in your head. But as systems grow large, configuration grows more complex. Your app may grow to have hundreds or even thousands of configuration statements. -:app:`Pyramid`\ 's configuration system keeps track of each of your statements. If you -accidentally add two that are identical, or :app:`Pyramid` can't make sense out of -what it would mean to have both statements active at the same time, it will -complain loudly at startup time. +:app:`Pyramid`\ 's configuration system keeps track of each of your statements. +If you accidentally add two that are identical, or :app:`Pyramid` can't make +sense out of what it would mean to have both statements active at the same +time, it will complain loudly at startup time. :app:`Pyramid`\ 's configuration system is not dumb though. If you use the :meth:`~pyramid.config.Configurator.include` system, it can automatically @@ -123,9 +123,9 @@ Compose Powerful Apps From Simple Parts Speaking of the :app:`Pyramid` structured "include" mechanism (see :meth:`~pyramid.config.Configurator.include`), it allows you to compose complex applications from multiple, simple Python packages. All the configuration -statements that can be performed in your main :app:`Pyramid` application can also be -used in included packages. You can add views, routes, and subscribers, and even -set authentication and authorization policies. +statements that can be performed in your main :app:`Pyramid` application can +also be used in included packages. You can add views, routes, and subscribers, +and even set authentication and authorization policies. If you need, you can extend or override the configuration of an existing application by including its configuration in your own and then modifying it. @@ -155,9 +155,10 @@ as you requested: Authenticate Users Your Way --------------------------- -:app:`Pyramid` ships with prebuilt, well-tested authentication and authorization -schemes out of the box. Using a scheme is a matter of configuration. So if you -need to change approaches later, you need only update your configuration. +:app:`Pyramid` ships with prebuilt, well-tested authentication and +authorization schemes out of the box. Using a scheme is a matter of +configuration. So if you need to change approaches later, you need only update +your configuration. In addition, the system that handles authentication and authorization is flexible and pluggable. If you want to use another security add-on, or define @@ -202,11 +203,11 @@ transaction manager. Return What You Want From Your Views ------------------------------------ -We have shown elsewhere (in the :doc:`introduction`) how using a :term:`renderer` -allows you to return simple Python dictionaries from your view code. But some -frameworks allow you to return strings or tuples from view callables. -When frameworks allow for this, code looks slightly prettier because there are -fewer imports and less code. For example, compare this: +We have shown elsewhere (in the :doc:`introduction`) how using a +:term:`renderer` allows you to return simple Python dictionaries from your view +code. But some frameworks allow you to return strings or tuples from view +callables. When frameworks allow for this, code looks slightly prettier because +there are fewer imports and less code. For example, compare this: .. code-block:: python :linenos: @@ -334,10 +335,11 @@ Perhaps the :app:`Pyramid` configurator's syntax feels a bit verbose to you. Or possibly you would like to add a feature to configuration without asking the core developers to change :app:`Pyramid` itself? -You can extend :app:`Pyramid`\ 's :term:`configurator` with your own directives. -For example, let's say you find yourself calling :meth:`pyramid.config.Configurator.add_view` repetitively. -Usually you can get rid of the boring with existing shortcuts, -but let's say that this is a case where there is no such shortcut: +You can extend :app:`Pyramid`\ 's :term:`configurator` with your own +directives. For example, let's say you find yourself calling +:meth:`pyramid.config.Configurator.add_view` repetitively. Usually you can get +rid of the boring with existing shortcuts, but let's say that this is a case +where there is no such shortcut: .. code-block:: python :linenos: @@ -353,8 +355,8 @@ but let's say that this is a case where there is no such shortcut: config.add_view('my.package.HEAD_view', route_name='xhr_route', xhr=True, permission='view', request_method='HEAD') -Pretty tedious right? -You can add a directive to the :app:`Pyramid` :term:`configurator` to automate some of the tedium away: +Pretty tedious right? You can add a directive to the :app:`Pyramid` +:term:`configurator` to automate some of the tedium away: .. code-block:: python :linenos: @@ -372,8 +374,8 @@ You can add a directive to the :app:`Pyramid` :term:`configurator` to automate s config = Configurator() config.add_directive('add_protected_xhr_views', add_protected_xhr_views) -Once that's done, -you can call the directive you've just added as a method of the :term:`configurator` object: +Once that's done, you can call the directive you've just added as a method of +the :term:`configurator` object: .. code-block:: python :linenos: @@ -383,11 +385,12 @@ you can call the directive you've just added as a method of the :term:`configura Much better! -You can share your configuration code with others, too. -Add your code to a Python package. -Put the call to :meth:`~pyramid.config.Configurator.add_directive` in a function. -When other programmers install your package, -they'll be able to use your configuration by passing your function to a call to :meth:`~pyramid.config.Configurator.include`. +You can share your configuration code with others, too. Add your code to a +Python package. Put the call to +:meth:`~pyramid.config.Configurator.add_directive` in a function. When other +programmers install your package, they'll be able to use your configuration by +passing your function to a call to +:meth:`~pyramid.config.Configurator.include`. .. seealso:: @@ -396,14 +399,15 @@ they'll be able to use your configuration by passing your function to a call to Introspect Your Application --------------------------- -If you're building a large, pluggable system, -it's useful to be able to get a list of what has been plugged in *at application runtime*. -For example, you might want to show users a set of tabs at the top of the screen -based on a list of the views they registered. +If you're building a large, pluggable system, it's useful to be able to get a +list of what has been plugged in *at application runtime*. For example, you +might want to show users a set of tabs at the top of the screen based on a list +of the views they registered. :app:`Pyramid` provides an :term:`introspector` for just this purpose. -Here's an example of using :app:`Pyramid`\ 's :term:`introspector` from within a view: +Here's an example of using :app:`Pyramid`\ 's :term:`introspector` from within +a view: .. code-block:: python :linenos: diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst index 4f9574ec6..b53ecd6bd 100644 --- a/docs/narr/introduction.rst +++ b/docs/narr/introduction.rst @@ -27,19 +27,19 @@ creating web applications easier. It is open source. framework, so long as your chosen framework fits the requirements of your application. -Pyramid follows these design and engineering principles: +:app:`Pyramid` follows these design and engineering principles: Simplicity - :app:`Pyramid` is designed to be easy to use. You can get started even if you - don't understand it all. And when you're ready to do more, :app:`Pyramid` - will be there for you. + :app:`Pyramid` is designed to be easy to use. You can get started even if + you don't understand it all. And when you're ready to do more, + :app:`Pyramid` will be there for you. Minimalism - Out of the box, :app:`Pyramid` provides only the core tools needed for nearly - all web applications: mapping URLs to code, security, and serving static - assets (files like JavaScript and CSS). Additional tools provide templating, - database integration and more. But with :app:`Pyramid` you can *"pay only for - what you eat"*. + Out of the box, :app:`Pyramid` provides only the core tools needed for + nearly all web applications: mapping URLs to code, security, and serving + static assets (files like JavaScript and CSS). Additional tools provide + templating, database integration and more. But with :app:`Pyramid` you can + *"pay only for what you eat"*. Documentation :app:`Pyramid` is committed to comprehensive and up-to-date documentation. @@ -52,30 +52,31 @@ Reliability is: "If it ain't tested, it's broke". Openness - As with Python, the Pyramid software is distributed under a `permissive open - source license `_. + As with Python, the :app:`Pyramid` software is distributed under a + `permissive open source license `_. .. _why_pyramid: Why Pyramid? ------------ -In a world filled with web frameworks, why should you choose Pyramid? +In a world filled with web frameworks, why should you choose :app:`Pyramid`\ ? Modern ~~~~~~ -Pyramid is fully compatible with Python 3. If you develop a Pyramid application -today, you can rest assured that you'll be able to use the most modern features -of your favorite language. And in the years to come, you'll continue to be -working on a framework that is up-to-date and forward-looking. +:app:`Pyramid` is fully compatible with Python 3. If you develop a +:app:`Pyramid` application today, you can rest assured that you'll be able +to use the most modern features of your favorite language. And in the years +to come, you'll continue to bed working on a framework that is up-to-date +and forward-looking. Tested ~~~~~~ -Untested code is broken by design. The Pyramid community has a strong testing -culture and our framework reflects that. Every release of Pyramid has 100% -statement coverage (as measured by `coverage +Untested code is broken by design. The :app:`Pyramid` community has a strong +testing culture and our framework reflects that. Every release of +:app:`Pyramid` has 100% statement coverage (as measured by `coverage `_) and 95% decision/condition coverage. (as measured by `instrumental `_) It is @@ -85,18 +86,18 @@ versions of Python after each commit to its GitHub repository. `Official Pyramid add-ons `_ are held to a similar testing standard. -We still find bugs in Pyramid, but we've noticed we find a lot fewer of them -while working on projects with a solid testing regime. +We still find bugs in :app:`Pyramid`, but we've noticed we find a lot fewer of +them while working on projects with a solid testing regime. Documented ~~~~~~~~~~ -The Pyramid documentation is comprehensive. We strive to keep our narrative -documentation both complete and friendly to newcomers. We also maintain a -:ref:`cookbook ` of recipes, demonstrations of -common scenarios you might face. Contributions in the form of improvements to -our documentation are always appreciated. And we always welcome improvements to -our `official tutorials +The :app:`Pyramid` documentation is comprehensive. We strive to keep our +narrative documentation both complete and friendly to newcomers. We also +maintain a :ref:`cookbook ` of recipes, +demonstrations of common scenarios you might face. Contributions in the form of +improvements to our documentation are always appreciated. And we always welcome +improvements to our `official tutorials `_ as well as new contributions to our `community maintained tutorials `_. @@ -125,34 +126,36 @@ What makes Pyramid unique ------------------------- There are many tools available for web development. What would make someone -want to use Pyramid instead? What makes Pyramid unique? +want to use :app:`Pyramid` instead? What makes :app:`Pyramid` unique? -With Pyramid you can write very small applications without needing to know a -lot. And by learning a bit more, you can write very large applications too. -Pyramid will allow you to become productive quickly, and will grow with you. It -won't hold you back when your application is small, and it won't get in your -way when your application becomes large. Other application frameworks seem to -fall into two non-overlapping categories: those that support "small apps" and -those designed for "big apps". +With :app:`Pyramid` you can write very small applications without needing to +know a lot. And by learning a bit more, you can write very large applications +too. :app:`Pyramid` will allow you to become productive quickly, and will grow +with you. It won't hold you back when your application is small, and it won't +get in your way when your application becomes large. Other application +frameworks seem to fall into two non-overlapping categories: those that support +"small apps" and those designed for "big apps". We don't believe you should have to make this choice. You can't really know how large your application will become. You certainly shouldn't have to rewrite a small application in another framework when it gets "too big". A well-designed -framework should be able to be good at both. Pyramid is that kind of framework. +framework should be able to be good at both. :app:`Pyramid` is that kind of +framework. -Pyramid provides a set of features that are unique among Python web frameworks. -Others may provide some, but only Pyramid provides them all, in one place, -fully documented, and *à la carte* without needing to pay for the whole banquet. +:app:`Pyramid` provides a set of features that are unique among Python web +frameworks. Others may provide some, but only :app:`Pyramid` provides them all, +in one place, fully documented, and *à la carte* without needing to pay for the +whole banquet. Build single-file applications ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -You can write a Pyramid application that lives entirely in one Python file. -Such an application is easy to understand since everything is in one place. It -is easy to deploy because you don't need to know much about Python packaging. -Pyramid allows you to do almost everything that so-called *microframeworks* can -in very similar ways. +You can write a :app:`Pyramid` application that lives entirely in one Python +file. Such an application is easy to understand since everything is in one +place. It is easy to deploy because you don't need to know much about Python +packaging. :app:`Pyramid` allows you to do almost everything that so-called +*microframeworks* can in very similar ways. .. literalinclude:: helloworld.py @@ -163,8 +166,8 @@ in very similar ways. Configure applications with decorators ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Pyramid allows you to keep your configuration right next to your code. That way -you don't have to switch files to see your configuration. For example: +:app:`Pyramid` allows you to keep your configuration right next to your code. +That way you don't have to switch files to see your configuration. For example: .. code-block:: python @@ -175,9 +178,9 @@ you don't have to switch files to see your configuration. For example: def fred_view(request): return Response('fred') -However, using Pyramid configuration decorators does not change your code. It -remains easy to extend, test, or reuse. You can test your code as if the -decorators were not there. You can instruct the framework to ignore some +However, using :app:`Pyramid` configuration decorators does not change your +code. It remains easy to extend, test, or reuse. You can test your code as if +the decorators were not there. You can instruct the framework to ignore some decorators. You can even use an imperative style to write your configuration, skipping decorators entirely. @@ -189,9 +192,9 @@ Generate application URLs ~~~~~~~~~~~~~~~~~~~~~~~~~ Dynamic web applications produce URLs that can change depending on what you are -viewing. Pyramid provides flexible, consistent, easy to use tools for generating -URLs. When you use these tools to write your application, you can change your -configuration without fear of breaking links in your web pages. +viewing. :app:`Pyramid` provides flexible, consistent, easy to use tools for +generating URLs. When you use these tools to write your application, you can +change your configuration without fear of breaking links in your web pages. .. seealso:: @@ -201,11 +204,11 @@ Serve static assets ~~~~~~~~~~~~~~~~~~~ Web applications often require JavaScript, CSS, images and other so-called -*static assets*. Pyramid provides flexible tools for serving these kinds of -files. You can serve them directly from Pyramid, or host them on an external -server or CDN (content delivery network). Either way, Pyramid can help you to -generate URLs so you can change where your files come from without changing any -code. +*static assets*. :app:`Pyramid` provides flexible tools for serving these kinds +of files. You can serve them directly from :app:`Pyramid`, or host them on an +external server or CDN (content delivery network). Either way, :app:`Pyramid` +can help you to generate URLs so you can change where your files come from +without changing any code. .. seealso:: @@ -214,19 +217,19 @@ code. Develop interactively ~~~~~~~~~~~~~~~~~~~~~ -Pyramid can automatically detect changes you make to template files and code, -so your changes are immediately available in your browser. You can debug using -plain old ``print()`` calls, which will display to your console. +:app:`Pyramid` can automatically detect changes you make to template files and +code, so your changes are immediately available in your browser. You can debug +using plain old ``print()`` calls, which will display to your console. -Pyramid has a debug toolbar that allows you to see information about how your -application is working right in your browser. See configuration, installed +:app:`Pyramid` has a debug toolbar that allows you to see information about how +your application is working right in your browser. See configuration, installed packages, SQL queries, logging statements and more. When your application has an error, an interactive debugger allows you to poke around from your browser to find out what happened. -To use the Pyramid debug toolbar, build your project with a Pyramid -:term:`cookiecutter`. +To use the :app:`Pyramid` debug toolbar, build your project with a +:app:`Pyramid` :term:`cookiecutter`. .. seealso:: @@ -235,16 +238,16 @@ To use the Pyramid debug toolbar, build your project with a Pyramid Debug with power ~~~~~~~~~~~~~~~~ -When things go wrong, Pyramid gives you powerful ways to fix the problem. +When things go wrong, :app:`Pyramid` gives you powerful ways to fix the problem. -You can configure Pyramid to print helpful information to the console. The -``debug_notfound`` setting shows information about URLs that aren't matched. -The ``debug_authorization`` setting provides helpful messages about why you -aren't allowed to do what you just tried. +You can configure :app:`Pyramid` to print helpful information to the console. +The ``debug_notfound`` setting shows information about URLs that aren't +matched. The ``debug_authorization`` setting provides helpful messages about +why you aren't allowed to do what you just tried. -Pyramid also has command line tools to help you verify your configuration. You -can use ``proutes`` and ``pviews`` to inspect how URLs are connected to your -application code. +:app:`Pyramid` also has command line tools to help you verify your +configuration. You can use ``proutes`` and ``pviews`` to inspect how URLs are +connected to your application code. .. seealso:: @@ -254,12 +257,13 @@ application code. Extend your application ~~~~~~~~~~~~~~~~~~~~~~~ -Pyramid add-ons extend the core of the framework with useful abilities. There -are add-ons available for your favorite template language, SQL and NoSQL +:app:`Pyramid` add-ons extend the core of the framework with useful abilities. +There are add-ons available for your favorite template language, SQL and NoSQL databases, authentication services and more. -Supported Pyramid add-ons are held to the same demanding standards as the -framework itself. You will find them to be fully tested and well documented. +Supported :app:`Pyramid` add-ons are held to the same demanding standards as +the framework itself. You will find them to be fully tested and well +documented. .. seealso:: @@ -268,12 +272,13 @@ framework itself. You will find them to be fully tested and well documented. Write your views, *your* way ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -A fundamental task for any framework is to map URLs to code. In Pyramid, that -code is called a :term:`view callable`. View callables can be functions, class -methods or even callable class instances. You are free to choose the approach -that best fits your use case. Regardless of your choice, Pyramid treats them -the same. You can change your mind at any time without any penalty. There are -no artificial distinctions between the various approaches. +A fundamental task for any framework is to map URLs to code. In :app:`Pyramid`, +that code is called a :term:`view callable`. View callables can be functions, +class methods or even callable class instances. You are free to choose the +approach that best fits your use case. Regardless of your choice, +:app:`Pyramid` treats them the same. You can change your mind at any time +without any penalty. There are no artificial distinctions between the various +approaches. Here's a view callable defined as a function: @@ -321,17 +326,17 @@ in a globally shared location, "the *static* directory". Others use a lookup scheme, like an ordered set of template directories. Both of these approaches have problems when it comes to customization. -Pyramid takes a different approach. Static assets are located using *asset +:app:`Pyramid` takes a different approach. Static assets are located using *asset specifications*, strings that contain reference both to a Python package name and a file or directory name, e.g. ``MyPackage:static/index.html``. These specifications are used for templates, JavaScript and CSS, translation files, and any other package-bound static resource. By using asset specifications, -Pyramid makes it easy to extend your application with other packages without +:app:`Pyramid` makes it easy to extend your application with other packages without worrying about conflicts. -What happens if another Pyramid package you are using provides an asset you -need to customize? Maybe that page template needs better HTML, or you want to -update some CSS. With asset specifications you can override the assets from +What happens if another :app:`Pyramid` package you are using provides an asset +you need to customize? Maybe that page template needs better HTML, or you want +to update some CSS. With asset specifications you can override the assets from other packages using simple wrappers. Examples: :ref:`asset_specifications` and :ref:`overriding_assets_section`. @@ -339,14 +344,14 @@ Examples: :ref:`asset_specifications` and :ref:`overriding_assets_section`. Use *your* templates ~~~~~~~~~~~~~~~~~~~~ -In Pyramid, the job of creating a ``Response`` belongs to a :term:`renderer`. -Any templating system—Mako, Chameleon, Jinja2—can be a renderer. In fact, -packages exist for all of these systems. But if you'd rather use another, a -structured API exists allowing you to create a renderer using your favorite -templating system. You can use the templating system *you* understand, not one -required by the framework. +In :app:`Pyramid`, the job of creating a ``Response`` belongs to a +:term:`renderer`. Any templating system—Mako, Chameleon, Jinja2—can be a +renderer. In fact, packages exist for all of these systems. But if you'd rather +use another, a structured API exists allowing you to create a renderer using +your favorite templating system. You can use the templating system *you* +understand, not one required by the framework. -What's more, Pyramid does not make you use a single templating system +What's more, :app:`Pyramid` does not make you use a single templating system exclusively. You can use multiple templating systems, even in the same project. @@ -357,11 +362,11 @@ Write testable views When you use a :term:`renderer` with your view callable, you are freed from needing to return a "webby" ``Response`` object. Instead your views can return -a simple Python dictionary. Pyramid will take care of rendering the information -in that dictionary to a ``Response`` on your behalf. As a result, your views -are more easily tested, since you don't need to parse HTML to evaluate the -results. Pyramid makes it a snap to write unit tests for your views, instead of -requiring you to use functional tests. +a simple Python dictionary. :app:`Pyramid` will take care of rendering the +information in that dictionary to a ``Response`` on your behalf. As a result, +your views are more easily tested, since you don't need to parse HTML to +evaluate the results. :app:`Pyramid` makes it a snap to write unit tests for +your views, instead of requiring you to use functional tests. .. index:: pair: renderer; explicitly calling @@ -381,7 +386,8 @@ For example, a typical web framework might return a ``Response`` object from a return render_to_response('myapp:templates/mytemplate.pt', {'a':1}, request=request) -While you *can* do this in Pyramid, you can also return a Python dictionary: +While you *can* do this in :app:`Pyramid`, you can also return a Python +dictionary: .. code-block:: python :linenos: @@ -392,13 +398,13 @@ While you *can* do this in Pyramid, you can also return a Python dictionary: def myview(request): return {'a':1} -By configuring your view to use a renderer, you tell Pyramid to use the +By configuring your view to use a renderer, you tell :app:`Pyramid` to use the ``{'a':1}`` dictionary and the specified template to render a response on your behalf. The string passed as ``renderer=`` above is an :term:`asset specification`. -Asset specifications are widely used in Pyramid. They allow for more reliable -customization. See :ref:`intro_asset_specs` for more information. +Asset specifications are widely used in :app:`Pyramid`. They allow for more +reliable customization. See :ref:`intro_asset_specs` for more information. Example: :ref:`renderers_chapter`. @@ -406,13 +412,13 @@ Use events to coordinate actions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When writing web applications, it is often important to have your code run at a -specific point in the lifecycle of a request. In Pyramid, you can accomplish -this using *subscribers* and *events*. +specific point in the lifecycle of a request. In :app:`Pyramid`, you can +accomplish this using *subscribers* and *events*. For example, you might have a job that needs to be done each time your -application handles a new request. Pyramid emits a ``NewRequest`` event at this -point in the request handling lifecycle. You can register your code as a -subscriber to this event using a clear, declarative style: +application handles a new request. :app:`Pyramid` emits a ``NewRequest`` event +at this point in the request handling lifecycle. You can register your code as +a subscriber to this event using a clear, declarative style: .. code-block:: python @@ -423,29 +429,30 @@ subscriber to this event using a clear, declarative style: def my_job(event): do_something(event.request) -Pyramid's event system can be extended as well. If you need, you can create -events of your own and send them using Pyramid's event system. Then anyone -working with your application can subscribe to your events and coordinate their -code with yours. +:app:`Pyramid`\ 's event system can be extended as well. If you need, you can +create events of your own and send them using :app:`Pyramid`\ 's event system. +Then anyone working with your application can subscribe to your events and +coordinate their code with yours. Example: :ref:`events_chapter` and :ref:`event_types`. Build international applications ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Pyramid ships with internationalization-related features in its core: +:app:`Pyramid` ships with internationalization-related features in its core: localization, pluralization, and creating message catalogs from source files -and templates. Pyramid allows for a plurality of message catalogs via the use -of translation domains. You can create a system that has its own translations -without conflict with other translations in other domains. +and templates. :app:`Pyramid` allows for a plurality of message catalogs via +the use of translation domains. You can create a system that has its own +translations without conflict with other translations in other domains. Example: :ref:`i18n_chapter`. Build efficient applications ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Pyramid provides an easy way to *cache* the results of slow or expensive views. -You can indicate in view configuration that you want a view to be cached: +:app:`Pyramid` provides an easy way to *cache* the results of slow or expensive +views. You can indicate in view configuration that you want a view to be +cached: .. code-block:: python @@ -453,7 +460,7 @@ You can indicate in view configuration that you want a view to be cached: def myview(request): # ... -Pyramid will automatically add the appropriate ``Cache-Control`` and +:app:`Pyramid` will automatically add the appropriate ``Cache-Control`` and ``Expires`` headers to the response it creates. See the :meth:`~pyramid.config.Configurator.add_view` method's ``http_cache`` @@ -462,21 +469,21 @@ documentation for more information. Build fast applications ~~~~~~~~~~~~~~~~~~~~~~~ -The Pyramid core is fast. It has been engineered from the ground up for speed. -It only does as much work as absolutely necessary when you ask it to get a job -done. If you need speed from your application, Pyramid is the right choice for -you. +The :app:`Pyramid` core is fast. It has been engineered from the ground up for +speed. It only does as much work as absolutely necessary when you ask it to get +a job done. If you need speed from your application, :app:`Pyramid` is the +right choice for you. Example: http://blog.curiasolutions.com/pages/the-great-web-framework-shootout.html Store session data ~~~~~~~~~~~~~~~~~~ -Pyramid has built-in support for HTTP sessions, so you can associate data with -specific users between requests. Lots of other frameworks also support -sessions. But Pyramid allows you to plug in your own custom sessioning system. -So long as your system conforms to a documented interface, you can drop it in -in place of the provided system. +:app:`Pyramid` has built-in support for HTTP sessions, so you can associate +data with specific users between requests. Lots of other frameworks also +support sessions. But :app:`Pyramid` allows you to plug in your own custom +sessioning system. So long as your system conforms to a documented interface, +you can drop it in in place of the provided system. Currently there is a binding package for the third-party Redis sessioning system that does exactly this. But if you have a specialized need (perhaps you @@ -488,7 +495,7 @@ Example: :ref:`sessions_chapter`. Handle problems with grace ~~~~~~~~~~~~~~~~~~~~~~~~~~ -Mistakes happen. Problems crop up. No-one writes bug-free code. Pyramid +Mistakes happen. Problems crop up. No-one writes bug-free code. :app:`Pyramid` provides a way to handle the exceptions your code encounters. An :term:`exception view` is a special kind of view which is automatically called when an particular exception type "bubbles up" without being handled by your @@ -507,8 +514,8 @@ Example: :ref:`exception_views`. And much, much more... ~~~~~~~~~~~~~~~~~~~~~~ -Pyramid has been built with a number of other sophisticated design features -that make it adaptable. Read more about them below. +:app:`Pyramid` has been built with a number of other sophisticated design +features that make it adaptable. Read more about them below. .. toctree:: :maxdepth: 2 @@ -540,10 +547,10 @@ includes details about how :app:`Pyramid` relates to the Pylons Project. :app:`Pyramid` and Other Web Frameworks --------------------------------------- -The first release of Pyramid's predecessor (named :mod:`repoze.bfg`) was made -in July of 2008. At the end of 2010, we changed the name of :mod:`repoze.bfg` -to :app:`Pyramid`. It was merged into the Pylons project as :app:`Pyramid` in -November of that year. +The first release of :app:`Pyramid`\ 's predecessor (named :mod:`repoze.bfg`) +was made in July of 2008. At the end of 2010, we changed the name of +:mod:`repoze.bfg` to :app:`Pyramid`. It was merged into the Pylons project as +:app:`Pyramid` in November of that year. :app:`Pyramid` was inspired by :term:`Zope`, :term:`Pylons` (version 1.0), and :term:`Django`. As a result, :app:`Pyramid` borrows several concepts and -- cgit v1.2.3 From be4b64f19c1dfde34d95e492c0554f13d8b8f797 Mon Sep 17 00:00:00 2001 From: cewing Date: Mon, 5 Jun 2017 08:49:30 -0700 Subject: fix more comments and refold lines, one per paragraph as specified in stylesheet. --- docs/narr/advanced-features.rst | 173 +++++++++------------------------------- 1 file changed, 38 insertions(+), 135 deletions(-) diff --git a/docs/narr/advanced-features.rst b/docs/narr/advanced-features.rst index 63bc5d3e7..82e20963d 100644 --- a/docs/narr/advanced-features.rst +++ b/docs/narr/advanced-features.rst @@ -1,32 +1,21 @@ Advanced :app:`Pyramid` Design Features ======================================= -:app:`Pyramid` has been built from the ground up to avoid the problems -that other frameworks can suffer. +:app:`Pyramid` has been built from the ground up to avoid the problems that other frameworks can suffer. You Don't Need Singletons ------------------------- -Have you ever struggled with parameterizing Django's ``settings.py`` file for -multiple installations of the same Django application? Have you ever needed to -monkey-patch a framework fixture to get it to behave properly for your -use case? Have you ever tried to deploy your application using an asynchronous -server and failed? +Have you ever struggled with parameterizing Django's ``settings.py`` file for multiple installations of the same Django application? Have you ever needed to monkey-patch a framework fixture to get it to behave properly for your use case? Have you ever tried to deploy your application using an asynchronous server and failed? -All these problems are symptoms of :term:`mutable` :term:`global state`, also -known as :term:`import time` :term:`side effect`\ s and arise from the use of -:term:`singleton` data structures. +All these problems are symptoms of :term:`mutable` :term:`global state`, also known as :term:`import time` :term:`side effect`\ s and arise from the use of :term:`singleton` data structures. -:app:`Pyramid` is written so that you don't run into these types of problems. -It is even possible to run multiple copies of the *same* :app:`Pyramid` -application configured differently within a single Python process. This makes -running :app:`Pyramid` in shared hosting environments a snap. +:app:`Pyramid` is written so that you don't run into these types of problems. It is even possible to run multiple copies of the *same* :app:`Pyramid` application configured differently within a single Python process. This makes running :app:`Pyramid` in shared hosting environments a snap. Simplify your View Code with Predicates --------------------------------------- -How many times have you found yourself beginning the logic of your view code -with something like this: +How many times have you found yourself beginning the logic of your view code with something like this: .. code-block:: python :linenos: @@ -36,17 +25,9 @@ with something like this: else: # do something else -Unlike many other systems, :app:`Pyramid` allows you to associate more than one -view with a single route. For example, you can create a route with the pattern -``/items`` and when the route is matched, you can send the request to one view -if the request method is GET, another view if the request method is POST, and -so on. +Unlike many other systems, :app:`Pyramid` allows you to associate more than one view with a single route. For example, you can create a route with the pattern ``/items`` and when the route is matched, you can send the request to one view if the request method is GET, another view if the request method is POST, and so on. -:app:`Pyramid` uses a system of :term:`view predicate`\ s to allow this. -Matching the request method is one basic thing you can do with a -:term:`view predicate`. You can also associate views with other request -parameters, such as elements in the query string, the Accept header, whether -the request is an AJAX (XHR) request or not, and lots of other things. +:app:`Pyramid` uses a system of :term:`view predicate`\ s to allow this. Matching the request method is one basic thing you can do with a :term:`view predicate`. You can also associate views with other request parameters, such as elements in the query string, the Accept header, whether the request is an AJAX (XHR) request or not, and lots of other things. For our example above, you can do this instead: @@ -61,8 +42,7 @@ For our example above, you can do this instead: def anon_view(request): # do something else -This approach allows you to develop view code that is simpler, more easily -understandable, and more directly testable. +This approach allows you to develop view code that is simpler, more easily understandable, and more directly testable. .. seealso:: @@ -71,47 +51,26 @@ understandable, and more directly testable. Stop Worrying About Transactions -------------------------------- -:app:`Pyramid`\ 's :term:`cookiecutter`\ s render projects that include a -*transaction management* system. When you use this system, you can stop -worrying about when to commit your changes, :app:`Pyramid` handles it for you. -The system will commit at the end of a request or abort if there was an -exception. +:app:`Pyramid`\ 's :term:`cookiecutter`\ s render projects that include a *transaction management* system. When you use this system, you can stop worrying about when to commit your changes, :app:`Pyramid` handles it for you. The system will commit at the end of a request or abort if there was an exception. -Why is that a good thing? Imagine a situation where you manually commit a -change to your persistence layer. It's very likely that other framework code -will run *after* your changes are done. If an error happens in that other code, -you can easily wind up with inconsistent data if you're not extremely careful. +Why is that a good thing? Imagine a situation where you manually commit a change to your persistence layer. It's very likely that other framework code will run *after* your changes are done. If an error happens in that other code, you can easily wind up with inconsistent data if you're not extremely careful. -Using transaction management saves you from needing to think about this. Either -a request completes successfully and all changes are committed, or it does -not and all changes are aborted. +Using transaction management saves you from needing to think about this. Either a request completes successfully and all changes are committed, or it does not and all changes are aborted. -:app:`Pyramid`\ 's transaction management is extendable, so you can synchronize -commits between multiple databases or databases of different kinds. It also -allows you to do things like conditionally send email if a transaction is -committed, but otherwise keep quiet. +:app:`Pyramid`\ 's transaction management is extendable, so you can synchronize commits between multiple databases or databases of different kinds. It also allows you to do things like conditionally send email if a transaction is committed, but otherwise keep quiet. .. seealso:: - See also :ref:`bfg_sql_wiki_tutorial` (note the lack of commit statements - anywhere in application code). + See also :ref:`bfg_sql_wiki_tutorial` (note the lack of commit statements anywhere in application code). Stop Worrying About Configuration --------------------------------- -When a system is small, it's reasonably easy to keep it all in your head. But -as systems grow large, configuration grows more complex. Your app may grow to -have hundreds or even thousands of configuration statements. +When a system is small, it's reasonably easy to keep it all in your head. But as systems grow large, configuration grows more complex. Your app may grow to have hundreds or even thousands of configuration statements. -:app:`Pyramid`\ 's configuration system keeps track of each of your statements. -If you accidentally add two that are identical, or :app:`Pyramid` can't make -sense out of what it would mean to have both statements active at the same -time, it will complain loudly at startup time. +:app:`Pyramid`\ 's configuration system keeps track of each of your statements. If you accidentally add two that are identical, or :app:`Pyramid` can't make sense out of what it would mean to have both statements active at the same time, it will complain loudly at startup time. -:app:`Pyramid`\ 's configuration system is not dumb though. If you use the -:meth:`~pyramid.config.Configurator.include` system, it can automatically -resolve conflicts on its own. More local statements are preferred over less -local ones. So you can intelligently factor large systems into smaller ones. +:app:`Pyramid`\ 's configuration system is not dumb though. If you use the :meth:`~pyramid.config.Configurator.include` system, it can automatically resolve conflicts on its own. More local statements are preferred over less local ones. So you can intelligently factor large systems into smaller ones. .. seealso:: @@ -120,21 +79,12 @@ local ones. So you can intelligently factor large systems into smaller ones. Compose Powerful Apps From Simple Parts ---------------------------------------- -Speaking of the :app:`Pyramid` structured "include" mechanism (see -:meth:`~pyramid.config.Configurator.include`), it allows you to compose complex -applications from multiple, simple Python packages. All the configuration -statements that can be performed in your main :app:`Pyramid` application can -also be used in included packages. You can add views, routes, and subscribers, -and even set authentication and authorization policies. +Speaking of the :app:`Pyramid` structured :meth:`~pyramid.config.Configurator.include` mechanism, it allows you to compose complex applications from multiple, simple Python packages. All the configuration statements that can be performed in your main :app:`Pyramid` application can also be used in included packages. You can add views, routes, and subscribers, and even set authentication and authorization policies. -If you need, you can extend or override the configuration of an existing -application by including its configuration in your own and then modifying it. +If you need, you can extend or override the configuration of an existing application by including its configuration in your own and then modifying it. -For example, if you want to reuse an existing application that already has a -bunch of routes, you can just use the ``include`` statement with a -``route_prefix``. All the routes of that application will be availabe, prefixed -as you requested: +For example, if you want to reuse an existing application that already has a bunch of routes, you can just use the ``include`` statement with a ``route_prefix``. All the routes of that application will be availabe, prefixed as you requested: .. code-block:: python :linenos: @@ -149,21 +99,14 @@ as you requested: .. seealso:: - See also :ref:`including_configuration` and - :ref:`building_an_extensible_app`. + See also :ref:`including_configuration` and :ref:`building_an_extensible_app`. Authenticate Users Your Way --------------------------- -:app:`Pyramid` ships with prebuilt, well-tested authentication and -authorization schemes out of the box. Using a scheme is a matter of -configuration. So if you need to change approaches later, you need only update -your configuration. +:app:`Pyramid` ships with prebuilt, well-tested authentication and authorization schemes out of the box. Using a scheme is a matter of configuration. So if you need to change approaches later, you need only update your configuration. -In addition, the system that handles authentication and authorization is -flexible and pluggable. If you want to use another security add-on, or define -your own, you can. And again, you need only update your application -configuration to make the change. +In addition, the system that handles authentication and authorization is flexible and pluggable. If you want to use another security add-on, or define your own, you can. And again, you need only update your application configuration to make the change. .. seealso:: @@ -172,29 +115,18 @@ configuration to make the change. Build Trees of Resources ------------------------ -:app:`Pyramid` supports :term:`traversal`, a way of mapping URLs to a concrete -:term:`resource tree`. If your application naturally consists of an arbitrary -heirarchy of different types of content (like a CMS or a Document Management -System), traversal is for you. If you have a requirement for a highly granular -security model ("Jane can edit documents in *this* folder, but not *that* -one"), traversal can be a powerful approach. +:app:`Pyramid` supports :term:`traversal`, a way of mapping URLs to a concrete :term:`resource tree`. If your application naturally consists of an arbitrary heirarchy of different types of content (like a CMS or a Document Management System), traversal is for you. If you have a requirement for a highly granular security model ("Jane can edit documents in *this* folder, but not *that* one"), traversal can be a powerful approach. .. seealso:: - See also :ref:`hello_traversal_chapter` and - :ref:`much_ado_about_traversal_chapter`. + See also :ref:`hello_traversal_chapter` and :ref:`much_ado_about_traversal_chapter`. Take Action on Each Request with Tweens --------------------------------------- -:app:`Pyramid` has a system for applying an arbitrary action to each request or -response called a :term:`tween`. The system is similar in concept to WSGI -:term:`middleware`, but can be more useful since :term:`tween`\ s run in the -:app:`Pyramid` context, and have access to templates, request objects, and -other niceties. +:app:`Pyramid` has a system for applying an arbitrary action to each request or response called a :term:`tween`. The system is similar in concept to WSGI :term:`middleware`, but can be more useful since :term:`tween`\ s run in the :app:`Pyramid` context, and have access to templates, request objects, and other niceties. -The :app:`Pyramid` debug toolbar is a :term:`tween`, as is the ``pyramid_tm`` -transaction manager. +The :app:`Pyramid` debug toolbar is a :term:`tween`, as is the ``pyramid_tm`` transaction manager. .. seealso:: @@ -203,11 +135,7 @@ transaction manager. Return What You Want From Your Views ------------------------------------ -We have shown elsewhere (in the :doc:`introduction`) how using a -:term:`renderer` allows you to return simple Python dictionaries from your view -code. But some frameworks allow you to return strings or tuples from view -callables. When frameworks allow for this, code looks slightly prettier because -there are fewer imports and less code. For example, compare this: +We have shown elsewhere (in the :doc:`introduction`) how using a :term:`renderer` allows you to return simple Python dictionaries from your view code. But some frameworks allow you to return strings or tuples from view callables. When frameworks allow for this, code looks slightly prettier because there are fewer imports and less code. For example, compare this: .. code-block:: python :linenos: @@ -227,13 +155,9 @@ To this: Nicer to look at, right? -Out of the box, :app:`Pyramid` will raise an exception if you try to run the -second example above. After all, a view should return a response, and "explicit -is better than implicit". +Out of the box, :app:`Pyramid` will raise an exception if you try to run the second example above. After all, a view should return a response, and "explicit is better than implicit". -But if you're a developer who likes the aesthetics of simplicity, -:app:`Pyramid` provides an way to support this sort of thing, the -:term:`response adapter`\ : +But if you're a developer who likes the aesthetics of simplicity, :app:`Pyramid` provides a way to support this sort of thing, the :term:`response adapter`\ : .. code-block:: python :linenos: @@ -266,8 +190,7 @@ With that, you may return strings from any of your view callables, e.g.: def goodbyeview(request): return "Goodbye world!" -You can even use a :term:`response adapter` to allow for custom content types -and return codes: +You can even use a :term:`response adapter` to allow for custom content types and return codes: .. code-block:: python :linenos: @@ -310,10 +233,7 @@ With this, both of these views will work as expected: Use Global Response Objects --------------------------- -Views have to return responses. But constructing them in view code is a chore. -And perhaps registering a :term:`response adapter` as shown above is just too -much work. :app:`Pyramid` provides a global response object as well. You can -use it directly, if you prefer: +Views have to return responses. But constructing them in view code is a chore. And perhaps registering a :term:`response adapter` as shown above is just too much work. :app:`Pyramid` provides a global response object as well. You can use it directly, if you prefer: .. code-block:: python :linenos: @@ -331,15 +251,9 @@ use it directly, if you prefer: Extend Configuration -------------------- -Perhaps the :app:`Pyramid` configurator's syntax feels a bit verbose to you. -Or possibly you would like to add a feature to configuration -without asking the core developers to change :app:`Pyramid` itself? +Perhaps the :app:`Pyramid` configurator's syntax feels a bit verbose to you. Or possibly you would like to add a feature to configuration without asking the core developers to change :app:`Pyramid` itself? -You can extend :app:`Pyramid`\ 's :term:`configurator` with your own -directives. For example, let's say you find yourself calling -:meth:`pyramid.config.Configurator.add_view` repetitively. Usually you can get -rid of the boring with existing shortcuts, but let's say that this is a case -where there is no such shortcut: +You can extend :app:`Pyramid`\ 's :term:`configurator` with your own directives. For example, let's say you find yourself calling :meth:`pyramid.config.Configurator.add_view` repetitively. Usually you can get rid of the boring with existing shortcuts, but let's say that this is a case where there is no such shortcut: .. code-block:: python :linenos: @@ -355,8 +269,7 @@ where there is no such shortcut: config.add_view('my.package.HEAD_view', route_name='xhr_route', xhr=True, permission='view', request_method='HEAD') -Pretty tedious right? You can add a directive to the :app:`Pyramid` -:term:`configurator` to automate some of the tedium away: +Pretty tedious right? You can add a directive to the :app:`Pyramid` :term:`configurator` to automate some of the tedium away: .. code-block:: python :linenos: @@ -374,8 +287,7 @@ Pretty tedious right? You can add a directive to the :app:`Pyramid` config = Configurator() config.add_directive('add_protected_xhr_views', add_protected_xhr_views) -Once that's done, you can call the directive you've just added as a method of -the :term:`configurator` object: +Once that's done, you can call the directive you've just added as a method of the :term:`configurator` object: .. code-block:: python :linenos: @@ -385,12 +297,7 @@ the :term:`configurator` object: Much better! -You can share your configuration code with others, too. Add your code to a -Python package. Put the call to -:meth:`~pyramid.config.Configurator.add_directive` in a function. When other -programmers install your package, they'll be able to use your configuration by -passing your function to a call to -:meth:`~pyramid.config.Configurator.include`. +You can share your configuration code with others, too. Add your code to a Python package. Put the call to :meth:`~pyramid.config.Configurator.add_directive` in a function. When other programmers install your package, they'll be able to use your configuration by passing your function to a call to :meth:`~pyramid.config.Configurator.include`. .. seealso:: @@ -399,15 +306,11 @@ passing your function to a call to Introspect Your Application --------------------------- -If you're building a large, pluggable system, it's useful to be able to get a -list of what has been plugged in *at application runtime*. For example, you -might want to show users a set of tabs at the top of the screen based on a list -of the views they registered. +If you're building a large, pluggable system, it's useful to be able to get a list of what has been plugged in *at application runtime*. For example, you might want to show users a set of tabs at the top of the screen based on a list of the views they registered. :app:`Pyramid` provides an :term:`introspector` for just this purpose. -Here's an example of using :app:`Pyramid`\ 's :term:`introspector` from within -a view: +Here's an example of using :app:`Pyramid`\ 's :term:`introspector` from within a view: .. code-block:: python :linenos: -- cgit v1.2.3 From 719ab350b5c2bb6772e8f376adf7bd10be412f52 Mon Sep 17 00:00:00 2001 From: cewing Date: Mon, 5 Jun 2017 09:17:03 -0700 Subject: fix a few more comments, and reflow introduction document to one line per paragraph --- docs/narr/introduction.rst | 327 ++++++++++----------------------------------- 1 file changed, 68 insertions(+), 259 deletions(-) diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst index b53ecd6bd..2f58a78e7 100644 --- a/docs/narr/introduction.rst +++ b/docs/narr/introduction.rst @@ -9,37 +9,21 @@ :app:`Pyramid` Introduction =========================== -:app:`Pyramid` is a Python web application *framework*. It is designed to make -creating web applications easier. It is open source. +:app:`Pyramid` is a Python web application *framework*. It is designed to make creating web applications easier. It is open source. .. sidebar:: What Is a Framework? - A *framework* provides capabilities that developers can enhance or extend. A - web application framework provides many of the common needs of building web - applications allowing developers to concentrate only on the parts that are - specific to their application. + A *framework* provides capabilities that developers can enhance or extend. A web application framework provides many of the common needs of building web applications allowing developers to concentrate only on the parts that are specific to their application. - Every framework makes choices about how a particular problem should be - solved. When developers choose to use a framework, they cede control over - the portions of their application that are provided by the framework. It is - possible to write a complete web application without any framework, by using - Python libraries. In practice, however, it is often more practical to use a - framework, so long as your chosen framework fits the requirements of your - application. + Every framework makes choices about how a particular problem should be solved. When developers choose to use a framework, they cede control over the portions of their application that are provided by the framework. It is possible to write a complete web application without any framework, by using Python libraries. In practice, however, it is often more practical to use a framework, so long as your chosen framework fits the requirements of your application. :app:`Pyramid` follows these design and engineering principles: Simplicity - :app:`Pyramid` is designed to be easy to use. You can get started even if - you don't understand it all. And when you're ready to do more, - :app:`Pyramid` will be there for you. + :app:`Pyramid` is designed to be easy to use. You can get started even if you don't understand it all. And when you're ready to do more, :app:`Pyramid` will be there for you. Minimalism - Out of the box, :app:`Pyramid` provides only the core tools needed for - nearly all web applications: mapping URLs to code, security, and serving - static assets (files like JavaScript and CSS). Additional tools provide - templating, database integration and more. But with :app:`Pyramid` you can - *"pay only for what you eat"*. + Out of the box, :app:`Pyramid` provides only the core tools needed for nearly all web applications: mapping URLs to code, security, and serving static assets (files like JavaScript and CSS). Additional tools provide templating, database integration and more. But with :app:`Pyramid` you can *"pay only for what you eat"*. Documentation :app:`Pyramid` is committed to comprehensive and up-to-date documentation. @@ -48,12 +32,10 @@ Speed :app:`Pyramid` is designed to be noticeably fast. Reliability - :app:`Pyramid` is developed conservatively and tested exhaustively. Our motto - is: "If it ain't tested, it's broke". + :app:`Pyramid` is developed conservatively and tested exhaustively. Our motto is: "If it ain't tested, it's broke". Openness - As with Python, the :app:`Pyramid` software is distributed under a - `permissive open source license `_. + As with Python, the :app:`Pyramid` software is distributed under a `permissive open source license `_. .. _why_pyramid: @@ -65,97 +47,49 @@ In a world filled with web frameworks, why should you choose :app:`Pyramid`\ ? Modern ~~~~~~ -:app:`Pyramid` is fully compatible with Python 3. If you develop a -:app:`Pyramid` application today, you can rest assured that you'll be able -to use the most modern features of your favorite language. And in the years -to come, you'll continue to bed working on a framework that is up-to-date -and forward-looking. +:app:`Pyramid` is fully compatible with Python 3. If you develop a :app:`Pyramid` application today, you can rest assured that you'll be able to use the most modern features of your favorite language. And in the years to come, you'll continue to bed working on a framework that is up-to-dateand forward-looking. Tested ~~~~~~ -Untested code is broken by design. The :app:`Pyramid` community has a strong -testing culture and our framework reflects that. Every release of -:app:`Pyramid` has 100% statement coverage (as measured by `coverage -`_) and 95% decision/condition coverage. (as -measured by `instrumental -`_) It is -automatically tested using `Travis `_ and -`Jenkins `_ on supported -versions of Python after each commit to its GitHub repository. `Official -Pyramid add-ons `_ are -held to a similar testing standard. - -We still find bugs in :app:`Pyramid`, but we've noticed we find a lot fewer of -them while working on projects with a solid testing regime. +Untested code is broken by design. The :app:`Pyramid` community has a strong testing culture and our framework reflects that. Every release of :app:`Pyramid` has 100% statement coverage (as measured by `coverage `_) and 95% decision/condition coverage. (as measured by `instrumental `_) It is automatically tested using `Travis `_ and `Jenkins `_ on supported versions of Python after each commit to its GitHub repository. `Official Pyramid add-ons `_ are held to a similar testing standard. + +We still find bugs in :app:`Pyramid`, but we've noticed we find a lot fewer of them while working on projects with a solid testing regime. Documented ~~~~~~~~~~ -The :app:`Pyramid` documentation is comprehensive. We strive to keep our -narrative documentation both complete and friendly to newcomers. We also -maintain a :ref:`cookbook ` of recipes, -demonstrations of common scenarios you might face. Contributions in the form of -improvements to our documentation are always appreciated. And we always welcome -improvements to our `official tutorials -`_ as well -as new contributions to our `community maintained tutorials -`_. +The :app:`Pyramid` documentation is comprehensive. We strive to keep our narrative documentation both complete and friendly to newcomers. We also maintain the :ref:`Pyramid Community Cookbook ` of ecipes demonstrating common scenarios you might face. Contributions in the form of improvements to our documentation are always appreciated. And we always welcome improvements to our `official tutorials `_ as well as new contributions to our `community maintained tutorials `_. Supported ~~~~~~~~~ -You can get help quickly with :app:`Pyramid`. It's our goal that no -:app:`Pyramid` question go unanswered. Whether you ask a question on IRC, on -the Pylons-discuss mailing list, or on StackOverflow, you're likely to get a -reasonably prompt response. +You can get help quickly with :app:`Pyramid`. It's our goal that no :app:`Pyramid` question go unanswered. Whether you ask a question on IRC, on the Pylons-discuss mailing list, or on StackOverflow, you're likely to get a reasonably prompt response. -:app:`Pyramid` is also a welcoming, friendly space for newcomers. We don't -tolerate "support trolls" or those who enjoy berating fellow users in our -support channels. We try to keep it well-lit and new-user-friendly. +:app:`Pyramid` is also a welcoming, friendly space for newcomers. We don't tolerate "support trolls" or those who enjoy berating fellow users in our support channels. We try to keep it well-lit and new-user-friendly. .. seealso:: - See also our `#pyramid IRC channel `_, - our `pylons-discuss mailing list `_, - and :ref:`support-and-development`. + See also our `#pyramid IRC channel `_, our `pylons-discuss mailing list `_, and :ref:`support-and-development`. .. _what_makes_pyramid_unique: What makes Pyramid unique ------------------------- -There are many tools available for web development. What would make someone -want to use :app:`Pyramid` instead? What makes :app:`Pyramid` unique? +There are many tools available for web development. What would make someone want to use :app:`Pyramid` instead? What makes :app:`Pyramid` unique? -With :app:`Pyramid` you can write very small applications without needing to -know a lot. And by learning a bit more, you can write very large applications -too. :app:`Pyramid` will allow you to become productive quickly, and will grow -with you. It won't hold you back when your application is small, and it won't -get in your way when your application becomes large. Other application -frameworks seem to fall into two non-overlapping categories: those that support -"small apps" and those designed for "big apps". +With :app:`Pyramid` you can write very small applications without needing to know a lot. And by learning a bit more, you can write very large applications too. :app:`Pyramid` will allow you to become productive quickly, and will grow with you. It won't hold you back when your application is small, and it won't get in your way when your application becomes large. Other application frameworks seem to fall into two non-overlapping categories: those that support "small apps" and those designed for "big apps". -We don't believe you should have to make this choice. You can't really know how -large your application will become. You certainly shouldn't have to rewrite a -small application in another framework when it gets "too big". A well-designed -framework should be able to be good at both. :app:`Pyramid` is that kind of -framework. +We don't believe you should have to make this choice. You can't really know how large your application will become. You certainly shouldn't have to rewrite a small application in another framework when it gets "too big". A well-designed framework should be able to be good at both. :app:`Pyramid` is that kind of framework. -:app:`Pyramid` provides a set of features that are unique among Python web -frameworks. Others may provide some, but only :app:`Pyramid` provides them all, -in one place, fully documented, and *à la carte* without needing to pay for the -whole banquet. +:app:`Pyramid` provides a set of features that are unique among Python web frameworks. Others may provide some, but only :app:`Pyramid` provides them all, in one place, fully documented, and *à la carte* without needing to pay for the whole banquet. Build single-file applications ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -You can write a :app:`Pyramid` application that lives entirely in one Python -file. Such an application is easy to understand since everything is in one -place. It is easy to deploy because you don't need to know much about Python -packaging. :app:`Pyramid` allows you to do almost everything that so-called -*microframeworks* can in very similar ways. +You can write a :app:`Pyramid` application that lives entirely in one Python file. Such an application is easy to understand since everything is in one place. It is easy to deploy because you don't need to know much about Python packaging. :app:`Pyramid` allows you to do almost everything that so-called *microframeworks* can in very similar ways. .. literalinclude:: helloworld.py @@ -166,8 +100,7 @@ packaging. :app:`Pyramid` allows you to do almost everything that so-called Configure applications with decorators ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -:app:`Pyramid` allows you to keep your configuration right next to your code. -That way you don't have to switch files to see your configuration. For example: +:app:`Pyramid` allows you to keep your configuration right next to your code. That way you don't have to switch files to see your configuration. For example: .. code-block:: python @@ -178,11 +111,7 @@ That way you don't have to switch files to see your configuration. For example: def fred_view(request): return Response('fred') -However, using :app:`Pyramid` configuration decorators does not change your -code. It remains easy to extend, test, or reuse. You can test your code as if -the decorators were not there. You can instruct the framework to ignore some -decorators. You can even use an imperative style to write your configuration, -skipping decorators entirely. +However, using :app:`Pyramid` configuration decorators does not change your code. It remains easy to extend, test, or reuse. You can test your code as if the decorators were not there. You can instruct the framework to ignore some decorators. You can even use an imperative style to write your configuration, skipping decorators entirely. .. seealso:: @@ -191,10 +120,7 @@ skipping decorators entirely. Generate application URLs ~~~~~~~~~~~~~~~~~~~~~~~~~ -Dynamic web applications produce URLs that can change depending on what you are -viewing. :app:`Pyramid` provides flexible, consistent, easy to use tools for -generating URLs. When you use these tools to write your application, you can -change your configuration without fear of breaking links in your web pages. +Dynamic web applications produce URLs that can change depending on what you are viewing. :app:`Pyramid` provides flexible, consistent, easy to use tools for generating URLs. When you use these tools to write your application, you can change your configuration without fear of breaking links in your web pages. .. seealso:: @@ -203,12 +129,7 @@ change your configuration without fear of breaking links in your web pages. Serve static assets ~~~~~~~~~~~~~~~~~~~ -Web applications often require JavaScript, CSS, images and other so-called -*static assets*. :app:`Pyramid` provides flexible tools for serving these kinds -of files. You can serve them directly from :app:`Pyramid`, or host them on an -external server or CDN (content delivery network). Either way, :app:`Pyramid` -can help you to generate URLs so you can change where your files come from -without changing any code. +Web applications often require JavaScript, CSS, images and other so-called *static assets*. :app:`Pyramid` provides flexible tools for serving these kinds of files. You can serve them directly from :app:`Pyramid`, or host them on an external server or CDN (content delivery network). Either way, :app:`Pyramid` can help you to generate URLs so you can change where your files come from without changing any code. .. seealso:: @@ -217,19 +138,13 @@ without changing any code. Develop interactively ~~~~~~~~~~~~~~~~~~~~~ -:app:`Pyramid` can automatically detect changes you make to template files and -code, so your changes are immediately available in your browser. You can debug -using plain old ``print()`` calls, which will display to your console. +:app:`Pyramid` can automatically detect changes you make to template files and code, so your changes are immediately available in your browser. You can debug using plain old ``print()`` calls, which will display to your console. -:app:`Pyramid` has a debug toolbar that allows you to see information about how -your application is working right in your browser. See configuration, installed -packages, SQL queries, logging statements and more. +:app:`Pyramid` has a debug toolbar that allows you to see information about how your application is working right in your browser. See configuration, installed packages, SQL queries, logging statements and more. -When your application has an error, an interactive debugger allows you to poke -around from your browser to find out what happened. +When your application has an error, an interactive debugger allows you to poke around from your browser to find out what happened. -To use the :app:`Pyramid` debug toolbar, build your project with a -:app:`Pyramid` :term:`cookiecutter`. +To use the :app:`Pyramid` debug toolbar, build your project with a :app:`Pyramid` :term:`cookiecutter`. .. seealso:: @@ -240,14 +155,9 @@ Debug with power When things go wrong, :app:`Pyramid` gives you powerful ways to fix the problem. -You can configure :app:`Pyramid` to print helpful information to the console. -The ``debug_notfound`` setting shows information about URLs that aren't -matched. The ``debug_authorization`` setting provides helpful messages about -why you aren't allowed to do what you just tried. +You can configure :app:`Pyramid` to print helpful information to the console. The ``debug_notfound`` setting shows information about URLs that aren't matched. The ``debug_authorization`` setting provides helpful messages about why you aren't allowed to do what you just tried. -:app:`Pyramid` also has command line tools to help you verify your -configuration. You can use ``proutes`` and ``pviews`` to inspect how URLs are -connected to your application code. +:app:`Pyramid` also has command line tools to help you verify your configuration. You can use ``proutes`` and ``pviews`` to inspect how URLs are connected to your application code. .. seealso:: @@ -257,13 +167,9 @@ connected to your application code. Extend your application ~~~~~~~~~~~~~~~~~~~~~~~ -:app:`Pyramid` add-ons extend the core of the framework with useful abilities. -There are add-ons available for your favorite template language, SQL and NoSQL -databases, authentication services and more. +:app:`Pyramid` add-ons extend the core of the framework with useful abilities. There are add-ons available for your favorite template language, SQL and NoSQL databases, authentication services and more. -Supported :app:`Pyramid` add-ons are held to the same demanding standards as -the framework itself. You will find them to be fully tested and well -documented. +Supported :app:`Pyramid` add-ons are held to the same demanding standards as the framework itself. You will find them to be fully tested and well documented. .. seealso:: @@ -272,13 +178,7 @@ documented. Write your views, *your* way ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -A fundamental task for any framework is to map URLs to code. In :app:`Pyramid`, -that code is called a :term:`view callable`. View callables can be functions, -class methods or even callable class instances. You are free to choose the -approach that best fits your use case. Regardless of your choice, -:app:`Pyramid` treats them the same. You can change your mind at any time -without any penalty. There are no artificial distinctions between the various -approaches. +A fundamental task for any framework is to map URLs to code. In :app:`Pyramid`, that code is called a :term:`view callable`. View callables can be functions, class methods or even callable class instances. You are free to choose the approach that best fits your use case. Regardless of your choice, :app:`Pyramid` treats them the same. You can change your mind at any time without any penalty. There are no artificial distinctions between the various approaches. Here's a view callable defined as a function: @@ -321,52 +221,27 @@ Here's a few views defined as methods of a class instead: Find *your* static assets ~~~~~~~~~~~~~~~~~~~~~~~~~ -In many web frameworks, the static assets required by an application are kept -in a globally shared location, "the *static* directory". Others use a lookup -scheme, like an ordered set of template directories. Both of these approaches -have problems when it comes to customization. +In many web frameworks, the static assets required by an application are kept in a globally shared location, "the *static* directory". Others use a lookup scheme, like an ordered set of template directories. Both of these approaches have problems when it comes to customization. -:app:`Pyramid` takes a different approach. Static assets are located using *asset -specifications*, strings that contain reference both to a Python package name -and a file or directory name, e.g. ``MyPackage:static/index.html``. These -specifications are used for templates, JavaScript and CSS, translation files, -and any other package-bound static resource. By using asset specifications, -:app:`Pyramid` makes it easy to extend your application with other packages without -worrying about conflicts. +:app:`Pyramid` takes a different approach. Static assets are located using *asset specifications*, strings that contain reference both to a Python package name and a file or directory name, e.g. ``MyPackage:static/index.html``. These specifications are used for templates, JavaScript and CSS, translation files, and any other package-bound static resource. By using asset specifications, :app:`Pyramid` makes it easy to extend your application with other packages without worrying about conflicts. -What happens if another :app:`Pyramid` package you are using provides an asset -you need to customize? Maybe that page template needs better HTML, or you want -to update some CSS. With asset specifications you can override the assets from -other packages using simple wrappers. +What happens if another :app:`Pyramid` package you are using provides an asset you need to customize? Maybe that page template needs better HTML, or you want to update some CSS. With asset specifications you can override the assets from other packages using simple wrappers. Examples: :ref:`asset_specifications` and :ref:`overriding_assets_section`. Use *your* templates ~~~~~~~~~~~~~~~~~~~~ -In :app:`Pyramid`, the job of creating a ``Response`` belongs to a -:term:`renderer`. Any templating system—Mako, Chameleon, Jinja2—can be a -renderer. In fact, packages exist for all of these systems. But if you'd rather -use another, a structured API exists allowing you to create a renderer using -your favorite templating system. You can use the templating system *you* -understand, not one required by the framework. +In :app:`Pyramid`, the job of creating a ``Response`` belongs to a :term:`renderer`. Any templating system—Mako, Chameleon, Jinja2—can be a renderer. In fact, packages exist for all of these systems. But if you'd rather use another, a structured API exists allowing you to create a renderer using your favorite templating system. You can use the templating system *you* understand, not one required by the framework. -What's more, :app:`Pyramid` does not make you use a single templating system -exclusively. You can use multiple templating systems, even in the same -project. +What's more, :app:`Pyramid` does not make you use a single templating system exclusively. You can use multiple templating systems, even in the same project. Example: :ref:`templates_used_directly`. Write testable views ~~~~~~~~~~~~~~~~~~~~ -When you use a :term:`renderer` with your view callable, you are freed from -needing to return a "webby" ``Response`` object. Instead your views can return -a simple Python dictionary. :app:`Pyramid` will take care of rendering the -information in that dictionary to a ``Response`` on your behalf. As a result, -your views are more easily tested, since you don't need to parse HTML to -evaluate the results. :app:`Pyramid` makes it a snap to write unit tests for -your views, instead of requiring you to use functional tests. +When you use a :term:`renderer` with your view callable, you are freed from needing to return a "webby" ``Response`` object. Instead your views can return a simple Python dictionary. :app:`Pyramid` will take care of rendering the information in that dictionary to a ``Response`` on your behalf. As a result, your views are more easily tested, since you don't need to parse HTML to evaluate the results. :app:`Pyramid` makes it a snap to write unit tests for your views, instead of requiring you to use functional tests. .. index:: pair: renderer; explicitly calling @@ -374,8 +249,7 @@ your views, instead of requiring you to use functional tests. .. _example_render_to_response_call: -For example, a typical web framework might return a ``Response`` object from a -``render_to_response`` call: +For example, a typical web framework might return a ``Response`` object from a ``render_to_response`` call: .. code-block:: python :linenos: @@ -386,8 +260,7 @@ For example, a typical web framework might return a ``Response`` object from a return render_to_response('myapp:templates/mytemplate.pt', {'a':1}, request=request) -While you *can* do this in :app:`Pyramid`, you can also return a Python -dictionary: +While you *can* do this in :app:`Pyramid`, you can also return a Python dictionary: .. code-block:: python :linenos: @@ -398,27 +271,18 @@ dictionary: def myview(request): return {'a':1} -By configuring your view to use a renderer, you tell :app:`Pyramid` to use the -``{'a':1}`` dictionary and the specified template to render a response on your -behalf. +By configuring your view to use a renderer, you tell :app:`Pyramid` to use the ``{'a':1}`` dictionary and the specified template to render a response on your behalf. -The string passed as ``renderer=`` above is an :term:`asset specification`. -Asset specifications are widely used in :app:`Pyramid`. They allow for more -reliable customization. See :ref:`intro_asset_specs` for more information. +The string passed as ``renderer=`` above is an :term:`asset specification`. Asset specifications are widely used in :app:`Pyramid`. They allow for more reliable customization. See :ref:`intro_asset_specs` for more information. Example: :ref:`renderers_chapter`. Use events to coordinate actions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -When writing web applications, it is often important to have your code run at a -specific point in the lifecycle of a request. In :app:`Pyramid`, you can -accomplish this using *subscribers* and *events*. +When writing web applications, it is often important to have your code run at a specific point in the lifecycle of a request. In :app:`Pyramid`, you can accomplish this using *subscribers* and *events*. -For example, you might have a job that needs to be done each time your -application handles a new request. :app:`Pyramid` emits a ``NewRequest`` event -at this point in the request handling lifecycle. You can register your code as -a subscriber to this event using a clear, declarative style: +For example, you might have a job that needs to be done each time your application handles a new request. :app:`Pyramid` emits a ``NewRequest`` event at this point in the request handling lifecycle. You can register your code as a subscriber to this event using a clear, declarative style: .. code-block:: python @@ -429,30 +293,21 @@ a subscriber to this event using a clear, declarative style: def my_job(event): do_something(event.request) -:app:`Pyramid`\ 's event system can be extended as well. If you need, you can -create events of your own and send them using :app:`Pyramid`\ 's event system. -Then anyone working with your application can subscribe to your events and -coordinate their code with yours. +:app:`Pyramid`\ 's event system can be extended as well. If you need, you can create events of your own and send them using :app:`Pyramid`\ 's event system. Then anyone working with your application can subscribe to your events and coordinate their code with yours. Example: :ref:`events_chapter` and :ref:`event_types`. Build international applications ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -:app:`Pyramid` ships with internationalization-related features in its core: -localization, pluralization, and creating message catalogs from source files -and templates. :app:`Pyramid` allows for a plurality of message catalogs via -the use of translation domains. You can create a system that has its own -translations without conflict with other translations in other domains. +:app:`Pyramid` ships with internationalization-related features in its core: localization, pluralization, and creating message catalogs from source files and templates. :app:`Pyramid` allows for a plurality of message catalogs via the use of translation domains. You can create a system that has its own translations without conflict with other translations in other domains. Example: :ref:`i18n_chapter`. Build efficient applications ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -:app:`Pyramid` provides an easy way to *cache* the results of slow or expensive -views. You can indicate in view configuration that you want a view to be -cached: +:app:`Pyramid` provides an easy way to *cache* the results of slow or expensive views. You can indicate in view configuration that you want a view to be cached: .. code-block:: python @@ -460,62 +315,39 @@ cached: def myview(request): # ... -:app:`Pyramid` will automatically add the appropriate ``Cache-Control`` and -``Expires`` headers to the response it creates. +:app:`Pyramid` will automatically add the appropriate ``Cache-Control`` and ``Expires`` headers to the response it creates. -See the :meth:`~pyramid.config.Configurator.add_view` method's ``http_cache`` -documentation for more information. +See the :meth:`~pyramid.config.Configurator.add_view` method's ``http_cache`` documentation for more information. Build fast applications ~~~~~~~~~~~~~~~~~~~~~~~ -The :app:`Pyramid` core is fast. It has been engineered from the ground up for -speed. It only does as much work as absolutely necessary when you ask it to get -a job done. If you need speed from your application, :app:`Pyramid` is the -right choice for you. +The :app:`Pyramid` core is fast. It has been engineered from the ground up for speed. It only does as much work as absolutely necessary when you ask it to get a job done. If you need speed from your application, :app:`Pyramid` is the right choice for you. Example: http://blog.curiasolutions.com/pages/the-great-web-framework-shootout.html Store session data ~~~~~~~~~~~~~~~~~~ -:app:`Pyramid` has built-in support for HTTP sessions, so you can associate -data with specific users between requests. Lots of other frameworks also -support sessions. But :app:`Pyramid` allows you to plug in your own custom -sessioning system. So long as your system conforms to a documented interface, -you can drop it in in place of the provided system. +:app:`Pyramid` has built-in support for HTTP sessions, so you can associate data with specific users between requests. Lots of other frameworks also support sessions. But :app:`Pyramid` allows you to plug in your own custom sessioning system. So long as your system conforms to a documented interface, you can drop it in in place of the provided system. -Currently there is a binding package for the third-party Redis sessioning -system that does exactly this. But if you have a specialized need (perhaps you -want to store your session data in MongoDB), you can. You can even switch -between implementations without changing your application code. +Currently there is a binding package for the third-party Redis sessioning system that does exactly this. But if you have a specialized need (perhaps you want to store your session data in MongoDB), you can. You can even switch between implementations without changing your application code. Example: :ref:`sessions_chapter`. Handle problems with grace ~~~~~~~~~~~~~~~~~~~~~~~~~~ -Mistakes happen. Problems crop up. No-one writes bug-free code. :app:`Pyramid` -provides a way to handle the exceptions your code encounters. An -:term:`exception view` is a special kind of view which is automatically called -when an particular exception type "bubbles up" without being handled by your -application. +Mistakes happen. Problems crop up. No one writes bug-free code. :app:`Pyramid`provides a way to handle the exceptions your code encounters. An :term:`exception view` is a special kind of view which is automatically called when a particular exception type arises without being handled by your application. -For example, you might register an exception view for the :exc:`Exception` -exception type, which will catch *all* exceptions, and present a pretty "well, -this is embarrassing" page. Or you might choose to register an exception view -for only certain application-specific exceptions. You can make a one for when a -file is not found, or when the user doesn't have permission to do something. In -the former case, you can show a pretty "Not Found" page; in the latter case you -might show a login form. +For example, you might register an exception view for the :exc:`Exception` exception type, which will catch *all* exceptions, and present a pretty "well, this is embarrassing" page. Or you might choose to register an exception view for only certain application-specific exceptions. You can make one for when a file is not found, or when the user doesn't have permission to do something. In the former case, you can show a pretty "Not Found" page; in the latter case you might show a login form. Example: :ref:`exception_views`. And much, much more... ~~~~~~~~~~~~~~~~~~~~~~ -:app:`Pyramid` has been built with a number of other sophisticated design -features that make it adaptable. Read more about them below. +:app:`Pyramid` has been built with a number of other sophisticated design features that make it adaptable. Read more about them below. .. toctree:: :maxdepth: 2 @@ -532,10 +364,7 @@ features that make it adaptable. Read more about them below. What Is The Pylons Project? --------------------------- -:app:`Pyramid` is a member of the collection of software published under the -Pylons Project. Pylons software is written by a loose-knit community of -contributors. The `Pylons Project website `_ -includes details about how :app:`Pyramid` relates to the Pylons Project. +:app:`Pyramid` is a member of the collection of software published under the Pylons Project. Pylons software is written by a loose-knit community of contributors. The `Pylons Project website `_ includes details about how :app:`Pyramid` relates to the Pylons Project. .. index:: single: pyramid and other frameworks @@ -547,34 +376,14 @@ includes details about how :app:`Pyramid` relates to the Pylons Project. :app:`Pyramid` and Other Web Frameworks --------------------------------------- -The first release of :app:`Pyramid`\ 's predecessor (named :mod:`repoze.bfg`) -was made in July of 2008. At the end of 2010, we changed the name of -:mod:`repoze.bfg` to :app:`Pyramid`. It was merged into the Pylons project as -:app:`Pyramid` in November of that year. - -:app:`Pyramid` was inspired by :term:`Zope`, :term:`Pylons` (version 1.0), and -:term:`Django`. As a result, :app:`Pyramid` borrows several concepts and -features from each, combining them into a unique web framework. - -Similar to :term:`Zope`, :app:`Pyramid` applications may easily be extended. If -you work within the constraints of the framework, you can produce applications -that can be reused, modified, or extended without needing to modify the -original application code. :app:`Pyramid` also inherits the concepts of -:term:`traversal` and declarative security from Zope. - -Similar to :term:`Pylons` version 1.0, :app:`Pyramid` is largely free of -policy. It makes no assertions about which database or template system you -should use. You are free to use whatever third-party components fit the needs -of your specific application. :app:`Pyramid` also inherits its approach to -:term:`URL dispatch` from Pylons. - -Similar to :term:`Django`, :app:`Pyramid` values extensive documentation. In -addition, the concept of a :term:`view` is used by :app:`Pyramid` much as it -would be by Django. - -Other Python web frameworks advertise themselves as members of a class of web -frameworks named `model-view-controller -`_ -frameworks. The authors of :app:`Pyramid` do not believe that the MVC pattern -fits the web particularly well. However, if this abstraction works for you, -:app:`Pyramid` also generally fits into this class. +The first release of :app:`Pyramid`\ 's predecessor (named :mod:`repoze.bfg`) was made in July of 2008. At the end of 2010, we changed the name of :mod:`repoze.bfg` to :app:`Pyramid`. It was merged into the Pylons project as :app:`Pyramid` in November of that year. + +:app:`Pyramid` was inspired by :term:`Zope`, :term:`Pylons` (version 1.0), and :term:`Django`. As a result, :app:`Pyramid` borrows several concepts and features from each, combining them into a unique web framework. + +Similar to :term:`Zope`, :app:`Pyramid` applications may easily be extended. If you work within the constraints of the framework, you can produce applications that can be reused, modified, or extended without needing to modify the original application code. :app:`Pyramid` also inherits the concepts of :term:`traversal` and declarative security from Zope. + +Similar to :term:`Pylons` version 1.0, :app:`Pyramid` is largely free of policy. It makes no assertions about which database or template system you should use. You are free to use whatever third-party components fit the needs of your specific application. :app:`Pyramid` also inherits its approach to :term:`URL dispatch` from Pylons. + +Similar to :term:`Django`, :app:`Pyramid` values extensive documentation. In addition, the concept of a :term:`view` is used by :app:`Pyramid` much as it would be by Django. + +Other Python web frameworks advertise themselves as members of a class of web frameworks named `model-view-controller `_ frameworks. The authors of :app:`Pyramid` do not believe that the MVC pattern fits the web particularly well. However, if this abstraction works for you, :app:`Pyramid` also generally fits into this class. -- cgit v1.2.3 From 4f66355fb5d7e6fe319742cb50f263425a88c57f Mon Sep 17 00:00:00 2001 From: Bert JW Regeer Date: Tue, 23 May 2017 14:35:48 -0700 Subject: Always push the threadlocals --- pyramid/view.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pyramid/view.py b/pyramid/view.py index 14c8c029e..47b756ad8 100644 --- a/pyramid/view.py +++ b/pyramid/view.py @@ -700,10 +700,9 @@ class ViewMethodsMixin(object): # https://github.com/Pylons/pyramid/issues/700 request_iface = attrs.get('request_iface', IRequest) - try: - if request is not self: - manager.push({'request': request, 'registry': registry}) + manager.push({'request': request, 'registry': registry}) + try: response = _call_view( registry, request, -- cgit v1.2.3 From cc8ce57839d26e82466df9f74c79281450bf18a5 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 5 Jun 2017 11:51:52 -0500 Subject: add changelog for #3060 --- CHANGES.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index fc1d5ae25..06bb71c1e 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -11,7 +11,11 @@ unreleased - Update RELEASING.txt for updating cookiecutters. Change cookiecutter URLs to use shortcut. - See: https://github.com/Pylons/pyramid/issues/3042 + See https://github.com/Pylons/pyramid/issues/3042 + +- Ensure the correct threadlocals are pushed during view execution when + invoked from ``request.invoke_exception_view``. + See https://github.com/Pylons/pyramid/pull/3060 1.9a2 (2017-05-09) ================== -- cgit v1.2.3 From 1814cda1f5ec85c24651bfd29645e4057ae5200e Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Mon, 5 Jun 2017 17:14:32 -0400 Subject: Ensure that instances of 'AllPermissionsList' are iterable. Closes #3073. --- pyramid/security.py | 5 ++++- pyramid/tests/test_security.py | 24 ++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/pyramid/security.py b/pyramid/security.py index 82e6b73a9..035f09f77 100644 --- a/pyramid/security.py +++ b/pyramid/security.py @@ -21,10 +21,13 @@ _marker = object() class AllPermissionsList(object): """ Stand in 'permission list' to represent all permissions """ + def __iter__(self): - return () + return iter(()) + def __contains__(self, other): return True + def __eq__(self, other): return isinstance(other, self.__class__) diff --git a/pyramid/tests/test_security.py b/pyramid/tests/test_security.py index 6d75ac8e3..5561a05d7 100644 --- a/pyramid/tests/test_security.py +++ b/pyramid/tests/test_security.py @@ -16,12 +16,32 @@ class TestAllPermissionsList(unittest.TestCase): def _makeOne(self): return self._getTargetClass()() - def test_it(self): + def test_equality_w_self(self): thing = self._makeOne() self.assertTrue(thing.__eq__(thing)) - self.assertEqual(thing.__iter__(), ()) + + def test_equality_w_other_instances_of_class(self): + thing = self._makeOne() + other = self._makeOne() + self.assertTrue(thing.__eq__(other)) + + def test_equality_miss(self): + thing = self._makeOne() + other = object() + self.assertFalse(thing.__eq__(other)) + + def test_contains_w_string(self): + thing = self._makeOne() self.assertTrue('anything' in thing) + def test_contains_w_object(self): + thing = self._makeOne() + self.assertTrue(object() in thing) + + def test_iterable(self): + thing = self._makeOne() + self.assertEqual(list(thing), []) + def test_singleton(self): from pyramid.security import ALL_PERMISSIONS self.assertEqual(ALL_PERMISSIONS.__class__, self._getTargetClass()) -- cgit v1.2.3 From 381fe100f37e5bbe4d8e032760f244c2a9f92667 Mon Sep 17 00:00:00 2001 From: cewing Date: Tue, 6 Jun 2017 08:33:52 -0700 Subject: restore the r. it's important --- docs/narr/introduction.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst index 2f58a78e7..3dd0cc464 100644 --- a/docs/narr/introduction.rst +++ b/docs/narr/introduction.rst @@ -59,7 +59,7 @@ We still find bugs in :app:`Pyramid`, but we've noticed we find a lot fewer of t Documented ~~~~~~~~~~ -The :app:`Pyramid` documentation is comprehensive. We strive to keep our narrative documentation both complete and friendly to newcomers. We also maintain the :ref:`Pyramid Community Cookbook ` of ecipes demonstrating common scenarios you might face. Contributions in the form of improvements to our documentation are always appreciated. And we always welcome improvements to our `official tutorials `_ as well as new contributions to our `community maintained tutorials `_. +The :app:`Pyramid` documentation is comprehensive. We strive to keep our narrative documentation both complete and friendly to newcomers. We also maintain the :ref:`Pyramid Community Cookbook ` of recipes demonstrating common scenarios you might face. Contributions in the form of improvements to our documentation are always appreciated. And we always welcome improvements to our `official tutorials `_ as well as new contributions to our `community maintained tutorials `_. Supported ~~~~~~~~~ -- cgit v1.2.3 From 57ce7b0c251d34e29a2eb5375c70e751c9b83f4e Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 7 Jun 2017 00:02:15 -0500 Subject: explain why we prefer to avoid activate fixes #3064 --- docs/narr/install.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/narr/install.rst b/docs/narr/install.rst index 2a25ad84d..c2bd00bff 100644 --- a/docs/narr/install.rst +++ b/docs/narr/install.rst @@ -206,9 +206,7 @@ After installing Python as described previously in :ref:`for-mac-os-x-users` or ``$VENV/bin/pip`` clearly specifies that ``pip`` is run from within the virtual environment and not at the system level. - ``activate`` drops turds into the user's shell environment, leaving them - vulnerable to executing commands in the wrong context. ``deactivate`` might - not correctly restore previous shell environment variables. + ``activate`` makes changes to the user's shell environment which can often be convenient. However, in the context of long-form documentation, environment configuration can easily be forgotten. By keeping each snippet explicit we can reduce copy / paste errors by users in which commands are executed against the wrong Python environment. Also, ``deactivate`` might not correctly restore previous shell environment variables. Avoiding ``activate`` keeps the environment more reproducible. Although using ``source bin/activate``, then ``pip``, requires fewer key strokes to issue commands once invoked, there are other things to consider. -- cgit v1.2.3 From daf06d8f8dbfa3245ea4de28defc0249219b6b0e Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 7 Jun 2017 01:05:32 -0500 Subject: add versionadded directive for invoke_exception_view --- pyramid/view.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyramid/view.py b/pyramid/view.py index 47b756ad8..14d11825e 100644 --- a/pyramid/view.py +++ b/pyramid/view.py @@ -668,6 +668,8 @@ class ViewMethodsMixin(object): response. Otherwise the previous values for ``request.exception`` and ``request.exc_info`` will be restored. + .. versionadded:: 1.7 + .. versionchanged:: 1.9 The ``request.exception`` and ``request.exc_info`` properties will reflect the exception used to render the response where previously -- cgit v1.2.3 From 7b3249dff86b4359508897c77dea6aa75421b052 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Fri, 9 Jun 2017 00:04:35 -0500 Subject: add changelog for #3074 --- CHANGES.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 06bb71c1e..1402045d4 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -17,6 +17,10 @@ unreleased invoked from ``request.invoke_exception_view``. See https://github.com/Pylons/pyramid/pull/3060 +- Fix a bug in which ``pyramid.security.ALL_PERMISSIONS`` failed to return + a valid iterator in its ``__iter__`` implementation. + See https://github.com/Pylons/pyramid/pull/3074 + 1.9a2 (2017-05-09) ================== -- cgit v1.2.3 From 5067ffcf19a8659777406b06485fefc75404f2fa Mon Sep 17 00:00:00 2001 From: Volker Diels-Grabsch Date: Fri, 9 Jun 2017 14:48:54 +0200 Subject: Fix forbidden_view for BasicAuthAuthenticationPolicy (#3066) --- CONTRIBUTORS.txt | 2 ++ pyramid/authentication.py | 10 ++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index cbee08d0d..445536e9e 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -302,3 +302,5 @@ Contributors - Jeremy(Ching-Rui) Chen, 2017/04/19 - Fang-Pen Lin, 2017/05/22 + +- Volker Diels-Grabsch, 2017/06/09 diff --git a/pyramid/authentication.py b/pyramid/authentication.py index 03b204e1a..445d6fcd2 100644 --- a/pyramid/authentication.py +++ b/pyramid/authentication.py @@ -1084,10 +1084,12 @@ class BasicAuthAuthenticationPolicy(CallbackAuthenticationPolicy): from pyramid.view import forbidden_view_config @forbidden_view_config() - def basic_challenge(request): - response = HTTPUnauthorized() - response.headers.update(forget(request)) - return response + def forbidden_view(request): + if request.authenticated_userid is None: + response = HTTPUnauthorized() + response.headers.update(forget(request)) + return response + return HTTPForbidden() """ def __init__(self, check, realm='Realm', debug=False): self.check = check -- cgit v1.2.3 From 85623b13b3790dcfae53697f282e9f8e68736e35 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 10 Jun 2017 21:45:09 -0700 Subject: Update src files for ZODB wiki tutorial - ref #3081 --- docs/tutorials/wiki/src/authorization/development.ini | 2 ++ docs/tutorials/wiki/src/authorization/production.ini | 2 ++ docs/tutorials/wiki/src/authorization/setup.py | 4 +++- docs/tutorials/wiki/src/authorization/tutorial/__init__.py | 3 +++ docs/tutorials/wiki/src/basiclayout/README.txt | 2 +- docs/tutorials/wiki/src/basiclayout/development.ini | 2 ++ docs/tutorials/wiki/src/basiclayout/production.ini | 2 ++ docs/tutorials/wiki/src/basiclayout/setup.py | 4 +++- docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py | 3 +++ docs/tutorials/wiki/src/installation/README.txt | 2 +- docs/tutorials/wiki/src/installation/development.ini | 2 ++ docs/tutorials/wiki/src/installation/production.ini | 2 ++ docs/tutorials/wiki/src/installation/setup.py | 4 +++- docs/tutorials/wiki/src/installation/tutorial/__init__.py | 3 +++ docs/tutorials/wiki/src/models/README.txt | 2 +- docs/tutorials/wiki/src/models/development.ini | 2 ++ docs/tutorials/wiki/src/models/production.ini | 2 ++ docs/tutorials/wiki/src/models/setup.py | 4 +++- docs/tutorials/wiki/src/models/tutorial/__init__.py | 3 +++ docs/tutorials/wiki/src/tests/development.ini | 2 ++ docs/tutorials/wiki/src/tests/production.ini | 2 ++ docs/tutorials/wiki/src/tests/setup.py | 4 +++- docs/tutorials/wiki/src/tests/tutorial/__init__.py | 3 +++ docs/tutorials/wiki/src/views/development.ini | 2 ++ docs/tutorials/wiki/src/views/production.ini | 2 ++ docs/tutorials/wiki/src/views/setup.py | 4 +++- docs/tutorials/wiki/src/views/tutorial/__init__.py | 3 +++ 27 files changed, 63 insertions(+), 9 deletions(-) diff --git a/docs/tutorials/wiki/src/authorization/development.ini b/docs/tutorials/wiki/src/authorization/development.ini index 74e7457d6..9d45c3611 100644 --- a/docs/tutorials/wiki/src/authorization/development.ini +++ b/docs/tutorials/wiki/src/authorization/development.ini @@ -16,6 +16,8 @@ pyramid.includes = zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 +retry.attempts = 3 + # By default, the toolbar only appears for clients from IP addresses # '127.0.0.1' and '::1'. # debugtoolbar.hosts = 127.0.0.1 ::1 diff --git a/docs/tutorials/wiki/src/authorization/production.ini b/docs/tutorials/wiki/src/authorization/production.ini index 60b6fe253..92a36813f 100644 --- a/docs/tutorials/wiki/src/authorization/production.ini +++ b/docs/tutorials/wiki/src/authorization/production.ini @@ -14,6 +14,8 @@ pyramid.default_locale_name = en zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 +retry.attempts = 3 + ### # wsgi server configuration ### diff --git a/docs/tutorials/wiki/src/authorization/setup.py b/docs/tutorials/wiki/src/authorization/setup.py index 4a9f041e3..3f0b1317c 100644 --- a/docs/tutorials/wiki/src/authorization/setup.py +++ b/docs/tutorials/wiki/src/authorization/setup.py @@ -9,9 +9,11 @@ with open(os.path.join(here, 'CHANGES.txt')) as f: CHANGES = f.read() requires = [ - 'pyramid', + 'plaster_pastedeploy', + 'pyramid >= 1.9a', 'pyramid_chameleon', 'pyramid_debugtoolbar', + 'pyramid_retry', 'pyramid_tm', 'pyramid_zodbconn', 'transaction', diff --git a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py b/docs/tutorials/wiki/src/authorization/tutorial/__init__.py index 8af2ee5c0..e584eff2b 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/authorization/tutorial/__init__.py @@ -19,10 +19,13 @@ def main(global_config, **settings): 'sosecret', callback=groupfinder, hashalg='sha512') authz_policy = ACLAuthorizationPolicy() config = Configurator(root_factory=root_factory, settings=settings) + settings = config.get_settings() + settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' config.set_authentication_policy(authn_policy) config.set_authorization_policy(authz_policy) config.include('pyramid_chameleon') config.include('pyramid_tm') + config.include('pyramid_retry') config.include('pyramid_zodbconn') config.add_static_view('static', 'static', cache_max_age=3600) config.scan() diff --git a/docs/tutorials/wiki/src/basiclayout/README.txt b/docs/tutorials/wiki/src/basiclayout/README.txt index 5ec53bf9d..8a56d14af 100644 --- a/docs/tutorials/wiki/src/basiclayout/README.txt +++ b/docs/tutorials/wiki/src/basiclayout/README.txt @@ -14,7 +14,7 @@ Getting Started - Upgrade packaging tools. - env/bin/pip install --upgrade pip setuptools wheel + env/bin/pip install --upgrade pip setuptools - Install the project in editable mode with its testing requirements. diff --git a/docs/tutorials/wiki/src/basiclayout/development.ini b/docs/tutorials/wiki/src/basiclayout/development.ini index 74e7457d6..9d45c3611 100644 --- a/docs/tutorials/wiki/src/basiclayout/development.ini +++ b/docs/tutorials/wiki/src/basiclayout/development.ini @@ -16,6 +16,8 @@ pyramid.includes = zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 +retry.attempts = 3 + # By default, the toolbar only appears for clients from IP addresses # '127.0.0.1' and '::1'. # debugtoolbar.hosts = 127.0.0.1 ::1 diff --git a/docs/tutorials/wiki/src/basiclayout/production.ini b/docs/tutorials/wiki/src/basiclayout/production.ini index 60b6fe253..92a36813f 100644 --- a/docs/tutorials/wiki/src/basiclayout/production.ini +++ b/docs/tutorials/wiki/src/basiclayout/production.ini @@ -14,6 +14,8 @@ pyramid.default_locale_name = en zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 +retry.attempts = 3 + ### # wsgi server configuration ### diff --git a/docs/tutorials/wiki/src/basiclayout/setup.py b/docs/tutorials/wiki/src/basiclayout/setup.py index 5d1e9c7b5..d743c984f 100644 --- a/docs/tutorials/wiki/src/basiclayout/setup.py +++ b/docs/tutorials/wiki/src/basiclayout/setup.py @@ -9,9 +9,11 @@ with open(os.path.join(here, 'CHANGES.txt')) as f: CHANGES = f.read() requires = [ - 'pyramid', + 'plaster_pastedeploy', + 'pyramid >= 1.9a', 'pyramid_chameleon', 'pyramid_debugtoolbar', + 'pyramid_retry', 'pyramid_tm', 'pyramid_zodbconn', 'transaction', diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py b/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py index 728f7ac02..eb703e086 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py @@ -12,8 +12,11 @@ def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ config = Configurator(root_factory=root_factory, settings=settings) + settings = config.get_settings() + settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' config.include('pyramid_chameleon') config.include('pyramid_tm') + config.include('pyramid_retry') config.include('pyramid_zodbconn') config.add_static_view('static', 'static', cache_max_age=3600) config.scan() diff --git a/docs/tutorials/wiki/src/installation/README.txt b/docs/tutorials/wiki/src/installation/README.txt index 5ec53bf9d..8a56d14af 100644 --- a/docs/tutorials/wiki/src/installation/README.txt +++ b/docs/tutorials/wiki/src/installation/README.txt @@ -14,7 +14,7 @@ Getting Started - Upgrade packaging tools. - env/bin/pip install --upgrade pip setuptools wheel + env/bin/pip install --upgrade pip setuptools - Install the project in editable mode with its testing requirements. diff --git a/docs/tutorials/wiki/src/installation/development.ini b/docs/tutorials/wiki/src/installation/development.ini index 74e7457d6..9d45c3611 100644 --- a/docs/tutorials/wiki/src/installation/development.ini +++ b/docs/tutorials/wiki/src/installation/development.ini @@ -16,6 +16,8 @@ pyramid.includes = zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 +retry.attempts = 3 + # By default, the toolbar only appears for clients from IP addresses # '127.0.0.1' and '::1'. # debugtoolbar.hosts = 127.0.0.1 ::1 diff --git a/docs/tutorials/wiki/src/installation/production.ini b/docs/tutorials/wiki/src/installation/production.ini index 60b6fe253..92a36813f 100644 --- a/docs/tutorials/wiki/src/installation/production.ini +++ b/docs/tutorials/wiki/src/installation/production.ini @@ -14,6 +14,8 @@ pyramid.default_locale_name = en zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 +retry.attempts = 3 + ### # wsgi server configuration ### diff --git a/docs/tutorials/wiki/src/installation/setup.py b/docs/tutorials/wiki/src/installation/setup.py index 5d1e9c7b5..d743c984f 100644 --- a/docs/tutorials/wiki/src/installation/setup.py +++ b/docs/tutorials/wiki/src/installation/setup.py @@ -9,9 +9,11 @@ with open(os.path.join(here, 'CHANGES.txt')) as f: CHANGES = f.read() requires = [ - 'pyramid', + 'plaster_pastedeploy', + 'pyramid >= 1.9a', 'pyramid_chameleon', 'pyramid_debugtoolbar', + 'pyramid_retry', 'pyramid_tm', 'pyramid_zodbconn', 'transaction', diff --git a/docs/tutorials/wiki/src/installation/tutorial/__init__.py b/docs/tutorials/wiki/src/installation/tutorial/__init__.py index 728f7ac02..eb703e086 100644 --- a/docs/tutorials/wiki/src/installation/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/installation/tutorial/__init__.py @@ -12,8 +12,11 @@ def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ config = Configurator(root_factory=root_factory, settings=settings) + settings = config.get_settings() + settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' config.include('pyramid_chameleon') config.include('pyramid_tm') + config.include('pyramid_retry') config.include('pyramid_zodbconn') config.add_static_view('static', 'static', cache_max_age=3600) config.scan() diff --git a/docs/tutorials/wiki/src/models/README.txt b/docs/tutorials/wiki/src/models/README.txt index 5ec53bf9d..8a56d14af 100644 --- a/docs/tutorials/wiki/src/models/README.txt +++ b/docs/tutorials/wiki/src/models/README.txt @@ -14,7 +14,7 @@ Getting Started - Upgrade packaging tools. - env/bin/pip install --upgrade pip setuptools wheel + env/bin/pip install --upgrade pip setuptools - Install the project in editable mode with its testing requirements. diff --git a/docs/tutorials/wiki/src/models/development.ini b/docs/tutorials/wiki/src/models/development.ini index 74e7457d6..9d45c3611 100644 --- a/docs/tutorials/wiki/src/models/development.ini +++ b/docs/tutorials/wiki/src/models/development.ini @@ -16,6 +16,8 @@ pyramid.includes = zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 +retry.attempts = 3 + # By default, the toolbar only appears for clients from IP addresses # '127.0.0.1' and '::1'. # debugtoolbar.hosts = 127.0.0.1 ::1 diff --git a/docs/tutorials/wiki/src/models/production.ini b/docs/tutorials/wiki/src/models/production.ini index 60b6fe253..92a36813f 100644 --- a/docs/tutorials/wiki/src/models/production.ini +++ b/docs/tutorials/wiki/src/models/production.ini @@ -14,6 +14,8 @@ pyramid.default_locale_name = en zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 +retry.attempts = 3 + ### # wsgi server configuration ### diff --git a/docs/tutorials/wiki/src/models/setup.py b/docs/tutorials/wiki/src/models/setup.py index 5d1e9c7b5..d743c984f 100644 --- a/docs/tutorials/wiki/src/models/setup.py +++ b/docs/tutorials/wiki/src/models/setup.py @@ -9,9 +9,11 @@ with open(os.path.join(here, 'CHANGES.txt')) as f: CHANGES = f.read() requires = [ - 'pyramid', + 'plaster_pastedeploy', + 'pyramid >= 1.9a', 'pyramid_chameleon', 'pyramid_debugtoolbar', + 'pyramid_retry', 'pyramid_tm', 'pyramid_zodbconn', 'transaction', diff --git a/docs/tutorials/wiki/src/models/tutorial/__init__.py b/docs/tutorials/wiki/src/models/tutorial/__init__.py index 728f7ac02..eb703e086 100644 --- a/docs/tutorials/wiki/src/models/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/models/tutorial/__init__.py @@ -12,8 +12,11 @@ def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ config = Configurator(root_factory=root_factory, settings=settings) + settings = config.get_settings() + settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' config.include('pyramid_chameleon') config.include('pyramid_tm') + config.include('pyramid_retry') config.include('pyramid_zodbconn') config.add_static_view('static', 'static', cache_max_age=3600) config.scan() diff --git a/docs/tutorials/wiki/src/tests/development.ini b/docs/tutorials/wiki/src/tests/development.ini index 74e7457d6..9d45c3611 100644 --- a/docs/tutorials/wiki/src/tests/development.ini +++ b/docs/tutorials/wiki/src/tests/development.ini @@ -16,6 +16,8 @@ pyramid.includes = zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 +retry.attempts = 3 + # By default, the toolbar only appears for clients from IP addresses # '127.0.0.1' and '::1'. # debugtoolbar.hosts = 127.0.0.1 ::1 diff --git a/docs/tutorials/wiki/src/tests/production.ini b/docs/tutorials/wiki/src/tests/production.ini index 60b6fe253..92a36813f 100644 --- a/docs/tutorials/wiki/src/tests/production.ini +++ b/docs/tutorials/wiki/src/tests/production.ini @@ -14,6 +14,8 @@ pyramid.default_locale_name = en zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 +retry.attempts = 3 + ### # wsgi server configuration ### diff --git a/docs/tutorials/wiki/src/tests/setup.py b/docs/tutorials/wiki/src/tests/setup.py index 4a9f041e3..3f0b1317c 100644 --- a/docs/tutorials/wiki/src/tests/setup.py +++ b/docs/tutorials/wiki/src/tests/setup.py @@ -9,9 +9,11 @@ with open(os.path.join(here, 'CHANGES.txt')) as f: CHANGES = f.read() requires = [ - 'pyramid', + 'plaster_pastedeploy', + 'pyramid >= 1.9a', 'pyramid_chameleon', 'pyramid_debugtoolbar', + 'pyramid_retry', 'pyramid_tm', 'pyramid_zodbconn', 'transaction', diff --git a/docs/tutorials/wiki/src/tests/tutorial/__init__.py b/docs/tutorials/wiki/src/tests/tutorial/__init__.py index 8af2ee5c0..e584eff2b 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/tests/tutorial/__init__.py @@ -19,10 +19,13 @@ def main(global_config, **settings): 'sosecret', callback=groupfinder, hashalg='sha512') authz_policy = ACLAuthorizationPolicy() config = Configurator(root_factory=root_factory, settings=settings) + settings = config.get_settings() + settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' config.set_authentication_policy(authn_policy) config.set_authorization_policy(authz_policy) config.include('pyramid_chameleon') config.include('pyramid_tm') + config.include('pyramid_retry') config.include('pyramid_zodbconn') config.add_static_view('static', 'static', cache_max_age=3600) config.scan() diff --git a/docs/tutorials/wiki/src/views/development.ini b/docs/tutorials/wiki/src/views/development.ini index 74e7457d6..9d45c3611 100644 --- a/docs/tutorials/wiki/src/views/development.ini +++ b/docs/tutorials/wiki/src/views/development.ini @@ -16,6 +16,8 @@ pyramid.includes = zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 +retry.attempts = 3 + # By default, the toolbar only appears for clients from IP addresses # '127.0.0.1' and '::1'. # debugtoolbar.hosts = 127.0.0.1 ::1 diff --git a/docs/tutorials/wiki/src/views/production.ini b/docs/tutorials/wiki/src/views/production.ini index 60b6fe253..92a36813f 100644 --- a/docs/tutorials/wiki/src/views/production.ini +++ b/docs/tutorials/wiki/src/views/production.ini @@ -14,6 +14,8 @@ pyramid.default_locale_name = en zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 +retry.attempts = 3 + ### # wsgi server configuration ### diff --git a/docs/tutorials/wiki/src/views/setup.py b/docs/tutorials/wiki/src/views/setup.py index 598ad8146..bd3d15af1 100644 --- a/docs/tutorials/wiki/src/views/setup.py +++ b/docs/tutorials/wiki/src/views/setup.py @@ -9,9 +9,11 @@ with open(os.path.join(here, 'CHANGES.txt')) as f: CHANGES = f.read() requires = [ - 'pyramid', + 'plaster_pastedeploy', + 'pyramid >= 1.9a', 'pyramid_chameleon', 'pyramid_debugtoolbar', + 'pyramid_retry', 'pyramid_tm', 'pyramid_zodbconn', 'transaction', diff --git a/docs/tutorials/wiki/src/views/tutorial/__init__.py b/docs/tutorials/wiki/src/views/tutorial/__init__.py index 728f7ac02..eb703e086 100644 --- a/docs/tutorials/wiki/src/views/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/views/tutorial/__init__.py @@ -12,8 +12,11 @@ def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ config = Configurator(root_factory=root_factory, settings=settings) + settings = config.get_settings() + settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' config.include('pyramid_chameleon') config.include('pyramid_tm') + config.include('pyramid_retry') config.include('pyramid_zodbconn') config.add_static_view('static', 'static', cache_max_age=3600) config.scan() -- cgit v1.2.3 From 909ae055f2f7391036736ff41a0564becb60478f Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 10 Jun 2017 22:09:12 -0700 Subject: synch emphasize-lines with src files for zodb wiki tutorial --- docs/tutorials/wiki/authorization.rst | 8 ++++---- docs/tutorials/wiki/basiclayout.rst | 12 ++++++++---- docs/tutorials/wiki/definingviews.rst | 2 +- docs/tutorials/wiki/installation.rst | 4 ++-- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst index 0ba734f83..211d69f3a 100644 --- a/docs/tutorials/wiki/authorization.rst +++ b/docs/tutorials/wiki/authorization.rst @@ -49,7 +49,7 @@ Open ``setup.py`` and edit it to look like the following: .. literalinclude:: src/authorization/setup.py :linenos: - :emphasize-lines: 21 + :emphasize-lines: 23 :language: python Only the highlighted line needs to be added. @@ -155,9 +155,9 @@ statements: Now add those policies to the configuration: .. literalinclude:: src/authorization/tutorial/__init__.py - :lines: 18-23 + :lines: 18-25 :lineno-match: - :emphasize-lines: 1-3,5-6 + :emphasize-lines: 1-3,7-9 :language: python Only the highlighted lines need to be added. @@ -327,7 +327,7 @@ Our ``tutorial/__init__.py`` will look like this when we're done: .. literalinclude:: src/authorization/tutorial/__init__.py :linenos: - :emphasize-lines: 4-5,8,18-20,22-23 + :emphasize-lines: 4-5,8,18-20,24-25 :language: python Only the highlighted lines need to be added or edited. diff --git a/docs/tutorials/wiki/basiclayout.rst b/docs/tutorials/wiki/basiclayout.rst index d00eab956..f713d1057 100644 --- a/docs/tutorials/wiki/basiclayout.rst +++ b/docs/tutorials/wiki/basiclayout.rst @@ -41,14 +41,18 @@ Open ``tutorial/__init__.py``. It should already contain the following: factory and the settings keywords parsed by :term:`PasteDeploy`. The root factory is named ``root_factory``. -#. *Line 15*. Include support for the :term:`Chameleon` template rendering +#. *Lines 15 and 16*. Get the settings and use an explicit transaction transaction manager for apps so that they do not implicitly create new transactions when touching the manager outside of the ``pyramid_tm`` lifecycle. + +#. *Line 17*. Include support for the :term:`Chameleon` template rendering bindings, allowing us to use the ``.pt`` templates. -#. *Line 16*. Include support for ``pyramid_tm``, allowing Pyramid requests to join the active transaction as provided by the `transaction `_ package. +#. *Line 18*. Include support for ``pyramid_tm``, allowing Pyramid requests to join the active transaction as provided by the `transaction `_ package. + +#. *Line 19*. Include support for ``pyramid_retry`` to retry a request when transient exceptions occur. -#. *Line 17*. Include support for ``pyramid_zodbconn``, providing integration between :term:`ZODB` and a Pyramid application. +#. *Line 20*. Include support for ``pyramid_zodbconn``, providing integration between :term:`ZODB` and a Pyramid application. -#. *Line 18*. Register a "static view", which answers requests whose URL +#. *Line 21*. Register a "static view", which answers requests whose URL paths start with ``/static``, using the :meth:`pyramid.config.Configurator.add_static_view` method. This statement registers a view that will serve up static assets, such as CSS diff --git a/docs/tutorials/wiki/definingviews.rst b/docs/tutorials/wiki/definingviews.rst index 442d5ed18..f4ca9b8d7 100644 --- a/docs/tutorials/wiki/definingviews.rst +++ b/docs/tutorials/wiki/definingviews.rst @@ -47,7 +47,7 @@ Open ``setup.py`` and edit it to look like the following: .. literalinclude:: src/views/setup.py :linenos: - :emphasize-lines: 20 + :emphasize-lines: 22 :language: python Only the highlighted line needs to be added. diff --git a/docs/tutorials/wiki/installation.rst b/docs/tutorials/wiki/installation.rst index 3e7434bd7..e9a93f9fe 100644 --- a/docs/tutorials/wiki/installation.rst +++ b/docs/tutorials/wiki/installation.rst @@ -180,12 +180,12 @@ Testing requirements are defined in our project's ``setup.py`` file, in the ``te .. literalinclude:: src/installation/setup.py :language: python :lineno-match: - :lines: 22-26 + :lines: 24-28 .. literalinclude:: src/installation/setup.py :language: python :lineno-match: - :lines: 46-48 + :lines: 48-50 .. _running_tests: -- cgit v1.2.3 From 2c0e3e334955574383fa73eaf932931199e13a8a Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 10 Jun 2017 22:57:54 -0700 Subject: update src files and synch emphasize-lines for alchemy wiki tutorial --- docs/tutorials/wiki2/authentication.rst | 4 ++-- docs/tutorials/wiki2/definingmodels.rst | 2 +- docs/tutorials/wiki2/installation.rst | 4 ++-- docs/tutorials/wiki2/src/authentication/README.txt | 2 +- docs/tutorials/wiki2/src/authentication/development.ini | 2 ++ docs/tutorials/wiki2/src/authentication/production.ini | 2 ++ docs/tutorials/wiki2/src/authentication/setup.py | 6 ++++-- docs/tutorials/wiki2/src/authentication/tutorial/models/__init__.py | 3 +++ docs/tutorials/wiki2/src/authorization/README.txt | 2 +- docs/tutorials/wiki2/src/authorization/development.ini | 2 ++ docs/tutorials/wiki2/src/authorization/production.ini | 2 ++ docs/tutorials/wiki2/src/authorization/setup.py | 6 ++++-- docs/tutorials/wiki2/src/authorization/tutorial/models/__init__.py | 3 +++ docs/tutorials/wiki2/src/basiclayout/README.txt | 2 +- docs/tutorials/wiki2/src/basiclayout/development.ini | 2 ++ docs/tutorials/wiki2/src/basiclayout/production.ini | 2 ++ docs/tutorials/wiki2/src/basiclayout/setup.py | 6 ++++-- docs/tutorials/wiki2/src/basiclayout/tutorial/models/__init__.py | 3 +++ docs/tutorials/wiki2/src/installation/README.txt | 2 +- docs/tutorials/wiki2/src/installation/development.ini | 2 ++ docs/tutorials/wiki2/src/installation/production.ini | 2 ++ docs/tutorials/wiki2/src/installation/setup.py | 6 ++++-- docs/tutorials/wiki2/src/installation/tutorial/models/__init__.py | 3 +++ docs/tutorials/wiki2/src/models/README.txt | 2 +- docs/tutorials/wiki2/src/models/development.ini | 2 ++ docs/tutorials/wiki2/src/models/production.ini | 2 ++ docs/tutorials/wiki2/src/models/setup.py | 6 ++++-- docs/tutorials/wiki2/src/models/tutorial/__init__.py | 2 -- docs/tutorials/wiki2/src/models/tutorial/models/__init__.py | 3 +++ docs/tutorials/wiki2/src/tests/README.txt | 2 +- docs/tutorials/wiki2/src/tests/development.ini | 2 ++ docs/tutorials/wiki2/src/tests/production.ini | 2 ++ docs/tutorials/wiki2/src/tests/setup.py | 6 ++++-- docs/tutorials/wiki2/src/tests/tutorial/models/__init__.py | 3 +++ docs/tutorials/wiki2/src/views/README.txt | 2 +- docs/tutorials/wiki2/src/views/development.ini | 2 ++ docs/tutorials/wiki2/src/views/production.ini | 2 ++ docs/tutorials/wiki2/src/views/setup.py | 6 ++++-- docs/tutorials/wiki2/src/views/tutorial/models/__init__.py | 3 +++ 39 files changed, 89 insertions(+), 28 deletions(-) diff --git a/docs/tutorials/wiki2/authentication.rst b/docs/tutorials/wiki2/authentication.rst index ff59ce70b..85977d1be 100644 --- a/docs/tutorials/wiki2/authentication.rst +++ b/docs/tutorials/wiki2/authentication.rst @@ -92,7 +92,7 @@ Our authentication policy is expecting a new setting, ``auth.secret``. Open the file ``development.ini`` and add the highlighted line below: .. literalinclude:: src/authentication/development.ini - :lines: 17-19 + :lines: 19-21 :emphasize-lines: 3 :lineno-match: :language: ini @@ -101,7 +101,7 @@ Finally, best practices tell us to use a different secret for production, so open ``production.ini`` and add a different secret: .. literalinclude:: src/authentication/production.ini - :lines: 15-17 + :lines: 17-19 :emphasize-lines: 3 :lineno-match: :language: ini diff --git a/docs/tutorials/wiki2/definingmodels.rst b/docs/tutorials/wiki2/definingmodels.rst index 801b56eb4..5cebb943c 100644 --- a/docs/tutorials/wiki2/definingmodels.rst +++ b/docs/tutorials/wiki2/definingmodels.rst @@ -153,7 +153,7 @@ the following: .. literalinclude:: src/models/tutorial/models/__init__.py :linenos: :language: py - :emphasize-lines: 10,11 + :emphasize-lines: 8,9 Here we align our imports with the names of the models, ``Page`` and ``User``. diff --git a/docs/tutorials/wiki2/installation.rst b/docs/tutorials/wiki2/installation.rst index 56197900c..0d49fc12b 100644 --- a/docs/tutorials/wiki2/installation.rst +++ b/docs/tutorials/wiki2/installation.rst @@ -190,12 +190,12 @@ Testing requirements are defined in our project's ``setup.py`` file, in the ``te .. literalinclude:: src/installation/setup.py :language: python :lineno-match: - :lines: 22-26 + :lines: 24-28 .. literalinclude:: src/installation/setup.py :language: python :lineno-match: - :lines: 46-48 + :lines: 48-50 .. _sql_running_tests: diff --git a/docs/tutorials/wiki2/src/authentication/README.txt b/docs/tutorials/wiki2/src/authentication/README.txt index 81102a869..7b33da610 100644 --- a/docs/tutorials/wiki2/src/authentication/README.txt +++ b/docs/tutorials/wiki2/src/authentication/README.txt @@ -14,7 +14,7 @@ Getting Started - Upgrade packaging tools. - env/bin/pip install --upgrade pip setuptools wheel + env/bin/pip install --upgrade pip setuptools - Install the project in editable mode with its testing requirements. diff --git a/docs/tutorials/wiki2/src/authentication/development.ini b/docs/tutorials/wiki2/src/authentication/development.ini index 0786c1f66..00d0dd2bf 100644 --- a/docs/tutorials/wiki2/src/authentication/development.ini +++ b/docs/tutorials/wiki2/src/authentication/development.ini @@ -16,6 +16,8 @@ pyramid.includes = sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite +retry.attempts = 3 + auth.secret = seekrit # By default, the toolbar only appears for clients from IP addresses diff --git a/docs/tutorials/wiki2/src/authentication/production.ini b/docs/tutorials/wiki2/src/authentication/production.ini index 05d60feec..e55e9fecc 100644 --- a/docs/tutorials/wiki2/src/authentication/production.ini +++ b/docs/tutorials/wiki2/src/authentication/production.ini @@ -14,6 +14,8 @@ pyramid.default_locale_name = en sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite +retry.attempts = 3 + auth.secret = real-seekrit ### diff --git a/docs/tutorials/wiki2/src/authentication/setup.py b/docs/tutorials/wiki2/src/authentication/setup.py index cc1aa421c..abc24876d 100644 --- a/docs/tutorials/wiki2/src/authentication/setup.py +++ b/docs/tutorials/wiki2/src/authentication/setup.py @@ -11,9 +11,11 @@ with open(os.path.join(here, 'CHANGES.txt')) as f: requires = [ 'bcrypt', 'docutils', - 'pyramid', - 'pyramid_jinja2', + 'plaster_pastedeploy', + 'pyramid >= 1.9a', 'pyramid_debugtoolbar', + 'pyramid_jinja2', + 'pyramid_retry', 'pyramid_tm', 'SQLAlchemy', 'transaction', diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/authentication/tutorial/models/__init__.py index cd8347ccd..3c9ba8e54 100644 --- a/docs/tutorials/wiki2/src/authentication/tutorial/models/__init__.py +++ b/docs/tutorials/wiki2/src/authentication/tutorial/models/__init__.py @@ -63,6 +63,9 @@ def includeme(config): # use pyramid_tm to hook the transaction lifecycle to the request config.include('pyramid_tm') + # use pyramid_retry to retry a request when transient exceptions occur + config.include('pyramid_retry') + session_factory = get_session_factory(get_engine(settings)) config.registry['dbsession_factory'] = session_factory diff --git a/docs/tutorials/wiki2/src/authorization/README.txt b/docs/tutorials/wiki2/src/authorization/README.txt index 81102a869..7b33da610 100644 --- a/docs/tutorials/wiki2/src/authorization/README.txt +++ b/docs/tutorials/wiki2/src/authorization/README.txt @@ -14,7 +14,7 @@ Getting Started - Upgrade packaging tools. - env/bin/pip install --upgrade pip setuptools wheel + env/bin/pip install --upgrade pip setuptools - Install the project in editable mode with its testing requirements. diff --git a/docs/tutorials/wiki2/src/authorization/development.ini b/docs/tutorials/wiki2/src/authorization/development.ini index 0786c1f66..00d0dd2bf 100644 --- a/docs/tutorials/wiki2/src/authorization/development.ini +++ b/docs/tutorials/wiki2/src/authorization/development.ini @@ -16,6 +16,8 @@ pyramid.includes = sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite +retry.attempts = 3 + auth.secret = seekrit # By default, the toolbar only appears for clients from IP addresses diff --git a/docs/tutorials/wiki2/src/authorization/production.ini b/docs/tutorials/wiki2/src/authorization/production.ini index 05d60feec..e55e9fecc 100644 --- a/docs/tutorials/wiki2/src/authorization/production.ini +++ b/docs/tutorials/wiki2/src/authorization/production.ini @@ -14,6 +14,8 @@ pyramid.default_locale_name = en sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite +retry.attempts = 3 + auth.secret = real-seekrit ### diff --git a/docs/tutorials/wiki2/src/authorization/setup.py b/docs/tutorials/wiki2/src/authorization/setup.py index cc1aa421c..abc24876d 100644 --- a/docs/tutorials/wiki2/src/authorization/setup.py +++ b/docs/tutorials/wiki2/src/authorization/setup.py @@ -11,9 +11,11 @@ with open(os.path.join(here, 'CHANGES.txt')) as f: requires = [ 'bcrypt', 'docutils', - 'pyramid', - 'pyramid_jinja2', + 'plaster_pastedeploy', + 'pyramid >= 1.9a', 'pyramid_debugtoolbar', + 'pyramid_jinja2', + 'pyramid_retry', 'pyramid_tm', 'SQLAlchemy', 'transaction', diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/authorization/tutorial/models/__init__.py index cd8347ccd..3c9ba8e54 100644 --- a/docs/tutorials/wiki2/src/authorization/tutorial/models/__init__.py +++ b/docs/tutorials/wiki2/src/authorization/tutorial/models/__init__.py @@ -63,6 +63,9 @@ def includeme(config): # use pyramid_tm to hook the transaction lifecycle to the request config.include('pyramid_tm') + # use pyramid_retry to retry a request when transient exceptions occur + config.include('pyramid_retry') + session_factory = get_session_factory(get_engine(settings)) config.registry['dbsession_factory'] = session_factory diff --git a/docs/tutorials/wiki2/src/basiclayout/README.txt b/docs/tutorials/wiki2/src/basiclayout/README.txt index 81102a869..7b33da610 100644 --- a/docs/tutorials/wiki2/src/basiclayout/README.txt +++ b/docs/tutorials/wiki2/src/basiclayout/README.txt @@ -14,7 +14,7 @@ Getting Started - Upgrade packaging tools. - env/bin/pip install --upgrade pip setuptools wheel + env/bin/pip install --upgrade pip setuptools - Install the project in editable mode with its testing requirements. diff --git a/docs/tutorials/wiki2/src/basiclayout/development.ini b/docs/tutorials/wiki2/src/basiclayout/development.ini index be80882a5..73ef3d863 100644 --- a/docs/tutorials/wiki2/src/basiclayout/development.ini +++ b/docs/tutorials/wiki2/src/basiclayout/development.ini @@ -16,6 +16,8 @@ pyramid.includes = sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite +retry.attempts = 3 + # By default, the toolbar only appears for clients from IP addresses # '127.0.0.1' and '::1'. # debugtoolbar.hosts = 127.0.0.1 ::1 diff --git a/docs/tutorials/wiki2/src/basiclayout/production.ini b/docs/tutorials/wiki2/src/basiclayout/production.ini index c01ad9a7e..8dea38631 100644 --- a/docs/tutorials/wiki2/src/basiclayout/production.ini +++ b/docs/tutorials/wiki2/src/basiclayout/production.ini @@ -14,6 +14,8 @@ pyramid.default_locale_name = en sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite +retry.attempts = 3 + ### # wsgi server configuration ### diff --git a/docs/tutorials/wiki2/src/basiclayout/setup.py b/docs/tutorials/wiki2/src/basiclayout/setup.py index d3992a8f2..9fc5519a5 100644 --- a/docs/tutorials/wiki2/src/basiclayout/setup.py +++ b/docs/tutorials/wiki2/src/basiclayout/setup.py @@ -9,9 +9,11 @@ with open(os.path.join(here, 'CHANGES.txt')) as f: CHANGES = f.read() requires = [ - 'pyramid', - 'pyramid_jinja2', + 'plaster_pastedeploy', + 'pyramid >= 1.9a', 'pyramid_debugtoolbar', + 'pyramid_jinja2', + 'pyramid_retry', 'pyramid_tm', 'SQLAlchemy', 'transaction', diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/models/__init__.py index ae575691c..d8a273e9e 100644 --- a/docs/tutorials/wiki2/src/basiclayout/tutorial/models/__init__.py +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/models/__init__.py @@ -62,6 +62,9 @@ def includeme(config): # use pyramid_tm to hook the transaction lifecycle to the request config.include('pyramid_tm') + # use pyramid_retry to retry a request when transient exceptions occur + config.include('pyramid_retry') + session_factory = get_session_factory(get_engine(settings)) config.registry['dbsession_factory'] = session_factory diff --git a/docs/tutorials/wiki2/src/installation/README.txt b/docs/tutorials/wiki2/src/installation/README.txt index 81102a869..7b33da610 100644 --- a/docs/tutorials/wiki2/src/installation/README.txt +++ b/docs/tutorials/wiki2/src/installation/README.txt @@ -14,7 +14,7 @@ Getting Started - Upgrade packaging tools. - env/bin/pip install --upgrade pip setuptools wheel + env/bin/pip install --upgrade pip setuptools - Install the project in editable mode with its testing requirements. diff --git a/docs/tutorials/wiki2/src/installation/development.ini b/docs/tutorials/wiki2/src/installation/development.ini index be80882a5..73ef3d863 100644 --- a/docs/tutorials/wiki2/src/installation/development.ini +++ b/docs/tutorials/wiki2/src/installation/development.ini @@ -16,6 +16,8 @@ pyramid.includes = sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite +retry.attempts = 3 + # By default, the toolbar only appears for clients from IP addresses # '127.0.0.1' and '::1'. # debugtoolbar.hosts = 127.0.0.1 ::1 diff --git a/docs/tutorials/wiki2/src/installation/production.ini b/docs/tutorials/wiki2/src/installation/production.ini index c01ad9a7e..8dea38631 100644 --- a/docs/tutorials/wiki2/src/installation/production.ini +++ b/docs/tutorials/wiki2/src/installation/production.ini @@ -14,6 +14,8 @@ pyramid.default_locale_name = en sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite +retry.attempts = 3 + ### # wsgi server configuration ### diff --git a/docs/tutorials/wiki2/src/installation/setup.py b/docs/tutorials/wiki2/src/installation/setup.py index d3992a8f2..9fc5519a5 100644 --- a/docs/tutorials/wiki2/src/installation/setup.py +++ b/docs/tutorials/wiki2/src/installation/setup.py @@ -9,9 +9,11 @@ with open(os.path.join(here, 'CHANGES.txt')) as f: CHANGES = f.read() requires = [ - 'pyramid', - 'pyramid_jinja2', + 'plaster_pastedeploy', + 'pyramid >= 1.9a', 'pyramid_debugtoolbar', + 'pyramid_jinja2', + 'pyramid_retry', 'pyramid_tm', 'SQLAlchemy', 'transaction', diff --git a/docs/tutorials/wiki2/src/installation/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/installation/tutorial/models/__init__.py index ae575691c..d8a273e9e 100644 --- a/docs/tutorials/wiki2/src/installation/tutorial/models/__init__.py +++ b/docs/tutorials/wiki2/src/installation/tutorial/models/__init__.py @@ -62,6 +62,9 @@ def includeme(config): # use pyramid_tm to hook the transaction lifecycle to the request config.include('pyramid_tm') + # use pyramid_retry to retry a request when transient exceptions occur + config.include('pyramid_retry') + session_factory = get_session_factory(get_engine(settings)) config.registry['dbsession_factory'] = session_factory diff --git a/docs/tutorials/wiki2/src/models/README.txt b/docs/tutorials/wiki2/src/models/README.txt index 81102a869..7b33da610 100644 --- a/docs/tutorials/wiki2/src/models/README.txt +++ b/docs/tutorials/wiki2/src/models/README.txt @@ -14,7 +14,7 @@ Getting Started - Upgrade packaging tools. - env/bin/pip install --upgrade pip setuptools wheel + env/bin/pip install --upgrade pip setuptools - Install the project in editable mode with its testing requirements. diff --git a/docs/tutorials/wiki2/src/models/development.ini b/docs/tutorials/wiki2/src/models/development.ini index be80882a5..73ef3d863 100644 --- a/docs/tutorials/wiki2/src/models/development.ini +++ b/docs/tutorials/wiki2/src/models/development.ini @@ -16,6 +16,8 @@ pyramid.includes = sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite +retry.attempts = 3 + # By default, the toolbar only appears for clients from IP addresses # '127.0.0.1' and '::1'. # debugtoolbar.hosts = 127.0.0.1 ::1 diff --git a/docs/tutorials/wiki2/src/models/production.ini b/docs/tutorials/wiki2/src/models/production.ini index c01ad9a7e..8dea38631 100644 --- a/docs/tutorials/wiki2/src/models/production.ini +++ b/docs/tutorials/wiki2/src/models/production.ini @@ -14,6 +14,8 @@ pyramid.default_locale_name = en sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite +retry.attempts = 3 + ### # wsgi server configuration ### diff --git a/docs/tutorials/wiki2/src/models/setup.py b/docs/tutorials/wiki2/src/models/setup.py index faf76aa27..c688c6866 100644 --- a/docs/tutorials/wiki2/src/models/setup.py +++ b/docs/tutorials/wiki2/src/models/setup.py @@ -10,9 +10,11 @@ with open(os.path.join(here, 'CHANGES.txt')) as f: requires = [ 'bcrypt', - 'pyramid', - 'pyramid_jinja2', + 'plaster_pastedeploy', + 'pyramid >= 1.9a', 'pyramid_debugtoolbar', + 'pyramid_jinja2', + 'pyramid_retry', 'pyramid_tm', 'SQLAlchemy', 'transaction', diff --git a/docs/tutorials/wiki2/src/models/tutorial/__init__.py b/docs/tutorials/wiki2/src/models/tutorial/__init__.py index 7654fc808..4dab44823 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/__init__.py +++ b/docs/tutorials/wiki2/src/models/tutorial/__init__.py @@ -5,8 +5,6 @@ def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ config = Configurator(settings=settings) - settings = config.get_settings() - settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' config.include('pyramid_jinja2') config.include('.models') config.include('.routes') diff --git a/docs/tutorials/wiki2/src/models/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/models/tutorial/models/__init__.py index cd8347ccd..3c9ba8e54 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/models/__init__.py +++ b/docs/tutorials/wiki2/src/models/tutorial/models/__init__.py @@ -63,6 +63,9 @@ def includeme(config): # use pyramid_tm to hook the transaction lifecycle to the request config.include('pyramid_tm') + # use pyramid_retry to retry a request when transient exceptions occur + config.include('pyramid_retry') + session_factory = get_session_factory(get_engine(settings)) config.registry['dbsession_factory'] = session_factory diff --git a/docs/tutorials/wiki2/src/tests/README.txt b/docs/tutorials/wiki2/src/tests/README.txt index 81102a869..7b33da610 100644 --- a/docs/tutorials/wiki2/src/tests/README.txt +++ b/docs/tutorials/wiki2/src/tests/README.txt @@ -14,7 +14,7 @@ Getting Started - Upgrade packaging tools. - env/bin/pip install --upgrade pip setuptools wheel + env/bin/pip install --upgrade pip setuptools - Install the project in editable mode with its testing requirements. diff --git a/docs/tutorials/wiki2/src/tests/development.ini b/docs/tutorials/wiki2/src/tests/development.ini index 0786c1f66..00d0dd2bf 100644 --- a/docs/tutorials/wiki2/src/tests/development.ini +++ b/docs/tutorials/wiki2/src/tests/development.ini @@ -16,6 +16,8 @@ pyramid.includes = sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite +retry.attempts = 3 + auth.secret = seekrit # By default, the toolbar only appears for clients from IP addresses diff --git a/docs/tutorials/wiki2/src/tests/production.ini b/docs/tutorials/wiki2/src/tests/production.ini index 05d60feec..e55e9fecc 100644 --- a/docs/tutorials/wiki2/src/tests/production.ini +++ b/docs/tutorials/wiki2/src/tests/production.ini @@ -14,6 +14,8 @@ pyramid.default_locale_name = en sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite +retry.attempts = 3 + auth.secret = real-seekrit ### diff --git a/docs/tutorials/wiki2/src/tests/setup.py b/docs/tutorials/wiki2/src/tests/setup.py index cc1aa421c..abc24876d 100644 --- a/docs/tutorials/wiki2/src/tests/setup.py +++ b/docs/tutorials/wiki2/src/tests/setup.py @@ -11,9 +11,11 @@ with open(os.path.join(here, 'CHANGES.txt')) as f: requires = [ 'bcrypt', 'docutils', - 'pyramid', - 'pyramid_jinja2', + 'plaster_pastedeploy', + 'pyramid >= 1.9a', 'pyramid_debugtoolbar', + 'pyramid_jinja2', + 'pyramid_retry', 'pyramid_tm', 'SQLAlchemy', 'transaction', diff --git a/docs/tutorials/wiki2/src/tests/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/tests/tutorial/models/__init__.py index cd8347ccd..3c9ba8e54 100644 --- a/docs/tutorials/wiki2/src/tests/tutorial/models/__init__.py +++ b/docs/tutorials/wiki2/src/tests/tutorial/models/__init__.py @@ -63,6 +63,9 @@ def includeme(config): # use pyramid_tm to hook the transaction lifecycle to the request config.include('pyramid_tm') + # use pyramid_retry to retry a request when transient exceptions occur + config.include('pyramid_retry') + session_factory = get_session_factory(get_engine(settings)) config.registry['dbsession_factory'] = session_factory diff --git a/docs/tutorials/wiki2/src/views/README.txt b/docs/tutorials/wiki2/src/views/README.txt index 81102a869..7b33da610 100644 --- a/docs/tutorials/wiki2/src/views/README.txt +++ b/docs/tutorials/wiki2/src/views/README.txt @@ -14,7 +14,7 @@ Getting Started - Upgrade packaging tools. - env/bin/pip install --upgrade pip setuptools wheel + env/bin/pip install --upgrade pip setuptools - Install the project in editable mode with its testing requirements. diff --git a/docs/tutorials/wiki2/src/views/development.ini b/docs/tutorials/wiki2/src/views/development.ini index be80882a5..73ef3d863 100644 --- a/docs/tutorials/wiki2/src/views/development.ini +++ b/docs/tutorials/wiki2/src/views/development.ini @@ -16,6 +16,8 @@ pyramid.includes = sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite +retry.attempts = 3 + # By default, the toolbar only appears for clients from IP addresses # '127.0.0.1' and '::1'. # debugtoolbar.hosts = 127.0.0.1 ::1 diff --git a/docs/tutorials/wiki2/src/views/production.ini b/docs/tutorials/wiki2/src/views/production.ini index c01ad9a7e..8dea38631 100644 --- a/docs/tutorials/wiki2/src/views/production.ini +++ b/docs/tutorials/wiki2/src/views/production.ini @@ -14,6 +14,8 @@ pyramid.default_locale_name = en sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite +retry.attempts = 3 + ### # wsgi server configuration ### diff --git a/docs/tutorials/wiki2/src/views/setup.py b/docs/tutorials/wiki2/src/views/setup.py index cc1aa421c..abc24876d 100644 --- a/docs/tutorials/wiki2/src/views/setup.py +++ b/docs/tutorials/wiki2/src/views/setup.py @@ -11,9 +11,11 @@ with open(os.path.join(here, 'CHANGES.txt')) as f: requires = [ 'bcrypt', 'docutils', - 'pyramid', - 'pyramid_jinja2', + 'plaster_pastedeploy', + 'pyramid >= 1.9a', 'pyramid_debugtoolbar', + 'pyramid_jinja2', + 'pyramid_retry', 'pyramid_tm', 'SQLAlchemy', 'transaction', diff --git a/docs/tutorials/wiki2/src/views/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/views/tutorial/models/__init__.py index cd8347ccd..3c9ba8e54 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/models/__init__.py +++ b/docs/tutorials/wiki2/src/views/tutorial/models/__init__.py @@ -63,6 +63,9 @@ def includeme(config): # use pyramid_tm to hook the transaction lifecycle to the request config.include('pyramid_tm') + # use pyramid_retry to retry a request when transient exceptions occur + config.include('pyramid_retry') + session_factory = get_session_factory(get_engine(settings)) config.registry['dbsession_factory'] = session_factory -- cgit v1.2.3 From bb079f4094da8e2dce39e4020f6126b9df533728 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 10 Jun 2017 23:20:52 -0700 Subject: update src files and synch emphasize-lines for myproject --- docs/narr/myproject/setup.py | 1 + docs/narr/testing.rst | 12 +++++------- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/docs/narr/myproject/setup.py b/docs/narr/myproject/setup.py index 00e377349..153a659ba 100644 --- a/docs/narr/myproject/setup.py +++ b/docs/narr/myproject/setup.py @@ -9,6 +9,7 @@ with open(os.path.join(here, 'CHANGES.txt')) as f: CHANGES = f.read() requires = [ + 'plaster_pastedeploy', 'pyramid', 'pyramid_jinja2', 'pyramid_debugtoolbar', diff --git a/docs/narr/testing.rst b/docs/narr/testing.rst index 406383bbd..f124ac562 100644 --- a/docs/narr/testing.rst +++ b/docs/narr/testing.rst @@ -376,18 +376,16 @@ following the ``requires`` block in the file ``myproject/setup.py``. .. literalinclude:: myproject/setup.py :language: python - :linenos: - :lines: 11-22 - :lineno-start: 11 - :emphasize-lines: 8- + :lines: 11-23 + :lineno-match: + :emphasize-lines: 9- Remember to change the dependency. .. literalinclude:: myproject/setup.py :language: python - :linenos: - :lines: 40-44 - :lineno-start: 40 + :lines: 42-46 + :lineno-match: :emphasize-lines: 2-4 As always, whenever you change your dependencies, make sure to run the correct -- cgit v1.2.3 From 65a6d3a79f8fdcdd32a70888c669a8548366f7cb Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 10 Jun 2017 23:45:31 -0700 Subject: update src files and synch emphasize-lines for quick_tour --- docs/quick_tour.rst | 20 ++++++-------------- docs/quick_tour/logging/setup.py | 1 + docs/quick_tour/package/setup.py | 1 + docs/quick_tour/sessions/setup.py | 1 + docs/quick_tour/sqla_demo/development.ini | 2 ++ docs/quick_tour/sqla_demo/production.ini | 2 ++ docs/quick_tour/sqla_demo/setup.py | 6 ++++-- .../sqla_demo/sqla_demo/models/__init__.py | 4 ++++ 8 files changed, 21 insertions(+), 16 deletions(-) diff --git a/docs/quick_tour.rst b/docs/quick_tour.rst index f3a0a27b8..5679b0dc8 100644 --- a/docs/quick_tour.rst +++ b/docs/quick_tour.rst @@ -647,8 +647,8 @@ add-on ``pyramid_debugtoolbar`` in its ``setup.py``: .. literalinclude:: quick_tour/package/setup.py :language: python :lineno-match: - :lines: 11-16 - :emphasize-lines: 4 + :lines: 11-17 + :emphasize-lines: 5 It was installed when you previously ran: @@ -657,14 +657,7 @@ It was installed when you previously ran: $ $VENV/bin/pip install -e ".[testing]" The ``pyramid_debugtoolbar`` package is a Pyramid add-on, which means we need -to include its configuration into our web application. The cookiecutter already took care of this for us in its ``__init__.py``: - -.. literalinclude:: quick_tour/package/hello_world/__init__.py - :language: python - :lineno-match: - :lines: 8 - -And it uses the ``pyramid.includes`` facility in our ``development.ini``: +to include its configuration into our web application. The cookiecutter already took care of this for us in its ``development.ini`` using the ``pyramid.includes`` facility: .. literalinclude:: quick_tour/package/development.ini :language: ini @@ -692,18 +685,17 @@ before its release. Our ``pyramid-cookiecutter-starter`` cookiecutter generated a ``tests.py`` module with one unit test and one functional test in it. It also configured ``setup.py`` with test requirements: ``py.test`` as the test runner, ``WebTest`` for running view tests, and the -``pytest-cov`` tool which yells at us for code that isn't tested. The -highlighted lines show this: +``pytest-cov`` tool which yells at us for code that isn't tested: .. literalinclude:: quick_tour/package/setup.py :language: python :lineno-match: - :lines: 18-22 + :lines: 19-23 .. literalinclude:: quick_tour/package/setup.py :language: python :lineno-match: - :lines: 42-44 + :lines: 43-45 We already installed the test requirements when we ran the command ``$VENV/bin/pip install -e ".[testing]"``. We can now run all our tests: diff --git a/docs/quick_tour/logging/setup.py b/docs/quick_tour/logging/setup.py index e32aecacd..44d90b990 100644 --- a/docs/quick_tour/logging/setup.py +++ b/docs/quick_tour/logging/setup.py @@ -9,6 +9,7 @@ with open(os.path.join(here, 'CHANGES.txt')) as f: CHANGES = f.read() requires = [ + 'plaster_pastedeploy', 'pyramid', 'pyramid_jinja2', 'pyramid_debugtoolbar', diff --git a/docs/quick_tour/package/setup.py b/docs/quick_tour/package/setup.py index e32aecacd..44d90b990 100644 --- a/docs/quick_tour/package/setup.py +++ b/docs/quick_tour/package/setup.py @@ -9,6 +9,7 @@ with open(os.path.join(here, 'CHANGES.txt')) as f: CHANGES = f.read() requires = [ + 'plaster_pastedeploy', 'pyramid', 'pyramid_jinja2', 'pyramid_debugtoolbar', diff --git a/docs/quick_tour/sessions/setup.py b/docs/quick_tour/sessions/setup.py index e32aecacd..44d90b990 100644 --- a/docs/quick_tour/sessions/setup.py +++ b/docs/quick_tour/sessions/setup.py @@ -9,6 +9,7 @@ with open(os.path.join(here, 'CHANGES.txt')) as f: CHANGES = f.read() requires = [ + 'plaster_pastedeploy', 'pyramid', 'pyramid_jinja2', 'pyramid_debugtoolbar', diff --git a/docs/quick_tour/sqla_demo/development.ini b/docs/quick_tour/sqla_demo/development.ini index 8d45a0975..a986c0063 100644 --- a/docs/quick_tour/sqla_demo/development.ini +++ b/docs/quick_tour/sqla_demo/development.ini @@ -16,6 +16,8 @@ pyramid.includes = sqlalchemy.url = sqlite:///%(here)s/sqla_demo.sqlite +retry.attempts = 3 + # By default, the toolbar only appears for clients from IP addresses # '127.0.0.1' and '::1'. # debugtoolbar.hosts = 127.0.0.1 ::1 diff --git a/docs/quick_tour/sqla_demo/production.ini b/docs/quick_tour/sqla_demo/production.ini index a85c354d3..9abb54231 100644 --- a/docs/quick_tour/sqla_demo/production.ini +++ b/docs/quick_tour/sqla_demo/production.ini @@ -14,6 +14,8 @@ pyramid.default_locale_name = en sqlalchemy.url = sqlite:///%(here)s/sqla_demo.sqlite +retry.attempts = 3 + ### # wsgi server configuration ### diff --git a/docs/quick_tour/sqla_demo/setup.py b/docs/quick_tour/sqla_demo/setup.py index 75c1403fb..855a15d58 100644 --- a/docs/quick_tour/sqla_demo/setup.py +++ b/docs/quick_tour/sqla_demo/setup.py @@ -9,9 +9,11 @@ with open(os.path.join(here, 'CHANGES.txt')) as f: CHANGES = f.read() requires = [ - 'pyramid', - 'pyramid_jinja2', + 'plaster_pastedeploy', + 'pyramid >= 1.9a', 'pyramid_debugtoolbar', + 'pyramid_jinja2', + 'pyramid_retry', 'pyramid_tm', 'SQLAlchemy', 'transaction', diff --git a/docs/quick_tour/sqla_demo/sqla_demo/models/__init__.py b/docs/quick_tour/sqla_demo/sqla_demo/models/__init__.py index e6eb98fbd..31aab9d26 100644 --- a/docs/quick_tour/sqla_demo/sqla_demo/models/__init__.py +++ b/docs/quick_tour/sqla_demo/sqla_demo/models/__init__.py @@ -58,9 +58,13 @@ def includeme(config): """ settings = config.get_settings() settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' + # use pyramid_tm to hook the transaction lifecycle to the request config.include('pyramid_tm') + # use pyramid_retry to retry a request when transient exceptions occur + config.include('pyramid_retry') + session_factory = get_session_factory(get_engine(settings)) config.registry['dbsession_factory'] = session_factory -- cgit v1.2.3 From fa13770dba3947ce78c2a67041cbd02272992104 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 11 Jun 2017 00:06:53 -0700 Subject: fix out of range error --- docs/tutorials/wiki/authorization.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst index 211d69f3a..c9ba9feb3 100644 --- a/docs/tutorials/wiki/authorization.rst +++ b/docs/tutorials/wiki/authorization.rst @@ -157,7 +157,7 @@ Now add those policies to the configuration: .. literalinclude:: src/authorization/tutorial/__init__.py :lines: 18-25 :lineno-match: - :emphasize-lines: 1-3,7-9 + :emphasize-lines: 1-3,7-8 :language: python Only the highlighted lines need to be added. -- cgit v1.2.3 From cdd0a86b46ecf0a16403ccf780c87f2c938b3e0c Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sun, 11 Jun 2017 22:53:10 -0500 Subject: update whatsnew-1.9 --- docs/whatsnew-1.9.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/whatsnew-1.9.rst b/docs/whatsnew-1.9.rst index 0ba29625c..eca159d4c 100644 --- a/docs/whatsnew-1.9.rst +++ b/docs/whatsnew-1.9.rst @@ -35,6 +35,10 @@ Minor Feature Additions - The threadlocals are now available inside any function invoked via :meth:`pyramid.config.Configurator.include`. This means the only config-time code that cannot rely on threadlocals is code executed from non-actions inside the main. This can be alleviated by invoking :meth:`pyramid.config.Configurator.begin` and :meth:`pyramid.config.Configurator.end` appropriately or using the new context manager feature of the configurator. See https://github.com/Pylons/pyramid/pull/2989 +- The threadlocals are now available inside exception views invoked via :meth:`pyramid.request.Request.invoke_exception_view` even when the ``request`` argument is overridden. See https://github.com/Pylons/pyramid/pull/3060 + +- When unsupported predicates are supplied to :meth:`pyramid.config.Configurator.add_view`, :meth:`pyramid.config.Configurator.add_route` and :meth:`pyramid.config.Configurator.add_subscriber` a much more helpful error message is output with a guess as to which predicate was intended. See https://github.com/Pylons/pyramid/pull/3054 + Deprecations ------------ -- cgit v1.2.3 From 72ca1352493ef5b9113090598608da0c0d49389a Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 12 Jun 2017 01:07:57 -0500 Subject: apply request extensions within invoke_subrequest itself --- pyramid/router.py | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/pyramid/router.py b/pyramid/router.py index 7f3f9fbea..a02ff1715 100644 --- a/pyramid/router.py +++ b/pyramid/router.py @@ -192,13 +192,21 @@ class Router(object): """ request.registry = self.registry request.invoke_subrequest = self.invoke_subrequest - return self.invoke_request( - request, - _use_tweens=use_tweens, - _apply_extensions=True, - ) + extensions = self.request_extensions + if extensions is not None: + apply_request_extensions(request, extensions=extensions) + return self.invoke_request(request, _use_tweens=use_tweens) def make_request(self, environ): + """ + Configure a request object for use by the router. + + The request is created using the configured + :class:`pyramid.interfaces.IRequestFactory` and will have any + configured request methods / properties added that were set by + :meth:`pyramid.config.Configurator.add_request_method`. + + """ request = self.request_factory(environ) request.registry = self.registry request.invoke_subrequest = self.invoke_subrequest @@ -207,8 +215,12 @@ class Router(object): apply_request_extensions(request, extensions=extensions) return request - def invoke_request(self, request, - _use_tweens=True, _apply_extensions=False): + def invoke_request(self, request, _use_tweens=True): + """ + Execute a request through the request processing pipeline and + return the generated response. + + """ registry = self.registry has_listeners = self.registry.has_listeners notify = self.registry.notify @@ -224,9 +236,6 @@ class Router(object): try: try: - extensions = self.request_extensions - if _apply_extensions and extensions is not None: - apply_request_extensions(request, extensions=extensions) response = handle_request(request) if request.response_callbacks: -- cgit v1.2.3 From 2e015c97443d381832554161d090b7608dba1e16 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 12 Jun 2017 01:11:02 -0500 Subject: typo --- pyramid/interfaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index ab83813c8..4a069ad65 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -713,7 +713,7 @@ class IExecutionPolicy(Interface): The return value should be a :class:`pyramid.interfaces.IResponse` object or an exception that will be handled by WSGI middleware. - The default execution policy simple creates a request and sends it + The default execution policy simply creates a request and sends it through the pipeline: .. code-block:: python -- cgit v1.2.3 From 21300198ee62eb00b757a77f2792329ff2d882a0 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 14 Jun 2017 23:21:04 -0500 Subject: fix p.security.ACLPermitsResult to subclass p.security.PermitsResult The ``IAuthorizationPolicy`` is expected to return an instance of ``PermitsResult`` and the ``ACLPermitsResult`` now subclasses this to form a consistent class hierarchy. Similarly the ``ACLDenied`` subclasses ``Denied`` and ``ACLAllowed`` subclasses ``Allowed`` for consistency. --- docs/api/security.rst | 22 ++++++--- pyramid/interfaces.py | 6 ++- pyramid/security.py | 109 +++++++++++++++++++++++++---------------- pyramid/tests/test_security.py | 4 ++ 4 files changed, 90 insertions(+), 51 deletions(-) diff --git a/docs/api/security.rst b/docs/api/security.rst index 88086dbbf..116459226 100644 --- a/docs/api/security.rst +++ b/docs/api/security.rst @@ -80,15 +80,23 @@ Return Values 'george', 'read')`` that means deny access. A sequence of ACEs makes up an ACL. It is a string, and its actual value is "Deny". +.. autoclass:: Denied + :members: msg + + .. automethod:: __new__ + +.. autoclass:: Allowed + :members: msg + + .. automethod:: __new__ + .. autoclass:: ACLDenied - :members: + :members: msg -.. autoclass:: ACLAllowed - :members: + .. automethod:: __new__ -.. autoclass:: Denied - :members: +.. autoclass:: ACLAllowed + :members: msg -.. autoclass:: Allowed - :members: + .. automethod:: __new__ diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index 4a069ad65..c6fbe3af8 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -503,8 +503,10 @@ class IAuthenticationPolicy(Interface): class IAuthorizationPolicy(Interface): """ An object representing a Pyramid authorization policy. """ def permits(context, principals, permission): - """ Return ``True`` if any of the ``principals`` is allowed the - ``permission`` in the current ``context``, else return ``False`` + """ Return an instance of :class:`pyramid.security.Allowed` if any + of the ``principals`` is allowed the ``permission`` in the current + ``context``, else return an instance of + :class:`pyramid.security.Denied`. """ def principals_allowed_by_permission(context, permission): diff --git a/pyramid/security.py b/pyramid/security.py index 035f09f77..d12314684 100644 --- a/pyramid/security.py +++ b/pyramid/security.py @@ -245,6 +245,14 @@ def view_execution_permitted(context, request, name=''): class PermitsResult(int): def __new__(cls, s, *args): + """ + Create a new instance. + + :param fmt: A format string explaining the reason for denial. + :param args: Arguments are stored and used with the format string + to generate the ``msg``. + + """ inst = int.__new__(cls, cls.boolval) inst.s = s inst.args = args @@ -252,6 +260,7 @@ class PermitsResult(int): @property def msg(self): + """ A string indicating why the result was generated.""" return self.s % self.args def __str__(self): @@ -263,24 +272,52 @@ class PermitsResult(int): self.msg) class Denied(PermitsResult): - """ An instance of ``Denied`` is returned when a security-related + """ + An instance of ``Denied`` is returned when a security-related API or other :app:`Pyramid` code denies an action unrelated to an ACL check. It evaluates equal to all boolean false types. It has an attribute named ``msg`` describing the circumstances for - the deny.""" + the deny. + + """ boolval = 0 class Allowed(PermitsResult): - """ An instance of ``Allowed`` is returned when a security-related + """ + An instance of ``Allowed`` is returned when a security-related API or other :app:`Pyramid` code allows an action unrelated to an ACL check. It evaluates equal to all boolean true types. It has an attribute named ``msg`` describing the circumstances for - the allow.""" + the allow. + + """ boolval = 1 -class ACLPermitsResult(int): +class ACLPermitsResult(PermitsResult): def __new__(cls, ace, acl, permission, principals, context): - inst = int.__new__(cls, cls.boolval) + """ + Create a new instance. + + :param ace: The :term:`ACE` that matched, triggering the result. + :param acl: The :term:`ACL` containing ``ace``. + :param permission: The required :term:`permission`. + :param principals: The list of :term:`principals ` provided. + :param context: The :term:`context` providing the :term:`lineage` + searched. + + """ + fmt = ('%s permission %r via ACE %r in ACL %r on context %r for ' + 'principals %r') + inst = PermitsResult.__new__( + cls, + fmt, + cls.__name__, + permission, + ace, + acl, + context, + principals, + ) inst.permission = permission inst.ace = ace inst.acl = acl @@ -288,44 +325,31 @@ class ACLPermitsResult(int): inst.context = context return inst - @property - def msg(self): - s = ('%s permission %r via ACE %r in ACL %r on context %r for ' - 'principals %r') - return s % (self.__class__.__name__, - self.permission, - self.ace, - self.acl, - self.context, - self.principals) - - def __str__(self): - return self.msg +class ACLDenied(ACLPermitsResult, Denied): + """ + An instance of ``ACLDenied`` is a specialization of + :class:`pyramid.security.Denied` that represents that a security check + made explicitly against ACL was denied. It evaluates equal to all + boolean false types. It also has the following attributes: ``acl``, + ``ace``, ``permission``, ``principals``, and ``context``. These + attributes indicate the security values involved in the request. Its + ``__str__`` method prints a summary of these attributes for debugging + purposes. The same summary is available as the ``msg`` attribute. - def __repr__(self): - return '<%s instance at %s with msg %r>' % (self.__class__.__name__, - id(self), - self.msg) + """ -class ACLDenied(ACLPermitsResult): - """ An instance of ``ACLDenied`` represents that a security check made - explicitly against ACL was denied. It evaluates equal to all boolean - false types. It also has the following attributes: ``acl``, ``ace``, - ``permission``, ``principals``, and ``context``. These attributes - indicate the security values involved in the request. Its __str__ method - prints a summary of these attributes for debugging purposes. The same - summary is available as the ``msg`` attribute.""" - boolval = 0 +class ACLAllowed(ACLPermitsResult, Allowed): + """ + An instance of ``ACLAllowed`` is a specialization of + :class:`pyramid.security.Allowed` that represents that a security check + made explicitly against ACL was allowed. It evaluates equal to all + boolean true types. It also has the following attributes: ``acl``, + ``ace``, ``permission``, ``principals``, and ``context``. These + attributes indicate the security values involved in the request. Its + ``__str__`` method prints a summary of these attributes for debugging + purposes. The same summary is available as the ``msg`` attribute. -class ACLAllowed(ACLPermitsResult): - """ An instance of ``ACLAllowed`` represents that a security check made - explicitly against ACL was allowed. It evaluates equal to all boolean - true types. It also has the following attributes: ``acl``, ``ace``, - ``permission``, ``principals``, and ``context``. These attributes - indicate the security values involved in the request. Its __str__ method - prints a summary of these attributes for debugging purposes. The same - summary is available as the ``msg`` attribute.""" - boolval = 1 + """ class AuthenticationAPIMixin(object): @@ -395,7 +419,8 @@ class AuthorizationAPIMixin(object): :type permission: unicode, str :param context: A resource object or ``None`` :type context: object - :returns: `pyramid.security.PermitsResult` + :returns: Either :class:`pyramid.security.Allowed` or + :class:`pyramid.security.Denied`. .. versionadded:: 1.5 diff --git a/pyramid/tests/test_security.py b/pyramid/tests/test_security.py index 5561a05d7..1da73ff73 100644 --- a/pyramid/tests/test_security.py +++ b/pyramid/tests/test_security.py @@ -92,9 +92,11 @@ class TestACLAllowed(unittest.TestCase): return klass(*arg, **kw) def test_it(self): + from pyramid.security import Allowed msg = ("ACLAllowed permission 'permission' via ACE 'ace' in ACL 'acl' " "on context 'ctx' for principals 'principals'") allowed = self._makeOne('ace', 'acl', 'permission', 'principals', 'ctx') + self.assertIsInstance(allowed, Allowed) self.assertTrue(msg in allowed.msg) self.assertEqual(allowed, True) self.assertTrue(allowed) @@ -112,9 +114,11 @@ class TestACLDenied(unittest.TestCase): return klass(*arg, **kw) def test_it(self): + from pyramid.security import Denied msg = ("ACLDenied permission 'permission' via ACE 'ace' in ACL 'acl' " "on context 'ctx' for principals 'principals'") denied = self._makeOne('ace', 'acl', 'permission', 'principals', 'ctx') + self.assertIsInstance(denied, Denied) self.assertTrue(msg in denied.msg) self.assertEqual(denied, False) self.assertFalse(denied) -- cgit v1.2.3 From b54a702599e00827ae0389808d07f941cdfb04c5 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 14 Jun 2017 23:56:42 -0500 Subject: add changelog for #3084 --- CHANGES.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 1402045d4..547c00bbc 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -21,6 +21,12 @@ unreleased a valid iterator in its ``__iter__`` implementation. See https://github.com/Pylons/pyramid/pull/3074 +- Normalize the permission results to a proper class hierarchy. + ``pyramid.security.ACLAllowed`` is now a subclass of + ``pyramid.security.Allowed`` and ``pyramid.securit.ACLDenied`` is now a + subclass of ``pyramid.security.Denied``. + See https://github.com/Pylons/pyramid/pull/3084 + 1.9a2 (2017-05-09) ================== -- cgit v1.2.3 From 975b025c7952d148392cc17b48388a4ee5dcbd45 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Thu, 15 Jun 2017 00:39:57 -0500 Subject: update whatsnew-1.9 --- CHANGES.txt | 2 +- docs/whatsnew-1.9.rst | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 547c00bbc..fdd9dd884 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -23,7 +23,7 @@ unreleased - Normalize the permission results to a proper class hierarchy. ``pyramid.security.ACLAllowed`` is now a subclass of - ``pyramid.security.Allowed`` and ``pyramid.securit.ACLDenied`` is now a + ``pyramid.security.Allowed`` and ``pyramid.security.ACLDenied`` is now a subclass of ``pyramid.security.Denied``. See https://github.com/Pylons/pyramid/pull/3084 diff --git a/docs/whatsnew-1.9.rst b/docs/whatsnew-1.9.rst index eca159d4c..0c3385a66 100644 --- a/docs/whatsnew-1.9.rst +++ b/docs/whatsnew-1.9.rst @@ -39,6 +39,8 @@ Minor Feature Additions - When unsupported predicates are supplied to :meth:`pyramid.config.Configurator.add_view`, :meth:`pyramid.config.Configurator.add_route` and :meth:`pyramid.config.Configurator.add_subscriber` a much more helpful error message is output with a guess as to which predicate was intended. See https://github.com/Pylons/pyramid/pull/3054 +- Normalize the permission results to a proper class hierarchy. :class:`pyramid.security.ACLAllowed` is now a subclass of :class:`pyramid.security.Allowed` and :class:`pyramid.security.ACLDenied` is now a subclass of :class:`pyramid.security.Denied`. See https://github.com/Pylons/pyramid/pull/3084 + Deprecations ------------ -- cgit v1.2.3 From 6f2f04a4d2d1df1b5fd7d5327c57e96e059279cd Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Thu, 15 Jun 2017 01:12:10 -0500 Subject: add a reraise argument to request.invoke_exception_view --- pyramid/tests/test_view.py | 27 +++++++++++++++++++++++++++ pyramid/view.py | 26 +++++++++++++++++--------- 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/pyramid/tests/test_view.py b/pyramid/tests/test_view.py index a9ce2234d..e03487a70 100644 --- a/pyramid/tests/test_view.py +++ b/pyramid/tests/test_view.py @@ -886,6 +886,18 @@ class TestViewMethodsMixin(unittest.TestCase): else: # pragma: no cover self.fail() + def test_it_reraises_if_not_found(self): + request = self._makeOne() + dummy_exc = RuntimeError() + try: + raise dummy_exc + except RuntimeError: + self.assertRaises( + RuntimeError, + lambda: request.invoke_exception_view(reraise=True)) + else: # pragma: no cover + self.fail() + def test_it_raises_predicate_mismatch(self): from pyramid.exceptions import PredicateMismatch def exc_view(exc, request): pass @@ -900,6 +912,21 @@ class TestViewMethodsMixin(unittest.TestCase): else: # pragma: no cover self.fail() + def test_it_reraises_after_predicate_mismatch(self): + def exc_view(exc, request): pass + self.config.add_view(exc_view, context=Exception, request_method='POST') + request = self._makeOne() + request.method = 'GET' + dummy_exc = RuntimeError() + try: + raise dummy_exc + except RuntimeError: + self.assertRaises( + RuntimeError, + lambda: request.invoke_exception_view(reraise=True)) + else: # pragma: no cover + self.fail() + class ExceptionResponse(Exception): status = '404 Not Found' app_iter = ['Not Found'] diff --git a/pyramid/view.py b/pyramid/view.py index 14d11825e..dc4aae3fa 100644 --- a/pyramid/view.py +++ b/pyramid/view.py @@ -16,6 +16,7 @@ from pyramid.interfaces import ( ) from pyramid.compat import decode_path_info +from pyramid.compat import reraise as reraise_ from pyramid.exceptions import ( ConfigurationError, @@ -630,8 +631,9 @@ class ViewMethodsMixin(object): self, exc_info=None, request=None, - secure=True - ): + secure=True, + reraise=False, + ): """ Executes an exception view related to the request it's called upon. The arguments it takes are these: @@ -654,14 +656,12 @@ class ViewMethodsMixin(object): does not have the appropriate permission, this should be ``True``. Default: ``True``. - If called with no arguments, it uses the global exception information - returned by ``sys.exc_info()`` as ``exc_info``, the request - object that this method is attached to as the ``request``, and - ``True`` for ``secure``. + ``reraise`` - This method returns a :term:`response` object or raises - :class:`pyramid.httpexceptions.HTTPNotFound` if a matching view cannot - be found. + A boolean indicating whether the original error should be reraised + if a :term:`response` object could not be created. If ``False`` + then an :class:`pyramid.httpexceptions.HTTPNotFound`` exception + will be raised. Default: ``False``. If a response is generated then ``request.exception`` and ``request.exc_info`` will be left at the values used to render the @@ -675,6 +675,8 @@ class ViewMethodsMixin(object): reflect the exception used to render the response where previously they were reset to the values prior to invoking the method. + Also added the ``reraise`` argument. + """ if request is None: request = self @@ -716,10 +718,16 @@ class ViewMethodsMixin(object): secure=secure, request_iface=request_iface.combined, ) + except: + if reraise: + reraise_(*exc_info) + raise finally: manager.pop() if response is None: + if reraise: + reraise_(*exc_info) raise HTTPNotFound # successful response, overwrite exception/exc_info -- cgit v1.2.3 From 804232f6ea90a5e537ccd46d87b66a976f736c0c Mon Sep 17 00:00:00 2001 From: drnextgis Date: Thu, 15 Jun 2017 23:08:54 +0700 Subject: quote_via urlencode argument --- CONTRIBUTORS.txt | 2 ++ pyramid/encode.py | 26 +++++++++++++------------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 445536e9e..32c0833b4 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -304,3 +304,5 @@ Contributors - Fang-Pen Lin, 2017/05/22 - Volker Diels-Grabsch, 2017/06/09 + +- Denis Rykov, 2017/06/15 diff --git a/pyramid/encode.py b/pyramid/encode.py index 0be0107b3..62f96938b 100644 --- a/pyramid/encode.py +++ b/pyramid/encode.py @@ -14,7 +14,16 @@ def url_quote(val, safe=''): # bw compat api val = str(val).encode('utf-8') return _url_quote(val, safe=safe) -def urlencode(query, doseq=True): +# bw compat api (dnr) +def quote_plus(val, safe=''): + cls = val.__class__ + if cls is text_type: + val = val.encode('utf-8') + elif cls is not binary_type: + val = str(val).encode('utf-8') + return _quote_plus(val, safe=safe) + +def urlencode(query, doseq=True, quote_via=quote_plus): """ An alternate implementation of Python's stdlib `urllib.urlencode function `_ which @@ -52,28 +61,19 @@ def urlencode(query, doseq=True): prefix = '' for (k, v) in query: - k = quote_plus(k) + k = quote_via(k) if is_nonstr_iter(v): for x in v: - x = quote_plus(x) + x = quote_via(x) result += '%s%s=%s' % (prefix, k, x) prefix = '&' elif v is None: result += '%s%s=' % (prefix, k) else: - v = quote_plus(v) + v = quote_via(v) result += '%s%s=%s' % (prefix, k, v) prefix = '&' return result - -# bw compat api (dnr) -def quote_plus(val, safe=''): - cls = val.__class__ - if cls is text_type: - val = val.encode('utf-8') - elif cls is not binary_type: - val = str(val).encode('utf-8') - return _quote_plus(val, safe=safe) -- cgit v1.2.3 From 4216e1f9204ea3d0d495d6edc84d8d656fc09e1d Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Thu, 15 Jun 2017 10:12:52 -0700 Subject: Use HTTPS for pylonsproject.org --- README.rst | 2 +- docs/index.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 0429c36b5..70dcc5dda 100644 --- a/README.rst +++ b/README.rst @@ -34,7 +34,7 @@ and deployment more fun, more predictable, and more productive. server = make_server('0.0.0.0', 8080, app) server.serve_forever() -Pyramid is a project of the `Pylons Project `_. +Pyramid is a project of the `Pylons Project `_. Support and Documentation ------------------------- diff --git a/docs/index.rst b/docs/index.rst index 7d3393548..e3d2835b7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -5,7 +5,7 @@ The Pyramid Web Framework ========================= :app:`Pyramid` is a small, fast, down-to-earth Python web framework. It is -developed as part of the `Pylons Project `_. +developed as part of the `Pylons Project `_. It is licensed under a `BSD-like license `_. Here is one of the simplest :app:`Pyramid` applications you can make: -- cgit v1.2.3 From 2cd6a6dbcdb517788ef3275af63feca703e73658 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Thu, 15 Jun 2017 21:59:18 -0700 Subject: Use HTTPS for pylonsproject.org --- contributing.md | 2 +- docs/narr/introduction.rst | 2 +- docs/narr/myproject/myproject/templates/layout.jinja2 | 2 +- docs/quick_tour/logging/hello_world/templates/layout.jinja2 | 2 +- docs/quick_tour/package/hello_world/templates/layout.jinja2 | 2 +- docs/quick_tour/sessions/hello_world/templates/layout.jinja2 | 2 +- docs/quick_tour/sqla_demo/sqla_demo/templates/layout.jinja2 | 2 +- docs/quick_tutorial/cookiecutters/cc_starter/templates/layout.jinja2 | 2 +- docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt | 2 +- docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt | 2 +- docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt | 2 +- docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt | 2 +- docs/tutorials/wiki2/src/basiclayout/tutorial/templates/layout.jinja2 | 2 +- docs/tutorials/wiki2/src/installation/tutorial/templates/layout.jinja2 | 2 +- docs/tutorials/wiki2/src/models/tutorial/templates/layout.jinja2 | 2 +- pyramid/scaffolds/alchemy/+package+/templates/layout.jinja2_tmpl | 2 +- pyramid/scaffolds/starter/+package+/templates/layout.jinja2_tmpl | 2 +- pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl | 2 +- .../fixture_scaffold/+package+/templates/mytemplate.pt_tmpl | 2 +- 19 files changed, 19 insertions(+), 19 deletions(-) diff --git a/contributing.md b/contributing.md index 82f60e7b8..c0f842e3e 100644 --- a/contributing.md +++ b/contributing.md @@ -3,7 +3,7 @@ Contributing All projects under the Pylons Projects, including this one, follow the guidelines established at [How to -Contribute](http://www.pylonsproject.org/community/how-to-contribute) and +Contribute](https://pylonsproject.org//community/how-to-contribute) and [Coding Style and Standards](http://docs.pylonsproject.org/en/latest/community/codestyle.html). diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst index 3dd0cc464..45ea712b2 100644 --- a/docs/narr/introduction.rst +++ b/docs/narr/introduction.rst @@ -364,7 +364,7 @@ And much, much more... What Is The Pylons Project? --------------------------- -:app:`Pyramid` is a member of the collection of software published under the Pylons Project. Pylons software is written by a loose-knit community of contributors. The `Pylons Project website `_ includes details about how :app:`Pyramid` relates to the Pylons Project. +:app:`Pyramid` is a member of the collection of software published under the Pylons Project. Pylons software is written by a loose-knit community of contributors. The `Pylons Project website `_ includes details about how :app:`Pyramid` relates to the Pylons Project. .. index:: single: pyramid and other frameworks diff --git a/docs/narr/myproject/myproject/templates/layout.jinja2 b/docs/narr/myproject/myproject/templates/layout.jinja2 index 820758fea..2b3c26628 100644 --- a/docs/narr/myproject/myproject/templates/layout.jinja2 +++ b/docs/narr/myproject/myproject/templates/layout.jinja2 @@ -42,7 +42,7 @@ diff --git a/docs/quick_tour/logging/hello_world/templates/layout.jinja2 b/docs/quick_tour/logging/hello_world/templates/layout.jinja2 index c82cac915..8473e8b5d 100644 --- a/docs/quick_tour/logging/hello_world/templates/layout.jinja2 +++ b/docs/quick_tour/logging/hello_world/templates/layout.jinja2 @@ -42,7 +42,7 @@ diff --git a/docs/quick_tour/package/hello_world/templates/layout.jinja2 b/docs/quick_tour/package/hello_world/templates/layout.jinja2 index c82cac915..8473e8b5d 100644 --- a/docs/quick_tour/package/hello_world/templates/layout.jinja2 +++ b/docs/quick_tour/package/hello_world/templates/layout.jinja2 @@ -42,7 +42,7 @@ diff --git a/docs/quick_tour/sessions/hello_world/templates/layout.jinja2 b/docs/quick_tour/sessions/hello_world/templates/layout.jinja2 index c82cac915..8473e8b5d 100644 --- a/docs/quick_tour/sessions/hello_world/templates/layout.jinja2 +++ b/docs/quick_tour/sessions/hello_world/templates/layout.jinja2 @@ -42,7 +42,7 @@ diff --git a/docs/quick_tour/sqla_demo/sqla_demo/templates/layout.jinja2 b/docs/quick_tour/sqla_demo/sqla_demo/templates/layout.jinja2 index b84b3ec0e..69c9941ac 100644 --- a/docs/quick_tour/sqla_demo/sqla_demo/templates/layout.jinja2 +++ b/docs/quick_tour/sqla_demo/sqla_demo/templates/layout.jinja2 @@ -42,7 +42,7 @@ diff --git a/docs/quick_tutorial/cookiecutters/cc_starter/templates/layout.jinja2 b/docs/quick_tutorial/cookiecutters/cc_starter/templates/layout.jinja2 index 3aed0a123..175a7b30b 100644 --- a/docs/quick_tutorial/cookiecutters/cc_starter/templates/layout.jinja2 +++ b/docs/quick_tutorial/cookiecutters/cc_starter/templates/layout.jinja2 @@ -42,7 +42,7 @@ diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt index 4ffc0eb22..5d3486061 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt @@ -43,7 +43,7 @@ diff --git a/docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt index 4ffc0eb22..5d3486061 100644 --- a/docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt @@ -43,7 +43,7 @@ diff --git a/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt index 4ffc0eb22..5d3486061 100644 --- a/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt @@ -43,7 +43,7 @@ diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt index 6c3809250..c5776be23 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt @@ -45,7 +45,7 @@
  • Docs
  • Github Project
  • IRC Channel
  • -
  • Pylons Project
  • +
  • Pylons Project
  • diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/layout.jinja2 b/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/layout.jinja2 index e29413cf9..d97c73ed2 100644 --- a/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/layout.jinja2 +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/layout.jinja2 @@ -42,7 +42,7 @@ diff --git a/docs/tutorials/wiki2/src/installation/tutorial/templates/layout.jinja2 b/docs/tutorials/wiki2/src/installation/tutorial/templates/layout.jinja2 index e29413cf9..d97c73ed2 100644 --- a/docs/tutorials/wiki2/src/installation/tutorial/templates/layout.jinja2 +++ b/docs/tutorials/wiki2/src/installation/tutorial/templates/layout.jinja2 @@ -42,7 +42,7 @@ diff --git a/docs/tutorials/wiki2/src/models/tutorial/templates/layout.jinja2 b/docs/tutorials/wiki2/src/models/tutorial/templates/layout.jinja2 index e29413cf9..d97c73ed2 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/templates/layout.jinja2 +++ b/docs/tutorials/wiki2/src/models/tutorial/templates/layout.jinja2 @@ -42,7 +42,7 @@ diff --git a/pyramid/scaffolds/alchemy/+package+/templates/layout.jinja2_tmpl b/pyramid/scaffolds/alchemy/+package+/templates/layout.jinja2_tmpl index 6f6946ba2..6906ea204 100644 --- a/pyramid/scaffolds/alchemy/+package+/templates/layout.jinja2_tmpl +++ b/pyramid/scaffolds/alchemy/+package+/templates/layout.jinja2_tmpl @@ -44,7 +44,7 @@
  • Docs
  • Github Project
  • IRC Channel
  • -
  • Pylons Project
  • +
  • Pylons Project
  • diff --git a/pyramid/scaffolds/starter/+package+/templates/layout.jinja2_tmpl b/pyramid/scaffolds/starter/+package+/templates/layout.jinja2_tmpl index f3c27e3f0..a6ec9d00a 100644 --- a/pyramid/scaffolds/starter/+package+/templates/layout.jinja2_tmpl +++ b/pyramid/scaffolds/starter/+package+/templates/layout.jinja2_tmpl @@ -44,7 +44,7 @@
  • Docs
  • Github Project
  • IRC Channel
  • -
  • Pylons Project
  • +
  • Pylons Project
  • diff --git a/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl b/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl index 4b24ee2df..04f5260e3 100644 --- a/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl +++ b/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl @@ -45,7 +45,7 @@
  • Docs
  • Github Project
  • IRC Channel
  • -
  • Pylons Project
  • +
  • Pylons Project
  • diff --git a/pyramid/tests/test_scaffolds/fixture_scaffold/+package+/templates/mytemplate.pt_tmpl b/pyramid/tests/test_scaffolds/fixture_scaffold/+package+/templates/mytemplate.pt_tmpl index 856bc22e7..f4d98ec29 100644 --- a/pyramid/tests/test_scaffolds/fixture_scaffold/+package+/templates/mytemplate.pt_tmpl +++ b/pyramid/tests/test_scaffolds/fixture_scaffold/+package+/templates/mytemplate.pt_tmpl @@ -41,7 +41,7 @@

    Pyramid links

    diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/layout.jinja2 b/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/layout.jinja2 index d97c73ed2..36b1fa374 100644 --- a/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/layout.jinja2 +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/layout.jinja2 @@ -42,7 +42,7 @@ diff --git a/docs/tutorials/wiki2/src/installation/tutorial/templates/layout.jinja2 b/docs/tutorials/wiki2/src/installation/tutorial/templates/layout.jinja2 index d97c73ed2..36b1fa374 100644 --- a/docs/tutorials/wiki2/src/installation/tutorial/templates/layout.jinja2 +++ b/docs/tutorials/wiki2/src/installation/tutorial/templates/layout.jinja2 @@ -42,7 +42,7 @@ diff --git a/docs/tutorials/wiki2/src/models/tutorial/templates/layout.jinja2 b/docs/tutorials/wiki2/src/models/tutorial/templates/layout.jinja2 index d97c73ed2..36b1fa374 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/templates/layout.jinja2 +++ b/docs/tutorials/wiki2/src/models/tutorial/templates/layout.jinja2 @@ -42,7 +42,7 @@ diff --git a/pyramid/scaffolds/alchemy/+package+/templates/layout.jinja2_tmpl b/pyramid/scaffolds/alchemy/+package+/templates/layout.jinja2_tmpl index 6906ea204..485d6efa4 100644 --- a/pyramid/scaffolds/alchemy/+package+/templates/layout.jinja2_tmpl +++ b/pyramid/scaffolds/alchemy/+package+/templates/layout.jinja2_tmpl @@ -44,7 +44,7 @@
  • Docs
  • Github Project
  • IRC Channel
  • -
  • Pylons Project
  • +
  • Pylons Project
  • diff --git a/pyramid/scaffolds/starter/+package+/templates/layout.jinja2_tmpl b/pyramid/scaffolds/starter/+package+/templates/layout.jinja2_tmpl index a6ec9d00a..679ba25ea 100644 --- a/pyramid/scaffolds/starter/+package+/templates/layout.jinja2_tmpl +++ b/pyramid/scaffolds/starter/+package+/templates/layout.jinja2_tmpl @@ -44,7 +44,7 @@
  • Docs
  • Github Project
  • IRC Channel
  • -
  • Pylons Project
  • +
  • Pylons Project
  • -- cgit v1.2.3 From d505174d3217025ea87a7b51d0adff73c5e9e199 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Thu, 15 Jun 2017 22:05:32 -0700 Subject: fix URL --- contributing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contributing.md b/contributing.md index 83a3f5b6e..5e0ac53bf 100644 --- a/contributing.md +++ b/contributing.md @@ -3,7 +3,7 @@ Contributing All projects under the Pylons Projects, including this one, follow the guidelines established at [How to -Contribute](https://pylonsproject.org/community/how-to-contribute) and +Contribute](https://pylonsproject.org/community-how-to-contribute.html) and [Coding Style and Standards](http://docs.pylonsproject.org/en/latest/community/codestyle.html). -- cgit v1.2.3 From c2161266fa7fdca8b6e7405de9a903c24b49ba69 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Thu, 15 Jun 2017 23:20:25 -0700 Subject: update contributing URL --- docs/index.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 6df3023f0..4b739d23f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -95,9 +95,7 @@ the trunk via ``git``, use either command: # Otherwise, HTTPS will work, using your GitHub login: git clone https://github.com/Pylons/pyramid.git -To find out how to become a contributor to :app:`Pyramid`, please see the -`contributor's section of the documentation -`_. +To find out how to become a contributor to :app:`Pyramid`, please see `How to Contribute Source Code and Documentation `_. .. _html_narrative_documentation: -- cgit v1.2.3 From 8226534e173df938c533ebab6db8cd08a60901b9 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 14 Jun 2017 20:58:40 -0500 Subject: add a router.request_context context manager the request context is to be used by execution policies to push/pop threadlocals and access the created request --- pyramid/interfaces.py | 50 +++++++++++++++++++++------ pyramid/router.py | 81 ++++++++++++++++++++++---------------------- pyramid/tests/test_router.py | 69 ++++++++++++++++++++++--------------- pyramid/threadlocal.py | 27 +++++++++++++-- 4 files changed, 148 insertions(+), 79 deletions(-) diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index c6fbe3af8..e9cc007ac 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -679,18 +679,41 @@ class IViewPermission(Interface): """ class IRouter(Interface): - """ WSGI application which routes requests to 'view' code based on - a view registry.""" + """ + WSGI application which routes requests to 'view' code based on + a view registry. + + """ registry = Attribute( """Component architecture registry local to this application.""") - def make_request(environ): + def request_context(environ): """ - Create a new request object. + Create a new request context from a WSGI environ. + + The request context is used to push/pop the threadlocals required + when processing the request. It also contains an initialized + :class:`pyramid.interfaces.IRequest` instance using the registered + :class:`pyramid.interfaces.IRequestFactory`. The context may be + used as a context manager to control the threadlocal lifecycle: + + .. code-block:: python + + with router.request_context(environ) as request: + ... + + Alternatively, the context may be used without the ``with`` statement + by manually invoking its ``begin()`` and ``end()`` methods. + + .. code-block:: python + + ctx = router.request_context(environ) + request = ctx.begin() + try: + ... + finally: + ctx.end() - This method initializes a new :class:`pyramid.interfaces.IRequest` - object using the application's - :class:`pyramid.interfaces.IRequestFactory`. """ def invoke_request(request): @@ -698,6 +721,10 @@ class IRouter(Interface): Invoke the :app:`Pyramid` request pipeline. See :ref:`router_chapter` for information on the request pipeline. + + The output should be a :class:`pyramid.interfaces.IResponse` object + or a raised exception. + """ class IExecutionPolicy(Interface): @@ -716,13 +743,16 @@ class IExecutionPolicy(Interface): object or an exception that will be handled by WSGI middleware. The default execution policy simply creates a request and sends it - through the pipeline: + through the pipeline, attempting to render any exception that escapes: .. code-block:: python def simple_execution_policy(environ, router): - request = router.make_request(environ) - return router.invoke_request(request) + with router.request_context(environ) as request: + try: + return router.invoke_request(request) + except Exception: + return request.invoke_exception_view(reraise=True) """ class ISettings(IDict): diff --git a/pyramid/router.py b/pyramid/router.py index a02ff1715..49b7b601b 100644 --- a/pyramid/router.py +++ b/pyramid/router.py @@ -1,4 +1,3 @@ -import sys from zope.interface import ( implementer, providedBy, @@ -25,12 +24,11 @@ from pyramid.events import ( BeforeTraversal, ) -from pyramid.compat import reraise from pyramid.httpexceptions import HTTPNotFound from pyramid.request import Request from pyramid.view import _call_view from pyramid.request import apply_request_extensions -from pyramid.threadlocal import manager +from pyramid.threadlocal import RequestContext from pyramid.traversal import ( DefaultRootFactory, @@ -43,8 +41,6 @@ class Router(object): debug_notfound = False debug_routematch = False - threadlocal_manager = manager - def __init__(self, registry): q = registry.queryUtility self.logger = q(IDebugLogger) @@ -195,16 +191,35 @@ class Router(object): extensions = self.request_extensions if extensions is not None: apply_request_extensions(request, extensions=extensions) - return self.invoke_request(request, _use_tweens=use_tweens) + with RequestContext(request): + return self.invoke_request(request, _use_tweens=use_tweens) - def make_request(self, environ): + def request_context(self, environ): """ - Configure a request object for use by the router. + Create a new request context from a WSGI environ. + + The request context is used to push/pop the threadlocals required + when processing the request. It also contains an initialized + :class:`pyramid.interfaces.IRequest` instance using the registered + :class:`pyramid.interfaces.IRequestFactory`. The context may be + used as a context manager to control the threadlocal lifecycle: + + .. code-block:: python + + with router.request_context(environ) as request: + ... - The request is created using the configured - :class:`pyramid.interfaces.IRequestFactory` and will have any - configured request methods / properties added that were set by - :meth:`pyramid.config.Configurator.add_request_method`. + Alternatively, the context may be used without the ``with`` statement + by manually invoking its ``begin()`` and ``end()`` methods. + + .. code-block:: python + + ctx = router.request_context(environ) + request = ctx.begin() + try: + ... + finally: + ctx.end() """ request = self.request_factory(environ) @@ -213,7 +228,7 @@ class Router(object): extensions = self.request_extensions if extensions is not None: apply_request_extensions(request, extensions=extensions) - return request + return RequestContext(request) def invoke_request(self, request, _use_tweens=True): """ @@ -222,11 +237,8 @@ class Router(object): """ registry = self.registry - has_listeners = self.registry.has_listeners - notify = self.registry.notify - threadlocals = {'registry': registry, 'request': request} - manager = self.threadlocal_manager - manager.push(threadlocals) + has_listeners = registry.has_listeners + notify = registry.notify if _use_tweens: handle_request = self.handle_request @@ -234,23 +246,18 @@ class Router(object): handle_request = self.orig_handle_request try: + response = handle_request(request) - try: - response = handle_request(request) - - if request.response_callbacks: - request._process_response_callbacks(response) + if request.response_callbacks: + request._process_response_callbacks(response) - has_listeners and notify(NewResponse(request, response)) + has_listeners and notify(NewResponse(request, response)) - return response - - finally: - if request.finished_callbacks: - request._process_finished_callbacks() + return response finally: - manager.pop() + if request.finished_callbacks: + request._process_finished_callbacks() def __call__(self, environ, start_response): """ @@ -264,14 +271,8 @@ class Router(object): return response(environ, start_response) def default_execution_policy(environ, router): - request = router.make_request(environ) - try: - return router.invoke_request(request) - except Exception: - exc_info = sys.exc_info() + with router.request_context(environ) as request: try: - return request.invoke_exception_view(exc_info) - except HTTPNotFound: - reraise(*exc_info) - finally: - del exc_info # avoid local ref cycle + return router.invoke_request(request) + except Exception: + return request.invoke_exception_view(reraise=True) diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py index bd023824c..6097018f0 100644 --- a/pyramid/tests/test_router.py +++ b/pyramid/tests/test_router.py @@ -641,22 +641,6 @@ class TestRouter(unittest.TestCase): result = router(environ, start_response) self.assertEqual(result, exception_response.app_iter) - def test_call_pushes_and_pops_threadlocal_manager(self): - from pyramid.interfaces import IViewClassifier - context = DummyContext() - self._registerTraverserFactory(context) - response = DummyResponse() - response.app_iter = ['Hello world'] - view = DummyView(response) - environ = self._makeEnviron() - self._registerView(view, '', IViewClassifier, None, None) - router = self._makeOne() - start_response = DummyStartResponse() - router.threadlocal_manager = DummyThreadLocalManager() - router(environ, start_response) - self.assertEqual(len(router.threadlocal_manager.pushed), 1) - self.assertEqual(len(router.threadlocal_manager.popped), 1) - def test_call_route_matches_and_has_factory(self): from pyramid.interfaces import IViewClassifier logger = self._registerLogger() @@ -1311,6 +1295,48 @@ class TestRouter(unittest.TestCase): result = router(environ, start_response) self.assertEqual(result, ["Hello, world"]) + def test_request_context_with_statement(self): + from pyramid.threadlocal import get_current_request + from pyramid.interfaces import IExecutionPolicy + from pyramid.request import Request + from pyramid.response import Response + registry = self.config.registry + result = [] + def dummy_policy(environ, router): + with router.request_context(environ): + result.append(get_current_request()) + result.append(get_current_request()) + return Response(status=200, body=b'foo') + registry.registerUtility(dummy_policy, IExecutionPolicy) + router = self._makeOne() + resp = Request.blank('/test_path').get_response(router) + self.assertEqual(resp.status_code, 200) + self.assertEqual(resp.body, b'foo') + self.assertEqual(result[0].path_info, '/test_path') + self.assertEqual(result[1], None) + + def test_request_context_manually(self): + from pyramid.threadlocal import get_current_request + from pyramid.interfaces import IExecutionPolicy + from pyramid.request import Request + from pyramid.response import Response + registry = self.config.registry + result = [] + def dummy_policy(environ, router): + ctx = router.request_context(environ) + ctx.begin() + result.append(get_current_request()) + ctx.end() + result.append(get_current_request()) + return Response(status=200, body=b'foo') + registry.registerUtility(dummy_policy, IExecutionPolicy) + router = self._makeOne() + resp = Request.blank('/test_path').get_response(router) + self.assertEqual(resp.status_code, 200) + self.assertEqual(resp.body, b'foo') + self.assertEqual(result[0].path_info, '/test_path') + self.assertEqual(result[1], None) + class DummyPredicate(object): def __call__(self, info, request): return True @@ -1362,17 +1388,6 @@ class DummyResponse(object): start_response(self.status, self.headerlist) return self.app_iter -class DummyThreadLocalManager: - def __init__(self): - self.pushed = [] - self.popped = [] - - def push(self, val): - self.pushed.append(val) - - def pop(self): - self.popped.append(True) - class DummyAuthenticationPolicy: pass diff --git a/pyramid/threadlocal.py b/pyramid/threadlocal.py index 9429fe953..e8f825715 100644 --- a/pyramid/threadlocal.py +++ b/pyramid/threadlocal.py @@ -36,7 +36,8 @@ def defaults(): manager = ThreadLocalManager(default=defaults) def get_current_request(): - """Return the currently active request or ``None`` if no request + """ + Return the currently active request or ``None`` if no request is currently active. This function should be used *extremely sparingly*, usually only @@ -44,11 +45,13 @@ def get_current_request(): ``get_current_request`` outside a testing context because its usage makes it possible to write code that can be neither easily tested nor scripted. + """ return manager.get()['request'] def get_current_registry(context=None): # context required by getSiteManager API - """Return the currently active :term:`application registry` or the + """ + Return the currently active :term:`application registry` or the global application registry if no request is currently active. This function should be used *extremely sparingly*, usually only @@ -56,5 +59,25 @@ def get_current_registry(context=None): # context required by getSiteManager API ``get_current_registry`` outside a testing context because its usage makes it possible to write code that can be neither easily tested nor scripted. + """ return manager.get()['registry'] + +class RequestContext(object): + def __init__(self, request): + self.request = request + + def begin(self): + request = self.request + registry = request.registry + manager.push({'registry': registry, 'request': request}) + return request + + def end(self): + manager.pop() + + def __enter__(self): + return self.begin() + + def __exit__(self, *args): + self.end() -- cgit v1.2.3 From 6f43b617476127cc333efb885970ca87e9de39fa Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sun, 18 Jun 2017 00:03:48 -0500 Subject: document and test p.encode.urlencode(quote_via=...) --- pyramid/encode.py | 19 ++++++++++++------- pyramid/tests/test_encode.py | 11 +++++++++-- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/pyramid/encode.py b/pyramid/encode.py index 62f96938b..73ff14e62 100644 --- a/pyramid/encode.py +++ b/pyramid/encode.py @@ -25,11 +25,10 @@ def quote_plus(val, safe=''): def urlencode(query, doseq=True, quote_via=quote_plus): """ - An alternate implementation of Python's stdlib `urllib.urlencode - function `_ which - accepts unicode keys and values within the ``query`` - dict/sequence; all Unicode keys and values are first converted to - UTF-8 before being used to compose the query string. + An alternate implementation of Python's stdlib + :func:`urllib.parse.urlencode` function which accepts unicode keys and + values within the ``query`` dict/sequence; all Unicode keys and values are + first converted to UTF-8 before being used to compose the query string. The value of ``query`` must be a sequence of two-tuples representing key/value pairs *or* an object (often a dictionary) @@ -44,12 +43,18 @@ def urlencode(query, doseq=True, quote_via=quote_plus): the ``doseq=True`` mode, no matter what the value of the second argument. - See the Python stdlib documentation for ``urllib.urlencode`` for - more information. + Both the key and value are encoded using the ``quote_via`` function which + by default is using a similar algorithm to :func:`urllib.parse.quote_plus` + which converts spaces into '+' characters and '/' into '%2F'. .. versionchanged:: 1.5 In a key/value pair, if the value is ``None`` then it will be dropped from the resulting output. + + .. versionchanged:: 1.9 + Added the ``quote_via`` argument to allow alternate quoting algorithms + to be used. + """ try: # presumed to be a dictionary diff --git a/pyramid/tests/test_encode.py b/pyramid/tests/test_encode.py index 8fb766d88..d3a9f7095 100644 --- a/pyramid/tests/test_encode.py +++ b/pyramid/tests/test_encode.py @@ -5,9 +5,9 @@ from pyramid.compat import ( ) class UrlEncodeTests(unittest.TestCase): - def _callFUT(self, query, doseq=False): + def _callFUT(self, query, doseq=False, **kw): from pyramid.encode import urlencode - return urlencode(query, doseq) + return urlencode(query, doseq, **kw) def test_ascii_only(self): result = self._callFUT([('a',1), ('b',2)]) @@ -53,6 +53,13 @@ class UrlEncodeTests(unittest.TestCase): result = self._callFUT([('a', '1'), ('b', None), ('c', None)]) self.assertEqual(result, 'a=1&b=&c=') + def test_quote_via(self): + def my_quoter(value): + return 'xxx' + value + result = self._callFUT([('a', '1'), ('b', None), ('c', None)], + quote_via=my_quoter) + self.assertEqual(result, 'xxxa=xxx1&xxxb=&xxxc=') + class URLQuoteTests(unittest.TestCase): def _callFUT(self, val, safe=''): from pyramid.encode import url_quote -- cgit v1.2.3 From 5c437abba0926f6093efbd481e49763de2436665 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sun, 18 Jun 2017 00:18:13 -0500 Subject: add changelog for #3088 --- CHANGES.txt | 4 ++++ docs/whatsnew-1.9.rst | 2 ++ 2 files changed, 6 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index fdd9dd884..939b777ab 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -27,6 +27,10 @@ unreleased subclass of ``pyramid.security.Denied``. See https://github.com/Pylons/pyramid/pull/3084 +- Add a ``quote_via`` argument to ``pyramid.encode.urlencode`` to follow + the stdlib's version and enable custom quoting functions. + See https://github.com/Pylons/pyramid/pull/3088 + 1.9a2 (2017-05-09) ================== diff --git a/docs/whatsnew-1.9.rst b/docs/whatsnew-1.9.rst index 0c3385a66..9e9c1614d 100644 --- a/docs/whatsnew-1.9.rst +++ b/docs/whatsnew-1.9.rst @@ -41,6 +41,8 @@ Minor Feature Additions - Normalize the permission results to a proper class hierarchy. :class:`pyramid.security.ACLAllowed` is now a subclass of :class:`pyramid.security.Allowed` and :class:`pyramid.security.ACLDenied` is now a subclass of :class:`pyramid.security.Denied`. See https://github.com/Pylons/pyramid/pull/3084 +- Add a ``quote_via`` argument to :func:`pyramid.encode.urlencode` to follow the stdlib's version and enable custom quoting functions. See https://github.com/Pylons/pyramid/pull/3088 + Deprecations ------------ -- cgit v1.2.3 From 4983421128e2e0fc92c771510f7b3af57de6d855 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sun, 18 Jun 2017 01:31:45 -0500 Subject: configure resource_url to use the same logic --- docs/api/url.rst | 2 +- pyramid/config/views.py | 3 +- pyramid/tests/test_config/test_views.py | 1 + pyramid/url.py | 86 +++++++++------------------------ 4 files changed, 27 insertions(+), 65 deletions(-) diff --git a/docs/api/url.rst b/docs/api/url.rst index 131d85806..8aaabc352 100644 --- a/docs/api/url.rst +++ b/docs/api/url.rst @@ -5,7 +5,7 @@ .. automodule:: pyramid.url - .. autofunction:: pyramid.url.resource_url(context, request, *elements, query=None, anchor=None) + .. autofunction:: resource_url .. autofunction:: route_url diff --git a/pyramid/config/views.py b/pyramid/config/views.py index 48c4e3437..e5ebc8e07 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -1950,8 +1950,7 @@ class StaticURLInfo(object): kw['subpath'] = subpath return request.route_url(route_name, **kw) else: - app_url, scheme, host, port, qs, anchor = \ - parse_url_overrides(kw) + app_url, qs, anchor = parse_url_overrides(request, kw) parsed = url_parse(url) if not parsed.scheme: url = urlparse.urlunparse(parsed._replace( diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py index 0816d9958..860254385 100644 --- a/pyramid/tests/test_config/test_views.py +++ b/pyramid/tests/test_config/test_views.py @@ -3411,6 +3411,7 @@ class DummyRequest: subpath = () matchdict = None request_iface = IRequest + application_url = 'http://example.com/foo' def __init__(self, environ=None): if environ is None: diff --git a/pyramid/url.py b/pyramid/url.py index cbfbfc553..c21d49c33 100644 --- a/pyramid/url.py +++ b/pyramid/url.py @@ -31,13 +31,13 @@ from pyramid.traversal import ( QUERY_SAFE = "/?:@!$&'()*+,;=" # RFC 3986 ANCHOR_SAFE = QUERY_SAFE -def parse_url_overrides(kw): +def parse_url_overrides(request, kw): """Parse special arguments passed when generating urls. The supplied dictionary is mutated when we pop arguments. - Returns a 6-tuple of the format: + Returns a 3-tuple of the format: - ``(app_url, scheme, host, port, qs, anchor)``. + ``(app_url, qs, anchor)``. """ app_url = kw.pop('_app_url', None) scheme = kw.pop('_scheme', None) @@ -46,6 +46,12 @@ def parse_url_overrides(kw): query = kw.pop('_query', '') anchor = kw.pop('_anchor', '') + if app_url is None: + if (scheme is not None or host is not None or port is not None): + app_url = request._partial_application_url(scheme, host, port) + else: + app_url = request.application_url + qs = '' if query: if isinstance(query, string_types): @@ -54,11 +60,9 @@ def parse_url_overrides(kw): qs = '?' + urlencode(query, doseq=True) if anchor: - anchor = url_quote(anchor, ANCHOR_SAFE) - if not anchor.startswith('#'): - anchor = '#' + anchor + anchor = '#' + url_quote(anchor, ANCHOR_SAFE) - return app_url, scheme, host, port, qs, anchor + return app_url, qs, anchor class URLMethodsMixin(object): """ Request methods mixin for BaseRequest having to do with URL @@ -255,13 +259,7 @@ class URLMethodsMixin(object): if route.pregenerator is not None: elements, kw = route.pregenerator(self, elements, kw) - app_url, scheme, host, port, qs, anchor = parse_url_overrides(kw) - - if app_url is None: - if (scheme is not None or host is not None or port is not None): - app_url = self._partial_application_url(scheme, host, port) - else: - app_url = self.application_url + app_url, qs, anchor = parse_url_overrides(self, kw) path = route.generate(kw) # raises KeyError if generate fails @@ -522,13 +520,15 @@ class URLMethodsMixin(object): virtual_path = getattr(url_adapter, 'virtual_path', None) - app_url = None - scheme = None - host = None - port = None + urlkw = {} + for name in ( + 'app_url', 'scheme', 'host', 'port', 'query', 'anchor' + ): + val = kw.get(name, None) + if val is not None: + urlkw['_' + name] = val if 'route_name' in kw: - newkw = {} route_name = kw['route_name'] remainder = getattr(url_adapter, 'virtual_path_tuple', None) if remainder is None: @@ -536,39 +536,16 @@ class URLMethodsMixin(object): # virtual_path_tuple remainder = tuple(url_adapter.virtual_path.split('/')) remainder_name = kw.get('route_remainder_name', 'traverse') - newkw[remainder_name] = remainder - - for name in ( - 'app_url', 'scheme', 'host', 'port', 'query', 'anchor' - ): - val = kw.get(name, None) - if val is not None: - newkw['_' + name] = val - + urlkw[remainder_name] = remainder + if 'route_kw' in kw: route_kw = kw.get('route_kw') if route_kw is not None: - newkw.update(route_kw) - - return self.route_url(route_name, *elements, **newkw) - - if 'app_url' in kw: - app_url = kw['app_url'] + urlkw.update(route_kw) - if 'scheme' in kw: - scheme = kw['scheme'] + return self.route_url(route_name, *elements, **urlkw) - if 'host' in kw: - host = kw['host'] - - if 'port' in kw: - port = kw['port'] - - if app_url is None: - if scheme or host or port: - app_url = self._partial_application_url(scheme, host, port) - else: - app_url = self.application_url + app_url, qs, anchor = parse_url_overrides(self, urlkw) resource_url = None local_url = getattr(resource, '__resource_url__', None) @@ -589,21 +566,6 @@ class URLMethodsMixin(object): # __resource_url__ function returned None resource_url = app_url + virtual_path - qs = '' - anchor = '' - - if 'query' in kw: - query = kw['query'] - if isinstance(query, string_types): - qs = '?' + url_quote(query, QUERY_SAFE) - elif query: - qs = '?' + urlencode(query, doseq=True) - - if 'anchor' in kw: - anchor = kw['anchor'] - anchor = url_quote(anchor, ANCHOR_SAFE) - anchor = '#' + anchor - if elements: suffix = _join_elements(elements) else: -- cgit v1.2.3 From effe0e6c5adf64ac99f54082121373f84be4611b Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sun, 18 Jun 2017 02:12:51 -0500 Subject: document changes and add tests --- pyramid/tests/test_url.py | 32 ++++++++++++++++++++++++ pyramid/url.py | 63 ++++++++++++++++++++++++++++------------------- 2 files changed, 69 insertions(+), 26 deletions(-) diff --git a/pyramid/tests/test_url.py b/pyramid/tests/test_url.py index ddf28e0b0..af2e5405c 100644 --- a/pyramid/tests/test_url.py +++ b/pyramid/tests/test_url.py @@ -115,6 +115,14 @@ class TestURLMethodsMixin(unittest.TestCase): self.assertEqual(result, 'http://example.com:5432/context/a') + def test_resource_url_with_query_None(self): + request = self._makeOne() + self._registerResourceURL(request.registry) + context = DummyContext() + result = request.resource_url(context, 'a', query=None) + self.assertEqual(result, + 'http://example.com:5432/context/a') + def test_resource_url_anchor_is_after_root_when_no_elements(self): request = self._makeOne() self._registerResourceURL(request.registry) @@ -157,6 +165,13 @@ class TestURLMethodsMixin(unittest.TestCase): self.assertEqual(result, 'http://example.com:5432/context/#%20/%23?&+') + def test_resource_url_anchor_is_None(self): + request = self._makeOne() + self._registerResourceURL(request.registry) + context = DummyContext() + result = request.resource_url(context, anchor=None) + self.assertEqual(result, 'http://example.com:5432/context/') + def test_resource_url_no_IResourceURL_registered(self): # falls back to ResourceURL root = DummyContext() @@ -421,6 +436,14 @@ class TestURLMethodsMixin(unittest.TestCase): self.assertEqual(result, 'http://example.com:5432/1/2/3?a=1#foo') + def test_route_url_with_query_None(self): + from pyramid.interfaces import IRoutesMapper + request = self._makeOne() + mapper = DummyRoutesMapper(route=DummyRoute('/1/2/3')) + request.registry.registerUtility(mapper, IRoutesMapper) + result = request.route_url('flub', a=1, b=2, c=3, _query=None) + self.assertEqual(result, 'http://example.com:5432/1/2/3') + def test_route_url_with_anchor_binary(self): from pyramid.interfaces import IRoutesMapper request = self._makeOne() @@ -442,6 +465,15 @@ class TestURLMethodsMixin(unittest.TestCase): self.assertEqual(result, 'http://example.com:5432/1/2/3#La%20Pe%C3%B1a') + def test_route_url_with_anchor_None(self): + from pyramid.interfaces import IRoutesMapper + request = self._makeOne() + mapper = DummyRoutesMapper(route=DummyRoute('/1/2/3')) + request.registry.registerUtility(mapper, IRoutesMapper) + result = request.route_url('flub', _anchor=None) + + self.assertEqual(result, 'http://example.com:5432/1/2/3') + def test_route_url_with_query(self): from pyramid.interfaces import IRoutesMapper request = self._makeOne() diff --git a/pyramid/url.py b/pyramid/url.py index c21d49c33..2e964dc7e 100644 --- a/pyramid/url.py +++ b/pyramid/url.py @@ -32,12 +32,14 @@ QUERY_SAFE = "/?:@!$&'()*+,;=" # RFC 3986 ANCHOR_SAFE = QUERY_SAFE def parse_url_overrides(request, kw): - """Parse special arguments passed when generating urls. + """ + Parse special arguments passed when generating urls. The supplied dictionary is mutated when we pop arguments. Returns a 3-tuple of the format: ``(app_url, qs, anchor)``. + """ app_url = kw.pop('_app_url', None) scheme = kw.pop('_scheme', None) @@ -59,10 +61,11 @@ def parse_url_overrides(request, kw): else: qs = '?' + urlencode(query, doseq=True) + frag = '' if anchor: - anchor = '#' + url_quote(anchor, ANCHOR_SAFE) + frag = '#' + url_quote(anchor, ANCHOR_SAFE) - return app_url, qs, anchor + return app_url, qs, frag class URLMethodsMixin(object): """ Request methods mixin for BaseRequest having to do with URL @@ -78,6 +81,7 @@ class URLMethodsMixin(object): passed, the ``port`` value is assumed to ``443``. Likewise, if ``scheme`` is passed as ``http`` and ``port`` is not passed, the ``port`` value is assumed to be ``80``. + """ e = self.environ if scheme is None: @@ -184,10 +188,6 @@ class URLMethodsMixin(object): as values, and a k=v pair will be placed into the query string for each value. - .. versionchanged:: 1.5 - Allow the ``_query`` option to be a string to enable alternative - encodings. - If a keyword argument ``_anchor`` is present, its string representation will be quoted per :rfc:`3986#section-3.5` and used as a named anchor in the generated URL @@ -201,10 +201,6 @@ class URLMethodsMixin(object): ``_anchor`` is passed as a Unicode object, it will be converted to UTF-8 before being appended to the URL. - .. versionchanged:: 1.5 - The ``_anchor`` option will be escaped instead of using - its raw string representation. - If both ``_anchor`` and ``_query`` are specified, the anchor element will always follow the query element, e.g. ``http://example.com?foo=1#bar``. @@ -245,6 +241,18 @@ class URLMethodsMixin(object): If the route object which matches the ``route_name`` argument has a :term:`pregenerator`, the ``*elements`` and ``**kw`` arguments passed to this function might be augmented or changed. + + .. versionchanged:: 1.5 + Allow the ``_query`` option to be a string to enable alternative + encodings. + + The ``_anchor`` option will be escaped instead of using + its raw string representation. + + .. versionchanged:: 1.9 + If ``_query`` or ``_anchor`` are falsey (such as ``None`` or an + empty string) they will not be included in the generated url. + """ try: reg = self.registry @@ -298,13 +306,13 @@ class URLMethodsMixin(object): implemented in terms of :meth:`pyramid.request.Request.route_url` in just this way. As a result, any ``_app_url`` passed within the ``**kw`` values to ``route_path`` will be ignored. + """ kw['_app_url'] = self.script_name return self.route_url(route_name, *elements, **kw) def resource_url(self, resource, *elements, **kw): """ - Generate a string representing the absolute URL of the :term:`resource` object based on the ``wsgi.url_scheme``, ``HTTP_HOST`` or ``SERVER_NAME`` in the request, plus any @@ -370,10 +378,6 @@ class URLMethodsMixin(object): as values, and a k=v pair will be placed into the query string for each value. - .. versionchanged:: 1.5 - Allow the ``query`` option to be a string to enable alternative - encodings. - If a keyword argument ``anchor`` is present, its string representation will be used as a named anchor in the generated URL (e.g. if ``anchor`` is passed as ``foo`` and the resource URL is @@ -386,10 +390,6 @@ class URLMethodsMixin(object): ``anchor`` is passed as a Unicode object, it will be converted to UTF-8 before being appended to the URL. - .. versionchanged:: 1.5 - The ``anchor`` option will be escaped instead of using - its raw string representation. - If both ``anchor`` and ``query`` are specified, the anchor element will always follow the query element, e.g. ``http://example.com?foo=1#bar``. @@ -418,9 +418,6 @@ class URLMethodsMixin(object): pass ``app_url=''``. Passing ``app_url=''`` when the resource path is ``/baz/bar`` will return ``/baz/bar``. - .. versionadded:: 1.3 - ``app_url`` - If ``app_url`` is passed and any of ``scheme``, ``port``, or ``host`` are also passed, ``app_url`` will take precedence and the values passed for ``scheme``, ``host``, and/or ``port`` will be ignored. @@ -432,9 +429,6 @@ class URLMethodsMixin(object): .. seealso:: See also :ref:`overriding_resource_url_generation`. - - .. versionadded:: 1.5 - ``route_name``, ``route_kw``, and ``route_remainder_name`` If ``route_name`` is passed, this function will delegate its URL production to the ``route_url`` function. Calling @@ -508,6 +502,23 @@ class URLMethodsMixin(object): For backwards compatibility purposes, this method is also aliased as the ``model_url`` method of request. + + .. versionchanged:: 1.3 + Added the ``app_url`` keyword argument. + + .. versionchanged:: 1.5 + Allow the ``query`` option to be a string to enable alternative + encodings. + + The ``anchor`` option will be escaped instead of using + its raw string representation. + + Added the ``route_name``, ``route_kw``, and + ``route_remainder_name`` keyword arguments. + + .. versionchanged:: 1.9 + If ``query`` or ``anchor`` are falsey (such as ``None`` or an + empty string) they will not be included in the generated url. """ try: reg = self.registry -- cgit v1.2.3 From 983216c05a79e6a725d68f94ef3d0ab1d25f97d2 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 19 Jun 2017 21:35:46 -0500 Subject: add changelog for #3034 --- CHANGES.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 939b777ab..d695599a5 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -31,6 +31,12 @@ unreleased the stdlib's version and enable custom quoting functions. See https://github.com/Pylons/pyramid/pull/3088 +- Support `_query=None` and `_anchor=None` in ``request.route_url`` as well + as ``query=None`` and ``anchor=None`` in ``request.resource_url``. + Previously this would cause an `?` and a `#`, respectively, in the url + with nothing after it. + See https://github.com/Pylons/pyramid/pull/3034 + 1.9a2 (2017-05-09) ================== -- cgit v1.2.3 From 5aa1afbb216a800a420ffa6121a54b4ea194482b Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 19 Jun 2017 21:39:36 -0500 Subject: add changelog for #3086 --- CHANGES.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index d695599a5..bc911d1e3 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -37,6 +37,12 @@ unreleased with nothing after it. See https://github.com/Pylons/pyramid/pull/3034 +- Revamp the ``IRouter`` API used by ``IExecutionPolicy`` to force + pushing/popping the request threadlocals. The + ``IRouter.make_request(environ)`` API has been replaced by + ``IRouter.request_context(environ)`` which should be used as a context + manager. See https://github.com/Pylons/pyramid/pull/3086 + 1.9a2 (2017-05-09) ================== -- cgit v1.2.3 From 53cfb8383593288f5114c1a31a6253987e9233bd Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 19 Jun 2017 21:43:03 -0500 Subject: update the whatsnew-1.9 with changes from #3034 --- CHANGES.txt | 4 ++-- docs/whatsnew-1.9.rst | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index bc911d1e3..dfa9844f9 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -34,8 +34,8 @@ unreleased - Support `_query=None` and `_anchor=None` in ``request.route_url`` as well as ``query=None`` and ``anchor=None`` in ``request.resource_url``. Previously this would cause an `?` and a `#`, respectively, in the url - with nothing after it. - See https://github.com/Pylons/pyramid/pull/3034 + with nothing after it. Now the unnecessary parts are dropped from the + generated URL. See https://github.com/Pylons/pyramid/pull/3034 - Revamp the ``IRouter`` API used by ``IExecutionPolicy`` to force pushing/popping the request threadlocals. The diff --git a/docs/whatsnew-1.9.rst b/docs/whatsnew-1.9.rst index 9e9c1614d..3c2e75d5c 100644 --- a/docs/whatsnew-1.9.rst +++ b/docs/whatsnew-1.9.rst @@ -43,6 +43,8 @@ Minor Feature Additions - Add a ``quote_via`` argument to :func:`pyramid.encode.urlencode` to follow the stdlib's version and enable custom quoting functions. See https://github.com/Pylons/pyramid/pull/3088 +- Support `_query=None` and `_anchor=None` in :meth:`pyramid.request.Request.route_url` as well as ``query=None`` and ``anchor=None`` in :meth:`pyramid.request.Request.resource_url`. Previously this would cause an `?` and a `#`, respectively, in the url with nothing after it. Now the unnecessary parts are dropped from the generated URL. See https://github.com/Pylons/pyramid/pull/3034 + Deprecations ------------ -- cgit v1.2.3 From bd124a79c3c1517290b9d074f88875f05a2b619e Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 19 Jun 2017 22:16:26 -0500 Subject: prep 1.9b1 --- CHANGES.txt | 4 ++-- setup.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index dfa9844f9..fc601c5ca 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,5 @@ -unreleased -========== +1.9b1 (2017-06-19) +================== - Add an informative error message when unknown predicates are supplied. The new message suggests alternatives based on the list of known predicates. diff --git a/setup.py b/setup.py index 03416efe7..840dcfdb5 100644 --- a/setup.py +++ b/setup.py @@ -61,7 +61,7 @@ testing_extras = tests_require + [ ] setup(name='pyramid', - version='1.9a2', + version='1.9b1', description='The Pyramid Web Framework, a Pylons project', long_description=README + '\n\n' + CHANGES, classifiers=[ -- cgit v1.2.3 From 78b4b7cdde93edb7d5a611a662295e48b73e4f7a Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 19 Jun 2017 22:28:06 -0500 Subject: use readme_renderer to check the README syntax --- RELEASING.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RELEASING.txt b/RELEASING.txt index 9f7db457e..451beef4f 100644 --- a/RELEASING.txt +++ b/RELEASING.txt @@ -71,8 +71,8 @@ Prepare new release branch - Change setup.py version to the release version number. -- Make sure PyPI long description renders (requires ``readme`` installed - into your Python):: +- Make sure PyPI long description renders (requires ``readme_renderer`` + installed into your Python):: $ python setup.py check -r -s -m -- cgit v1.2.3 From 068010176a6bb4ea98a0598600dc5f571a2a0502 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 19 Jun 2017 23:47:13 -0500 Subject: mention check_csrf_origin moving to the pyramid.csrf module --- docs/whatsnew-1.9.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/whatsnew-1.9.rst b/docs/whatsnew-1.9.rst index 3c2e75d5c..77a84e431 100644 --- a/docs/whatsnew-1.9.rst +++ b/docs/whatsnew-1.9.rst @@ -52,7 +52,7 @@ Deprecations - Retrieving CSRF token from the session has been deprecated in favor of equivalent methods in the :mod:`pyramid.csrf` module. The CSRF methods (``ISession.get_csrf_token`` and ``ISession.new_csrf_token``) are no longer required on the :class:`pyramid.interfaces.ISession` interface except when using the default :class:`pyramid.csrf.LegacySessionCSRFStoragePolicy`. - Also, ``pyramid.session.check_csrf_token`` is now located at :func:`pyramid.csrf.check_csrf_token`. + Also, ``pyramid.session.check_csrf_token`` is now located at :func:`pyramid.csrf.check_csrf_token` and ``pyramid.session.check_csrf_origin`` is moved to :func:`pyramid.csrf.check_csrf_origin`. See https://github.com/Pylons/pyramid/pull/2854 and https://github.com/Pylons/pyramid/pull/3019 -- cgit v1.2.3 From 0707974715d0795384f75ea2de55ffc424219ac0 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Tue, 20 Jun 2017 14:36:00 -0700 Subject: update intersphinx for pytest and repoze.who - Closes #3098 --- docs/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index e63019c63..6158164ed 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -70,7 +70,7 @@ intersphinx_mapping = { 'plaster': ('http://docs.pylonsproject.org/projects/plaster/en/latest/', None), 'pylonswebframework': ('http://docs.pylonsproject.org/projects/pylons-webframework/en/latest/', None), 'python': ('https://docs.python.org/3', None), - 'pytest': ('https://pytest.org/en/latest/', None), + 'pytest': ('https://docs.pytest.org/en/latest/', None), 'sphinx': ('http://www.sphinx-doc.org/en/latest', None), 'sqla': ('http://docs.sqlalchemy.org/en/latest', None), 'tm': ('http://docs.pylonsproject.org/projects/pyramid-tm/en/latest/', None), @@ -80,7 +80,7 @@ intersphinx_mapping = { 'venusian': ('http://docs.pylonsproject.org/projects/venusian/en/latest', None), 'webob': ('http://docs.webob.org/en/latest', None), 'webtest': ('http://webtest.pythonpaste.org/en/latest', None), - 'who': ('http://repozewho.readthedocs.org/en/latest', None), + 'who': ('http://repozewho.readthedocs.io/en/latest', None), 'zcml': ('http://docs.pylonsproject.org/projects/pyramid-zcml/en/latest', None), 'zcomponent': ('http://zopecomponent.readthedocs.io/en/latest/', None), 'zinterface': ('http://zopeinterface.readthedocs.io/en/latest/', None), -- cgit v1.2.3 From 19d341b5be789e97000d3dcbd33de75d8b061829 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Mon, 26 Jun 2017 03:48:21 -0700 Subject: change http://docs.pylonsproject.org to https - use correct URL for code style - use correct Pyramid version for zodb wiki src file template --- CHANGES.txt | 4 ++-- HACKING.txt | 2 +- README.rst | 4 ++-- RELEASING.txt | 8 +++---- contributing.md | 4 ++-- docs/conf.py | 26 +++++++++++----------- docs/copyright.rst | 2 +- docs/designdefense.rst | 2 +- docs/glossary.rst | 16 ++++++------- docs/narr/i18n.rst | 4 ++-- docs/narr/logging.rst | 2 +- docs/narr/myproject/development.ini | 4 ++-- docs/narr/myproject/production.ini | 4 ++-- docs/narr/project.rst | 2 +- docs/narr/templates.rst | 6 ++--- docs/quick_tour/logging/development.ini | 4 ++-- docs/quick_tour/logging/production.ini | 4 ++-- docs/quick_tour/package/development.ini | 4 ++-- docs/quick_tour/package/production.ini | 4 ++-- docs/quick_tour/sessions/development.ini | 4 ++-- docs/quick_tour/sessions/production.ini | 4 ++-- docs/quick_tour/sqla_demo/development.ini | 4 ++-- docs/quick_tour/sqla_demo/production.ini | 4 ++-- docs/quick_tutorial/cookiecutters/development.ini | 4 ++-- docs/quick_tutorial/cookiecutters/production.ini | 4 ++-- docs/quick_tutorial/functional_testing.rst | 2 +- docs/tutorials/wiki/installation.rst | 6 ++--- .../wiki/src/authorization/development.ini | 4 ++-- .../wiki/src/authorization/production.ini | 4 ++-- .../tutorials/wiki/src/basiclayout/development.ini | 4 ++-- docs/tutorials/wiki/src/basiclayout/production.ini | 4 ++-- .../wiki/src/installation/development.ini | 4 ++-- .../tutorials/wiki/src/installation/production.ini | 4 ++-- docs/tutorials/wiki/src/models/development.ini | 4 ++-- docs/tutorials/wiki/src/models/production.ini | 4 ++-- docs/tutorials/wiki/src/tests/development.ini | 4 ++-- docs/tutorials/wiki/src/tests/production.ini | 4 ++-- .../src/tests/tutorial/templates/mytemplate.pt | 4 ++-- docs/tutorials/wiki/src/views/development.ini | 4 ++-- docs/tutorials/wiki/src/views/production.ini | 4 ++-- docs/tutorials/wiki2/installation.rst | 4 ++-- .../wiki2/src/authentication/development.ini | 4 ++-- .../wiki2/src/authentication/production.ini | 4 ++-- .../wiki2/src/authorization/development.ini | 4 ++-- .../wiki2/src/authorization/production.ini | 4 ++-- .../wiki2/src/basiclayout/development.ini | 4 ++-- .../tutorials/wiki2/src/basiclayout/production.ini | 4 ++-- .../wiki2/src/installation/development.ini | 4 ++-- .../wiki2/src/installation/production.ini | 4 ++-- docs/tutorials/wiki2/src/models/development.ini | 4 ++-- docs/tutorials/wiki2/src/models/production.ini | 4 ++-- docs/tutorials/wiki2/src/tests/development.ini | 4 ++-- docs/tutorials/wiki2/src/tests/production.ini | 4 ++-- docs/tutorials/wiki2/src/views/development.ini | 4 ++-- docs/tutorials/wiki2/src/views/production.ini | 4 ++-- docs/tutorials/wiki2/tests.rst | 2 +- docs/whatsnew-1.9.rst | 4 ++-- pyramid/scaffolds/__init__.py | 4 ++-- .../alchemy/+package+/templates/layout.jinja2_tmpl | 2 +- pyramid/scaffolds/alchemy/development.ini_tmpl | 4 ++-- pyramid/scaffolds/alchemy/production.ini_tmpl | 4 ++-- .../starter/+package+/templates/layout.jinja2_tmpl | 2 +- pyramid/scaffolds/starter/development.ini_tmpl | 4 ++-- pyramid/scaffolds/starter/production.ini_tmpl | 4 ++-- .../zodb/+package+/templates/mytemplate.pt_tmpl | 2 +- pyramid/scaffolds/zodb/development.ini_tmpl | 4 ++-- pyramid/scaffolds/zodb/production.ini_tmpl | 4 ++-- 67 files changed, 145 insertions(+), 145 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index fc601c5ca..2f24262c2 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -77,7 +77,7 @@ Major Features - The file format used by all ``p*`` command line scripts such as ``pserve`` and ``pshell``, as well as the ``pyramid.paster.bootstrap`` function is now replaceable thanks to a new dependency on - `plaster `_. + `plaster `_. For now, Pyramid is still shipping with integrated support for the PasteDeploy INI format by depending on the @@ -93,7 +93,7 @@ Major Features The first library to use this feature is `pyramid_retry - `_. + `_. See https://github.com/Pylons/pyramid/pull/2964 diff --git a/HACKING.txt b/HACKING.txt index bbebb5165..3e7ddd089 100644 --- a/HACKING.txt +++ b/HACKING.txt @@ -140,7 +140,7 @@ Coding Style - PEP8 compliance. Whitespace rules are relaxed: not necessary to put two newlines between classes. But 79-column lines, in particular, are mandatory. - See http://docs.pylonsproject.org/en/latest/community/codestyle.html for more + See https://pylonsproject.org/community-coding-style-standards.html for more information. - Please do not remove trailing whitespace. Configure your editor to reduce diff --git a/README.rst b/README.rst index 2b2535bfb..57829eb19 100644 --- a/README.rst +++ b/README.rst @@ -6,7 +6,7 @@ Pyramid :alt: 1.9-branch Travis CI Status .. image:: https://readthedocs.org/projects/pyramid/badge/?version=1.9-branch - :target: http://docs.pylonsproject.org/projects/pyramid/en/1.9-branch/ + :target: https://docs.pylonsproject.org/projects/pyramid/en/1.9-branch/ :alt: 1.9-branch Documentation Status .. image:: https://img.shields.io/badge/irc-freenode-blue.svg @@ -40,7 +40,7 @@ Support and Documentation ------------------------- See `Pyramid Support and Development -`_ +`_ for documentation, reporting bugs, and getting support. Developing and Contributing diff --git a/RELEASING.txt b/RELEASING.txt index 451beef4f..c8ff6578a 100644 --- a/RELEASING.txt +++ b/RELEASING.txt @@ -142,10 +142,10 @@ https://pypi.python.org/pypi/pyramid/1.x === One time only for new version, first pre-release === What's New -http://docs.pylonsproject.org/projects/pyramid/en/1.X-branch/whatsnew-1.X.html +https://docs.pylonsproject.org/projects/pyramid/en/1.X-branch/whatsnew-1.X.html === For all subsequent pre-releases === Changes -http://docs.pylonsproject.org/projects/pyramid/en/1.X-branch/changes.html#version-yyyy-mm-dd +https://docs.pylonsproject.org/projects/pyramid/en/1.X-branch/changes.html#version-yyyy-mm-dd Issues https://github.com/Pylons/pyramid/issues @@ -161,11 +161,11 @@ Here are the changes: <> What's New In Pyramid 1.X: -http://docs.pylonsproject.org/projects/pyramid/en/1.X-branch/whatsnew-1.X.html +https://docs.pylonsproject.org/projects/pyramid/en/1.X-branch/whatsnew-1.X.html 1.X release documentation (across all alphas and betas, as well as when it gets to final release): -http://docs.pylonsproject.org/projects/pyramid/en/1.X-branch/ +https://docs.pylonsproject.org/projects/pyramid/en/1.X-branch/ You can install it via PyPI: diff --git a/contributing.md b/contributing.md index 5e0ac53bf..fec4f4f27 100644 --- a/contributing.md +++ b/contributing.md @@ -5,7 +5,7 @@ All projects under the Pylons Projects, including this one, follow the guidelines established at [How to Contribute](https://pylonsproject.org/community-how-to-contribute.html) and [Coding Style and -Standards](http://docs.pylonsproject.org/en/latest/community/codestyle.html). +Standards](https://pylonsproject.org/community-coding-style-standards.html). You can contribute to this project in several ways. @@ -51,7 +51,7 @@ Building documentation for a Pylons Project project improve the process for Windows users are welcome by submitting an issue or a pull request. Windows users may find it helpful to follow the guide [Installing Pyramid on a Windows -System](http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/install.html#installing-pyramid-on-a-windows-system). +System](https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/install.html#installing-pyramid-on-a-windows-system). 1. Fork the repo on GitHub by clicking the [Fork] button. 2. Clone your fork into a workspace on your local machine. diff --git a/docs/conf.py b/docs/conf.py index 6158164ed..68d23a651 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -62,26 +62,26 @@ extensions = [ # Looks for objects in external projects intersphinx_mapping = { - 'colander': ('http://docs.pylonsproject.org/projects/colander/en/latest', None), - 'cookbook': ('http://docs.pylonsproject.org/projects/pyramid-cookbook/en/latest/', None), + 'colander': ('https://docs.pylonsproject.org/projects/colander/en/latest', None), + 'cookbook': ('https://docs.pylonsproject.org/projects/pyramid-cookbook/en/latest/', None), 'cookiecutter': ('https://cookiecutter.readthedocs.io/en/latest/', None), - 'deform': ('http://docs.pylonsproject.org/projects/deform/en/latest', None), - 'jinja2': ('http://docs.pylonsproject.org/projects/pyramid-jinja2/en/latest/', None), - 'plaster': ('http://docs.pylonsproject.org/projects/plaster/en/latest/', None), - 'pylonswebframework': ('http://docs.pylonsproject.org/projects/pylons-webframework/en/latest/', None), + 'deform': ('https://docs.pylonsproject.org/projects/deform/en/latest', None), + 'jinja2': ('https://docs.pylonsproject.org/projects/pyramid-jinja2/en/latest/', None), + 'plaster': ('https://docs.pylonsproject.org/projects/plaster/en/latest/', None), + 'pylonswebframework': ('https://docs.pylonsproject.org/projects/pylons-webframework/en/latest/', None), 'python': ('https://docs.python.org/3', None), 'pytest': ('https://docs.pytest.org/en/latest/', None), 'sphinx': ('http://www.sphinx-doc.org/en/latest', None), 'sqla': ('http://docs.sqlalchemy.org/en/latest', None), - 'tm': ('http://docs.pylonsproject.org/projects/pyramid-tm/en/latest/', None), - 'toolbar': ('http://docs.pylonsproject.org/projects/pyramid-debugtoolbar/en/latest', None), - 'tstring': ('http://docs.pylonsproject.org/projects/translationstring/en/latest', None), - 'tutorials': ('http://docs.pylonsproject.org/projects/pyramid-tutorials/en/latest/', None), - 'venusian': ('http://docs.pylonsproject.org/projects/venusian/en/latest', None), + 'tm': ('https://docs.pylonsproject.org/projects/pyramid-tm/en/latest/', None), + 'toolbar': ('https://docs.pylonsproject.org/projects/pyramid-debugtoolbar/en/latest', None), + 'tstring': ('https://docs.pylonsproject.org/projects/translationstring/en/latest', None), + 'tutorials': ('https://docs.pylonsproject.org/projects/pyramid-tutorials/en/latest/', None), + 'venusian': ('https://docs.pylonsproject.org/projects/venusian/en/latest', None), 'webob': ('http://docs.webob.org/en/latest', None), 'webtest': ('http://webtest.pythonpaste.org/en/latest', None), 'who': ('http://repozewho.readthedocs.io/en/latest', None), - 'zcml': ('http://docs.pylonsproject.org/projects/pyramid-zcml/en/latest', None), + 'zcml': ('https://docs.pylonsproject.org/projects/pyramid-zcml/en/latest', None), 'zcomponent': ('http://zopecomponent.readthedocs.io/en/latest/', None), 'zinterface': ('http://zopeinterface.readthedocs.io/en/latest/', None), } @@ -136,7 +136,7 @@ if book: # ----------------------- # enable pylons_sphinx_latesturl when this branch is no longer "latest" # pylons_sphinx_latesturl_base = ( -# 'http://docs.pylonsproject.org/projects/pyramid/en/latest/') +# 'https://docs.pylonsproject.org/projects/pyramid/en/latest/') # pylons_sphinx_latesturl_pagename_overrides = { # # map old pagename -> new pagename # 'whatsnew-1.0': 'index', diff --git a/docs/copyright.rst b/docs/copyright.rst index 30ae40603..e0833fa57 100644 --- a/docs/copyright.rst +++ b/docs/copyright.rst @@ -96,7 +96,7 @@ HTML Version and Source Code ---------------------------- An HTML version of this book is freely available via -http://docs.pylonsproject.org/projects/pyramid/en/latest/ +https://docs.pylonsproject.org/projects/pyramid/en/latest/ The source code for the examples used in this book are available within the :app:`Pyramid` software distribution, always available diff --git a/docs/designdefense.rst b/docs/designdefense.rst index 0a72ff27d..2504f32aa 100644 --- a/docs/designdefense.rst +++ b/docs/designdefense.rst @@ -1433,7 +1433,7 @@ object which *is not logically global*: # credentials were invalid The `Pylons 1.X -`_ +`_ web framework uses a similar strategy. It calls these things "Stacked Object Proxies", so, for purposes of this discussion, I'll do so as well. diff --git a/docs/glossary.rst b/docs/glossary.rst index 9031ede04..ce8e727e3 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -349,7 +349,7 @@ Glossary `A full-featured Python web framework `_. Pylons - `A lightweight Python web framework `_ + `A lightweight Python web framework `_ and a predecessor of Pyramid. ZODB @@ -367,7 +367,7 @@ Glossary file. It was developed by Ian Bicking. plaster - `plaster `_ is + `plaster `_ is a library used by :app:`Pyramid` which acts as an abstraction between command-line scripts and the file format used to load the :term:`WSGI` components and application settings. By default :app:`Pyramid` ships @@ -954,16 +954,16 @@ Glossary pyramid_handlers An add-on package which allows :app:`Pyramid` users to create classes that are analogues of Pylons 1 "controllers". See - http://docs.pylonsproject.org/projects/pyramid_handlers/en/latest/. + https://docs.pylonsproject.org/projects/pyramid_handlers/en/latest/. pyramid_jinja2 :term:`Jinja2` templating system bindings for Pyramid, documented at - http://docs.pylonsproject.org/projects/pyramid_jinja2/en/latest/. This + https://docs.pylonsproject.org/projects/pyramid_jinja2/en/latest/. This package also includes a scaffold named ``pyramid_jinja2_starter``, which creates an application package based on the Jinja2 templating system. Akhet - `Akhet `_ is a + `Akhet `_ is a Pyramid library and demo application with a Pylons-like feel. It's most known for its former application scaffold, which helped users transition from Pylons and those preferring a more Pylons-like API. @@ -1006,7 +1006,7 @@ Glossary database information. :mod:`pyramid_debugtoolbar` is configured into the ``development.ini`` of all applications which use a Pyramid :term:`cookiecutter`. For more information, see - http://docs.pylonsproject.org/projects/pyramid_debugtoolbar/en/latest/. + https://docs.pylonsproject.org/projects/pyramid_debugtoolbar/en/latest/. scaffold A project template that generates some of the major parts of a Pyramid @@ -1023,7 +1023,7 @@ Glossary used in production applications, because the logger can be configured to log to a file, to UNIX syslog, to the Windows Event Log, or even to email. See its `documentation - `_. + `_. console script A script written to the ``bin`` (on UNIX, or ``Scripts`` on Windows) @@ -1078,7 +1078,7 @@ Glossary A :term:`WSGI` server that runs on UNIX and Windows under Python 2.7+ and Python 3.3+. Projects generated via Pyramid cookiecutters use Waitress as a WGSI server. See - http://docs.pylonsproject.org/projects/waitress/en/latest/ for detailed + https://docs.pylonsproject.org/projects/waitress/en/latest/ for detailed information. Green Unicorn diff --git a/docs/narr/i18n.rst b/docs/narr/i18n.rst index 3549b53a5..29d4dd02a 100644 --- a/docs/narr/i18n.rst +++ b/docs/narr/i18n.rst @@ -681,9 +681,9 @@ Jinja2 Pyramid i18n Support The add-on `pyramid_jinja2 `_ provides a scaffold with an example of how to use internationalization with Jinja2 in Pyramid. See the documentation sections `Internalization (i18n) -`_ +`_ and `Paster Template I18N -`_. +`_. .. index:: diff --git a/docs/narr/logging.rst b/docs/narr/logging.rst index 9cc5b4ed8..2bd8ef4cd 100644 --- a/docs/narr/logging.rst +++ b/docs/narr/logging.rst @@ -219,7 +219,7 @@ Logging Exceptions To log or email exceptions generated by your :app:`Pyramid` application, use the :term:`pyramid_exclog` package. Details about its configuration are in its `documentation -`_. +`_. .. index:: single: TransLogger diff --git a/docs/narr/myproject/development.ini b/docs/narr/myproject/development.ini index 20a8a4868..7e5496881 100644 --- a/docs/narr/myproject/development.ini +++ b/docs/narr/myproject/development.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -28,7 +28,7 @@ listen = localhost:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/narr/myproject/production.ini b/docs/narr/myproject/production.ini index 13be488e7..7060ef854 100644 --- a/docs/narr/myproject/production.ini +++ b/docs/narr/myproject/production.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -22,7 +22,7 @@ listen = *:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/narr/project.rst b/docs/narr/project.rst index f542eae86..776a0de44 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -1112,7 +1112,7 @@ Automatically Reloading Your Code During development, it can be really useful to automatically have the webserver restart when you make changes. ``pserve`` has a ``--reload`` switch to enable this. It uses the -`hupper `_ package +`hupper `_ package to enable this behavior. When your code crashes, ``hupper`` will wait for another change or the ``SIGHUP`` signal before restarting again. diff --git a/docs/narr/templates.rst b/docs/narr/templates.rst index 4eadbd2f0..6738e9270 100644 --- a/docs/narr/templates.rst +++ b/docs/narr/templates.rst @@ -450,12 +450,12 @@ templating languages including the following: .. _Chameleon: http://chameleon.readthedocs.org/en/latest/ .. _pyramid_chameleon: - http://docs.pylonsproject.org/projects/pyramid-chameleon/en/latest/ + https://docs.pylonsproject.org/projects/pyramid-chameleon/en/latest/ .. _Jinja2: http://jinja.pocoo.org/docs/dev/ .. _pyramid_jinja2: - http://docs.pylonsproject.org/projects/pyramid-jinja2/en/latest/ + https://docs.pylonsproject.org/projects/pyramid-jinja2/en/latest/ .. _Mako: http://www.makotemplates.org/ .. _pyramid_mako: - http://docs.pylonsproject.org/projects/pyramid-mako/en/latest/ + https://docs.pylonsproject.org/projects/pyramid-mako/en/latest/ diff --git a/docs/quick_tour/logging/development.ini b/docs/quick_tour/logging/development.ini index b0210cbad..5e7317fc7 100644 --- a/docs/quick_tour/logging/development.ini +++ b/docs/quick_tour/logging/development.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -28,7 +28,7 @@ listen = localhost:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/quick_tour/logging/production.ini b/docs/quick_tour/logging/production.ini index 9c12bc4ec..3eedddda1 100644 --- a/docs/quick_tour/logging/production.ini +++ b/docs/quick_tour/logging/production.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -22,7 +22,7 @@ listen = *:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/quick_tour/package/development.ini b/docs/quick_tour/package/development.ini index b0210cbad..5e7317fc7 100644 --- a/docs/quick_tour/package/development.ini +++ b/docs/quick_tour/package/development.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -28,7 +28,7 @@ listen = localhost:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/quick_tour/package/production.ini b/docs/quick_tour/package/production.ini index 9c12bc4ec..3eedddda1 100644 --- a/docs/quick_tour/package/production.ini +++ b/docs/quick_tour/package/production.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -22,7 +22,7 @@ listen = *:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/quick_tour/sessions/development.ini b/docs/quick_tour/sessions/development.ini index b0210cbad..5e7317fc7 100644 --- a/docs/quick_tour/sessions/development.ini +++ b/docs/quick_tour/sessions/development.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -28,7 +28,7 @@ listen = localhost:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/quick_tour/sessions/production.ini b/docs/quick_tour/sessions/production.ini index 9c12bc4ec..3eedddda1 100644 --- a/docs/quick_tour/sessions/production.ini +++ b/docs/quick_tour/sessions/production.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -22,7 +22,7 @@ listen = *:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/quick_tour/sqla_demo/development.ini b/docs/quick_tour/sqla_demo/development.ini index a986c0063..8836a846e 100644 --- a/docs/quick_tour/sqla_demo/development.ini +++ b/docs/quick_tour/sqla_demo/development.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -32,7 +32,7 @@ listen = localhost:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/quick_tour/sqla_demo/production.ini b/docs/quick_tour/sqla_demo/production.ini index 9abb54231..4566e02a0 100644 --- a/docs/quick_tour/sqla_demo/production.ini +++ b/docs/quick_tour/sqla_demo/production.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -26,7 +26,7 @@ listen = *:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/quick_tutorial/cookiecutters/development.ini b/docs/quick_tutorial/cookiecutters/development.ini index a5093fb52..ec621169d 100644 --- a/docs/quick_tutorial/cookiecutters/development.ini +++ b/docs/quick_tutorial/cookiecutters/development.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -28,7 +28,7 @@ listen = localhost:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/quick_tutorial/cookiecutters/production.ini b/docs/quick_tutorial/cookiecutters/production.ini index e24a065b1..8d2b9c79d 100644 --- a/docs/quick_tutorial/cookiecutters/production.ini +++ b/docs/quick_tutorial/cookiecutters/production.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -22,7 +22,7 @@ listen = *:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/quick_tutorial/functional_testing.rst b/docs/quick_tutorial/functional_testing.rst index 33793578a..518e3d67d 100644 --- a/docs/quick_tutorial/functional_testing.rst +++ b/docs/quick_tutorial/functional_testing.rst @@ -14,7 +14,7 @@ Unit tests are a common and popular approach to test-driven development (TDD). In web applications, though, the templating and entire apparatus of a web site are important parts of the delivered quality. We'd like a way to test these. -`WebTest `_ is a +`WebTest `_ is a Python package that does functional testing. With WebTest you can write tests which simulate a full HTTP request against a WSGI application, then test the information in the response. For speed purposes, WebTest skips the diff --git a/docs/tutorials/wiki/installation.rst b/docs/tutorials/wiki/installation.rst index e9a93f9fe..fba488aa9 100644 --- a/docs/tutorials/wiki/installation.rst +++ b/docs/tutorials/wiki/installation.rst @@ -379,13 +379,13 @@ assumptions: tutorial, we'll only be using :term:`traversal` and :term:`ZODB`. .. _pyramid_chameleon: - http://docs.pylonsproject.org/projects/pyramid-chameleon/en/latest/ + https://docs.pylonsproject.org/projects/pyramid-chameleon/en/latest/ .. _pyramid_tm: - http://docs.pylonsproject.org/projects/pyramid-tm/en/latest/ + https://docs.pylonsproject.org/projects/pyramid-tm/en/latest/ .. _pyramid_zodbconn: - http://docs.pylonsproject.org/projects/pyramid-zodbconn/en/latest/ + https://docs.pylonsproject.org/projects/pyramid-zodbconn/en/latest/ .. _transaction: http://zodb.readthedocs.org/en/latest/transactions.html diff --git a/docs/tutorials/wiki/src/authorization/development.ini b/docs/tutorials/wiki/src/authorization/development.ini index 9d45c3611..fec08941d 100644 --- a/docs/tutorials/wiki/src/authorization/development.ini +++ b/docs/tutorials/wiki/src/authorization/development.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -32,7 +32,7 @@ listen = localhost:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/tutorials/wiki/src/authorization/production.ini b/docs/tutorials/wiki/src/authorization/production.ini index 92a36813f..f2fa8d6d5 100644 --- a/docs/tutorials/wiki/src/authorization/production.ini +++ b/docs/tutorials/wiki/src/authorization/production.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -26,7 +26,7 @@ listen = *:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/tutorials/wiki/src/basiclayout/development.ini b/docs/tutorials/wiki/src/basiclayout/development.ini index 9d45c3611..fec08941d 100644 --- a/docs/tutorials/wiki/src/basiclayout/development.ini +++ b/docs/tutorials/wiki/src/basiclayout/development.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -32,7 +32,7 @@ listen = localhost:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/tutorials/wiki/src/basiclayout/production.ini b/docs/tutorials/wiki/src/basiclayout/production.ini index 92a36813f..f2fa8d6d5 100644 --- a/docs/tutorials/wiki/src/basiclayout/production.ini +++ b/docs/tutorials/wiki/src/basiclayout/production.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -26,7 +26,7 @@ listen = *:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/tutorials/wiki/src/installation/development.ini b/docs/tutorials/wiki/src/installation/development.ini index 9d45c3611..fec08941d 100644 --- a/docs/tutorials/wiki/src/installation/development.ini +++ b/docs/tutorials/wiki/src/installation/development.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -32,7 +32,7 @@ listen = localhost:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/tutorials/wiki/src/installation/production.ini b/docs/tutorials/wiki/src/installation/production.ini index 92a36813f..f2fa8d6d5 100644 --- a/docs/tutorials/wiki/src/installation/production.ini +++ b/docs/tutorials/wiki/src/installation/production.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -26,7 +26,7 @@ listen = *:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/tutorials/wiki/src/models/development.ini b/docs/tutorials/wiki/src/models/development.ini index 9d45c3611..fec08941d 100644 --- a/docs/tutorials/wiki/src/models/development.ini +++ b/docs/tutorials/wiki/src/models/development.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -32,7 +32,7 @@ listen = localhost:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/tutorials/wiki/src/models/production.ini b/docs/tutorials/wiki/src/models/production.ini index 92a36813f..f2fa8d6d5 100644 --- a/docs/tutorials/wiki/src/models/production.ini +++ b/docs/tutorials/wiki/src/models/production.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -26,7 +26,7 @@ listen = *:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/tutorials/wiki/src/tests/development.ini b/docs/tutorials/wiki/src/tests/development.ini index 9d45c3611..fec08941d 100644 --- a/docs/tutorials/wiki/src/tests/development.ini +++ b/docs/tutorials/wiki/src/tests/development.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -32,7 +32,7 @@ listen = localhost:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/tutorials/wiki/src/tests/production.ini b/docs/tutorials/wiki/src/tests/production.ini index 92a36813f..f2fa8d6d5 100644 --- a/docs/tutorials/wiki/src/tests/production.ini +++ b/docs/tutorials/wiki/src/tests/production.ini @@ -1,6 +1,6 @@ ### # app configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] @@ -26,7 +26,7 @@ listen = *:6543 ### # logging configuration -# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt index 2d967f83a..92f8eedcf 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt @@ -41,8 +41,8 @@