View

Large diffs are not rendered by default.

Oops, something went wrong.
View
@@ -18,6 +18,7 @@ require permission, instead of a default "403 Forbidden" page.
We will implement the access control with the following steps:
* Add password hashing dependencies.
* Add users and groups (``security.py``, a new module).
* Add an :term:`ACL` (``models.py``).
* Add an :term:`authentication policy` and an :term:`authorization policy`
@@ -38,11 +39,32 @@ Then we will add the login and logout feature:
Access control
--------------
Add dependencies
~~~~~~~~~~~~~~~~
Just like in :ref:`wiki_defining_views`, we need a new dependency. We need to add the `bcrypt <https://pypi.python.org/pypi/bcrypt>`_ package, to our tutorial package's ``setup.py`` file by assigning this dependency to the ``requires`` parameter in the ``setup()`` function.
Open ``setup.py`` and edit it to look like the following:
.. literalinclude:: src/authorization/setup.py
:linenos:
:emphasize-lines: 21
:language: python
Only the highlighted line needs to be added.
Do not forget to run ``pip install -e .`` just like in :ref:`wiki-running-pip-install`.
.. note::
We are using the ``bcrypt`` package from PyPI to hash our passwords securely. There are other one-way hash algorithms for passwords if bcrypt is an issue on your system. Just make sure that it's an algorithm approved for storing passwords versus a generic one-way hash.
Add users and groups
~~~~~~~~~~~~~~~~~~~~
Create a new ``tutorial/security.py`` module with the
following content:
Create a new ``tutorial/security.py`` module with the following content:
.. literalinclude:: src/authorization/tutorial/security.py
:linenos:
@@ -61,7 +83,20 @@ request)`` returns ``None``. We will use ``groupfinder()`` as an
:term:`authentication policy` "callback" that will provide the
:term:`principal` or principals for a user.
In a production system, user and group data will most often come from a
There are two helper methods that will help us later to authenticate users.
The first is ``hash_password`` which takes a raw password and transforms it using
bcrypt into an irreversible representation, a process known as "hashing". The
second method, ``check_password``, will allow us to compare the hashed value of the
submitted password against the hashed value of the password stored in the user's
record. If the two hashed values match, then the submitted
password is valid, and we can authenticate the user.
We hash passwords so that it is impossible to decrypt and use them to
authenticate in the application. If we stored passwords foolishly in clear text,
then anyone with access to the database could retrieve any password to authenticate
as any user.
In a production system, user and group data will most often be saved and come from a
database, but here we use "dummy" data to represent user and groups sources.
Add an ACL
View
@@ -52,6 +52,7 @@ Open ``setup.py`` and edit it to look like the following:
Only the highlighted line needs to be added.
.. _wiki-running-pip-install:
Running ``pip install -e .``
============================
View
@@ -18,6 +18,7 @@
'ZODB3',
'waitress',
'docutils',
'bcrypt',
]
tests_require = [
View
@@ -1,5 +1,18 @@
USERS = {'editor':'editor',
'viewer':'viewer'}
import bcrypt
def hash_password(pw):
hashed_pw = bcrypt.hashpw(pw.encode('utf-8'), bcrypt.gensalt())
# return unicode instead of bytes because databases handle it better
return hashed_pw.decode('utf-8')
def check_password(expected_hash, pw):
if expected_hash is not None:
return bcrypt.checkpw(pw.encode('utf-8'), expected_hash.encode('utf-8'))
return False
USERS = {'editor': hash_password('editor'),
'viewer': hash_password('viewer')}
GROUPS = {'editor':['group:editors']}
def groupfinder(userid, request):
View
@@ -14,7 +14,7 @@
)
from .security import USERS
from .security import USERS, check_password
from .models import Page
# regular expression used to find WikiWords
@@ -94,7 +94,7 @@ def login(request):
if 'form.submitted' in request.params:
login = request.params['login']
password = request.params['password']
if USERS.get(login) == password:
if check_password(USERS.get(login), password):
headers = remember(request, login)
return HTTPFound(location=came_from,
headers=headers)
View
@@ -18,6 +18,7 @@
'ZODB3',
'waitress',
'docutils',
'bcrypt',
]
tests_require = [
View
@@ -1,5 +1,18 @@
USERS = {'editor':'editor',
'viewer':'viewer'}
import bcrypt
def hash_password(pw):
hashed_pw = bcrypt.hashpw(pw.encode('utf-8'), bcrypt.gensalt())
# return unicode instead of bytes because databases handle it better
return hashed_pw.decode('utf-8')
def check_password(expected_hash, pw):
if expected_hash is not None:
return bcrypt.checkpw(pw.encode('utf-8'), expected_hash.encode('utf-8'))
return False
USERS = {'editor': hash_password('editor'),
'viewer': hash_password('viewer')}
GROUPS = {'editor':['group:editors']}
def groupfinder(userid, request):
View
@@ -122,6 +122,17 @@ def test_it_submitted(self):
self.assertEqual(response.location, 'http://example.com/')
self.assertEqual(context.data, 'Hello yo!')
class SecurityTests(unittest.TestCase):
def test_hashing(self):
from .security import hash_password, check_password
password = 'secretpassword'
hashed_password = hash_password(password)
self.assertTrue(check_password(hashed_password, password))
self.assertFalse(check_password(hashed_password, 'attackerpassword'))
self.assertFalse(check_password(None, password))
class FunctionalTests(unittest.TestCase):
viewer_login = '/login?login=viewer&password=viewer' \
View
@@ -14,7 +14,7 @@
)
from .security import USERS
from .security import USERS, check_password
from .models import Page
# regular expression used to find WikiWords
@@ -94,7 +94,7 @@ def login(request):
if 'form.submitted' in request.params:
login = request.params['login']
password = request.params['password']
if USERS.get(login) == password:
if check_password(USERS.get(login), password):
headers = remember(request, login)
return HTTPFound(location=came_from,
headers=headers)
View
@@ -28,6 +28,7 @@ def usage(argv):
def main(argv=sys.argv):
if len(argv) < 2:
usage(argv)
return
config_uri = argv[1]
options = parse_vars(argv[2:])
setup_logging(config_uri)
View
@@ -11,6 +11,9 @@ class FunctionalTests(unittest.TestCase):
basic_wrong_login = (
'/login?login=basic&password=incorrect'
'&next=FrontPage&form.submitted=Login')
basic_login_no_next = (
'/login?login=basic&password=basic'
'&form.submitted=Login')
editor_login = (
'/login?login=editor&password=editor'
'&next=FrontPage&form.submitted=Login')
@@ -68,6 +71,10 @@ def test_successful_log_in(self):
res = self.testapp.get(self.basic_login, status=302)
self.assertEqual(res.location, 'http://localhost/FrontPage')
def test_successful_log_in_no_next(self):
res = self.testapp.get(self.basic_login_no_next, status=302)
self.assertEqual(res.location, 'http://localhost/')
def test_failed_log_in(self):
res = self.testapp.get(self.basic_wrong_login, status=200)
self.assertTrue(b'login' in res.body)
@@ -120,3 +127,8 @@ def test_editors_member_user_can_view(self):
self.testapp.get(self.editor_login, status=302)
res = self.testapp.get('/FrontPage', status=200)
self.assertTrue(b'FrontPage' in res.body)
def test_redirect_to_edit_for_existing_page(self):
self.testapp.get(self.editor_login, status=302)
res = self.testapp.get('/add_page/FrontPage', status=302)
self.assertTrue(b'FrontPage' in res.body)
View
@@ -0,0 +1,20 @@
import mock
import unittest
class TestInitializeDB(unittest.TestCase):
@mock.patch('tutorial.scripts.initializedb.sys')
def test_usage(self, mocked_sys):
from ..scripts.initializedb import main
main(argv=['foo'])
mocked_sys.exit.assert_called_with(1)
@mock.patch('tutorial.scripts.initializedb.get_tm_session')
@mock.patch('tutorial.scripts.initializedb.sys')
def test_run(self, mocked_sys, mocked_session):
from ..scripts.initializedb import main
main(argv=['foo', 'development.ini'])
mocked_session.assert_called_once()
View
@@ -0,0 +1,21 @@
import mock
import unittest
class TestMyAuthenticationPolicy(unittest.TestCase):
def test_no_user(self):
request = mock.Mock()
request.user = None
from ..security import MyAuthenticationPolicy
policy = MyAuthenticationPolicy(None)
self.assertEqual(policy.authenticated_userid(request), None)
def test_authenticated_user(self):
request = mock.Mock()
request.user.id = 'foo'
from ..security import MyAuthenticationPolicy
policy = MyAuthenticationPolicy(None)
self.assertEqual(policy.authenticated_userid(request), 'foo')
View
@@ -0,0 +1,67 @@
import unittest
import transaction
from pyramid import testing
class BaseTest(unittest.TestCase):
def setUp(self):
from ..models import get_tm_session
self.config = testing.setUp(settings={
'sqlalchemy.url': 'sqlite:///:memory:'
})
self.config.include('..models')
self.config.include('..routes')
session_factory = self.config.registry['dbsession_factory']
self.session = get_tm_session(session_factory, transaction.manager)
self.init_database()
def init_database(self):
from ..models.meta import Base
session_factory = self.config.registry['dbsession_factory']
engine = session_factory.kw['bind']
Base.metadata.create_all(engine)
def tearDown(self):
testing.tearDown()
transaction.abort()
def makeUser(self, name, role):
from ..models import User
return User(name=name, role=role)
class TestSetPassword(BaseTest):
def test_password_hash_saved(self):
user = self.makeUser(name='foo', role='bar')
self.assertFalse(user.password_hash)
user.set_password('secret')
self.assertTrue(user.password_hash)
class TestCheckPassword(BaseTest):
def test_password_hash_not_set(self):
user = self.makeUser(name='foo', role='bar')
self.assertFalse(user.password_hash)
self.assertFalse(user.check_password('secret'))
def test_correct_password(self):
user = self.makeUser(name='foo', role='bar')
user.set_password('secret')
self.assertTrue(user.password_hash)
self.assertTrue(user.check_password('secret'))
def test_incorrect_password(self):
user = self.makeUser(name='foo', role='bar')
user.set_password('secret')
self.assertTrue(user.password_hash)
self.assertFalse(user.check_password('incorrect'))
View
@@ -0,0 +1,338 @@
.. _typographical-conventions:
Typographical Conventions
=========================
.. meta::
:description: This chapter describes typographical conventions used in the Pyramid documentation.
:keywords: Pyramid, Typographical Conventions
.. _typographical-conventions-introduction:
Introduction
------------
This chapter describes typographical conventions used in the Pyramid documentation. Documentation authors and contributors should review the :ref:`style-guide`.
.. _typographical-conventions-glossary:
Glossary
--------
A glossary defines terms used throughout the documentation. References to glossary terms appear as follows.
:term:`request`
Note it is hyperlinked, and when clicked it will take the user to the term in the Glossary and highlight the term.
.. _typographical-conventions-links:
Links
-----
Links are presented as follows, and may be clickable.
`TryPyramid <https://TryPyramid.com>`_
.. seealso:: See also :ref:`typographical-conventions-cross-references` for other links within the documentation.
.. _typographical-conventions-topic:
Topic
-----
A topic is similar to a block quote with a title, or a self-contained section with no subsections. A topic indicates a self-contained idea that is separate from the flow of the document. Topics may occur anywhere a section or transition may occur.
.. topic:: Topic Title
Subsequent indented lines comprise
the body of the topic, and are
interpreted as body elements.
.. _typographical-conventions-displaying-code:
Code
----
Code may be displayed in blocks or inline. Blocks of code may use syntax highlighting, line numbering, and emphasis.
.. _typographical-conventions-syntax-highlighting:
Syntax highlighting
^^^^^^^^^^^^^^^^^^^
XML:
.. code-block:: xml
<somesnippet>Some XML</somesnippet>
Unix shell commands are prefixed with a ``$`` character. (See :term:`venv` for the meaning of ``$VENV``.)
.. code-block:: bash
$ $VENV/bin/pip install -e .
Windows commands are prefixed with a drive letter with an optional directory name. (See :term:`venv` for the meaning of ``%VENV%``.)
.. code-block:: doscon
c:\> %VENV%\Scripts\pcreate -s starter MyProject
cfg:
.. code-block:: cfg
[some-part]
# A random part in the buildout
recipe = collective.recipe.foo
option = value
ini:
.. code-block:: ini
[nosetests]
match=^test
where=pyramid
nocapture=1
Interactive Python:
.. code-block:: pycon
>>> class Foo:
... bar = 100
...
>>> f = Foo()
>>> f.bar
100
>>> f.bar / 0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero
.. _typographical-conventions-long-commands:
Displaying long commands
^^^^^^^^^^^^^^^^^^^^^^^^
When a command that should be typed on one line is too long to fit on the displayed width of a page, the backslash character ``\`` is used to indicate that the subsequent printed line should be part of the command:
.. code-block:: bash
$ $VENV/bin/py.test tutorial/tests.py --cov-report term-missing \
--cov=tutorial -q
.. _typographical-conventions-code-block-options:
Code block options
^^^^^^^^^^^^^^^^^^
To emphasize lines, we give the appearance that a highlighting pen has been used on the code.
.. code-block:: python
:emphasize-lines: 1,3
if "foo" == "bar":
# This is Python code
pass
A code block with line numbers.
.. code-block:: python
:linenos:
if "foo" == "bar":
# This is Python code
pass
Some code blocks may be given a caption.
.. code-block:: python
:caption: sample.py
:name: sample-py-typographical-conventions
if "foo" == "bar":
# This is Python code
pass
.. _typographical-conventions-inline-code:
Inline code
^^^^^^^^^^^
Inline code is displayed as follows, where the inline code is 'pip install -e ".[docs]"'.
Install requirements for building documentation: ``pip install -e ".[docs]"``
.. _typographical-conventions-feature-versioning:
Feature versioning
------------------
We designate the version in which something is added, changed, or deprecated in the project.
.. _typographical-conventions-version-added:
Version added
^^^^^^^^^^^^^
The version in which a feature is added to a project is displayed as follows.
.. versionadded:: 1.1
:func:`pyramid.paster.bootstrap`
.. _typographical-conventions-version-changed:
Version changed
^^^^^^^^^^^^^^^
The version in which a feature is changed in a project is displayed as follows.
.. versionchanged:: 1.8
Added the ability for ``bootstrap`` to cleanup automatically via the ``with`` statement.
.. _typographical-conventions-deprecated:
Deprecated
^^^^^^^^^^
The version in which a feature is deprecated in a project is displayed as follows.
.. deprecated:: 1.7
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
--------
Warnings represent limitations and advice related to a topic or concept.
.. warning::
This is a warning.
.. _typographical-conventions-notes:
Notes
-----
Notes represent additional information related to a topic or concept.
.. note::
This is a note.
.. _typographical-conventions-see-also:
See also
--------
"See also" messages refer to topics that are related to the current topic, but have a narrative tone to them instead of merely a link without explanation. "See also" is rendered in a block as well, so that it stands out for the reader's attention.
.. seealso::
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
----------------
Cross-references are links that may be to a document, arbitrary location, object, or other items.
.. _typographical-conventions-cross-referencing-documents:
Cross-referencing documents
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Links to pages within this documentation display as follows.
:doc:`quick_tour`
.. _typographical-conventions-cross-referencing-arbitrary-locations:
Cross-referencing arbitrary locations
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Links to sections, and tables and figures with captions, within this documentation display as follows.
:ref:`i18n_chapter`
.. _typographical-conventions-cross-referencing-python:
Python modules, classes, methods, and functions
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
All of the following are clickable links to Python modules, classes, methods, and functions.
Python module names display as follows.
:mod:`pyramid.config`
Python class names display as follows.
:class:`pyramid.config.Configurator`
Python method names display as follows.
:meth:`pyramid.config.Configurator.add_view`
Python function names display as follows.
:func:`pyramid.renderers.render_to_response`
Sometimes we show only the last segment of a Python object's name, which displays as follows.
:func:`~pyramid.renderers.render_to_response`
The application "Pyramid" itself displays as follows.
:app:`Pyramid`
View
@@ -126,7 +126,7 @@ Feature Additions
- The :attr:`pyramid.tweens.EXCVIEW` tween will now re-raise the original
exception if no exception view could be found to handle it. This allows
the exception to be handled upstream by another tween or middelware.
the exception to be handled upstream by another tween or middleware.
See https://github.com/Pylons/pyramid/pull/2567
Deprecations
View
@@ -1,301 +1,2 @@
import re
from pyramid.exceptions import ConfigurationError
from pyramid.compat import is_nonstr_iter
from pyramid.traversal import (
find_interface,
traversal_path,
resource_path_tuple
)
from pyramid.urldispatch import _compile_route
from pyramid.util import object_description
from pyramid.session import check_csrf_token
from .util import as_sorted_tuple
_marker = object()
class XHRPredicate(object):
def __init__(self, val, config):
self.val = bool(val)
def text(self):
return 'xhr = %s' % self.val
phash = text
def __call__(self, context, request):
return bool(request.is_xhr) is self.val
class RequestMethodPredicate(object):
def __init__(self, val, config):
request_method = as_sorted_tuple(val)
if 'GET' in request_method and 'HEAD' not in request_method:
# GET implies HEAD too
request_method = as_sorted_tuple(request_method + ('HEAD',))
self.val = request_method
def text(self):
return 'request_method = %s' % (','.join(self.val))
phash = text
def __call__(self, context, request):
return request.method in self.val
class PathInfoPredicate(object):
def __init__(self, val, config):
self.orig = val
try:
val = re.compile(val)
except re.error as why:
raise ConfigurationError(why.args[0])
self.val = val
def text(self):
return 'path_info = %s' % (self.orig,)
phash = text
def __call__(self, context, request):
return self.val.match(request.upath_info) is not None
class RequestParamPredicate(object):
def __init__(self, val, config):
val = as_sorted_tuple(val)
reqs = []
for p in val:
k = p
v = None
if p.startswith('='):
if '=' in p[1:]:
k, v = p[1:].split('=', 1)
k = '=' + k
k, v = k.strip(), v.strip()
elif '=' in p:
k, v = p.split('=', 1)
k, v = k.strip(), v.strip()
reqs.append((k, v))
self.val = val
self.reqs = reqs
def text(self):
return 'request_param %s' % ','.join(
['%s=%s' % (x,y) if y else x for x, y in self.reqs]
)
phash = text
def __call__(self, context, request):
for k, v in self.reqs:
actual = request.params.get(k)
if actual is None:
return False
if v is not None and actual != v:
return False
return True
class HeaderPredicate(object):
def __init__(self, val, config):
name = val
v = None
if ':' in name:
name, val_str = name.split(':', 1)
try:
v = re.compile(val_str)
except re.error as why:
raise ConfigurationError(why.args[0])
if v is None:
self._text = 'header %s' % (name,)
else:
self._text = 'header %s=%s' % (name, val_str)
self.name = name
self.val = v
def text(self):
return self._text
phash = text
def __call__(self, context, request):
if self.val is None:
return self.name in request.headers
val = request.headers.get(self.name)
if val is None:
return False
return self.val.match(val) is not None
class AcceptPredicate(object):
def __init__(self, val, config):
self.val = val
def text(self):
return 'accept = %s' % (self.val,)
phash = text
def __call__(self, context, request):
return self.val in request.accept
class ContainmentPredicate(object):
def __init__(self, val, config):
self.val = config.maybe_dotted(val)
def text(self):
return 'containment = %s' % (self.val,)
phash = text
def __call__(self, context, request):
ctx = getattr(request, 'context', context)
return find_interface(ctx, self.val) is not None
class RequestTypePredicate(object):
def __init__(self, val, config):
self.val = val
def text(self):
return 'request_type = %s' % (self.val,)
phash = text
def __call__(self, context, request):
return self.val.providedBy(request)
class MatchParamPredicate(object):
def __init__(self, val, config):
val = as_sorted_tuple(val)
self.val = val
reqs = [ p.split('=', 1) for p in val ]
self.reqs = [ (x.strip(), y.strip()) for x, y in reqs ]
def text(self):
return 'match_param %s' % ','.join(
['%s=%s' % (x,y) for x, y in self.reqs]
)
phash = text
def __call__(self, context, request):
if not request.matchdict:
# might be None
return False
for k, v in self.reqs:
if request.matchdict.get(k) != v:
return False
return True
class CustomPredicate(object):
def __init__(self, func, config):
self.func = func
def text(self):
return getattr(
self.func,
'__text__',
'custom predicate: %s' % object_description(self.func)
)
def phash(self):
# using hash() here rather than id() is intentional: we
# want to allow custom predicates that are part of
# frameworks to be able to define custom __hash__
# functions for custom predicates, so that the hash output
# of predicate instances which are "logically the same"
# may compare equal.
return 'custom:%r' % hash(self.func)
def __call__(self, context, request):
return self.func(context, request)
class TraversePredicate(object):
# Can only be used as a *route* "predicate"; it adds 'traverse' to the
# matchdict if it's specified in the routing args. This causes the
# ResourceTreeTraverser to use the resolved traverse pattern as the
# traversal path.
def __init__(self, val, config):
_, self.tgenerate = _compile_route(val)
self.val = val
def text(self):
return 'traverse matchdict pseudo-predicate'
def phash(self):
# This isn't actually a predicate, it's just a infodict modifier that
# injects ``traverse`` into the matchdict. As a result, we don't
# need to update the hash.
return ''
def __call__(self, context, request):
if 'traverse' in context:
return True
m = context['match']
tvalue = self.tgenerate(m) # tvalue will be urlquoted string
m['traverse'] = traversal_path(tvalue)
# This isn't actually a predicate, it's just a infodict modifier that
# injects ``traverse`` into the matchdict. As a result, we just
# return True.
return True
class CheckCSRFTokenPredicate(object):
check_csrf_token = staticmethod(check_csrf_token) # testing
def __init__(self, val, config):
self.val = val
def text(self):
return 'check_csrf = %s' % (self.val,)
phash = text
def __call__(self, context, request):
val = self.val
if val:
if val is True:
val = 'csrf_token'
return self.check_csrf_token(request, val, raises=False)
return True
class PhysicalPathPredicate(object):
def __init__(self, val, config):
if is_nonstr_iter(val):
self.val = tuple(val)
else:
val = tuple(filter(None, val.split('/')))
self.val = ('',) + val
def text(self):
return 'physical_path = %s' % (self.val,)
phash = text
def __call__(self, context, request):
if getattr(context, '__name__', _marker) is not _marker:
return resource_path_tuple(context) == self.val
return False
class EffectivePrincipalsPredicate(object):
def __init__(self, val, config):
if is_nonstr_iter(val):
self.val = set(val)
else:
self.val = set((val,))
def text(self):
return 'effective_principals = %s' % sorted(list(self.val))
phash = text
def __call__(self, context, request):
req_principals = request.effective_principals
if is_nonstr_iter(req_principals):
rpset = set(req_principals)
if self.val.issubset(rpset):
return True
return False
import zope.deprecation
zope.deprecation.moved('pyramid.predicates', 'Pyramid 2.0')
View
@@ -13,12 +13,10 @@
from pyramid.request import route_request_iface
from pyramid.urldispatch import RoutesMapper
from pyramid.config.util import (
action_method,
as_sorted_tuple,
)
from pyramid.config.util import action_method
from pyramid.util import as_sorted_tuple
import pyramid.config.predicates
import pyramid.predicates
class RoutesConfiguratorMixin(object):
@action_method
@@ -446,7 +444,7 @@ def add_route_predicate(self, name, factory, weighs_more_than=None,
)
def add_default_route_predicates(self):
p = pyramid.config.predicates
p = pyramid.predicates
for (name, factory) in (
('xhr', p.XHRPredicate),
('request_method', p.RequestMethodPredicate),
View
@@ -9,9 +9,9 @@
PHASE2_CONFIG,
)
from pyramid.config.util import as_sorted_tuple
from pyramid.exceptions import ConfigurationError
from pyramid.util import action_method
from pyramid.util import as_sorted_tuple
class SecurityConfiguratorMixin(object):
@action_method
View
@@ -17,9 +17,9 @@
from pyramid.config.util import (
action_method,
is_string_or_iterable,
TopologicalSorter,
)
from pyramid.util import is_string_or_iterable
class TweensConfiguratorMixin(object):
def add_tween(self, tween_factory, under=None, over=None):
View
@@ -4,8 +4,7 @@
from pyramid.compat import (
bytes_,
getargspec,
is_nonstr_iter,
string_types,
is_nonstr_iter
)
from pyramid.compat import im_func
@@ -24,18 +23,6 @@
MAX_ORDER = 1 << 30
DEFAULT_PHASH = md5().hexdigest()
def is_string_or_iterable(v):
if isinstance(v, string_types):
return True
if hasattr(v, '__iter__'):
return True
def as_sorted_tuple(val):
if not is_nonstr_iter(val):
val = (val,)
val = tuple(sorted(val))
return val
class not_(object):
"""
View
@@ -70,10 +70,11 @@
from pyramid.util import (
viewdefaults,
action_method,
as_sorted_tuple,
TopologicalSorter,
)
import pyramid.config.predicates
import pyramid.predicates
import pyramid.viewderivers
from pyramid.viewderivers import (
@@ -89,7 +90,6 @@
from pyramid.config.util import (
DEFAULT_PHASH,
MAX_ORDER,
as_sorted_tuple,
)
urljoin = urlparse.urljoin
@@ -1143,7 +1143,7 @@ def add_view_predicate(self, name, factory, weighs_more_than=None,
)
def add_default_view_predicates(self):
p = pyramid.config.predicates
p = pyramid.predicates
for (name, factory) in (
('xhr', p.XHRPredicate),
('request_method', p.RequestMethodPredicate),
View
@@ -246,7 +246,7 @@ def _json_formatter(self, status, body, title, environ):
'title': self.title}
def prepare(self, environ):
if not self.body and not self.empty_body:
if not self.has_body and not self.empty_body:
html_comment = ''
comment = self.comment or ''
accept_value = environ.get('HTTP_ACCEPT', '')
View
@@ -0,0 +1,300 @@
import re
from pyramid.exceptions import ConfigurationError
from pyramid.compat import is_nonstr_iter
from pyramid.session import check_csrf_token
from pyramid.traversal import (
find_interface,
traversal_path,
resource_path_tuple
)
from pyramid.urldispatch import _compile_route
from pyramid.util import object_description
from pyramid.util import as_sorted_tuple
_marker = object()
class XHRPredicate(object):
def __init__(self, val, config):
self.val = bool(val)
def text(self):
return 'xhr = %s' % self.val
phash = text
def __call__(self, context, request):
return bool(request.is_xhr) is self.val
class RequestMethodPredicate(object):
def __init__(self, val, config):
request_method = as_sorted_tuple(val)
if 'GET' in request_method and 'HEAD' not in request_method:
# GET implies HEAD too
request_method = as_sorted_tuple(request_method + ('HEAD',))
self.val = request_method
def text(self):
return 'request_method = %s' % (','.join(self.val))
phash = text
def __call__(self, context, request):
return request.method in self.val
class PathInfoPredicate(object):
def __init__(self, val, config):
self.orig = val
try:
val = re.compile(val)
except re.error as why:
raise ConfigurationError(why.args[0])
self.val = val
def text(self):
return 'path_info = %s' % (self.orig,)
phash = text
def __call__(self, context, request):
return self.val.match(request.upath_info) is not None
class RequestParamPredicate(object):
def __init__(self, val, config):
val = as_sorted_tuple(val)
reqs = []
for p in val:
k = p
v = None
if p.startswith('='):
if '=' in p[1:]:
k, v = p[1:].split('=', 1)
k = '=' + k
k, v = k.strip(), v.strip()
elif '=' in p:
k, v = p.split('=', 1)
k, v = k.strip(), v.strip()
reqs.append((k, v))
self.val = val
self.reqs = reqs
def text(self):
return 'request_param %s' % ','.join(
['%s=%s' % (x,y) if y else x for x, y in self.reqs]
)
phash = text
def __call__(self, context, request):
for k, v in self.reqs:
actual = request.params.get(k)
if actual is None:
return False
if v is not None and actual != v:
return False
return True
class HeaderPredicate(object):
def __init__(self, val, config):
name = val
v = None
if ':' in name:
name, val_str = name.split(':', 1)
try:
v = re.compile(val_str)
except re.error as why:
raise ConfigurationError(why.args[0])
if v is None:
self._text = 'header %s' % (name,)
else:
self._text = 'header %s=%s' % (name, val_str)
self.name = name
self.val = v
def text(self):
return self._text
phash = text
def __call__(self, context, request):
if self.val is None:
return self.name in request.headers
val = request.headers.get(self.name)
if val is None:
return False
return self.val.match(val) is not None
class AcceptPredicate(object):
def __init__(self, val, config):
self.val = val
def text(self):
return 'accept = %s' % (self.val,)
phash = text
def __call__(self, context, request):
return self.val in request.accept
class ContainmentPredicate(object):
def __init__(self, val, config):
self.val = config.maybe_dotted(val)
def text(self):
return 'containment = %s' % (self.val,)
phash = text
def __call__(self, context, request):
ctx = getattr(request, 'context', context)
return find_interface(ctx, self.val) is not None
class RequestTypePredicate(object):
def __init__(self, val, config):
self.val = val
def text(self):
return 'request_type = %s' % (self.val,)
phash = text
def __call__(self, context, request):
return self.val.providedBy(request)
class MatchParamPredicate(object):
def __init__(self, val, config):
val = as_sorted_tuple(val)
self.val = val
reqs = [ p.split('=', 1) for p in val ]
self.reqs = [ (x.strip(), y.strip()) for x, y in reqs ]
def text(self):
return 'match_param %s' % ','.join(
['%s=%s' % (x,y) for x, y in self.reqs]
)
phash = text
def __call__(self, context, request):
if not request.matchdict:
# might be None
return False
for k, v in self.reqs:
if request.matchdict.get(k) != v:
return False
return True
class CustomPredicate(object):
def __init__(self, func, config):
self.func = func
def text(self):
return getattr(
self.func,
'__text__',
'custom predicate: %s' % object_description(self.func)
)
def phash(self):
# using hash() here rather than id() is intentional: we
# want to allow custom predicates that are part of
# frameworks to be able to define custom __hash__
# functions for custom predicates, so that the hash output
# of predicate instances which are "logically the same"
# may compare equal.
return 'custom:%r' % hash(self.func)
def __call__(self, context, request):
return self.func(context, request)
class TraversePredicate(object):
# Can only be used as a *route* "predicate"; it adds 'traverse' to the
# matchdict if it's specified in the routing args. This causes the
# ResourceTreeTraverser to use the resolved traverse pattern as the
# traversal path.
def __init__(self, val, config):
_, self.tgenerate = _compile_route(val)
self.val = val
def text(self):
return 'traverse matchdict pseudo-predicate'
def phash(self):
# This isn't actually a predicate, it's just a infodict modifier that
# injects ``traverse`` into the matchdict. As a result, we don't
# need to update the hash.
return ''
def __call__(self, context, request):
if 'traverse' in context:
return True
m = context['match']
tvalue = self.tgenerate(m) # tvalue will be urlquoted string
m['traverse'] = traversal_path(tvalue)
# This isn't actually a predicate, it's just a infodict modifier that
# injects ``traverse`` into the matchdict. As a result, we just
# return True.
return True
class CheckCSRFTokenPredicate(object):
check_csrf_token = staticmethod(check_csrf_token) # testing
def __init__(self, val, config):
self.val = val
def text(self):
return 'check_csrf = %s' % (self.val,)
phash = text
def __call__(self, context, request):
val = self.val
if val:
if val is True:
val = 'csrf_token'
return self.check_csrf_token(request, val, raises=False)
return True
class PhysicalPathPredicate(object):
def __init__(self, val, config):
if is_nonstr_iter(val):
self.val = tuple(val)
else:
val = tuple(filter(None, val.split('/')))
self.val = ('',) + val
def text(self):
return 'physical_path = %s' % (self.val,)
phash = text
def __call__(self, context, request):
if getattr(context, '__name__', _marker) is not _marker:
return resource_path_tuple(context) == self.val
return False
class EffectivePrincipalsPredicate(object):
def __init__(self, val, config):
if is_nonstr_iter(val):
self.val = set(val)
else:
self.val = set((val,))
def text(self):
return 'effective_principals = %s' % sorted(list(self.val))
phash = text
def __call__(self, context, request):
req_principals = request.effective_principals
if is_nonstr_iter(req_principals):
rpset = set(req_principals)
if self.val.issubset(rpset):
return True
return False
View
@@ -81,7 +81,7 @@ def write_files(self, command, output_dir, vars):
template_dir = self.template_dir()
if not self.exists(output_dir):
self.out("Creating directory %s" % output_dir)
if not command.options.simulate:
if not command.args.simulate:
# Don't let copydir create this top-level directory,
# since copydir will svn add it sometimes:
self.makedirs(output_dir)
@@ -90,9 +90,9 @@ def write_files(self, command, output_dir, vars):
output_dir,
vars,
verbosity=command.verbosity,
simulate=command.options.simulate,
interactive=command.options.interactive,
overwrite=command.options.overwrite,
simulate=command.args.simulate,
interactive=command.args.interactive,
overwrite=command.args.overwrite,
indent=1,
template_renderer=self.render_template,
)
View
@@ -2,7 +2,7 @@
# (http://pythonpaste.org) Licensed under the MIT license:
# http://www.opensource.org/licenses/mit-license.php
import optparse
import argparse
import os
import os.path
import pkg_resources
@@ -12,6 +12,7 @@
_bad_chars_re = re.compile('[^a-zA-Z0-9_]')
def main(argv=sys.argv, quiet=False):
command = PCreateCommand(argv, quiet)
try:
@@ -21,73 +22,77 @@ def main(argv=sys.argv, quiet=False):
class PCreateCommand(object):
verbosity = 1 # required
description = "Render Pyramid scaffolding to an output directory"
usage = "usage: %prog [options] -s <scaffold> output_directory"
parser = optparse.OptionParser(usage, description=description)
parser.add_option('-s', '--scaffold',
dest='scaffold_name',
action='append',
help=("Add a scaffold to the create process "
"(multiple -s args accepted)"))
parser.add_option('-t', '--template',
dest='scaffold_name',
action='append',
help=('A backwards compatibility alias for '
'-s/--scaffold. Add a scaffold to the '
'create process (multiple -t args accepted)'))
parser.add_option('-l', '--list',
dest='list',
action='store_true',
help="List all available scaffold names")
parser.add_option('--list-templates',
dest='list',
action='store_true',
help=("A backwards compatibility alias for -l/--list. "
"List all available scaffold names."))
parser.add_option('--package-name',
dest='package_name',
action='store',
type='string',
help='Package name to use. The name provided is assumed '
'to be a valid Python package name, and will not '
'be validated. By default the package name is '
'derived from the value of output_directory.')
parser.add_option('--simulate',
dest='simulate',
action='store_true',
help='Simulate but do no work')
parser.add_option('--overwrite',
dest='overwrite',
action='store_true',
help='Always overwrite')
parser.add_option('--interactive',
dest='interactive',
action='store_true',
help='When a file would be overwritten, interrogate '
'(this is the default, but you may specify it to '
'override --overwrite)')
parser.add_option('--ignore-conflicting-name',
dest='force_bad_name',
action='store_true',
default=False,
help='Do create a project even if the chosen name '
'is the name of an already existing / importable '
'package.')
verbosity = 1 # required
parser = argparse.ArgumentParser(
description="Render Pyramid scaffolding to an output directory")
parser.add_argument('-s', '--scaffold',
dest='scaffold_name',
action='append',
help=("Add a scaffold to the create process "
"(multiple -s args accepted)"))
parser.add_argument('-t', '--template',
dest='scaffold_name',
action='append',
help=('A backwards compatibility alias for '
'-s/--scaffold. Add a scaffold to the '
'create process (multiple -t args accepted)'))
parser.add_argument('-l', '--list',
dest='list',
action='store_true',
help="List all available scaffold names")
parser.add_argument('--list-templates',
dest='list',
action='store_true',
help=("A backwards compatibility alias for -l/--list. "
"List all available scaffold names."))
parser.add_argument('--package-name',
dest='package_name',
action='store',
help='Package name to use. The name provided is '
'assumed to be a valid Python package name, and '
'will not be validated. By default the package '
'name is derived from the value of '
'output_directory.')
parser.add_argument('--simulate',
dest='simulate',
action='store_true',
help='Simulate but do no work')
parser.add_argument('--overwrite',
dest='overwrite',
action='store_true',
help='Always overwrite')
parser.add_argument('--interactive',
dest='interactive',
action='store_true',
help='When a file would be overwritten, interrogate '
'(this is the default, but you may specify it to '
'override --overwrite)')
parser.add_argument('--ignore-conflicting-name',
dest='force_bad_name',
action='store_true',
default=False,
help='Do create a project even if the chosen name '
'is the name of an already existing / importable '
'package.')
parser.add_argument('output_directory',

This comment has been minimized.

@bertjwregeer

bertjwregeer Nov 29, 2016

Member

You'll need to make this optional if you want the program to behave the same as before... otherwise argparse will helpfully sys.exit() and let the user know they need to specify an output directory...

@bertjwregeer

bertjwregeer Nov 29, 2016

Member

You'll need to make this optional if you want the program to behave the same as before... otherwise argparse will helpfully sys.exit() and let the user know they need to specify an output directory...

This comment has been minimized.

@stevepiercy

stevepiercy Nov 29, 2016

Member

I think leaving output_directory as required is a more helpful behavior from the user perspective. The behavior would not change, except in the messages returned. argparse's messages seem more user friendly and informative, too.

# master
bash-3.2$ ../env35/bin/pcreate -s starter
usage: pcreate [options] -s <scaffold> output_directory
pcreate: error: the following arguments are required: output_directory

# 1.7-branch
bash-3.2$ ../env35/bin/pcreate -s starter
You must provide a project name

If it were optional, then that would imply that the program could run without an output_directory option. That in turn implies either the current working directory would be the default, or that the program be interactive and require an input. This has less appeal to me.

@stevepiercy

stevepiercy Nov 29, 2016

Member

I think leaving output_directory as required is a more helpful behavior from the user perspective. The behavior would not change, except in the messages returned. argparse's messages seem more user friendly and informative, too.

# master
bash-3.2$ ../env35/bin/pcreate -s starter
usage: pcreate [options] -s <scaffold> output_directory
pcreate: error: the following arguments are required: output_directory

# 1.7-branch
bash-3.2$ ../env35/bin/pcreate -s starter
You must provide a project name

If it were optional, then that would imply that the program could run without an output_directory option. That in turn implies either the current working directory would be the default, or that the program be interactive and require an input. This has less appeal to me.

This comment has been minimized.

@stevepiercy

stevepiercy Dec 11, 2016

Member

@bertjwregeer changes submitted, ready for review.

@stevepiercy

stevepiercy Dec 11, 2016

Member

@bertjwregeer changes submitted, ready for review.

nargs = '?',
default = None,
help='The directory where the project will be '
'created.')
pyramid_dist = pkg_resources.get_distribution("pyramid")
def __init__(self, argv, quiet=False):
self.quiet = quiet
self.options, self.args = self.parser.parse_args(argv[1:])
if not self.options.interactive and not self.options.overwrite:
self.options.interactive = True
self.args = self.parser.parse_args(argv[1:])
if not self.args.interactive and not self.args.overwrite:
self.args.interactive = True
self.scaffolds = self.all_scaffolds()
def run(self):
if self.options.list:
if self.args.list:
return self.show_scaffolds()
if not self.options.scaffold_name and not self.args:
if not self.args.scaffold_name and not self.args.output_directory:
if not self.quiet: # pragma: no cover
self.parser.print_help()
self.out('')
@@ -101,18 +106,18 @@ def run(self):
@property
def output_path(self):
return os.path.abspath(os.path.normpath(self.args[0]))
return os.path.abspath(os.path.normpath(self.args.output_directory))
@property
def project_vars(self):
output_dir = self.output_path
project_name = os.path.basename(os.path.split(output_dir)[1])
if self.options.package_name is None:
if self.args.package_name is None:
pkg_name = _bad_chars_re.sub(
'', project_name.lower().replace('-', '_'))
safe_name = pkg_resources.safe_name(project_name)
else:
pkg_name = self.options.package_name
pkg_name = self.args.package_name
safe_name = pkg_name
egg_name = pkg_resources.to_filename(safe_name)
@@ -145,7 +150,7 @@ def project_vars(self):
def render_scaffolds(self):
props = self.project_vars
output_dir = self.output_path
for scaffold_name in self.options.scaffold_name:
for scaffold_name in self.args.scaffold_name:
for scaffold in self.scaffolds:
if scaffold.name == scaffold_name:
scaffold.run(self, output_dir, props)
@@ -182,23 +187,23 @@ def out(self, msg): # pragma: no cover
print(msg)
def validate_input(self):
if not self.options.scaffold_name:
if not self.args.scaffold_name:
self.out('You must provide at least one scaffold name: -s <scaffold name>')
self.out('')
self.show_scaffolds()
return False
if not self.args:
if not self.args.output_directory:
self.out('You must provide a project name')
return False
available = [x.name for x in self.scaffolds]
diff = set(self.options.scaffold_name).difference(available)
diff = set(self.args.scaffold_name).difference(available)
if diff:
self.out('Unavailable scaffolds: %s' % ", ".join(sorted(diff)))
return False
pkg_name = self.project_vars['package']
if pkg_name == 'site' and not self.options.force_bad_name:
if pkg_name == 'site' and not self.args.force_bad_name:
self.out('The package name "site" has a special meaning in '
'Python. Are you sure you want to use it as your '
'project\'s name?')
@@ -214,7 +219,7 @@ def validate_input(self):
if not pkg_exists:
return True
if self.options.force_bad_name:
if self.args.force_bad_name:
return True
self.out('A package named "{0}" already exists, are you sure you want '
'to use it as your project\'s name?'.format(pkg_name))
View
@@ -1,7 +1,7 @@
import sys
import platform
import pkg_resources
import optparse
import argparse
from operator import itemgetter
def out(*args): # pragma: no cover
@@ -10,12 +10,15 @@ def out(*args): # pragma: no cover
sys.stdout.write(' ')
sys.stdout.write('\n')
def get_parser():
parser = argparse.ArgumentParser(
description="Show Python distribution versions and locations in use")
return parser
def main(argv=sys.argv, pkg_resources=pkg_resources, platform=platform.platform,
out=out):
# all args except argv are for unit testing purposes only
description = "Show Python distribution versions and locations in use"
usage = "usage: %prog"
parser = optparse.OptionParser(usage, description=description)
parser = get_parser()
parser.parse_args(argv[1:])
packages = []
for distribution in pkg_resources.working_set:
View
@@ -1,5 +1,5 @@
import base64
import optparse
import argparse
import sys
import textwrap
@@ -39,64 +39,81 @@ class PRequestCommand(object):
If the path is relative (doesn't begin with "/") it is interpreted as
relative to "/". The path passed to this script should be URL-quoted.
The path can be succeeded with a query string (e.g. `/path?a=1&=b2').
The path can be succeeded with a query string (e.g. '/path?a=1&=b2').
The variable "environ['paste.command_request']" will be set to "True" in
the request's WSGI environment, so your application can distinguish these
calls from normal requests.
"""
usage = "usage: %prog config_uri path_info [args/options]"
parser = optparse.OptionParser(
usage=usage,
parser = argparse.ArgumentParser(
description=textwrap.dedent(description)
)
parser.add_option(
parser.add_argument(
'-n', '--app-name',
dest='app_name',
metavar='NAME',
help=(
"Load the named application from the config file (default 'main')"
),
type="string",
)
parser.add_option(
parser.add_argument(
'--header',
dest='headers',
metavar='NAME:VALUE',
type='string',
action='append',
help=(
"Header to add to request (you can use this option multiple times)"
),
)
parser.add_option(
parser.add_argument(
'-d', '--display-headers',
dest='display_headers',
action='store_true',
help='Display status and headers before the response body'
)
parser.add_option(
parser.add_argument(
'-m', '--method',
dest='method',
choices=['GET', 'HEAD', 'POST', 'PUT', 'PATCH','DELETE',
'PROPFIND', 'OPTIONS'],
type='choice',
help='Request method type (GET, POST, PUT, PATCH, DELETE, '
'PROPFIND, OPTIONS)',
)
parser.add_option(
parser.add_argument(
'-l', '--login',
dest='login',
type='string',
help='HTTP basic auth username:password pair',
)
parser.add_argument(
'config_uri',
nargs='?',
default=None,
help='The URI to the configuration file.',
)
parser.add_argument(
'path_info',
nargs='?',
default=None,
help='The path of the request.',
)
parser.add_argument(
'config_vars',
nargs='*',
default=(),
help='Arbitrary options to override those in the [app:main] section '
'of the configuration file.',
)
get_app = staticmethod(get_app)
stdin = sys.stdin
def __init__(self, argv, quiet=False):
self.quiet = quiet
self.options, self.args = self.parser.parse_args(argv[1:])
self.args = self.parser.parse_args(argv[1:])
def out(self, msg): # pragma: no cover
if not self.quiet:
@@ -106,11 +123,11 @@ def configure_logging(self, app_spec):
setup_logging(app_spec)
def run(self):
if not len(self.args) >= 2:
if not self.args.config_uri and not self.args.path_info:
self.out('You must provide at least two arguments')
return 2
app_spec = self.args[0]
path = self.args[1]
app_spec = self.args.config_uri
path = self.args.path_info
self.configure_logging(app_spec)
@@ -125,12 +142,12 @@ def run(self):
path = url_unquote(path)
headers = {}
if self.options.login:
enc = base64.b64encode(self.options.login.encode('ascii'))
if self.args.login:
enc = base64.b64encode(self.args.login.encode('ascii'))
headers['Authorization'] = 'Basic ' + enc.decode('ascii')
if self.options.headers:
for item in self.options.headers:
if self.args.headers:
for item in self.args.headers:
if ':' not in item:
self.out(
"Bad --header=%s option, value must be in the form "
@@ -139,10 +156,10 @@ def run(self):
name, value = item.split(':', 1)
headers[name] = value.strip()
app = self.get_app(app_spec, self.options.app_name,
options=parse_vars(self.args[2:]))
app = self.get_app(app_spec, self.args.app_name,
options=self.args.config_vars)
request_method = (self.options.method or 'GET').upper()
request_method = (self.args.method or 'GET').upper()
environ = {
'REQUEST_METHOD': request_method,
@@ -177,7 +194,7 @@ def run(self):
request = Request.blank(path, environ=environ)
response = request.get_response(app)
if self.options.display_headers:
if self.args.display_headers:
self.out(response.status)
for name, value in response.headerlist:
self.out('%s: %s' % (name, value))
View
@@ -1,5 +1,5 @@
import fnmatch
import optparse
import argparse
import sys
import textwrap
import re
@@ -247,24 +247,41 @@ class PRoutesCommand(object):
"""
bootstrap = (bootstrap,)
stdout = sys.stdout
usage = '%prog config_uri'
ConfigParser = configparser.ConfigParser # testing
parser = optparse.OptionParser(
usage,
ConfigParser = configparser.ConfigParser # testing
parser = argparse.ArgumentParser(
description=textwrap.dedent(description)
)
parser.add_option('-g', '--glob',
action='store', type='string', dest='glob',
default='', help='Display routes matching glob pattern')
)
parser.add_argument('-g', '--glob',
action='store',
dest='glob',
default='',
help='Display routes matching glob pattern')
parser.add_argument('-f', '--format',
action='store',
dest='format',
default='',
help=('Choose which columns to display, this will '
'override the format key in the [proutes] ini '
'section'))
parser.add_argument(
'config_uri',
nargs='?',
default=None,
help='The URI to the configuration file.',
)
parser.add_option('-f', '--format',
action='store', type='string', dest='format',
default='', help=('Choose which columns to display, this '
'will override the format key in the '
'[proutes] ini section'))
parser.add_argument(
'config_vars',
nargs='*',
default=(),
help='Arbitrary options to override those in the [app:main] section '
'of the configuration file.',
)
def __init__(self, argv, quiet=False):
self.options, self.args = self.parser.parse_args(argv[1:])
self.args = self.parser.parse_args(argv[1:])
self.quiet = quiet
self.available_formats = [
'name', 'pattern', 'view', 'method'
@@ -312,19 +329,19 @@ def _get_mapper(self, registry):
return config.get_routes_mapper()
def run(self, quiet=False):
if not self.args:
if not self.args.config_uri:
self.out('requires a config file argument')
return 2
config_uri = self.args[0]
env = self.bootstrap[0](config_uri, options=parse_vars(self.args[1:]))
config_uri = self.args.config_uri
env = self.bootstrap[0](config_uri, options=parse_vars(self.args.config_vars))
registry = env['registry']
mapper = self._get_mapper(registry)
self.proutes_file_config(config_uri)
if self.options.format:
columns = self.options.format.split(',')
if self.args.format:
columns = self.args.format.split(',')
self.column_format = [x.strip() for x in columns]
is_valid = self.validate_formats(self.column_format)
@@ -361,9 +378,9 @@ def run(self, quiet=False):
route_data = get_route_data(route, registry)
for name, pattern, view, method in route_data:
if self.options.glob:
match = (fnmatch.fnmatch(name, self.options.glob) or
fnmatch.fnmatch(pattern, self.options.glob))
if self.args.glob:
match = (fnmatch.fnmatch(name, self.args.glob) or
fnmatch.fnmatch(pattern, self.args.glob))
if not match:
continue
View
@@ -8,7 +8,7 @@
# Code taken also from QP: http://www.mems-exchange.org/software/qp/ From
# lib/site.py
import optparse
import argparse
import os
import re
import sys
@@ -28,17 +28,19 @@
)
from pyramid.compat import PY2
from pyramid.compat import configparser
from pyramid.scripts.common import parse_vars
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):
usage = '%prog config_uri [var=value]'
description = """\
This command serves a web application that uses a PasteDeploy
configuration file for the server and application.
@@ -48,92 +50,140 @@ class PServeCommand(object):
"""
default_verbosity = 1
parser = optparse.OptionParser(
usage,
parser = argparse.ArgumentParser(
description=textwrap.dedent(description)
)
parser.add_option(
parser.add_argument(
'-n', '--app-name',
dest='app_name',
metavar='NAME',
help="Load the named application (default main)")
parser.add_option(
parser.add_argument(
'-s', '--server',
dest='server',
metavar='SERVER_TYPE',
help="Use the named server.")
parser.add_option(
parser.add_argument(
'--server-name',
dest='server_name',
metavar='SECTION_NAME',
help=("Use the named server as defined in the configuration file "
"(default: main)"))
parser.add_option(
parser.add_argument(
'--reload',
dest='reload',
action='store_true',
help="Use auto-restart file monitor")
parser.add_option(
parser.add_argument(
'--reload-interval',
dest='reload_interval',
default=1,
help=("Seconds between checking files (low number can cause "
"significant CPU usage)"))
parser.add_option(
parser.add_argument(
'-b', '--browser',
dest='browser',
action='store_true',
help="Open a web browser to server url")
parser.add_option(
parser.add_argument(
'-v', '--verbose',
default=default_verbosity,
dest='verbose',
action='count',
help="Set verbose level (default " + str(default_verbosity) + ")")
parser.add_option(
parser.add_argument(
'-q', '--quiet',
action='store_const',
const=0,
dest='verbose',
help="Suppress verbose output")
parser.add_argument(
'config_uri',
nargs='?',
default=None,
help='The URI to the configuration file.',
)
parser.add_argument(
'config_vars',
nargs='*',
default=(),
help="Variables required by the config file. For example, "
"`http_port=%%(http_port)s` would expect `http_port=8080` to be "
"passed here.",
)
ConfigParser = configparser.ConfigParser # testing
loadapp = staticmethod(loadapp) # testing
loadserver = staticmethod(loadserver) # testing
_scheme_re = re.compile(r'^[a-z][a-z]+:', re.I)
def __init__(self, argv, quiet=False):
self.options, self.args = self.parser.parse_args(argv[1:])
self.args = self.parser.parse_args(argv[1:])
if quiet:
self.options.verbose = 0
self.args.verbose = 0
self.watch_files = []
def out(self, msg): # pragma: no cover
if self.options.verbose > 0:
if self.args.verbose > 0:
print(msg)
def get_options(self):
restvars = self.args[1:]
def get_config_vars(self):
restvars = self.args.config_vars
return parse_vars(restvars)
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)
# track file paths relative to the ini file
resolver = AssetResolver(package=None)
for file in watch_files:
if ':' in file:
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))
def run(self): # pragma: no cover
if not self.args:
if not self.args.config_uri:
self.out('You must give a config file')
return 2
app_spec = self.args[0]
app_spec = self.args.config_uri
vars = self.get_options()
app_name = self.options.app_name
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
server_name = self.options.server_name
if self.options.server:
else:
config_path = None
server_name = self.args.server_name
if self.args.server:
server_spec = 'egg:pyramid'
assert server_name is None
server_name = self.options.server
server_name = self.args.server
else:
server_spec = app_spec
base = os.getcwd()
# do not open the browser on each reload so check hupper first
if self.options.browser and not hupper.is_active():
if self.args.browser and not hupper.is_active():
def open_browser():
context = loadcontext(
SERVER, app_spec, name=server_name, relative_to=base,
@@ -145,63 +195,48 @@ def open_browser():
t.setDaemon(True)
t.start()
if self.options.reload and not hupper.is_active():
if self.options.verbose > 1:
if self.args.reload and not hupper.is_active():
if self.args.verbose > 1:
self.out('Running reloading file monitor')
hupper.start_reloader(
'pyramid.scripts.pserve.main',
reload_interval=int(self.options.reload_interval),
verbose=self.options.verbose,
reload_interval=int(self.args.reload_interval),
verbose=self.args.verbose,
)
return 0
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)
if hupper.is_active():
reloader = hupper.get_reloader()
if app_spec.startswith('config:'):
reloader.watch_files([app_spec[len('config:'):]])
reloader.watch_files(self.watch_files)
log_fn = app_spec
if log_fn.startswith('config:'):
log_fn = app_spec[len('config:'):]
elif log_fn.startswith('egg:'):
log_fn = None
if log_fn:
log_fn = os.path.join(base, log_fn)
setup_logging(log_fn, global_conf=vars)
server = self.loadserver(server_spec, name=server_name,
relative_to=base, global_conf=vars)
server = self.loadserver(
server_spec, name=server_name, relative_to=base, global_conf=vars)
app = self.loadapp(
app_spec, name=app_name, relative_to=base, global_conf=vars)
if self.options.verbose > 0:
if self.args.verbose > 0:
if hasattr(os, 'getpid'):
msg = 'Starting server in PID %i.' % os.getpid()
else:
msg = 'Starting server.'
self.out(msg)
def serve():
try:
server(app)
except (SystemExit, KeyboardInterrupt) as e:
if self.options.verbose > 1:
raise
if str(e):
msg = ' ' + str(e)
else:
msg = ''
self.out('Exiting%s (-v to see traceback)' % msg)
serve()
def loadapp(self, app_spec, name, relative_to, **kw): # pragma: no cover
return loadapp(app_spec, name=name, relative_to=relative_to, **kw)
def loadserver(self, server_spec, name, relative_to, **kw):# pragma:no cover
return loadserver(
server_spec, name=name, relative_to=relative_to, **kw)
try:
server(app)
except (SystemExit, KeyboardInterrupt) as e:
if self.args.verbose > 1:
raise
if str(e):
msg = ' ' + str(e)
else:
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
View
@@ -1,5 +1,5 @@
from code import interact
import optparse
import argparse
import os
import sys
import textwrap
@@ -28,7 +28,6 @@ def python_shell_runner(env, help, interact=interact):
class PShellCommand(object):
usage = '%prog config_uri'
description = """\
Open an interactive shell with a Pyramid app loaded. This command
accepts one positional argument named "config_uri" which specifies the
@@ -45,26 +44,30 @@ class PShellCommand(object):
bootstrap = (bootstrap,) # for testing
pkg_resources = pkg_resources # for testing
parser = optparse.OptionParser(
usage,
parser = argparse.ArgumentParser(
description=textwrap.dedent(description)
)
parser.add_option('-p', '--python-shell',
action='store', type='string', dest='python_shell',
default='',
help=('Select the shell to use. A list of possible '
'shells is available using the --list-shells '
'option.'))
parser.add_option('-l', '--list-shells',
dest='list',
action='store_true',
help='List all available shells.')
parser.add_option('--setup',
dest='setup',
help=("A callable that will be passed the environment "
"before it is made available to the shell. This "
"option will override the 'setup' key in the "
"[pshell] ini section."))
parser.add_argument('-p', '--python-shell',
action='store',
dest='python_shell',
default='',
help=('Select the shell to use. A list of possible '
'shells is available using the --list-shells '
'option.'))
parser.add_argument('-l', '--list-shells',
dest='list',
action='store_true',
help='List all available shells.')
parser.add_argument('--setup',
dest='setup',
help=("A callable that will be passed the environment "
"before it is made available to the shell. This "
"option will override the 'setup' key in the "
"[pshell] ini section."))
parser.add_argument('config_uri',
nargs='?',
default=None,
help='The URI to the configuration file.')
ConfigParser = configparser.ConfigParser # testing
default_runner = python_shell_runner # testing
@@ -77,7 +80,7 @@ class PShellCommand(object):
def __init__(self, argv, quiet=False):
self.quiet = quiet
self.options, self.args = self.parser.parse_args(argv[1:])
self.args = self.parser.parse_args(argv[1:])
def pshell_file_config(self, filename):
config = self.ConfigParser()
@@ -106,12 +109,12 @@ def out(self, msg): # pragma: no cover
print(msg)
def run(self, shell=None):
if self.options.list:
if self.args.list:
return self.show_shells()
if not self.args:
if not self.args.config_uri:
self.out('Requires a config file argument')
return 2
config_uri = self.args[0]
config_uri = self.args.config_uri
config_file = config_uri.split('#', 1)[0]
setup_logging(config_file)
self.pshell_file_config(config_file)
@@ -132,8 +135,8 @@ def run(self, shell=None):
'Default root factory used to create `root`.')
# override use_script with command-line options
if self.options.setup:
self.setup = self.options.setup
if self.args.setup:
self.setup = self.args.setup
if self.setup:
# store the env before muddling it with the script
@@ -214,7 +217,7 @@ def make_shell(self):
shells = self.find_all_shells()
shell = None
user_shell = self.options.python_shell.lower()
user_shell = self.args.python_shell.lower()
if not user_shell:
preferred_shells = self.preferred_shells
View
@@ -1,4 +1,4 @@
import optparse
import argparse
import sys
import textwrap
@@ -14,7 +14,6 @@ def main(argv=sys.argv, quiet=False):
return command.run()
class PTweensCommand(object):
usage = '%prog config_uri'
description = """\
Print all implicit and explicit tween objects used by a Pyramid
application. The handler output includes whether the system is using an
@@ -28,17 +27,21 @@ class PTweensCommand(object):
will be assumed. Example: "ptweens myapp.ini#main".
"""
parser = optparse.OptionParser(
usage,
parser = argparse.ArgumentParser(
description=textwrap.dedent(description),
)
parser.add_argument('config_uri',
nargs='?',
default=None,
help='The URI to the configuration file.')
stdout = sys.stdout
bootstrap = (bootstrap,) # testing
def __init__(self, argv, quiet=False):
self.quiet = quiet
self.options, self.args = self.parser.parse_args(argv[1:])
self.args = self.parser.parse_args(argv[1:])
def _get_tweens(self, registry):
from pyramid.config import Configurator
View
@@ -1,4 +1,4 @@
import optparse
import argparse
import sys
import textwrap
@@ -13,7 +13,6 @@ def main(argv=sys.argv, quiet=False):
return command.run()
class PViewsCommand(object):
usage = '%prog config_uri url'
description = """\
Print, for a given URL, the views that might match. Underneath each
potentially matching route, list the predicates required. Underneath
@@ -28,16 +27,21 @@ class PViewsCommand(object):
"""
stdout = sys.stdout
parser = optparse.OptionParser(
usage,
parser = argparse.ArgumentParser(
prog="pviews",
description=textwrap.dedent(description)
)
parser.add_argument('config_uri',
nargs='?',
default=None,
help='The URI to the configuration file.')
bootstrap = (bootstrap,) # testing
def __init__(self, argv, quiet=False):
self.quiet = quiet
self.options, self.args = self.parser.parse_args(argv[1:])
self.args = self.parser.parse_args(argv[1:])
def out(self, msg): # pragma: no cover
if not self.quiet:
View
@@ -5,7 +5,7 @@ class TestPredicateList(unittest.TestCase):
def _makeOne(self):
from pyramid.config.util import PredicateList
from pyramid.config import predicates
from pyramid import predicates
inst = PredicateList()
for name, factory in (
('xhr', predicates.XHRPredicate),
@@ -594,6 +594,15 @@ def test_it_without_phash_val(self):
self.assertEqual(inst.phash(), '')
self.assertEqual(inst(None, None), True)
class TestDeprecatedPredicates(unittest.TestCase):
def test_it(self):
import warnings
with warnings.catch_warnings(record=True) as w:
warnings.filterwarnings('always')
from pyramid.config.predicates import XHRPredicate
self.assertEqual(len(w), 1)
class DummyPredicate(object):
def __init__(self, result):
self.result = result
View
@@ -6,7 +6,7 @@
class TestXHRPredicate(unittest.TestCase):
def _makeOne(self, val):
from pyramid.config.predicates import XHRPredicate
from pyramid.predicates import XHRPredicate
return XHRPredicate(val, None)
def test___call___true(self):
@@ -33,7 +33,7 @@ def test_phash(self):
class TestRequestMethodPredicate(unittest.TestCase):
def _makeOne(self, val):
from pyramid.config.predicates import RequestMethodPredicate
from pyramid.predicates import RequestMethodPredicate
return RequestMethodPredicate(val, None)
def test_ctor_get_but_no_head(self):
@@ -71,7 +71,7 @@ def test_phash(self):
class TestPathInfoPredicate(unittest.TestCase):
def _makeOne(self, val):
from pyramid.config.predicates import PathInfoPredicate
from pyramid.predicates import PathInfoPredicate
return PathInfoPredicate(val, None)
def test_ctor_compilefail(self):
@@ -102,7 +102,7 @@ def test_phash(self):
class TestRequestParamPredicate(unittest.TestCase):
def _makeOne(self, val):
from pyramid.config.predicates import RequestParamPredicate
from pyramid.predicates import RequestParamPredicate
return RequestParamPredicate(val, None)
def test___call___true_exists(self):
@@ -174,7 +174,7 @@ def test_phash_withval(self):
class TestMatchParamPredicate(unittest.TestCase):
def _makeOne(self, val):
from pyramid.config.predicates import MatchParamPredicate
from pyramid.predicates import MatchParamPredicate
return MatchParamPredicate(val, None)
def test___call___true_single(self):
@@ -216,7 +216,7 @@ def test_phash(self):
class TestCustomPredicate(unittest.TestCase):
def _makeOne(self, val):
from pyramid.config.predicates import CustomPredicate
from pyramid.predicates import CustomPredicate
return CustomPredicate(val, None)
def test___call___true(self):
@@ -255,7 +255,7 @@ def test_phash(self):
class TestTraversePredicate(unittest.TestCase):
def _makeOne(self, val):
from pyramid.config.predicates import TraversePredicate
from pyramid.predicates import TraversePredicate
return TraversePredicate(val, None)
def test___call__traverse_has_remainder_already(self):
@@ -297,7 +297,7 @@ def test_phash(self):
class Test_CheckCSRFTokenPredicate(unittest.TestCase):
def _makeOne(self, val, config):
from pyramid.config.predicates import CheckCSRFTokenPredicate
from pyramid.predicates import CheckCSRFTokenPredicate
return CheckCSRFTokenPredicate(val, config)
def test_text(self):
@@ -340,7 +340,7 @@ def test_it_call_val_False(self):
class TestHeaderPredicate(unittest.TestCase):
def _makeOne(self, val):
from pyramid.config.predicates import HeaderPredicate
from pyramid.predicates import HeaderPredicate
return HeaderPredicate(val, None)
def test___call___true_exists(self):
@@ -404,7 +404,7 @@ def test_phash_withregex(self):
class Test_PhysicalPathPredicate(unittest.TestCase):
def _makeOne(self, val, config):
from pyramid.config.predicates import PhysicalPathPredicate
from pyramid.predicates import PhysicalPathPredicate
return PhysicalPathPredicate(val, config)
def test_text(self):
@@ -468,7 +468,7 @@ def tearDown(self):
testing.tearDown()
def _makeOne(self, val, config):
from pyramid.config.predicates import EffectivePrincipalsPredicate
from pyramid.predicates import EffectivePrincipalsPredicate
return EffectivePrincipalsPredicate(val, config)
def test_text(self):
View
@@ -82,8 +82,9 @@ def __init__(self, *views, **attrs):
self.__request_attrs__ = attrs
class DummyConfigParser(object):
def __init__(self, result):
def __init__(self, result, defaults=None):
self.result = result
self.defaults = defaults
def read(self, filename):
self.filename = filename
@@ -98,8 +99,9 @@ def items(self, section):
class DummyConfigParserFactory(object):
items = None
def __call__(self):
self.parser = DummyConfigParser(self.items)
def __call__(self, defaults=None):
self.defaults = defaults
self.parser = DummyConfigParser(self.items, defaults)
return self.parser
class DummyCloser(object):
View
@@ -20,7 +20,7 @@ def _getTargetClass(self):
def _makeOne(self):
cmd = self._getTargetClass()([])
cmd.bootstrap = (dummy.DummyBootstrap(),)
cmd.args = ('/foo/bar/myapp.ini#myapp',)
cmd.args.config_uri = '/foo/bar/myapp.ini#myapp'
return cmd
@@ -38,7 +38,8 @@ def _makeConfig(self, *arg, **kw):
def test_good_args(self):
cmd = self._getTargetClass()([])
cmd.bootstrap = (dummy.DummyBootstrap(),)
cmd.args = ('/foo/bar/myapp.ini#myapp', 'a=1')
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
@@ -52,7 +53,8 @@ def test_good_args(self):
def test_bad_args(self):
cmd = self._getTargetClass()([])
cmd.bootstrap = (dummy.DummyBootstrap(),)
cmd.args = ('/foo/bar/myapp.ini#myapp', 'a')
cmd.args.config_uri = '/foo/bar/myapp.ini#myapp'
cmd.args.config_vars = ('a',)
route = dummy.DummyRoute('a', '/a')
mapper = dummy.DummyMapper(route)
cmd._get_mapper = lambda *arg: mapper
@@ -586,7 +588,7 @@ def view2(context, request): return 'view2'
)
command = self._makeOne()
command.options.glob = '*foo*'
command.args.glob = '*foo*'
L = []
command.out = L.append
@@ -618,8 +620,8 @@ def view1(context, request): return 'view1'
)
command = self._makeOne()
command.options.glob = '*foo*'
command.options.format = 'method,name'
command.args.glob = '*foo*'
command.args.format = 'method,name'
L = []
command.out = L.append
command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
@@ -648,8 +650,8 @@ def view1(context, request): return 'view1'
)
command = self._makeOne()
command.options.glob = '*foo*'
command.options.format = 'predicates,name,pattern'
command.args.glob = '*foo*'
command.args.format = 'predicates,name,pattern'
L = []
command.out = L.append
command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
View
@@ -1,17 +1,21 @@
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
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):
@@ -23,6 +27,7 @@ def _makeOne(self, *args):
effargs.extend(args)
cmd = self._getTargetClass()(effargs)
cmd.out = self.out
cmd.ConfigParser = self.config_factory
return cmd
def test_run_no_args(self):
@@ -31,34 +36,46 @@ def test_run_no_args(self):
self.assertEqual(result, 2)
self.assertEqual(self.out_.getvalue(), 'You must give a config file')
def test_get_options_no_command(self):
def test_config_args_no_command(self):
inst = self._makeOne()
inst.args = ['foo', 'a=1', 'b=2']
result = inst.get_options()
inst.args.config_args = ['a=1', 'b=2']
result = inst.get_config_args()
self.assertEqual(result, {'a': '1', 'b': '2'})
def test_parse_vars_good(self):
from pyramid.tests.test_scripts.dummy import DummyApp
inst = self._makeOne('development.ini', 'a=1', 'b=2')
inst.loadserver = self._get_server
app = DummyApp()
app = dummy.DummyApp()
def get_app(*args, **kwargs):
app.global_conf = kwargs.get('global_conf', None)
inst.loadapp = get_app
inst.run()
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, {
'a': '1',
'here': os.path.abspath('/base'),
})
self.assertEqual(inst.watch_files, [
os.path.abspath('/base/foo'),
os.path.abspath('/baz'),
os.path.abspath(os.path.join(here, '*.py')),
])
class Test_main(unittest.TestCase):
def _callFUT(self, argv):
from pyramid.scripts.pserve import main
View
@@ -28,13 +28,24 @@
from pyramid.interfaces import IActionInfo
from pyramid.path import DottedNameResolver as _DottedNameResolver
_marker = object()
class DottedNameResolver(_DottedNameResolver):
def __init__(self, package=None): # default to package = None for bw compat
_DottedNameResolver.__init__(self, package)
_marker = object()
def is_string_or_iterable(v):
if isinstance(v, string_types):
return True
if hasattr(v, '__iter__'):
return True
def as_sorted_tuple(val):
if not is_nonstr_iter(val):
val = (val,)
val = tuple(sorted(val))
return val
class InstancePropertyHelper(object):
"""A helper object for assigning properties and descriptors to instances.
View
@@ -41,7 +41,7 @@
install_requires = [
'setuptools',
'WebOb >= 1.3.1', # request.domain and CookieProfile
'WebOb >= 1.7.0rc2', # Response.has_body
'repoze.lru >= 0.4', # py3 compat
'zope.interface >= 3.8.0', # has zope.interface.registry
'zope.deprecation >= 3.5.0', # py3 compat
@@ -64,7 +64,7 @@
'repoze.sphinx.autointerface',
'pylons_sphinx_latesturl',
'pylons-sphinx-themes',
'sphinxcontrib-programoutput',
'sphinxcontrib-autoprogram',
]
testing_extras = tests_require + [