summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAmos Latteier <amos@latteier.com>2016-06-30 10:17:43 -0700
committerAmos Latteier <amos@latteier.com>2016-06-30 10:17:43 -0700
commit35209e4ac53520e1159bd8a6b47128f38a75db18 (patch)
tree85b0f7e8e3794d8980dd10c10b7e57a862e694aa
parent3fd41dcb5a94a73f43862f6d5c063af7b54e6ff3 (diff)
parentbcba92d1b23745d736bdcbc9b799929de382db7b (diff)
downloadpyramid-35209e4ac53520e1159bd8a6b47128f38a75db18.tar.gz
pyramid-35209e4ac53520e1159bd8a6b47128f38a75db18.tar.bz2
pyramid-35209e4ac53520e1159bd8a6b47128f38a75db18.zip
Merge branch 'master' into exception_only
-rw-r--r--CHANGES.txt10
-rw-r--r--CONTRIBUTORS.txt8
-rw-r--r--README.rst31
-rw-r--r--contributing.md44
-rw-r--r--docs/designdefense.rst2
-rw-r--r--docs/narr/security.rst8
-rw-r--r--docs/narr/urldispatch.rst2
-rw-r--r--docs/quick_tour.rst30
-rw-r--r--docs/quick_tour/json/hello_world.jinja22
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/models/__init__.py2
-rw-r--r--docs/quick_tour/static_assets/hello_world.jinja22
-rw-r--r--docs/quick_tour/static_assets/hello_world_static.jinja22
-rw-r--r--docs/tutorials/wiki2/src/authentication/tutorial/models/__init__.py4
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/models/__init__.py4
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/models/__init__.py2
-rw-r--r--docs/tutorials/wiki2/src/installation/tutorial/models/__init__.py2
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/models/__init__.py4
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/models/__init__.py4
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/models/__init__.py4
-rw-r--r--pyramid/config/views.py4
-rw-r--r--pyramid/scaffolds/__init__.py16
-rw-r--r--pyramid/scaffolds/alchemy/+package+/models/__init__.py_tmpl2
-rw-r--r--pyramid/scripts/pserve.py408
-rw-r--r--pyramid/tests/test_scripts/test_pserve.py195
24 files changed, 129 insertions, 663 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index c0681053d..4fdd7bf2f 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -4,6 +4,16 @@ unreleased
Backward Incompatibilities
--------------------------
+ - Following the Pyramid deprecation period (1.6 -> 1.8),
+ daemon support for pserve has been removed. This includes removing the
+ daemon commands (start, stop, restart, status) as well as the following
+ arguments:
+ --daemon --pid-file --log-file --monitor-restart --status --user --group
+ --stop-daemon
+
+ To run your server as a daemon you should use a process manager instead of
+ pserve.
+
Features
--------
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index 762793d0b..25ccf6838 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -270,3 +270,11 @@ Contributors
- Vincent Férotin, 2016/05/08
- Berker Peksag, 2016/05/16
+
+- Zack Brunson, 2016/06/02
+
+- Marco Martinez, 2016/06/02
+
+- Cris Ewing, 2016/06/03
+
+- Jean-Christophe Bohin, 2016/06/13 \ No newline at end of file
diff --git a/README.rst b/README.rst
index dec7e03d0..35c335d9c 100644
--- a/README.rst
+++ b/README.rst
@@ -17,11 +17,28 @@ Pyramid
:target: https://webchat.freenode.net/?channels=pyramid
:alt: IRC Freenode
-Pyramid is a small, fast, down-to-earth, open source Python web framework. It
-makes real-world web application development and deployment more fun, more
-predictable, and more productive.
+`Pyramid <https://trypyramid.com/>`_ is a small, fast, down-to-earth, open
+source Python web framework. It makes real-world web application development
+and deployment more fun, more predictable, and more productive.
-Pyramid is produced by the `Pylons Project <http://pylonsproject.org/>`_.
+.. code-block:: python
+
+ from wsgiref.simple_server import make_server
+ from pyramid.config import Configurator
+ from pyramid.response import Response
+
+ def hello_world(request):
+ 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()
+ server = make_server('0.0.0.0', 8080, app)
+ server.serve_forever()
+
+Pyramid is a project of the `Pylons Project <http://www.pylonsproject.org/>`_.
Support and Documentation
-------------------------
@@ -46,6 +63,6 @@ Pyramid is offered under the BSD-derived `Repoze Public License
Authors
-------
-Pyramid is made available by `Agendaless Consulting <http://agendaless.com>`_
-and a team of contributors.
-
+Pyramid is made available by `Agendaless Consulting <https://agendaless.com>`_
+and a team of `contributors
+<https://github.com/Pylons/pyramid/graphs/contributors>`_.
diff --git a/contributing.md b/contributing.md
index ae201d370..b5f17ae06 100644
--- a/contributing.md
+++ b/contributing.md
@@ -56,11 +56,15 @@ System](http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/install.ht
git clone git@github.com:<username>/pyramid.git
-3. Add a git remote "upstream" for the cloned fork.
+3. Change directories into the cloned repository
+
+ cd pyramid
+
+4. Add a git remote "upstream" for the cloned fork.
git remote add upstream git@github.com:Pylons/pyramid.git
-4. Set an environment variable as instructed in the
+5. Create a virtual environment and set an environment variable as instructed in the
[prerequisites](https://github.com/Pylons/pyramid/blob/master/HACKING.txt#L55-L58).
# Mac and Linux
@@ -69,36 +73,34 @@ System](http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/install.ht
# Windows
set VENV=c:\hack-on-pyramid\env
-5. Try to build the docs in your workspace.
+6. Install `tox` into your virtual environment.
- # Mac and Linux
- $ make clean html SPHINXBUILD=$VENV/bin/sphinx-build
+ $ $VENV/bin/pip install tox
- # Windows
- c:\> make clean html SPHINXBUILD=%VENV%\bin\sphinx-build
+7. Try to build the docs in your workspace.
+
+ $ $VENV/bin/tox -e docs
- If successful, then you can make changes to the documentation. You can
- load the built documentation in the `/_build/html/` directory in a web
- browser.
+ When the build finishes, you'll find HTML documentation rendered in
+ `.tox/docs/html`. An `epub` version will be in `.tox/docs/epub`. And the
+ result of the tests that are run on the documentation will be in
+ `.tox/docs/doctest`.
-6. From this point forward, follow the typical [git
+8. From this point forward, follow the typical [git
workflow](https://help.github.com/articles/what-is-a-good-git-workflow/).
- Start by pulling from the upstream to get the most current changes.
+ *Always* start by pulling from the upstream to get the most current changes.
git pull upstream master
-7. Make a branch, make changes to the docs, and rebuild them as indicated in
- step 5. To speed up the build process, you can omit `clean` from the above
- command to rebuild only those pages that depend on the files you have
- changed.
+9. Make a branch, make changes to the docs, and rebuild them as indicated above.
-8. Once you are satisfied with your changes and the documentation builds
- successfully without errors or warnings, then git commit and push them to
- your "origin" repository on GitHub.
+10. Once you are satisfied with your changes and the documentation builds
+ successfully without errors or warnings, then git commit and push them to
+ your "origin" repository on GitHub.
git commit -m "commit message"
git push -u origin --all # first time only, subsequent can be just 'git push'.
-9. Create a [pull request](https://help.github.com/articles/using-pull-requests/).
+11. Create a [pull request](https://help.github.com/articles/using-pull-requests/).
-10. Repeat the process starting from Step 6.
+12. Repeat the process starting from Step 8.
diff --git a/docs/designdefense.rst b/docs/designdefense.rst
index f42582e47..5f65671bb 100644
--- a/docs/designdefense.rst
+++ b/docs/designdefense.rst
@@ -1009,7 +1009,7 @@ Microframeworks have smaller Hello World programs
Self-described "microframeworks" exist. `Bottle
<http://bottlepy.org/docs/dev/index.html>`_ and `Flask
<http://flask.pocoo.org/>`_ are two that are becoming popular. `Bobo
-<http://bobo.digicool.com/en/latest/>`_ doesn't describe itself as a
+<https://bobo.readthedocs.io/en/latest/>`_ doesn't describe itself as a
microframework, but its intended user base is much the same. Many others exist.
We've even (only as a teaching tool, not as any sort of official project)
`created one using Pyramid <http://static.repoze.org/casts/videotags.html>`_.
diff --git a/docs/narr/security.rst b/docs/narr/security.rst
index 7cbea113c..77e7fd707 100644
--- a/docs/narr/security.rst
+++ b/docs/narr/security.rst
@@ -290,6 +290,14 @@ properties of the instance.
def __init__(self, owner):
self.owner = owner
+.. warning::
+
+ Writing ``__acl__`` as properties is discouraged because an
+ ``AttributeError`` occurring in ``fget`` or ``fset`` will be silently
+ dismissed (this is consistent with Python ``getattr`` and ``hasattr``
+ behaviors). For dynamic ACLs, simply use callables, as documented above.
+
+
.. index::
single: ACE
single: access control entry
diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst
index 2472ace31..7d37c04df 100644
--- a/docs/narr/urldispatch.rst
+++ b/docs/narr/urldispatch.rst
@@ -557,7 +557,7 @@ Here is an example of a corresponding ``mypackage.views`` module:
@view_config(route_name='idea')
def idea_view(request):
- return Response(request.matchdict['id'])
+ return Response(request.matchdict['idea'])
@view_config(route_name='user')
def user_view(request):
diff --git a/docs/quick_tour.rst b/docs/quick_tour.rst
index b170e5d98..dde91b495 100644
--- a/docs/quick_tour.rst
+++ b/docs/quick_tour.rst
@@ -1,3 +1,4 @@
+
.. _quick_tour:
=====================
@@ -70,6 +71,7 @@ step. Here's a tiny application in Pyramid:
.. literalinclude:: quick_tour/hello_world/app.py
:linenos:
+ :language: python
This simple example is easy to run. Save this as ``app.py`` and run it:
@@ -120,6 +122,7 @@ library for request and response handling. In our example above, Pyramid hands
Let's see some features of requests and responses in action:
.. literalinclude:: quick_tour/requests/app.py
+ :language: python
:pyobject: hello_world
In this Pyramid view, we get the URL being visited from ``request.url``. Also
@@ -159,6 +162,7 @@ Let's move the views out to their own ``views.py`` module and change the
First our revised ``app.py``:
.. literalinclude:: quick_tour/views/app.py
+ :language: python
:linenos:
We added some more routes, but we also removed the view code. Our views and
@@ -169,6 +173,7 @@ We now have a ``views.py`` module that is focused on handling requests and
responses:
.. literalinclude:: quick_tour/views/views.py
+ :language: python
:linenos:
We have four views, each leading to the other. If you start at
@@ -214,6 +219,7 @@ What if we want part of the URL to be available as data in my view? We can use
this route declaration, for example:
.. literalinclude:: quick_tour/routing/app.py
+ :language: python
:linenos:
:lines: 6
:lineno-start: 6
@@ -222,6 +228,7 @@ With this, URLs such as ``/howdy/amy/smith`` will assign ``amy`` to ``first``
and ``smith`` to ``last``. We can then use this data in our view:
.. literalinclude:: quick_tour/routing/views.py
+ :language: python
:linenos:
:lines: 5-8
:lineno-start: 5
@@ -260,6 +267,7 @@ With the package installed, we can include the template bindings into our
configuration in ``app.py``:
.. literalinclude:: quick_tour/templating/app.py
+ :language: python
:linenos:
:lines: 6-8
:lineno-start: 6
@@ -268,6 +276,7 @@ configuration in ``app.py``:
Now lets change our ``views.py`` file:
.. literalinclude:: quick_tour/templating/views.py
+ :language: python
:linenos:
:emphasize-lines: 4,6
@@ -304,6 +313,7 @@ With the package installed, we can include the template bindings into our
configuration:
.. literalinclude:: quick_tour/jinja2/app.py
+ :language: python
:linenos:
:lines: 6-8
:lineno-start: 6
@@ -312,6 +322,7 @@ configuration:
The only change in our view is to point the renderer at the ``.jinja2`` file:
.. literalinclude:: quick_tour/jinja2/views.py
+ :language: python
:linenos:
:lines: 4-6
:lineno-start: 4
@@ -339,9 +350,10 @@ Static assets
Of course the Web is more than just markup. You need static assets: CSS, JS,
and images. Let's point our web app at a directory from which Pyramid will
serve some static assets. First let's make another call to the
-:term:`configurator`:
+:term:`configurator` in ``app.py``:
.. literalinclude:: quick_tour/static_assets/app.py
+ :language: python
:linenos:
:lines: 6-8
:lineno-start: 6
@@ -359,7 +371,7 @@ Next make a directory named ``static``, and place ``app.css`` inside:
All we need to do now is point to it in the ``<head>`` of our Jinja2 template,
``hello_world.jinja2``:
-.. literalinclude:: quick_tour/static_assets/hello_world.jinja2
+.. literalinclude:: quick_tour/static_assets/hello_world_static.jinja2
:language: jinja
:linenos:
:lines: 4-6
@@ -371,16 +383,16 @@ the site is later moved under ``/somesite/static/``? Or perhaps a web developer
changes the arrangement on disk? Pyramid provides a helper to allow flexibility
on URL generation:
-.. literalinclude:: quick_tour/static_assets/hello_world_static.jinja2
+.. literalinclude:: quick_tour/static_assets/hello_world.jinja2
:language: jinja
:linenos:
:lines: 4-6
:lineno-start: 4
:emphasize-lines: 2
-By using ``request.static_url`` to generate the full URL to the static
-assets, you both ensure you stay in sync with the configuration and
-gain refactoring flexibility later.
+By using ``request.static_url`` to generate the full URL to the static assets,
+you ensure that you stay in sync with the configuration and gain refactoring
+flexibility later.
.. seealso:: See also:
:ref:`Quick Tutorial Static Assets <qtut_static_assets>`,
@@ -396,6 +408,7 @@ to update the UI in the browser by requesting server data as JSON. Pyramid
supports this with a JSON renderer:
.. literalinclude:: quick_tour/json/views.py
+ :language: python
:linenos:
:lines: 9-
:lineno-start: 9
@@ -408,6 +421,7 @@ We also need to add a route to ``app.py`` so that our app will know how to
respond to a request for ``hello.json``.
.. literalinclude:: quick_tour/json/app.py
+ :language: python
:linenos:
:lines: 6-8
:lineno-start: 6
@@ -437,6 +451,7 @@ The following shows a "Hello World" example with three operations: view a form,
save a change, or press the delete button in our ``views.py``:
.. literalinclude:: quick_tour/view_classes/views.py
+ :language: python
:linenos:
:lines: 7-
:lineno-start: 7
@@ -728,6 +743,7 @@ Our unit test passed, although its coverage is incomplete. What did our test
look like?
.. literalinclude:: quick_tour/package/hello_world/tests.py
+ :language: python
:linenos:
Pyramid supplies helpers for test writing, which we use in the test setup and
@@ -881,6 +897,7 @@ SQLAlchemy uses "models" for this mapping. The scaffold generated a sample
model:
.. literalinclude:: quick_tour/sqla_demo/sqla_demo/models/mymodel.py
+ :language: python
:start-after: Start Sphinx Include
:end-before: End Sphinx Include
@@ -888,6 +905,7 @@ View code, which mediates the logic between web requests and the rest of the
system, can then easily get at the data thanks to SQLAlchemy:
.. literalinclude:: quick_tour/sqla_demo/sqla_demo/views/default.py
+ :language: python
:start-after: Start Sphinx Include
:end-before: End Sphinx Include
diff --git a/docs/quick_tour/json/hello_world.jinja2 b/docs/quick_tour/json/hello_world.jinja2
index 4fb9be074..a55865231 100644
--- a/docs/quick_tour/json/hello_world.jinja2
+++ b/docs/quick_tour/json/hello_world.jinja2
@@ -2,7 +2,7 @@
<html lang="en">
<head>
<title>Hello World</title>
- <link rel="stylesheet" href="{{ request.static_url('static/app.css') }}"/>
+ <link rel="stylesheet" href="{{ request.static_url('__main__:static/app.css') }}"/>
</head>
<body>
<h1>Hello {{ name }}!</h1>
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 6ffc10a78..76e0fd26b 100644
--- a/docs/quick_tour/sqla_demo/sqla_demo/models/__init__.py
+++ b/docs/quick_tour/sqla_demo/sqla_demo/models/__init__.py
@@ -1,7 +1,7 @@
from sqlalchemy.orm import configure_mappers
# import all models classes here for sqlalchemy mappers
# to pick up
-from .mymodel import MyModel # flake8: noqa
+from .mymodel import MyModel # noqa
# run configure mappers to ensure we avoid any race conditions
configure_mappers()
diff --git a/docs/quick_tour/static_assets/hello_world.jinja2 b/docs/quick_tour/static_assets/hello_world.jinja2
index 0fb2ce296..a55865231 100644
--- a/docs/quick_tour/static_assets/hello_world.jinja2
+++ b/docs/quick_tour/static_assets/hello_world.jinja2
@@ -2,7 +2,7 @@
<html lang="en">
<head>
<title>Hello World</title>
- <link rel="stylesheet" href="/static/app.css"/>
+ <link rel="stylesheet" href="{{ request.static_url('__main__:static/app.css') }}"/>
</head>
<body>
<h1>Hello {{ name }}!</h1>
diff --git a/docs/quick_tour/static_assets/hello_world_static.jinja2 b/docs/quick_tour/static_assets/hello_world_static.jinja2
index 4fb9be074..0fb2ce296 100644
--- a/docs/quick_tour/static_assets/hello_world_static.jinja2
+++ b/docs/quick_tour/static_assets/hello_world_static.jinja2
@@ -2,7 +2,7 @@
<html lang="en">
<head>
<title>Hello World</title>
- <link rel="stylesheet" href="{{ request.static_url('static/app.css') }}"/>
+ <link rel="stylesheet" href="/static/app.css"/>
</head>
<body>
<h1>Hello {{ name }}!</h1>
diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/authentication/tutorial/models/__init__.py
index a8871f6f5..8147052ad 100644
--- a/docs/tutorials/wiki2/src/authentication/tutorial/models/__init__.py
+++ b/docs/tutorials/wiki2/src/authentication/tutorial/models/__init__.py
@@ -5,8 +5,8 @@ import zope.sqlalchemy
# import or define all models here to ensure they are attached to the
# Base.metadata prior to any initialization routines
-from .page import Page # flake8: noqa
-from .user import User # flake8: noqa
+from .page import Page # noqa
+from .user import User # noqa
# run configure_mappers after defining all of the models to ensure
# all relationships can be setup
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/authorization/tutorial/models/__init__.py
index a8871f6f5..8147052ad 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/models/__init__.py
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/models/__init__.py
@@ -5,8 +5,8 @@ import zope.sqlalchemy
# import or define all models here to ensure they are attached to the
# Base.metadata prior to any initialization routines
-from .page import Page # flake8: noqa
-from .user import User # flake8: noqa
+from .page import Page # noqa
+from .user import User # noqa
# run configure_mappers after defining all of the models to ensure
# all relationships can be setup
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/models/__init__.py
index 48a957ecb..3fc82cfba 100644
--- a/docs/tutorials/wiki2/src/basiclayout/tutorial/models/__init__.py
+++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/models/__init__.py
@@ -5,7 +5,7 @@ import zope.sqlalchemy
# import or define all models here to ensure they are attached to the
# Base.metadata prior to any initialization routines
-from .mymodel import MyModel # flake8: noqa
+from .mymodel import MyModel # noqa
# run configure_mappers after defining all of the models to ensure
# all relationships can be setup
diff --git a/docs/tutorials/wiki2/src/installation/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/installation/tutorial/models/__init__.py
index 48a957ecb..3fc82cfba 100644
--- a/docs/tutorials/wiki2/src/installation/tutorial/models/__init__.py
+++ b/docs/tutorials/wiki2/src/installation/tutorial/models/__init__.py
@@ -5,7 +5,7 @@ import zope.sqlalchemy
# import or define all models here to ensure they are attached to the
# Base.metadata prior to any initialization routines
-from .mymodel import MyModel # flake8: noqa
+from .mymodel import MyModel # noqa
# run configure_mappers after defining all of the models to ensure
# all relationships can be setup
diff --git a/docs/tutorials/wiki2/src/models/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/models/tutorial/models/__init__.py
index a8871f6f5..8147052ad 100644
--- a/docs/tutorials/wiki2/src/models/tutorial/models/__init__.py
+++ b/docs/tutorials/wiki2/src/models/tutorial/models/__init__.py
@@ -5,8 +5,8 @@ import zope.sqlalchemy
# import or define all models here to ensure they are attached to the
# Base.metadata prior to any initialization routines
-from .page import Page # flake8: noqa
-from .user import User # flake8: noqa
+from .page import Page # noqa
+from .user import User # noqa
# run configure_mappers after defining all of the models to ensure
# all relationships can be setup
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/tests/tutorial/models/__init__.py
index a8871f6f5..8147052ad 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/models/__init__.py
+++ b/docs/tutorials/wiki2/src/tests/tutorial/models/__init__.py
@@ -5,8 +5,8 @@ import zope.sqlalchemy
# import or define all models here to ensure they are attached to the
# Base.metadata prior to any initialization routines
-from .page import Page # flake8: noqa
-from .user import User # flake8: noqa
+from .page import Page # noqa
+from .user import User # noqa
# run configure_mappers after defining all of the models to ensure
# all relationships can be setup
diff --git a/docs/tutorials/wiki2/src/views/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/views/tutorial/models/__init__.py
index a8871f6f5..8147052ad 100644
--- a/docs/tutorials/wiki2/src/views/tutorial/models/__init__.py
+++ b/docs/tutorials/wiki2/src/views/tutorial/models/__init__.py
@@ -5,8 +5,8 @@ import zope.sqlalchemy
# import or define all models here to ensure they are attached to the
# Base.metadata prior to any initialization routines
-from .page import Page # flake8: noqa
-from .user import User # flake8: noqa
+from .page import Page # noqa
+from .user import User # noqa
# run configure_mappers after defining all of the models to ensure
# all relationships can be setup
diff --git a/pyramid/config/views.py b/pyramid/config/views.py
index b13885833..e341922d3 100644
--- a/pyramid/config/views.py
+++ b/pyramid/config/views.py
@@ -899,6 +899,7 @@ class ViewsConfiguratorMixin(object):
# __no_permission_required__ handled by _secure_view
derived_view = self._derive_view(
view,
+ route_name=route_name,
permission=permission,
predicates=preds,
attr=attr,
@@ -1348,7 +1349,7 @@ class ViewsConfiguratorMixin(object):
def _derive_view(self, view, permission=None, predicates=(),
attr=None, renderer=None, wrapper_viewname=None,
viewname=None, accept=None, order=MAX_ORDER,
- phash=DEFAULT_PHASH, decorator=None,
+ phash=DEFAULT_PHASH, decorator=None, route_name=None,
mapper=None, http_cache=None, context=None,
require_csrf=None, extra_options=None):
view = self.maybe_dotted(view)
@@ -1378,6 +1379,7 @@ class ViewsConfiguratorMixin(object):
decorator=decorator,
http_cache=http_cache,
require_csrf=require_csrf,
+ route_name=route_name
)
if extra_options:
options.update(extra_options)
diff --git a/pyramid/scaffolds/__init__.py b/pyramid/scaffolds/__init__.py
index 4e811a42b..62c3eeecc 100644
--- a/pyramid/scaffolds/__init__.py
+++ b/pyramid/scaffolds/__init__.py
@@ -35,11 +35,10 @@ class PyramidTemplate(Template):
msg = dedent(
"""
%(separator)s
- Tutorials: http://docs.pylonsproject.org/projects/pyramid_tutorials
- Documentation: http://docs.pylonsproject.org/projects/pyramid
-
- Twitter (tips & updates): http://twitter.com/pylons
- Mailing List: http://groups.google.com/group/pylons-discuss
+ Tutorials: http://docs.pylonsproject.org/projects/pyramid_tutorials/en/latest/
+ Documentation: http://docs.pylonsproject.org/projects/pyramid/en/latest/
+ Twitter: https://twitter.com/trypyramid
+ Mailing List: https://groups.google.com/forum/#!forum/pylons-discuss
Welcome to Pyramid. Sorry for the convenience.
%(separator)s
@@ -53,12 +52,13 @@ class PyramidTemplate(Template):
class StarterProjectTemplate(PyramidTemplate):
_template_dir = 'starter'
- summary = 'Pyramid starter project'
+ summary = 'Pyramid starter project using URL dispatch and Chameleon'
class ZODBProjectTemplate(PyramidTemplate):
_template_dir = 'zodb'
- summary = 'Pyramid ZODB project using traversal'
+ summary = 'Pyramid project using ZODB, traversal, and Chameleon'
class AlchemyProjectTemplate(PyramidTemplate):
_template_dir = 'alchemy'
- summary = 'Pyramid SQLAlchemy project using url dispatch'
+ summary = 'Pyramid project using SQLAlchemy, SQLite, URL dispatch, and'
+ ' Chameleon'
diff --git a/pyramid/scaffolds/alchemy/+package+/models/__init__.py_tmpl b/pyramid/scaffolds/alchemy/+package+/models/__init__.py_tmpl
index 26b50aaf6..f626d1ef0 100644
--- a/pyramid/scaffolds/alchemy/+package+/models/__init__.py_tmpl
+++ b/pyramid/scaffolds/alchemy/+package+/models/__init__.py_tmpl
@@ -5,7 +5,7 @@ import zope.sqlalchemy
# import or define all models here to ensure they are attached to the
# Base.metadata prior to any initialization routines
-from .mymodel import MyModel # flake8: noqa
+from .mymodel import MyModel # noqa
# run configure_mappers after defining all of the models to ensure
# all relationships can be setup
diff --git a/pyramid/scripts/pserve.py b/pyramid/scripts/pserve.py
index 74bda1dce..ec7f31704 100644
--- a/pyramid/scripts/pserve.py
+++ b/pyramid/scripts/pserve.py
@@ -10,8 +10,6 @@
import atexit
import ctypes
-import errno
-import logging
import optparse
import os
import py_compile
@@ -59,20 +57,13 @@ def main(argv=sys.argv, quiet=False):
command = PServeCommand(argv, quiet=quiet)
return command.run()
-class DaemonizeException(Exception):
- pass
-
class PServeCommand(object):
- usage = '%prog config_uri [start|stop|restart|status] [var=value]'
+ usage = '%prog config_uri [var=value]'
description = """\
This command serves a web application that uses a PasteDeploy
configuration file for the server and application.
- If start/stop/restart is given, then --daemon is implied, and it will
- start (normal operation), stop (--stop-daemon), or do both.
- Note: Daemonization features are deprecated.
-
You can also include variable assignments like 'http_port=8080'
and then use %(http_port)s in your config files.
"""
@@ -98,23 +89,6 @@ class PServeCommand(object):
metavar='SECTION_NAME',
help=("Use the named server as defined in the configuration file "
"(default: main)"))
- if hasattr(os, 'fork'):
- parser.add_option(
- '--daemon',
- dest="daemon",
- action="store_true",
- help="Run in daemon (background) mode [DEPRECATED]")
- parser.add_option(
- '--pid-file',
- dest='pid_file',
- metavar='FILENAME',
- help=("Save PID to file (default to pyramid.pid if running in "
- "daemon mode) [DEPRECATED]"))
- parser.add_option(
- '--log-file',
- dest='log_file',
- metavar='LOG_FILE',
- help="Save output to the given log file (redirects stdout) [DEPRECATED]")
parser.add_option(
'--reload',
dest='reload',
@@ -127,22 +101,11 @@ class PServeCommand(object):
help=("Seconds between checking files (low number can cause "
"significant CPU usage)"))
parser.add_option(
- '--monitor-restart',
- dest='monitor_restart',
- action='store_true',
- help="Auto-restart server if it dies [DEPRECATED]")
- parser.add_option(
'-b', '--browser',
dest='browser',
action='store_true',
help="Open a web browser to server url")
parser.add_option(
- '--status',
- action='store_true',
- dest='show_status',
- help=("Show the status of the (presumably daemonized) server "
- "[DEPRECATED]"))
- parser.add_option(
'-v', '--verbose',
default=default_verbosity,
dest='verbose',
@@ -155,33 +118,11 @@ class PServeCommand(object):
dest='verbose',
help="Suppress verbose output")
- if hasattr(os, 'setuid'):
- # I don't think these are available on Windows
- parser.add_option(
- '--user',
- dest='set_user',
- metavar="USERNAME",
- help="Set the user (usually only possible when run as root)")
- parser.add_option(
- '--group',
- dest='set_group',
- metavar="GROUP",
- help="Set the group (usually only possible when run as root)")
-
- parser.add_option(
- '--stop-daemon',
- dest='stop_daemon',
- action='store_true',
- help=('Stop a daemonized server (given a PID file, or default '
- 'pyramid.pid file) [DEPRECATED]'))
-
_scheme_re = re.compile(r'^[a-z][a-z]+:', re.I)
_reloader_environ_key = 'PYTHON_RELOADER_SHOULD_RUN'
_monitor_environ_key = 'PASTE_MONITOR_SHOULD_RUN'
- possible_subcommands = ('start', 'stop', 'restart', 'status')
-
def __init__(self, argv, quiet=False):
self.options, self.args = self.parser.parse_args(argv[1:])
if quiet:
@@ -192,51 +133,16 @@ class PServeCommand(object):
print(msg)
def get_options(self):
- if (
- len(self.args) > 1 and
- self.args[1] in self.possible_subcommands
- ):
- restvars = self.args[2:]
- else:
- restvars = self.args[1:]
-
+ restvars = self.args[1:]
return parse_vars(restvars)
def run(self): # pragma: no cover
- if self.options.stop_daemon:
- self._warn_daemon_deprecated()
- return self.stop_daemon()
-
- if not hasattr(self.options, 'set_user'):
- # Windows case:
- self.options.set_user = self.options.set_group = None
-
- # @@: Is this the right stage to set the user at?
- if self.options.set_user or self.options.set_group:
- self.change_user_group(
- self.options.set_user, self.options.set_group)
-
if not self.args:
self.out('You must give a config file')
return 2
app_spec = self.args[0]
- if (
- len(self.args) > 1 and
- self.args[1] in self.possible_subcommands
- ):
- cmd = self.args[1]
- else:
- cmd = None
-
if self.options.reload:
- if (
- getattr(self.options, 'daemon', False) or
- cmd in ('start', 'stop', 'restart')
- ):
- self.out(
- 'Error: Cannot use reloading while running as a dameon.')
- return 2
if os.environ.get(self._reloader_environ_key):
if self.options.verbose > 1:
self.out('Running reloading file monitor')
@@ -246,31 +152,6 @@ class PServeCommand(object):
else:
return self.restart_with_reloader()
- if cmd not in (None, 'start', 'stop', 'restart', 'status'):
- self.out(
- 'Error: must give start|stop|restart (not %s)' % cmd)
- return 2
-
- if cmd == 'status' or self.options.show_status:
- self._warn_daemon_deprecated()
- return self.show_status()
-
- if cmd in ('restart', 'stop'):
- self._warn_daemon_deprecated()
- result = self.stop_daemon()
- if result:
- if cmd == 'restart':
- self.out("Could not stop daemon; aborting")
- else:
- self.out("Could not stop daemon")
- return result
- if cmd == 'stop':
- return result
- self.options.daemon = True
-
- if cmd == 'start':
- self.options.daemon = True
-
app_name = self.options.app_name
vars = self.get_options()
@@ -286,75 +167,6 @@ class PServeCommand(object):
server_spec = app_spec
base = os.getcwd()
- # warn before setting a default
- if self.options.pid_file or self.options.log_file:
- self._warn_daemon_deprecated()
-
- if getattr(self.options, 'daemon', False):
- if not self.options.pid_file:
- self.options.pid_file = 'pyramid.pid'
- if not self.options.log_file:
- self.options.log_file = 'pyramid.log'
-
- # Ensure the log file is writeable
- if self.options.log_file:
- try:
- writeable_log_file = open(self.options.log_file, 'a')
- except IOError as ioe:
- msg = 'Error: Unable to write to log file: %s' % ioe
- raise ValueError(msg)
- writeable_log_file.close()
-
- # Ensure the pid file is writeable
- if self.options.pid_file:
- try:
- writeable_pid_file = open(self.options.pid_file, 'a')
- except IOError as ioe:
- msg = 'Error: Unable to write to pid file: %s' % ioe
- raise ValueError(msg)
- writeable_pid_file.close()
-
- # warn before forking
- if (
- self.options.monitor_restart and
- not os.environ.get(self._monitor_environ_key)
- ):
- self.out('''\
---monitor-restart has been deprecated in Pyramid 1.6. It will be removed
-in a future release per Pyramid's deprecation policy. Please consider using
-a real process manager for your processes like Systemd, Circus, or Supervisor.
-''')
-
- if (
- getattr(self.options, 'daemon', False) and
- not os.environ.get(self._monitor_environ_key)
- ):
- self._warn_daemon_deprecated()
- try:
- self.daemonize()
- except DaemonizeException as ex:
- if self.options.verbose > 0:
- self.out(str(ex))
- return 2
-
- if (
- not os.environ.get(self._monitor_environ_key) and
- self.options.pid_file
- ):
- self.record_pid(self.options.pid_file)
-
- if (
- self.options.monitor_restart and
- not os.environ.get(self._monitor_environ_key)
- ):
- return self.restart_with_monitor()
-
- if self.options.log_file:
- stdout_log = LazyWriter(self.options.log_file, 'a')
- sys.stdout = stdout_log
- sys.stderr = stdout_log
- logging.basicConfig(stream=stdout_log)
-
log_fn = app_spec
if log_fn.startswith('config:'):
log_fn = app_spec[len('config:'):]
@@ -442,141 +254,6 @@ a real process manager for your processes like Systemd, Circus, or Supervisor.
name += '.exe'
return name
- def daemonize(self): # pragma: no cover
- pid = live_pidfile(self.options.pid_file)
- if pid:
- raise DaemonizeException(
- "Daemon is already running (PID: %s from PID file %s)"
- % (pid, self.options.pid_file))
-
- if self.options.verbose > 0:
- self.out('Entering daemon mode')
- pid = os.fork()
- if pid:
- # The forked process also has a handle on resources, so we
- # *don't* want proper termination of the process, we just
- # want to exit quick (which os._exit() does)
- os._exit(0)
- # Make this the session leader
- os.setsid()
- # Fork again for good measure!
- pid = os.fork()
- if pid:
- os._exit(0)
-
- # @@: Should we set the umask and cwd now?
-
- import resource # Resource usage information.
- maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
- if (maxfd == resource.RLIM_INFINITY):
- maxfd = MAXFD
- # Iterate through and close all file descriptors.
- for fd in range(0, maxfd):
- try:
- os.close(fd)
- except OSError: # ERROR, fd wasn't open to begin with (ignored)
- pass
-
- if (hasattr(os, "devnull")):
- REDIRECT_TO = os.devnull
- else:
- REDIRECT_TO = "/dev/null"
- os.open(REDIRECT_TO, os.O_RDWR) # standard input (0)
- # Duplicate standard input to standard output and standard error.
- os.dup2(0, 1) # standard output (1)
- os.dup2(0, 2) # standard error (2)
-
- def _remove_pid_file(self, written_pid, filename, verbosity):
- current_pid = os.getpid()
- if written_pid != current_pid:
- # A forked process must be exiting, not the process that
- # wrote the PID file
- return
- if not os.path.exists(filename):
- return
- with open(filename) as f:
- content = f.read().strip()
- try:
- pid_in_file = int(content)
- except ValueError:
- pass
- else:
- if pid_in_file != current_pid:
- msg = "PID file %s contains %s, not expected PID %s"
- self.out(msg % (filename, pid_in_file, current_pid))
- return
- if verbosity > 0:
- self.out("Removing PID file %s" % filename)
- try:
- os.unlink(filename)
- return
- except OSError as e:
- # Record, but don't give traceback
- self.out("Cannot remove PID file: (%s)" % e)
- # well, at least lets not leave the invalid PID around...
- try:
- with open(filename, 'w') as f:
- f.write('')
- except OSError as e:
- self.out('Stale PID left in file: %s (%s)' % (filename, e))
- else:
- self.out('Stale PID removed')
-
- def record_pid(self, pid_file):
- pid = os.getpid()
- if self.options.verbose > 1:
- self.out('Writing PID %s to %s' % (pid, pid_file))
- with open(pid_file, 'w') as f:
- f.write(str(pid))
- atexit.register(self._remove_pid_file, pid, pid_file, self.options.verbose)
-
- def stop_daemon(self): # pragma: no cover
- pid_file = self.options.pid_file or 'pyramid.pid'
- if not os.path.exists(pid_file):
- self.out('No PID file exists in %s' % pid_file)
- return 1
- pid = read_pidfile(pid_file)
- if not pid:
- self.out("Not a valid PID file in %s" % pid_file)
- return 1
- pid = live_pidfile(pid_file)
- if not pid:
- self.out("PID in %s is not valid (deleting)" % pid_file)
- try:
- os.unlink(pid_file)
- except (OSError, IOError) as e:
- self.out("Could not delete: %s" % e)
- return 2
- return 1
- for j in range(10):
- if not live_pidfile(pid_file):
- break
- import signal
- kill(pid, signal.SIGTERM)
- time.sleep(1)
- else:
- self.out("failed to kill web process %s" % pid)
- return 3
- if os.path.exists(pid_file):
- os.unlink(pid_file)
- return 0
-
- def show_status(self): # pragma: no cover
- pid_file = self.options.pid_file or 'pyramid.pid'
- if not os.path.exists(pid_file):
- self.out('No PID file %s' % pid_file)
- return 1
- pid = read_pidfile(pid_file)
- if not pid:
- self.out('No PID in file %s' % pid_file)
- return 1
- pid = live_pidfile(pid_file)
- if not pid:
- self.out('PID %s in %s is not running' % (pid, pid_file))
- return 1
- self.out('Server running in PID %s' % pid)
- return 0
-
def restart_with_reloader(self): # pragma: no cover
self.restart_with_monitor(reloader=True)
@@ -624,60 +301,6 @@ a real process manager for your processes like Systemd, Circus, or Supervisor.
if self.options.verbose > 0:
self.out('%s %s %s' % ('-' * 20, 'Restarting', '-' * 20))
- def change_user_group(self, user, group): # pragma: no cover
- import pwd
- import grp
-
- self.out('''\
-The --user and --group options have been deprecated in Pyramid 1.6. They will
-be removed in a future release per Pyramid's deprecation policy. Please
-consider using a real process manager for your processes like Systemd, Circus,
-or Supervisor, all of which support process security.
-''')
-
- uid = gid = None
- if group:
- try:
- gid = int(group)
- group = grp.getgrgid(gid).gr_name
- except ValueError:
- import grp
- try:
- entry = grp.getgrnam(group)
- except KeyError:
- raise ValueError(
- "Bad group: %r; no such group exists" % group)
- gid = entry.gr_gid
- try:
- uid = int(user)
- user = pwd.getpwuid(uid).pw_name
- except ValueError:
- try:
- entry = pwd.getpwnam(user)
- except KeyError:
- raise ValueError(
- "Bad username: %r; no such user exists" % user)
- if not gid:
- gid = entry.pw_gid
- uid = entry.pw_uid
- if self.options.verbose > 0:
- self.out('Changing user to %s:%s (%s:%s)' % (
- user, group or '(unknown)', uid, gid))
- if gid:
- os.setgid(gid)
- if uid:
- os.setuid(uid)
-
- def _warn_daemon_deprecated(self):
- self.out('''\
-The daemon options have been deprecated in Pyramid 1.6. They will be removed
-in a future release per Pyramid's deprecation policy. Please consider using
-a real process manager for your processes like Systemd, Circus, or Supervisor.
-
-The following commands are deprecated:
- [start,stop,restart,status] --daemon, --stop-server, --status, --pid-file, --log-file
-''')
-
class LazyWriter(object):
"""
@@ -718,33 +341,6 @@ class LazyWriter(object):
def flush(self):
self.open().flush()
-def live_pidfile(pidfile): # pragma: no cover
- """(pidfile:str) -> int | None
- Returns an int found in the named file, if there is one,
- and if there is a running process with that process id.
- Return None if no such process exists.
- """
- pid = read_pidfile(pidfile)
- if pid:
- try:
- kill(int(pid), 0)
- return pid
- except OSError as e:
- if e.errno == errno.EPERM:
- return pid
- return None
-
-def read_pidfile(filename):
- if os.path.exists(filename):
- try:
- with open(filename) as f:
- content = f.read()
- return int(content.strip())
- except (ValueError, IOError):
- return None
- else:
- return None
-
def ensure_port_cleanup(
bound_addresses, maxtries=30, sleeptime=2): # pragma: no cover
"""
diff --git a/pyramid/tests/test_scripts/test_pserve.py b/pyramid/tests/test_scripts/test_pserve.py
index bc21665aa..bf4763602 100644
--- a/pyramid/tests/test_scripts/test_pserve.py
+++ b/pyramid/tests/test_scripts/test_pserve.py
@@ -1,23 +1,11 @@
-import atexit
import os
import tempfile
import unittest
-from pyramid.compat import PY2
-if PY2:
- import __builtin__
-else:
- import builtins as __builtin__
-
class TestPServeCommand(unittest.TestCase):
def setUp(self):
from pyramid.compat import NativeIO
self.out_ = NativeIO()
- self.pid_file = None
-
- def tearDown(self):
- if self.pid_file and os.path.exists(self.pid_file):
- os.remove(self.pid_file)
def out(self, msg):
self.out_.write(msg)
@@ -39,172 +27,12 @@ class TestPServeCommand(unittest.TestCase):
cmd.out = self.out
return cmd
- def _makeOneWithPidFile(self, pid):
- self.pid_file = tempfile.mktemp()
- inst = self._makeOne()
- with open(self.pid_file, 'w') as f:
- f.write(str(pid))
- return inst
-
- def test_remove_pid_file_verbose(self):
- inst = self._makeOneWithPidFile(os.getpid())
- inst._remove_pid_file(os.getpid(), self.pid_file, verbosity=1)
- self._assert_pid_file_removed(verbose=True)
-
- def test_remove_pid_file_not_verbose(self):
- inst = self._makeOneWithPidFile(os.getpid())
- inst._remove_pid_file(os.getpid(), self.pid_file, verbosity=0)
- self._assert_pid_file_removed(verbose=False)
-
- def test_remove_pid_not_a_number(self):
- inst = self._makeOneWithPidFile('not a number')
- inst._remove_pid_file(os.getpid(), self.pid_file, verbosity=1)
- self._assert_pid_file_removed(verbose=True)
-
- def test_remove_pid_current_pid_is_not_written_pid(self):
- inst = self._makeOneWithPidFile(os.getpid())
- inst._remove_pid_file('99999', self.pid_file, verbosity=1)
- self._assert_pid_file_not_removed('')
-
- def test_remove_pid_current_pid_is_not_pid_in_file(self):
- inst = self._makeOneWithPidFile('99999')
- inst._remove_pid_file(os.getpid(), self.pid_file, verbosity=1)
- msg = 'PID file %s contains 99999, not expected PID %s'
- self._assert_pid_file_not_removed(msg % (self.pid_file, os.getpid()))
-
- def test_remove_pid_no_pid_file(self):
- inst = self._makeOne()
- self.pid_file = 'some unknown path'
- inst._remove_pid_file(os.getpid(), self.pid_file, verbosity=1)
- self._assert_pid_file_removed(verbose=False)
-
- def test_remove_pid_file_unlink_exception(self):
- inst = self._makeOneWithPidFile(os.getpid())
- self._remove_pid_unlink_exception(inst)
- msg = [
- 'Removing PID file %s' % (self.pid_file),
- 'Cannot remove PID file: (Some OSError - unlink)',
- 'Stale PID removed']
- self._assert_pid_file_not_removed(msg=''.join(msg))
- with open(self.pid_file) as f:
- self.assertEqual(f.read(), '')
-
- def test_remove_pid_file_stale_pid_write_exception(self):
- inst = self._makeOneWithPidFile(os.getpid())
- self._remove_pid_unlink_and_write_exceptions(inst)
- msg = [
- 'Removing PID file %s' % (self.pid_file),
- 'Cannot remove PID file: (Some OSError - unlink)',
- 'Stale PID left in file: %s ' % (self.pid_file),
- '(Some OSError - open)']
- self._assert_pid_file_not_removed(msg=''.join(msg))
- with open(self.pid_file) as f:
- self.assertEqual(int(f.read()), os.getpid())
-
- def test_record_pid_verbose(self):
- self._assert_record_pid(verbosity=2, msg='Writing PID %d to %s')
-
- def test_record_pid_not_verbose(self):
- self._assert_record_pid(verbosity=1, msg='')
-
- def _remove_pid_unlink_exception(self, inst):
- old_unlink = os.unlink
- def fake_unlink(filename):
- raise OSError('Some OSError - unlink')
-
- try:
- os.unlink = fake_unlink
- inst._remove_pid_file(os.getpid(), self.pid_file, verbosity=1)
- finally:
- os.unlink = old_unlink
-
- def _remove_pid_unlink_and_write_exceptions(self, inst):
- old_unlink = os.unlink
- def fake_unlink(filename):
- raise OSError('Some OSError - unlink')
-
- run_already = []
- old_open = __builtin__.open
- def fake_open(*args):
- if not run_already:
- run_already.append(True)
- return old_open(*args)
- raise OSError('Some OSError - open')
-
- try:
- os.unlink = fake_unlink
- __builtin__.open = fake_open
- inst._remove_pid_file(os.getpid(), self.pid_file, verbosity=1)
- finally:
- os.unlink = old_unlink
- __builtin__.open = old_open
-
- def _assert_pid_file_removed(self, verbose=False):
- self.assertFalse(os.path.exists(self.pid_file))
- msg = 'Removing PID file %s' % (self.pid_file) if verbose else ''
- self.assertEqual(self.out_.getvalue(), msg)
-
- def _assert_pid_file_not_removed(self, msg):
- self.assertTrue(os.path.exists(self.pid_file))
- self.assertEqual(self.out_.getvalue(), msg)
-
- def _assert_record_pid(self, verbosity, msg):
- old_atexit = atexit.register
- def fake_atexit(*args):
- pass
-
- self.pid_file = tempfile.mktemp()
- pid = os.getpid()
- inst = self._makeOne()
- inst.options.verbose = verbosity
-
- try:
- atexit.register = fake_atexit
- inst.record_pid(self.pid_file)
- finally:
- atexit.register = old_atexit
-
- msg = msg % (pid, self.pid_file) if msg else ''
- self.assertEqual(self.out_.getvalue(), msg)
- with open(self.pid_file) as f:
- self.assertEqual(int(f.read()), pid)
-
def test_run_no_args(self):
inst = self._makeOne()
result = inst.run()
self.assertEqual(result, 2)
self.assertEqual(self.out_.getvalue(), 'You must give a config file')
- def test_run_stop_daemon_no_such_pid_file(self):
- path = os.path.join(os.path.dirname(__file__), 'wontexist.pid')
- inst = self._makeOne('--stop-daemon', '--pid-file=%s' % path)
- inst.run()
- msg = 'No PID file exists in %s' % path
- self.assertTrue(msg in self.out_.getvalue())
-
- def test_run_stop_daemon_bad_pid_file(self):
- path = __file__
- inst = self._makeOne('--stop-daemon', '--pid-file=%s' % path)
- inst.run()
- msg = 'Not a valid PID file in %s' % path
- self.assertTrue(msg in self.out_.getvalue())
-
- def test_run_stop_daemon_invalid_pid_in_file(self):
- fn = tempfile.mktemp()
- with open(fn, 'wb') as tmp:
- tmp.write(b'9999999')
- tmp.close()
- inst = self._makeOne('--stop-daemon', '--pid-file=%s' % fn)
- inst.run()
- msg = 'PID in %s is not valid (deleting)' % fn
- self.assertTrue(msg in self.out_.getvalue())
-
- def test_get_options_with_command(self):
- inst = self._makeOne()
- inst.args = ['foo', 'stop', 'a=1', 'b=2']
- result = inst.get_options()
- self.assertEqual(result, {'a': '1', 'b': '2'})
-
def test_get_options_no_command(self):
inst = self._makeOne()
inst.args = ['foo', 'a=1', 'b=2']
@@ -233,29 +61,6 @@ class TestPServeCommand(unittest.TestCase):
inst.loadserver = self._get_server
self.assertRaises(ValueError, inst.run)
-class Test_read_pidfile(unittest.TestCase):
- def _callFUT(self, filename):
- from pyramid.scripts.pserve import read_pidfile
- return read_pidfile(filename)
-
- def test_read_pidfile(self):
- filename = tempfile.mktemp()
- try:
- with open(filename, 'w') as f:
- f.write('12345')
- result = self._callFUT(filename)
- self.assertEqual(result, 12345)
- finally:
- os.remove(filename)
-
- def test_read_pidfile_no_pid_file(self):
- result = self._callFUT('some unknown path')
- self.assertEqual(result, None)
-
- def test_read_pidfile_not_a_number(self):
- result = self._callFUT(__file__)
- self.assertEqual(result, None)
-
class Test_main(unittest.TestCase):
def _callFUT(self, argv):
from pyramid.scripts.pserve import main