diff options
25 files changed, 231 insertions, 152 deletions
diff --git a/CHANGES.rst b/CHANGES.rst index 4cb8ba44b..d24fb24e8 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -25,9 +25,19 @@ Features imports from `cherrypy.wsgiserver`. See https://github.com/Pylons/pyramid/pull/3235 +- Add a context manager ``route_prefix_context`` to the + ``pyramid.config.Configurator`` to allow for convenient setting of the + route_prefix for ``include`` and ``add_route`` calls inside the context. + See https://github.com/Pylons/pyramid/pull/3279 + Bug Fixes --------- +- Set appropriate ``code`` and ``title`` attributes on the ``HTTPClientError`` and + ``HTTPServerError`` exception classes. This prevents inadvertently returning a 520 + error code. + See https://github.com/Pylons/pyramid/pull/3280 + Deprecations ------------ diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index b6dbcff2c..60e4e5732 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -316,3 +316,7 @@ Contributors - Deneys Maartens, 2017/11/03 - Heron Rossi, 2018/03/08 + +- Hunter Senft-Grupp, 2018/05/14 + +- Junhak Lee, 2018/05/14
\ No newline at end of file diff --git a/docs/Makefile b/docs/Makefile index 411ff35df..91f36aefc 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -12,7 +12,7 @@ PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -.PHONY: help clean html text web pickle htmlhelp latex latexpdf changes linkcheck epub doctest +.PHONY: help clean html text web pickle htmlhelp latex latexpdf xelatexpdf changes linkcheck epub doctest help: @echo "Please use \`make <target>' where <target> is one of" @@ -22,6 +22,7 @@ help: @echo " htmlhelp to make HTML files and a HTML help project" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " xelatexpdf to make LaTeX files and run them through xelatex" @echo " changes to make an overview over all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " epub to make an epub" @@ -30,6 +31,12 @@ help: clean: -rm -rf $(BUILDDIR)/* +xelatexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through xelatex..." + $(MAKE) PDFLATEX=xelatex -C $(BUILDDIR)/latex all-pdf + @echo "xelatex finished; the PDF files are in $(BUILDDIR)/latex." + html: mkdir -p $(BUILDDIR)/html $(BUILDDIR)/doctrees $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @@ -68,7 +75,7 @@ latex: cp _static/latex-note.png $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make latexpdf' to build a PDF file from them." + @echo "Run \`make xelatexpdf' to build a PDF file from them." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex diff --git a/docs/conf.py b/docs/conf.py index cf92e05e8..80585b336 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -53,7 +53,6 @@ extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', - 'sphinx.ext.todo', 'sphinx.ext.viewcode', 'sphinxcontrib.autoprogram', # enable pylons_sphinx_latesturl when this branch is no longer "latest" @@ -124,9 +123,6 @@ exclude_patterns = ['_themes/README.rst', ] # unit titles (such as .. function::). add_module_names = False -# Add support for todo items -todo_include_todos = True - # The name of the Pygments (syntax highlighting) style to use. #pygments_style = book and 'bw' or 'tango' if book: @@ -190,7 +186,10 @@ latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). latex_font_size = '10pt' -latex_additional_files = ['_static/latex-note.png', '_static/latex-warning.png'] +latex_additional_files = [ + '_static/latex-note.png', + '_static/latex-warning.png', +] # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, document class [howto/manual]). @@ -220,6 +219,17 @@ latex_domain_indices = False _PREAMBLE = r""" \usepackage[]{geometry} \geometry{bindingoffset=0.45in,textheight=7.25in,hdivide={0.5in,*,0.75in},vdivide={1in,7.25in,1in},papersize={7.5in,9.25in}} + +%XeLaTeX packages +\usepackage{xltxtra} +\usepackage{fontspec} %Font package +\usepackage{xunicode} + +%Select fonts +\setmainfont[Mapping=tex-text]{nimbusserif} +\setsansfont[Mapping=tex-text]{nimbussans} +\setmonofont{nimbusmono} + \hypersetup{ colorlinks=true, linkcolor=black, @@ -276,28 +286,22 @@ _PREAMBLE = r""" \definecolor{VerbatimColor}{rgb}{1,1,1} \definecolor{VerbatimBorderColor}{rgb}{1,1,1} -\makeatletter -\renewcommand{\py@noticestart@warning}{\py@heavybox} -\renewcommand{\py@noticeend@warning}{\py@endheavybox} -\renewcommand{\py@noticestart@note}{\py@heavybox} -\renewcommand{\py@noticeend@note}{\py@endheavybox} -\makeatother - % icons in note and warning boxes \usepackage{ifthen} -% Keep a copy of the original notice environment -\let\origbeginnotice\notice -\let\origendnotice\endnotice -% Redefine the notice environment so we can add our own code to it -\renewenvironment{notice}[2]{% - \origbeginnotice{#1}{}% equivalent to original \begin{notice}{#1}{#2} +% Keep a copy of the original sphinxadmonition environment +\let\origbeginadmon\sphinxadmonition +\let\origendadmon\endsphinxadmonition + +% Redefine the sphinxadmonition environment so we can add our own code to it +\renewenvironment{sphinxadmonition}[2]{% + \origbeginadmon{#1}{}% equivalent to original \begin{sphinxadmonition}{#1}{#2} % load graphics \ifthenelse{\equal{#1}{warning}}{\includegraphics{latex-warning.png}}{} \ifthenelse{\equal{#1}{note}}{\includegraphics{latex-note.png}}{} % etc. -}{% - \origendnotice% equivalent to original \end{notice} + }{% +\origendadmon % equivalent to original \end{sphinxadmonition} } % try to prevent code-block boxes from splitting across pages @@ -318,7 +322,6 @@ _PREAMBLE = r""" latex_elements = { 'preamble': _PREAMBLE, - 'wrapperclass': 'book', 'date': '', 'releasename': 'Version', 'title': r'The Pyramid Web Framework', @@ -342,17 +345,6 @@ def frontmatter(name, arguments, options, content, lineno, content_offset, block_text, state, state_machine): return [nodes.raw( '', - r""" -\frontmatter -% prevent part/chapter/section numbering -\setcounter{secnumdepth}{-2} -% suppress headers -\pagestyle{plain} -% reset page counter -\setcounter{page}{1} -% suppress first toc pagenum -\addtocontents{toc}{\protect\thispagestyle{empty}} -""", format='latex')] @@ -360,26 +352,14 @@ def mainmatter(name, arguments, options, content, lineno, content_offset, block_text, state, state_machine): return [nodes.raw( '', - r""" -\mainmatter -% allow part/chapter/section numbering -\setcounter{secnumdepth}{2} -% get headers back -\pagestyle{fancy} -\fancyhf{} -\renewcommand{\headrulewidth}{0.5pt} -\renewcommand{\footrulewidth}{0pt} -\fancyfoot[C]{\thepage} -\fancyhead[RO]{\rightmark} -\fancyhead[LE]{\leftmark} -""", format='latex')] def backmatter(name, arguments, options, content, lineno, content_offset, block_text, state, state_machine): - return [nodes.raw('', '\\backmatter\n\\setcounter{secnumdepth}{-1}\n', - format='latex')] + return [nodes.raw( + '', + format='latex')] def app_role(role, rawtext, text, lineno, inliner, options={}, content=[]): diff --git a/docs/make_book b/docs/make_book index 94e249441..c3d3743ae 100755 --- a/docs/make_book +++ b/docs/make_book @@ -1,4 +1,4 @@ #!/bin/sh -make clean latex SPHINXBUILD=../env/bin/sphinx-build BOOK=1 +make clean xelatexpdf SPHINXBUILD=../env/bin/sphinx-build BOOK=1 cd _build/latex && make all-pdf diff --git a/docs/make_pdf b/docs/make_pdf index 6c9863bc9..6cf0ff726 100755 --- a/docs/make_pdf +++ b/docs/make_pdf @@ -1,4 +1,4 @@ #!/bin/sh -make clean latex SPHINXBUILD=../env/bin/sphinx-build +make clean xelatexpdf SPHINXBUILD=../env/bin/sphinx-build cd _build/latex && make all-pdf diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index 9ac01e24a..00c7bd3bf 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -1045,6 +1045,24 @@ may be added in the future. For example: config = Configurator() config.include(users_include, route_prefix='/users') +A convenience context manager exists to set the route prefix for any +:meth:`pyramid.config.Configurator.add_route` or +:meth:`pyramid.config.Configurator.include` calls within the context. + +.. code-block:: python + :linenos: + + from pyramid.config import Configurator + + def timing_include(config): + config.add_route('timing.show_times', '/times') + + def main(global_config, **settings) + config = Configurator() + with config.route_prefix_context('/timing'): + config.include(timing_include) + config.add_route('timing.average', '/average') + .. index:: single: route predicates (custom) diff --git a/docs/quick_tutorial/authentication/setup.py b/docs/quick_tutorial/authentication/setup.py index e89b4e241..a5117af5a 100644 --- a/docs/quick_tutorial/authentication/setup.py +++ b/docs/quick_tutorial/authentication/setup.py @@ -5,7 +5,6 @@ requires = [ 'pyramid', 'pyramid_chameleon', 'waitress', - ] setup(name='tutorial', diff --git a/docs/quick_tutorial/authorization/setup.py b/docs/quick_tutorial/authorization/setup.py index e89b4e241..a5117af5a 100644 --- a/docs/quick_tutorial/authorization/setup.py +++ b/docs/quick_tutorial/authorization/setup.py @@ -5,7 +5,6 @@ requires = [ 'pyramid', 'pyramid_chameleon', 'waitress', - ] setup(name='tutorial', diff --git a/docs/quick_tutorial/json/setup.py b/docs/quick_tutorial/json/setup.py index 1699d9b1e..744612371 100644 --- a/docs/quick_tutorial/json/setup.py +++ b/docs/quick_tutorial/json/setup.py @@ -4,7 +4,6 @@ requires = [ 'pyramid', 'pyramid_chameleon', 'waitress', - ] setup(name='tutorial', diff --git a/docs/quick_tutorial/logging/setup.py b/docs/quick_tutorial/logging/setup.py index 1699d9b1e..744612371 100644 --- a/docs/quick_tutorial/logging/setup.py +++ b/docs/quick_tutorial/logging/setup.py @@ -4,7 +4,6 @@ requires = [ 'pyramid', 'pyramid_chameleon', 'waitress', - ] setup(name='tutorial', diff --git a/docs/quick_tutorial/more_view_classes/setup.py b/docs/quick_tutorial/more_view_classes/setup.py index 1699d9b1e..744612371 100644 --- a/docs/quick_tutorial/more_view_classes/setup.py +++ b/docs/quick_tutorial/more_view_classes/setup.py @@ -4,7 +4,6 @@ requires = [ 'pyramid', 'pyramid_chameleon', 'waitress', - ] setup(name='tutorial', diff --git a/docs/quick_tutorial/requirements.rst b/docs/quick_tutorial/requirements.rst index a65cfe6d2..29f263176 100644 --- a/docs/quick_tutorial/requirements.rst +++ b/docs/quick_tutorial/requirements.rst @@ -79,15 +79,15 @@ will reside as we proceed through the tutorial: .. code-block:: text - `── ~ - `── projects - `── quick_tutorial - │── env - `── step_one - │── intro - │ │── __init__.py - │ `── app.py - `── setup.py + ~ + └── projects + └── quick_tutorial + ├── env + └── step_one + ├── intro + │ ├── __init__.py + │ └── app.py + └── setup.py For Linux, the commands to do so are as follows: @@ -202,17 +202,3 @@ part is pretty easy. We'll also install a WSGI server, Waitress. Our Python virtual environment now has the Pyramid software available as well as the ``waitress`` package. -You can optionally install some of the extra Python packages used in this -tutorial. - -.. code-block:: bash - - # Mac and Linux - $ $VENV/bin/pip install webtest pytest pytest-cov deform sqlalchemy \ - pyramid_chameleon pyramid_debugtoolbar pyramid_jinja2 waitress \ - pyramid_tm zope.sqlalchemy - -.. code-block:: doscon - - # Windows - c:\> %VENV%\Scripts\pip install webtest pytest pytest-cov deform sqlalchemy pyramid_chameleon pyramid_debugtoolbar pyramid_jinja2 waitress pyramid_tm zope.sqlalchemy diff --git a/docs/quick_tutorial/retail_forms/setup.py b/docs/quick_tutorial/retail_forms/setup.py index f64049792..968889e74 100644 --- a/docs/quick_tutorial/retail_forms/setup.py +++ b/docs/quick_tutorial/retail_forms/setup.py @@ -5,7 +5,6 @@ requires = [ 'pyramid', 'pyramid_chameleon', 'waitress', - ] setup(name='tutorial', diff --git a/docs/quick_tutorial/routing/setup.py b/docs/quick_tutorial/routing/setup.py index 1699d9b1e..744612371 100644 --- a/docs/quick_tutorial/routing/setup.py +++ b/docs/quick_tutorial/routing/setup.py @@ -4,7 +4,6 @@ requires = [ 'pyramid', 'pyramid_chameleon', 'waitress', - ] setup(name='tutorial', diff --git a/docs/quick_tutorial/sessions/setup.py b/docs/quick_tutorial/sessions/setup.py index 1699d9b1e..744612371 100644 --- a/docs/quick_tutorial/sessions/setup.py +++ b/docs/quick_tutorial/sessions/setup.py @@ -4,7 +4,6 @@ requires = [ 'pyramid', 'pyramid_chameleon', 'waitress', - ] setup(name='tutorial', diff --git a/docs/quick_tutorial/static_assets/setup.py b/docs/quick_tutorial/static_assets/setup.py index 1699d9b1e..744612371 100644 --- a/docs/quick_tutorial/static_assets/setup.py +++ b/docs/quick_tutorial/static_assets/setup.py @@ -4,7 +4,6 @@ requires = [ 'pyramid', 'pyramid_chameleon', 'waitress', - ] setup(name='tutorial', diff --git a/docs/quick_tutorial/templating/setup.py b/docs/quick_tutorial/templating/setup.py index 1699d9b1e..744612371 100644 --- a/docs/quick_tutorial/templating/setup.py +++ b/docs/quick_tutorial/templating/setup.py @@ -4,7 +4,6 @@ requires = [ 'pyramid', 'pyramid_chameleon', 'waitress', - ] setup(name='tutorial', diff --git a/docs/quick_tutorial/tutorial_approach.rst b/docs/quick_tutorial/tutorial_approach.rst index 7d87b6a49..83b229746 100644 --- a/docs/quick_tutorial/tutorial_approach.rst +++ b/docs/quick_tutorial/tutorial_approach.rst @@ -23,14 +23,14 @@ below: .. code-block:: text quick_tutorial - │── env - `── request_response - `── tutorial - │ │── __init__.py - │ │── tests.py - │ `── views.py - │── development.ini - `── setup.py + ├── env + └── request_response + ├── tutorial + │ ├── __init__.py + │ ├── tests.py + │ └── views.py + ├── development.ini + └── setup.py Each of the directories in our ``quick_tutorial`` workspace (e.g., ``request_response``) is a *Python project* (except as noted for the ``hello_world`` step). The ``tutorial`` diff --git a/docs/quick_tutorial/view_classes/setup.py b/docs/quick_tutorial/view_classes/setup.py index 1699d9b1e..744612371 100644 --- a/docs/quick_tutorial/view_classes/setup.py +++ b/docs/quick_tutorial/view_classes/setup.py @@ -4,7 +4,6 @@ requires = [ 'pyramid', 'pyramid_chameleon', 'waitress', - ] setup(name='tutorial', diff --git a/docs/typographical-conventions.rst b/docs/typographical-conventions.rst index f128effea..2bfe4ffa6 100644 --- a/docs/typographical-conventions.rst +++ b/docs/typographical-conventions.rst @@ -217,18 +217,6 @@ The version in which a feature is deprecated in a project is displayed as follow Use the ``require_csrf`` option or read :ref:`auto_csrf_checking` instead to have :class:`pyramid.exceptions.BadCSRFToken` exceptions raised. -.. _typographical-conventions-danger: - -Danger ------- - -Danger represents critical information related to a topic or concept, and should recommend to the user "don't do this dangerous thing". - -.. danger:: - - This is danger or an error. - - .. _typographical-conventions-warnings: Warnings @@ -265,18 +253,6 @@ See also See :ref:`Quick Tutorial section on Requirements <qtut_requirements>`. -.. _typographical-conventions-todo: - -Todo ----- - -Todo items designated tasks that require further work. - -.. todo:: - - This is a todo item. - - .. _typographical-conventions-cross-references: Cross-references diff --git a/pyramid/config/__init__.py b/pyramid/config/__init__.py index a34f0b4db..886eec0df 100644 --- a/pyramid/config/__init__.py +++ b/pyramid/config/__init__.py @@ -790,21 +790,6 @@ class Configurator( action_state = self.action_state - if route_prefix is None: - route_prefix = '' - - old_route_prefix = self.route_prefix - if old_route_prefix is None: - old_route_prefix = '' - - route_prefix = '%s/%s' % ( - old_route_prefix.rstrip('/'), - route_prefix.lstrip('/') - ) - route_prefix = route_prefix.strip('/') - if not route_prefix: - route_prefix = None - c = self.maybe_dotted(callable) module = self.inspect.getmodule(c) if module is c: @@ -825,20 +810,22 @@ class Configurator( if action_state.processSpec(spec): - configurator = self.__class__( - registry=self.registry, - package=package_of(module), - root_package=self.root_package, - autocommit=self.autocommit, - route_prefix=route_prefix, - ) - configurator.basepath = os.path.dirname(sourcefile) - configurator.includepath = self.includepath + (spec,) - self.begin() - try: - c(configurator) - finally: - self.end() + with self.route_prefix_context(route_prefix): + configurator = self.__class__( + registry=self.registry, + package=package_of(module), + root_package=self.root_package, + autocommit=self.autocommit, + route_prefix=self.route_prefix, + ) + configurator.basepath = os.path.dirname(sourcefile) + configurator.includepath = self.includepath + (spec,) + + self.begin() + try: + c(configurator) + finally: + self.end() def add_directive(self, name, directive, action_wrap=True): """ diff --git a/pyramid/config/routes.py b/pyramid/config/routes.py index 203baa128..598af300f 100644 --- a/pyramid/config/routes.py +++ b/pyramid/config/routes.py @@ -1,3 +1,4 @@ +import contextlib import warnings from pyramid.compat import urlparse @@ -467,3 +468,53 @@ class RoutesConfiguratorMixin(object): self.registry.registerUtility(mapper, IRoutesMapper) return mapper + @contextlib.contextmanager + def route_prefix_context(self, route_prefix): + """ Return this configurator with the + :attr:`pyramid.config.Configurator.route_prefix` attribute mutated to + include the new ``route_prefix``. + + When the context exits, the ``route_prefix`` is reset to the original. + + Example Usage: + + >>> config = Configurator() + >>> with config.route_prefix_context('foo'): + ... config.add_route('bar', '/bar') + + Arguments + + route_prefix + + A string suitable to be used as a route prefix, or ``None``. + + .. versionadded:: 1.10 + """ + + original_route_prefix = self.route_prefix + + if route_prefix is None: + route_prefix = '' + + old_route_prefix = self.route_prefix + if old_route_prefix is None: + old_route_prefix = '' + + route_prefix = '{}/{}'.format( + old_route_prefix.rstrip('/'), + route_prefix.lstrip('/'), + ) + + route_prefix = route_prefix.strip('/') + + if not route_prefix: + route_prefix = None + + self.begin() + try: + self.route_prefix = route_prefix + yield + + finally: + self.route_prefix = original_route_prefix + self.end() diff --git a/pyramid/httpexceptions.py b/pyramid/httpexceptions.py index 1f3934fdc..734080434 100644 --- a/pyramid/httpexceptions.py +++ b/pyramid/httpexceptions.py @@ -606,7 +606,8 @@ class HTTPClientError(HTTPError): a bug. A server-side traceback is not warranted. Unless specialized, this is a '400 Bad Request' """ - pass + code = 400 + title = 'Bad Request' class HTTPBadRequest(HTTPClientError): """ @@ -617,8 +618,6 @@ class HTTPBadRequest(HTTPClientError): code: 400, title: Bad Request """ - code = 400 - title = 'Bad Request' explanation = ('The server could not comply with the request since ' 'it is either malformed or otherwise incorrect.') @@ -1032,11 +1031,18 @@ class HTTPServerError(HTTPError): This is an error condition in which the server is presumed to be in-error. Unless specialized, this is a '500 Internal Server Error'. """ - pass - -class HTTPInternalServerError(HTTPServerError): code = 500 title = 'Internal Server Error' + +class HTTPInternalServerError(HTTPServerError): + """ + subclass of :class:`~HTTPServerError` + + This indicates that the server encountered an unexpected condition + which prevented it from fulfilling the request. + + code: 500, title: Internal Server Error + """ explanation = ( 'The server has either erred or is incapable of performing ' 'the requested operation.') @@ -1150,6 +1156,7 @@ for name, value in list(globals().items()): if ( isinstance(value, class_types) and issubclass(value, HTTPException) and + value not in {HTTPClientError, HTTPServerError} and not name.startswith('_') ): code = getattr(value, 'code', None) diff --git a/pyramid/tests/test_url.py b/pyramid/tests/test_url.py index af2e5405c..31b3dd571 100644 --- a/pyramid/tests/test_url.py +++ b/pyramid/tests/test_url.py @@ -1251,6 +1251,70 @@ class Test_external_static_url_integration(unittest.TestCase): request.route_url('acme', foo='bar'), 'http://acme.org/bar') +class Test_with_route_prefix(unittest.TestCase): + + def setUp(self): + self.config = testing.setUp() + + def tearDown(self): + testing.tearDown() + + def _makeRequest(self, route): + from pyramid.request import Request + return Request.blank(route) + + def test_old_route_is_preserved(self): + self.config.route_prefix = 'old_prefix' + with self.config.route_prefix_context('new_addon'): + assert 'new_addon' in self.config.route_prefix + + assert 'old_prefix' == self.config.route_prefix + + def test_route_prefix_none(self): + self.config.route_prefix = 'old_prefix' + with self.config.route_prefix_context(None): + assert 'old_prefix' == self.config.route_prefix + + assert 'old_prefix' == self.config.route_prefix + + def test_route_prefix_empty(self): + self.config.route_prefix = 'old_prefix' + with self.config.route_prefix_context(''): + assert 'old_prefix' == self.config.route_prefix + + assert 'old_prefix' == self.config.route_prefix + + def test_route_has_prefix(self): + with self.config.route_prefix_context('bar'): + self.config.add_route('acme', '/foo') + request = self._makeRequest('/') + self.assertEqual( + request.route_url('acme'), + 'http://localhost/bar/foo', + ) + + def test_route_does_not_have_prefix(self): + with self.config.route_prefix_context('bar'): + pass + + self.config.add_route('acme', '/foo') + request = self._makeRequest('/') + self.assertEqual( + request.route_url('acme'), + 'http://localhost/foo', + ) + + def test_error_reset_prefix(self): + self.config.route_prefix = 'old_prefix' + + try: + with self.config.route_prefix_context('new_prefix'): + raise RuntimeError + except RuntimeError: + pass + + assert self.config.route_prefix == 'old_prefix' + class DummyContext(object): def __init__(self, next=None): self.next = next |
