From 6b0e4625da2c53a1e3fdb4857fc7c6ba6ce562cf Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 31 Oct 2018 01:36:26 -0500 Subject: initial work to remove py2 from the codebase --- .travis.yml | 6 +- appveyor.yml | 2 - setup.py | 9 +- src/pyramid/compat.py | 302 +++++++++--------------------------- src/pyramid/i18n.py | 15 +- src/pyramid/interfaces.py | 15 -- src/pyramid/request.py | 4 +- src/pyramid/scripts/pserve.py | 23 ++- src/pyramid/session.py | 8 +- src/pyramid/testing.py | 4 +- src/pyramid/traversal.py | 61 ++------ src/pyramid/urldispatch.py | 12 +- src/pyramid/util.py | 13 +- tests/test_config/test_adapters.py | 6 +- tests/test_config/test_factories.py | 7 +- tests/test_config/test_init.py | 3 - tests/test_path.py | 6 +- tests/test_request.py | 7 +- tests/test_session.py | 8 +- tests/test_traversal.py | 7 +- tests/test_urldispatch.py | 7 +- tests/test_util.py | 27 +--- tox.ini | 24 +-- 23 files changed, 133 insertions(+), 443 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6efbee21b..c4860d2de 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,20 +4,16 @@ sudo: false matrix: include: - - python: 2.7 - env: TOXENV=py27 - python: 3.4 env: TOXENV=py34 - python: 3.5 env: TOXENV=py35 - python: 3.6 env: TOXENV=py36 - - python: pypy - env: TOXENV=pypy - python: pypy3 env: TOXENV=pypy3 - python: 3.6 - env: TOXENV=py2-cover,py3-cover,coverage + env: TOXENV=py36-cover,coverage - python: 3.5 env: TOXENV=docs - python: 3.6 diff --git a/appveyor.yml b/appveyor.yml index 8c9d158e1..a9bcd40f1 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -8,8 +8,6 @@ environment: TOXENV: "py35" - PYTHON: "C:\\Python34" TOXENV: "py34" - - PYTHON: "C:\\Python27" - TOXENV: "py27" cache: - '%LOCALAPPDATA%\pip\Cache' diff --git a/setup.py b/setup.py index 0143764b8..b63f4c182 100644 --- a/setup.py +++ b/setup.py @@ -69,7 +69,6 @@ setup( "Development Status :: 6 - Mature", "Intended Audience :: Developers", "Programming Language :: Python", - "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", @@ -96,13 +95,9 @@ setup( package_dir={'': 'src'}, include_package_data=True, zip_safe=False, - python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*', + python_requires='>=3.4', install_requires=install_requires, - extras_require={ - ':python_version<"3.2"': ['repoze.lru >= 0.4'], - 'testing': testing_extras, - 'docs': docs_extras, - }, + extras_require={'testing': testing_extras, 'docs': docs_extras}, tests_require=tests_require, test_suite="tests", entry_points="""\ diff --git a/src/pyramid/compat.py b/src/pyramid/compat.py index 31832c874..77e9bea98 100644 --- a/src/pyramid/compat.py +++ b/src/pyramid/compat.py @@ -13,34 +13,15 @@ except BaseException: # pragma: no cover __pypy__ = None PYPY = False -try: - import cPickle as pickle -except ImportError: # pragma: no cover - import pickle - -try: - from functools import lru_cache -except ImportError: - from repoze.lru import lru_cache - -# PY3 is left as bw-compat but PY2 should be used for most checks. -PY2 = sys.version_info[0] == 2 -PY3 = sys.version_info[0] == 3 - -if PY2: - string_types = (basestring,) - integer_types = (int, long) - class_types = (type, types.ClassType) - text_type = unicode - binary_type = str - long = long -else: - string_types = (str,) - integer_types = (int,) - class_types = (type,) - text_type = str - binary_type = bytes - long = int +from functools import lru_cache +import pickle + +string_types = (str,) +integer_types = (int,) +class_types = (type,) +text_type = str +binary_type = bytes +long = int def text_(s, encoding='latin-1', errors='strict'): @@ -59,236 +40,105 @@ def bytes_(s, encoding='latin-1', errors='strict'): return s -if PY2: - - def ascii_native_(s): - if isinstance(s, text_type): - s = s.encode('ascii') - return str(s) - - -else: - - def ascii_native_(s): - if isinstance(s, text_type): - s = s.encode('ascii') - return str(s, 'ascii', 'strict') - - -ascii_native_.__doc__ = """ -Python 3: If ``s`` is an instance of ``text_type``, return -``s.encode('ascii')``, otherwise return ``str(s, 'ascii', 'strict')`` - -Python 2: If ``s`` is an instance of ``text_type``, return -``s.encode('ascii')``, otherwise return ``str(s)`` -""" - - -if PY2: - - def native_(s, encoding='latin-1', errors='strict'): - """ If ``s`` is an instance of ``text_type``, return - ``s.encode(encoding, errors)``, otherwise return ``str(s)``""" - if isinstance(s, text_type): - return s.encode(encoding, errors) - return str(s) - - -else: - - def native_(s, encoding='latin-1', errors='strict'): - """ If ``s`` is an instance of ``text_type``, return - ``s``, otherwise return ``str(s, encoding, errors)``""" - if isinstance(s, text_type): - return s - return str(s, encoding, errors) - - -native_.__doc__ = """ -Python 3: If ``s`` is an instance of ``text_type``, return ``s``, otherwise -return ``str(s, encoding, errors)`` - -Python 2: If ``s`` is an instance of ``text_type``, return -``s.encode(encoding, errors)``, otherwise return ``str(s)`` -""" - -if PY2: - import urlparse - from urllib import quote as url_quote - from urllib import quote_plus as url_quote_plus - from urllib import unquote as url_unquote - from urllib import urlencode as url_encode - from urllib2 import urlopen as url_open - - def url_unquote_text( - v, encoding='utf-8', errors='replace' - ): # pragma: no cover - v = url_unquote(v) - return v.decode(encoding, errors) - - def url_unquote_native( - v, encoding='utf-8', errors='replace' - ): # pragma: no cover - return native_(url_unquote_text(v, encoding, errors)) - - -else: - from urllib import parse - - urlparse = parse - from urllib.parse import quote as url_quote - from urllib.parse import quote_plus as url_quote_plus - from urllib.parse import unquote as url_unquote - from urllib.parse import urlencode as url_encode - from urllib.request import urlopen as url_open - - url_unquote_text = url_unquote - url_unquote_native = url_unquote - - -if PY2: # pragma: no cover - - def exec_(code, globs=None, locs=None): - """Execute code in a namespace.""" - if globs is None: - frame = sys._getframe(1) - globs = frame.f_globals - if locs is None: - locs = frame.f_locals - del frame - elif locs is None: - locs = globs - exec("""exec code in globs, locs""") - - exec_( - """def reraise(tp, value, tb=None): - raise tp, value, tb -""" - ) +def ascii_native_(s): + """ + If ``s`` is an instance of ``text_type``, return + ``s.encode('ascii')``, otherwise return ``str(s, 'ascii', 'strict')`` + """ + if isinstance(s, text_type): + s = s.encode('ascii') + return str(s, 'ascii', 'strict') -else: # pragma: no cover - import builtins - exec_ = getattr(builtins, "exec") +def native_(s, encoding='latin-1', errors='strict'): + """ If ``s`` is an instance of ``text_type``, return + ``s``, otherwise return ``str(s, encoding, errors)`` + """ + if isinstance(s, text_type): + return s + return str(s, encoding, errors) - def reraise(tp, value, tb=None): - if value is None: - value = tp - if value.__traceback__ is not tb: - raise value.with_traceback(tb) - raise value - del builtins +from urllib import parse +urlparse = parse +from urllib.parse import quote as url_quote +from urllib.parse import quote_plus as url_quote_plus +from urllib.parse import unquote as url_unquote +from urllib.parse import urlencode as url_encode +from urllib.request import urlopen as url_open -if PY2: # pragma: no cover +url_unquote_text = url_unquote +url_unquote_native = url_unquote - def iteritems_(d): - return d.iteritems() - def itervalues_(d): - return d.itervalues() +import builtins - def iterkeys_(d): - return d.iterkeys() +exec_ = getattr(builtins, "exec") -else: # pragma: no cover +def reraise(tp, value, tb=None): + if value is None: + value = tp + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value - def iteritems_(d): - return d.items() - def itervalues_(d): - return d.values() +del builtins - def iterkeys_(d): - return d.keys() +def iteritems_(d): + return d.items() -if PY2: - map_ = map -else: - def map_(*arg): - return list(map(*arg)) +def itervalues_(d): + return d.values() -if PY2: +def iterkeys_(d): + return d.keys() - def is_nonstr_iter(v): - return hasattr(v, '__iter__') +def map_(*arg): + return list(map(*arg)) -else: - def is_nonstr_iter(v): - if isinstance(v, str): - return False - return hasattr(v, '__iter__') +def is_nonstr_iter(v): + if isinstance(v, str): + return False + return hasattr(v, '__iter__') -if PY2: - im_func = 'im_func' - im_self = 'im_self' -else: - im_func = '__func__' - im_self = '__self__' +im_func = '__func__' +im_self = '__self__' -try: - import configparser -except ImportError: - import ConfigParser as configparser +import configparser -try: - from http.cookies import SimpleCookie -except ImportError: - from Cookie import SimpleCookie +from http.cookies import SimpleCookie -if PY2: - from cgi import escape -else: - from html import escape +from html import escape -if PY2: - input_ = raw_input -else: - input_ = input +input_ = input -if PY2: - from io import BytesIO as NativeIO -else: - from io import StringIO as NativeIO +from io import StringIO as NativeIO # "json" is not an API; it's here to support older pyramid_debugtoolbar # versions which attempt to import it import json -if PY2: - - def decode_path_info(path): - return path.decode('utf-8') +# see PEP 3333 for why we encode WSGI PATH_INFO to latin-1 before +# decoding it to utf-8 +def decode_path_info(path): + return path.encode('latin-1').decode('utf-8') -else: - # see PEP 3333 for why we encode WSGI PATH_INFO to latin-1 before - # decoding it to utf-8 - def decode_path_info(path): - return path.encode('latin-1').decode('utf-8') +# see PEP 3333 for why we decode the path to latin-1 +from urllib.parse import unquote_to_bytes -if PY2: - from urlparse import unquote as unquote_to_bytes - - def unquote_bytes_to_wsgi(bytestring): - return unquote_to_bytes(bytestring) - - -else: - # see PEP 3333 for why we decode the path to latin-1 - from urllib.parse import unquote_to_bytes - - def unquote_bytes_to_wsgi(bytestring): - return unquote_to_bytes(bytestring).decode('latin-1') +def unquote_bytes_to_wsgi(bytestring): + return unquote_to_bytes(bytestring).decode('latin-1') def is_bound_method(ob): @@ -296,15 +146,9 @@ def is_bound_method(ob): # support annotations and keyword-only arguments in PY3 -if PY2: - from inspect import getargspec -else: - from inspect import getfullargspec as getargspec +from inspect import getfullargspec as getargspec -if PY2: - from itertools import izip_longest as zip_longest -else: - from itertools import zip_longest +from itertools import zip_longest def is_unbound_method(fn): @@ -318,9 +162,7 @@ def is_unbound_method(fn): spec = getargspec(fn) has_self = len(spec.args) > 0 and spec.args[0] == 'self' - if PY2 and inspect.ismethod(fn): - return True - elif inspect.isfunction(fn) and has_self: + if inspect.isfunction(fn) and has_self: return True return False diff --git a/src/pyramid/i18n.py b/src/pyramid/i18n.py index e99a29aab..45f528852 100644 --- a/src/pyramid/i18n.py +++ b/src/pyramid/i18n.py @@ -8,7 +8,6 @@ from translationstring import ( TranslationStringFactory, # API ) -from pyramid.compat import PY2 from pyramid.decorator import reify from pyramid.interfaces import ( @@ -353,10 +352,7 @@ class Translations(gettext.GNUTranslations, object): """Like ``ugettext()``, but look the message up in the specified domain. """ - if PY2: - return self._domains.get(domain, self).ugettext(message) - else: - return self._domains.get(domain, self).gettext(message) + return self._domains.get(domain, self).gettext(message) def dngettext(self, domain, singular, plural, num): """Like ``ngettext()``, but look the message up in the specified @@ -374,14 +370,7 @@ class Translations(gettext.GNUTranslations, object): """Like ``ungettext()`` but look the message up in the specified domain. """ - if PY2: - return self._domains.get(domain, self).ungettext( - singular, plural, num - ) - else: - return self._domains.get(domain, self).ngettext( - singular, plural, num - ) + return self._domains.get(domain, self).ngettext(singular, plural, num) class LocalizerRequestMixin(object): diff --git a/src/pyramid/interfaces.py b/src/pyramid/interfaces.py index 31bcd7e88..f1e238c6b 100644 --- a/src/pyramid/interfaces.py +++ b/src/pyramid/interfaces.py @@ -1,7 +1,5 @@ from zope.interface import Attribute, Interface -from pyramid.compat import PY2 - # public API interfaces @@ -366,19 +364,6 @@ class IDict(Interface): def values(): """ Return a list of values from the dictionary """ - if PY2: - - def iterkeys(): - """ Return an iterator of keys from the dictionary """ - - def iteritems(): - """ Return an iterator of (k,v) pairs from the dictionary """ - - def itervalues(): - """ Return an iterator of values from the dictionary """ - - has_key = __contains__ - def pop(k, default=None): """ Pop the key k from the dictionary and return its value. If k doesn't exist, and default is provided, return the default. If k diff --git a/src/pyramid/request.py b/src/pyramid/request.py index 907b4477f..5ee87ff58 100644 --- a/src/pyramid/request.py +++ b/src/pyramid/request.py @@ -13,7 +13,7 @@ from pyramid.interfaces import ( ISessionFactory, ) -from pyramid.compat import text_, bytes_, native_, iteritems_ +from pyramid.compat import text_, bytes_, native_ from pyramid.decorator import reify from pyramid.i18n import LocalizerRequestMixin @@ -328,7 +328,7 @@ def apply_request_extensions(request, extensions=None): if extensions is None: extensions = request.registry.queryUtility(IRequestExtensions) if extensions is not None: - for name, fn in iteritems_(extensions.methods): + for name, fn in extensions.methods.items(): method = fn.__get__(request, request.__class__) setattr(request, name, method) diff --git a/src/pyramid/scripts/pserve.py b/src/pyramid/scripts/pserve.py index 581479d65..7d68521a4 100644 --- a/src/pyramid/scripts/pserve.py +++ b/src/pyramid/scripts/pserve.py @@ -19,8 +19,6 @@ import webbrowser import hupper -from pyramid.compat import PY2 - from pyramid.scripts.common import get_config_loader from pyramid.scripts.common import parse_vars from pyramid.path import AssetResolver @@ -380,18 +378,15 @@ def cherrypy_server_runner( server = WSGIServer(bind_addr, app, server_name=server_name, **kwargs) if ssl_pem is not None: - if PY2: - server.ssl_certificate = server.ssl_private_key = ssl_pem - else: - # creates wsgiserver.ssl_builtin as side-effect - try: - from cheroot.server import get_ssl_adapter_class - from cheroot.ssl.builtin import BuiltinSSLAdapter - except ImportError: - from cherrypy.wsgiserver import get_ssl_adapter_class - from cherrypy.wsgiserver.ssl_builtin import BuiltinSSLAdapter - get_ssl_adapter_class() - server.ssl_adapter = BuiltinSSLAdapter(ssl_pem, ssl_pem) + # creates wsgiserver.ssl_builtin as side-effect + try: + from cheroot.server import get_ssl_adapter_class + from cheroot.ssl.builtin import BuiltinSSLAdapter + except ImportError: + from cherrypy.wsgiserver import get_ssl_adapter_class + from cherrypy.wsgiserver.ssl_builtin import BuiltinSSLAdapter + get_ssl_adapter_class() + server.ssl_adapter = BuiltinSSLAdapter(ssl_pem, ssl_pem) if protocol_version: server.protocol = protocol_version diff --git a/src/pyramid/session.py b/src/pyramid/session.py index 68e0c506c..e9486ec6d 100644 --- a/src/pyramid/session.py +++ b/src/pyramid/session.py @@ -7,7 +7,7 @@ from zope.interface import implementer from webob.cookies import JSONSerializer, SignedSerializer -from pyramid.compat import pickle, PY2, text_, bytes_, native_ +from pyramid.compat import pickle, text_, bytes_, native_ from pyramid.csrf import check_csrf_origin, check_csrf_token from pyramid.interfaces import ISession @@ -255,12 +255,6 @@ def BaseCookieSessionFactory( __len__ = manage_accessed(dict.__len__) __iter__ = manage_accessed(dict.__iter__) - if PY2: - iteritems = manage_accessed(dict.iteritems) - itervalues = manage_accessed(dict.itervalues) - iterkeys = manage_accessed(dict.iterkeys) - has_key = manage_accessed(dict.has_key) - # modifying dictionary methods clear = manage_changed(dict.clear) update = manage_changed(dict.update) diff --git a/src/pyramid/testing.py b/src/pyramid/testing.py index f700b5a4e..5fe8f8a62 100644 --- a/src/pyramid/testing.py +++ b/src/pyramid/testing.py @@ -8,7 +8,7 @@ from zope.interface import implementer, alsoProvides from pyramid.interfaces import IRequest, ISession -from pyramid.compat import PY3, PYPY, class_types, text_ +from pyramid.compat import PYPY, class_types, text_ from pyramid.config import Configurator from pyramid.decorator import reify @@ -640,8 +640,6 @@ def skip_on(*platforms): # pragma: no cover skip = True if platform == 'pypy' and PYPY: skip = True - if platform == 'py3' and PY3: - skip = True def decorator(func): if isinstance(func, class_types): diff --git a/src/pyramid/traversal.py b/src/pyramid/traversal.py index 338b49083..f46937d73 100644 --- a/src/pyramid/traversal.py +++ b/src/pyramid/traversal.py @@ -9,7 +9,6 @@ from pyramid.interfaces import ( ) from pyramid.compat import ( - PY2, native_, text_, ascii_native_, @@ -580,49 +579,23 @@ the ``safe`` argument to this function. This corresponds to the """ -if PY2: - # special-case on Python 2 for speed? unchecked - def quote_path_segment(segment, safe=PATH_SEGMENT_SAFE): - """ %s """ % quote_path_segment_doc - # The bit of this code that deals with ``_segment_cache`` is an - # optimization: we cache all the computation of URL path segments - # in this module-scope dictionary with the original string (or - # unicode value) as the key, so we can look it up later without - # needing to reencode or re-url-quote it - try: - return _segment_cache[(segment, safe)] - except KeyError: - if ( - segment.__class__ is text_type - ): # isinstance slighly slower (~15%) - result = url_quote(segment.encode('utf-8'), safe) - else: - result = url_quote(str(segment), safe) - # we don't need a lock to mutate _segment_cache, as the below - # will generate exactly one Python bytecode (STORE_SUBSCR) - _segment_cache[(segment, safe)] = result - return result - - -else: - - def quote_path_segment(segment, safe=PATH_SEGMENT_SAFE): - """ %s """ % quote_path_segment_doc - # The bit of this code that deals with ``_segment_cache`` is an - # optimization: we cache all the computation of URL path segments - # in this module-scope dictionary with the original string (or - # unicode value) as the key, so we can look it up later without - # needing to reencode or re-url-quote it - try: - return _segment_cache[(segment, safe)] - except KeyError: - if segment.__class__ not in (text_type, binary_type): - segment = str(segment) - result = url_quote(native_(segment, 'utf-8'), safe) - # we don't need a lock to mutate _segment_cache, as the below - # will generate exactly one Python bytecode (STORE_SUBSCR) - _segment_cache[(segment, safe)] = result - return result +def quote_path_segment(segment, safe=PATH_SEGMENT_SAFE): + """ %s """ % quote_path_segment_doc + # The bit of this code that deals with ``_segment_cache`` is an + # optimization: we cache all the computation of URL path segments + # in this module-scope dictionary with the original string (or + # unicode value) as the key, so we can look it up later without + # needing to reencode or re-url-quote it + try: + return _segment_cache[(segment, safe)] + except KeyError: + if segment.__class__ not in (text_type, binary_type): + segment = str(segment) + result = url_quote(native_(segment, 'utf-8'), safe) + # we don't need a lock to mutate _segment_cache, as the below + # will generate exactly one Python bytecode (STORE_SUBSCR) + _segment_cache[(segment, safe)] = result + return result slash = text_('/') diff --git a/src/pyramid/urldispatch.py b/src/pyramid/urldispatch.py index de8a69d2a..1d3f8d91b 100644 --- a/src/pyramid/urldispatch.py +++ b/src/pyramid/urldispatch.py @@ -4,7 +4,6 @@ from zope.interface import implementer from pyramid.interfaces import IRoutesMapper, IRoute from pyramid.compat import ( - PY2, native_, text_, text_type, @@ -227,14 +226,9 @@ def _compile_route(route): def generator(dict): newdict = {} for k, v in dict.items(): - if PY2: - if v.__class__ is text_type: - # url_quote below needs bytes, not unicode on Py2 - v = v.encode('utf-8') - else: - if v.__class__ is binary_type: - # url_quote below needs a native string, not bytes on Py3 - v = v.decode('utf-8') + if v.__class__ is binary_type: + # url_quote below needs a native string, not bytes on Py3 + v = v.decode('utf-8') if k == remainder: # a stararg argument diff --git a/src/pyramid/util.py b/src/pyramid/util.py index bebf9e7d3..39b67471e 100644 --- a/src/pyramid/util.py +++ b/src/pyramid/util.py @@ -1,11 +1,6 @@ from contextlib import contextmanager import functools - -try: - # py2.7.7+ and py3.3+ have native comparison support - from hmac import compare_digest -except ImportError: # pragma: no cover - compare_digest = None +from hmac import compare_digest import inspect import weakref @@ -19,7 +14,6 @@ from pyramid.compat import ( string_types, bytes_, text_, - PY2, native_, ) @@ -337,10 +331,7 @@ def object_description(object): if isinstance(object, (bool, float, type(None))): return text_(str(object)) if isinstance(object, set): - if PY2: - return shortrepr(object, ')') - else: - return shortrepr(object, '}') + return shortrepr(object, '}') if isinstance(object, tuple): return shortrepr(object, ')') if isinstance(object, list): diff --git a/tests/test_config/test_adapters.py b/tests/test_config/test_adapters.py index d871e8825..60a4f3090 100644 --- a/tests/test_config/test_adapters.py +++ b/tests/test_config/test_adapters.py @@ -1,6 +1,5 @@ import unittest -from pyramid.compat import PY2 from . import IDummy @@ -270,10 +269,7 @@ class AdaptersConfiguratorMixinTests(unittest.TestCase): from pyramid.interfaces import IResponse config = self._makeOne(autocommit=True) - if PY2: - str_name = '__builtin__.str' - else: - str_name = 'builtins.str' + str_name = 'builtins.str' config.add_response_adapter('pyramid.response.Response', str_name) result = config.registry.queryAdapter('foo', IResponse) self.assertTrue(result.body, b'foo') diff --git a/tests/test_config/test_factories.py b/tests/test_config/test_factories.py index c03d3f68b..cca528275 100644 --- a/tests/test_config/test_factories.py +++ b/tests/test_config/test_factories.py @@ -161,7 +161,6 @@ class TestFactoriesMixin(unittest.TestCase): self.assertRaises(AttributeError, config.add_request_method) def test_add_request_method_with_text_type_name(self): - from pyramid.compat import text_, PY2 from pyramid.exceptions import ConfigurationError config = self._makeOne(autocommit=True) @@ -170,11 +169,7 @@ class TestFactoriesMixin(unittest.TestCase): pass def get_bad_name(): - if PY2: - name = text_(b'La Pe\xc3\xb1a', 'utf-8') - else: - name = b'La Pe\xc3\xb1a' - + name = b'La Pe\xc3\xb1a' config.add_request_method(boomshaka, name=name) self.assertRaises(ConfigurationError, get_bad_name) diff --git a/tests/test_config/test_init.py b/tests/test_config/test_init.py index 811672fb3..1cd63f113 100644 --- a/tests/test_config/test_init.py +++ b/tests/test_config/test_init.py @@ -2,7 +2,6 @@ import os import unittest from pyramid.compat import im_func -from pyramid.testing import skip_on from . import dummy_tween_factory from . import dummy_include @@ -1157,7 +1156,6 @@ test_config.dummy_include2""" "@view_config(name='two', renderer='string')" in which ) - @skip_on('py3') def test_hook_zca(self): from zope.component import getSiteManager @@ -1173,7 +1171,6 @@ test_config.dummy_include2""" finally: getSiteManager.reset() - @skip_on('py3') def test_unhook_zca(self): from zope.component import getSiteManager diff --git a/tests/test_path.py b/tests/test_path.py index 626bb1139..da7cd64e1 100644 --- a/tests/test_path.py +++ b/tests/test_path.py @@ -1,6 +1,5 @@ import unittest import os -from pyramid.compat import PY2 here = os.path.abspath(os.path.dirname(__file__)) @@ -429,10 +428,7 @@ class TestDottedNameResolver(unittest.TestCase): def test_zope_dottedname_style_resolve_builtin(self): typ = self._makeOne() - if PY2: - result = typ._zope_dottedname_style('__builtin__.str', None) - else: - result = typ._zope_dottedname_style('builtins.str', None) + result = typ._zope_dottedname_style('builtins.str', None) self.assertEqual(result, str) def test_zope_dottedname_style_resolve_absolute(self): diff --git a/tests/test_request.py b/tests/test_request.py index dcac501aa..60cc2b31a 100644 --- a/tests/test_request.py +++ b/tests/test_request.py @@ -1,7 +1,7 @@ import unittest from pyramid import testing -from pyramid.compat import PY2, text_, bytes_, native_ +from pyramid.compat import text_, bytes_, native_ from pyramid.security import AuthenticationAPIMixin, AuthorizationAPIMixin @@ -352,10 +352,7 @@ class TestRequest(unittest.TestCase): inp = text_( b'/\xe6\xb5\x81\xe8\xa1\x8c\xe8\xb6\x8b\xe5\x8a\xbf', 'utf-8' ) - if PY2: - body = json.dumps({'a': inp}).decode('utf-8').encode('utf-16') - else: - body = bytes(json.dumps({'a': inp}), 'utf-16') + body = bytes(json.dumps({'a': inp}), 'utf-16') request.body = body request.content_type = 'application/json; charset=utf-16' self.assertEqual(request.json_body, {'a': inp}) diff --git a/tests/test_session.py b/tests/test_session.py index 5e2a1ff55..f7e7bab05 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -607,13 +607,7 @@ class DummySerializer(object): return base64.b64encode(json.dumps(value).encode('utf-8')) def loads(self, value): - try: - return json.loads(base64.b64decode(value).decode('utf-8')) - - # base64.b64decode raises a TypeError on py2 instead of a ValueError - # and a ValueError is required for the session to handle it properly - except TypeError: - raise ValueError + return json.loads(base64.b64decode(value).decode('utf-8')) class DummySessionFactory(dict): diff --git a/tests/test_traversal.py b/tests/test_traversal.py index 61e480cbc..b517fa646 100644 --- a/tests/test_traversal.py +++ b/tests/test_traversal.py @@ -3,7 +3,7 @@ import unittest from pyramid.testing import cleanUp -from pyramid.compat import text_, native_, text_type, url_quote, PY2 +from pyramid.compat import text_, native_, text_type, url_quote class TraversalPathTests(unittest.TestCase): @@ -346,10 +346,7 @@ class ResourceTreeTraverserTests(unittest.TestCase): foo = DummyContext(bar, path) root = DummyContext(foo, 'root') policy = self._makeOne(root) - if PY2: - vhm_root = b'/Qu\xc3\xa9bec' - else: - vhm_root = b'/Qu\xc3\xa9bec'.decode('latin-1') + vhm_root = b'/Qu\xc3\xa9bec'.decode('latin-1') environ = self._getEnviron(HTTP_X_VHM_ROOT=vhm_root) request = DummyRequest(environ, path_info=text_('/bar')) result = policy(request) diff --git a/tests/test_urldispatch.py b/tests/test_urldispatch.py index 772250e89..b50e86b99 100644 --- a/tests/test_urldispatch.py +++ b/tests/test_urldispatch.py @@ -1,6 +1,6 @@ import unittest from pyramid import testing -from pyramid.compat import text_, PY2 +from pyramid.compat import text_ class TestRoute(unittest.TestCase): @@ -132,10 +132,7 @@ class RoutesMapperTests(unittest.TestCase): from pyramid.exceptions import URLDecodeError mapper = self._makeOne() - if PY2: - path_info = b'\xff\xfe\xe6\x00' - else: - path_info = b'\xff\xfe\xe6\x00'.decode('latin-1') + path_info = b'\xff\xfe\xe6\x00'.decode('latin-1') request = self._getRequest(PATH_INFO=path_info) self.assertRaises(URLDecodeError, mapper, request) diff --git a/tests/test_util.py b/tests/test_util.py index a36655f6f..8af5fe557 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -1,5 +1,5 @@ import unittest -from pyramid.compat import PY2, text_, bytes_ +from pyramid.compat import text_, bytes_ class Test_InstancePropertyHelper(unittest.TestCase): @@ -170,14 +170,10 @@ class Test_InstancePropertyHelper(unittest.TestCase): self.assertEqual(2, foo.y) def test_make_property_unicode(self): - from pyramid.compat import text_ from pyramid.exceptions import ConfigurationError cls = self._getTargetClass() - if PY2: - name = text_(b'La Pe\xc3\xb1a', 'utf-8') - else: - name = b'La Pe\xc3\xb1a' + name = b'La Pe\xc3\xb1a' def make_bad_name(): cls.make_property(lambda x: 1, name=name, reify=True) @@ -498,10 +494,7 @@ class Test_object_description(unittest.TestCase): self.assertEqual(self._callFUT(('a', 'b')), "('a', 'b')") def test_set(self): - if PY2: - self.assertEqual(self._callFUT(set(['a'])), "set(['a'])") - else: - self.assertEqual(self._callFUT(set(['a'])), "{'a'}") + self.assertEqual(self._callFUT(set(['a'])), "{'a'}") def test_list(self): self.assertEqual(self._callFUT(['a']), "['a']") @@ -841,26 +834,16 @@ class TestSentinel(unittest.TestCase): class TestCallableName(unittest.TestCase): def test_valid_ascii(self): from pyramid.util import get_callable_name - from pyramid.compat import text_ - - if PY2: - name = text_(b'hello world', 'utf-8') - else: - name = b'hello world' + name = b'hello world' self.assertEqual(get_callable_name(name), 'hello world') def test_invalid_ascii(self): from pyramid.util import get_callable_name - from pyramid.compat import text_ from pyramid.exceptions import ConfigurationError def get_bad_name(): - if PY2: - name = text_(b'La Pe\xc3\xb1a', 'utf-8') - else: - name = b'La Pe\xc3\xb1a' - + name = b'La Pe\xc3\xb1a' get_callable_name(name) self.assertRaises(ConfigurationError, get_bad_name) diff --git a/tox.ini b/tox.ini index 5bf19d2a7..0dae17e0f 100644 --- a/tox.ini +++ b/tox.ini @@ -1,23 +1,19 @@ [tox] envlist = lint, - py27,py34,py35,py36,py37,py38,pypy,pypy3, - docs,{py2,py3}-cover,coverage, + py34,py35,py36,py37,py38,pypy3, + docs,py36-cover,coverage, [testenv] # Most of these are defaults but if you specify any you can't fall back # to defaults for others. basepython = - py27: python2.7 py34: python3.4 py35: python3.5 py36: python3.6 py37: python3.7 py38: python3.8 - pypy: pypy pypy3: pypy3 - py2: python2.7 - py3: python3.6 commands = nosetests --with-xunit --xunit-file=nosetests-{envname}.xml {posargs:} @@ -59,21 +55,13 @@ extras = # we separate coverage into its own testenv because a) "last run wins" wrt # cobertura jenkins reporting and b) pypy and jython can't handle any # combination of versions of coverage and nosexcover that i can find. -[testenv:py2-cover] -commands = - coverage run {envbindir}/nosetests - coverage xml -o coverage-py2.xml -setenv = - COVERAGE_FILE=.coverage.py2 -extras = - testing - -[testenv:py3-cover] +[testenv:py36-cover] +basepython = python3.6 commands = coverage run {envbindir}/nosetests - coverage xml -o coverage-py3.xml + coverage xml -o coverage-{envname}.xml setenv = - COVERAGE_FILE=.coverage.py3 + COVERAGE_FILE=.coverage.{envname} extras = testing -- cgit v1.2.3 From b1a257bacc1c4ac2c1401ed02c51d9c6c03685d2 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 12 Nov 2018 23:16:09 -0600 Subject: get rid of type aliases --- src/pyramid/asset.py | 4 +-- src/pyramid/authentication.py | 12 +++---- src/pyramid/compat.py | 24 +++++-------- src/pyramid/config/__init__.py | 13 ++----- src/pyramid/config/tweens.py | 4 +-- src/pyramid/config/views.py | 12 ++----- src/pyramid/encode.py | 10 +++--- src/pyramid/httpexceptions.py | 12 +++---- src/pyramid/path.py | 8 ++--- src/pyramid/renderers.py | 6 ++-- src/pyramid/scripts/proutes.py | 3 +- src/pyramid/settings.py | 4 +-- src/pyramid/static.py | 7 ++-- src/pyramid/testing.py | 4 +-- src/pyramid/traversal.py | 72 ++++++++++++++++++------------------- src/pyramid/url.py | 5 +-- src/pyramid/urldispatch.py | 24 +++++-------- src/pyramid/util.py | 8 ++--- tests/test_authentication.py | 12 ------- tests/test_config/test_factories.py | 2 +- tests/test_config/test_views.py | 13 ++----- tests/test_httpexceptions.py | 4 +-- tests/test_traversal.py | 15 ++------ tests/test_view.py | 3 +- 24 files changed, 99 insertions(+), 182 deletions(-) diff --git a/src/pyramid/asset.py b/src/pyramid/asset.py index 0d7575a85..a32babe6c 100644 --- a/src/pyramid/asset.py +++ b/src/pyramid/asset.py @@ -1,13 +1,11 @@ import os import pkg_resources -from pyramid.compat import string_types - from pyramid.path import package_path, package_name def resolve_asset_spec(spec, pname='__main__'): - if pname and not isinstance(pname, string_types): + if pname and not isinstance(pname, str): pname = pname.__name__ # as package if os.path.isabs(spec): return None, spec diff --git a/src/pyramid/authentication.py b/src/pyramid/authentication.py index 7cb6b6811..7810745b5 100644 --- a/src/pyramid/authentication.py +++ b/src/pyramid/authentication.py @@ -13,9 +13,6 @@ from zope.interface import implementer from webob.cookies import CookieProfile from pyramid.compat import ( - long, - text_type, - binary_type, url_unquote, url_quote, bytes_, @@ -857,9 +854,8 @@ class AuthTktCookieHelper(object): userid_type_encoders = { int: ('int', str), - long: ('int', str), - text_type: ('b64unicode', lambda x: b64encode(utf_8_encode(x)[0])), - binary_type: ('b64str', lambda x: b64encode(x)), + str: ('b64unicode', lambda x: b64encode(utf_8_encode(x)[0])), + bytes: ('b64str', lambda x: b64encode(x)), } def __init__( @@ -1048,7 +1044,7 @@ class AuthTktCookieHelper(object): "type provided.".format(type(userid)), RuntimeWarning, ) - encoding, encoder = self.userid_type_encoders.get(text_type) + encoding, encoder = self.userid_type_encoders.get(str) userid = str(userid) userid = encoder(userid) @@ -1056,7 +1052,7 @@ class AuthTktCookieHelper(object): new_tokens = [] for token in tokens: - if isinstance(token, text_type): + if isinstance(token, str): try: token = ascii_native_(token) except UnicodeEncodeError: diff --git a/src/pyramid/compat.py b/src/pyramid/compat.py index 77e9bea98..47e842fbb 100644 --- a/src/pyramid/compat.py +++ b/src/pyramid/compat.py @@ -13,48 +13,40 @@ except BaseException: # pragma: no cover __pypy__ = None PYPY = False -from functools import lru_cache import pickle -string_types = (str,) -integer_types = (int,) -class_types = (type,) -text_type = str -binary_type = bytes -long = int - def text_(s, encoding='latin-1', errors='strict'): - """ If ``s`` is an instance of ``binary_type``, return + """ If ``s`` is an instance of ``bytes``, return ``s.decode(encoding, errors)``, otherwise return ``s``""" - if isinstance(s, binary_type): + if isinstance(s, bytes): return s.decode(encoding, errors) return s def bytes_(s, encoding='latin-1', errors='strict'): - """ If ``s`` is an instance of ``text_type``, return + """ If ``s`` is an instance of ``str``, return ``s.encode(encoding, errors)``, otherwise return ``s``""" - if isinstance(s, text_type): + if isinstance(s, str): return s.encode(encoding, errors) return s def ascii_native_(s): """ - If ``s`` is an instance of ``text_type``, return + If ``s`` is an instance of ``str``, return ``s.encode('ascii')``, otherwise return ``str(s, 'ascii', 'strict')`` """ - if isinstance(s, text_type): + if isinstance(s, str): s = s.encode('ascii') return str(s, 'ascii', 'strict') def native_(s, encoding='latin-1', errors='strict'): - """ If ``s`` is an instance of ``text_type``, return + """ If ``s`` is an instance of ``str``, return ``s``, otherwise return ``str(s, encoding, errors)`` """ - if isinstance(s, text_type): + if isinstance(s, str): return s return str(s, encoding, errors) diff --git a/src/pyramid/config/__init__.py b/src/pyramid/config/__init__.py index 475f0d9a2..198532e0c 100644 --- a/src/pyramid/config/__init__.py +++ b/src/pyramid/config/__init__.py @@ -19,8 +19,6 @@ from pyramid.asset import resolve_asset_spec from pyramid.authorization import ACLAuthorizationPolicy -from pyramid.compat import text_, string_types - from pyramid.events import ApplicationCreated from pyramid.exceptions import ConfigurationError @@ -59,7 +57,6 @@ from pyramid.config.zca import ZCAConfiguratorMixin from pyramid.path import DottedNameResolver -empty = text_('') _marker = object() not_ = not_ # api @@ -367,7 +364,7 @@ class Configurator( self._set_settings(settings) - if isinstance(debug_logger, string_types): + if isinstance(debug_logger, str): debug_logger = logging.getLogger(debug_logger) if debug_logger is None: @@ -489,11 +486,7 @@ class Configurator( if not hasattr(_registry, 'registerSelfAdapter'): def registerSelfAdapter( - required=None, - provided=None, - name=empty, - info=empty, - event=True, + required=None, provided=None, name='', info='', event=True ): return _registry.registerAdapter( lambda x: x, @@ -759,7 +752,7 @@ class Configurator( when generating an absolute asset specification. If the provided ``relative_spec`` argument is already absolute, or if the ``relative_spec`` is not a string, it is simply returned.""" - if not isinstance(relative_spec, string_types): + if not isinstance(relative_spec, str): return relative_spec return self._make_spec(relative_spec) diff --git a/src/pyramid/config/tweens.py b/src/pyramid/config/tweens.py index 7fc786a97..7f00c5f4f 100644 --- a/src/pyramid/config/tweens.py +++ b/src/pyramid/config/tweens.py @@ -2,7 +2,7 @@ from zope.interface import implementer from pyramid.interfaces import ITweens -from pyramid.compat import string_types, is_nonstr_iter +from pyramid.compat import is_nonstr_iter from pyramid.exceptions import ConfigurationError @@ -105,7 +105,7 @@ class TweensConfiguratorMixin(object): @action_method def _add_tween(self, tween_factory, under=None, over=None, explicit=False): - if not isinstance(tween_factory, string_types): + if not isinstance(tween_factory, str): raise ConfigurationError( 'The "tween_factory" argument to add_tween must be a ' 'dotted name to a globally importable object, not %r' diff --git a/src/pyramid/config/views.py b/src/pyramid/config/views.py index 0c4a17376..718ea8bf3 100644 --- a/src/pyramid/config/views.py +++ b/src/pyramid/config/views.py @@ -32,13 +32,7 @@ from pyramid.interfaces import ( from pyramid import renderers from pyramid.asset import resolve_asset_spec -from pyramid.compat import ( - string_types, - urlparse, - url_quote, - WIN, - is_nonstr_iter, -) +from pyramid.compat import urlparse, url_quote, WIN, is_nonstr_iter from pyramid.decorator import reify @@ -889,7 +883,7 @@ class ViewsConfiguratorMixin(object): if not IInterface.providedBy(r_context): r_context = implementedBy(r_context) - if isinstance(renderer, string_types): + if isinstance(renderer, str): renderer = renderers.RendererHelper( name=renderer, package=self.package, registry=self.registry ) @@ -1582,7 +1576,7 @@ class ViewsConfiguratorMixin(object): ): view = self.maybe_dotted(view) mapper = self.maybe_dotted(mapper) - if isinstance(renderer, string_types): + if isinstance(renderer, str): renderer = renderers.RendererHelper( name=renderer, package=self.package, registry=self.registry ) diff --git a/src/pyramid/encode.py b/src/pyramid/encode.py index 2cf2247da..428bd0c08 100644 --- a/src/pyramid/encode.py +++ b/src/pyramid/encode.py @@ -1,6 +1,4 @@ from pyramid.compat import ( - text_type, - binary_type, is_nonstr_iter, url_quote as _url_quote, url_quote_plus as _quote_plus, @@ -9,9 +7,9 @@ from pyramid.compat import ( def url_quote(val, safe=''): # bw compat api cls = val.__class__ - if cls is text_type: + if cls is str: val = val.encode('utf-8') - elif cls is not binary_type: + elif cls is not bytes: val = str(val).encode('utf-8') return _url_quote(val, safe=safe) @@ -19,9 +17,9 @@ def url_quote(val, safe=''): # bw compat api # bw compat api (dnr) def quote_plus(val, safe=''): cls = val.__class__ - if cls is text_type: + if cls is str: val = val.encode('utf-8') - elif cls is not binary_type: + elif cls is not bytes: val = str(val).encode('utf-8') return _quote_plus(val, safe=safe) diff --git a/src/pyramid/httpexceptions.py b/src/pyramid/httpexceptions.py index 959a45f37..7e83f783f 100644 --- a/src/pyramid/httpexceptions.py +++ b/src/pyramid/httpexceptions.py @@ -137,7 +137,7 @@ from zope.interface import implementer from webob import html_escape as _html_escape from webob.acceptparse import create_accept_header -from pyramid.compat import class_types, text_type, binary_type, text_ +from pyramid.compat import text_ from pyramid.interfaces import IExceptionResponse from pyramid.response import Response @@ -146,13 +146,13 @@ from pyramid.response import Response def _no_escape(value): if value is None: return '' - if not isinstance(value, text_type): + if not isinstance(value, str): if hasattr(value, '__unicode__'): value = value.__unicode__() - if isinstance(value, binary_type): + if isinstance(value, bytes): value = text_(value, 'utf-8') else: - value = text_type(value) + value = str(value) return value @@ -326,7 +326,7 @@ ${body}''' args[k.lower()] = escape(v) body = body_tmpl.substitute(args) page = page_template.substitute(status=self.status, body=body) - if isinstance(page, text_type): + if isinstance(page, str): page = page.encode(self.charset if self.charset else 'UTF-8') self.app_iter = [page] self.body = page @@ -1331,7 +1331,7 @@ status_map = {} code = None for name, value in list(globals().items()): if ( - isinstance(value, class_types) + isinstance(value, type) and issubclass(value, HTTPException) and value not in {HTTPClientError, HTTPServerError} and not name.startswith('_') diff --git a/src/pyramid/path.py b/src/pyramid/path.py index c70be99db..47877ce5d 100644 --- a/src/pyramid/path.py +++ b/src/pyramid/path.py @@ -7,8 +7,6 @@ from zope.interface import implementer from pyramid.interfaces import IAssetDescriptor -from pyramid.compat import string_types - ignore_types = [imp.C_EXTENSION, imp.C_BUILTIN] init_names = [ '__init__%s' % x[0] @@ -101,7 +99,7 @@ class Resolver(object): if package in (None, CALLER_PACKAGE): self.package = package else: - if isinstance(package, string_types): + if isinstance(package, str): try: __import__(package) except ImportError: @@ -307,7 +305,7 @@ class DottedNameResolver(Resolver): v = r.resolve('xml') # v is the xml module """ - if not isinstance(dotted, string_types): + if not isinstance(dotted, str): raise ValueError('%r is not a string' % (dotted,)) package = self.package if package is CALLER_PACKAGE: @@ -328,7 +326,7 @@ class DottedNameResolver(Resolver): v = r.maybe_resolve(xml) # v is the xml module; no exception raised """ - if isinstance(dotted, string_types): + if isinstance(dotted, str): package = self.package if package is CALLER_PACKAGE: package = caller_package() diff --git a/src/pyramid/renderers.py b/src/pyramid/renderers.py index a8e3ec16f..832369fd4 100644 --- a/src/pyramid/renderers.py +++ b/src/pyramid/renderers.py @@ -8,8 +8,6 @@ from zope.interface.registry import Components from pyramid.interfaces import IJSONAdapter, IRendererFactory, IRendererInfo -from pyramid.compat import string_types, text_type - from pyramid.csrf import get_csrf_token from pyramid.decorator import reify @@ -169,7 +167,7 @@ def get_renderer(renderer_name, package=None, registry=None): def string_renderer_factory(info): def _render(value, system): - if not isinstance(value, string_types): + if not isinstance(value, str): value = str(value) request = system.get('request') if request is not None: @@ -485,7 +483,7 @@ class RendererHelper(object): response = response_factory(request) if result is not None: - if isinstance(result, text_type): + if isinstance(result, str): response.text = result elif isinstance(result, bytes): response.body = result diff --git a/src/pyramid/scripts/proutes.py b/src/pyramid/scripts/proutes.py index 2bce7d1de..78c2295d5 100644 --- a/src/pyramid/scripts/proutes.py +++ b/src/pyramid/scripts/proutes.py @@ -7,7 +7,6 @@ import re from zope.interface import Interface from pyramid.paster import bootstrap -from pyramid.compat import string_types from pyramid.interfaces import IRouteRequest from pyramid.config import not_ @@ -188,7 +187,7 @@ def get_route_data(route, registry): view_request_methods[view_module] = [] view_request_methods_order.append(view_module) - if isinstance(request_method, string_types): + if isinstance(request_method, str): request_method = (request_method,) elif isinstance(request_method, not_): request_method = ('!%s' % request_method.value,) diff --git a/src/pyramid/settings.py b/src/pyramid/settings.py index af9433840..d1eb4ff14 100644 --- a/src/pyramid/settings.py +++ b/src/pyramid/settings.py @@ -1,5 +1,3 @@ -from pyramid.compat import string_types - truthy = frozenset(('t', 'true', 'y', 'yes', 'on', '1')) falsey = frozenset(('f', 'false', 'n', 'no', 'off', '0')) @@ -17,7 +15,7 @@ def asbool(s): def aslist_cronly(value): - if isinstance(value, string_types): + if isinstance(value, str): value = filter(None, [x.strip() for x in value.splitlines()]) return list(value) diff --git a/src/pyramid/static.py b/src/pyramid/static.py index 58ad97a46..b300df9ee 100644 --- a/src/pyramid/static.py +++ b/src/pyramid/static.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from functools import lru_cache import json import os @@ -8,8 +9,6 @@ from pkg_resources import resource_exists, resource_filename, resource_isdir from pyramid.asset import abspath_from_asset_spec, resolve_asset_spec -from pyramid.compat import lru_cache, text_ - from pyramid.httpexceptions import HTTPNotFound, HTTPMovedPermanently from pyramid.path import caller_package @@ -18,8 +17,6 @@ from pyramid.response import _guess_type, FileResponse from pyramid.traversal import traversal_path_info -slash = text_('/') - class static_view(object): """ An instance of this class is a callable which can act as a @@ -160,7 +157,7 @@ def _secure_path(path_tuple): return None if any([_contains_slash(item) for item in path_tuple]): return None - encoded = slash.join(path_tuple) # will be unicode + encoded = '/'.join(path_tuple) # will be unicode return encoded diff --git a/src/pyramid/testing.py b/src/pyramid/testing.py index 5fe8f8a62..af8008e45 100644 --- a/src/pyramid/testing.py +++ b/src/pyramid/testing.py @@ -8,7 +8,7 @@ from zope.interface import implementer, alsoProvides from pyramid.interfaces import IRequest, ISession -from pyramid.compat import PYPY, class_types, text_ +from pyramid.compat import PYPY, text_ from pyramid.config import Configurator from pyramid.decorator import reify @@ -642,7 +642,7 @@ def skip_on(*platforms): # pragma: no cover skip = True def decorator(func): - if isinstance(func, class_types): + if isinstance(func, type): if skip: return None else: diff --git a/src/pyramid/traversal.py b/src/pyramid/traversal.py index f46937d73..5ff8d22ad 100644 --- a/src/pyramid/traversal.py +++ b/src/pyramid/traversal.py @@ -1,3 +1,4 @@ +from functools import lru_cache from zope.interface import implementer from zope.interface.interfaces import IInterface @@ -10,14 +11,10 @@ from pyramid.interfaces import ( from pyramid.compat import ( native_, - text_, ascii_native_, - text_type, - binary_type, is_nonstr_iter, decode_path_info, unquote_bytes_to_wsgi, - lru_cache, ) from pyramid.encode import url_quote @@ -28,7 +25,7 @@ from pyramid.threadlocal import get_current_registry PATH_SEGMENT_SAFE = "~!$&'()*+,;=:@" # from webob PATH_SAFE = PATH_SEGMENT_SAFE + "/" -empty = text_('') +empty = '' def find_root(resource): @@ -93,7 +90,7 @@ def find_resource(resource, path): :func:`pyramid.traversal.resource_path_tuple` can always be resolved by ``find_resource``. """ - if isinstance(path, text_type): + if isinstance(path, str): path = ascii_native_(path) D = traverse(resource, path) view_name = D['view_name'] @@ -445,7 +442,7 @@ def traversal_path(path): not. A :exc:`UnicodeEncodeError` will be raised if the Unicode cannot be encoded directly to ASCII. """ - if isinstance(path, text_type): + if isinstance(path, str): # must not possess characters outside ascii path = path.encode('ascii') # we unquote this path exactly like a PEP 3333 server would @@ -550,37 +547,36 @@ def split_path_info(path): _segment_cache = {} -quote_path_segment_doc = """ \ -Return a quoted representation of a 'path segment' (such as -the string ``__name__`` attribute of a resource) as a string. If the -``segment`` passed in is a unicode object, it is converted to a -UTF-8 string, then it is URL-quoted using Python's -``urllib.quote``. If the ``segment`` passed in is a string, it is -URL-quoted using Python's :mod:`urllib.quote`. If the segment -passed in is not a string or unicode object, an error will be -raised. The return value of ``quote_path_segment`` is always a -string, never Unicode. - -You may pass a string of characters that need not be encoded as -the ``safe`` argument to this function. This corresponds to the -``safe`` argument to :mod:`urllib.quote`. - -.. note:: - - The return value for each segment passed to this - function is cached in a module-scope dictionary for - speed: the cached version is returned when possible - rather than recomputing the quoted version. No cache - emptying is ever done for the lifetime of an - application, however. If you pass arbitrary - user-supplied strings to this function (as opposed to - some bounded set of values from a 'working set' known to - your application), it may become a memory leak. -""" - def quote_path_segment(segment, safe=PATH_SEGMENT_SAFE): - """ %s """ % quote_path_segment_doc + """ + Return a quoted representation of a 'path segment' (such as + the string ``__name__`` attribute of a resource) as a string. If the + ``segment`` passed in is a unicode object, it is converted to a + UTF-8 string, then it is URL-quoted using Python's + ``urllib.quote``. If the ``segment`` passed in is a string, it is + URL-quoted using Python's :mod:`urllib.quote`. If the segment + passed in is not a string or unicode object, an error will be + raised. The return value of ``quote_path_segment`` is always a + string, never Unicode. + + You may pass a string of characters that need not be encoded as + the ``safe`` argument to this function. This corresponds to the + ``safe`` argument to :mod:`urllib.quote`. + + .. note:: + + The return value for each segment passed to this + function is cached in a module-scope dictionary for + speed: the cached version is returned when possible + rather than recomputing the quoted version. No cache + emptying is ever done for the lifetime of an + application, however. If you pass arbitrary + user-supplied strings to this function (as opposed to + some bounded set of values from a 'working set' known to + your application), it may become a memory leak. + + """ # The bit of this code that deals with ``_segment_cache`` is an # optimization: we cache all the computation of URL path segments # in this module-scope dictionary with the original string (or @@ -589,7 +585,7 @@ def quote_path_segment(segment, safe=PATH_SEGMENT_SAFE): try: return _segment_cache[(segment, safe)] except KeyError: - if segment.__class__ not in (text_type, binary_type): + if segment.__class__ not in (str, bytes): segment = str(segment) result = url_quote(native_(segment, 'utf-8'), safe) # we don't need a lock to mutate _segment_cache, as the below @@ -598,7 +594,7 @@ def quote_path_segment(segment, safe=PATH_SEGMENT_SAFE): return result -slash = text_('/') +slash = '/' @implementer(ITraverser) diff --git a/src/pyramid/url.py b/src/pyramid/url.py index 00dd13bfe..730b7de23 100644 --- a/src/pyramid/url.py +++ b/src/pyramid/url.py @@ -1,10 +1,11 @@ """ Utility functions for dealing with URLs in pyramid """ +from functools import lru_cache import os from pyramid.interfaces import IResourceURL, IRoutesMapper, IStaticURLInfo -from pyramid.compat import bytes_, lru_cache, string_types +from pyramid.compat import bytes_ from pyramid.encode import url_quote, urlencode from pyramid.path import caller_package from pyramid.threadlocal import get_current_registry @@ -45,7 +46,7 @@ def parse_url_overrides(request, kw): qs = '' if query: - if isinstance(query, string_types): + if isinstance(query, str): qs = '?' + url_quote(query, QUERY_SAFE) else: qs = '?' + urlencode(query, doseq=True) diff --git a/src/pyramid/urldispatch.py b/src/pyramid/urldispatch.py index 1d3f8d91b..c1db3c969 100644 --- a/src/pyramid/urldispatch.py +++ b/src/pyramid/urldispatch.py @@ -3,15 +3,7 @@ from zope.interface import implementer from pyramid.interfaces import IRoutesMapper, IRoute -from pyramid.compat import ( - native_, - text_, - text_type, - string_types, - binary_type, - is_nonstr_iter, - decode_path_info, -) +from pyramid.compat import native_, text_, is_nonstr_iter, decode_path_info from pyramid.exceptions import URLDecodeError @@ -126,7 +118,7 @@ def _compile_route(route): # using the ASCII decoding. We decode it using ASCII because we don't # want to accept bytestrings with high-order characters in them here as # we have no idea what the encoding represents. - if route.__class__ is not text_type: + if route.__class__ is not str: try: route = text_(route, 'ascii') except UnicodeDecodeError: @@ -173,7 +165,7 @@ def _compile_route(route): name, reg = name.split(':', 1) else: reg = '[^/]+' - gen.append('%%(%s)s' % native_(name)) # native + gen.append('%%(%s)s' % name) # native name = '(?P<%s>%s)' % (name, reg) # unicode rpat.append(name) s = pat.pop() # unicode @@ -188,7 +180,7 @@ def _compile_route(route): if remainder: rpat.append('(?P<%s>.*?)' % remainder) # unicode - gen.append('%%(%s)s' % native_(remainder)) # native + gen.append('%%(%s)s' % remainder) # native pattern = ''.join(rpat) + '$' # unicode @@ -201,7 +193,7 @@ def _compile_route(route): # because we don't want to accept bytestrings with high-order # characters in them here as we have no idea what the encoding # represents. - if path.__class__ is not text_type: + if path.__class__ is not str: path = text_(path, 'ascii') m = match(path) if m is None: @@ -226,7 +218,7 @@ def _compile_route(route): def generator(dict): newdict = {} for k, v in dict.items(): - if v.__class__ is binary_type: + if v.__class__ is bytes: # url_quote below needs a native string, not bytes on Py3 v = v.decode('utf-8') @@ -235,11 +227,11 @@ def _compile_route(route): if is_nonstr_iter(v): v = '/'.join([q(x) for x in v]) # native else: - if v.__class__ not in string_types: + if v.__class__ is not str: v = str(v) v = q(v) else: - if v.__class__ not in string_types: + if v.__class__ is not str: v = str(v) # v may be bytes (py2) or native string (py3) v = q(v) diff --git a/src/pyramid/util.py b/src/pyramid/util.py index 39b67471e..d3e5d1578 100644 --- a/src/pyramid/util.py +++ b/src/pyramid/util.py @@ -10,8 +10,6 @@ from pyramid.compat import ( getargspec, im_func, is_nonstr_iter, - integer_types, - string_types, bytes_, text_, native_, @@ -30,7 +28,7 @@ class DottedNameResolver(_DottedNameResolver): def is_string_or_iterable(v): - if isinstance(v, string_types): + if isinstance(v, str): return True if hasattr(v, '__iter__'): return True @@ -324,9 +322,9 @@ def object_description(object): is a boolean, an integer, a list, a tuple, a set, or ``None``, a (possibly shortened) string representation is returned. """ - if isinstance(object, string_types): + if isinstance(object, str): return text_(object) - if isinstance(object, integer_types): + if isinstance(object, int): return text_(str(object)) if isinstance(object, (bool, float, type(None))): return text_(str(object)) diff --git a/tests/test_authentication.py b/tests/test_authentication.py index fc3e60587..87b7da5a8 100644 --- a/tests/test_authentication.py +++ b/tests/test_authentication.py @@ -1272,18 +1272,6 @@ class TestAuthTktCookieHelper(unittest.TestCase): self.assertEqual(val['userid'], '1') self.assertEqual(val['user_data'], 'userid_type:int') - def test_remember_long_userid(self): - from pyramid.compat import long - - helper = self._makeOne('secret') - request = self._makeRequest() - result = helper.remember(request, long(1)) - values = self._parseHeaders(result) - self.assertEqual(len(result), 3) - val = self._cookieValue(values[0]) - self.assertEqual(val['userid'], '1') - self.assertEqual(val['user_data'], 'userid_type:int') - def test_remember_unicode_userid(self): import base64 diff --git a/tests/test_config/test_factories.py b/tests/test_config/test_factories.py index cca528275..bbc38b6cd 100644 --- a/tests/test_config/test_factories.py +++ b/tests/test_config/test_factories.py @@ -160,7 +160,7 @@ class TestFactoriesMixin(unittest.TestCase): config = self._makeOne(autocommit=True) self.assertRaises(AttributeError, config.add_request_method) - def test_add_request_method_with_text_type_name(self): + def test_add_request_method_with_text_name(self): from pyramid.exceptions import ConfigurationError config = self._makeOne(autocommit=True) diff --git a/tests/test_config/test_views.py b/tests/test_config/test_views.py index b72b9b36a..aa5b67050 100644 --- a/tests/test_config/test_views.py +++ b/tests/test_config/test_views.py @@ -2795,15 +2795,6 @@ class TestViewsConfigurationMixin(unittest.TestCase): request = self._makeRequest(config) self.assertRaises(PredicateMismatch, wrapper, context, request) - # Since Python 3 has to be all cool and fancy and different... - def _assertBody(self, response, value): - from pyramid.compat import text_type - - if isinstance(value, text_type): # pragma: no cover - self.assertEqual(response.text, value) - else: # pragma: no cover - self.assertEqual(response.body, value) - def test_add_notfound_view_with_renderer(self): from zope.interface import implementedBy from pyramid.interfaces import IRequest @@ -2820,7 +2811,7 @@ class TestViewsConfigurationMixin(unittest.TestCase): request_iface=IRequest, ) result = view(None, request) - self._assertBody(result, '{}') + self.assertEqual(result.text, '{}') def test_add_forbidden_view_with_renderer(self): from zope.interface import implementedBy @@ -2838,7 +2829,7 @@ class TestViewsConfigurationMixin(unittest.TestCase): request_iface=IRequest, ) result = view(None, request) - self._assertBody(result, '{}') + self.assertEqual(result.text, '{}') def test_set_view_mapper(self): from pyramid.interfaces import IViewMapperFactory diff --git a/tests/test_httpexceptions.py b/tests/test_httpexceptions.py index 4c13e096d..195496e2e 100644 --- a/tests/test_httpexceptions.py +++ b/tests/test_httpexceptions.py @@ -1,6 +1,6 @@ import unittest -from pyramid.compat import bytes_, string_types, text_ +from pyramid.compat import bytes_, text_ class Test_exception_response(unittest.TestCase): @@ -406,7 +406,7 @@ class TestHTTPException(unittest.TestCase): def test_allow_detail_non_str(self): exc = self._makeOne(detail={'error': 'This is a test'}) - self.assertIsInstance(exc.__str__(), string_types) + self.assertIsInstance(exc.__str__(), str) class TestRenderAllExceptionsWithoutArguments(unittest.TestCase): diff --git a/tests/test_traversal.py b/tests/test_traversal.py index b517fa646..ed5e0031e 100644 --- a/tests/test_traversal.py +++ b/tests/test_traversal.py @@ -3,7 +3,7 @@ import unittest from pyramid.testing import cleanUp -from pyramid.compat import text_, native_, text_type, url_quote +from pyramid.compat import text_, native_, url_quote class TraversalPathTests(unittest.TestCase): @@ -71,8 +71,8 @@ class TraversalPathInfoTests(unittest.TestCase): def test_segments_are_unicode(self): result = self._callFUT('/foo/bar') - self.assertEqual(type(result[0]), text_type) - self.assertEqual(type(result[1]), text_type) + self.assertEqual(type(result[0]), str) + self.assertEqual(type(result[1]), str) def test_same_value_returned_if_cached(self): result1 = self._callFUT('/foo/bar') @@ -870,15 +870,6 @@ class QuotePathSegmentTests(unittest.TestCase): result = self._callFUT(s) self.assertEqual(result, '12345') - def test_long(self): - from pyramid.compat import long - import sys - - s = long(sys.maxsize + 1) - result = self._callFUT(s) - expected = str(s) - self.assertEqual(result, expected) - def test_other(self): class Foo(object): def __str__(self): diff --git a/tests/test_view.py b/tests/test_view.py index f82480169..de40df1d5 100644 --- a/tests/test_view.py +++ b/tests/test_view.py @@ -417,12 +417,11 @@ class RenderViewToIterableTests(BaseTest, unittest.TestCase): from pyramid.request import Request from pyramid.config import Configurator from pyramid.view import render_view - from webob.compat import text_type config = Configurator(settings={}) def view(request): - request.response.text = text_type('') + request.response.text = '' return request.response config.add_view(name='test', view=view) -- cgit v1.2.3 From 4cb6a965f7e8ce44ef609436f314b56119131f73 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 12 Nov 2018 23:19:06 -0600 Subject: get rid of pickle alias --- src/pyramid/compat.py | 2 -- src/pyramid/session.py | 3 ++- tests/test_session.py | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/pyramid/compat.py b/src/pyramid/compat.py index 47e842fbb..c2d7c7f3f 100644 --- a/src/pyramid/compat.py +++ b/src/pyramid/compat.py @@ -13,8 +13,6 @@ except BaseException: # pragma: no cover __pypy__ = None PYPY = False -import pickle - def text_(s, encoding='latin-1', errors='strict'): """ If ``s`` is an instance of ``bytes``, return diff --git a/src/pyramid/session.py b/src/pyramid/session.py index e9486ec6d..1b1bfb3e5 100644 --- a/src/pyramid/session.py +++ b/src/pyramid/session.py @@ -1,5 +1,6 @@ import binascii import os +import pickle import time from zope.deprecation import deprecated @@ -7,7 +8,7 @@ from zope.interface import implementer from webob.cookies import JSONSerializer, SignedSerializer -from pyramid.compat import pickle, text_, bytes_, native_ +from pyramid.compat import text_, bytes_, native_ from pyramid.csrf import check_csrf_origin, check_csrf_token from pyramid.interfaces import ISession diff --git a/tests/test_session.py b/tests/test_session.py index f7e7bab05..8e5e82bb2 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -1,8 +1,8 @@ import base64 import json +import pickle import unittest from pyramid import testing -from pyramid.compat import pickle class SharedCookieSessionTests(object): -- cgit v1.2.3 From d879bdfd477a138746e8593b8d9f30c492ff71e1 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 12 Nov 2018 23:20:48 -0600 Subject: remove exec alias --- src/pyramid/compat.py | 8 -------- src/pyramid/scripts/pshell.py | 3 +-- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/pyramid/compat.py b/src/pyramid/compat.py index c2d7c7f3f..e502cbce4 100644 --- a/src/pyramid/compat.py +++ b/src/pyramid/compat.py @@ -62,11 +62,6 @@ url_unquote_text = url_unquote url_unquote_native = url_unquote -import builtins - -exec_ = getattr(builtins, "exec") - - def reraise(tp, value, tb=None): if value is None: value = tp @@ -75,9 +70,6 @@ def reraise(tp, value, tb=None): raise value -del builtins - - def iteritems_(d): return d.items() diff --git a/src/pyramid/scripts/pshell.py b/src/pyramid/scripts/pshell.py index e63114d18..a9f02e408 100644 --- a/src/pyramid/scripts/pshell.py +++ b/src/pyramid/scripts/pshell.py @@ -6,7 +6,6 @@ import sys import textwrap import pkg_resources -from pyramid.compat import exec_ from pyramid.util import DottedNameResolver from pyramid.util import make_contextmanager from pyramid.paster import bootstrap @@ -214,7 +213,7 @@ class PShellCommand(object): if self.pystartup and os.path.isfile(self.pystartup): with open(self.pystartup, 'rb') as fp: - exec_(fp.read().decode('utf-8'), env) + exec(fp.read().decode('utf-8'), env) if '__builtins__' in env: del env['__builtins__'] -- cgit v1.2.3 From e2c7bbf5566f5be4d1b5b58d3e23948b4dc9b651 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 12 Nov 2018 23:27:59 -0600 Subject: get rid of NativeIO alias and a few others --- src/pyramid/compat.py | 28 ---------------------------- src/pyramid/security.py | 3 +-- tests/test_scripts/test_prequest.py | 21 ++++++--------------- tests/test_scripts/test_pserve.py | 5 ++--- 4 files changed, 9 insertions(+), 48 deletions(-) diff --git a/src/pyramid/compat.py b/src/pyramid/compat.py index e502cbce4..3ad8720d6 100644 --- a/src/pyramid/compat.py +++ b/src/pyramid/compat.py @@ -70,22 +70,6 @@ def reraise(tp, value, tb=None): raise value -def iteritems_(d): - return d.items() - - -def itervalues_(d): - return d.values() - - -def iterkeys_(d): - return d.keys() - - -def map_(*arg): - return list(map(*arg)) - - def is_nonstr_iter(v): if isinstance(v, str): return False @@ -95,20 +79,10 @@ def is_nonstr_iter(v): im_func = '__func__' im_self = '__self__' -import configparser - from http.cookies import SimpleCookie from html import escape -input_ = input - -from io import StringIO as NativeIO - -# "json" is not an API; it's here to support older pyramid_debugtoolbar -# versions which attempt to import it -import json - # see PEP 3333 for why we encode WSGI PATH_INFO to latin-1 before # decoding it to utf-8 def decode_path_info(path): @@ -130,8 +104,6 @@ def is_bound_method(ob): # support annotations and keyword-only arguments in PY3 from inspect import getfullargspec as getargspec -from itertools import zip_longest - def is_unbound_method(fn): """ diff --git a/src/pyramid/security.py b/src/pyramid/security.py index 08ae295d8..f8743e9a7 100644 --- a/src/pyramid/security.py +++ b/src/pyramid/security.py @@ -8,7 +8,6 @@ from pyramid.interfaces import ( IViewClassifier, ) -from pyramid.compat import map_ from pyramid.threadlocal import get_current_registry Everyone = 'system.Everyone' @@ -149,7 +148,7 @@ def view_execution_permitted(context, request, name=''): """ reg = _get_registry(request) - provides = [IViewClassifier] + map_(providedBy, (request, context)) + provides = [IViewClassifier] + [providedBy(x) for x in (request, context)] # XXX not sure what to do here about using _find_views or analogue; # for now let's just keep it as-is view = reg.adapters.lookup(provides, ISecuredView, name=name) diff --git a/tests/test_scripts/test_prequest.py b/tests/test_scripts/test_prequest.py index 1521172bc..aadde719a 100644 --- a/tests/test_scripts/test_prequest.py +++ b/tests/test_scripts/test_prequest.py @@ -1,3 +1,4 @@ +from io import StringIO import unittest from . import dummy @@ -134,13 +135,11 @@ class TestPRequestCommand(unittest.TestCase): self.assertEqual(self._out, ['abc']) def test_command_method_post(self): - from pyramid.compat import NativeIO - command = self._makeOne( ['', '--method=POST', 'development.ini', '/'], [('Content-Type', 'text/html; charset=UTF-8')], ) - stdin = NativeIO() + stdin = StringIO() command.stdin = stdin command.run() self.assertEqual(self._environ['REQUEST_METHOD'], 'POST') @@ -150,13 +149,11 @@ class TestPRequestCommand(unittest.TestCase): self.assertEqual(self._out, ['abc']) def test_command_method_put(self): - from pyramid.compat import NativeIO - command = self._makeOne( ['', '--method=PUT', 'development.ini', '/'], [('Content-Type', 'text/html; charset=UTF-8')], ) - stdin = NativeIO() + stdin = StringIO() command.stdin = stdin command.run() self.assertEqual(self._environ['REQUEST_METHOD'], 'PUT') @@ -166,13 +163,11 @@ class TestPRequestCommand(unittest.TestCase): self.assertEqual(self._out, ['abc']) def test_command_method_patch(self): - from pyramid.compat import NativeIO - command = self._makeOne( ['', '--method=PATCH', 'development.ini', '/'], [('Content-Type', 'text/html; charset=UTF-8')], ) - stdin = NativeIO() + stdin = StringIO() command.stdin = stdin command.run() self.assertEqual(self._environ['REQUEST_METHOD'], 'PATCH') @@ -182,13 +177,11 @@ class TestPRequestCommand(unittest.TestCase): self.assertEqual(self._out, ['abc']) def test_command_method_propfind(self): - from pyramid.compat import NativeIO - command = self._makeOne( ['', '--method=PROPFIND', 'development.ini', '/'], [('Content-Type', 'text/html; charset=UTF-8')], ) - stdin = NativeIO() + stdin = StringIO() command.stdin = stdin command.run() self.assertEqual(self._environ['REQUEST_METHOD'], 'PROPFIND') @@ -196,13 +189,11 @@ class TestPRequestCommand(unittest.TestCase): self.assertEqual(self._out, ['abc']) def test_command_method_options(self): - from pyramid.compat import NativeIO - command = self._makeOne( ['', '--method=OPTIONS', 'development.ini', '/'], [('Content-Type', 'text/html; charset=UTF-8')], ) - stdin = NativeIO() + stdin = StringIO() command.stdin = stdin command.run() self.assertEqual(self._environ['REQUEST_METHOD'], 'OPTIONS') diff --git a/tests/test_scripts/test_pserve.py b/tests/test_scripts/test_pserve.py index b85f4ddb7..a573f2e5b 100644 --- a/tests/test_scripts/test_pserve.py +++ b/tests/test_scripts/test_pserve.py @@ -1,3 +1,4 @@ +from io import StringIO import os import unittest from . import dummy @@ -8,9 +9,7 @@ here = os.path.abspath(os.path.dirname(__file__)) class TestPServeCommand(unittest.TestCase): def setUp(self): - from pyramid.compat import NativeIO - - self.out_ = NativeIO() + self.out_ = StringIO() def out(self, msg): self.out_.write(msg) -- cgit v1.2.3 From 656ce2b499bd05ceb9b0ae492a0be15a07dc283f Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Tue, 13 Nov 2018 10:31:58 -0600 Subject: get rid of some unnecessary constants --- src/pyramid/traversal.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/pyramid/traversal.py b/src/pyramid/traversal.py index 5ff8d22ad..5adf728b6 100644 --- a/src/pyramid/traversal.py +++ b/src/pyramid/traversal.py @@ -25,8 +25,6 @@ from pyramid.threadlocal import get_current_registry PATH_SEGMENT_SAFE = "~!$&'()*+,;=:@" # from webob PATH_SAFE = PATH_SEGMENT_SAFE + "/" -empty = '' - def find_root(resource): """ Find the root node in the resource tree to which ``resource`` @@ -594,9 +592,6 @@ def quote_path_segment(segment, safe=PATH_SEGMENT_SAFE): return result -slash = '/' - - @implementer(ITraverser) class ResourceTreeTraverser(object): """ A resource tree traverser that should be used (for speed) when @@ -616,12 +611,12 @@ class ResourceTreeTraverser(object): if matchdict is not None: - path = matchdict.get('traverse', slash) or slash + path = matchdict.get('traverse', '/') or '/' if is_nonstr_iter(path): # this is a *traverse stararg (not a {traverse}) # routing has already decoded these elements, so we just # need to join them - path = '/' + slash.join(path) or slash + path = '/' + '/'.join(path) or '/' subpath = matchdict.get('subpath', ()) if not is_nonstr_iter(subpath): @@ -635,10 +630,10 @@ class ResourceTreeTraverser(object): subpath = () try: # empty if mounted under a path in mod_wsgi, for example - path = request.path_info or slash + path = request.path_info or '/' except KeyError: # if environ['PATH_INFO'] is just not there - path = slash + path = '/' except UnicodeDecodeError as e: raise URLDecodeError( e.encoding, e.object, e.start, e.end, e.reason @@ -660,7 +655,7 @@ class ResourceTreeTraverser(object): root = self.root ob = vroot = root - if vpath == slash: # invariant: vpath must not be empty + if vpath == '/': # invariant: vpath must not be empty # prevent a call to traversal_path if we know it's going # to return the empty tuple vpath_tuple = () @@ -714,7 +709,7 @@ class ResourceTreeTraverser(object): return { 'context': ob, - 'view_name': empty, + 'view_name': '', 'subpath': subpath, 'traversed': vpath_tuple, 'virtual_root': vroot, -- cgit v1.2.3 From 9ead1d8e84edcb86ea9e07b4d2c31e7b74a098ed Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 14 Nov 2018 18:45:53 -0600 Subject: move is_unbound_method to pyramid.util --- src/pyramid/compat.py | 28 ---------------------------- src/pyramid/util.py | 34 ++++++++++++++++++++++++---------- src/pyramid/viewderivers.py | 9 ++++++--- tests/test_compat.py | 32 -------------------------------- tests/test_config/test_init.py | 6 ++---- tests/test_config/test_views.py | 14 +++++++------- tests/test_util.py | 23 +++++++++++++++++++++++ 7 files changed, 62 insertions(+), 84 deletions(-) delete mode 100644 tests/test_compat.py diff --git a/src/pyramid/compat.py b/src/pyramid/compat.py index 3ad8720d6..12b4b7b00 100644 --- a/src/pyramid/compat.py +++ b/src/pyramid/compat.py @@ -76,9 +76,6 @@ def is_nonstr_iter(v): return hasattr(v, '__iter__') -im_func = '__func__' -im_self = '__self__' - from http.cookies import SimpleCookie from html import escape @@ -95,28 +92,3 @@ from urllib.parse import unquote_to_bytes def unquote_bytes_to_wsgi(bytestring): return unquote_to_bytes(bytestring).decode('latin-1') - - -def is_bound_method(ob): - return inspect.ismethod(ob) and getattr(ob, im_self, None) is not None - - -# support annotations and keyword-only arguments in PY3 -from inspect import getfullargspec as getargspec - - -def is_unbound_method(fn): - """ - This consistently verifies that the callable is bound to a - class. - """ - is_bound = is_bound_method(fn) - - if not is_bound and inspect.isroutine(fn): - spec = getargspec(fn) - has_self = len(spec.args) > 0 and spec.args[0] == 'self' - - if inspect.isfunction(fn) and has_self: - return True - - return False diff --git a/src/pyramid/util.py b/src/pyramid/util.py index d3e5d1578..23cce195a 100644 --- a/src/pyramid/util.py +++ b/src/pyramid/util.py @@ -6,14 +6,7 @@ import weakref from pyramid.exceptions import ConfigurationError, CyclicDependencyError -from pyramid.compat import ( - getargspec, - im_func, - is_nonstr_iter, - bytes_, - text_, - native_, -) +from pyramid.compat import is_nonstr_iter, bytes_, text_, native_ from pyramid.path import DottedNameResolver as _DottedNameResolver @@ -616,13 +609,13 @@ def takes_one_arg(callee, attr=None, argname=None): return False try: - argspec = getargspec(fn) + argspec = inspect.getfullargspec(fn) except TypeError: return False args = argspec[0] - if hasattr(fn, im_func) or ismethod: + if hasattr(fn, '__func__') or ismethod: # it's an instance method (or unbound method on py2) if not args: return False @@ -653,3 +646,24 @@ class SimpleSerializer(object): def dumps(self, appstruct): return bytes_(appstruct) + + +def is_bound_method(ob): + return inspect.ismethod(ob) and getattr(ob, '__self__', None) is not None + + +def is_unbound_method(fn): + """ + This consistently verifies that the callable is bound to a + class. + """ + is_bound = is_bound_method(fn) + + if not is_bound and inspect.isroutine(fn): + spec = inspect.getfullargspec(fn) + has_self = len(spec.args) > 0 and spec.args[0] == 'self' + + if inspect.isfunction(fn) and has_self: + return True + + return False diff --git a/src/pyramid/viewderivers.py b/src/pyramid/viewderivers.py index fbe0c252c..181cc9e5c 100644 --- a/src/pyramid/viewderivers.py +++ b/src/pyramid/viewderivers.py @@ -17,11 +17,14 @@ from pyramid.interfaces import ( IViewMapperFactory, ) -from pyramid.compat import is_bound_method, is_unbound_method - from pyramid.exceptions import ConfigurationError from pyramid.httpexceptions import HTTPForbidden -from pyramid.util import object_description, takes_one_arg +from pyramid.util import ( + object_description, + takes_one_arg, + is_bound_method, + is_unbound_method, +) from pyramid.view import render_view_to_response from pyramid import renderers diff --git a/tests/test_compat.py b/tests/test_compat.py deleted file mode 100644 index 4a14caedf..000000000 --- a/tests/test_compat.py +++ /dev/null @@ -1,32 +0,0 @@ -import unittest -from pyramid.compat import is_unbound_method - - -class TestUnboundMethods(unittest.TestCase): - def test_old_style_bound(self): - self.assertFalse(is_unbound_method(OldStyle().run)) - - def test_new_style_bound(self): - self.assertFalse(is_unbound_method(NewStyle().run)) - - def test_old_style_unbound(self): - self.assertTrue(is_unbound_method(OldStyle.run)) - - def test_new_style_unbound(self): - self.assertTrue(is_unbound_method(NewStyle.run)) - - def test_normal_func_unbound(self): - def func(): # pragma: no cover - return 'OK' - - self.assertFalse(is_unbound_method(func)) - - -class OldStyle: - def run(self): # pragma: no cover - return 'OK' - - -class NewStyle(object): - def run(self): # pragma: no cover - return 'OK' diff --git a/tests/test_config/test_init.py b/tests/test_config/test_init.py index 1cd63f113..ce2b042ec 100644 --- a/tests/test_config/test_init.py +++ b/tests/test_config/test_init.py @@ -1,8 +1,6 @@ import os import unittest -from pyramid.compat import im_func - from . import dummy_tween_factory from . import dummy_include from . import dummy_extend @@ -1205,7 +1203,7 @@ test_config.dummy_include2""" directives = {'foo': (foo, True)} config.registry._directives = directives foo_meth = config.foo - self.assertTrue(getattr(foo_meth, im_func).__docobj__ is foo) + self.assertTrue(getattr(foo_meth, '__func__').__docobj__ is foo) def test___getattr__matches_no_action_wrap(self): config = self._makeOne() @@ -1216,7 +1214,7 @@ test_config.dummy_include2""" directives = {'foo': (foo, False)} config.registry._directives = directives foo_meth = config.foo - self.assertTrue(getattr(foo_meth, im_func) is foo) + self.assertTrue(getattr(foo_meth, '__func__') is foo) class TestConfigurator_add_directive(unittest.TestCase): diff --git a/tests/test_config/test_views.py b/tests/test_config/test_views.py index aa5b67050..d530542b7 100644 --- a/tests/test_config/test_views.py +++ b/tests/test_config/test_views.py @@ -3,7 +3,7 @@ import unittest from zope.interface import implementer from pyramid import testing -from pyramid.compat import im_func, text_ +from pyramid.compat import text_ from pyramid.exceptions import ConfigurationError from pyramid.exceptions import ConfigurationExecutionError from pyramid.exceptions import ConfigurationConflictError @@ -3723,16 +3723,16 @@ class Test_preserve_view_attrs(unittest.TestCase): self.assertTrue(view1.__module__ is view2.__module__) self.assertTrue(view1.__name__ is view2.__name__) self.assertTrue( - getattr(view1.__call_permissive__, im_func) - is getattr(view2.__call_permissive__, im_func) + getattr(view1.__call_permissive__, '__func__') + is getattr(view2.__call_permissive__, '__func__') ) self.assertTrue( - getattr(view1.__permitted__, im_func) - is getattr(view2.__permitted__, im_func) + getattr(view1.__permitted__, '__func__') + is getattr(view2.__permitted__, '__func__') ) self.assertTrue( - getattr(view1.__predicated__, im_func) - is getattr(view2.__predicated__, im_func) + getattr(view1.__predicated__, '__func__') + is getattr(view2.__predicated__, '__func__') ) diff --git a/tests/test_util.py b/tests/test_util.py index 8af5fe557..676290676 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -1223,3 +1223,26 @@ class TestSimpleSerializer(unittest.TestCase): def test_dumps(self): inst = self._makeOne() self.assertEqual(inst.dumps('abc'), bytes_('abc')) + + +class TestUnboundMethods(unittest.TestCase): + class Dummy(object): + def run(self): # pragma: no cover + return 'OK' + + def _callFUT(self, val): + from pyramid.util import is_unbound_method + + return is_unbound_method(val) + + def test_bound_method(self): + self.assertFalse(self._callFUT(self.Dummy().run)) + + def test_unbound_method(self): + self.assertTrue(self._callFUT(self.Dummy.run)) + + def test_normal_func_unbound(self): + def func(): # pragma: no cover + return 'OK' + + self.assertFalse(self._callFUT(func)) -- cgit v1.2.3 From 5e7361044cbd18d5d9f72f603a9861c1e69a037b Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 14 Nov 2018 18:50:02 -0600 Subject: remove SimpleCookie and escape shims --- src/pyramid/compat.py | 4 ---- tests/pkgs/permbugapp/__init__.py | 2 +- tests/test_authentication.py | 3 +-- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/pyramid/compat.py b/src/pyramid/compat.py index 12b4b7b00..2dde5eeee 100644 --- a/src/pyramid/compat.py +++ b/src/pyramid/compat.py @@ -76,10 +76,6 @@ def is_nonstr_iter(v): return hasattr(v, '__iter__') -from http.cookies import SimpleCookie - -from html import escape - # see PEP 3333 for why we encode WSGI PATH_INFO to latin-1 before # decoding it to utf-8 def decode_path_info(path): diff --git a/tests/pkgs/permbugapp/__init__.py b/tests/pkgs/permbugapp/__init__.py index aedd405f8..72b5d9c17 100644 --- a/tests/pkgs/permbugapp/__init__.py +++ b/tests/pkgs/permbugapp/__init__.py @@ -1,4 +1,4 @@ -from pyramid.compat import escape +from html import escape from pyramid.security import view_execution_permitted from pyramid.response import Response diff --git a/tests/test_authentication.py b/tests/test_authentication.py index 87b7da5a8..a18ccaeb4 100644 --- a/tests/test_authentication.py +++ b/tests/test_authentication.py @@ -1,3 +1,4 @@ +from http.cookies import SimpleCookie import unittest import warnings from pyramid import testing @@ -706,8 +707,6 @@ class TestAuthTktCookieHelper(unittest.TestCase): return cookie def _parseCookie(self, cookie): - from pyramid.compat import SimpleCookie - cookies = SimpleCookie() cookies.load(cookie) return cookies.get('auth_tkt') -- cgit v1.2.3 From a480acfa4381604320525b5e76e22bfb16d9c964 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 14 Nov 2018 18:54:09 -0600 Subject: move reraise to pyramid.util --- src/pyramid/compat.py | 8 -------- src/pyramid/config/actions.py | 2 +- src/pyramid/tweens.py | 2 +- src/pyramid/util.py | 8 ++++++++ src/pyramid/view.py | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/pyramid/compat.py b/src/pyramid/compat.py index 2dde5eeee..17527271e 100644 --- a/src/pyramid/compat.py +++ b/src/pyramid/compat.py @@ -62,14 +62,6 @@ url_unquote_text = url_unquote url_unquote_native = url_unquote -def reraise(tp, value, tb=None): - if value is None: - value = tp - if value.__traceback__ is not tb: - raise value.with_traceback(tb) - raise value - - def is_nonstr_iter(v): if isinstance(v, str): return False diff --git a/src/pyramid/config/actions.py b/src/pyramid/config/actions.py index 9c1227d4a..4a526e242 100644 --- a/src/pyramid/config/actions.py +++ b/src/pyramid/config/actions.py @@ -5,7 +5,6 @@ import sys import traceback from zope.interface import implementer -from pyramid.compat import reraise from pyramid.exceptions import ( ConfigurationConflictError, ConfigurationError, @@ -14,6 +13,7 @@ from pyramid.exceptions import ( from pyramid.interfaces import IActionInfo from pyramid.registry import undefer from pyramid.util import is_nonstr_iter +from pyramid.util import reraise class ActionConfiguratorMixin(object): diff --git a/src/pyramid/tweens.py b/src/pyramid/tweens.py index 839c53b8f..b5660b44b 100644 --- a/src/pyramid/tweens.py +++ b/src/pyramid/tweens.py @@ -1,7 +1,7 @@ import sys -from pyramid.compat import reraise from pyramid.httpexceptions import HTTPNotFound +from pyramid.util import reraise def _error_handler(request, exc): diff --git a/src/pyramid/util.py b/src/pyramid/util.py index 23cce195a..544fdc6de 100644 --- a/src/pyramid/util.py +++ b/src/pyramid/util.py @@ -667,3 +667,11 @@ def is_unbound_method(fn): return True return False + + +def reraise(tp, value, tb=None): + if value is None: + value = tp + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value diff --git a/src/pyramid/view.py b/src/pyramid/view.py index 9f58e72ae..9e85d7281 100644 --- a/src/pyramid/view.py +++ b/src/pyramid/view.py @@ -16,7 +16,6 @@ from pyramid.interfaces import ( ) from pyramid.compat import decode_path_info -from pyramid.compat import reraise as reraise_ from pyramid.exceptions import ConfigurationError, PredicateMismatch @@ -29,6 +28,7 @@ from pyramid.httpexceptions import ( from pyramid.threadlocal import get_current_registry, manager from pyramid.util import hide_attrs +from pyramid.util import reraise as reraise_ _marker = object() -- cgit v1.2.3 From 8c0cbe3be52a56ba9dc8ab6ced028dab9b48b333 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 14 Nov 2018 18:58:56 -0600 Subject: move is_nonstr_iter to pyramid.util --- src/pyramid/authorization.py | 4 ++-- src/pyramid/compat.py | 6 ------ src/pyramid/config/predicates.py | 3 ++- src/pyramid/config/tweens.py | 8 +++++--- src/pyramid/config/views.py | 4 ++-- src/pyramid/encode.py | 2 +- src/pyramid/predicates.py | 4 +--- src/pyramid/traversal.py | 2 +- src/pyramid/urldispatch.py | 4 +++- src/pyramid/util.py | 8 +++++++- 10 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/pyramid/authorization.py b/src/pyramid/authorization.py index 974748765..6056a8d25 100644 --- a/src/pyramid/authorization.py +++ b/src/pyramid/authorization.py @@ -4,10 +4,10 @@ from pyramid.interfaces import IAuthorizationPolicy from pyramid.location import lineage -from pyramid.compat import is_nonstr_iter - from pyramid.security import ACLAllowed, ACLDenied, Allow, Deny, Everyone +from pyramid.util import is_nonstr_iter + @implementer(IAuthorizationPolicy) class ACLAuthorizationPolicy(object): diff --git a/src/pyramid/compat.py b/src/pyramid/compat.py index 17527271e..1010e93f1 100644 --- a/src/pyramid/compat.py +++ b/src/pyramid/compat.py @@ -62,12 +62,6 @@ url_unquote_text = url_unquote url_unquote_native = url_unquote -def is_nonstr_iter(v): - if isinstance(v, str): - return False - return hasattr(v, '__iter__') - - # see PEP 3333 for why we encode WSGI PATH_INFO to latin-1 before # decoding it to utf-8 def decode_path_info(path): diff --git a/src/pyramid/config/predicates.py b/src/pyramid/config/predicates.py index 31e770562..789bcd6ae 100644 --- a/src/pyramid/config/predicates.py +++ b/src/pyramid/config/predicates.py @@ -1,12 +1,13 @@ from hashlib import md5 from webob.acceptparse import Accept -from pyramid.compat import bytes_, is_nonstr_iter +from pyramid.compat import bytes_ from pyramid.exceptions import ConfigurationError from pyramid.interfaces import IPredicateList, PHASE1_CONFIG from pyramid.predicates import Notted from pyramid.registry import predvalseq from pyramid.util import TopologicalSorter +from pyramid.util import is_nonstr_iter MAX_ORDER = 1 << 30 diff --git a/src/pyramid/config/tweens.py b/src/pyramid/config/tweens.py index 7f00c5f4f..c85639d14 100644 --- a/src/pyramid/config/tweens.py +++ b/src/pyramid/config/tweens.py @@ -2,13 +2,15 @@ from zope.interface import implementer from pyramid.interfaces import ITweens -from pyramid.compat import is_nonstr_iter - from pyramid.exceptions import ConfigurationError from pyramid.tweens import MAIN, INGRESS, EXCVIEW -from pyramid.util import is_string_or_iterable, TopologicalSorter +from pyramid.util import ( + is_nonstr_iter, + is_string_or_iterable, + TopologicalSorter, +) from pyramid.config.actions import action_method diff --git a/src/pyramid/config/views.py b/src/pyramid/config/views.py index 718ea8bf3..0039cd8fe 100644 --- a/src/pyramid/config/views.py +++ b/src/pyramid/config/views.py @@ -32,7 +32,7 @@ from pyramid.interfaces import ( from pyramid import renderers from pyramid.asset import resolve_asset_spec -from pyramid.compat import urlparse, url_quote, WIN, is_nonstr_iter +from pyramid.compat import urlparse, url_quote, WIN from pyramid.decorator import reify @@ -53,7 +53,7 @@ from pyramid.url import parse_url_overrides from pyramid.view import AppendSlashNotFoundViewFactory -from pyramid.util import as_sorted_tuple, TopologicalSorter +from pyramid.util import as_sorted_tuple, is_nonstr_iter, TopologicalSorter import pyramid.predicates import pyramid.viewderivers diff --git a/src/pyramid/encode.py b/src/pyramid/encode.py index 428bd0c08..1ed2c5a55 100644 --- a/src/pyramid/encode.py +++ b/src/pyramid/encode.py @@ -1,8 +1,8 @@ from pyramid.compat import ( - is_nonstr_iter, url_quote as _url_quote, url_quote_plus as _quote_plus, ) +from pyramid.util import is_nonstr_iter def url_quote(val, safe=''): # bw compat api diff --git a/src/pyramid/predicates.py b/src/pyramid/predicates.py index 280f6c03c..5a1127fb3 100644 --- a/src/pyramid/predicates.py +++ b/src/pyramid/predicates.py @@ -2,8 +2,6 @@ import re from pyramid.exceptions import ConfigurationError -from pyramid.compat import is_nonstr_iter - from pyramid.csrf import check_csrf_token from pyramid.traversal import ( find_interface, @@ -12,7 +10,7 @@ from pyramid.traversal import ( ) from pyramid.urldispatch import _compile_route -from pyramid.util import as_sorted_tuple, object_description +from pyramid.util import as_sorted_tuple, is_nonstr_iter, object_description _marker = object() diff --git a/src/pyramid/traversal.py b/src/pyramid/traversal.py index 5adf728b6..429b12e23 100644 --- a/src/pyramid/traversal.py +++ b/src/pyramid/traversal.py @@ -12,7 +12,6 @@ from pyramid.interfaces import ( from pyramid.compat import ( native_, ascii_native_, - is_nonstr_iter, decode_path_info, unquote_bytes_to_wsgi, ) @@ -21,6 +20,7 @@ from pyramid.encode import url_quote from pyramid.exceptions import URLDecodeError from pyramid.location import lineage from pyramid.threadlocal import get_current_registry +from pyramid.util import is_nonstr_iter PATH_SEGMENT_SAFE = "~!$&'()*+,;=:@" # from webob PATH_SAFE = PATH_SEGMENT_SAFE + "/" diff --git a/src/pyramid/urldispatch.py b/src/pyramid/urldispatch.py index c1db3c969..6d25b2dd5 100644 --- a/src/pyramid/urldispatch.py +++ b/src/pyramid/urldispatch.py @@ -3,12 +3,14 @@ from zope.interface import implementer from pyramid.interfaces import IRoutesMapper, IRoute -from pyramid.compat import native_, text_, is_nonstr_iter, decode_path_info +from pyramid.compat import native_, text_, decode_path_info from pyramid.exceptions import URLDecodeError from pyramid.traversal import quote_path_segment, split_path_info, PATH_SAFE +from pyramid.util import is_nonstr_iter + _marker = object() diff --git a/src/pyramid/util.py b/src/pyramid/util.py index 544fdc6de..6cd8225aa 100644 --- a/src/pyramid/util.py +++ b/src/pyramid/util.py @@ -6,7 +6,7 @@ import weakref from pyramid.exceptions import ConfigurationError, CyclicDependencyError -from pyramid.compat import is_nonstr_iter, bytes_, text_, native_ +from pyramid.compat import bytes_, text_, native_ from pyramid.path import DottedNameResolver as _DottedNameResolver @@ -20,6 +20,12 @@ class DottedNameResolver(_DottedNameResolver): _DottedNameResolver.__init__(self, package) +def is_nonstr_iter(v): + if isinstance(v, str): + return False + return hasattr(v, '__iter__') + + def is_string_or_iterable(v): if isinstance(v, str): return True -- cgit v1.2.3 From 0b570220d9f442700eb97c5a5c4eca6ab03a1ee4 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 14 Nov 2018 19:13:07 -0600 Subject: remove urllib.parse shims --- src/pyramid/authentication.py | 17 ++++------------- src/pyramid/compat.py | 13 ------------- src/pyramid/config/routes.py | 4 ++-- src/pyramid/config/views.py | 14 ++++++-------- src/pyramid/csrf.py | 5 +++-- src/pyramid/encode.py | 7 +++---- src/pyramid/scripts/prequest.py | 4 ++-- tests/test_integration.py | 7 ++++--- tests/test_traversal.py | 7 ++++--- 9 files changed, 28 insertions(+), 50 deletions(-) diff --git a/src/pyramid/authentication.py b/src/pyramid/authentication.py index 7810745b5..05131a521 100644 --- a/src/pyramid/authentication.py +++ b/src/pyramid/authentication.py @@ -6,19 +6,14 @@ import hashlib import base64 import re import time as time_mod +from urllib.parse import quote, unquote import warnings from zope.interface import implementer from webob.cookies import CookieProfile -from pyramid.compat import ( - url_unquote, - url_quote, - bytes_, - ascii_native_, - native_, -) +from pyramid.compat import bytes_, ascii_native_, native_ from pyramid.interfaces import IAuthenticationPolicy, IDebugLogger @@ -724,11 +719,7 @@ class AuthTicket(object): ) def cookie_value(self): - v = '%s%08x%s!' % ( - self.digest(), - int(self.time), - url_quote(self.userid), - ) + v = '%s%08x%s!' % (self.digest(), int(self.time), quote(self.userid)) if self.tokens: v += self.tokens + '!' v += self.user_data @@ -767,7 +758,7 @@ def parse_ticket(secret, ticket, ip, hashalg='md5'): userid, data = ticket[digest_size + 8 :].split('!', 1) except ValueError: raise BadTicket('userid is not followed by !') - userid = url_unquote(userid) + userid = unquote(userid) if '!' in data: tokens, user_data = data.split('!', 1) else: # pragma: no cover (never generated) diff --git a/src/pyramid/compat.py b/src/pyramid/compat.py index 1010e93f1..10d3c4998 100644 --- a/src/pyramid/compat.py +++ b/src/pyramid/compat.py @@ -49,19 +49,6 @@ def native_(s, encoding='latin-1', errors='strict'): return str(s, encoding, errors) -from urllib import parse - -urlparse = parse -from urllib.parse import quote as url_quote -from urllib.parse import quote_plus as url_quote_plus -from urllib.parse import unquote as url_unquote -from urllib.parse import urlencode as url_encode -from urllib.request import urlopen as url_open - -url_unquote_text = url_unquote -url_unquote_native = url_unquote - - # see PEP 3333 for why we encode WSGI PATH_INFO to latin-1 before # decoding it to utf-8 def decode_path_info(path): diff --git a/src/pyramid/config/routes.py b/src/pyramid/config/routes.py index 52540c935..4b26b7481 100644 --- a/src/pyramid/config/routes.py +++ b/src/pyramid/config/routes.py @@ -1,7 +1,7 @@ import contextlib +from urllib.parse import urlparse import warnings -from pyramid.compat import urlparse from pyramid.interfaces import ( IRequest, IRouteRequest, @@ -358,7 +358,7 @@ class RoutesConfiguratorMixin(object): # check for an external route; an external route is one which is # is a full url (e.g. 'http://example.com/{id}') - parsed = urlparse.urlparse(pattern) + parsed = urlparse(pattern) external_url = pattern if parsed.hostname: diff --git a/src/pyramid/config/views.py b/src/pyramid/config/views.py index 0039cd8fe..484c0d754 100644 --- a/src/pyramid/config/views.py +++ b/src/pyramid/config/views.py @@ -5,6 +5,7 @@ import operator import os import warnings +from urllib.parse import quote, urljoin, urlparse, urlunparse from webob.acceptparse import Accept from zope.interface import Interface, implementedBy, implementer from zope.interface.interfaces import IInterface @@ -32,7 +33,7 @@ from pyramid.interfaces import ( from pyramid import renderers from pyramid.asset import resolve_asset_spec -from pyramid.compat import urlparse, url_quote, WIN +from pyramid.compat import WIN from pyramid.decorator import reify @@ -77,9 +78,6 @@ from pyramid.config.predicates import ( sort_accept_offers, ) -urljoin = urlparse.urljoin -url_parse = urlparse.urlparse - DefaultViewMapper = DefaultViewMapper # bw-compat preserve_view_attrs = preserve_view_attrs # bw-compat requestonly = requestonly # bw-compat @@ -2191,14 +2189,14 @@ class StaticURLInfo(object): return request.route_url(route_name, **kw) else: app_url, qs, anchor = parse_url_overrides(request, kw) - parsed = url_parse(url) + parsed = urlparse(url) if not parsed.scheme: - url = urlparse.urlunparse( + url = urlunparse( parsed._replace( scheme=request.environ['wsgi.url_scheme'] ) ) - subpath = url_quote(subpath) + subpath = quote(subpath) result = urljoin(url, subpath) return result + qs + anchor @@ -2227,7 +2225,7 @@ class StaticURLInfo(object): # make sure it ends with a slash name = name + '/' - if url_parse(name).netloc: + if urlparse(name).netloc: # it's a URL # url, spec, route_name url = name diff --git a/src/pyramid/csrf.py b/src/pyramid/csrf.py index fba5d9baa..ece55ce10 100644 --- a/src/pyramid/csrf.py +++ b/src/pyramid/csrf.py @@ -1,10 +1,11 @@ +from urllib.parse import urlparse import uuid from webob.cookies import CookieProfile from zope.interface import implementer -from pyramid.compat import bytes_, urlparse, text_ +from pyramid.compat import bytes_, text_ from pyramid.exceptions import BadCSRFOrigin, BadCSRFToken from pyramid.interfaces import ICSRFStoragePolicy from pyramid.settings import aslist @@ -303,7 +304,7 @@ def check_csrf_origin(request, trusted_origins=None, raises=True): # Parse our origin so we we can extract the required information from # it. - originp = urlparse.urlparse(origin) + originp = urlparse(origin) # Ensure that our Referer is also secure. if originp.scheme != "https": diff --git a/src/pyramid/encode.py b/src/pyramid/encode.py index 1ed2c5a55..11d3a7787 100644 --- a/src/pyramid/encode.py +++ b/src/pyramid/encode.py @@ -1,7 +1,6 @@ -from pyramid.compat import ( - url_quote as _url_quote, - url_quote_plus as _quote_plus, -) +from urllib.parse import quote as _url_quote +from urllib.parse import quote_plus as _quote_plus + from pyramid.util import is_nonstr_iter diff --git a/src/pyramid/scripts/prequest.py b/src/pyramid/scripts/prequest.py index e8f5ff8b3..eb2032419 100644 --- a/src/pyramid/scripts/prequest.py +++ b/src/pyramid/scripts/prequest.py @@ -2,8 +2,8 @@ import base64 import argparse import sys import textwrap +from urllib.parse import unquote -from pyramid.compat import url_unquote from pyramid.request import Request from pyramid.scripts.common import get_config_loader from pyramid.scripts.common import parse_vars @@ -152,7 +152,7 @@ class PRequestCommand(object): except ValueError: qs = '' - path = url_unquote(path) + path = unquote(path) headers = {} if self.args.login: diff --git a/tests/test_integration.py b/tests/test_integration.py index d57a7cf6e..0652d8ee8 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -4,6 +4,7 @@ import gc import locale import os import unittest +from urllib.parse import quote from webtest import TestApp from zope.interface import Interface @@ -11,7 +12,7 @@ from pyramid.wsgi import wsgiapp from pyramid.view import view_config from pyramid.static import static_view from pyramid.testing import skip_on -from pyramid.compat import text_, url_quote +from pyramid.compat import text_ from .pkgs.exceptionviewapp.models import AnException, NotAnException @@ -108,7 +109,7 @@ class StaticAppBase(IntegrationBase): os.makedirs(pathdir) with open(path, 'wb') as fp: fp.write(body) - url = url_quote('/static/héhé/index.html') + url = quote('/static/héhé/index.html') res = self.testapp.get(url, status=200) self.assertEqual(res.body, body) finally: @@ -123,7 +124,7 @@ class StaticAppBase(IntegrationBase): with open(path, 'wb') as fp: fp.write(body) try: - url = url_quote('/static/héhé.html') + url = quote('/static/héhé.html') res = self.testapp.get(url, status=200) self.assertEqual(res.body, body) finally: diff --git a/tests/test_traversal.py b/tests/test_traversal.py index ed5e0031e..252e99f6f 100644 --- a/tests/test_traversal.py +++ b/tests/test_traversal.py @@ -1,9 +1,10 @@ # -*- coding: utf-8 -*- import unittest +from urllib.parse import quote from pyramid.testing import cleanUp -from pyramid.compat import text_, native_, url_quote +from pyramid.compat import text_, native_ class TraversalPathTests(unittest.TestCase): @@ -14,7 +15,7 @@ class TraversalPathTests(unittest.TestCase): def test_utf8(self): la = b'La Pe\xc3\xb1a' - encoded = url_quote(la) + encoded = quote(la) decoded = text_(la, 'utf-8') path = '/'.join([encoded, encoded]) result = self._callFUT(path) @@ -24,7 +25,7 @@ class TraversalPathTests(unittest.TestCase): from pyramid.exceptions import URLDecodeError la = text_(b'La Pe\xc3\xb1a', 'utf-8').encode('utf-16') - encoded = url_quote(la) + encoded = quote(la) path = '/'.join([encoded, encoded]) self.assertRaises(URLDecodeError, self._callFUT, path) -- cgit v1.2.3 From a705f56c3ebf34f25ab567d85b7d5b421983aa4a Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 14 Nov 2018 20:35:39 -0600 Subject: rely on webob for request.path_info and request.scheme --- src/pyramid/compat.py | 17 --------------- src/pyramid/config/testing.py | 4 ++-- src/pyramid/config/views.py | 4 +--- src/pyramid/scripts/pviews.py | 2 +- src/pyramid/static.py | 2 +- src/pyramid/traversal.py | 19 +++++++++++------ src/pyramid/urldispatch.py | 13 ++++-------- src/pyramid/view.py | 4 +--- tests/test_config/test_testing.py | 14 ++++++++----- tests/test_config/test_views.py | 6 ++++-- tests/test_scripts/dummy.py | 5 ++++- tests/test_scripts/test_pviews.py | 18 ++++++++++------ tests/test_url.py | 1 + tests/test_urldispatch.py | 44 ++++++++++++++++++++++----------------- 14 files changed, 78 insertions(+), 75 deletions(-) diff --git a/src/pyramid/compat.py b/src/pyramid/compat.py index 10d3c4998..30fc10395 100644 --- a/src/pyramid/compat.py +++ b/src/pyramid/compat.py @@ -1,7 +1,4 @@ -import inspect import platform -import sys -import types WIN = platform.system() == 'Windows' @@ -47,17 +44,3 @@ def native_(s, encoding='latin-1', errors='strict'): if isinstance(s, str): return s return str(s, encoding, errors) - - -# see PEP 3333 for why we encode WSGI PATH_INFO to latin-1 before -# decoding it to utf-8 -def decode_path_info(path): - return path.encode('latin-1').decode('utf-8') - - -# see PEP 3333 for why we decode the path to latin-1 -from urllib.parse import unquote_to_bytes - - -def unquote_bytes_to_wsgi(bytestring): - return unquote_to_bytes(bytestring).decode('latin-1') diff --git a/src/pyramid/config/testing.py b/src/pyramid/config/testing.py index bba5054e6..9c998840a 100644 --- a/src/pyramid/config/testing.py +++ b/src/pyramid/config/testing.py @@ -9,7 +9,7 @@ from pyramid.interfaces import ( from pyramid.renderers import RendererHelper -from pyramid.traversal import decode_path_info, split_path_info +from pyramid.traversal import split_path_info from pyramid.config.actions import action_method @@ -95,7 +95,7 @@ class TestingConfiguratorMixin(object): self.context = context def __call__(self, request): - path = decode_path_info(request.environ['PATH_INFO']) + path = request.path_info ob = resources[path] traversed = split_path_info(path) return { diff --git a/src/pyramid/config/views.py b/src/pyramid/config/views.py index 484c0d754..412e30304 100644 --- a/src/pyramid/config/views.py +++ b/src/pyramid/config/views.py @@ -2192,9 +2192,7 @@ class StaticURLInfo(object): parsed = urlparse(url) if not parsed.scheme: url = urlunparse( - parsed._replace( - scheme=request.environ['wsgi.url_scheme'] - ) + parsed._replace(scheme=request.scheme) ) subpath = quote(subpath) result = urljoin(url, subpath) diff --git a/src/pyramid/scripts/pviews.py b/src/pyramid/scripts/pviews.py index 891dc4709..d2a4bfa40 100644 --- a/src/pyramid/scripts/pviews.py +++ b/src/pyramid/scripts/pviews.py @@ -70,7 +70,7 @@ class PViewsCommand(object): def _find_multi_routes(self, mapper, request): infos = [] - path = request.environ['PATH_INFO'] + path = request.path_info # find all routes that match path, regardless of predicates for route in mapper.get_routes(): match = route.match(path) diff --git a/src/pyramid/static.py b/src/pyramid/static.py index b300df9ee..e3561e93e 100644 --- a/src/pyramid/static.py +++ b/src/pyramid/static.py @@ -88,7 +88,7 @@ class static_view(object): if self.use_subpath: path_tuple = request.subpath else: - path_tuple = traversal_path_info(request.environ['PATH_INFO']) + path_tuple = traversal_path_info(request.path_info) path = _secure_path(path_tuple) if path is None: diff --git a/src/pyramid/traversal.py b/src/pyramid/traversal.py index 429b12e23..e846881f3 100644 --- a/src/pyramid/traversal.py +++ b/src/pyramid/traversal.py @@ -1,4 +1,6 @@ from functools import lru_cache +from urllib.parse import unquote_to_bytes + from zope.interface import implementer from zope.interface.interfaces import IInterface @@ -9,12 +11,7 @@ from pyramid.interfaces import ( VH_ROOT_KEY, ) -from pyramid.compat import ( - native_, - ascii_native_, - decode_path_info, - unquote_bytes_to_wsgi, -) +from pyramid.compat import native_, ascii_native_ from pyramid.encode import url_quote from pyramid.exceptions import URLDecodeError @@ -543,6 +540,16 @@ def split_path_info(path): return tuple(clean) +# see PEP 3333 for why we encode to latin-1 then decode to utf-8 +def decode_path_info(path): + return path.encode('latin-1').decode('utf-8') + + +# see PEP 3333 for why we decode the path to latin-1 +def unquote_bytes_to_wsgi(bytestring): + return unquote_to_bytes(bytestring).decode('latin-1') + + _segment_cache = {} diff --git a/src/pyramid/urldispatch.py b/src/pyramid/urldispatch.py index 6d25b2dd5..6348ae7e2 100644 --- a/src/pyramid/urldispatch.py +++ b/src/pyramid/urldispatch.py @@ -3,7 +3,7 @@ from zope.interface import implementer from pyramid.interfaces import IRoutesMapper, IRoute -from pyramid.compat import native_, text_, decode_path_info +from pyramid.compat import text_ from pyramid.exceptions import URLDecodeError @@ -75,10 +75,9 @@ class RoutesMapper(object): return self.routes[name].generate(kw) def __call__(self, request): - environ = request.environ try: # empty if mounted under a path in mod_wsgi, for example - path = decode_path_info(environ['PATH_INFO'] or '/') + path = request.path_info or '/' except KeyError: path = '/' except UnicodeDecodeError as e: @@ -202,14 +201,10 @@ def _compile_route(route): return None d = {} for k, v in m.groupdict().items(): - # k and v will be Unicode 2.6.4 and lower doesnt accept unicode - # kwargs as **kw, so we explicitly cast the keys to native - # strings in case someone wants to pass the result as **kw - nk = native_(k, 'ascii') if k == remainder: - d[nk] = split_path_info(v) + d[k] = split_path_info(v) else: - d[nk] = v + d[k] = v return d gen = ''.join(gen) diff --git a/src/pyramid/view.py b/src/pyramid/view.py index 9e85d7281..944ad93ea 100644 --- a/src/pyramid/view.py +++ b/src/pyramid/view.py @@ -15,8 +15,6 @@ from pyramid.interfaces import ( IExceptionViewClassifier, ) -from pyramid.compat import decode_path_info - from pyramid.exceptions import ConfigurationError, PredicateMismatch from pyramid.httpexceptions import ( @@ -305,7 +303,7 @@ class AppendSlashNotFoundViewFactory(object): self.redirect_class = redirect_class def __call__(self, context, request): - path = decode_path_info(request.environ['PATH_INFO'] or '/') + path = request.path_info registry = request.registry mapper = registry.queryUtility(IRoutesMapper) if mapper is not None and not path.endswith('/'): diff --git a/tests/test_config/test_testing.py b/tests/test_config/test_testing.py index ede31e1b6..870bbe9fa 100644 --- a/tests/test_config/test_testing.py +++ b/tests/test_config/test_testing.py @@ -69,23 +69,27 @@ class TestingConfiguratorMixinTests(unittest.TestCase): config = self._makeOne(autocommit=True) config.testing_resources(resources) adapter = config.registry.getAdapter(None, ITraverser) - result = adapter(DummyRequest({'PATH_INFO': '/ob1'})) + request = DummyRequest() + request.path_info = '/ob1' + result = adapter(request) self.assertEqual(result['context'], ob1) self.assertEqual(result['view_name'], '') self.assertEqual(result['subpath'], ()) self.assertEqual(result['traversed'], (text_('ob1'),)) self.assertEqual(result['virtual_root'], ob1) self.assertEqual(result['virtual_root_path'], ()) - result = adapter(DummyRequest({'PATH_INFO': '/ob2'})) + request = DummyRequest() + request.path_info = '/ob2' + result = adapter(request) self.assertEqual(result['context'], ob2) self.assertEqual(result['view_name'], '') self.assertEqual(result['subpath'], ()) self.assertEqual(result['traversed'], (text_('ob2'),)) self.assertEqual(result['virtual_root'], ob2) self.assertEqual(result['virtual_root_path'], ()) - self.assertRaises( - KeyError, adapter, DummyRequest({'PATH_INFO': '/ob3'}) - ) + request = DummyRequest() + request.path_info = '/ob3' + self.assertRaises(KeyError, adapter, request) try: config.begin() self.assertEqual(find_resource(None, '/ob1'), ob1) diff --git a/tests/test_config/test_views.py b/tests/test_config/test_views.py index d530542b7..e25ee881e 100644 --- a/tests/test_config/test_views.py +++ b/tests/test_config/test_views.py @@ -1357,6 +1357,7 @@ class TestViewsConfigurationMixin(unittest.TestCase): request_method='POST', ) request = self._makeRequest(config) + request.path_info = '/' request.method = 'POST' request.params = {} router = Router(config.registry) @@ -1412,6 +1413,7 @@ class TestViewsConfigurationMixin(unittest.TestCase): request_method='POST', ) request = self._makeRequest(config) + request.path_info = '/' request.method = 'POST' request.params = {} router = Router(config.registry) @@ -2722,7 +2724,7 @@ class TestViewsConfigurationMixin(unittest.TestCase): view, renderer=null_renderer, append_slash=True ) request = self._makeRequest(config) - request.environ['PATH_INFO'] = '/foo' + request.path_info = '/foo' request.query_string = 'a=1&b=2' request.path = '/scriptname/foo' view = self._getViewCallable( @@ -2751,7 +2753,7 @@ class TestViewsConfigurationMixin(unittest.TestCase): view, renderer=null_renderer, append_slash=HTTPMovedPermanently ) request = self._makeRequest(config) - request.environ['PATH_INFO'] = '/foo' + request.path_info = '/foo' request.query_string = 'a=1&b=2' request.path = '/scriptname/foo' view = self._getViewCallable( diff --git a/tests/test_scripts/dummy.py b/tests/test_scripts/dummy.py index 8e340f645..bb3475d39 100644 --- a/tests/test_scripts/dummy.py +++ b/tests/test_scripts/dummy.py @@ -81,8 +81,11 @@ class DummyRoute(object): class DummyRequest: application_url = 'http://example.com:5432' script_name = '' + path_info = '/' - def __init__(self, environ): + def __init__(self, environ=None): + if environ is None: + environ = {} self.environ = environ self.matchdict = {} diff --git a/tests/test_scripts/test_pviews.py b/tests/test_scripts/test_pviews.py index 0b26a9cf3..c8d29113f 100644 --- a/tests/test_scripts/test_pviews.py +++ b/tests/test_scripts/test_pviews.py @@ -53,7 +53,8 @@ class TestPViewsCommand(unittest.TestCase): class View1(object): pass - request = dummy.DummyRequest({'PATH_INFO': '/a'}) + request = dummy.DummyRequest() + request.path_info = '/a' root = DefaultRootFactory(request) root_iface = providedBy(root) registry.registerAdapter( @@ -78,7 +79,8 @@ class TestPViewsCommand(unittest.TestCase): def view1(): # pragma: no cover pass - request = dummy.DummyRequest({'PATH_INFO': '/a'}) + request = dummy.DummyRequest() + request.path_info = '/a' root = DefaultRootFactory(request) root_iface = providedBy(root) registry.registerAdapter( @@ -105,7 +107,8 @@ class TestPViewsCommand(unittest.TestCase): class View1(object): pass - request = dummy.DummyRequest({'PATH_INFO': '/a'}) + request = dummy.DummyRequest() + request.path_info = '/a' root = DefaultRootFactory(request) root_iface = providedBy(root) view = View1() @@ -267,7 +270,8 @@ class TestPViewsCommand(unittest.TestCase): dummy.DummyRoute('b', '/a', factory=factory, matchdict={}), ] mapper = dummy.DummyMapper(*routes) - request = dummy.DummyRequest({'PATH_INFO': '/a'}) + request = dummy.DummyRequest() + request.path_info = '/a' result = command._find_multi_routes(mapper, request) self.assertEqual( result, @@ -288,7 +292,8 @@ class TestPViewsCommand(unittest.TestCase): dummy.DummyRoute('b', '/a', factory=factory, matchdict={}), ] mapper = dummy.DummyMapper(*routes) - request = dummy.DummyRequest({'PATH_INFO': '/a'}) + request = dummy.DummyRequest() + request.path_info = '/a' result = command._find_multi_routes(mapper, request) self.assertEqual(result, [{'match': {}, 'route': routes[1]}]) @@ -303,7 +308,8 @@ class TestPViewsCommand(unittest.TestCase): dummy.DummyRoute('b', '/a', factory=factory), ] mapper = dummy.DummyMapper(*routes) - request = dummy.DummyRequest({'PATH_INFO': '/a'}) + request = dummy.DummyRequest() + request.path_info = '/a' result = command._find_multi_routes(mapper, request) self.assertEqual(result, []) diff --git a/tests/test_url.py b/tests/test_url.py index 94a0a61c9..a852f3301 100644 --- a/tests/test_url.py +++ b/tests/test_url.py @@ -25,6 +25,7 @@ class TestURLMethodsMixin(unittest.TestCase): def __init__(self, environ): self.environ = environ + self.scheme = environ.get('wsgi.url_scheme', 'http') request = Request(environ) request.registry = self.config.registry diff --git a/tests/test_urldispatch.py b/tests/test_urldispatch.py index b50e86b99..9296a50e1 100644 --- a/tests/test_urldispatch.py +++ b/tests/test_urldispatch.py @@ -59,9 +59,7 @@ class RoutesMapperTests(unittest.TestCase): def _getRequest(self, **kw): from pyramid.threadlocal import get_current_registry - environ = {'SERVER_NAME': 'localhost', 'wsgi.url_scheme': 'http'} - environ.update(kw) - request = DummyRequest(environ) + request = DummyRequest(**kw) reg = get_current_registry() request.registry = reg return request @@ -83,7 +81,7 @@ class RoutesMapperTests(unittest.TestCase): def test_no_route_matches(self): mapper = self._makeOne() - request = self._getRequest(PATH_INFO='/') + request = self._getRequest(path_info='/') result = mapper(request) self.assertEqual(result['match'], None) self.assertEqual(result['route'], None) @@ -130,16 +128,22 @@ class RoutesMapperTests(unittest.TestCase): def test___call__pathinfo_cant_be_decoded(self): from pyramid.exceptions import URLDecodeError + from pyramid.threadlocal import get_current_registry + + class DummyRequest: + @property + def path_info(self): + return b'\xff\xfe\xe6\x00'.decode('utf-8') mapper = self._makeOne() - path_info = b'\xff\xfe\xe6\x00'.decode('latin-1') - request = self._getRequest(PATH_INFO=path_info) + request = DummyRequest() + request.registry = get_current_registry() self.assertRaises(URLDecodeError, mapper, request) def test___call__route_matches(self): mapper = self._makeOne() mapper.connect('foo', 'archives/:action/:article') - request = self._getRequest(PATH_INFO='/archives/action1/article1') + request = self._getRequest(path_info='/archives/action1/article1') result = mapper(request) self.assertEqual(result['route'], mapper.routes['foo']) self.assertEqual(result['match']['action'], 'action1') @@ -150,7 +154,7 @@ class RoutesMapperTests(unittest.TestCase): mapper.connect( 'foo', 'archives/:action/:article', predicates=[lambda *arg: True] ) - request = self._getRequest(PATH_INFO='/archives/action1/article1') + request = self._getRequest(path_info='/archives/action1/article1') result = mapper(request) self.assertEqual(result['route'], mapper.routes['foo']) self.assertEqual(result['match']['action'], 'action1') @@ -164,7 +168,7 @@ class RoutesMapperTests(unittest.TestCase): predicates=[lambda *arg: True, lambda *arg: False], ) mapper.connect('bar', 'archives/:action/:article') - request = self._getRequest(PATH_INFO='/archives/action1/article1') + request = self._getRequest(path_info='/archives/action1/article1') result = mapper(request) self.assertEqual(result['route'], mapper.routes['bar']) self.assertEqual(result['match']['action'], 'action1') @@ -179,7 +183,7 @@ class RoutesMapperTests(unittest.TestCase): return True mapper.connect('foo', 'archives/:action/article1', predicates=[pred]) - request = self._getRequest(PATH_INFO='/archives/action1/article1') + request = self._getRequest(path_info='/archives/action1/article1') mapper(request) def test_cc_bug(self): @@ -191,13 +195,13 @@ class RoutesMapperTests(unittest.TestCase): 'juri', 'licenses/:license_code/:license_version/:jurisdiction' ) - request = self._getRequest(PATH_INFO='/licenses/1/v2/rdf') + request = self._getRequest(path_info='/licenses/1/v2/rdf') result = mapper(request) self.assertEqual(result['route'], mapper.routes['rdf']) self.assertEqual(result['match']['license_code'], '1') self.assertEqual(result['match']['license_version'], 'v2') - request = self._getRequest(PATH_INFO='/licenses/1/v2/usa') + request = self._getRequest(path_info='/licenses/1/v2/usa') result = mapper(request) self.assertEqual(result['route'], mapper.routes['juri']) self.assertEqual(result['match']['license_code'], '1') @@ -207,7 +211,7 @@ class RoutesMapperTests(unittest.TestCase): def test___call__root_route_matches(self): mapper = self._makeOne() mapper.connect('root', '') - request = self._getRequest(PATH_INFO='/') + request = self._getRequest(path_info='/') result = mapper(request) self.assertEqual(result['route'], mapper.routes['root']) self.assertEqual(result['match'], {}) @@ -215,7 +219,7 @@ class RoutesMapperTests(unittest.TestCase): def test___call__root_route_matches2(self): mapper = self._makeOne() mapper.connect('root', '/') - request = self._getRequest(PATH_INFO='/') + request = self._getRequest(path_info='/') result = mapper(request) self.assertEqual(result['route'], mapper.routes['root']) self.assertEqual(result['match'], {}) @@ -223,7 +227,7 @@ class RoutesMapperTests(unittest.TestCase): def test___call__root_route_when_path_info_empty(self): mapper = self._makeOne() mapper.connect('root', '/') - request = self._getRequest(PATH_INFO='') + request = self._getRequest(path_info='') result = mapper(request) self.assertEqual(result['route'], mapper.routes['root']) self.assertEqual(result['match'], {}) @@ -231,7 +235,7 @@ class RoutesMapperTests(unittest.TestCase): def test___call__root_route_when_path_info_notempty(self): mapper = self._makeOne() mapper.connect('root', '/') - request = self._getRequest(PATH_INFO='/') + request = self._getRequest(path_info='/') result = mapper(request) self.assertEqual(result['route'], mapper.routes['root']) self.assertEqual(result['match'], {}) @@ -239,7 +243,7 @@ class RoutesMapperTests(unittest.TestCase): def test___call__no_path_info(self): mapper = self._makeOne() mapper.connect('root', '/') - request = self._getRequest() + request = self._getRequest(path_info='') result = mapper(request) self.assertEqual(result['route'], mapper.routes['root']) self.assertEqual(result['match'], {}) @@ -643,8 +647,10 @@ class DummyContext(object): class DummyRequest(object): - def __init__(self, environ): - self.environ = environ + scheme = 'http' + + def __init__(self, **kw): + self.__dict__.update(kw) class DummyRoute(object): -- cgit v1.2.3 From 78dcc6dff88829831ead187804ac9233eafab52e Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 14 Nov 2018 21:26:39 -0600 Subject: remove several places supporting bytes for py2 --- src/pyramid/response.py | 4 +--- src/pyramid/urldispatch.py | 11 +--------- src/pyramid/util.py | 27 ++++++++++------------- tests/test_httpexceptions.py | 6 +++++ tests/test_renderers.py | 2 +- tests/test_urldispatch.py | 17 +++++++++++++++ tests/test_util.py | 52 ++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 90 insertions(+), 29 deletions(-) diff --git a/src/pyramid/response.py b/src/pyramid/response.py index 38f9fa1ce..8a2ba8929 100644 --- a/src/pyramid/response.py +++ b/src/pyramid/response.py @@ -100,14 +100,12 @@ class FileIter(object): def __iter__(self): return self - def next(self): + def __next__(self): val = self.file.read(self.block_size) if not val: raise StopIteration return val - __next__ = next # py3 - def close(self): self.file.close() diff --git a/src/pyramid/urldispatch.py b/src/pyramid/urldispatch.py index 6348ae7e2..97626c5dd 100644 --- a/src/pyramid/urldispatch.py +++ b/src/pyramid/urldispatch.py @@ -188,14 +188,6 @@ def _compile_route(route): match = re.compile(pattern).match def matcher(path): - # This function really wants to consume Unicode patterns natively, - # but if someone passes us a bytestring, we allow it by converting it - # to Unicode using the ASCII decoding. We decode it using ASCII - # because we don't want to accept bytestrings with high-order - # characters in them here as we have no idea what the encoding - # represents. - if path.__class__ is not str: - path = text_(path, 'ascii') m = match(path) if m is None: return None @@ -216,7 +208,7 @@ def _compile_route(route): newdict = {} for k, v in dict.items(): if v.__class__ is bytes: - # url_quote below needs a native string, not bytes on Py3 + # url_quote below needs a native string v = v.decode('utf-8') if k == remainder: @@ -230,7 +222,6 @@ def _compile_route(route): else: if v.__class__ is not str: v = str(v) - # v may be bytes (py2) or native string (py3) v = q(v) # at this point, the value will be a native string diff --git a/src/pyramid/util.py b/src/pyramid/util.py index 6cd8225aa..0688e67d3 100644 --- a/src/pyramid/util.py +++ b/src/pyramid/util.py @@ -603,10 +603,7 @@ def takes_one_arg(callee, attr=None, argname=None): if inspect.isroutine(callee): fn = callee elif inspect.isclass(callee): - try: - fn = callee.__init__ - except AttributeError: - return False + fn = callee.__init__ ismethod = hasattr(fn, '__call__') else: try: @@ -614,15 +611,11 @@ def takes_one_arg(callee, attr=None, argname=None): except AttributeError: return False - try: - argspec = inspect.getfullargspec(fn) - except TypeError: - return False - + argspec = inspect.getfullargspec(fn) args = argspec[0] if hasattr(fn, '__func__') or ismethod: - # it's an instance method (or unbound method on py2) + # it's an instance method if not args: return False args = args[1:] @@ -676,8 +669,12 @@ def is_unbound_method(fn): def reraise(tp, value, tb=None): - if value is None: - value = tp - if value.__traceback__ is not tb: - raise value.with_traceback(tb) - raise value + try: + if value is None: + value = tp() + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + finally: + value = None + tb = None diff --git a/tests/test_httpexceptions.py b/tests/test_httpexceptions.py index 195496e2e..48c4a22f3 100644 --- a/tests/test_httpexceptions.py +++ b/tests/test_httpexceptions.py @@ -67,6 +67,12 @@ class Test__no_escape(unittest.TestCase): def test_not_basestring(self): self.assertEqual(self._callFUT(42), '42') + def test_bytes(self): + self.assertEqual( + self._callFUT(b'/La Pe\xc3\xb1a/{x}'), + b'/La Pe\xc3\xb1a/{x}'.decode('utf-8'), + ) + def test_unicode(self): class DummyUnicodeObject(object): def __unicode__(self): diff --git a/tests/test_renderers.py b/tests/test_renderers.py index 0eacfa996..0e9f99d15 100644 --- a/tests/test_renderers.py +++ b/tests/test_renderers.py @@ -774,7 +774,7 @@ class DummyResponse: body = b'' # compat for renderer that will set unicode on py3 - def _set_text(self, val): # pragma: no cover + def _set_text(self, val): self.body = val.encode('utf8') text = property(fset=_set_text) diff --git a/tests/test_urldispatch.py b/tests/test_urldispatch.py index 9296a50e1..a74731730 100644 --- a/tests/test_urldispatch.py +++ b/tests/test_urldispatch.py @@ -140,6 +140,23 @@ class RoutesMapperTests(unittest.TestCase): request.registry = get_current_registry() self.assertRaises(URLDecodeError, mapper, request) + def test___call__pathinfo_KeyError(self): + from pyramid.threadlocal import get_current_registry + + class DummyRequest: + @property + def path_info(self): + # if the PATH_INFO is missing from the environ + raise KeyError + + mapper = self._makeOne() + mapper.connect('root', '') + request = DummyRequest() + request.registry = get_current_registry() + result = mapper(request) + self.assertEqual(result['route'], mapper.routes['root']) + self.assertEqual(result['match'], {}) + def test___call__route_matches(self): mapper = self._makeOne() mapper.connect('foo', 'archives/:action/:article') diff --git a/tests/test_util.py b/tests/test_util.py index 676290676..d6d1d1502 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -1,3 +1,4 @@ +import sys import unittest from pyramid.compat import text_, bytes_ @@ -1246,3 +1247,54 @@ class TestUnboundMethods(unittest.TestCase): return 'OK' self.assertFalse(self._callFUT(func)) + + +class TestReraise(unittest.TestCase): + def _callFUT(self, *args): + from pyramid.util import reraise + + return reraise(*args) + + def test_it(self): + # tests cribbed from six.py + def get_next(tb): + return tb.tb_next.tb_next.tb_next + + e = Exception('blah') + try: + raise e + except Exception: + tp, val, tb = sys.exc_info() + + try: + self._callFUT(tp, val, tb) + except Exception: + tp2, val2, tb2 = sys.exc_info() + self.assertIs(tp2, Exception) + self.assertIs(val2, e) + self.assertIs(get_next(tb2), tb) + + try: + self._callFUT(tp, val) + except Exception: + tp2, val2, tb2 = sys.exc_info() + self.assertIs(tp2, Exception) + self.assertIs(val2, e) + self.assertIsNot(get_next(tb2), tb) + + try: + self._callFUT(tp, val, tb2) + except Exception: + tp2, val2, tb3 = sys.exc_info() + self.assertIs(tp2, Exception) + self.assertIs(val2, e) + self.assertIs(get_next(tb3), tb2) + + try: + self._callFUT(tp, None, tb) + except Exception: + tp2, val2, tb2 = sys.exc_info() + self.assertIs(tp2, Exception) + self.assertIsNot(val2, val) + self.assertIsInstance(val2, Exception) + self.assertIs(get_next(tb2), tb) -- cgit v1.2.3 From 2f8ede09e52162e475aececf587b21e96a2b1a79 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 14 Nov 2018 22:15:57 -0600 Subject: move text_, bytes_ and ascii_ to pyramid.util and remove native_ --- src/pyramid/authentication.py | 13 ++----- src/pyramid/compat.py | 35 ------------------ src/pyramid/config/__init__.py | 3 +- src/pyramid/config/predicates.py | 3 +- src/pyramid/csrf.py | 12 ++++-- src/pyramid/httpexceptions.py | 3 +- src/pyramid/registry.py | 5 +-- src/pyramid/request.py | 11 ++++-- src/pyramid/session.py | 5 ++- src/pyramid/testing.py | 4 +- src/pyramid/traversal.py | 10 ++--- src/pyramid/url.py | 2 +- src/pyramid/urldispatch.py | 4 +- src/pyramid/util.py | 71 ++++++++++++++++++++++++++---------- tests/pkgs/forbiddenapp/__init__.py | 2 +- tests/test_authentication.py | 2 +- tests/test_config/test_predicates.py | 2 +- tests/test_config/test_routes.py | 2 +- tests/test_config/test_testing.py | 2 +- tests/test_config/test_views.py | 2 +- tests/test_encode.py | 4 +- tests/test_httpexceptions.py | 2 +- tests/test_integration.py | 6 +-- tests/test_predicates.py | 2 +- tests/test_renderers.py | 4 +- tests/test_request.py | 4 +- tests/test_response.py | 2 +- tests/test_traversal.py | 7 ++-- tests/test_url.py | 3 +- tests/test_urldispatch.py | 2 +- tests/test_util.py | 23 +++++++----- 31 files changed, 125 insertions(+), 127 deletions(-) diff --git a/src/pyramid/authentication.py b/src/pyramid/authentication.py index 05131a521..21cfc0c0e 100644 --- a/src/pyramid/authentication.py +++ b/src/pyramid/authentication.py @@ -13,13 +13,11 @@ from zope.interface import implementer from webob.cookies import CookieProfile -from pyramid.compat import bytes_, ascii_native_, native_ - from pyramid.interfaces import IAuthenticationPolicy, IDebugLogger from pyramid.security import Authenticated, Everyone -from pyramid.util import strings_differ +from pyramid.util import strings_differ, bytes_, ascii_, text_ from pyramid.util import SimpleSerializer VALID_TOKEN = re.compile(r"^[A-Za-z][A-Za-z0-9+_-]*$") @@ -747,7 +745,7 @@ def parse_ticket(secret, ticket, ip, hashalg='md5'): If the ticket cannot be parsed, a ``BadTicket`` exception will be raised with an explanation. """ - ticket = native_(ticket).strip('"') + ticket = text_(ticket).strip('"') digest_size = hashlib.new(hashalg).digest_size * 2 digest = ticket[:digest_size] try: @@ -866,16 +864,13 @@ class AuthTktCookieHelper(object): domain=None, samesite='Lax', ): - - serializer = SimpleSerializer() - self.cookie_profile = CookieProfile( cookie_name=cookie_name, secure=secure, max_age=max_age, httponly=http_only, path=path, - serializer=serializer, + serializer=SimpleSerializer(), samesite=samesite, ) @@ -1045,7 +1040,7 @@ class AuthTktCookieHelper(object): for token in tokens: if isinstance(token, str): try: - token = ascii_native_(token) + token = ascii_(token) except UnicodeEncodeError: raise ValueError("Invalid token %r" % (token,)) if not (isinstance(token, str) and VALID_TOKEN.match(token)): diff --git a/src/pyramid/compat.py b/src/pyramid/compat.py index 30fc10395..ec0ca7017 100644 --- a/src/pyramid/compat.py +++ b/src/pyramid/compat.py @@ -9,38 +9,3 @@ try: # pragma: no cover except BaseException: # pragma: no cover __pypy__ = None PYPY = False - - -def text_(s, encoding='latin-1', errors='strict'): - """ If ``s`` is an instance of ``bytes``, return - ``s.decode(encoding, errors)``, otherwise return ``s``""" - if isinstance(s, bytes): - return s.decode(encoding, errors) - return s - - -def bytes_(s, encoding='latin-1', errors='strict'): - """ If ``s`` is an instance of ``str``, return - ``s.encode(encoding, errors)``, otherwise return ``s``""" - if isinstance(s, str): - return s.encode(encoding, errors) - return s - - -def ascii_native_(s): - """ - If ``s`` is an instance of ``str``, return - ``s.encode('ascii')``, otherwise return ``str(s, 'ascii', 'strict')`` - """ - if isinstance(s, str): - s = s.encode('ascii') - return str(s, 'ascii', 'strict') - - -def native_(s, encoding='latin-1', errors='strict'): - """ If ``s`` is an instance of ``str``, return - ``s``, otherwise return ``str(s, encoding, errors)`` - """ - if isinstance(s, str): - return s - return str(s, encoding, errors) diff --git a/src/pyramid/config/__init__.py b/src/pyramid/config/__init__.py index 198532e0c..072b654c4 100644 --- a/src/pyramid/config/__init__.py +++ b/src/pyramid/config/__init__.py @@ -35,7 +35,7 @@ from pyramid.settings import aslist from pyramid.threadlocal import manager -from pyramid.util import WeakOrderedSet, object_description +from pyramid.util import WeakOrderedSet, get_callable_name, object_description from pyramid.config.actions import action_method, ActionState from pyramid.config.predicates import not_ @@ -698,6 +698,7 @@ class Configurator( ``add_directive`` does not participate in conflict detection, and later calls to ``add_directive`` will override earlier calls. """ + name = get_callable_name(name) c = self.maybe_dotted(directive) if not hasattr(self.registry, '_directives'): self.registry._directives = {} diff --git a/src/pyramid/config/predicates.py b/src/pyramid/config/predicates.py index 789bcd6ae..3eb07c17d 100644 --- a/src/pyramid/config/predicates.py +++ b/src/pyramid/config/predicates.py @@ -1,13 +1,12 @@ from hashlib import md5 from webob.acceptparse import Accept -from pyramid.compat import bytes_ from pyramid.exceptions import ConfigurationError from pyramid.interfaces import IPredicateList, PHASE1_CONFIG from pyramid.predicates import Notted from pyramid.registry import predvalseq from pyramid.util import TopologicalSorter -from pyramid.util import is_nonstr_iter +from pyramid.util import is_nonstr_iter, bytes_ MAX_ORDER = 1 << 30 diff --git a/src/pyramid/csrf.py b/src/pyramid/csrf.py index ece55ce10..26c628acc 100644 --- a/src/pyramid/csrf.py +++ b/src/pyramid/csrf.py @@ -5,11 +5,16 @@ from webob.cookies import CookieProfile from zope.interface import implementer -from pyramid.compat import bytes_, text_ from pyramid.exceptions import BadCSRFOrigin, BadCSRFToken from pyramid.interfaces import ICSRFStoragePolicy from pyramid.settings import aslist -from pyramid.util import SimpleSerializer, is_same_domain, strings_differ +from pyramid.util import ( + SimpleSerializer, + is_same_domain, + strings_differ, + bytes_, + text_, +) @implementer(ICSRFStoragePolicy) @@ -118,7 +123,6 @@ class CookieCSRFStoragePolicy(object): path='/', samesite='Lax', ): - serializer = SimpleSerializer() self.cookie_profile = CookieProfile( cookie_name=cookie_name, secure=secure, @@ -126,7 +130,7 @@ class CookieCSRFStoragePolicy(object): httponly=httponly, path=path, domains=[domain], - serializer=serializer, + serializer=SimpleSerializer(), samesite=samesite, ) self.cookie_name = cookie_name diff --git a/src/pyramid/httpexceptions.py b/src/pyramid/httpexceptions.py index 7e83f783f..56797dc88 100644 --- a/src/pyramid/httpexceptions.py +++ b/src/pyramid/httpexceptions.py @@ -137,10 +137,9 @@ from zope.interface import implementer from webob import html_escape as _html_escape from webob.acceptparse import create_accept_header -from pyramid.compat import text_ - from pyramid.interfaces import IExceptionResponse from pyramid.response import Response +from pyramid.util import text_ def _no_escape(value): diff --git a/src/pyramid/registry.py b/src/pyramid/registry.py index c24125830..7b2547dd7 100644 --- a/src/pyramid/registry.py +++ b/src/pyramid/registry.py @@ -4,15 +4,12 @@ import threading from zope.interface import implementer from zope.interface.registry import Components -from pyramid.compat import text_ from pyramid.decorator import reify from pyramid.interfaces import IIntrospector, IIntrospectable, ISettings from pyramid.path import CALLER_PACKAGE, caller_package -empty = text_('') - class Registry(Components, dict): """ A registry object is an :term:`application registry`. @@ -77,7 +74,7 @@ class Registry(Components, dict): return result def registerSelfAdapter( - self, required=None, provided=None, name=empty, info=empty, event=True + self, required=None, provided=None, name='', info='', event=True ): # registerAdapter analogue which always returns the object itself # when required is matched diff --git a/src/pyramid/request.py b/src/pyramid/request.py index 5ee87ff58..23c00468d 100644 --- a/src/pyramid/request.py +++ b/src/pyramid/request.py @@ -13,14 +13,17 @@ from pyramid.interfaces import ( ISessionFactory, ) -from pyramid.compat import text_, bytes_, native_ - from pyramid.decorator import reify from pyramid.i18n import LocalizerRequestMixin from pyramid.response import Response, _get_response_factory from pyramid.security import AuthenticationAPIMixin, AuthorizationAPIMixin from pyramid.url import URLMethodsMixin -from pyramid.util import InstancePropertyHelper, InstancePropertyMixin +from pyramid.util import ( + InstancePropertyHelper, + InstancePropertyMixin, + text_, + bytes_, +) from pyramid.view import ViewMethodsMixin @@ -281,7 +284,7 @@ def call_app_with_subpath_as_path_info(request, app): # compute new_path_info new_path_info = '/' + '/'.join( - [native_(x.encode('utf-8'), 'latin-1') for x in subpath] + [text_(x.encode('utf-8'), 'latin-1') for x in subpath] ) if new_path_info != '/': # don't want a sole double-slash diff --git a/src/pyramid/session.py b/src/pyramid/session.py index 1b1bfb3e5..70ac4f55f 100644 --- a/src/pyramid/session.py +++ b/src/pyramid/session.py @@ -8,11 +8,12 @@ from zope.interface import implementer from webob.cookies import JSONSerializer, SignedSerializer -from pyramid.compat import text_, bytes_, native_ from pyramid.csrf import check_csrf_origin, check_csrf_token from pyramid.interfaces import ISession +from pyramid.util import text_, bytes_ + def manage_accessed(wrapped): """ Decorator which causes a cookie to be renewed when an accessor @@ -304,7 +305,7 @@ def BaseCookieSessionFactory( exception is not None ): # dont set a cookie during exceptions return False - cookieval = native_( + cookieval = text_( serializer.dumps((self.accessed, self.created, dict(self))) ) if len(cookieval) > 4064: diff --git a/src/pyramid/testing.py b/src/pyramid/testing.py index af8008e45..04497234b 100644 --- a/src/pyramid/testing.py +++ b/src/pyramid/testing.py @@ -8,7 +8,7 @@ from zope.interface import implementer, alsoProvides from pyramid.interfaces import IRequest, ISession -from pyramid.compat import PYPY, text_ +from pyramid.compat import PYPY from pyramid.config import Configurator from pyramid.decorator import reify @@ -28,7 +28,7 @@ from pyramid.threadlocal import get_current_registry, manager from pyramid.i18n import LocalizerRequestMixin from pyramid.request import CallbackMethodsMixin from pyramid.url import URLMethodsMixin -from pyramid.util import InstancePropertyMixin +from pyramid.util import InstancePropertyMixin, text_ from pyramid.view import ViewMethodsMixin diff --git a/src/pyramid/traversal.py b/src/pyramid/traversal.py index e846881f3..7de4c3f91 100644 --- a/src/pyramid/traversal.py +++ b/src/pyramid/traversal.py @@ -11,13 +11,11 @@ from pyramid.interfaces import ( VH_ROOT_KEY, ) -from pyramid.compat import native_, ascii_native_ - from pyramid.encode import url_quote from pyramid.exceptions import URLDecodeError from pyramid.location import lineage from pyramid.threadlocal import get_current_registry -from pyramid.util import is_nonstr_iter +from pyramid.util import ascii_, is_nonstr_iter, text_ PATH_SEGMENT_SAFE = "~!$&'()*+,;=:@" # from webob PATH_SAFE = PATH_SEGMENT_SAFE + "/" @@ -86,7 +84,7 @@ def find_resource(resource, path): resolved by ``find_resource``. """ if isinstance(path, str): - path = ascii_native_(path) + path = ascii_(path) D = traverse(resource, path) view_name = D['view_name'] context = D['context'] @@ -303,7 +301,7 @@ def traverse(resource, path): # step rather than later down the line as the result of calling # ``traversal_path``). - path = ascii_native_(path) + path = ascii_(path) if path and path[0] == '/': resource = find_root(resource) @@ -592,7 +590,7 @@ def quote_path_segment(segment, safe=PATH_SEGMENT_SAFE): except KeyError: if segment.__class__ not in (str, bytes): segment = str(segment) - result = url_quote(native_(segment, 'utf-8'), safe) + result = url_quote(text_(segment, 'utf-8'), safe) # we don't need a lock to mutate _segment_cache, as the below # will generate exactly one Python bytecode (STORE_SUBSCR) _segment_cache[(segment, safe)] = result diff --git a/src/pyramid/url.py b/src/pyramid/url.py index 730b7de23..22551a349 100644 --- a/src/pyramid/url.py +++ b/src/pyramid/url.py @@ -5,10 +5,10 @@ import os from pyramid.interfaces import IResourceURL, IRoutesMapper, IStaticURLInfo -from pyramid.compat import bytes_ from pyramid.encode import url_quote, urlencode from pyramid.path import caller_package from pyramid.threadlocal import get_current_registry +from pyramid.util import bytes_ from pyramid.traversal import ( ResourceURL, diff --git a/src/pyramid/urldispatch.py b/src/pyramid/urldispatch.py index 97626c5dd..73b7be9f3 100644 --- a/src/pyramid/urldispatch.py +++ b/src/pyramid/urldispatch.py @@ -3,13 +3,11 @@ from zope.interface import implementer from pyramid.interfaces import IRoutesMapper, IRoute -from pyramid.compat import text_ - from pyramid.exceptions import URLDecodeError from pyramid.traversal import quote_path_segment, split_path_info, PATH_SAFE -from pyramid.util import is_nonstr_iter +from pyramid.util import is_nonstr_iter, text_ _marker = object() diff --git a/src/pyramid/util.py b/src/pyramid/util.py index 0688e67d3..ab46c91f7 100644 --- a/src/pyramid/util.py +++ b/src/pyramid/util.py @@ -4,10 +4,6 @@ from hmac import compare_digest import inspect import weakref -from pyramid.exceptions import ConfigurationError, CyclicDependencyError - -from pyramid.compat import bytes_, text_, native_ - from pyramid.path import DottedNameResolver as _DottedNameResolver _marker = object() @@ -20,6 +16,32 @@ class DottedNameResolver(_DottedNameResolver): _DottedNameResolver.__init__(self, package) +def text_(s, encoding='latin-1', errors='strict'): + """ If ``s`` is an instance of ``bytes``, return + ``s.decode(encoding, errors)``, otherwise return ``s``""" + if isinstance(s, bytes): + return s.decode(encoding, errors) + return s + + +def bytes_(s, encoding='latin-1', errors='strict'): + """ If ``s`` is an instance of ``str``, return + ``s.encode(encoding, errors)``, otherwise return ``s``""" + if isinstance(s, str): + return s.encode(encoding, errors) + return s + + +def ascii_(s): + """ + If ``s`` is an instance of ``str``, return + ``s.encode('ascii')``, otherwise return ``str(s, 'ascii', 'strict')`` + """ + if isinstance(s, str): + s = s.encode('ascii') + return str(s, 'ascii', 'strict') + + def is_nonstr_iter(v): if isinstance(v, str): return False @@ -322,11 +344,11 @@ def object_description(object): (possibly shortened) string representation is returned. """ if isinstance(object, str): - return text_(object) + return object if isinstance(object, int): - return text_(str(object)) + return str(object) if isinstance(object, (bool, float, type(None))): - return text_(str(object)) + return str(object) if isinstance(object, set): return shortrepr(object, '}') if isinstance(object, tuple): @@ -337,26 +359,25 @@ def object_description(object): return shortrepr(object, '}') module = inspect.getmodule(object) if module is None: - return text_('object %s' % str(object)) + return 'object %s' % str(object) modulename = module.__name__ if inspect.ismodule(object): - return text_('module %s' % modulename) + return 'module %s' % modulename if inspect.ismethod(object): oself = getattr(object, '__self__', None) - if oself is None: # pragma: no cover - oself = getattr(object, 'im_self', None) - return text_( - 'method %s of class %s.%s' - % (object.__name__, modulename, oself.__class__.__name__) + return 'method %s of class %s.%s' % ( + object.__name__, + modulename, + oself.__class__.__name__, ) if inspect.isclass(object): dottedname = '%s.%s' % (modulename, object.__name__) - return text_('class %s' % dottedname) + return 'class %s' % dottedname if inspect.isfunction(object): dottedname = '%s.%s' % (modulename, object.__name__) - return text_('function %s' % dottedname) - return text_('object %s' % str(object)) + return 'function %s' % dottedname + return 'object %s' % str(object) def shortrepr(object, closer): @@ -487,11 +508,17 @@ class TopologicalSorter(object): has_after.add(b) if not self.req_before.issubset(has_before): + # avoid circular dependency + from pyramid.exceptions import ConfigurationError + raise ConfigurationError( 'Unsatisfied before dependencies: %s' % (', '.join(sorted(self.req_before - has_before))) ) if not self.req_after.issubset(has_after): + # avoid circular dependency + from pyramid.exceptions import ConfigurationError + raise ConfigurationError( 'Unsatisfied after dependencies: %s' % (', '.join(sorted(self.req_after - has_after))) @@ -512,6 +539,9 @@ class TopologicalSorter(object): del graph[root] if graph: + # avoid circular dependency + from pyramid.exceptions import CyclicDependencyError + # loop in input cycledeps = {} for k, v in graph.items(): @@ -533,8 +563,11 @@ def get_callable_name(name): if it is not. """ try: - return native_(name, 'ascii') + return ascii_(name) except (UnicodeEncodeError, UnicodeDecodeError): + # avoid circular dependency + from pyramid.exceptions import ConfigurationError + msg = ( '`name="%s"` is invalid. `name` must be ascii because it is ' 'used on __name__ of the method' @@ -641,7 +674,7 @@ def takes_one_arg(callee, attr=None, argname=None): class SimpleSerializer(object): def loads(self, bstruct): - return native_(bstruct) + return text_(bstruct) def dumps(self, appstruct): return bytes_(appstruct) diff --git a/tests/pkgs/forbiddenapp/__init__.py b/tests/pkgs/forbiddenapp/__init__.py index 9ebf62a9d..31ea4dd52 100644 --- a/tests/pkgs/forbiddenapp/__init__.py +++ b/tests/pkgs/forbiddenapp/__init__.py @@ -1,6 +1,6 @@ from webob import Response from pyramid.httpexceptions import HTTPForbidden -from pyramid.compat import bytes_ +from pyramid.util import bytes_ def x_view(request): # pragma: no cover diff --git a/tests/test_authentication.py b/tests/test_authentication.py index a18ccaeb4..8671eba05 100644 --- a/tests/test_authentication.py +++ b/tests/test_authentication.py @@ -2,7 +2,7 @@ from http.cookies import SimpleCookie import unittest import warnings from pyramid import testing -from pyramid.compat import text_, bytes_ +from pyramid.util import text_, bytes_ class TestCallbackAuthenticationPolicyDebugging(unittest.TestCase): diff --git a/tests/test_config/test_predicates.py b/tests/test_config/test_predicates.py index 079652b39..c27b41639 100644 --- a/tests/test_config/test_predicates.py +++ b/tests/test_config/test_predicates.py @@ -1,6 +1,6 @@ import unittest -from pyramid.compat import text_ +from pyramid.util import text_ class TestPredicateList(unittest.TestCase): diff --git a/tests/test_config/test_routes.py b/tests/test_config/test_routes.py index e6540c343..4ff67cf66 100644 --- a/tests/test_config/test_routes.py +++ b/tests/test_config/test_routes.py @@ -2,7 +2,7 @@ import unittest from . import dummyfactory from . import DummyContext -from pyramid.compat import text_ +from pyramid.util import text_ class RoutesConfiguratorMixinTests(unittest.TestCase): diff --git a/tests/test_config/test_testing.py b/tests/test_config/test_testing.py index 870bbe9fa..0fb73d268 100644 --- a/tests/test_config/test_testing.py +++ b/tests/test_config/test_testing.py @@ -1,8 +1,8 @@ import unittest from zope.interface import implementer -from pyramid.compat import text_ from pyramid.security import AuthenticationAPIMixin, AuthorizationAPIMixin +from pyramid.util import text_ from . import IDummy diff --git a/tests/test_config/test_views.py b/tests/test_config/test_views.py index e25ee881e..685b81a0f 100644 --- a/tests/test_config/test_views.py +++ b/tests/test_config/test_views.py @@ -3,11 +3,11 @@ import unittest from zope.interface import implementer from pyramid import testing -from pyramid.compat import text_ from pyramid.exceptions import ConfigurationError from pyramid.exceptions import ConfigurationExecutionError from pyramid.exceptions import ConfigurationConflictError from pyramid.interfaces import IResponse, IRequest, IMultiView +from pyramid.util import text_ from . import IDummy from . import dummy_view diff --git a/tests/test_encode.py b/tests/test_encode.py index f70050cac..4df08d509 100644 --- a/tests/test_encode.py +++ b/tests/test_encode.py @@ -1,5 +1,5 @@ import unittest -from pyramid.compat import text_, native_ +from pyramid.util import text_ class UrlEncodeTests(unittest.TestCase): @@ -74,7 +74,7 @@ class URLQuoteTests(unittest.TestCase): self.assertEqual(result, 'La%2FPe%C3%B1a') def test_it_native(self): - la = native_(b'La/Pe\xc3\xb1a', 'utf-8') + la = text_(b'La/Pe\xc3\xb1a', 'utf-8') result = self._callFUT(la) self.assertEqual(result, 'La%2FPe%C3%B1a') diff --git a/tests/test_httpexceptions.py b/tests/test_httpexceptions.py index 48c4a22f3..5decfc39c 100644 --- a/tests/test_httpexceptions.py +++ b/tests/test_httpexceptions.py @@ -1,6 +1,6 @@ import unittest -from pyramid.compat import bytes_, text_ +from pyramid.util import bytes_, text_ class Test_exception_response(unittest.TestCase): diff --git a/tests/test_integration.py b/tests/test_integration.py index 0652d8ee8..d1f65274b 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -8,11 +8,11 @@ from urllib.parse import quote from webtest import TestApp from zope.interface import Interface -from pyramid.wsgi import wsgiapp -from pyramid.view import view_config from pyramid.static import static_view from pyramid.testing import skip_on -from pyramid.compat import text_ +from pyramid.util import text_ +from pyramid.view import view_config +from pyramid.wsgi import wsgiapp from .pkgs.exceptionviewapp.models import AnException, NotAnException diff --git a/tests/test_predicates.py b/tests/test_predicates.py index c072b4229..a99651a8f 100644 --- a/tests/test_predicates.py +++ b/tests/test_predicates.py @@ -2,7 +2,7 @@ import unittest from pyramid import testing -from pyramid.compat import text_ +from pyramid.util import text_ class TestXHRPredicate(unittest.TestCase): diff --git a/tests/test_renderers.py b/tests/test_renderers.py index 0e9f99d15..db8b3b4f2 100644 --- a/tests/test_renderers.py +++ b/tests/test_renderers.py @@ -1,8 +1,8 @@ import unittest -from pyramid.testing import cleanUp from pyramid import testing -from pyramid.compat import text_ +from pyramid.testing import cleanUp +from pyramid.util import text_ class TestJSON(unittest.TestCase): diff --git a/tests/test_request.py b/tests/test_request.py index 60cc2b31a..484d86e01 100644 --- a/tests/test_request.py +++ b/tests/test_request.py @@ -1,8 +1,8 @@ import unittest from pyramid import testing -from pyramid.compat import text_, bytes_, native_ from pyramid.security import AuthenticationAPIMixin, AuthorizationAPIMixin +from pyramid.util import text_, bytes_ class TestRequest(unittest.TestCase): @@ -478,7 +478,7 @@ class Test_call_app_with_subpath_as_path_info(unittest.TestCase): self.assertEqual(request.environ['PATH_INFO'], '/hello/') def test_subpath_path_info_and_script_name_have_utf8(self): - encoded = native_(text_(b'La Pe\xc3\xb1a')) + encoded = text_(b'La Pe\xc3\xb1a') decoded = text_(bytes_(encoded), 'utf-8') request = DummyRequest( {'PATH_INFO': '/' + encoded, 'SCRIPT_NAME': '/' + encoded} diff --git a/tests/test_response.py b/tests/test_response.py index 5231e47f0..18d4335ad 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -73,9 +73,9 @@ class TestFileResponse(unittest.TestCase): # function returns Unicode for the content_type, unlike any previous # version of Python. See https://github.com/Pylons/pyramid/issues/1360 # for more information. - from pyramid.compat import text_ import mimetypes as old_mimetypes from pyramid import response + from pyramid.util import text_ class FakeMimetypesModule(object): def guess_type(self, *arg, **kw): diff --git a/tests/test_traversal.py b/tests/test_traversal.py index 252e99f6f..de712a6e8 100644 --- a/tests/test_traversal.py +++ b/tests/test_traversal.py @@ -4,7 +4,7 @@ from urllib.parse import quote from pyramid.testing import cleanUp -from pyramid.compat import text_, native_ +from pyramid.util import text_ class TraversalPathTests(unittest.TestCase): @@ -87,15 +87,14 @@ class TraversalPathInfoTests(unittest.TestCase): def test_highorder(self): la = b'La Pe\xc3\xb1a' - latin1 = native_(la) + latin1 = text_(la) result = self._callFUT(latin1) self.assertEqual(result, (text_(la, 'utf-8'),)) def test_highorder_undecodeable(self): from pyramid.exceptions import URLDecodeError - la = text_(b'La Pe\xc3\xb1a', 'utf-8') - notlatin1 = native_(la) + notlatin1 = text_(b'La Pe\xc3\xb1a', 'utf-8') self.assertRaises(URLDecodeError, self._callFUT, notlatin1) diff --git a/tests/test_url.py b/tests/test_url.py index a852f3301..4c761ce50 100644 --- a/tests/test_url.py +++ b/tests/test_url.py @@ -3,7 +3,8 @@ import unittest from pyramid import testing -from pyramid.compat import text_, WIN +from pyramid.compat import WIN +from pyramid.util import text_ class TestURLMethodsMixin(unittest.TestCase): diff --git a/tests/test_urldispatch.py b/tests/test_urldispatch.py index a74731730..5d77042ae 100644 --- a/tests/test_urldispatch.py +++ b/tests/test_urldispatch.py @@ -1,6 +1,6 @@ import unittest from pyramid import testing -from pyramid.compat import text_ +from pyramid.util import text_ class TestRoute(unittest.TestCase): diff --git a/tests/test_util.py b/tests/test_util.py index d6d1d1502..0f313955b 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -1,6 +1,6 @@ import sys import unittest -from pyramid.compat import text_, bytes_ +from pyramid.util import text_, bytes_ class Test_InstancePropertyHelper(unittest.TestCase): @@ -833,21 +833,26 @@ class TestSentinel(unittest.TestCase): class TestCallableName(unittest.TestCase): - def test_valid_ascii(self): + def _callFUT(self, val): from pyramid.util import get_callable_name + return get_callable_name(val) + + def test_valid_ascii_bytes(self): name = b'hello world' - self.assertEqual(get_callable_name(name), 'hello world') + self.assertEqual(self._callFUT(name), 'hello world') - def test_invalid_ascii(self): - from pyramid.util import get_callable_name + def test_valid_ascii_string(self): from pyramid.exceptions import ConfigurationError - def get_bad_name(): - name = b'La Pe\xc3\xb1a' - get_callable_name(name) + name = b'La Pe\xc3\xb1a'.decode('utf-8') + self.assertRaises(ConfigurationError, self._callFUT, name) - self.assertRaises(ConfigurationError, get_bad_name) + def test_invalid_ascii(self): + from pyramid.exceptions import ConfigurationError + + name = b'La Pe\xc3\xb1a' + self.assertRaises(ConfigurationError, self._callFUT, name) class Test_hide_attrs(unittest.TestCase): -- cgit v1.2.3 From d4ce9d3f915b18425b4ce5224a51a89342270371 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 14 Nov 2018 22:23:51 -0600 Subject: move WIN and PYPY tests into pyramid.util --- src/pyramid/compat.py | 11 ----------- src/pyramid/config/views.py | 8 ++++++-- src/pyramid/testing.py | 4 +--- src/pyramid/util.py | 11 +++++++++++ tests/test_url.py | 3 +-- 5 files changed, 19 insertions(+), 18 deletions(-) delete mode 100644 src/pyramid/compat.py diff --git a/src/pyramid/compat.py b/src/pyramid/compat.py deleted file mode 100644 index ec0ca7017..000000000 --- a/src/pyramid/compat.py +++ /dev/null @@ -1,11 +0,0 @@ -import platform - -WIN = platform.system() == 'Windows' - -try: # pragma: no cover - import __pypy__ - - PYPY = True -except BaseException: # pragma: no cover - __pypy__ = None - PYPY = False diff --git a/src/pyramid/config/views.py b/src/pyramid/config/views.py index 412e30304..ac531ecb2 100644 --- a/src/pyramid/config/views.py +++ b/src/pyramid/config/views.py @@ -33,7 +33,6 @@ from pyramid.interfaces import ( from pyramid import renderers from pyramid.asset import resolve_asset_spec -from pyramid.compat import WIN from pyramid.decorator import reify @@ -54,7 +53,12 @@ from pyramid.url import parse_url_overrides from pyramid.view import AppendSlashNotFoundViewFactory -from pyramid.util import as_sorted_tuple, is_nonstr_iter, TopologicalSorter +from pyramid.util import ( + as_sorted_tuple, + is_nonstr_iter, + TopologicalSorter, + WIN, +) import pyramid.predicates import pyramid.viewderivers diff --git a/src/pyramid/testing.py b/src/pyramid/testing.py index 04497234b..6831ea4e2 100644 --- a/src/pyramid/testing.py +++ b/src/pyramid/testing.py @@ -8,8 +8,6 @@ from zope.interface import implementer, alsoProvides from pyramid.interfaces import IRequest, ISession -from pyramid.compat import PYPY - from pyramid.config import Configurator from pyramid.decorator import reify from pyramid.path import caller_package @@ -28,7 +26,7 @@ from pyramid.threadlocal import get_current_registry, manager from pyramid.i18n import LocalizerRequestMixin from pyramid.request import CallbackMethodsMixin from pyramid.url import URLMethodsMixin -from pyramid.util import InstancePropertyMixin, text_ +from pyramid.util import InstancePropertyMixin, PYPY, text_ from pyramid.view import ViewMethodsMixin diff --git a/src/pyramid/util.py b/src/pyramid/util.py index ab46c91f7..1180fce83 100644 --- a/src/pyramid/util.py +++ b/src/pyramid/util.py @@ -2,12 +2,23 @@ from contextlib import contextmanager import functools from hmac import compare_digest import inspect +import platform import weakref from pyramid.path import DottedNameResolver as _DottedNameResolver _marker = object() +WIN = platform.system() == 'Windows' + +try: # pragma: no cover + import __pypy__ + + PYPY = True +except BaseException: # pragma: no cover + __pypy__ = None + PYPY = False + class DottedNameResolver(_DottedNameResolver): def __init__( diff --git a/tests/test_url.py b/tests/test_url.py index 4c761ce50..648f48d53 100644 --- a/tests/test_url.py +++ b/tests/test_url.py @@ -3,8 +3,7 @@ import unittest from pyramid import testing -from pyramid.compat import WIN -from pyramid.util import text_ +from pyramid.util import WIN, text_ class TestURLMethodsMixin(unittest.TestCase): -- cgit v1.2.3 From a6d8b494c3de85eceb33ab57a046fb6955f909fc Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Fri, 16 Nov 2018 05:34:00 -0800 Subject: Remove most Python 2 mentions from docs. Exclusions: - `docs/tutorials/wiki/*` was written for Python 2 and supposedly won't run on Python 3. - `docs/api/compat.rst` because it drops some stuff and moves other bits into `pyramid.util` and should be part of another PR. - Ignored a couple of times where `env27` and `Python 2.` appear in command output, but they are inconsequential. --- docs/glossary.rst | 2 +- docs/narr/install.rst | 3 +-- docs/narr/upgrading.rst | 1 - docs/narr/webob.rst | 3 +-- docs/quick_tour.rst | 15 +++++---------- docs/quick_tutorial/index.rst | 3 +-- docs/quick_tutorial/requirements.rst | 9 ++++----- docs/tutorials/wiki2/background.rst | 2 +- docs/tutorials/wiki2/installation.rst | 10 ---------- 9 files changed, 14 insertions(+), 34 deletions(-) diff --git a/docs/glossary.rst b/docs/glossary.rst index f42b298df..e15d28fa5 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -1071,7 +1071,7 @@ Glossary Green Unicorn Aka ``gunicorn``, a fast :term:`WSGI` server that runs on Unix under - Python 2.6+ or Python 3.1+. See https://gunicorn.org/ for detailed + Python 2.6+ or Python 3.4+. See https://gunicorn.org/ for detailed information. predicate factory diff --git a/docs/narr/install.rst b/docs/narr/install.rst index 248b432d3..268ae5f8d 100644 --- a/docs/narr/install.rst +++ b/docs/narr/install.rst @@ -21,8 +21,7 @@ the following sections. .. sidebar:: Python Versions - As of this writing, :app:`Pyramid` is tested against Python 2.7, - Python 3.4, Python 3.5, Python 3.6, Python 3.7, and PyPy. + As of this writing, :app:`Pyramid` is tested against Python 3.4, Python 3.5, Python 3.6, Python 3.7, Python 3.8 (with allowed failures), and PyPy3. :app:`Pyramid` is known to run on all popular Unix-like systems such as Linux, macOS, and FreeBSD, as well as on Windows platforms. It is also known to diff --git a/docs/narr/upgrading.rst b/docs/narr/upgrading.rst index 12e146cf1..87e4647c3 100644 --- a/docs/narr/upgrading.rst +++ b/docs/narr/upgrading.rst @@ -86,7 +86,6 @@ At the time of a Pyramid version release, each supports all versions of Python through the end of their lifespans. The end-of-life for a given version of Python is when security updates are no longer released. -- `Python 2.7 Lifespan `_ 2020-01-01. - `Python 3.4 Lifespan `_ 2019-03-16 . - `Python 3.5 Lifespan `_ 2020-09-13 . - `Python 3.6 Lifespan `_ 2021-12-23. diff --git a/docs/narr/webob.rst b/docs/narr/webob.rst index 89dc6e0f1..c9a5a68e1 100644 --- a/docs/narr/webob.rst +++ b/docs/narr/webob.rst @@ -188,8 +188,7 @@ of them. Here are a couple that might be useful: Text (Unicode) ++++++++++++++ -Many of the properties of the request object will be text values (``unicode`` -under Python 2 or ``str`` under Python 3) if the request encoding/charset is +Many of the properties of the request object will be text values (``str`` type) if the request encoding/charset is provided. If it is provided, the values in ``req.POST``, ``req.GET``, ``req.params``, and ``req.cookies`` will contain text. The client *can* indicate the charset with something like ``Content-Type: diff --git a/docs/quick_tour.rst b/docs/quick_tour.rst index 1e2c71cf0..fbafead66 100644 --- a/docs/quick_tour.rst +++ b/docs/quick_tour.rst @@ -15,12 +15,9 @@ If you would prefer to cut and paste the example code in this tour you may brows Installation ============ -Once you have a standard Python environment setup, getting started with Pyramid -is a breeze. Unfortunately "standard" is not so simple in Python. For this -Quick Tour, it means `Python `_, :mod:`python:venv` (or `virtualenv for -Python 2.7 `_), -`pip `_, and `Setuptools -`_. +Once you have a standard Python environment setup, getting started with Pyramid is a breeze. +Unfortunately "standard" is not so simple in Python. +For this Quick Tour, it means `Python `_, :mod:`python:venv`, `pip `_, and `Setuptools `_. To save a little bit of typing and to be certain that we use the modules, scripts, and packages installed in our virtual environment, we'll set an @@ -52,10 +49,8 @@ For Windows: # or for a specific released version c:\\> %VENV%\\Scripts\\pip install "pyramid==\ |release|\ " -Of course Pyramid runs fine on Python 2.7+, as do the examples in this *Quick -Tour*. We're showing Python 3 for simplicity. (Pyramid had production support -for Python 3 in October 2011.) Also for simplicity, the remaining examples will -show only Unix commands. +As of version 2.0, Pyramid runs on Python 3 only. +For simplicity, the remaining examples will show only Unix commands. .. seealso:: See also: :ref:`Quick Tutorial section on Requirements `, diff --git a/docs/quick_tutorial/index.rst b/docs/quick_tutorial/index.rst index b5b7b3313..44a172067 100644 --- a/docs/quick_tutorial/index.rst +++ b/docs/quick_tutorial/index.rst @@ -4,8 +4,7 @@ Quick Tutorial for Pyramid ========================== -Pyramid is a web framework for Python 2 and 3. This tutorial gives a Python -3/2-compatible, high-level tour of the major features. +Pyramid is a web framework for Python 3. This tutorial gives a Python 3-compatible, high-level tour of the major features. This hands-on tutorial covers "a little about a lot": practical introductions to the most common facilities. Fun, fast-paced, and most certainly not aimed at diff --git a/docs/quick_tutorial/requirements.rst b/docs/quick_tutorial/requirements.rst index a74c07673..f3ffdf153 100644 --- a/docs/quick_tutorial/requirements.rst +++ b/docs/quick_tutorial/requirements.rst @@ -19,12 +19,11 @@ virtual environment.) This *Quick Tutorial* is based on: -* **Python 3.7**. Pyramid fully supports Python 3.4+ and Python 2.7+. This - tutorial uses **Python 3.7** but runs fine under Python 2.7. +* **Python 3.7**. Pyramid fully supports Python 3.4+. + This tutorial uses **Python 3.7**. -* **venv**. We believe in virtual environments. For this tutorial, we use - Python 3's built-in solution :term:`venv`. For Python 2.7, you can install - :term:`virtualenv`. +* **venv**. We believe in virtual environments. + For this tutorial, we use Python 3's built-in solution :term:`venv`. * **pip**. We use :term:`pip` for package management. diff --git a/docs/tutorials/wiki2/background.rst b/docs/tutorials/wiki2/background.rst index c14d3cb7d..09315a77d 100644 --- a/docs/tutorials/wiki2/background.rst +++ b/docs/tutorials/wiki2/background.rst @@ -17,6 +17,6 @@ variant, etc.) *or* a Windows system of any kind. .. note:: - This tutorial runs on both Python 2 and 3 without modification. + This tutorial runs on Python 3 without modification. Have fun! diff --git a/docs/tutorials/wiki2/installation.rst b/docs/tutorials/wiki2/installation.rst index 924927cd4..705979065 100644 --- a/docs/tutorials/wiki2/installation.rst +++ b/docs/tutorials/wiki2/installation.rst @@ -126,16 +126,6 @@ On Unix On Windows ^^^^^^^^^^ -Each version of Python uses different paths, so you will need to adjust the path to the command for your Python version. Recent versions of the Python 3 installer for Windows now install a Python launcher. - -Python 2.7: - -.. code-block:: doscon - - c:\Python27\Scripts\virtualenv %VENV% - -Python 3.7: - .. code-block:: doscon python -m venv %VENV% -- cgit v1.2.3 From 699bd8720dc16ef5dd3321ebbeaa6fa94505b33f Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Fri, 16 Nov 2018 05:38:29 -0800 Subject: Remove virtualenv and explicit py3 mentions --- docs/narr/commandline.rst | 8 ++++---- docs/quick_tutorial/requirements.rst | 3 +-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst index b571a7d7b..962193ec3 100644 --- a/docs/narr/commandline.rst +++ b/docs/narr/commandline.rst @@ -1006,8 +1006,8 @@ top-level directory, your ``setup.py`` file will look something like this: requires = ['pyramid', 'pyramid_debugtoolbar'] tests_require = [ - 'WebTest >= 1.3.1', # py3 compat - 'pytest', # includes virtualenv + 'WebTest >= 1.3.1', + 'pytest', 'pytest-cov', ] @@ -1073,8 +1073,8 @@ The result will be something like: requires = ['pyramid', 'pyramid_debugtoolbar'] tests_require = [ - 'WebTest >= 1.3.1', # py3 compat - 'pytest', # includes virtualenv + 'WebTest >= 1.3.1', + 'pytest', 'pytest-cov', ] diff --git a/docs/quick_tutorial/requirements.rst b/docs/quick_tutorial/requirements.rst index f3ffdf153..2ed9b8b55 100644 --- a/docs/quick_tutorial/requirements.rst +++ b/docs/quick_tutorial/requirements.rst @@ -157,8 +157,7 @@ environment variable. # Windows python -m venv %VENV% -.. seealso:: See also Python 3's :mod:`venv module ` and Python - 2's `virtualenv `_ package. +.. seealso:: See also :mod:`venv module `. Update packaging tools in the virtual environment -- cgit v1.2.3 From 6de8255cb8078dbc8db63c82487cb393ce6b8552 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 17 Nov 2018 05:50:31 -0800 Subject: Remove *_types per b1a257bacc1c4ac2c1401ed02c51d9c6c03685d2 - Remove Python 2 items, and remove explicit mention of Python 3. --- docs/api/compat.rst | 84 +++++++++++++---------------------------------------- 1 file changed, 20 insertions(+), 64 deletions(-) diff --git a/docs/api/compat.rst b/docs/api/compat.rst index bb34f38e4..b54b4868a 100644 --- a/docs/api/compat.rst +++ b/docs/api/compat.rst @@ -12,145 +12,101 @@ systems which require compatibility imports. .. autofunction:: ascii_native_ - .. attribute:: binary_type - - Binary type for this platform. For Python 3, it's ``bytes``. For - Python 2, it's ``str``. - .. autofunction:: bytes_ - .. attribute:: class_types - - Sequence of class types for this platform. For Python 3, it's - ``(type,)``. For Python 2, it's ``(type, types.ClassType)``. - .. attribute:: configparser - On Python 2, the ``ConfigParser`` module, on Python 3, the - ``configparser`` module. + The ``configparser`` module. .. function:: escape(v) - On Python 2, the ``cgi.escape`` function, on Python 3, the - ``html.escape`` function. + The ``html.escape`` function. .. function:: exec_(code, globs=None, locs=None) - Exec code in a compatible way on both Python 2 and 3. + Exec code. .. attribute:: im_func - On Python 2, the string value ``im_func``, on Python 3, the string - value ``__func__``. + The string value ``__func__``. .. function:: input_(v) - On Python 2, the ``raw_input`` function, on Python 3, the - ``input`` function. - - .. attribute:: integer_types - - Sequence of integer types for this platform. For Python 3, it's - ``(int,)``. For Python 2, it's ``(int, long)``. + The ``input`` function. .. function:: is_nonstr_iter(v) - Return ``True`` if ``v`` is a non-``str`` iterable on both Python 2 and - Python 3. + Return ``True`` if ``v`` is a non-``str``. .. function:: iteritems_(d) - Return ``d.items()`` on Python 3, ``d.iteritems()`` on Python 2. + Return ``d.items()``. .. function:: itervalues_(d) - Return ``d.values()`` on Python 3, ``d.itervalues()`` on Python 2. + Return ``d.values()``. .. function:: iterkeys_(d) - Return ``d.keys()`` on Python 3, ``d.iterkeys()`` on Python 2. + Return ``d.keys()``. .. attribute:: long - Long type for this platform. For Python 3, it's ``int``. For - Python 2, it's ``long``. + Long type ``int``. .. function:: map_(v) - Return ``list(map(v))`` on Python 3, ``map(v)`` on Python 2. + Return ``list(map(v))``. .. attribute:: pickle ``cPickle`` module if it exists, ``pickle`` module otherwise. - .. attribute:: PY3 - - ``True`` if running on Python 3, ``False`` otherwise. - .. attribute:: PYPY ``True`` if running on PyPy, ``False`` otherwise. .. function:: reraise(tp, value, tb=None) - Reraise an exception in a compatible way on both Python 2 and Python 3, - e.g. ``reraise(*sys.exc_info())``. - - .. attribute:: string_types - - Sequence of string types for this platform. For Python 3, it's - ``(str,)``. For Python 2, it's ``(basestring,)``. + Reraise an exception ``reraise(*sys.exc_info())``. .. attribute:: SimpleCookie - On Python 2, the ``Cookie.SimpleCookie`` class, on Python 3, the ``http.cookies.SimpleCookie`` module. .. autofunction:: text_ - .. attribute:: text_type - - Text type for this platform. For Python 3, it's ``str``. For Python - 2, it's ``unicode``. - .. autofunction:: native_ .. attribute:: urlparse - ``urlparse`` module on Python 2, ``urllib.parse`` module on Python 3. + ``urllib.parse`` .. attribute:: url_quote - ``urllib.quote`` function on Python 2, ``urllib.parse.quote`` function - on Python 3. + ``urllib.parse.quote`` .. attribute:: url_quote_plus - ``urllib.quote_plus`` function on Python 2, ``urllib.parse.quote_plus`` - function on Python 3. + ``urllib.parse.quote_plus`` .. attribute:: url_unquote - ``urllib.unquote`` function on Python 2, ``urllib.parse.unquote`` - function on Python 3. + ``urllib.parse.unquote`` .. attribute:: url_encode - ``urllib.urlencode`` function on Python 2, ``urllib.parse.urlencode`` - function on Python 3. + ``urllib.parse.urlencode`` .. attribute:: url_open - ``urllib2.urlopen`` function on Python 2, ``urllib.request.urlopen`` - function on Python 3. + ``urllib.request.urlopen`` .. function:: url_unquote_text(v, encoding='utf-8', errors='replace') - On Python 2, return ``url_unquote(v).decode(encoding(encoding, errors))``; - on Python 3, return the result of ``urllib.parse.unquote``. + Return the result of ``urllib.parse.unquote``. .. function:: url_unquote_native(v, encoding='utf-8', errors='replace') - On Python 2, return ``native_(url_unquote_text_v, encoding, errors))``; - on Python 3, return the result of ``urllib.parse.unquote``. + Return the result of ``urllib.parse.unquote``. -- cgit v1.2.3 From e6beded7543305b47ef7bc2ba8a0d9ff76ceb5b1 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 17 Nov 2018 05:52:12 -0800 Subject: Remove pickle and exec aliases --- docs/api/compat.rst | 8 -------- 1 file changed, 8 deletions(-) diff --git a/docs/api/compat.rst b/docs/api/compat.rst index b54b4868a..c3816f8e9 100644 --- a/docs/api/compat.rst +++ b/docs/api/compat.rst @@ -22,10 +22,6 @@ systems which require compatibility imports. The ``html.escape`` function. - .. function:: exec_(code, globs=None, locs=None) - - Exec code. - .. attribute:: im_func The string value ``__func__``. @@ -58,10 +54,6 @@ systems which require compatibility imports. Return ``list(map(v))``. - .. attribute:: pickle - - ``cPickle`` module if it exists, ``pickle`` module otherwise. - .. attribute:: PYPY ``True`` if running on PyPy, ``False`` otherwise. -- cgit v1.2.3 From 4863dfea9783c11e70f1604415dbc32c12d59ac6 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 17 Nov 2018 05:55:07 -0800 Subject: Remove iter*, configparser, input_, map_ --- docs/api/compat.rst | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/docs/api/compat.rst b/docs/api/compat.rst index c3816f8e9..f647c3108 100644 --- a/docs/api/compat.rst +++ b/docs/api/compat.rst @@ -14,10 +14,6 @@ systems which require compatibility imports. .. autofunction:: bytes_ - .. attribute:: configparser - - The ``configparser`` module. - .. function:: escape(v) The ``html.escape`` function. @@ -26,34 +22,14 @@ systems which require compatibility imports. The string value ``__func__``. - .. function:: input_(v) - - The ``input`` function. - .. function:: is_nonstr_iter(v) Return ``True`` if ``v`` is a non-``str``. - .. function:: iteritems_(d) - - Return ``d.items()``. - - .. function:: itervalues_(d) - - Return ``d.values()``. - - .. function:: iterkeys_(d) - - Return ``d.keys()``. - .. attribute:: long Long type ``int``. - .. function:: map_(v) - - Return ``list(map(v))``. - .. attribute:: PYPY ``True`` if running on PyPy, ``False`` otherwise. -- cgit v1.2.3 From f84582524b625265ad379e41513770aefe7c6dcd Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 17 Nov 2018 05:56:42 -0800 Subject: Remove SimpleCookie and escape shims --- docs/api/compat.rst | 8 -------- 1 file changed, 8 deletions(-) diff --git a/docs/api/compat.rst b/docs/api/compat.rst index f647c3108..e25290c37 100644 --- a/docs/api/compat.rst +++ b/docs/api/compat.rst @@ -14,10 +14,6 @@ systems which require compatibility imports. .. autofunction:: bytes_ - .. function:: escape(v) - - The ``html.escape`` function. - .. attribute:: im_func The string value ``__func__``. @@ -38,10 +34,6 @@ systems which require compatibility imports. Reraise an exception ``reraise(*sys.exc_info())``. - .. attribute:: SimpleCookie - - ``http.cookies.SimpleCookie`` module. - .. autofunction:: text_ .. autofunction:: native_ -- cgit v1.2.3 From 5f59fb7db454e47e9845d74ec2b04e581beca574 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 17 Nov 2018 05:57:06 -0800 Subject: Remove reraise --- docs/api/compat.rst | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/api/compat.rst b/docs/api/compat.rst index e25290c37..6f94ea2d4 100644 --- a/docs/api/compat.rst +++ b/docs/api/compat.rst @@ -30,10 +30,6 @@ systems which require compatibility imports. ``True`` if running on PyPy, ``False`` otherwise. - .. function:: reraise(tp, value, tb=None) - - Reraise an exception ``reraise(*sys.exc_info())``. - .. autofunction:: text_ .. autofunction:: native_ -- cgit v1.2.3 From 087dc33696b81235468e80b93ee7eee510d40d8e Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 17 Nov 2018 05:57:30 -0800 Subject: Remove is_nonstr_iter --- docs/api/compat.rst | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/api/compat.rst b/docs/api/compat.rst index 6f94ea2d4..25b81addc 100644 --- a/docs/api/compat.rst +++ b/docs/api/compat.rst @@ -18,10 +18,6 @@ systems which require compatibility imports. The string value ``__func__``. - .. function:: is_nonstr_iter(v) - - Return ``True`` if ``v`` is a non-``str``. - .. attribute:: long Long type ``int``. -- cgit v1.2.3 From c2fabed4dceb33c655a498583841769aa78bebbf Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 17 Nov 2018 06:00:19 -0800 Subject: Remove urllib.parse shims --- docs/api/compat.rst | 33 --------------------------------- 1 file changed, 33 deletions(-) diff --git a/docs/api/compat.rst b/docs/api/compat.rst index 25b81addc..d8a4d0cbb 100644 --- a/docs/api/compat.rst +++ b/docs/api/compat.rst @@ -29,36 +29,3 @@ systems which require compatibility imports. .. autofunction:: text_ .. autofunction:: native_ - - .. attribute:: urlparse - - ``urllib.parse`` - - .. attribute:: url_quote - - ``urllib.parse.quote`` - - .. attribute:: url_quote_plus - - ``urllib.parse.quote_plus`` - - .. attribute:: url_unquote - - ``urllib.parse.unquote`` - - .. attribute:: url_encode - - ``urllib.parse.urlencode`` - - .. attribute:: url_open - - ``urllib.request.urlopen`` - - .. function:: url_unquote_text(v, encoding='utf-8', errors='replace') - - Return the result of ``urllib.parse.unquote``. - - .. function:: url_unquote_native(v, encoding='utf-8', errors='replace') - - Return the result of ``urllib.parse.unquote``. - -- cgit v1.2.3 From 8b7705f27a6ecc47742ec743de0ced7e8a15eb17 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 17 Nov 2018 06:05:42 -0800 Subject: Remove native_, rename ascii_native_ to ascii_ --- docs/api/compat.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/api/compat.rst b/docs/api/compat.rst index d8a4d0cbb..f05bdd51b 100644 --- a/docs/api/compat.rst +++ b/docs/api/compat.rst @@ -10,7 +10,7 @@ systems which require compatibility imports. .. automodule:: pyramid.compat - .. autofunction:: ascii_native_ + .. autofunction:: ascii_ .. autofunction:: bytes_ @@ -27,5 +27,3 @@ systems which require compatibility imports. ``True`` if running on PyPy, ``False`` otherwise. .. autofunction:: text_ - - .. autofunction:: native_ -- cgit v1.2.3 From 1662edc38e145fa820544fb3aad91ab86e3185da Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 17 Nov 2018 06:07:44 -0800 Subject: Remove long type (should have been removed with other type aliases) --- docs/api/compat.rst | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/api/compat.rst b/docs/api/compat.rst index f05bdd51b..d1552e644 100644 --- a/docs/api/compat.rst +++ b/docs/api/compat.rst @@ -18,10 +18,6 @@ systems which require compatibility imports. The string value ``__func__``. - .. attribute:: long - - Long type ``int``. - .. attribute:: PYPY ``True`` if running on PyPy, ``False`` otherwise. -- cgit v1.2.3 From 5ec822694f6d2e14513b3a3b03da2315ff996ce7 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 17 Nov 2018 06:18:26 -0800 Subject: Update Python source files in docs to use `from html import escape`, now that we ripped it out from `pyramid.compat`. --- docs/quick_tour/views/views.py | 4 ++-- docs/tutorials/wiki2/src/authentication/tutorial/views/default.py | 2 +- docs/tutorials/wiki2/src/authorization/tutorial/views/default.py | 2 +- docs/tutorials/wiki2/src/tests/tutorial/views/default.py | 2 +- docs/tutorials/wiki2/src/views/tutorial/views/default.py | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/quick_tour/views/views.py b/docs/quick_tour/views/views.py index 95a2b60ca..ffbe1d893 100644 --- a/docs/quick_tour/views/views.py +++ b/docs/quick_tour/views/views.py @@ -1,4 +1,4 @@ -from pyramid.compat import escape +from html import escape from pyramid.httpexceptions import HTTPFound from pyramid.response import Response @@ -16,7 +16,7 @@ def home_view(request): def hello_view(request): name = request.params.get('name', 'No Name') body = '

Hi %s, this redirects

' - # pyramid.compat.escape to prevent Cross-Site Scripting (XSS) [CWE 79] + # Python html.escape to prevent Cross-Site Scripting (XSS) [CWE 79] return Response(body % escape(name)) diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/views/default.py b/docs/tutorials/wiki2/src/authentication/tutorial/views/default.py index 8ed90d5b2..2f0210255 100644 --- a/docs/tutorials/wiki2/src/authentication/tutorial/views/default.py +++ b/docs/tutorials/wiki2/src/authentication/tutorial/views/default.py @@ -1,4 +1,4 @@ -from pyramid.compat import escape +from html import escape import re from docutils.core import publish_parts diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/views/default.py b/docs/tutorials/wiki2/src/authorization/tutorial/views/default.py index ad271fb46..ad8491b7b 100644 --- a/docs/tutorials/wiki2/src/authorization/tutorial/views/default.py +++ b/docs/tutorials/wiki2/src/authorization/tutorial/views/default.py @@ -1,4 +1,4 @@ -from pyramid.compat import escape +from html import escape import re from docutils.core import publish_parts diff --git a/docs/tutorials/wiki2/src/tests/tutorial/views/default.py b/docs/tutorials/wiki2/src/tests/tutorial/views/default.py index ad271fb46..ad8491b7b 100644 --- a/docs/tutorials/wiki2/src/tests/tutorial/views/default.py +++ b/docs/tutorials/wiki2/src/tests/tutorial/views/default.py @@ -1,4 +1,4 @@ -from pyramid.compat import escape +from html import escape import re from docutils.core import publish_parts diff --git a/docs/tutorials/wiki2/src/views/tutorial/views/default.py b/docs/tutorials/wiki2/src/views/tutorial/views/default.py index a866af1de..5e28b64fd 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/views/default.py +++ b/docs/tutorials/wiki2/src/views/tutorial/views/default.py @@ -1,4 +1,4 @@ -from pyramid.compat import escape +from html import escape import re from docutils.core import publish_parts -- cgit v1.2.3 From c5e93f9f87c6d98be8db51fc3711ea83c881899a Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 17 Nov 2018 06:30:45 -0800 Subject: Remove mention of py3 compat --- docs/quick_tour/logging/setup.py | 2 +- docs/quick_tour/package/setup.py | 2 +- docs/quick_tour/sessions/setup.py | 2 +- docs/quick_tour/sqla_demo/setup.py | 2 +- docs/quick_tutorial/cookiecutters/setup.py | 2 +- docs/tutorials/wiki/src/authorization/setup.py | 2 +- docs/tutorials/wiki/src/basiclayout/setup.py | 2 +- docs/tutorials/wiki/src/installation/setup.py | 2 +- docs/tutorials/wiki/src/models/setup.py | 2 +- docs/tutorials/wiki/src/tests/setup.py | 2 +- docs/tutorials/wiki/src/views/setup.py | 2 +- docs/tutorials/wiki2/src/authentication/setup.py | 2 +- docs/tutorials/wiki2/src/authorization/setup.py | 2 +- docs/tutorials/wiki2/src/basiclayout/setup.py | 2 +- docs/tutorials/wiki2/src/installation/setup.py | 2 +- docs/tutorials/wiki2/src/models/setup.py | 2 +- docs/tutorials/wiki2/src/tests/setup.py | 2 +- docs/tutorials/wiki2/src/views/setup.py | 2 +- 18 files changed, 18 insertions(+), 18 deletions(-) diff --git a/docs/quick_tour/logging/setup.py b/docs/quick_tour/logging/setup.py index 27b025384..47f21a9dc 100644 --- a/docs/quick_tour/logging/setup.py +++ b/docs/quick_tour/logging/setup.py @@ -17,7 +17,7 @@ requires = [ ] tests_require = [ - 'WebTest >= 1.3.1', # py3 compat + 'WebTest >= 1.3.1', 'pytest>=3.7.4', 'pytest-cov', ] diff --git a/docs/quick_tour/package/setup.py b/docs/quick_tour/package/setup.py index 27b025384..47f21a9dc 100644 --- a/docs/quick_tour/package/setup.py +++ b/docs/quick_tour/package/setup.py @@ -17,7 +17,7 @@ requires = [ ] tests_require = [ - 'WebTest >= 1.3.1', # py3 compat + 'WebTest >= 1.3.1', 'pytest>=3.7.4', 'pytest-cov', ] diff --git a/docs/quick_tour/sessions/setup.py b/docs/quick_tour/sessions/setup.py index 27b025384..47f21a9dc 100644 --- a/docs/quick_tour/sessions/setup.py +++ b/docs/quick_tour/sessions/setup.py @@ -17,7 +17,7 @@ requires = [ ] tests_require = [ - 'WebTest >= 1.3.1', # py3 compat + 'WebTest >= 1.3.1', 'pytest>=3.7.4', 'pytest-cov', ] diff --git a/docs/quick_tour/sqla_demo/setup.py b/docs/quick_tour/sqla_demo/setup.py index 76cd518ca..71ebfab3a 100644 --- a/docs/quick_tour/sqla_demo/setup.py +++ b/docs/quick_tour/sqla_demo/setup.py @@ -23,7 +23,7 @@ requires = [ ] tests_require = [ - 'WebTest >= 1.3.1', # py3 compat + 'WebTest >= 1.3.1', 'pytest>=3.7.4', 'pytest-cov', ] diff --git a/docs/quick_tutorial/cookiecutters/setup.py b/docs/quick_tutorial/cookiecutters/setup.py index 9482e7c32..404e7d228 100644 --- a/docs/quick_tutorial/cookiecutters/setup.py +++ b/docs/quick_tutorial/cookiecutters/setup.py @@ -17,7 +17,7 @@ requires = [ ] tests_require = [ - 'WebTest >= 1.3.1', # py3 compat + 'WebTest >= 1.3.1', 'pytest>=3.7.4', 'pytest-cov', ] diff --git a/docs/tutorials/wiki/src/authorization/setup.py b/docs/tutorials/wiki/src/authorization/setup.py index 7011387f6..4a1224664 100644 --- a/docs/tutorials/wiki/src/authorization/setup.py +++ b/docs/tutorials/wiki/src/authorization/setup.py @@ -24,7 +24,7 @@ requires = [ ] tests_require = [ - 'WebTest >= 1.3.1', # py3 compat + 'WebTest >= 1.3.1', 'pytest>=3.7.4', 'pytest-cov', ] diff --git a/docs/tutorials/wiki/src/basiclayout/setup.py b/docs/tutorials/wiki/src/basiclayout/setup.py index e05e279e2..e266a546b 100644 --- a/docs/tutorials/wiki/src/basiclayout/setup.py +++ b/docs/tutorials/wiki/src/basiclayout/setup.py @@ -22,7 +22,7 @@ requires = [ ] tests_require = [ - 'WebTest >= 1.3.1', # py3 compat + 'WebTest >= 1.3.1', 'pytest>=3.7.4', 'pytest-cov', ] diff --git a/docs/tutorials/wiki/src/installation/setup.py b/docs/tutorials/wiki/src/installation/setup.py index e05e279e2..e266a546b 100644 --- a/docs/tutorials/wiki/src/installation/setup.py +++ b/docs/tutorials/wiki/src/installation/setup.py @@ -22,7 +22,7 @@ requires = [ ] tests_require = [ - 'WebTest >= 1.3.1', # py3 compat + 'WebTest >= 1.3.1', 'pytest>=3.7.4', 'pytest-cov', ] diff --git a/docs/tutorials/wiki/src/models/setup.py b/docs/tutorials/wiki/src/models/setup.py index e05e279e2..e266a546b 100644 --- a/docs/tutorials/wiki/src/models/setup.py +++ b/docs/tutorials/wiki/src/models/setup.py @@ -22,7 +22,7 @@ requires = [ ] tests_require = [ - 'WebTest >= 1.3.1', # py3 compat + 'WebTest >= 1.3.1', 'pytest>=3.7.4', 'pytest-cov', ] diff --git a/docs/tutorials/wiki/src/tests/setup.py b/docs/tutorials/wiki/src/tests/setup.py index 7011387f6..4a1224664 100644 --- a/docs/tutorials/wiki/src/tests/setup.py +++ b/docs/tutorials/wiki/src/tests/setup.py @@ -24,7 +24,7 @@ requires = [ ] tests_require = [ - 'WebTest >= 1.3.1', # py3 compat + 'WebTest >= 1.3.1', 'pytest>=3.7.4', 'pytest-cov', ] diff --git a/docs/tutorials/wiki/src/views/setup.py b/docs/tutorials/wiki/src/views/setup.py index a11ae6c8f..2d120224f 100644 --- a/docs/tutorials/wiki/src/views/setup.py +++ b/docs/tutorials/wiki/src/views/setup.py @@ -23,7 +23,7 @@ requires = [ ] tests_require = [ - 'WebTest >= 1.3.1', # py3 compat + 'WebTest >= 1.3.1', 'pytest>=3.7.4', 'pytest-cov', ] diff --git a/docs/tutorials/wiki2/src/authentication/setup.py b/docs/tutorials/wiki2/src/authentication/setup.py index e2a30c0e7..18dc1b617 100644 --- a/docs/tutorials/wiki2/src/authentication/setup.py +++ b/docs/tutorials/wiki2/src/authentication/setup.py @@ -25,7 +25,7 @@ requires = [ ] tests_require = [ - 'WebTest >= 1.3.1', # py3 compat + 'WebTest >= 1.3.1', 'pytest>=3.7.4', 'pytest-cov', ] diff --git a/docs/tutorials/wiki2/src/authorization/setup.py b/docs/tutorials/wiki2/src/authorization/setup.py index e2a30c0e7..18dc1b617 100644 --- a/docs/tutorials/wiki2/src/authorization/setup.py +++ b/docs/tutorials/wiki2/src/authorization/setup.py @@ -25,7 +25,7 @@ requires = [ ] tests_require = [ - 'WebTest >= 1.3.1', # py3 compat + 'WebTest >= 1.3.1', 'pytest>=3.7.4', 'pytest-cov', ] diff --git a/docs/tutorials/wiki2/src/basiclayout/setup.py b/docs/tutorials/wiki2/src/basiclayout/setup.py index 11725dd51..135e51ce8 100644 --- a/docs/tutorials/wiki2/src/basiclayout/setup.py +++ b/docs/tutorials/wiki2/src/basiclayout/setup.py @@ -23,7 +23,7 @@ requires = [ ] tests_require = [ - 'WebTest >= 1.3.1', # py3 compat + 'WebTest >= 1.3.1', 'pytest>=3.7.4', 'pytest-cov', ] diff --git a/docs/tutorials/wiki2/src/installation/setup.py b/docs/tutorials/wiki2/src/installation/setup.py index 11725dd51..135e51ce8 100644 --- a/docs/tutorials/wiki2/src/installation/setup.py +++ b/docs/tutorials/wiki2/src/installation/setup.py @@ -23,7 +23,7 @@ requires = [ ] tests_require = [ - 'WebTest >= 1.3.1', # py3 compat + 'WebTest >= 1.3.1', 'pytest>=3.7.4', 'pytest-cov', ] diff --git a/docs/tutorials/wiki2/src/models/setup.py b/docs/tutorials/wiki2/src/models/setup.py index 09e3126ea..556e1837d 100644 --- a/docs/tutorials/wiki2/src/models/setup.py +++ b/docs/tutorials/wiki2/src/models/setup.py @@ -24,7 +24,7 @@ requires = [ ] tests_require = [ - 'WebTest >= 1.3.1', # py3 compat + 'WebTest >= 1.3.1', 'pytest>=3.7.4', 'pytest-cov', ] diff --git a/docs/tutorials/wiki2/src/tests/setup.py b/docs/tutorials/wiki2/src/tests/setup.py index e2a30c0e7..18dc1b617 100644 --- a/docs/tutorials/wiki2/src/tests/setup.py +++ b/docs/tutorials/wiki2/src/tests/setup.py @@ -25,7 +25,7 @@ requires = [ ] tests_require = [ - 'WebTest >= 1.3.1', # py3 compat + 'WebTest >= 1.3.1', 'pytest>=3.7.4', 'pytest-cov', ] diff --git a/docs/tutorials/wiki2/src/views/setup.py b/docs/tutorials/wiki2/src/views/setup.py index e2a30c0e7..18dc1b617 100644 --- a/docs/tutorials/wiki2/src/views/setup.py +++ b/docs/tutorials/wiki2/src/views/setup.py @@ -25,7 +25,7 @@ requires = [ ] tests_require = [ - 'WebTest >= 1.3.1', # py3 compat + 'WebTest >= 1.3.1', 'pytest>=3.7.4', 'pytest-cov', ] -- cgit v1.2.3 From ae9453732bcbd4a9812e6cd9232bba6d469a9953 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 17 Nov 2018 15:49:12 -0800 Subject: Remove explicit mention of Python 3 --- docs/quick_tutorial/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quick_tutorial/index.rst b/docs/quick_tutorial/index.rst index 44a172067..d727502e9 100644 --- a/docs/quick_tutorial/index.rst +++ b/docs/quick_tutorial/index.rst @@ -4,7 +4,7 @@ Quick Tutorial for Pyramid ========================== -Pyramid is a web framework for Python 3. This tutorial gives a Python 3-compatible, high-level tour of the major features. +This tutorial gives a high-level tour of the major features. This hands-on tutorial covers "a little about a lot": practical introductions to the most common facilities. Fun, fast-paced, and most certainly not aimed at -- cgit v1.2.3 From 54fba396ce913c9731f920447d680f1480d8517e Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 18 Nov 2018 05:15:42 -0800 Subject: Unpin webtest and pytest --- docs/quick_tour/logging/setup.py | 4 ++-- docs/quick_tour/package/setup.py | 4 ++-- docs/quick_tour/sessions/setup.py | 4 ++-- docs/quick_tour/sqla_demo/setup.py | 4 ++-- docs/quick_tutorial/cookiecutters/setup.py | 4 ++-- docs/tutorials/wiki/src/authorization/setup.py | 4 ++-- docs/tutorials/wiki/src/basiclayout/setup.py | 4 ++-- docs/tutorials/wiki/src/installation/setup.py | 4 ++-- docs/tutorials/wiki/src/models/setup.py | 4 ++-- docs/tutorials/wiki/src/tests/setup.py | 4 ++-- docs/tutorials/wiki/src/views/setup.py | 4 ++-- docs/tutorials/wiki2/src/authentication/setup.py | 4 ++-- docs/tutorials/wiki2/src/authorization/setup.py | 4 ++-- docs/tutorials/wiki2/src/basiclayout/setup.py | 4 ++-- docs/tutorials/wiki2/src/installation/setup.py | 4 ++-- docs/tutorials/wiki2/src/models/setup.py | 4 ++-- docs/tutorials/wiki2/src/tests/setup.py | 4 ++-- docs/tutorials/wiki2/src/views/setup.py | 4 ++-- 18 files changed, 36 insertions(+), 36 deletions(-) diff --git a/docs/quick_tour/logging/setup.py b/docs/quick_tour/logging/setup.py index 47f21a9dc..e9c15db04 100644 --- a/docs/quick_tour/logging/setup.py +++ b/docs/quick_tour/logging/setup.py @@ -17,8 +17,8 @@ requires = [ ] tests_require = [ - 'WebTest >= 1.3.1', - 'pytest>=3.7.4', + 'WebTest', + 'pytest', 'pytest-cov', ] diff --git a/docs/quick_tour/package/setup.py b/docs/quick_tour/package/setup.py index 47f21a9dc..e9c15db04 100644 --- a/docs/quick_tour/package/setup.py +++ b/docs/quick_tour/package/setup.py @@ -17,8 +17,8 @@ requires = [ ] tests_require = [ - 'WebTest >= 1.3.1', - 'pytest>=3.7.4', + 'WebTest', + 'pytest', 'pytest-cov', ] diff --git a/docs/quick_tour/sessions/setup.py b/docs/quick_tour/sessions/setup.py index 47f21a9dc..e9c15db04 100644 --- a/docs/quick_tour/sessions/setup.py +++ b/docs/quick_tour/sessions/setup.py @@ -17,8 +17,8 @@ requires = [ ] tests_require = [ - 'WebTest >= 1.3.1', - 'pytest>=3.7.4', + 'WebTest', + 'pytest', 'pytest-cov', ] diff --git a/docs/quick_tour/sqla_demo/setup.py b/docs/quick_tour/sqla_demo/setup.py index 71ebfab3a..28a8e0815 100644 --- a/docs/quick_tour/sqla_demo/setup.py +++ b/docs/quick_tour/sqla_demo/setup.py @@ -23,8 +23,8 @@ requires = [ ] tests_require = [ - 'WebTest >= 1.3.1', - 'pytest>=3.7.4', + 'WebTest', + 'pytest', 'pytest-cov', ] diff --git a/docs/quick_tutorial/cookiecutters/setup.py b/docs/quick_tutorial/cookiecutters/setup.py index 404e7d228..d5d3d018b 100644 --- a/docs/quick_tutorial/cookiecutters/setup.py +++ b/docs/quick_tutorial/cookiecutters/setup.py @@ -17,8 +17,8 @@ requires = [ ] tests_require = [ - 'WebTest >= 1.3.1', - 'pytest>=3.7.4', + 'WebTest', + 'pytest', 'pytest-cov', ] diff --git a/docs/tutorials/wiki/src/authorization/setup.py b/docs/tutorials/wiki/src/authorization/setup.py index 4a1224664..7b405745e 100644 --- a/docs/tutorials/wiki/src/authorization/setup.py +++ b/docs/tutorials/wiki/src/authorization/setup.py @@ -24,8 +24,8 @@ requires = [ ] tests_require = [ - 'WebTest >= 1.3.1', - 'pytest>=3.7.4', + 'WebTest', + 'pytest', 'pytest-cov', ] diff --git a/docs/tutorials/wiki/src/basiclayout/setup.py b/docs/tutorials/wiki/src/basiclayout/setup.py index e266a546b..a4f143d24 100644 --- a/docs/tutorials/wiki/src/basiclayout/setup.py +++ b/docs/tutorials/wiki/src/basiclayout/setup.py @@ -22,8 +22,8 @@ requires = [ ] tests_require = [ - 'WebTest >= 1.3.1', - 'pytest>=3.7.4', + 'WebTest', + 'pytest', 'pytest-cov', ] diff --git a/docs/tutorials/wiki/src/installation/setup.py b/docs/tutorials/wiki/src/installation/setup.py index e266a546b..a4f143d24 100644 --- a/docs/tutorials/wiki/src/installation/setup.py +++ b/docs/tutorials/wiki/src/installation/setup.py @@ -22,8 +22,8 @@ requires = [ ] tests_require = [ - 'WebTest >= 1.3.1', - 'pytest>=3.7.4', + 'WebTest', + 'pytest', 'pytest-cov', ] diff --git a/docs/tutorials/wiki/src/models/setup.py b/docs/tutorials/wiki/src/models/setup.py index e266a546b..a4f143d24 100644 --- a/docs/tutorials/wiki/src/models/setup.py +++ b/docs/tutorials/wiki/src/models/setup.py @@ -22,8 +22,8 @@ requires = [ ] tests_require = [ - 'WebTest >= 1.3.1', - 'pytest>=3.7.4', + 'WebTest', + 'pytest', 'pytest-cov', ] diff --git a/docs/tutorials/wiki/src/tests/setup.py b/docs/tutorials/wiki/src/tests/setup.py index 4a1224664..7b405745e 100644 --- a/docs/tutorials/wiki/src/tests/setup.py +++ b/docs/tutorials/wiki/src/tests/setup.py @@ -24,8 +24,8 @@ requires = [ ] tests_require = [ - 'WebTest >= 1.3.1', - 'pytest>=3.7.4', + 'WebTest', + 'pytest', 'pytest-cov', ] diff --git a/docs/tutorials/wiki/src/views/setup.py b/docs/tutorials/wiki/src/views/setup.py index 2d120224f..3c19db7b9 100644 --- a/docs/tutorials/wiki/src/views/setup.py +++ b/docs/tutorials/wiki/src/views/setup.py @@ -23,8 +23,8 @@ requires = [ ] tests_require = [ - 'WebTest >= 1.3.1', - 'pytest>=3.7.4', + 'WebTest', + 'pytest', 'pytest-cov', ] diff --git a/docs/tutorials/wiki2/src/authentication/setup.py b/docs/tutorials/wiki2/src/authentication/setup.py index 18dc1b617..f71998afc 100644 --- a/docs/tutorials/wiki2/src/authentication/setup.py +++ b/docs/tutorials/wiki2/src/authentication/setup.py @@ -25,8 +25,8 @@ requires = [ ] tests_require = [ - 'WebTest >= 1.3.1', - 'pytest>=3.7.4', + 'WebTest', + 'pytest', 'pytest-cov', ] diff --git a/docs/tutorials/wiki2/src/authorization/setup.py b/docs/tutorials/wiki2/src/authorization/setup.py index 18dc1b617..f71998afc 100644 --- a/docs/tutorials/wiki2/src/authorization/setup.py +++ b/docs/tutorials/wiki2/src/authorization/setup.py @@ -25,8 +25,8 @@ requires = [ ] tests_require = [ - 'WebTest >= 1.3.1', - 'pytest>=3.7.4', + 'WebTest', + 'pytest', 'pytest-cov', ] diff --git a/docs/tutorials/wiki2/src/basiclayout/setup.py b/docs/tutorials/wiki2/src/basiclayout/setup.py index 135e51ce8..746012a74 100644 --- a/docs/tutorials/wiki2/src/basiclayout/setup.py +++ b/docs/tutorials/wiki2/src/basiclayout/setup.py @@ -23,8 +23,8 @@ requires = [ ] tests_require = [ - 'WebTest >= 1.3.1', - 'pytest>=3.7.4', + 'WebTest', + 'pytest', 'pytest-cov', ] diff --git a/docs/tutorials/wiki2/src/installation/setup.py b/docs/tutorials/wiki2/src/installation/setup.py index 135e51ce8..746012a74 100644 --- a/docs/tutorials/wiki2/src/installation/setup.py +++ b/docs/tutorials/wiki2/src/installation/setup.py @@ -23,8 +23,8 @@ requires = [ ] tests_require = [ - 'WebTest >= 1.3.1', - 'pytest>=3.7.4', + 'WebTest', + 'pytest', 'pytest-cov', ] diff --git a/docs/tutorials/wiki2/src/models/setup.py b/docs/tutorials/wiki2/src/models/setup.py index 556e1837d..b9dc9d93f 100644 --- a/docs/tutorials/wiki2/src/models/setup.py +++ b/docs/tutorials/wiki2/src/models/setup.py @@ -24,8 +24,8 @@ requires = [ ] tests_require = [ - 'WebTest >= 1.3.1', - 'pytest>=3.7.4', + 'WebTest', + 'pytest', 'pytest-cov', ] diff --git a/docs/tutorials/wiki2/src/tests/setup.py b/docs/tutorials/wiki2/src/tests/setup.py index 18dc1b617..f71998afc 100644 --- a/docs/tutorials/wiki2/src/tests/setup.py +++ b/docs/tutorials/wiki2/src/tests/setup.py @@ -25,8 +25,8 @@ requires = [ ] tests_require = [ - 'WebTest >= 1.3.1', - 'pytest>=3.7.4', + 'WebTest', + 'pytest', 'pytest-cov', ] diff --git a/docs/tutorials/wiki2/src/views/setup.py b/docs/tutorials/wiki2/src/views/setup.py index 18dc1b617..f71998afc 100644 --- a/docs/tutorials/wiki2/src/views/setup.py +++ b/docs/tutorials/wiki2/src/views/setup.py @@ -25,8 +25,8 @@ requires = [ ] tests_require = [ - 'WebTest >= 1.3.1', - 'pytest>=3.7.4', + 'WebTest', + 'pytest', 'pytest-cov', ] -- cgit v1.2.3 From a05a4663f57b9acac876359b1d37b20514457550 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 18 Nov 2018 05:20:33 -0800 Subject: Add comments to setup.py docs_extras --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index b63f4c182..3176442e1 100644 --- a/setup.py +++ b/setup.py @@ -44,9 +44,9 @@ tests_require = [ docs_extras = [ - 'Sphinx >= 1.8.1', + 'Sphinx >= 1.8.1', # Unicode characters in tree diagrams 'docutils', - 'pylons-sphinx-themes >= 1.0.8', + 'pylons-sphinx-themes >= 1.0.8', # Ethical Ads 'pylons_sphinx_latesturl', 'repoze.sphinx.autointerface', 'sphinxcontrib-autoprogram', -- cgit v1.2.3 From fe1740acba6a9bfe5ac3aed41a9a264a9fd30814 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 18 Nov 2018 05:22:54 -0800 Subject: Remove `docs/api/compat.rst`. The remaining items were moved into `pyramid.util`, but we don't want to document anything in that module. --- docs/api/compat.rst | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 docs/api/compat.rst diff --git a/docs/api/compat.rst b/docs/api/compat.rst deleted file mode 100644 index d1552e644..000000000 --- a/docs/api/compat.rst +++ /dev/null @@ -1,25 +0,0 @@ -.. _compat_module: - -:mod:`pyramid.compat` ----------------------- - -The ``pyramid.compat`` module provides platform and version compatibility for -Pyramid and its add-ons across Python platform and version differences. APIs -will be removed from this module over time as Pyramid ceases to support -systems which require compatibility imports. - -.. automodule:: pyramid.compat - - .. autofunction:: ascii_ - - .. autofunction:: bytes_ - - .. attribute:: im_func - - The string value ``__func__``. - - .. attribute:: PYPY - - ``True`` if running on PyPy, ``False`` otherwise. - - .. autofunction:: text_ -- cgit v1.2.3 From 82c2307d738be3b31aa05814e6b3fcbb97f41d18 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 18 Nov 2018 05:29:39 -0800 Subject: Remove Python 2 and explicit mention of Python 3 --- HACKING.txt | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/HACKING.txt b/HACKING.txt index acd65e4fd..901eb8518 100644 --- a/HACKING.txt +++ b/HACKING.txt @@ -34,10 +34,10 @@ In order to add a feature to Pyramid: - The feature must be documented in both the API and narrative documentation (in `docs/`). -- The feature must work fully on the following CPython versions: 2.7, 3.4, 3.5, - 3.6, and 3.7 on both UNIX and Windows. +- The feature must work fully on the following CPython versions: 3.4, 3.5, 3.6, + and 3.7 on both UNIX and Windows. -- The feature must work on the latest version of PyPy. +- The feature must work on the latest version of PyPy3. - The feature must not depend on any particular persistence layer (filesystem, SQL, etc). @@ -65,10 +65,9 @@ Running Tests $ tox -e py37 - This command will run tests on the latest versions of Python 2 and 3 with - coverage totaled for both versions. + This command will run tests on the latest version of Python 3 with coverage. - $ tox -e py2-cover,py3-cover,coverage + $ tox -e py3-cover,coverage - To run individual tests (i.e., during development), you can use `nosetests` syntax as follows, where `$VENV` is an environment variable set to the path @@ -105,7 +104,7 @@ Test Coverage ------------- - The codebase *must* have 100% test statement coverage after each commit. You - can test coverage via `tox -epy2-cover,py3-cover,coverage`. + can test coverage via `tox -epy3-cover,coverage`. Documentation Coverage and Building HTML Documentation -- cgit v1.2.3 From 847d1835a5b76da4925ee3248e69148cc4235df9 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 18 Nov 2018 05:32:29 -0800 Subject: Remove scaffolds testing instructions --- RELEASING.txt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/RELEASING.txt b/RELEASING.txt index 94726f9f3..73e79d057 100644 --- a/RELEASING.txt +++ b/RELEASING.txt @@ -33,11 +33,6 @@ Prepare new release branch - Run tests on Windows if feasible. -- Make sure all scaffold tests pass (CPython 2.7, 3.4, 3.5, 3.6, and 3.7, and - PyPy on UNIX; this doesn't work on Windows): - - $ tox -e{py27,py34,py35,py36,py37,pypy}-scaffolds - - For each ``pyramid-cookiecutter-*``, make a new branch off "master" with the same name to align with the new Pyramid release branch name. -- cgit v1.2.3 From 80cce1874f90a73f74d7c9ef8156df3637694387 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sun, 18 Nov 2018 17:24:54 -0600 Subject: clean up docstrings referencing unicode data --- src/pyramid/encode.py | 4 +- src/pyramid/i18n.py | 15 +++--- src/pyramid/response.py | 4 -- src/pyramid/security.py | 4 +- src/pyramid/traversal.py | 132 ++++++++++++++++++++--------------------------- 5 files changed, 68 insertions(+), 91 deletions(-) diff --git a/src/pyramid/encode.py b/src/pyramid/encode.py index 11d3a7787..ed8e177b8 100644 --- a/src/pyramid/encode.py +++ b/src/pyramid/encode.py @@ -26,8 +26,8 @@ def quote_plus(val, safe=''): def urlencode(query, doseq=True, quote_via=quote_plus): """ An alternate implementation of Python's stdlib - :func:`urllib.parse.urlencode` function which accepts unicode keys and - values within the ``query`` dict/sequence; all Unicode keys and values are + :func:`urllib.parse.urlencode` function which accepts string keys and + values within the ``query`` dict/sequence; all string keys and values are first converted to UTF-8 before being used to compose the query string. The value of ``query`` must be a sequence of two-tuples diff --git a/src/pyramid/i18n.py b/src/pyramid/i18n.py index 45f528852..112044e47 100644 --- a/src/pyramid/i18n.py +++ b/src/pyramid/i18n.py @@ -45,12 +45,11 @@ class Localizer(object): ``translate`` method accepts three arguments: ``tstring`` (required), ``domain`` (optional) and ``mapping`` (optional). When called, it will translate the ``tstring`` translation - string to a ``unicode`` object using the current locale. If - the current locale could not be determined, the result of - interpolation of the default value is returned. The optional - ``domain`` argument can be used to specify or override the - domain of the ``tstring`` (useful when ``tstring`` is a normal - string rather than a translation string). The optional + string the current locale. If the current locale could not be + determined, the result of interpolation of the default value is + returned. The optional ``domain`` argument can be used to specify + or override the domain of the ``tstring`` (useful when ``tstring`` + is a normal string rather than a translation string). The optional ``mapping`` argument can specify or override the ``tstring`` interpolation mapping, useful when the ``tstring`` argument is a simple string instead of a translation string. @@ -74,11 +73,11 @@ class Localizer(object): def pluralize(self, singular, plural, n, domain=None, mapping=None): """ - Return a Unicode string translation by using two + Return a string translation by using two :term:`message identifier` objects as a singular/plural pair and an ``n`` value representing the number that appears in the message using gettext plural forms support. The ``singular`` - and ``plural`` objects should be unicode strings. There is no + and ``plural`` objects should be strings. There is no reason to use translation string objects as arguments as all metadata is ignored. diff --git a/src/pyramid/response.py b/src/pyramid/response.py index 8a2ba8929..ea4677226 100644 --- a/src/pyramid/response.py +++ b/src/pyramid/response.py @@ -212,8 +212,4 @@ def _guess_type(path): content_type, content_encoding = mimetypes.guess_type(path, strict=False) if content_type is None: content_type = 'application/octet-stream' - # str-ifying content_type is a workaround for a bug in Python 2.7.7 - # on Windows where mimetypes.guess_type returns unicode for the - # content_type. - content_type = str(content_type) return content_type, content_encoding diff --git a/src/pyramid/security.py b/src/pyramid/security.py index f8743e9a7..61819588b 100644 --- a/src/pyramid/security.py +++ b/src/pyramid/security.py @@ -112,7 +112,7 @@ def forget(request): def principals_allowed_by_permission(context, permission): """ Provided a ``context`` (a resource object), and a ``permission`` - (a string or unicode object), if an :term:`authorization policy` is + string, if an :term:`authorization policy` is in effect, return a sequence of :term:`principal` ids that possess the permission in the ``context``. If no authorization policy is in effect, this will return a sequence with the single value @@ -340,7 +340,7 @@ class AuthorizationAPIMixin(object): ``request.context`` attribute. :param permission: Does this request have the given permission? - :type permission: unicode, str + :type permission: str :param context: A resource object or ``None`` :type context: object :returns: Either :class:`pyramid.security.Allowed` or diff --git a/src/pyramid/traversal.py b/src/pyramid/traversal.py index 7de4c3f91..72176fc4b 100644 --- a/src/pyramid/traversal.py +++ b/src/pyramid/traversal.py @@ -57,16 +57,12 @@ def find_resource(resource, path): object supplied to the function as the ``resource`` argument. If an empty string is passed as ``path``, the ``resource`` passed in will be returned. Resource path strings must be escaped in the following - manner: each Unicode path segment must be encoded as UTF-8 and as - each path segment must escaped via Python's :mod:`urllib.quote`. - For example, ``/path/to%20the/La%20Pe%C3%B1a`` (absolute) or - ``to%20the/La%20Pe%C3%B1a`` (relative). The + manner: each path segment must be UTF-8 encoded and escaped via Python's + :mod:`urllib.quote`. For example, ``/path/to%20the/La%20Pe%C3%B1a`` + (absolute) or ``to%20the/La%20Pe%C3%B1a`` (relative). The :func:`pyramid.traversal.resource_path` function generates strings - which follow these rules (albeit only absolute ones). - - Rules for passing *text* (Unicode) as the ``path`` argument are the same - as those for a string. In particular, the text may not have any nonascii - characters in it. + which follow these rules (albeit only absolute ones). The text may not + have any nonascii characters in it. Rules for passing a *tuple* as the ``path`` argument: if the first element in the path tuple is the empty string (for example ``('', @@ -77,11 +73,10 @@ def find_resource(resource, path): traversal will begin at the resource object supplied to the function as the ``resource`` argument. If an empty sequence is passed as ``path``, the ``resource`` passed in itself will be returned. No - URL-quoting or UTF-8-encoding of individual path segments within - the tuple is required (each segment may be any string or unicode - object representing a resource name). Resource path tuples generated by - :func:`pyramid.traversal.resource_path_tuple` can always be - resolved by ``find_resource``. + URL-quoting of individual path segments within the tuple is required + (each segment may be any string representing a resource name). Resource + path tuples generated by :func:`pyramid.traversal.resource_path_tuple` can + always be resolved by ``find_resource``. """ if isinstance(path, str): path = ascii_(path) @@ -126,8 +121,8 @@ def resource_path(resource, *elements): ``/`` character in a path string represents that the path is absolute). Resource path strings returned will be escaped in the following - manner: each unicode path segment will be encoded as UTF-8 and - each path segment will be escaped via Python's :mod:`urllib.quote`. + manner: each path segment will be encoded as UTF-8 and escaped via + Python's :mod:`urllib.quote`. For example, ``/path/to%20the/La%20Pe%C3%B1a``. This function is a logical inverse of @@ -140,7 +135,7 @@ def resource_path(resource, *elements): Each segment in the path string returned will use the ``__name__`` attribute of the resource it represents within the resource tree. Each - of these segments *should* be a unicode or string object (as per the + of these segments *should* be a string (as per the contract of :term:`location`-awareness). However, no conversion or safety checking of resource names is performed. For instance, if one of the resources in your tree has a ``__name__`` which (by error) is a @@ -189,30 +184,30 @@ def traverse(resource, path): :term:`traversal` or :term:`url dispatch`; if the ``resource`` was found via traversal, this is usually a representation of the path segment which directly follows the path to the ``context`` - in the ``path``. The ``view_name`` will be a Unicode object or - the empty string. The ``view_name`` will be the empty string if + in the ``path``. The ``view_name`` will be a string. The + ``view_name`` will be the empty string if there is no element which follows the ``context`` path. An example: if the path passed is ``/foo/bar``, and a resource object is found at ``/foo`` (but not at ``/foo/bar``), the 'view - name' will be ``u'bar'``. If the ``resource`` was found via + name' will be ``'bar'``. If the ``resource`` was found via urldispatch, the view_name will be the name the route found was registered with. - ``subpath``: For a ``resource`` found via :term:`traversal`, this is a sequence of path segments found in the ``path`` that follow - the ``view_name`` (if any). Each of these items is a Unicode - object. If no path segments follow the ``view_name``, the + the ``view_name`` (if any). Each of these items is a string. + If no path segments follow the ``view_name``, the subpath will be the empty sequence. An example: if the path passed is ``/foo/bar/baz/buz``, and a resource object is found at ``/foo`` (but not ``/foo/bar``), the 'view name' will be - ``u'bar'`` and the :term:`subpath` will be ``[u'baz', u'buz']``. + ``'bar'`` and the :term:`subpath` will be ``['baz', 'buz']``. For a ``resource`` found via url dispatch, the subpath will be a sequence of values discerned from ``*subpath`` in the route pattern matched or the empty sequence. - ``traversed``: The sequence of path elements traversed from the root to find the ``context`` object during :term:`traversal`. - Each of these items is a Unicode object. If no path segments + Each of these items is a string. If no path segments were traversed to find the ``context`` object (e.g. if the ``path`` provided is the empty string), the ``traversed`` value will be the empty sequence. If the ``resource`` is a resource found @@ -232,7 +227,7 @@ def traverse(resource, path): - ``virtual_root_path`` -- If :term:`traversal` was used to find the ``resource``, this will be the sequence of path elements traversed to find the ``virtual_root`` resource. Each of these - items is a Unicode object. If no path segments were traversed + items is a string. If no path segments were traversed to find the ``virtual_root`` resource (e.g. if virtual hosting is not in effect), the ``traversed`` value will be the empty list. If url dispatch was used to find the ``resource``, this will be @@ -249,9 +244,9 @@ def traverse(resource, path): object supplied to the function as the ``resource`` argument. If an empty string is passed as ``path``, the ``resource`` passed in will be returned. Resource path strings must be escaped in the following - manner: each Unicode path segment must be encoded as UTF-8 and - each path segment must escaped via Python's :mod:`urllib.quote`. - For example, ``/path/to%20the/La%20Pe%C3%B1a`` (absolute) or + manner: each path segment must be encoded as UTF-8 and escaped via + Python's :mod:`urllib.quote`. For example, + ``/path/to%20the/La%20Pe%C3%B1a`` (absolute) or ``to%20the/La%20Pe%C3%B1a`` (relative). The :func:`pyramid.traversal.resource_path` function generates strings which follow these rules (albeit only absolute ones). @@ -266,26 +261,24 @@ def traverse(resource, path): as the ``resource`` argument. If an empty sequence is passed as ``path``, the ``resource`` passed in itself will be returned. No URL-quoting or UTF-8-encoding of individual path segments within - the tuple is required (each segment may be any string or unicode - object representing a resource name). + the tuple is required (each segment may be any string representing + a resource name). - Explanation of the conversion of ``path`` segment values to - Unicode during traversal: Each segment is URL-unquoted, and - decoded into Unicode. Each segment is assumed to be encoded using - the UTF-8 encoding (or a subset, such as ASCII); a + Explanation of the decoding of ``path`` segment values during traversal: + Each segment is URL-unquoted, and UTF-8 decoded. Each segment is assumed + to be encoded using the UTF-8 encoding (or a subset, such as ASCII); a :exc:`pyramid.exceptions.URLDecodeError` is raised if a segment cannot be decoded. If a segment name is empty or if it is ``.``, it is ignored. If a segment name is ``..``, the previous segment is deleted, and the ``..`` is ignored. As a result of this process, the return values ``view_name``, each element in the ``subpath``, each element in ``traversed``, and each element in - the ``virtual_root_path`` will be Unicode as opposed to a string, - and will be URL-decoded. + the ``virtual_root_path`` will be decoded strings. """ if is_nonstr_iter(path): - # the traverser factory expects PATH_INFO to be a string, not - # unicode and it expects path segments to be utf-8 and + # the traverser factory expects PATH_INFO to be a string and it + # expects path segments to be utf-8 and # urlencoded (it's the same traverser which accepts PATH_INFO # from user agents; user agents always send strings). if path: @@ -346,7 +339,7 @@ def resource_path_tuple(resource, *elements): Each segment in the path tuple returned will equal the ``__name__`` attribute of the resource it represents within the resource tree. Each - of these segments *should* be a unicode or string object (as per the + of these segments *should* be a string (as per the contract of :term:`location`-awareness). However, no conversion or safety checking of resource names is performed. For instance, if one of the resources in your tree has a ``__name__`` which (by error) is a @@ -428,11 +421,10 @@ def traversal_path(path): """ Variant of :func:`pyramid.traversal.traversal_path_info` suitable for decoding paths that are URL-encoded. - If this function is passed a Unicode object instead of a sequence of - bytes as ``path``, that Unicode object *must* directly encodeable to - ASCII. For example, u'/foo' will work but u'/' (a - Unicode object with characters that cannot be encoded to ascii) will - not. A :exc:`UnicodeEncodeError` will be raised if the Unicode cannot be + If this function is passed a string, it *must* directly encodeable to + ASCII. For example, '/foo' will work but '/' (a + string object with characters that cannot be encoded to ascii) will + not. A :exc:`UnicodeEncodeError` will be raised if the string cannot be encoded directly to ASCII. """ if isinstance(path, str): @@ -450,18 +442,11 @@ def traversal_path_info(path): already-URL-decoded ``str`` type as if it had come to us from an upstream WSGI server as the ``PATH_INFO`` environ variable. - The ``path`` is first decoded to from its WSGI representation to Unicode; - it is decoded differently depending on platform: - - - On Python 2, ``path`` is decoded to Unicode from bytes using the UTF-8 - decoding directly; a :exc:`pyramid.exc.URLDecodeError` is raised if a the - URL cannot be decoded. - - - On Python 3, as per the PEP 3333 spec, ``path`` is first encoded to - bytes using the Latin-1 encoding; the resulting set of bytes is - subsequently decoded to text using the UTF-8 encoding; a - :exc:`pyramid.exc.URLDecodeError` is raised if a the URL cannot be - decoded. + The ``path`` is first decoded to from its WSGI representation to text. + Per the PEP 3333 spec, ``path`` is first encoded to bytes using the + Latin-1 encoding; the resulting set of bytes is subsequently decoded to + text using the UTF-8 encoding; a :exc:`pyramid.exc.URLDecodeError` is + raised if a the URL cannot be decoded. The ``path`` is split on slashes, creating a list of segments. If a segment name is empty or if it is ``.``, it is ignored. If a segment @@ -476,31 +461,31 @@ def traversal_path_info(path): ``/foo/bar/baz`` - (u'foo', u'bar', u'baz') + ('foo', 'bar', 'baz') ``foo/bar/baz`` - (u'foo', u'bar', u'baz') + ('foo', 'bar', 'baz') ``/foo/bar/baz/`` - (u'foo', u'bar', u'baz') + ('foo', 'bar', 'baz') ``/foo//bar//baz/`` - (u'foo', u'bar', u'baz') + ('foo', 'bar', 'baz') ``/foo/bar/baz/..`` - (u'foo', u'bar') + ('foo', 'bar') ``/my%20archives/hello`` - (u'my archives', u'hello') + ('my archives', 'hello') ``/archives/La%20Pe%C3%B1a`` - (u'archives', u'') + ('archives', '') .. note:: @@ -555,13 +540,10 @@ def quote_path_segment(segment, safe=PATH_SEGMENT_SAFE): """ Return a quoted representation of a 'path segment' (such as the string ``__name__`` attribute of a resource) as a string. If the - ``segment`` passed in is a unicode object, it is converted to a - UTF-8 string, then it is URL-quoted using Python's - ``urllib.quote``. If the ``segment`` passed in is a string, it is - URL-quoted using Python's :mod:`urllib.quote`. If the segment - passed in is not a string or unicode object, an error will be - raised. The return value of ``quote_path_segment`` is always a - string, never Unicode. + ``segment`` passed in is a bytes object, it is decoded as a UTF-8 string. + The result is then URL-quoted using Python's ``urllib.quote``. + If the segment passed in is not bytes or a string, an error will be + raised. The return value of ``quote_path_segment`` is always a string. You may pass a string of characters that need not be encoded as the ``safe`` argument to this function. This corresponds to the @@ -582,14 +564,14 @@ def quote_path_segment(segment, safe=PATH_SEGMENT_SAFE): """ # The bit of this code that deals with ``_segment_cache`` is an # optimization: we cache all the computation of URL path segments - # in this module-scope dictionary with the original string (or - # unicode value) as the key, so we can look it up later without - # needing to reencode or re-url-quote it + # in this module-scope dictionary with the original string as the + # key, so we can look it up later without needing to reencode + # or re-url-quote it try: - return _segment_cache[(segment, safe)] - except KeyError: if segment.__class__ not in (str, bytes): segment = str(segment) + return _segment_cache[(segment, safe)] + except KeyError: result = url_quote(text_(segment, 'utf-8'), safe) # we don't need a lock to mutate _segment_cache, as the below # will generate exactly one Python bytecode (STORE_SUBSCR) -- cgit v1.2.3 From 8f2fbe31c6c1aaa1b7792249ebeb984946e7a5e1 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sun, 18 Nov 2018 23:29:18 -0600 Subject: stop using u-prefix strings --- src/pyramid/util.py | 8 ++++---- tests/test_integration.py | 4 ++-- tests/test_traversal.py | 12 ++++++------ 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/pyramid/util.py b/src/pyramid/util.py index 1180fce83..cad8142dd 100644 --- a/src/pyramid/util.py +++ b/src/pyramid/util.py @@ -338,14 +338,14 @@ def object_description(object): usually involving a Python dotted name. For example: >>> object_description(None) - u'None' + 'None' >>> from xml.dom import minidom >>> object_description(minidom) - u'module xml.dom.minidom' + 'module xml.dom.minidom' >>> object_description(minidom.Attr) - u'class xml.dom.minidom.Attr' + 'class xml.dom.minidom.Attr' >>> object_description(minidom.Attr.appendChild) - u'method appendChild of class xml.dom.minidom.Attr' + 'method appendChild of class xml.dom.minidom.Attr' If this method cannot identify the type of the object, a generic description ala ``object `` will be returned. diff --git a/tests/test_integration.py b/tests/test_integration.py index d1f65274b..e6dccbb5b 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -726,8 +726,8 @@ class UnicodeInURLTest(unittest.TestCase): res = testapp.get(request_path, status=404) # Pyramid default 404 handler outputs: - # u'404 Not Found\n\nThe resource could not be found.\n\n\n' - # u'/avalia\xe7\xe3o_participante\n\n' + # '404 Not Found\n\nThe resource could not be found.\n\n\n' + # '/avalia\xe7\xe3o_participante\n\n' self.assertTrue(request_path_unicode in res.text) def test_unicode_in_url_200(self): diff --git a/tests/test_traversal.py b/tests/test_traversal.py index de712a6e8..188ee803c 100644 --- a/tests/test_traversal.py +++ b/tests/test_traversal.py @@ -677,7 +677,7 @@ class FindResourceTests(unittest.TestCase): def test_absolute_unicode_found(self): # test for bug wiggy found in wild, traceback stack: - # root = u'/%E6%B5%81%E8%A1%8C%E8%B6%8B%E5%8A%BF' + # root = '/%E6%B5%81%E8%A1%8C%E8%B6%8B%E5%8A%BF' # wiggy's code: section=find_resource(page, root) # find_resource L76: D = traverse(resource, path) # traverse L291: return traverser(request) @@ -1214,18 +1214,18 @@ class Test__join_path_tuple(unittest.TestCase): def test_segments_with_unsafes(self): safe_segments = tuple( - u"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" - u"-._~!$&'()*+,;=:@" + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "-._~!$&'()*+,;=:@" ) result = self._callFUT(safe_segments) - self.assertEqual(result, u'/'.join(safe_segments)) + self.assertEqual(result, '/'.join(safe_segments)) unsafe_segments = tuple( chr(i) for i in range(0x20, 0x80) if not chr(i) in safe_segments - ) + (u'あ',) + ) + ('あ',) result = self._callFUT(unsafe_segments) self.assertEqual( result, - u'/'.join( + '/'.join( ''.join( '%%%02X' % (ord(c) if isinstance(c, str) else c) for c in unsafe_segment.encode('utf-8') -- cgit v1.2.3 From e0fcb74e9d0c347a2c1627d07007f1d566dc7e17 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sun, 18 Nov 2018 23:43:18 -0600 Subject: fix up some more docstrings --- src/pyramid/i18n.py | 2 +- src/pyramid/traversal.py | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/pyramid/i18n.py b/src/pyramid/i18n.py index 112044e47..a20503be2 100644 --- a/src/pyramid/i18n.py +++ b/src/pyramid/i18n.py @@ -45,7 +45,7 @@ class Localizer(object): ``translate`` method accepts three arguments: ``tstring`` (required), ``domain`` (optional) and ``mapping`` (optional). When called, it will translate the ``tstring`` translation - string the current locale. If the current locale could not be + string using the current locale. If the current locale could not be determined, the result of interpolation of the default value is returned. The optional ``domain`` argument can be used to specify or override the domain of the ``tstring`` (useful when ``tstring`` diff --git a/src/pyramid/traversal.py b/src/pyramid/traversal.py index 72176fc4b..92436e403 100644 --- a/src/pyramid/traversal.py +++ b/src/pyramid/traversal.py @@ -62,7 +62,7 @@ def find_resource(resource, path): (absolute) or ``to%20the/La%20Pe%C3%B1a`` (relative). The :func:`pyramid.traversal.resource_path` function generates strings which follow these rules (albeit only absolute ones). The text may not - have any nonascii characters in it. + have any non-ASCII characters in it. Rules for passing a *tuple* as the ``path`` argument: if the first element in the path tuple is the empty string (for example ``('', @@ -190,8 +190,10 @@ def traverse(resource, path): example: if the path passed is ``/foo/bar``, and a resource object is found at ``/foo`` (but not at ``/foo/bar``), the 'view name' will be ``'bar'``. If the ``resource`` was found via - urldispatch, the view_name will be the name the route found was - registered with. + url dispatch, the ``view_name`` will be the empty string unless + the ``traverse`` predicate was specified or the ``*traverse`` route + pattern was used, at which point normal traversal rules dictate the + result. - ``subpath``: For a ``resource`` found via :term:`traversal`, this is a sequence of path segments found in the ``path`` that follow @@ -421,9 +423,9 @@ def traversal_path(path): """ Variant of :func:`pyramid.traversal.traversal_path_info` suitable for decoding paths that are URL-encoded. - If this function is passed a string, it *must* directly encodeable to + If this function is passed a string, it *must* be directly encodeable to ASCII. For example, '/foo' will work but '/' (a - string object with characters that cannot be encoded to ascii) will + string object with characters that cannot be encoded to ASCII) will not. A :exc:`UnicodeEncodeError` will be raised if the string cannot be encoded directly to ASCII. """ -- cgit v1.2.3 From 336e91d824ceb55a113b434be4957d1a5baa11a8 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Sun, 18 Nov 2018 23:47:15 -0600 Subject: fix moar strings --- src/pyramid/traversal.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pyramid/traversal.py b/src/pyramid/traversal.py index 92436e403..3dc37613e 100644 --- a/src/pyramid/traversal.py +++ b/src/pyramid/traversal.py @@ -444,11 +444,11 @@ def traversal_path_info(path): already-URL-decoded ``str`` type as if it had come to us from an upstream WSGI server as the ``PATH_INFO`` environ variable. - The ``path`` is first decoded to from its WSGI representation to text. - Per the PEP 3333 spec, ``path`` is first encoded to bytes using the + The ``path`` is first decoded from its WSGI representation to text. + Per the :pep:`3333` spec, ``path`` is first encoded to bytes using the Latin-1 encoding; the resulting set of bytes is subsequently decoded to text using the UTF-8 encoding; a :exc:`pyramid.exc.URLDecodeError` is - raised if a the URL cannot be decoded. + raised if the URL cannot be decoded. The ``path`` is split on slashes, creating a list of segments. If a segment name is empty or if it is ``.``, it is ignored. If a segment @@ -544,7 +544,7 @@ def quote_path_segment(segment, safe=PATH_SEGMENT_SAFE): the string ``__name__`` attribute of a resource) as a string. If the ``segment`` passed in is a bytes object, it is decoded as a UTF-8 string. The result is then URL-quoted using Python's ``urllib.quote``. - If the segment passed in is not bytes or a string, an error will be + If the segment passed in is not bytes nor a string, an error will be raised. The return value of ``quote_path_segment`` is always a string. You may pass a string of characters that need not be encoded as -- cgit v1.2.3 From f77957109be35d7b93014d5623fae7e670797213 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Mon, 19 Nov 2018 02:35:36 -0800 Subject: unpin webtest (missed in first pass) --- docs/narr/commandline.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst index 962193ec3..21b2a0839 100644 --- a/docs/narr/commandline.rst +++ b/docs/narr/commandline.rst @@ -1006,7 +1006,7 @@ top-level directory, your ``setup.py`` file will look something like this: requires = ['pyramid', 'pyramid_debugtoolbar'] tests_require = [ - 'WebTest >= 1.3.1', + 'WebTest', 'pytest', 'pytest-cov', ] @@ -1073,7 +1073,7 @@ The result will be something like: requires = ['pyramid', 'pyramid_debugtoolbar'] tests_require = [ - 'WebTest >= 1.3.1', + 'WebTest', 'pytest', 'pytest-cov', ] -- cgit v1.2.3 From cfaedc5e247af279059ab93fea4c9d7a27ddcf8a Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Mon, 19 Nov 2018 02:46:18 -0800 Subject: Standardize casing of URL when it is an acronym --- docs/glossary.rst | 8 ++++---- src/pyramid/traversal.py | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/glossary.rst b/docs/glossary.rst index e15d28fa5..2d51bf795 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -165,7 +165,7 @@ Glossary An object representing a node in the :term:`resource tree` of an application. If :term:`traversal` is used, a resource is an element in the resource tree traversed by the system. When traversal is used, a - resource becomes the :term:`context` of a :term:`view`. If :term:`url + resource becomes the :term:`context` of a :term:`view`. If :term:`URL dispatch` is used, a single resource is generated for each request and is used as the context resource of a view. @@ -405,13 +405,13 @@ Glossary the Routes syntax (which was inspired by Ruby On Rails pattern syntax). route - A single pattern matched by the :term:`url dispatch` subsystem, + A single pattern matched by the :term:`URL dispatch` subsystem, which generally resolves to a :term:`root factory` (and then ultimately a :term:`view`). .. seealso:: - See also :term:`url dispatch`. + See also :term:`URL dispatch`. route configuration Route configuration is the act of associating request parameters with a @@ -912,7 +912,7 @@ Glossary :meth:`pyramid.config.Configurator.add_route` and :meth:`pyramid.config.Configurator.add_view` to make it more convenient to register a collection of views as a single class when using - :term:`url dispatch`. View handlers ship as part of the + :term:`URL dispatch`. View handlers ship as part of the :term:`pyramid_handlers` add-on package. Deployment settings diff --git a/src/pyramid/traversal.py b/src/pyramid/traversal.py index 3dc37613e..9ed5754b7 100644 --- a/src/pyramid/traversal.py +++ b/src/pyramid/traversal.py @@ -171,17 +171,17 @@ def traverse(resource, path): A definition of each value in the returned dictionary: - ``context``: The :term:`context` (a :term:`resource` object) found - via traversal or url dispatch. If the ``path`` passed in is the + via traversal or URL dispatch. If the ``path`` passed in is the empty string, the value of the ``resource`` argument passed to this function is returned. - ``root``: The resource object at which :term:`traversal` begins. - If the ``resource`` passed in was found via url dispatch or if the + If the ``resource`` passed in was found via URL dispatch or if the ``path`` passed in was relative (non-absolute), the value of the ``resource`` argument passed to this function is returned. - ``view_name``: The :term:`view name` found during - :term:`traversal` or :term:`url dispatch`; if the ``resource`` was + :term:`traversal` or :term:`URL dispatch`; if the ``resource`` was found via traversal, this is usually a representation of the path segment which directly follows the path to the ``context`` in the ``path``. The ``view_name`` will be a string. The @@ -190,7 +190,7 @@ def traverse(resource, path): example: if the path passed is ``/foo/bar``, and a resource object is found at ``/foo`` (but not at ``/foo/bar``), the 'view name' will be ``'bar'``. If the ``resource`` was found via - url dispatch, the ``view_name`` will be the empty string unless + URL dispatch, the ``view_name`` will be the empty string unless the ``traverse`` predicate was specified or the ``*traverse`` route pattern was used, at which point normal traversal rules dictate the result. @@ -203,7 +203,7 @@ def traverse(resource, path): passed is ``/foo/bar/baz/buz``, and a resource object is found at ``/foo`` (but not ``/foo/bar``), the 'view name' will be ``'bar'`` and the :term:`subpath` will be ``['baz', 'buz']``. - For a ``resource`` found via url dispatch, the subpath will be a + For a ``resource`` found via URL dispatch, the subpath will be a sequence of values discerned from ``*subpath`` in the route pattern matched or the empty sequence. @@ -213,7 +213,7 @@ def traverse(resource, path): were traversed to find the ``context`` object (e.g. if the ``path`` provided is the empty string), the ``traversed`` value will be the empty sequence. If the ``resource`` is a resource found - via :term:`url dispatch`, traversed will be None. + via :term:`URL dispatch`, traversed will be None. - ``virtual_root``: A resource object representing the 'virtual' root of the resource tree being traversed during :term:`traversal`. @@ -232,7 +232,7 @@ def traverse(resource, path): items is a string. If no path segments were traversed to find the ``virtual_root`` resource (e.g. if virtual hosting is not in effect), the ``traversed`` value will be the empty list. - If url dispatch was used to find the ``resource``, this will be + If URL dispatch was used to find the ``resource``, this will be ``None``. If the path cannot be resolved, a :exc:`KeyError` will be raised. -- cgit v1.2.3 From 8c943501e87bed7836bb9ec1b216a561cc3f6be6 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 19 Nov 2018 21:35:37 -0600 Subject: rip out moar unicode prefixes --- docs/glossary.rst | 14 ++++----- docs/narr/hooks.rst | 8 ++--- docs/narr/i18n.rst | 11 ++++--- docs/narr/myproject/setup.py | 4 +-- docs/narr/renderers.rst | 5 ++-- docs/narr/traversal.rst | 6 ++-- docs/narr/urldispatch.rst | 30 +++++++++---------- docs/narr/views.rst | 39 ++++++------------------- docs/narr/webob.rst | 20 +++++-------- docs/tutorials/wiki/src/tests/tutorial/tests.py | 4 +-- 10 files changed, 57 insertions(+), 84 deletions(-) diff --git a/docs/glossary.rst b/docs/glossary.rst index 2d51bf795..97806d958 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -232,7 +232,7 @@ Glossary object *location-aware*. permission - A string or Unicode object that represents an action being taken against + A string that represents an action being taken against a :term:`context` resource. A permission is associated with a view name and a resource type by the developer. Resources are decorated with security declarations (e.g. an :term:`ACL`), which reference these @@ -289,7 +289,7 @@ Glossary :term:`authorization policy`. principal - A *principal* is a string or Unicode object representing an entity, + A *principal* is a string representing an entity, typically a user or group. Principals are provided by an :term:`authentication policy`. For example, if a user has the :term:`userid` `bob`, and is a member of two groups named `group foo` and @@ -298,7 +298,7 @@ Glossary foo` and `group bar`. userid - A *userid* is a string or Unicode object used to identify and authenticate + A *userid* is a string used to identify and authenticate a real-world user or client. A userid is supplied to an :term:`authentication policy` in order to discover the user's :term:`principals `. In the authentication policies which @@ -523,8 +523,8 @@ Glossary from the :term:`physical root`. For example, the physical path of the ``abc`` subobject of the physical root object is ``/abc``. Physical paths can also be specified as tuples where the first element is the empty - string (representing the root), and every other element is a Unicode - object, e.g. ``('', 'abc')``. Physical paths are also sometimes called + string (representing the root), and every other element is a string, + e.g. ``('', 'abc')``. Physical paths are also sometimes called "traversal paths". lineage @@ -755,7 +755,7 @@ Glossary Translation String An instance of :class:`pyramid.i18n.TranslationString`, which - is a class that behaves like a Unicode string, but has several + is a class that behaves like a string, but has several extra attributes such as ``domain``, ``msgid``, and ``mapping`` for use during translation. Translation strings are usually created by hand within software, but are sometimes created on the @@ -779,7 +779,7 @@ Glossary Translator A callable which receives a :term:`translation string` and returns a - translated Unicode object for the purposes of internationalization. A + translated string for the purposes of internationalization. A :term:`localizer` supplies a translator to a :app:`Pyramid` application accessible via its :class:`~pyramid.i18n.Localizer.translate` method. diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index 9f405c336..5e67a81c7 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -659,15 +659,15 @@ that implements the following interface: ``virtual_root``, and ``virtual_root_path``. These values are typically the result of a resource tree traversal. ``root`` is the physical root object, ``context`` will be a resource - object, ``view_name`` will be the view name used (a Unicode - name), ``subpath`` will be a sequence of Unicode names that + object, ``view_name`` will be the view name used (a string), + ``subpath`` will be a sequence of strings that followed the view name but were not traversed, ``traversed`` - will be a sequence of Unicode names that were traversed + will be a sequence of strings that were traversed (including the virtual root path, if any) ``virtual_root`` will be a resource object representing the virtual root (or the physical root if traversal was not performed), and ``virtual_root_path`` will be a sequence representing the - virtual root path (a sequence of Unicode names) or None if + virtual root path (a sequence of strings) or None if traversal was not performed. Extra keys for special purpose functionality can be added as diff --git a/docs/narr/i18n.rst b/docs/narr/i18n.rst index 9b838c7f4..3a4f5af5b 100644 --- a/docs/narr/i18n.rst +++ b/docs/narr/i18n.rst @@ -33,7 +33,7 @@ While you write your software, you can insert specialized markup into your Python code that makes it possible for the system to translate text values into the languages used by your application's users. This markup creates a :term:`translation string`. A translation string is an object that behaves -mostly like a normal Unicode object, except that it also carries around extra +mostly like a normal Unicode string, except that it also carries around extra information related to its job as part of the :app:`Pyramid` translation machinery. @@ -49,7 +49,7 @@ The most primitive way to create a translation string is to use the from pyramid.i18n import TranslationString ts = TranslationString('Add') -This creates a Unicode-like object that is a TranslationString. +This creates a ``str``-like object that is a TranslationString. .. note:: @@ -61,9 +61,8 @@ This creates a Unicode-like object that is a TranslationString. The first argument to :class:`~pyramid.i18n.TranslationString` is the ``msgid``; it is required. It represents the key into the translation mappings -provided by a particular localization. The ``msgid`` argument must be a Unicode -object or an ASCII string. The msgid may optionally contain *replacement -markers*. For instance: +provided by a particular localization. The ``msgid`` argument must be a string. +The msgid may optionally contain *replacement markers*. For instance: .. code-block:: python :linenos: @@ -429,7 +428,7 @@ Performing a Translation A :term:`localizer` has a ``translate`` method which accepts either a :term:`translation string` or a Unicode string and which returns a Unicode -object representing the translation. Generating a translation in a view +string representing the translation. Generating a translation in a view component of an application might look like so: .. code-block:: python diff --git a/docs/narr/myproject/setup.py b/docs/narr/myproject/setup.py index cf626880f..1ee272270 100644 --- a/docs/narr/myproject/setup.py +++ b/docs/narr/myproject/setup.py @@ -17,8 +17,8 @@ requires = [ ] tests_require = [ - 'WebTest >= 1.3.1', # py3 compat - 'pytest>=3.7.4', + 'WebTest', + 'pytest', 'pytest-cov', ] diff --git a/docs/narr/renderers.rst b/docs/narr/renderers.rst index 493f808d5..6b4982e4b 100644 --- a/docs/narr/renderers.rst +++ b/docs/narr/renderers.rst @@ -145,8 +145,7 @@ used in the ``renderer`` attribute of view configurations. The ``string`` renderer renders a view callable result to a string. If a view callable returns a non-Response object, and the ``string`` renderer is associated in that view's configuration, the result will be to run the object -through the Python ``str`` function to generate a string. Note that if a -Unicode object is returned by the view callable, it is not ``str()``-ified. +through the Python ``str`` function to generate a string. Here's an example of a view that returns a dictionary. If the ``string`` renderer is specified in the configuration for this view, the view will render @@ -496,7 +495,7 @@ interface. A typical class that follows this setup is as follows: def __call__(self, value, system): """ Call the renderer implementation with the value and the system value passed in as arguments and return - the result (a string or unicode object). The value is + the result (a bytes or string object). The value is the return value of a view. The system value is a dictionary containing available system values (e.g., view, context, and request). """ diff --git a/docs/narr/traversal.rst b/docs/narr/traversal.rst index 9b91e21ba..769d0984c 100644 --- a/docs/narr/traversal.rst +++ b/docs/narr/traversal.rst @@ -237,7 +237,7 @@ uses this algorithm to find a :term:`context` resource and a :term:`view name`. The traversal algorithm by default attempts to first URL-unquote and then Unicode-decode each path segment derived from ``PATH_INFO`` from its - natural byte string (``str`` type) representation. URL unquoting is + natural string representation. URL unquoting is performed using the Python standard library ``urllib.unquote`` function. Conversion from a URL-decoded string into Unicode is attempted using the UTF-8 encoding. If any URL-unquoted path segment in ``PATH_INFO`` is not @@ -246,10 +246,10 @@ uses this algorithm to find a :term:`context` resource and a :term:`view name`. to the ``__getitem__`` of any resource during traversal. Thus a request with a ``PATH_INFO`` variable of ``/a/b/c`` maps to the - traversal sequence ``[u'a', u'b', u'c']``. + traversal sequence ``['a', 'b', 'c']``. #. :term:`Traversal` begins at the root resource returned by the root factory. - For the traversal sequence ``[u'a', u'b', u'c']``, the root resource's + For the traversal sequence ``['a', 'b', 'c']``, the root resource's ``__getitem__`` is called with the name ``'a'``. Traversal continues through the sequence. In our example, if the root resource's ``__getitem__`` called with the name ``a`` returns a resource (a.k.a. diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index 3b737b46d..b9b42a9bd 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -165,8 +165,8 @@ The above pattern will match these URLs, generating the following matchdicts: .. code-block:: text - foo/1/2 -> {'baz':u'1', 'bar':u'2'} - foo/abc/def -> {'baz':u'abc', 'bar':u'def'} + foo/1/2 -> {'baz':'1', 'bar':'2'} + foo/abc/def -> {'baz':'abc', 'bar':'def'} It will not match the following patterns however: @@ -184,7 +184,7 @@ instance, if this route pattern was used: foo/{name}.html The literal path ``/foo/biz.html`` will match the above route pattern, and the -match result will be ``{'name':u'biz'}``. However, the literal path +match result will be ``{'name':'biz'}``. However, the literal path ``/foo/biz`` will not match, because it does not contain a literal ``.html`` at the end of the segment represented by ``{name}.html`` (it only contains ``biz``, not ``biz.html``). @@ -242,7 +242,7 @@ The matchdict will look like so (the value is URL-decoded / UTF-8 decoded): .. code-block:: text - {'bar':u'La Pe\xf1a'} + {'bar': 'La Pe\xf1a'} Literal strings in the path segment should represent the *decoded* value of the ``PATH_INFO`` provided to Pyramid. You don't want to use a URL-encoded value @@ -303,10 +303,10 @@ The above pattern will match these URLs, generating the following matchdicts: .. code-block:: text foo/1/2/ -> - {'baz':u'1', 'bar':u'2', 'fizzle':()} + {'baz':'1', 'bar':'2', 'fizzle':()} foo/abc/def/a/b/c -> - {'baz':u'abc', 'bar':u'def', 'fizzle':(u'a', u'b', u'c')} + {'baz':'abc', 'bar':'def', 'fizzle':('a', 'b', 'c')} Note that when a ``*stararg`` remainder match is matched, the value put into the matchdict is turned into a tuple of path segments representing the @@ -327,7 +327,7 @@ Will generate the following matchdict: .. code-block:: text - {'fizzle':(u'La Pe\xf1a', u'a', u'b', u'c')} + {'fizzle':('La Pe\xf1a', 'a', 'b', 'c')} By default, the ``*stararg`` will parse the remainder sections into a tuple split by segment. Changing the regular expression used to match a marker can @@ -341,8 +341,8 @@ The above pattern will match these URLs, generating the following matchdicts: .. code-block:: text - foo/1/2/ -> {'baz':u'1', 'bar':u'2', 'fizzle':u''} - foo/abc/def/a/b/c -> {'baz':u'abc', 'bar':u'def', 'fizzle': u'a/b/c'} + foo/1/2/ -> {'baz':'1', 'bar':'2', 'fizzle':''} + foo/abc/def/a/b/c -> {'baz':'abc', 'bar':'def', 'fizzle': 'a/b/c'} This occurs because the default regular expression for a marker is ``[^/]+`` which will match everything up to the first ``/``, while ``{fizzle:.*}`` will @@ -562,12 +562,12 @@ Here is an example of a corresponding ``mypackage.views`` module: @view_config(route_name='user') def user_view(request): user = request.matchdict['user'] - return Response(u'The user is {}.'.format(user)) + return Response('The user is {}.'.format(user)) @view_config(route_name='tag') def tag_view(request): tag = request.matchdict['tag'] - return Response(u'The tag is {}.'.format(tag)) + return Response('The tag is {}.'.format(tag)) The above configuration will allow :app:`Pyramid` to service URLs in these forms: @@ -714,13 +714,13 @@ Therefore, if you've added a route like so: .. code-block:: python - config.add_route('la', u'/La Peña/{city}') + config.add_route('la', '/La Peña/{city}') And you later generate a URL using ``route_path`` or ``route_url`` like so: .. code-block:: python - url = request.route_path('la', city=u'Québec') + url = request.route_path('la', city='Québec') You will wind up with the path encoded to UTF-8 and URL-quoted like so: @@ -739,7 +739,7 @@ And you later generate a URL using ``route_path`` or ``route_url`` using a .. code-block:: python - url = request.route_path('abc', foo=u'Québec/biz') + url = request.route_path('abc', foo='Québec/biz') The value you pass will be URL-quoted except for embedded slashes in the result: @@ -752,7 +752,7 @@ You can get a similar result by passing a tuple composed of path elements: .. code-block:: python - url = request.route_path('abc', foo=(u'Québec', u'biz')) + url = request.route_path('abc', foo=('Québec', 'biz')) Each value in the tuple will be URL-quoted and joined by slashes in this case: diff --git a/docs/narr/views.rst b/docs/narr/views.rst index a53063f78..40717c37a 100644 --- a/docs/narr/views.rst +++ b/docs/narr/views.rst @@ -442,10 +442,7 @@ browser client, and its ``action`` points at some :app:`Pyramid` view code: :linenos: - - - -
+
@@ -457,8 +454,8 @@ browser client, and its ``action`` points at some :app:`Pyramid` view code: The ``myview`` view code in the :app:`Pyramid` application *must* expect that -the values returned by ``request.params`` will be of type ``unicode``, as -opposed to type ``str``. The following will work to accept a form post from the +the values returned by ``request.params`` will be of type ``str``, as opposed +to type ``bytes``. The following will work to accept a form post from the above form: .. code-block:: python @@ -468,24 +465,12 @@ above form: firstname = request.params['firstname'] lastname = request.params['lastname'] -But the following ``myview`` view code *may not* work, as it tries to decode -already-decoded (``unicode``) values obtained from ``request.params``: - -.. code-block:: python - :linenos: - - def myview(request): - # the .decode('utf-8') will break below if there are any high-order - # characters in the firstname or lastname - firstname = request.params['firstname'].decode('utf-8') - lastname = request.params['lastname'].decode('utf-8') - For implicit decoding to work reliably, you should ensure that every form you render that posts to a :app:`Pyramid` view explicitly defines a charset encoding of UTF-8. This can be done via a response that has a ``;charset=UTF-8`` in its ``Content-Type`` header; or, as in the form above, -with a ``meta http-equiv`` tag that implies that the charset is UTF-8 within -the HTML ``head`` of the page containing the form. This must be done +with a ``accept-charset`` tag that implies that informs the browser that the +server expects the form content to be encoded using UTF-8. This must be done explicitly because all known browser clients assume that they should encode form data in the same character set implied by the ``Content-Type`` value of the response containing the form when subsequently submitting that form. There @@ -499,21 +484,15 @@ when it can't decode some high-order character encoded in another character set within form data, e.g., when ``request.params['somename']`` is accessed. If you are using the :class:`~pyramid.response.Response` class to generate a -response, or if you use the ``render_template_*`` templating APIs, the UTF-8 -``charset`` is set automatically as the default via the ``Content-Type`` -header. If you return a ``Content-Type`` header without an explicit -``charset``, a request will add a ``;charset=utf-8`` trailer to the +response, or if you use the ``pyramid.renderers.render_*`` templating APIs, +the UTF-8 ``charset`` is set automatically as the default via the +``Content-Type`` header. If you return a ``Content-Type`` header without an +explicit ``charset``, a request will add a ``;charset=utf-8`` trailer to the ``Content-Type`` header value for you for response content types that are textual (e.g., ``text/html`` or ``application/xml``) as it is rendered. If you are using your own response object, you will need to ensure you do this yourself. -.. note:: Only the *values* of request params obtained via ``request.params``, - ``request.GET`` or ``request.POST`` are decoded to Unicode objects - implicitly in the :app:`Pyramid` default configuration. The keys are still - (byte) strings. - - .. index:: single: view calling convention diff --git a/docs/narr/webob.rst b/docs/narr/webob.rst index c9a5a68e1..72f2db42e 100644 --- a/docs/narr/webob.rst +++ b/docs/narr/webob.rst @@ -188,14 +188,10 @@ of them. Here are a couple that might be useful: Text (Unicode) ++++++++++++++ -Many of the properties of the request object will be text values (``str`` type) if the request encoding/charset is -provided. If it is provided, the values in ``req.POST``, ``req.GET``, -``req.params``, and ``req.cookies`` will contain text. The client *can* -indicate the charset with something like ``Content-Type: -application/x-www-form-urlencoded; charset=utf8``, but browsers seldom set -this. You can reset the charset of an existing request with ``newreq = -req.decode('utf-8')``, or during instantiation with ``Request(environ, -charset='utf8')``. +Most of the properties of the request object will be text values. +The values in ``req.POST``, ``req.GET``, ``req.params``, and ``req.cookies`` will contain text and are generated assuming a UTF-8 charset. +The client *can* indicate the charset with something like ``Content-Type: application/x-www-form-urlencoded; charset=utf8``, but browsers seldom set this. +You can reset the charset of an existing request with ``newreq = req.decode('utf-8')``, or during instantiation with ``Request(environ, charset='utf8')``. .. index:: single: multidict (WebOb) @@ -263,7 +259,7 @@ to a :app:`Pyramid` application: jQuery.ajax({type:'POST', url: 'http://localhost:6543/', // the pyramid server data: JSON.stringify({'a':1}), - contentType: 'application/json; charset=utf-8'}); + contentType: 'application/json'}); When such a request reaches a view in your application, the ``request.json_body`` attribute will be available in the view callable body. @@ -279,7 +275,7 @@ For the above view, printed to the console will be: .. code-block:: python - {u'a': 1} + {'a': 1} For bonus points, here's a bit of client-side code that will produce a request that has a body suitable for reading via ``request.json_body`` using Python's @@ -385,8 +381,8 @@ A response object has three fundamental parts: ``response.app_iter`` An iterable (such as a list or generator) that will produce the content of - the response. This is also accessible as ``response.body`` (a string), - ``response.text`` (a unicode object, informed by ``response.charset``), and + the response. This is also accessible as ``response.body`` (bytes), + ``response.text`` (a string, informed by ``response.charset``), and ``response.body_file`` (a file-like object; writing to it appends to ``app_iter``). diff --git a/docs/tutorials/wiki/src/tests/tutorial/tests.py b/docs/tutorials/wiki/src/tests/tutorial/tests.py index 098e9c1bd..d713d3fdd 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/tests.py +++ b/docs/tutorials/wiki/src/tests/tutorial/tests.py @@ -8,12 +8,12 @@ class PageModelTests(unittest.TestCase): from .models import Page return Page - def _makeOne(self, data=u'some data'): + def _makeOne(self, data='some data'): return self._getTargetClass()(data=data) def test_constructor(self): instance = self._makeOne() - self.assertEqual(instance.data, u'some data') + self.assertEqual(instance.data, 'some data') class WikiModelTests(unittest.TestCase): -- cgit v1.2.3 From a08763a9b5213ed352a26c603a6d3722aebe9dd0 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Tue, 20 Nov 2018 00:42:39 -0800 Subject: proper hyphenation of UTF-8-decoded --- docs/narr/traversal.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/narr/traversal.rst b/docs/narr/traversal.rst index 769d0984c..0282c6096 100644 --- a/docs/narr/traversal.rst +++ b/docs/narr/traversal.rst @@ -242,7 +242,7 @@ uses this algorithm to find a :term:`context` resource and a :term:`view name`. Conversion from a URL-decoded string into Unicode is attempted using the UTF-8 encoding. If any URL-unquoted path segment in ``PATH_INFO`` is not decodeable using the UTF-8 decoding, a :exc:`TypeError` is raised. A - segment will be fully URL-unquoted and UTF8-decoded before it is passed in + segment will be fully URL-unquoted and UTF-8-decoded before it is passed in to the ``__getitem__`` of any resource during traversal. Thus a request with a ``PATH_INFO`` variable of ``/a/b/c`` maps to the -- cgit v1.2.3 From 1007fa3773a311a72dd7b2e5e8c52584c5d6e416 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Tue, 20 Nov 2018 00:55:17 -0800 Subject: proper spacing of interpreter output between a dict's colon and value --- docs/narr/urldispatch.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index b9b42a9bd..129dce8f3 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -165,8 +165,8 @@ The above pattern will match these URLs, generating the following matchdicts: .. code-block:: text - foo/1/2 -> {'baz':'1', 'bar':'2'} - foo/abc/def -> {'baz':'abc', 'bar':'def'} + foo/1/2 -> {'baz': '1', 'bar': '2'} + foo/abc/def -> {'baz': 'abc', 'bar': 'def'} It will not match the following patterns however: @@ -303,10 +303,10 @@ The above pattern will match these URLs, generating the following matchdicts: .. code-block:: text foo/1/2/ -> - {'baz':'1', 'bar':'2', 'fizzle':()} + {'baz': '1', 'bar': '2', 'fizzle': ()} foo/abc/def/a/b/c -> - {'baz':'abc', 'bar':'def', 'fizzle':('a', 'b', 'c')} + {'baz': 'abc', 'bar': 'def', 'fizzle': ('a', 'b', 'c')} Note that when a ``*stararg`` remainder match is matched, the value put into the matchdict is turned into a tuple of path segments representing the @@ -327,7 +327,7 @@ Will generate the following matchdict: .. code-block:: text - {'fizzle':('La Pe\xf1a', 'a', 'b', 'c')} + {'fizzle': ('La Pe\xf1a', 'a', 'b', 'c')} By default, the ``*stararg`` will parse the remainder sections into a tuple split by segment. Changing the regular expression used to match a marker can @@ -341,8 +341,8 @@ The above pattern will match these URLs, generating the following matchdicts: .. code-block:: text - foo/1/2/ -> {'baz':'1', 'bar':'2', 'fizzle':''} - foo/abc/def/a/b/c -> {'baz':'abc', 'bar':'def', 'fizzle': 'a/b/c'} + foo/1/2/ -> {'baz': '1', 'bar': '2', 'fizzle': ''} + foo/abc/def/a/b/c -> {'baz': 'abc', 'bar': 'def', 'fizzle': 'a/b/c'} This occurs because the default regular expression for a marker is ``[^/]+`` which will match everything up to the first ``/``, while ``{fizzle:.*}`` will -- cgit v1.2.3 From 9eec98980d17fee7c27aa1b36a0b8ae74b081e26 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Tue, 20 Nov 2018 00:59:57 -0800 Subject: grammar fixes --- docs/narr/views.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/narr/views.rst b/docs/narr/views.rst index 40717c37a..1b4118b85 100644 --- a/docs/narr/views.rst +++ b/docs/narr/views.rst @@ -469,7 +469,7 @@ For implicit decoding to work reliably, you should ensure that every form you render that posts to a :app:`Pyramid` view explicitly defines a charset encoding of UTF-8. This can be done via a response that has a ``;charset=UTF-8`` in its ``Content-Type`` header; or, as in the form above, -with a ``accept-charset`` tag that implies that informs the browser that the +with an ``accept-charset`` attribute, informing the browser that the server expects the form content to be encoded using UTF-8. This must be done explicitly because all known browser clients assume that they should encode form data in the same character set implied by the ``Content-Type`` value of -- cgit v1.2.3 From c9d5832707877faac5b94943e00951b69192185d Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Tue, 20 Nov 2018 01:07:29 -0800 Subject: moar proper spacing of interpreter output between a dict's colon and value --- docs/narr/urldispatch.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index 129dce8f3..9372163e8 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -184,7 +184,7 @@ instance, if this route pattern was used: foo/{name}.html The literal path ``/foo/biz.html`` will match the above route pattern, and the -match result will be ``{'name':'biz'}``. However, the literal path +match result will be ``{'name': 'biz'}``. However, the literal path ``/foo/biz`` will not match, because it does not contain a literal ``.html`` at the end of the segment represented by ``{name}.html`` (it only contains ``biz``, not ``biz.html``). @@ -513,7 +513,7 @@ When the ``/site/{id}`` route pattern matches during a request, the When this route matches, a ``matchdict`` will be generated and attached to the request as ``request.matchdict``. If the specific URL matched is ``/site/1``, the ``matchdict`` will be a dictionary with a single key, ``id``; the value -will be the string ``'1'``, ex.: ``{'id':'1'}``. +will be the string ``'1'``, ex.: ``{'id': '1'}``. The ``mypackage.views`` module referred to above might look like so: @@ -581,17 +581,17 @@ forms: - When a URL matches the pattern ``/ideas/{idea}``, the view callable available at the dotted Python pathname ``mypackage.views.idea_view`` will be called. For the specific URL ``/ideas/1``, the ``matchdict`` generated - and attached to the :term:`request` will consist of ``{'idea':'1'}``. + and attached to the :term:`request` will consist of ``{'idea': '1'}``. - When a URL matches the pattern ``/users/{user}``, the view callable available at the dotted Python pathname ``mypackage.views.user_view`` will be called. For the specific URL ``/users/1``, the ``matchdict`` generated and - attached to the :term:`request` will consist of ``{'user':'1'}``. + attached to the :term:`request` will consist of ``{'user': '1'}``. - When a URL matches the pattern ``/tags/{tag}``, the view callable available at the dotted Python pathname ``mypackage.views.tag_view`` will be called. For the specific URL ``/tags/1``, the ``matchdict`` generated and attached to - the :term:`request` will consist of ``{'tag':'1'}``. + the :term:`request` will consist of ``{'tag': '1'}``. In this example we've again associated each of our routes with a :term:`view callable` directly. In all cases, the request, which will have a ``matchdict`` -- cgit v1.2.3 From b1c500c3d4cb269aed254742501040c1c64367c2 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Tue, 20 Nov 2018 01:11:09 -0800 Subject: s/msgid/``msgid`` as needed --- docs/narr/i18n.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/narr/i18n.rst b/docs/narr/i18n.rst index 3a4f5af5b..b8cd396c0 100644 --- a/docs/narr/i18n.rst +++ b/docs/narr/i18n.rst @@ -62,7 +62,7 @@ This creates a ``str``-like object that is a TranslationString. The first argument to :class:`~pyramid.i18n.TranslationString` is the ``msgid``; it is required. It represents the key into the translation mappings provided by a particular localization. The ``msgid`` argument must be a string. -The msgid may optionally contain *replacement markers*. For instance: +The ``msgid`` may optionally contain *replacement markers*. For instance: .. code-block:: python :linenos: @@ -80,14 +80,14 @@ may be supplied at the same time as the replacement marker itself: from pyramid.i18n import TranslationString ts = TranslationString('Add ${number}', mapping={'number':1}) -Any number of replacement markers can be present in the msgid value, any number +Any number of replacement markers can be present in the ``msgid`` value, any number of times. Only markers which can be replaced by the values in the *mapping* will be replaced at translation time. The others will not be interpolated and will be output literally. A translation string should also usually carry a *domain*. The domain represents a translation category to disambiguate it from other translations of -the same msgid, in case they conflict. +the same ``msgid``, in case they conflict. .. code-block:: python :linenos: @@ -99,7 +99,7 @@ the same msgid, in case they conflict. The above translation string named a domain of ``form``. A :term:`translator` function will often use the domain to locate the right translator file on the filesystem which contains translations for a given domain. In this case, if it -were trying to translate our msgid to German, it might try to find a +were trying to translate our ``msgid`` to German, it might try to find a translation from a :term:`gettext` file within a :term:`translation directory` like this one: -- cgit v1.2.3 From b404d4b29e5eaa08fb38e9bd4818e1a2d390c10b Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Fri, 23 Nov 2018 15:54:53 -0600 Subject: fix a couple more doc strings --- docs/glossary.rst | 2 +- docs/narr/hooks.rst | 2 +- docs/narr/webob.rst | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/glossary.rst b/docs/glossary.rst index 97806d958..e21ae2fdc 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -523,7 +523,7 @@ Glossary from the :term:`physical root`. For example, the physical path of the ``abc`` subobject of the physical root object is ``/abc``. Physical paths can also be specified as tuples where the first element is the empty - string (representing the root), and every other element is a string, + string (representing the root), and every other element is a Unicode string, e.g. ``('', 'abc')``. Physical paths are also sometimes called "traversal paths". diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index 5e67a81c7..0dac8d426 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -667,7 +667,7 @@ that implements the following interface: will be a resource object representing the virtual root (or the physical root if traversal was not performed), and ``virtual_root_path`` will be a sequence representing the - virtual root path (a sequence of strings) or None if + virtual root path (a sequence of strings) or ``None`` if traversal was not performed. Extra keys for special purpose functionality can be added as diff --git a/docs/narr/webob.rst b/docs/narr/webob.rst index 72f2db42e..665bbddc9 100644 --- a/docs/narr/webob.rst +++ b/docs/narr/webob.rst @@ -382,7 +382,7 @@ A response object has three fundamental parts: ``response.app_iter`` An iterable (such as a list or generator) that will produce the content of the response. This is also accessible as ``response.body`` (bytes), - ``response.text`` (a string, informed by ``response.charset``), and + ``response.text`` (a Unicode string, informed by ``response.charset``), and ``response.body_file`` (a file-like object; writing to it appends to ``app_iter``). -- cgit v1.2.3 From f6b0ae2a32d6bcd40246ef1ec3abb16ce65324dc Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Fri, 23 Nov 2018 15:55:00 -0600 Subject: always use compare_digest --- src/pyramid/util.py | 8 ++------ tests/test_util.py | 32 +++----------------------------- 2 files changed, 5 insertions(+), 35 deletions(-) diff --git a/src/pyramid/util.py b/src/pyramid/util.py index cad8142dd..e552b37de 100644 --- a/src/pyramid/util.py +++ b/src/pyramid/util.py @@ -301,7 +301,7 @@ class WeakOrderedSet(object): return self._items[oid]() -def strings_differ(string1, string2, compare_digest=compare_digest): +def strings_differ(string1, string2): """Check whether two strings differ while avoiding timing attacks. This function returns True if the given strings differ and False @@ -325,11 +325,7 @@ def strings_differ(string1, string2, compare_digest=compare_digest): left = string2 right = string2 - if compare_digest is not None: - invalid_bits += not compare_digest(left, right) - else: - for a, b in zip(left, right): - invalid_bits += a != b + invalid_bits += not compare_digest(left, right) return invalid_bits != 0 diff --git a/tests/test_util.py b/tests/test_util.py index 0f313955b..84bc9379f 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -436,37 +436,11 @@ class Test_strings_differ(unittest.TestCase): self.assertFalse(self._callFUT('123', '123')) self.assertTrue(self._callFUT('123', '1234')) - def test_it_with_internal_comparator(self): - result = self._callFUT(b'foo', b'foo', compare_digest=None) - self.assertFalse(result) - - result = self._callFUT(b'123', b'abc', compare_digest=None) - self.assertTrue(result) - - def test_it_with_external_comparator(self): - class DummyComparator(object): - called = False - - def __init__(self, ret_val): - self.ret_val = ret_val - - def __call__(self, a, b): - self.called = True - return self.ret_val - - dummy_compare = DummyComparator(True) - result = self._callFUT(b'foo', b'foo', compare_digest=dummy_compare) - self.assertTrue(dummy_compare.called) + def test_it(self): + result = self._callFUT(b'foo', b'foo') self.assertFalse(result) - dummy_compare = DummyComparator(False) - result = self._callFUT(b'123', b'345', compare_digest=dummy_compare) - self.assertTrue(dummy_compare.called) - self.assertTrue(result) - - dummy_compare = DummyComparator(False) - result = self._callFUT(b'abc', b'abc', compare_digest=dummy_compare) - self.assertTrue(dummy_compare.called) + result = self._callFUT(b'123', b'abc') self.assertTrue(result) -- cgit v1.2.3 From 8f6b195b3f1e1f1f2f16e09902540ece823e9cc9 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Fri, 23 Nov 2018 14:37:11 -0800 Subject: Remove Python versions mention --- docs/tutorials/wiki/background.rst | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/tutorials/wiki/background.rst b/docs/tutorials/wiki/background.rst index c10ab9e55..1a076de85 100644 --- a/docs/tutorials/wiki/background.rst +++ b/docs/tutorials/wiki/background.rst @@ -15,9 +15,4 @@ To code along with this tutorial, the developer will need a Unix machine with development tools (macOS with XCode, any Linux or BSD variant, and so on) *or* a Windows system of any kind. -.. warning:: - - This tutorial has been written for Python 2. It is unlikely to work - without modification under Python 3. - Have fun! -- cgit v1.2.3 From 4fe2cc396fbfdfb6af78de6dd9190279a6638a19 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Fri, 23 Nov 2018 15:05:19 -0800 Subject: Correct grammar, use shorter sentences, rewrap to one sentence per line. --- docs/tutorials/wiki/design.rst | 96 ++++++++++++++++++------------------------ 1 file changed, 42 insertions(+), 54 deletions(-) diff --git a/docs/tutorials/wiki/design.rst b/docs/tutorials/wiki/design.rst index 30d443bb8..5c86293f6 100644 --- a/docs/tutorials/wiki/design.rst +++ b/docs/tutorials/wiki/design.rst @@ -4,85 +4,77 @@ Design ====== -Following is a quick overview of the design of our wiki application, to help -us understand the changes that we will be making as we work through the -tutorial. +Following is a quick overview of the design of our wiki application to help us understand the changes that we will make as we work through the tutorial. + Overall ------- -We choose to use :term:`reStructuredText` markup in the wiki text. Translation -from reStructuredText to HTML is provided by the widely used ``docutils`` -Python module. We will add this module in the dependency list on the project -``setup.py`` file. +We choose to use :term:`reStructuredText` markup in the wiki text. +Conversion from reStructuredText to HTML is provided by the widely used ``docutils`` Python module. +We will add this module in the dependency list on the project ``setup.py`` file. + Models ------ -The root resource named ``Wiki`` will be a mapping of wiki page -names to page resources. The page resources will be instances -of a *Page* class and they store the text content. +The root resource named ``Wiki`` will be a mapping of wiki page names to page resources. +The page resources will be instances of a *Page* class. +They store the text content. + +URLs like ``/PageName`` will be traversed using Wiki[ *PageName* ] => page. +The resulting context is the page resource of an existing page. -URLs like ``/PageName`` will be traversed using Wiki[ -*PageName* ] => page, and the context that results is the page -resource of an existing page. +To add a page to the wiki, a new instance of the page resource is created. +Its name and reference are added to the Wiki mapping. -To add a page to the wiki, a new instance of the page resource -is created and its name and reference are added to the Wiki -mapping. +A page named ``FrontPage`` containing the text *This is the front page* will be created when the storage is initialized. +It will be used as the wiki home page. -A page named ``FrontPage`` containing the text *This is the front page*, will -be created when the storage is initialized, and will be used as the wiki home -page. Views ----- -There will be three views to handle the normal operations of adding, -editing, and viewing wiki pages, plus one view for the wiki front page. -Two templates will be used, one for viewing, and one for both adding -and editing wiki pages. +There will be three views to handle the normal operations of adding, editing, and viewing wiki pages, plus one view for the wiki front page. +Two templates will be used, one for viewing, and one for both adding and editing wiki pages. -As of version 1.5 :app:`Pyramid` no longer ships with templating systems. In this tutorial, we will use :term:`Chameleon`. Chameleon is a variant of :term:`ZPT`, which is an XML-based templating language. +As of version 1.5 :app:`Pyramid` no longer ships with templating systems. +In this tutorial we will use :term:`Chameleon`. +Chameleon is a variant of :term:`ZPT`, which is an XML-based templating language. Security -------- -We'll eventually be adding security to our application. The components we'll -use to do this are below. +We'll eventually add security to our application. +The components we'll use to do this are below. -- USERS, a dictionary mapping :term:`userids ` to their - corresponding passwords. +- USERS, a dictionary mapping :term:`userids ` to their corresponding passwords. -- GROUPS, a dictionary mapping :term:`userids ` to a - list of groups to which they belong. +- GROUPS, a dictionary mapping :term:`userids ` to a list of groups to which they belong. -- ``groupfinder``, an *authorization callback* that looks up USERS and - GROUPS. It will be provided in a new ``security.py`` file. +- ``groupfinder``, an *authorization callback* that looks up USERS and GROUPS. + It will be provided in a new ``security.py`` file. -- An :term:`ACL` is attached to the root :term:`resource`. Each row below - details an :term:`ACE`: +- An :term:`ACL` is attached to the root :term:`resource`. + Each row below details an :term:`ACE`: - +----------+----------------+----------------+ - | Action | Principal | Permission | - +==========+================+================+ - | Allow | Everyone | View | - +----------+----------------+----------------+ - | Allow | group:editors | Edit | - +----------+----------------+----------------+ + +----------+----------------+----------------+ + | Action | Principal | Permission | + +==========+================+================+ + | Allow | Everyone | View | + +----------+----------------+----------------+ + | Allow | group:editors | Edit | + +----------+----------------+----------------+ -- Permission declarations are added to the views to assert the security - policies as each request is handled. +- Permission declarations are added to the views to assert the security policies as each request is handled. -Two additional views and one template will handle the login and -logout tasks. +Two additional views and one template will handle the login and logout tasks. Summary ------- -The URL, context, actions, template and permission associated to each view are -listed in the following table: +The URL, context, actions, template and permission associated to each view are listed in the following table: +----------------------+-------------+-----------------+-----------------------+------------+------------+ | URL | View | Context | Action | Template | Permission | @@ -139,10 +131,6 @@ listed in the following table: | | | | /FrontPage | | | +----------------------+-------------+-----------------+-----------------------+------------+------------+ -.. [1] This is the default view for a Page context - when there is no view name. -.. [2] Pyramid will return a default 404 Not Found page - if the page *PageName* does not exist yet. -.. [3] ``pyramid.exceptions.Forbidden`` is reached when a - user tries to invoke a view that is - not authorized by the authorization policy. +.. [1] This is the default view for a Page context when there is no view name. +.. [2] Pyramid will return a default 404 Not Found page if the page *PageName* does not exist yet. +.. [3] ``pyramid.exceptions.Forbidden`` is reached when a user tries to invoke a view that is not authorized by the authorization policy. -- cgit v1.2.3 From 9c2373c3b32f587061eae387edf2678a4307897a Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Fri, 23 Nov 2018 17:13:04 -0600 Subject: simplify the tox setup, get rid of basepython warning --- tox.ini | 35 +++++++---------------------------- 1 file changed, 7 insertions(+), 28 deletions(-) diff --git a/tox.ini b/tox.ini index 0dae17e0f..33e3c2c81 100644 --- a/tox.ini +++ b/tox.ini @@ -1,25 +1,19 @@ [tox] envlist = lint, - py34,py35,py36,py37,py38,pypy3, + py34,py35,py36,py37,pypy3, docs,py36-cover,coverage, [testenv] -# Most of these are defaults but if you specify any you can't fall back -# to defaults for others. -basepython = - py34: python3.4 - py35: python3.5 - py36: python3.6 - py37: python3.7 - py38: python3.8 - pypy3: pypy3 - commands = - nosetests --with-xunit --xunit-file=nosetests-{envname}.xml {posargs:} - + cover: coverage run \ + {envbindir}/nosetests --with-xunit --xunit-file=nosetests-{envname}.xml {posargs:} extras = testing +deps = + cover: coverage +setenv = + COVERAGE_FILE=.coverage.{envname} [testenv:lint] skip_install = true @@ -52,24 +46,10 @@ commands = extras = docs -# we separate coverage into its own testenv because a) "last run wins" wrt -# cobertura jenkins reporting and b) pypy and jython can't handle any -# combination of versions of coverage and nosexcover that i can find. -[testenv:py36-cover] -basepython = python3.6 -commands = - coverage run {envbindir}/nosetests - coverage xml -o coverage-{envname}.xml -setenv = - COVERAGE_FILE=.coverage.{envname} -extras = - testing - [testenv:coverage] skip_install = true basepython = python3.6 commands = - coverage erase coverage combine coverage xml coverage report --fail-under=100 @@ -97,7 +77,6 @@ commands = python setup.py sdist --dist-dir {toxinidir}/dist # build wheel from sdist pip wheel -v --no-deps --no-index --no-build-isolation --wheel-dir {toxinidir}/dist --find-links {toxinidir}/dist pyramid - deps = setuptools wheel -- cgit v1.2.3 From 7d2124c28072bf63ce7ad566ad1e2e3d19c22b76 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Fri, 23 Nov 2018 15:47:54 -0800 Subject: Correct grammar, use shorter sentences, rewrap to one sentence per line, inline links, update outputs. --- docs/tutorials/wiki/installation.rst | 179 ++++++++++++++++++----------------- 1 file changed, 93 insertions(+), 86 deletions(-) diff --git a/docs/tutorials/wiki/installation.rst b/docs/tutorials/wiki/installation.rst index d0037e584..b89822e83 100644 --- a/docs/tutorials/wiki/installation.rst +++ b/docs/tutorials/wiki/installation.rst @@ -4,12 +4,13 @@ Installation ============ + Before you begin ---------------- -This tutorial assumes that you have already followed the steps in -:ref:`installing_chapter`, except **do not create a virtual environment or -install Pyramid**. Thereby you will satisfy the following requirements. +This tutorial assumes that you have already followed the steps in :ref:`installing_chapter`, except **do not create a virtual environment or +install Pyramid**. +Thereby you will satisfy the following requirements. * A Python interpreter is installed on your operating system. * You've satisfied the :ref:`requirements-for-installing-packages`. @@ -17,13 +18,17 @@ install Pyramid**. Thereby you will satisfy the following requirements. Install cookiecutter -------------------- -We will use a :term:`cookiecutter` to create a Python package project from a Python package project template. See `Cookiecutter Installation `_ for instructions. +We will use a :term:`cookiecutter` to create a Python package project from a Python package project template. +See `Cookiecutter Installation `_ for instructions. Generate a Pyramid project from a cookiecutter ---------------------------------------------- -We will create a Pyramid project in your home directory for Unix or at the root for Windows. It is assumed you know the path to where you installed ``cookiecutter``. Issue the following commands and override the defaults in the prompts as follows. +We will create a Pyramid project in your home directory for Unix or at the root for Windows. +It is assumed you know the path to where you installed ``cookiecutter``. +Issue the following commands and override the defaults in the prompts as follows. + On Unix ^^^^^^^ @@ -33,6 +38,7 @@ On Unix cd ~ cookiecutter gh:Pylons/pyramid-cookiecutter-starter --checkout master + On Windows ^^^^^^^^^^ @@ -41,30 +47,33 @@ On Windows cd \ cookiecutter gh:Pylons/pyramid-cookiecutter-starter --checkout master + On all operating systems ^^^^^^^^^^^^^^^^^^^^^^^^ If prompted for the first item, accept the default ``yes`` by hitting return. .. code-block:: text - You've cloned ~/.cookiecutters/pyramid-cookiecutter-theone before. - Is it okay to delete and re-clone it? [yes]: yes + You've downloaded ~/.cookiecutters/pyramid-cookiecutter-starter before. + Is it okay to delete and re-download it? [yes]: yes project_name [Pyramid Scaffold]: myproj repo_name [myproj]: tutorial Select template_language: 1 - jinja2 2 - chameleon 3 - mako - Choose from 1, 2, 3 [1]: 1 + Choose from 1, 2, 3 [1]: 2 Select backend: 1 - none 2 - sqlalchemy 3 - zodb Choose from 1, 2, 3 [1]: 3 + Change directory into your newly created project ------------------------------------------------ + On Unix ^^^^^^^ @@ -72,6 +81,7 @@ On Unix cd tutorial + On Windows ^^^^^^^^^^ @@ -85,6 +95,7 @@ Set and use a ``VENV`` environment variable We will set the ``VENV`` environment variable to the absolute path of the virtual environment, and use it going forward. + On Unix ^^^^^^^ @@ -92,6 +103,7 @@ On Unix export VENV=~/tutorial + On Windows ^^^^^^^^^^ @@ -103,6 +115,7 @@ On Windows Create a virtual environment ---------------------------- + On Unix ^^^^^^^ @@ -110,17 +123,10 @@ On Unix python3 -m venv $VENV + On Windows ^^^^^^^^^^ -Each version of Python uses different paths, so you might need to adjust the path to the command for your Python version. Recent versions of the Python 3 installer for Windows now install a Python launcher. - -Python 2.7: - -.. code-block:: doscon - - c:\Python27\Scripts\virtualenv %VENV% - Python 3.7: .. code-block:: doscon @@ -131,6 +137,7 @@ Python 3.7: Upgrade packaging tools in the virtual environment -------------------------------------------------- + On Unix ^^^^^^^ @@ -138,6 +145,7 @@ On Unix $VENV/bin/pip install --upgrade pip setuptools + On Windows ^^^^^^^^^^ @@ -151,7 +159,10 @@ On Windows Installing the project in development mode ------------------------------------------ -In order to do development on the project easily, you must "register" the project as a development egg in your workspace. We will install testing requirements at the same time. We do so with the following command. +In order to work on the project, you must "register" the project as a development egg in your workspace. +We will install testing requirements at the same time. +We do so with the following command. + On Unix ^^^^^^^ @@ -160,6 +171,7 @@ On Unix $VENV/bin/pip install -e ".[testing]" + On Windows ^^^^^^^^^^ @@ -167,6 +179,7 @@ On Windows %VENV%\Scripts\pip install -e ".[testing]" + On all operating systems ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -174,16 +187,18 @@ The console will show ``pip`` checking for packages and installing missing packa .. code-block:: bash - Successfully installed BTrees-4.3.1 Chameleon-3.0 Mako-1.0.6 \ - MarkupSafe-0.23 PasteDeploy-1.5.2 Pygments-2.1.3 WebOb-1.6.3 \ - WebTest-2.0.23 ZConfig-3.1.0 ZEO-5.0.4 ZODB-5.1.1 ZODB3-3.11.0 \ - beautifulsoup4-4.5.1 coverage-4.2 mock-2.0.0 pbr-1.10.0 persistent-4.2.2 \ - py-1.4.31 pyramid-1.7.3 pyramid-chameleon-0.3 pyramid-debugtoolbar-3.0.5 \ - pyramid-mako-1.0.2 pyramid-tm-1.1.1 pyramid-zodbconn-0.7 pytest-3.0.5 \ - pytest-cov-2.4.0 repoze.lru-0.6 six-1.10.0 transaction-2.0.3 \ - translationstring-1.3 tutorial venusian-1.0 waitress-1.0.1 \ - zc.lockfile-1.2.1 zdaemon-4.2.0 zodbpickle-0.6.0 zodburi-2.0 \ - zope.deprecation-4.2.0 zope.interface-4.3.3 + Successfully installed BTrees-4.5.1 Chameleon-3.5 Mako-1.0.7 \ + MarkupSafe-1.1.0 PasteDeploy-1.5.2 Pygments-2.2.0 WebTest-2.0.32 \ + ZConfig-3.3.0 ZEO-5.2.0 ZODB-5.5.1 ZODB3-3.11.0 atomicwrites-1.2.1 \ + attrs-18.2.0 beautifulsoup4-4.6.3 coverage-4.5.2 hupper-1.4.1 \ + more-itertools-4.3.0 persistent-4.4.3 plaster-1.0 plaster-pastedeploy-0.6 \ + pluggy-0.8.0 py-1.7.0 pyramid-1.10.1 pyramid-chameleon-0.3 \ + pyramid-debugtoolbar-4.5 pyramid-mako-1.0.2 pyramid-retry-1.0 \ + pyramid-tm-2.2.1 pyramid-zodbconn-0.8.1 pytest-4.0.0 pytest-cov-2.6.0 \ + repoze.lru-0.7 six-1.11.0 transaction-2.4.0 translationstring-1.3 \ + tutorial venusian-1.1.0 waitress-1.1.0 webob-1.8.4 zc.lockfile-1.4 \ + zdaemon-4.3 zodbpickle-1.0.2 zodburi-2.3.0 zope.deprecation-4.3.0 \ + zope.interface-4.6.0 Testing requirements are defined in our project's ``setup.py`` file, in the ``tests_require`` and ``extras_require`` stanzas. @@ -208,6 +223,7 @@ requirements, you may run the tests for the project. The following commands provide options to ``pytest`` that specify the module for which its tests shall be run, and to run ``pytest`` in quiet mode. + On Unix ^^^^^^^ @@ -215,6 +231,7 @@ On Unix $VENV/bin/pytest -q + On Windows ^^^^^^^^^^ @@ -233,13 +250,11 @@ For a successful test run, you should see output that ends like this: Expose test coverage information -------------------------------- -You can run the ``pytest`` command to see test coverage information. This -runs the tests in the same way that ``pytest`` does, but provides additional -:term:`coverage` information, exposing which lines of your project are covered by the -tests. +You can run the ``pytest`` command to see test coverage information. +This runs the tests in the same way that ``pytest`` does, but provides additional :term:`coverage` information, exposing which lines of your project are covered by the tests. + +We've already installed the ``pytest-cov`` package into our virtual environment, so we can run the tests with coverage. -We've already installed the ``pytest-cov`` package into our virtual -environment, so we can run the tests with coverage. On Unix ^^^^^^^ @@ -248,6 +263,7 @@ On Unix $VENV/bin/pytest --cov --cov-report=term-missing + On Windows ^^^^^^^^^^ @@ -259,23 +275,30 @@ If successful, you will see output something like this: .. code-block:: bash - ======================== test session starts ======================== - platform Python 3.6.0, pytest-3.0.5, py-1.4.31, pluggy-0.4.0 - rootdir: /Users/stevepiercy/tutorial, inifile: - plugins: cov-2.4.0 - collected 1 items + ======================== test session starts ========================= + platform darwin -- Python 3.7.0, pytest-4.0.0, py-1.7.0, pluggy-0.8.0 + rootdir: /Users/stevepiercy/projects/hack-on-pyramid/tutorial, inifile: pytest.ini + plugins: cov-2.6.0 + collected 1 item tutorial/tests.py . - ------------------ coverage: platform Python 3.6.0 ------------------ - Name Stmts Miss Cover Missing - ------------------------------------------------------- - tutorial/__init__.py 14 9 36% 7-8, 14-20 - tutorial/models.py 10 6 40% 9-14 - tutorial/views.py 4 0 100% - ------------------------------------------------------- - TOTAL 28 15 46% + [100%] + + ---------- coverage: platform darwin, python 3.7.0-final-0 ----------- + Name Stmts Miss Cover Missing + ----------------------------------------------------------- + tutorial/__init__.py 17 12 29% 7-8, 14-23 + tutorial/models/__init__.py 8 4 50% 9-12 + tutorial/pshell.py 6 6 0% 1-12 + tutorial/routes.py 2 2 0% 1-2 + tutorial/views/__init__.py 0 0 100% + tutorial/views/default.py 4 0 100% + tutorial/views/notfound.py 4 4 0% 1-7 + ----------------------------------------------------------- + TOTAL 41 28 32% + - ===================== 1 passed in 0.31 seconds ====================== + ===================== 1 passed in 0.31 seconds ======================= Our package doesn't quite have 100% test coverage. @@ -285,11 +308,10 @@ Our package doesn't quite have 100% test coverage. Test and coverage cookiecutter defaults --------------------------------------- -The Pyramid cookiecutter includes configuration defaults for ``pytest`` and -test coverage. These configuration files are ``pytest.ini`` and -``.coveragerc``, located at the root of your package. Without these defaults, -we would need to specify the path to the module on which we want to run tests -and coverage. +The Pyramid cookiecutter includes configuration defaults for ``pytest`` and test coverage. +These configuration files are ``pytest.ini`` and ``.coveragerc``, located at the root of your package. +Without these defaults, we would need to specify the path to the module on which we want to run tests and coverage. + On Unix ^^^^^^^ @@ -305,13 +327,11 @@ On Windows %VENV%\Scripts\pytest --cov=tutorial tutorial\tests.py -q -``pytest`` follows :ref:`conventions for Python test discovery -`, and the configuration defaults from the cookiecutter -tell ``pytest`` where to find the module on which we want to run tests and -coverage. -.. seealso:: See ``pytest``'s documentation for :ref:`pytest:usage` or invoke - ``pytest -h`` to see its full set of options. +``pytest`` follows :ref:`conventions for Python test discovery `. +The configuration defaults from the cookiecutter tell ``pytest`` where to find the module on which we want to run tests and coverage. + +.. seealso:: See ``pytest``'s documentation for :ref:`pytest:usage` or invoke ``pytest -h`` to see its full set of options. .. _wiki-start-the-application: @@ -319,8 +339,9 @@ coverage. Start the application --------------------- -Start the application. See :ref:`what_is_this_pserve_thing` for more -information on ``pserve``. +Start the application. +See :ref:`what_is_this_pserve_thing` for more information on ``pserve``. + On Unix ^^^^^^^ @@ -329,6 +350,7 @@ On Unix $VENV/bin/pserve development.ini --reload + On Windows ^^^^^^^^^^ @@ -336,10 +358,10 @@ On Windows %VENV%\Scripts\pserve development.ini --reload + .. note:: - Your OS firewall, if any, may pop up a dialog asking for authorization - to allow python to accept incoming network connections. + Your OS firewall, if any, may pop up a dialog asking for authorization to allow python to accept incoming network connections. If successful, you will see something like this on your console: @@ -356,13 +378,12 @@ This means the server is ready to accept requests. Visit the application in a browser ---------------------------------- -In a browser, visit http://localhost:6543/. You will see the generated -application's default page. +In a browser, visit http://localhost:6543/. +You will see the generated application's default page. -One thing you'll notice is the "debug toolbar" icon on right hand side of the -page. You can read more about the purpose of the icon at -:ref:`debug_toolbar`. It allows you to get information about your -application while you develop. +One thing you'll notice is the "debug toolbar" icon on right hand side of the page. +You can read more about the purpose of the icon at :ref:`debug_toolbar`. +It allows you to get information about your application while you develop. Decisions the cookiecutter backend option ``zodb`` has made for you @@ -374,24 +395,10 @@ When creating a project and selecting the backend option of ``zodb``, the cookie - You are willing to use :term:`traversal` to map URLs to code. -- You want to use pyramid_zodbconn_, pyramid_tm_, and the transaction_ packages - to manage connections and transactions with :term:`ZODB`. +- You want to use `pyramid_zodbconn `_, `pyramid_tm `_, and the `transaction `_ packages to manage connections and transactions with :term:`ZODB`. .. note:: - :app:`Pyramid` supports any persistent storage mechanism (e.g., an SQL - database or filesystem files). It also supports an additional mechanism to - map URLs to code (:term:`URL dispatch`). However, for the purposes of this - tutorial, we'll only be using :term:`traversal` and :term:`ZODB`. - -.. _pyramid_chameleon: - https://docs.pylonsproject.org/projects/pyramid-chameleon/en/latest/ - -.. _pyramid_tm: - https://docs.pylonsproject.org/projects/pyramid-tm/en/latest/ - -.. _pyramid_zodbconn: - https://docs.pylonsproject.org/projects/pyramid-zodbconn/en/latest/ - -.. _transaction: - https://zodb.readthedocs.io/en/latest/transactions.html + :app:`Pyramid` supports any persistent storage mechanism (e.g., an SQL database or filesystem files). + It also supports an additional mechanism to map URLs to code (:term:`URL dispatch`). + However, for the purposes of this tutorial, we will only use :term:`traversal` and :term:`ZODB`. -- cgit v1.2.3 From 638bb50af2847ce309dc29d8054aabe138cb6fc1 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Fri, 23 Nov 2018 15:53:51 -0800 Subject: remove extra whitespace --- docs/tutorials/wiki/installation.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/tutorials/wiki/installation.rst b/docs/tutorials/wiki/installation.rst index b89822e83..37e3498b2 100644 --- a/docs/tutorials/wiki/installation.rst +++ b/docs/tutorials/wiki/installation.rst @@ -392,9 +392,7 @@ Decisions the cookiecutter backend option ``zodb`` has made for you When creating a project and selecting the backend option of ``zodb``, the cookiecutter makes the following assumptions: - You are willing to use :term:`ZODB` for persistent storage. - - You are willing to use :term:`traversal` to map URLs to code. - - You want to use `pyramid_zodbconn `_, `pyramid_tm `_, and the `transaction `_ packages to manage connections and transactions with :term:`ZODB`. .. note:: -- cgit v1.2.3 From 76ada71ec1d28d8f38f15dcffa311180d99d2daa Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Fri, 23 Nov 2018 16:59:32 -0800 Subject: synch up source files with actual cookiecutter results --- docs/tutorials/wiki/src/installation/MANIFEST.in | 2 +- docs/tutorials/wiki/src/installation/pytest.ini | 2 +- docs/tutorials/wiki/src/installation/setup.py | 4 +- .../wiki/src/installation/tutorial/__init__.py | 8 +-- .../wiki/src/installation/tutorial/models.py | 12 ---- .../wiki/src/installation/tutorial/pshell.py | 1 + .../installation/tutorial/templates/mytemplate.pt | 70 +++------------------- .../wiki/src/installation/tutorial/tests.py | 3 +- .../wiki/src/installation/tutorial/views.py | 7 --- 9 files changed, 19 insertions(+), 90 deletions(-) delete mode 100644 docs/tutorials/wiki/src/installation/tutorial/models.py delete mode 100644 docs/tutorials/wiki/src/installation/tutorial/views.py diff --git a/docs/tutorials/wiki/src/installation/MANIFEST.in b/docs/tutorials/wiki/src/installation/MANIFEST.in index 81beba1b1..05cc195d9 100644 --- a/docs/tutorials/wiki/src/installation/MANIFEST.in +++ b/docs/tutorials/wiki/src/installation/MANIFEST.in @@ -1,2 +1,2 @@ include *.txt *.ini *.cfg *.rst -recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml +recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2 diff --git a/docs/tutorials/wiki/src/installation/pytest.ini b/docs/tutorials/wiki/src/installation/pytest.ini index 8b76bc410..a3489cdf8 100644 --- a/docs/tutorials/wiki/src/installation/pytest.ini +++ b/docs/tutorials/wiki/src/installation/pytest.ini @@ -1,3 +1,3 @@ [pytest] testpaths = tutorial -python_files = *.py +python_files = test*.py diff --git a/docs/tutorials/wiki/src/installation/setup.py b/docs/tutorials/wiki/src/installation/setup.py index a4f143d24..d6d488ed2 100644 --- a/docs/tutorials/wiki/src/installation/setup.py +++ b/docs/tutorials/wiki/src/installation/setup.py @@ -10,15 +10,15 @@ with open(os.path.join(here, 'CHANGES.txt')) as f: requires = [ 'plaster_pastedeploy', - 'pyramid >= 1.9a', + 'pyramid', 'pyramid_chameleon', 'pyramid_debugtoolbar', + 'waitress', 'pyramid_retry', 'pyramid_tm', 'pyramid_zodbconn', 'transaction', 'ZODB3', - 'waitress', ] tests_require = [ diff --git a/docs/tutorials/wiki/src/installation/tutorial/__init__.py b/docs/tutorials/wiki/src/installation/tutorial/__init__.py index f2b3c9568..bd0c71f5b 100644 --- a/docs/tutorials/wiki/src/installation/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/installation/tutorial/__init__.py @@ -11,13 +11,13 @@ def root_factory(request): def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ - settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' with Configurator(settings=settings) as config: - config.include('pyramid_chameleon') + settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' config.include('pyramid_tm') config.include('pyramid_retry') config.include('pyramid_zodbconn') config.set_root_factory(root_factory) - config.add_static_view('static', 'static', cache_max_age=3600) + config.include('pyramid_chameleon') + config.include('.routes') config.scan() - return config.make_wsgi_app() + return config.make_wsgi_app() diff --git a/docs/tutorials/wiki/src/installation/tutorial/models.py b/docs/tutorials/wiki/src/installation/tutorial/models.py deleted file mode 100644 index aca6a4129..000000000 --- a/docs/tutorials/wiki/src/installation/tutorial/models.py +++ /dev/null @@ -1,12 +0,0 @@ -from persistent.mapping import PersistentMapping - - -class MyModel(PersistentMapping): - __parent__ = __name__ = None - - -def appmaker(zodb_root): - if 'app_root' not in zodb_root: - app_root = MyModel() - zodb_root['app_root'] = app_root - return zodb_root['app_root'] diff --git a/docs/tutorials/wiki/src/installation/tutorial/pshell.py b/docs/tutorials/wiki/src/installation/tutorial/pshell.py index 3d026291b..a7cfa6a27 100644 --- a/docs/tutorials/wiki/src/installation/tutorial/pshell.py +++ b/docs/tutorials/wiki/src/installation/tutorial/pshell.py @@ -1,5 +1,6 @@ from . import models + def setup(env): request = env['request'] diff --git a/docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt index d63ea8c45..adac4fe35 100644 --- a/docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt @@ -1,65 +1,11 @@ - - - - - - - - - +
+
- Cookiecutter ZODB project for the Pyramid Web Framework - - - - - - - - - - - - - -
-
-
-
- -
-
-
-

Pyramid ZODB Project

-

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

-
-
+
+

Pyramid Starter project

+

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

- -
- -
-
-
- - - - - - - +
+
diff --git a/docs/tutorials/wiki/src/installation/tutorial/tests.py b/docs/tutorials/wiki/src/installation/tutorial/tests.py index ca7a47279..6279d9f66 100644 --- a/docs/tutorials/wiki/src/installation/tutorial/tests.py +++ b/docs/tutorials/wiki/src/installation/tutorial/tests.py @@ -11,7 +11,8 @@ class ViewTests(unittest.TestCase): testing.tearDown() def test_my_view(self): - from .views import my_view + from .views.default import my_view request = testing.DummyRequest() info = my_view(request) self.assertEqual(info['project'], 'myproj') + diff --git a/docs/tutorials/wiki/src/installation/tutorial/views.py b/docs/tutorials/wiki/src/installation/tutorial/views.py deleted file mode 100644 index c1878bdd0..000000000 --- a/docs/tutorials/wiki/src/installation/tutorial/views.py +++ /dev/null @@ -1,7 +0,0 @@ -from pyramid.view import view_config -from .models import MyModel - - -@view_config(context=MyModel, renderer='templates/mytemplate.pt') -def my_view(request): - return {'project': 'myproj'} -- cgit v1.2.3 From f326582294868e5529320b532738c8762cf816c5 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Fri, 23 Nov 2018 17:00:31 -0800 Subject: Add files from cookiecutter --- .../src/installation/tutorial/models/__init__.py | 12 +++++ .../wiki/src/installation/tutorial/routes.py | 2 + .../src/installation/tutorial/templates/404.pt | 10 ++++ .../src/installation/tutorial/templates/layout.pt | 62 ++++++++++++++++++++++ .../src/installation/tutorial/views/__init__.py | 0 .../src/installation/tutorial/views/default.py | 8 +++ .../src/installation/tutorial/views/notfound.py | 7 +++ 7 files changed, 101 insertions(+) create mode 100644 docs/tutorials/wiki/src/installation/tutorial/models/__init__.py create mode 100644 docs/tutorials/wiki/src/installation/tutorial/routes.py create mode 100644 docs/tutorials/wiki/src/installation/tutorial/templates/404.pt create mode 100644 docs/tutorials/wiki/src/installation/tutorial/templates/layout.pt create mode 100644 docs/tutorials/wiki/src/installation/tutorial/views/__init__.py create mode 100644 docs/tutorials/wiki/src/installation/tutorial/views/default.py create mode 100644 docs/tutorials/wiki/src/installation/tutorial/views/notfound.py diff --git a/docs/tutorials/wiki/src/installation/tutorial/models/__init__.py b/docs/tutorials/wiki/src/installation/tutorial/models/__init__.py new file mode 100644 index 000000000..aca6a4129 --- /dev/null +++ b/docs/tutorials/wiki/src/installation/tutorial/models/__init__.py @@ -0,0 +1,12 @@ +from persistent.mapping import PersistentMapping + + +class MyModel(PersistentMapping): + __parent__ = __name__ = None + + +def appmaker(zodb_root): + if 'app_root' not in zodb_root: + app_root = MyModel() + zodb_root['app_root'] = app_root + return zodb_root['app_root'] diff --git a/docs/tutorials/wiki/src/installation/tutorial/routes.py b/docs/tutorials/wiki/src/installation/tutorial/routes.py new file mode 100644 index 000000000..3c0a37992 --- /dev/null +++ b/docs/tutorials/wiki/src/installation/tutorial/routes.py @@ -0,0 +1,2 @@ +def includeme(config): + config.add_static_view('static', 'static', cache_max_age=3600) diff --git a/docs/tutorials/wiki/src/installation/tutorial/templates/404.pt b/docs/tutorials/wiki/src/installation/tutorial/templates/404.pt new file mode 100644 index 000000000..07298940c --- /dev/null +++ b/docs/tutorials/wiki/src/installation/tutorial/templates/404.pt @@ -0,0 +1,10 @@ +
+
+ +
+

Pyramid Starter project

+

404 Page Not Found

+
+ +
+
diff --git a/docs/tutorials/wiki/src/installation/tutorial/templates/layout.pt b/docs/tutorials/wiki/src/installation/tutorial/templates/layout.pt new file mode 100644 index 000000000..9fdaef00f --- /dev/null +++ b/docs/tutorials/wiki/src/installation/tutorial/templates/layout.pt @@ -0,0 +1,62 @@ + + + + + + + + + + + Cookiecutter Starter project for the Pyramid Web Framework + + + + + + + + + + + + + +
+
+
+
+ +
+
+
No content
+
+
+ +
+ +
+
+
+ + + + + + + + diff --git a/docs/tutorials/wiki/src/installation/tutorial/views/__init__.py b/docs/tutorials/wiki/src/installation/tutorial/views/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/docs/tutorials/wiki/src/installation/tutorial/views/default.py b/docs/tutorials/wiki/src/installation/tutorial/views/default.py new file mode 100644 index 000000000..5d708d15c --- /dev/null +++ b/docs/tutorials/wiki/src/installation/tutorial/views/default.py @@ -0,0 +1,8 @@ +from pyramid.view import view_config + +from ..models import MyModel + + +@view_config(context=MyModel, renderer='../templates/mytemplate.pt') +def my_view(request): + return {'project': 'myproj'} diff --git a/docs/tutorials/wiki/src/installation/tutorial/views/notfound.py b/docs/tutorials/wiki/src/installation/tutorial/views/notfound.py new file mode 100644 index 000000000..728791d0a --- /dev/null +++ b/docs/tutorials/wiki/src/installation/tutorial/views/notfound.py @@ -0,0 +1,7 @@ +from pyramid.view import notfound_view_config + + +@notfound_view_config(renderer='../templates/404.pt') +def notfound_view(request): + request.response.status = 404 + return {} -- cgit v1.2.3 From feedf4cc38a59ee3f83c8a01b4632f1df6b7d645 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Fri, 23 Nov 2018 19:57:58 -0600 Subject: no longer need to exclude scaffolds from black --- pyproject.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b30e4f465..d8ec559df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,9 +15,6 @@ exclude = ''' | dist | build | docs - | src/pyramid/scaffolds/alchemy - | src/pyramid/scaffolds/starter - | src/pyramid/scaffolds/zodb )/ ''' -- cgit v1.2.3 From 0c4e11c4a64121309cda3b728eca9a6ad9d9959d Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Fri, 23 Nov 2018 17:59:34 -0800 Subject: Synch up src files --- docs/tutorials/wiki/src/basiclayout/MANIFEST.in | 2 +- docs/tutorials/wiki/src/basiclayout/pytest.ini | 2 +- docs/tutorials/wiki/src/basiclayout/setup.py | 4 +- .../wiki/src/basiclayout/tutorial/__init__.py | 8 +-- .../wiki/src/basiclayout/tutorial/models.py | 12 ---- .../src/basiclayout/tutorial/models/__init__.py | 12 ++++ .../wiki/src/basiclayout/tutorial/pshell.py | 1 + .../wiki/src/basiclayout/tutorial/routes.py | 2 + .../wiki/src/basiclayout/tutorial/templates/404.pt | 10 ++++ .../src/basiclayout/tutorial/templates/layout.pt | 62 +++++++++++++++++++ .../basiclayout/tutorial/templates/mytemplate.pt | 70 +++------------------- .../wiki/src/basiclayout/tutorial/tests.py | 3 +- .../wiki/src/basiclayout/tutorial/views.py | 7 --- .../src/basiclayout/tutorial/views/__init__.py | 0 .../wiki/src/basiclayout/tutorial/views/default.py | 8 +++ .../src/basiclayout/tutorial/views/notfound.py | 7 +++ 16 files changed, 120 insertions(+), 90 deletions(-) delete mode 100644 docs/tutorials/wiki/src/basiclayout/tutorial/models.py create mode 100644 docs/tutorials/wiki/src/basiclayout/tutorial/models/__init__.py create mode 100644 docs/tutorials/wiki/src/basiclayout/tutorial/routes.py create mode 100644 docs/tutorials/wiki/src/basiclayout/tutorial/templates/404.pt create mode 100644 docs/tutorials/wiki/src/basiclayout/tutorial/templates/layout.pt delete mode 100644 docs/tutorials/wiki/src/basiclayout/tutorial/views.py create mode 100644 docs/tutorials/wiki/src/basiclayout/tutorial/views/__init__.py create mode 100644 docs/tutorials/wiki/src/basiclayout/tutorial/views/default.py create mode 100644 docs/tutorials/wiki/src/basiclayout/tutorial/views/notfound.py diff --git a/docs/tutorials/wiki/src/basiclayout/MANIFEST.in b/docs/tutorials/wiki/src/basiclayout/MANIFEST.in index 81beba1b1..05cc195d9 100644 --- a/docs/tutorials/wiki/src/basiclayout/MANIFEST.in +++ b/docs/tutorials/wiki/src/basiclayout/MANIFEST.in @@ -1,2 +1,2 @@ include *.txt *.ini *.cfg *.rst -recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml +recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2 diff --git a/docs/tutorials/wiki/src/basiclayout/pytest.ini b/docs/tutorials/wiki/src/basiclayout/pytest.ini index 8b76bc410..a3489cdf8 100644 --- a/docs/tutorials/wiki/src/basiclayout/pytest.ini +++ b/docs/tutorials/wiki/src/basiclayout/pytest.ini @@ -1,3 +1,3 @@ [pytest] testpaths = tutorial -python_files = *.py +python_files = test*.py diff --git a/docs/tutorials/wiki/src/basiclayout/setup.py b/docs/tutorials/wiki/src/basiclayout/setup.py index a4f143d24..d6d488ed2 100644 --- a/docs/tutorials/wiki/src/basiclayout/setup.py +++ b/docs/tutorials/wiki/src/basiclayout/setup.py @@ -10,15 +10,15 @@ with open(os.path.join(here, 'CHANGES.txt')) as f: requires = [ 'plaster_pastedeploy', - 'pyramid >= 1.9a', + 'pyramid', 'pyramid_chameleon', 'pyramid_debugtoolbar', + 'waitress', 'pyramid_retry', 'pyramid_tm', 'pyramid_zodbconn', 'transaction', 'ZODB3', - 'waitress', ] tests_require = [ diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py b/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py index f2b3c9568..bd0c71f5b 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py @@ -11,13 +11,13 @@ def root_factory(request): def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ - settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' with Configurator(settings=settings) as config: - config.include('pyramid_chameleon') + settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' config.include('pyramid_tm') config.include('pyramid_retry') config.include('pyramid_zodbconn') config.set_root_factory(root_factory) - config.add_static_view('static', 'static', cache_max_age=3600) + config.include('pyramid_chameleon') + config.include('.routes') config.scan() - return config.make_wsgi_app() + return config.make_wsgi_app() diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/models.py b/docs/tutorials/wiki/src/basiclayout/tutorial/models.py deleted file mode 100644 index aca6a4129..000000000 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/models.py +++ /dev/null @@ -1,12 +0,0 @@ -from persistent.mapping import PersistentMapping - - -class MyModel(PersistentMapping): - __parent__ = __name__ = None - - -def appmaker(zodb_root): - if 'app_root' not in zodb_root: - app_root = MyModel() - zodb_root['app_root'] = app_root - return zodb_root['app_root'] diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/models/__init__.py b/docs/tutorials/wiki/src/basiclayout/tutorial/models/__init__.py new file mode 100644 index 000000000..aca6a4129 --- /dev/null +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/models/__init__.py @@ -0,0 +1,12 @@ +from persistent.mapping import PersistentMapping + + +class MyModel(PersistentMapping): + __parent__ = __name__ = None + + +def appmaker(zodb_root): + if 'app_root' not in zodb_root: + app_root = MyModel() + zodb_root['app_root'] = app_root + return zodb_root['app_root'] diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/pshell.py b/docs/tutorials/wiki/src/basiclayout/tutorial/pshell.py index 3d026291b..a7cfa6a27 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/pshell.py +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/pshell.py @@ -1,5 +1,6 @@ from . import models + def setup(env): request = env['request'] diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/routes.py b/docs/tutorials/wiki/src/basiclayout/tutorial/routes.py new file mode 100644 index 000000000..3c0a37992 --- /dev/null +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/routes.py @@ -0,0 +1,2 @@ +def includeme(config): + config.add_static_view('static', 'static', cache_max_age=3600) diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/404.pt b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/404.pt new file mode 100644 index 000000000..07298940c --- /dev/null +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/404.pt @@ -0,0 +1,10 @@ +
+
+ +
+

Pyramid Starter project

+

404 Page Not Found

+
+ +
+
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/layout.pt b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/layout.pt new file mode 100644 index 000000000..9fdaef00f --- /dev/null +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/layout.pt @@ -0,0 +1,62 @@ + + + + + + + + + + + Cookiecutter Starter project for the Pyramid Web Framework + + + + + + + + + + + + + +
+
+
+
+ +
+
+
No content
+
+
+ +
+ +
+
+
+ + + + + + + + diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt index d63ea8c45..adac4fe35 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt @@ -1,65 +1,11 @@ - - - - - - - - - +
+
- Cookiecutter ZODB project for the Pyramid Web Framework - - - - - - - - - - - - - -
-
-
-
- -
-
-
-

Pyramid ZODB Project

-

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

-
-
+
+

Pyramid Starter project

+

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

- -
- -
-
-
- - - - - - - +
+
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py b/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py index ca7a47279..6279d9f66 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py @@ -11,7 +11,8 @@ class ViewTests(unittest.TestCase): testing.tearDown() def test_my_view(self): - from .views import my_view + from .views.default import my_view request = testing.DummyRequest() info = my_view(request) self.assertEqual(info['project'], 'myproj') + diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/views.py b/docs/tutorials/wiki/src/basiclayout/tutorial/views.py deleted file mode 100644 index c1878bdd0..000000000 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/views.py +++ /dev/null @@ -1,7 +0,0 @@ -from pyramid.view import view_config -from .models import MyModel - - -@view_config(context=MyModel, renderer='templates/mytemplate.pt') -def my_view(request): - return {'project': 'myproj'} diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/views/__init__.py b/docs/tutorials/wiki/src/basiclayout/tutorial/views/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/views/default.py b/docs/tutorials/wiki/src/basiclayout/tutorial/views/default.py new file mode 100644 index 000000000..5d708d15c --- /dev/null +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/views/default.py @@ -0,0 +1,8 @@ +from pyramid.view import view_config + +from ..models import MyModel + + +@view_config(context=MyModel, renderer='../templates/mytemplate.pt') +def my_view(request): + return {'project': 'myproj'} diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/views/notfound.py b/docs/tutorials/wiki/src/basiclayout/tutorial/views/notfound.py new file mode 100644 index 000000000..728791d0a --- /dev/null +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/views/notfound.py @@ -0,0 +1,7 @@ +from pyramid.view import notfound_view_config + + +@notfound_view_config(renderer='../templates/404.pt') +def notfound_view(request): + request.response.status = 404 + return {} -- cgit v1.2.3 From 29d6b3b030b0a0a60415dd3fe7e39c7c9b28b189 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Fri, 23 Nov 2018 18:00:45 -0800 Subject: Clean up application configuration in __init__.py --- docs/tutorials/wiki/basiclayout.rst | 105 ++++++++++++++++++------------------ 1 file changed, 53 insertions(+), 52 deletions(-) diff --git a/docs/tutorials/wiki/basiclayout.rst b/docs/tutorials/wiki/basiclayout.rst index 49ee6902e..f2c6dbb37 100644 --- a/docs/tutorials/wiki/basiclayout.rst +++ b/docs/tutorials/wiki/basiclayout.rst @@ -4,80 +4,81 @@ Basic Layout ============ -The starter files generated by selecting the ``zodb`` backend in the -cookiecutter are very basic, but they provide a good orientation for the -high-level patterns common to most :term:`traversal`-based (and -:term:`ZODB`-based) :app:`Pyramid` projects. +The starter files generated by the cookiecutter are very basic, but they provide a good orientation for the high-level patterns common to most :term:`traversal`-based (and :term:`ZODB`-based) :app:`Pyramid` projects. Application configuration with ``__init__.py`` ---------------------------------------------- -A directory on disk can be turned into a Python :term:`package` by containing -an ``__init__.py`` file. Even if empty, this marks a directory as a Python -package. We use ``__init__.py`` both as a marker, indicating the directory in -which it's contained is a package, and to contain application configuration -code. +A directory on disk can be turned into a Python :term:`package` by containing an ``__init__.py`` file. +Even if empty, this marks a directory as a Python package. +We use ``__init__.py`` both as a marker, indicating the directory in which it is contained is a package, and to contain application configuration code. -When you run the application using the ``pserve`` command using the -``development.ini`` generated configuration file, the application -configuration points at a :term:`Setuptools` :term:`entry point` described as -``egg:tutorial``. In our application, because the application's ``setup.py`` -file says so, this entry point happens to be the ``main`` function within the -file named ``__init__.py``. +When you run the application using the ``pserve`` command using the ``development.ini`` generated configuration file, the application configuration points at a :term:`Setuptools` :term:`entry point` described as ``egg:tutorial``. +In our application, because the application's ``setup.py`` file says so, this entry point happens to be the ``main`` function within the file named ``__init__.py``. -Open ``tutorial/__init__.py``. It should already contain the following: +Open ``tutorial/__init__.py``. +It should already contain the following: .. literalinclude:: src/basiclayout/tutorial/__init__.py - :linenos: - :language: py + :linenos: + :language: py + +#. *Lines 1-3*. + Perform some dependency imports. + +#. *Lines 6-8*. + Define a :term:`root factory` for our Pyramid application. + +#. *Line 11*. + ``__init__.py`` defines a function named ``main``. -#. *Lines 1-3*. Perform some dependency imports. +#. *Line 14*. + Construct a :term:`Configurator` as a :term:`context manager` with the ``settings`` keyword parsed by :term:`PasteDeploy`. See :term:`Deployment settings`. -#. *Lines 6-8*. Define a :term:`root factory` for our Pyramid application. +#. *Line 15*. + Use an explicit transaction manager for apps so that they do not implicitly create new transactions when touching the manager outside of the ``pyramid_tm`` lifecycle. -#. *Line 11*. ``__init__.py`` defines a function named ``main``. +#. *Line 16*. + Include support for ``pyramid_tm``, allowing Pyramid requests to join the active transaction as provided by the `transaction `_ package. -#. *Line 14*. Use an explicit transaction manager for apps so that they do not implicitly create new transactions when touching the manager outside of the ``pyramid_tm`` lifecycle. +#. *Line 17*. + Include support for ``pyramid_retry`` to retry a request when transient exceptions occur. -#. *Line 15*. Construct a :term:`Configurator` as a :term:`context manager` with the settings keyword parsed by :term:`PasteDeploy`. +#. *Line 18*. + Include support for ``pyramid_zodbconn``, providing integration between :term:`ZODB` and a Pyramid application. -#. *Line 16*. Include support for the :term:`Chameleon` template rendering - bindings, allowing us to use the ``.pt`` templates. +#. *Line 19*. + Set a root factory using our function named ``root_factory``. -#. *Line 17*. Include support for ``pyramid_tm``, allowing Pyramid requests to join the active transaction as provided by the `transaction `_ package. +#. *Line 20*. + Include support for the :term:`Chameleon` template rendering bindings, allowing us to use the ``.pt`` templates. -#. *Line 18*. Include support for ``pyramid_retry`` to retry a request when transient exceptions occur. +#. *Line 21*. + Include routes from the ``.routes`` module. + This registers a "static view" using the :meth:`pyramid.config.Configurator.add_static_view` method. + This view answers requests whose URL path starts with ``/static``. + This statement registers a view that will serve up static assets, such as CSS and image files. + In this case the URL will answer requests at ``http://localhost:6543/static/`` and below. -#. *Line 19*. Include support for ``pyramid_zodbconn``, providing integration between :term:`ZODB` and a Pyramid application. + The first argument is the "name" ``static``, which indicates that the URL path prefix of the view will be ``/static``. -#. *Line 20*. Set a root factory using our function named ``root_factory``. + The second argument of this method is the "path". + It is a relative :term:`asset specification`. + It finds the resources it should serve within the ``static`` directory inside the ``tutorial`` package. + Alternatively the cookiecutter could have used an *absolute* asset specification as the path (``tutorial:static``). -#. *Line 21*. Register a "static view", which answers requests whose URL - paths start with ``/static``, using the - :meth:`pyramid.config.Configurator.add_static_view` method. This - statement registers a view that will serve up static assets, such as CSS - and image files, for us, in this case, at - ``http://localhost:6543/static/`` and below. The first argument is the - "name" ``static``, which indicates that the URL path prefix of the view - will be ``/static``. The second argument of this tag is the "path", - which is a relative :term:`asset specification`, so it finds the resources - it should serve within the ``static`` directory inside the ``tutorial`` - package. Alternatively the cookiecutter could have used an *absolute* asset - specification as the path (``tutorial:static``). + The third argument is an optional ``cache_max_age`` which specifies the number of seconds the static asset will be HTTP-cached. -#. *Line 22*. Perform a :term:`scan`. A scan will find :term:`configuration - decoration`, such as view configuration decorators (e.g., ``@view_config``) - in the source code of the ``tutorial`` package and will take actions based - on these decorators. We don't pass any arguments to - :meth:`~pyramid.config.Configurator.scan`, which implies that the scan - should take place in the current package (in this case, ``tutorial``). - The cookiecutter could have equivalently said ``config.scan('tutorial')``, but - it chose to omit the package name argument. +#. *Line 22*. + Perform a :term:`scan`. + A scan will find :term:`configuration decoration`, such as view configuration decorators (e.g., ``@view_config``) in the source code of the ``tutorial`` package. + It will take actions based on these decorators. + We don't pass any arguments to :meth:`~pyramid.config.Configurator.scan`, which implies that the scan should take place in the current package (in this case, ``tutorial``). + The cookiecutter could have equivalently said ``config.scan('tutorial')``, but it chose to omit the package name argument. -#. *Line 23*. Use the - :meth:`pyramid.config.Configurator.make_wsgi_app` method - to return a :term:`WSGI` application. +#. *Line 23*. + Use the :meth:`pyramid.config.Configurator.make_wsgi_app` method to return a :term:`WSGI` application. Resources and models with ``models.py`` --------------------------------------- -- cgit v1.2.3 From 9c1091c692c7c05ae9d6c1b8f9a8f83720ed8401 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Fri, 23 Nov 2018 18:01:15 -0800 Subject: Clean up models, now in a package --- docs/tutorials/wiki/basiclayout.rst | 53 +++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/docs/tutorials/wiki/basiclayout.rst b/docs/tutorials/wiki/basiclayout.rst index f2c6dbb37..83225c6e1 100644 --- a/docs/tutorials/wiki/basiclayout.rst +++ b/docs/tutorials/wiki/basiclayout.rst @@ -80,42 +80,37 @@ It should already contain the following: #. *Line 23*. Use the :meth:`pyramid.config.Configurator.make_wsgi_app` method to return a :term:`WSGI` application. -Resources and models with ``models.py`` ---------------------------------------- - -:app:`Pyramid` uses the word :term:`resource` to describe objects arranged -hierarchically in a :term:`resource tree`. This tree is consulted by -:term:`traversal` to map URLs to code. In this application, the resource -tree represents the site structure, but it *also* represents the -:term:`domain model` of the application, because each resource is a node -stored persistently in a :term:`ZODB` database. The ``models.py`` file is -where the ``zodb`` cookiecutter put the classes that implement our -resource objects, each of which also happens to be a domain model object. + +Resources and models with ``models`` package +-------------------------------------------- + +:app:`Pyramid` uses the word :term:`resource` to describe objects arranged hierarchically in a :term:`resource tree`. +This tree is consulted by :term:`traversal` to map URLs to code. +In this application, the resource tree represents the site structure, but it *also* represents the :term:`domain model` of the application. +Each resource is a node stored persistently in a :term:`ZODB` database. +The ``models.py`` file is where the ``zodb`` cookiecutter put the classes that implement our resource objects, each of which also happens to be a domain model object. Here is the source for ``models.py``: -.. literalinclude:: src/basiclayout/tutorial/models.py +.. literalinclude:: src/basiclayout/tutorial/models/__init__.py :linenos: :language: python -#. *Lines 4-5*. The ``MyModel`` :term:`resource` class is implemented here. - Instances of this class are capable of being persisted in :term:`ZODB` - because the class inherits from the - :class:`persistent.mapping.PersistentMapping` class. The ``__parent__`` - and ``__name__`` are important parts of the :term:`traversal` protocol. - By default, set these to ``None`` to indicate that this is the - :term:`root` object. - -#. *Lines 8-12*. ``appmaker`` is used to return the *application - root* object. It is called on *every request* to the - :app:`Pyramid` application. It also performs bootstrapping by - *creating* an application root (inside the ZODB root object) if one - does not already exist. It is used by the ``root_factory`` we've defined - in our ``__init__.py``. +#. *Lines 4-5*. + The ``MyModel`` :term:`resource` class is implemented here. + Instances of this class are capable of being persisted in :term:`ZODB` because the class inherits from the :class:`persistent.mapping.PersistentMapping` class. + The ``__parent__`` and ``__name__`` are important parts of the :term:`traversal` protocol. + By default, set these to ``None`` to indicate that this is the :term:`root` object. + +#. *Lines 8-12*. + ``appmaker`` is used to return the *application root* object. + It is called on *every request* to the :app:`Pyramid` application. + It also performs bootstrapping by *creating* an application root (inside the ZODB root object) if one does not already exist. + It is used by the ``root_factory`` we've defined in our ``__init__.py``. - Bootstrapping is done by first seeing if the database has the persistent - application root. If not, we make an instance, store it, and commit the - transaction. We then return the application root object. + Bootstrapping is done by first seeing if the database has the persistent application root. + If not, we make an instance, store it, and commit the transaction. + We then return the application root object. Views With ``views.py`` ----------------------- -- cgit v1.2.3 From b770c31e5713b33f7d77e22a6dc1ddb530d00b9c Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Fri, 23 Nov 2018 18:01:25 -0800 Subject: Clean up views, now in a package --- docs/tutorials/wiki/basiclayout.rst | 96 +++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 51 deletions(-) diff --git a/docs/tutorials/wiki/basiclayout.rst b/docs/tutorials/wiki/basiclayout.rst index 83225c6e1..a1a1da286 100644 --- a/docs/tutorials/wiki/basiclayout.rst +++ b/docs/tutorials/wiki/basiclayout.rst @@ -112,63 +112,57 @@ Here is the source for ``models.py``: If not, we make an instance, store it, and commit the transaction. We then return the application root object. -Views With ``views.py`` ------------------------ -Our cookiecutter generated a default ``views.py`` on our behalf. It -contains a single view, which is used to render the page shown when you visit -the URL ``http://localhost:6543/``. +View declarations via the ``views`` package +------------------------------------------- -Here is the source for ``views.py``: +Our cookiecutter generated a default ``views`` package on our behalf. +It contains a two views. +which is used to render the page shown when you visit the URL ``http://localhost:6543/``. -.. literalinclude:: src/basiclayout/tutorial/views.py - :linenos: - :language: python +Open ``tutorial/views/default.py`` in the ``views`` package. +It should already contain the following: + +.. literalinclude:: src/basiclayout/tutorial/views/default.py + :linenos: + :language: python Let's try to understand the components in this module: -#. *Lines 1-2*. Perform some dependency imports. - -#. *Line 5*. Use the :func:`pyramid.view.view_config` :term:`configuration - decoration` to perform a :term:`view configuration` registration. This - view configuration registration will be activated when the application is - started. It will be activated by virtue of it being found as the result - of a :term:`scan` (when Line 14 of ``__init__.py`` is run). - - The ``@view_config`` decorator accepts a number of keyword arguments. We - use two keyword arguments here: ``context`` and ``renderer``. - - The ``context`` argument signifies that the decorated view callable should - only be run when :term:`traversal` finds the ``tutorial.models.MyModel`` - :term:`resource` to be the :term:`context` of a request. In English, this - means that when the URL ``/`` is visited, because ``MyModel`` is the root - model, this view callable will be invoked. - - The ``renderer`` argument names an :term:`asset specification` of - ``templates/mytemplate.pt``. This asset specification points at a - :term:`Chameleon` template which lives in the ``mytemplate.pt`` file - within the ``templates`` directory of the ``tutorial`` package. And - indeed if you look in the ``templates`` directory of this package, you'll - see a ``mytemplate.pt`` template file, which renders the default home page - of the generated project. This asset specification is *relative* (to the - view.py's current package). Alternatively we could have used the - absolute asset specification ``tutorial:templates/mytemplate.pt``, but - chose to use the relative version. - - Since this call to ``@view_config`` doesn't pass a ``name`` argument, the - ``my_view`` function which it decorates represents the "default" view - callable used when the context is of the type ``MyModel``. - -#. *Lines 6-7*. We define a :term:`view callable` named ``my_view``, which - we decorated in the step above. This view callable is a *function* we - write generated by the ``zodb`` cookiecutter that is given a - ``request`` and which returns a dictionary. The ``mytemplate.pt`` - :term:`renderer` named by the asset specification in the step above will - convert this dictionary to a :term:`response` on our behalf. - - The function returns the dictionary ``{'project':'tutorial'}``. This - dictionary is used by the template named by the ``mytemplate.pt`` asset - specification to fill in certain values on the page. +#. *Lines 1-3*. + Perform some dependency imports. + +#. *Line 6*. + Use the :func:`pyramid.view.view_config` :term:`configuration decoration` to perform a :term:`view configuration` registration. + This view configuration registration will be activated when the application is started. + Remember in our application's ``__init__.py`` when we executed the :meth:`pyramid.config.Configurator.scan` method ``config.scan()``? + By calling the scan method, Pyramid's configurator will find and process this ``@view_config`` decorator, and create a view configuration within our application. + Without being processed by ``scan``, the decorator effectively does nothing. + ``@view_config`` is inert without being detected via a :term:`scan`. + + The ``@view_config`` decorator accepts a number of keyword arguments. + We use two keyword arguments here: ``context`` and ``renderer``. + + The ``context`` argument signifies that the decorated view callable ``my_view`` should only be run when :term:`traversal` finds the ``tutorial.models.MyModel`` :term:`resource` as the :term:`context` of a request. + In English this means that when the URL ``/`` is visited, and because ``MyModel`` is the root model, this view callable will be invoked. + + The ``renderer`` argument names an :term:`asset specification` of ``templates/mytemplate.pt``. + This asset specification points at a :term:`Chameleon` template which lives in the ``mytemplate.pt`` file within the ``templates`` directory of the ``tutorial`` package. + And indeed if you look in the ``templates`` directory of this package, you will see a ``mytemplate.pt`` template file + This template renders the default home page of the generated project. + This asset specification is *relative* to the ``views`` package. + Alternatively we could have used the absolute asset specification ``tutorial:templates/mytemplate.pt``. + + Since this call to ``@view_config`` doesn't pass a ``name`` argument, the ``my_view`` function which it decorates represents the "default" view callable used when the context is of the type ``MyModel``. + +#. *Lines 7-8*. + A :term:`view callable` named ``my_view`` is defined, which is decorated in the step above. + This view callable is a *function* generated by the cookiecutter. + It is given a single argument, ``request``. + This is the standard call signature for a Pyramid :term:`view callable`. + The function returns the dictionary ``{'project': 'myproj'}``. + This dictionary is used by the template named by the ``mytemplate.pt`` asset specification to fill in certain values on the page. + Configuration in ``development.ini`` ------------------------------------ -- cgit v1.2.3 From a4b1059b4c92f8b25486159cc650a8a94cd62772 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Fri, 23 Nov 2018 18:33:46 -0800 Subject: Rewrite application configuration to mirror the URL-dispatch + SQLAlchemy wiki style --- docs/tutorials/wiki/basiclayout.rst | 160 +++++++++++++++++++++++++++--------- 1 file changed, 120 insertions(+), 40 deletions(-) diff --git a/docs/tutorials/wiki/basiclayout.rst b/docs/tutorials/wiki/basiclayout.rst index a1a1da286..33e0b4ff3 100644 --- a/docs/tutorials/wiki/basiclayout.rst +++ b/docs/tutorials/wiki/basiclayout.rst @@ -24,62 +24,132 @@ It should already contain the following: :linenos: :language: py -#. *Lines 1-3*. - Perform some dependency imports. +Let's go over this piece-by-piece. +First we need some imports to support later code. + +.. literalinclude:: src/basiclayout/tutorial/__init__.py + :end-before: root_factory + :lineno-match: + :language: py + +Define a :term:`root factory` for our Pyramid application. +It establishes a connection to ZODB database. +It returns an ``appmaker``, which we will describe in the next section :ref:`wiki-resources-and-models`. + +.. literalinclude:: src/basiclayout/tutorial/__init__.py + :pyobject: root_factory + :lineno-match: + :language: py + +``__init__.py`` defines a function named ``main``. +Here is the entirety of the ``main`` function that we have defined in our ``__init__.py``: + +.. literalinclude:: src/basiclayout/tutorial/__init__.py + :pyobject: main + :lineno-match: + :language: py + +When you invoke the ``pserve development.ini`` command, the ``main`` function above is executed. +It accepts some settings and returns a :term:`WSGI` application. +See :ref:`startup_chapter` for more about ``pserve``. + +Next in ``main``, construct a :term:`Configurator` object using a context manager. +See also :term:`Deployment settings`. + +.. literalinclude:: src/basiclayout/tutorial/__init__.py + :lines: 14 + :lineno-match: + :language: py + +``settings`` is passed to the ``Configurator`` as a keyword argument with the dictionary values passed as the ``**settings`` argument. +This will be a dictionary of settings parsed from the ``.ini`` file, which contains +deployment-related values, such as ``pyramid.reload_templates``, ``zodbconn.uri``, and so on. + +Next use an explicit transaction manager for our application. +This prevents new transactions from being implicitly created when touching the manager outside of the ``pyramid_tm`` lifecycle. + +.. literalinclude:: src/basiclayout/tutorial/__init__.py + :lines: 15 + :lineno-match: + :language: py + +Next include support for ``pyramid_tm``, allowing Pyramid requests to join the active transaction as provided by the `transaction `_ package. + +.. literalinclude:: src/basiclayout/tutorial/__init__.py + :lines: 16 + :lineno-match: + :language: py + +Next include support for ``pyramid_retry`` to retry a request when transient exceptions occur. -#. *Lines 6-8*. - Define a :term:`root factory` for our Pyramid application. +.. literalinclude:: src/basiclayout/tutorial/__init__.py + :lines: 17 + :lineno-match: + :language: py -#. *Line 11*. - ``__init__.py`` defines a function named ``main``. +Next include support for ``pyramid_zodbconn``, providing integration between :term:`ZODB` and a Pyramid application. + +.. literalinclude:: src/basiclayout/tutorial/__init__.py + :lines: 18 + :lineno-match: + :language: py -#. *Line 14*. - Construct a :term:`Configurator` as a :term:`context manager` with the ``settings`` keyword parsed by :term:`PasteDeploy`. See :term:`Deployment settings`. +Next set a root factory using our function named ``root_factory``. + +.. literalinclude:: src/basiclayout/tutorial/__init__.py + :lines: 19 + :lineno-match: + :language: py + +Next include support for the :term:`Chameleon` template rendering bindings, allowing us to use the ``.pt`` templates. + +.. literalinclude:: src/basiclayout/tutorial/__init__.py + :lines: 20 + :lineno-match: + :language: py -#. *Line 15*. - Use an explicit transaction manager for apps so that they do not implicitly create new transactions when touching the manager outside of the ``pyramid_tm`` lifecycle. +Next include routes from the ``.routes`` module. -#. *Line 16*. - Include support for ``pyramid_tm``, allowing Pyramid requests to join the active transaction as provided by the `transaction `_ package. +.. literalinclude:: src/basiclayout/tutorial/__init__.py + :lines: 21 + :lineno-match: + :language: py -#. *Line 17*. - Include support for ``pyramid_retry`` to retry a request when transient exceptions occur. +This registers a "static view" using the :meth:`pyramid.config.Configurator.add_static_view` method. +This view answers requests whose URL path starts with ``/static``. +This statement registers a view that will serve up static assets, such as CSS and image files. +In this case the URL will answer requests at ``http://localhost:6543/static/`` and below. -#. *Line 18*. - Include support for ``pyramid_zodbconn``, providing integration between :term:`ZODB` and a Pyramid application. +The first argument is the "name" ``static``, which indicates that the URL path prefix of the view will be ``/static``. -#. *Line 19*. - Set a root factory using our function named ``root_factory``. +The second argument of this method is the "path". +It is a relative :term:`asset specification`. +It finds the resources it should serve within the ``static`` directory inside the ``tutorial`` package. +Alternatively the cookiecutter could have used an *absolute* asset specification as the path (``tutorial:static``). -#. *Line 20*. - Include support for the :term:`Chameleon` template rendering bindings, allowing us to use the ``.pt`` templates. +The third argument is an optional ``cache_max_age`` which specifies the number of seconds the static asset will be HTTP-cached. -#. *Line 21*. - Include routes from the ``.routes`` module. - This registers a "static view" using the :meth:`pyramid.config.Configurator.add_static_view` method. - This view answers requests whose URL path starts with ``/static``. - This statement registers a view that will serve up static assets, such as CSS and image files. - In this case the URL will answer requests at ``http://localhost:6543/static/`` and below. +Next perform a :term:`scan`. - The first argument is the "name" ``static``, which indicates that the URL path prefix of the view will be ``/static``. +.. literalinclude:: src/basiclayout/tutorial/__init__.py + :lines: 22 + :lineno-match: + :language: py - The second argument of this method is the "path". - It is a relative :term:`asset specification`. - It finds the resources it should serve within the ``static`` directory inside the ``tutorial`` package. - Alternatively the cookiecutter could have used an *absolute* asset specification as the path (``tutorial:static``). +A scan will find :term:`configuration decoration`, such as view configuration decorators (e.g., ``@view_config``) in the source code of the ``tutorial`` package. +It will take actions based on these decorators. +We don't pass any arguments to :meth:`~pyramid.config.Configurator.scan`, which implies that the scan should take place in the current package (in this case, ``tutorial``). +The cookiecutter could have equivalently said ``config.scan('tutorial')``, but it chose to omit the package name argument. - The third argument is an optional ``cache_max_age`` which specifies the number of seconds the static asset will be HTTP-cached. +Finally use the :meth:`pyramid.config.Configurator.make_wsgi_app` method to return a :term:`WSGI` application. -#. *Line 22*. - Perform a :term:`scan`. - A scan will find :term:`configuration decoration`, such as view configuration decorators (e.g., ``@view_config``) in the source code of the ``tutorial`` package. - It will take actions based on these decorators. - We don't pass any arguments to :meth:`~pyramid.config.Configurator.scan`, which implies that the scan should take place in the current package (in this case, ``tutorial``). - The cookiecutter could have equivalently said ``config.scan('tutorial')``, but it chose to omit the package name argument. +.. literalinclude:: src/basiclayout/tutorial/__init__.py + :lines: 23 + :lineno-match: + :language: py -#. *Line 23*. - Use the :meth:`pyramid.config.Configurator.make_wsgi_app` method to return a :term:`WSGI` application. +.. _wiki-resources-and-models: Resources and models with ``models`` package -------------------------------------------- @@ -163,6 +233,16 @@ Let's try to understand the components in this module: The function returns the dictionary ``{'project': 'myproj'}``. This dictionary is used by the template named by the ``mytemplate.pt`` asset specification to fill in certain values on the page. +Now let us open the ``notfound.py`` module, and describe its function. + +.. literalinclude:: src/basiclayout/tutorial/views/notfound.py + :linenos: + :language: python + +Without repeating ourselves, we will point out the differences between this view and the previous. + +#. + Configuration in ``development.ini`` ------------------------------------ -- cgit v1.2.3 From dd2aefc04435b65d67163c432409b4f76685b93a Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Fri, 23 Nov 2018 18:36:31 -0800 Subject: Clean up grammar in models section --- docs/tutorials/wiki/basiclayout.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/tutorials/wiki/basiclayout.rst b/docs/tutorials/wiki/basiclayout.rst index 33e0b4ff3..7e6e86143 100644 --- a/docs/tutorials/wiki/basiclayout.rst +++ b/docs/tutorials/wiki/basiclayout.rst @@ -170,16 +170,16 @@ Here is the source for ``models.py``: The ``MyModel`` :term:`resource` class is implemented here. Instances of this class are capable of being persisted in :term:`ZODB` because the class inherits from the :class:`persistent.mapping.PersistentMapping` class. The ``__parent__`` and ``__name__`` are important parts of the :term:`traversal` protocol. - By default, set these to ``None`` to indicate that this is the :term:`root` object. + By default, these are set to ``None`` to indicate that this is the :term:`root` object. #. *Lines 8-12*. ``appmaker`` is used to return the *application root* object. It is called on *every request* to the :app:`Pyramid` application. It also performs bootstrapping by *creating* an application root (inside the ZODB root object) if one does not already exist. - It is used by the ``root_factory`` we've defined in our ``__init__.py``. + It is used by the ``root_factory`` we have defined in our ``__init__.py``. Bootstrapping is done by first seeing if the database has the persistent application root. - If not, we make an instance, store it, and commit the transaction. + If not, then we make an instance, store it, and commit the transaction. We then return the application root object. -- cgit v1.2.3 From 28f659844f0f3520a8bedf4d318970b039bf9734 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Fri, 23 Nov 2018 18:42:18 -0800 Subject: Remove redundancy in appmaker narrative --- docs/tutorials/wiki/basiclayout.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/tutorials/wiki/basiclayout.rst b/docs/tutorials/wiki/basiclayout.rst index 7e6e86143..39e15bb58 100644 --- a/docs/tutorials/wiki/basiclayout.rst +++ b/docs/tutorials/wiki/basiclayout.rst @@ -174,12 +174,12 @@ Here is the source for ``models.py``: #. *Lines 8-12*. ``appmaker`` is used to return the *application root* object. - It is called on *every request* to the :app:`Pyramid` application. + It is called on *every request* to the :app:`Pyramid` application by virtue of the ``root_factory`` defined in our ``__init__.py``. It also performs bootstrapping by *creating* an application root (inside the ZODB root object) if one does not already exist. - It is used by the ``root_factory`` we have defined in our ``__init__.py``. - + Bootstrapping is done by first seeing if the database has the persistent application root. If not, then we make an instance, store it, and commit the transaction. + We then return the application root object. -- cgit v1.2.3 From 4ccf01a091485463bfae1129ed858546b6a93da6 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Fri, 23 Nov 2018 18:54:09 -0800 Subject: Add notfound_view function description and 404.pt template. --- docs/tutorials/wiki/basiclayout.rst | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/docs/tutorials/wiki/basiclayout.rst b/docs/tutorials/wiki/basiclayout.rst index 39e15bb58..3f84f12c3 100644 --- a/docs/tutorials/wiki/basiclayout.rst +++ b/docs/tutorials/wiki/basiclayout.rst @@ -188,8 +188,8 @@ View declarations via the ``views`` package Our cookiecutter generated a default ``views`` package on our behalf. It contains a two views. -which is used to render the page shown when you visit the URL ``http://localhost:6543/``. +The first view is used to render the page shown when you visit the URL ``http://localhost:6543/``. Open ``tutorial/views/default.py`` in the ``views`` package. It should already contain the following: @@ -233,7 +233,7 @@ Let's try to understand the components in this module: The function returns the dictionary ``{'project': 'myproj'}``. This dictionary is used by the template named by the ``mytemplate.pt`` asset specification to fill in certain values on the page. -Now let us open the ``notfound.py`` module, and describe its function. +Let us open ``tutorial/views/default.py`` in the ``views`` package to look at the second view. .. literalinclude:: src/basiclayout/tutorial/views/notfound.py :linenos: @@ -241,7 +241,16 @@ Now let us open the ``notfound.py`` module, and describe its function. Without repeating ourselves, we will point out the differences between this view and the previous. -#. +#. *Line 4*. + The ``notfound_view`` function is decorated with ``@notfound_view_config``. + This decorator registers a :term:`Not Found View` using :meth:`pyramid.config.Configurator.add_notfound_view`. + + The ``renderer`` argument names an :term:`asset specification` of ``templates/404.pt``. + +#. *Lines 5-7*. + A :term:`view callable` named ``notfound_view`` is defined, which is decorated in the step above. + It sets the HTTP response status code to ``404``. + The function returns an empty dictionary to the template ``404.pt``, which accepts no parameters anyway. Configuration in ``development.ini`` -- cgit v1.2.3 From d42b654b1dda5e0e80c25c2cb8b8836ad538d60c Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Fri, 23 Nov 2018 18:57:26 -0800 Subject: Clean up configuration for development.ini --- docs/tutorials/wiki/basiclayout.rst | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/docs/tutorials/wiki/basiclayout.rst b/docs/tutorials/wiki/basiclayout.rst index 3f84f12c3..ab9b7df5c 100644 --- a/docs/tutorials/wiki/basiclayout.rst +++ b/docs/tutorials/wiki/basiclayout.rst @@ -256,15 +256,11 @@ Without repeating ourselves, we will point out the differences between this view Configuration in ``development.ini`` ------------------------------------ -The ``development.ini`` (in the ``tutorial`` :term:`project` directory, as -opposed to the ``tutorial`` :term:`package` directory) looks like this: +The ``development.ini`` (in the ``tutorial`` :term:`project` directory, as opposed to the ``tutorial`` :term:`package` directory) looks like this: .. literalinclude:: src/basiclayout/development.ini :language: ini -Note the existence of a ``[app:main]`` section which specifies our WSGI -application. Our ZODB database settings are specified as the -``zodbconn.uri`` setting within this section. This value, and the other -values within this section, are passed as ``**settings`` to the ``main`` -function we defined in ``__init__.py`` when the server is started via -``pserve``. +Note the existence of a ``[app:main]`` section which specifies our WSGI application. +Our ZODB database settings are specified as the ``zodbconn.uri`` setting within this section. +When the server is started via ``pserve``, the values within this section are passed as ``**settings`` to the ``main`` function defined in ``__init__.py``. -- cgit v1.2.3 From 3a06b648ca0fe2bb29b9c2ca167e95560f19a742 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Fri, 23 Nov 2018 19:05:17 -0800 Subject: Clean up introduction and delete the database sections of definingmodels.rst --- docs/tutorials/wiki/definingmodels.rst | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/docs/tutorials/wiki/definingmodels.rst b/docs/tutorials/wiki/definingmodels.rst index e973cfdfe..3f1cdf007 100644 --- a/docs/tutorials/wiki/definingmodels.rst +++ b/docs/tutorials/wiki/definingmodels.rst @@ -4,30 +4,26 @@ Defining the Domain Model ========================= -The first change we'll make to our stock cookiecutter-generated application will -be to define two :term:`resource` constructors, one representing a wiki page, -and another representing the wiki as a mapping of wiki page names to page -objects. We'll do this inside our ``models.py`` file. +Let's make changes to our stock cookiecutter-generated application. +We will define two :term:`resource` constructors, one representing a wiki page, and another representing the wiki as a mapping of wiki page names to page objects. +We will do this inside our ``models.py`` file. -Because we're using :term:`ZODB` to represent our -:term:`resource tree`, each of these resource constructors represents a -:term:`domain model` object, so we'll call these constructors "model -constructors". Both our Page and Wiki constructors will be class objects. A -single instance of the "Wiki" class will serve as a container for "Page" -objects, which will be instances of the "Page" class. +Because we are using :term:`ZODB` to represent our :term:`resource tree`, each of these resource constructors represents a :term:`domain model` object. +We will call these constructors "model constructors". +Both our Page and Wiki constructors will be class objects. +A single instance of the "Wiki" class will serve as a container for "Page" objects, which will be instances of the "Page" class. Delete the database ------------------- -In the next step, we're going to remove the ``MyModel`` Python model -class from our ``models.py`` file. Since this class is referred to within -our persistent storage (represented on disk as a file named ``Data.fs``), -we'll have strange things happen the next time we want to visit the -application in a browser. Remove the ``Data.fs`` from the ``tutorial`` -directory before proceeding any further. It's always fine to do this as long -as you don't care about the content of the database; the database itself will -be recreated as necessary. +In the next step, we will remove the ``MyModel`` Python model class from our ``models`` package. +Since this class is referred to within our persistent storage (represented on disk as a file named ``Data.fs``), we will have strange things happen the next time we want to visit the application in a browser. + +Remove the ``Data.fs`` from the ``tutorial`` directory before proceeding any further. +It is always fine to do this as long as you don't care about the content of the database. +The database itself will be recreated as necessary. + Edit ``models.py`` ------------------ -- cgit v1.2.3 From 2296426292ad92e7a56ded4cef55a15b7cefdb7d Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 24 Nov 2018 04:09:25 -0800 Subject: Synch up src/models files --- docs/tutorials/wiki/src/models/MANIFEST.in | 2 +- docs/tutorials/wiki/src/models/pytest.ini | 2 +- docs/tutorials/wiki/src/models/setup.py | 4 +- .../tutorials/wiki/src/models/tutorial/__init__.py | 8 +-- docs/tutorials/wiki/src/models/tutorial/models.py | 20 ------- .../wiki/src/models/tutorial/models/__init__.py | 20 +++++++ docs/tutorials/wiki/src/models/tutorial/pshell.py | 1 + docs/tutorials/wiki/src/models/tutorial/routes.py | 2 + .../wiki/src/models/tutorial/templates/404.pt | 10 ++++ .../wiki/src/models/tutorial/templates/layout.pt | 62 +++++++++++++++++++ .../src/models/tutorial/templates/mytemplate.pt | 70 +++------------------- docs/tutorials/wiki/src/models/tutorial/tests.py | 3 +- docs/tutorials/wiki/src/models/tutorial/views.py | 7 --- .../wiki/src/models/tutorial/views/__init__.py | 0 .../wiki/src/models/tutorial/views/default.py | 8 +++ .../wiki/src/models/tutorial/views/notfound.py | 7 +++ 16 files changed, 128 insertions(+), 98 deletions(-) delete mode 100644 docs/tutorials/wiki/src/models/tutorial/models.py create mode 100644 docs/tutorials/wiki/src/models/tutorial/models/__init__.py create mode 100644 docs/tutorials/wiki/src/models/tutorial/routes.py create mode 100644 docs/tutorials/wiki/src/models/tutorial/templates/404.pt create mode 100644 docs/tutorials/wiki/src/models/tutorial/templates/layout.pt delete mode 100644 docs/tutorials/wiki/src/models/tutorial/views.py create mode 100644 docs/tutorials/wiki/src/models/tutorial/views/__init__.py create mode 100644 docs/tutorials/wiki/src/models/tutorial/views/default.py create mode 100644 docs/tutorials/wiki/src/models/tutorial/views/notfound.py diff --git a/docs/tutorials/wiki/src/models/MANIFEST.in b/docs/tutorials/wiki/src/models/MANIFEST.in index 81beba1b1..05cc195d9 100644 --- a/docs/tutorials/wiki/src/models/MANIFEST.in +++ b/docs/tutorials/wiki/src/models/MANIFEST.in @@ -1,2 +1,2 @@ include *.txt *.ini *.cfg *.rst -recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml +recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2 diff --git a/docs/tutorials/wiki/src/models/pytest.ini b/docs/tutorials/wiki/src/models/pytest.ini index 8b76bc410..a3489cdf8 100644 --- a/docs/tutorials/wiki/src/models/pytest.ini +++ b/docs/tutorials/wiki/src/models/pytest.ini @@ -1,3 +1,3 @@ [pytest] testpaths = tutorial -python_files = *.py +python_files = test*.py diff --git a/docs/tutorials/wiki/src/models/setup.py b/docs/tutorials/wiki/src/models/setup.py index a4f143d24..d6d488ed2 100644 --- a/docs/tutorials/wiki/src/models/setup.py +++ b/docs/tutorials/wiki/src/models/setup.py @@ -10,15 +10,15 @@ with open(os.path.join(here, 'CHANGES.txt')) as f: requires = [ 'plaster_pastedeploy', - 'pyramid >= 1.9a', + 'pyramid', 'pyramid_chameleon', 'pyramid_debugtoolbar', + 'waitress', 'pyramid_retry', 'pyramid_tm', 'pyramid_zodbconn', 'transaction', 'ZODB3', - 'waitress', ] tests_require = [ diff --git a/docs/tutorials/wiki/src/models/tutorial/__init__.py b/docs/tutorials/wiki/src/models/tutorial/__init__.py index f2b3c9568..bd0c71f5b 100644 --- a/docs/tutorials/wiki/src/models/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/models/tutorial/__init__.py @@ -11,13 +11,13 @@ def root_factory(request): def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ - settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' with Configurator(settings=settings) as config: - config.include('pyramid_chameleon') + settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' config.include('pyramid_tm') config.include('pyramid_retry') config.include('pyramid_zodbconn') config.set_root_factory(root_factory) - config.add_static_view('static', 'static', cache_max_age=3600) + config.include('pyramid_chameleon') + config.include('.routes') config.scan() - return config.make_wsgi_app() + return config.make_wsgi_app() diff --git a/docs/tutorials/wiki/src/models/tutorial/models.py b/docs/tutorials/wiki/src/models/tutorial/models.py deleted file mode 100644 index 7c6597afa..000000000 --- a/docs/tutorials/wiki/src/models/tutorial/models.py +++ /dev/null @@ -1,20 +0,0 @@ -from persistent import Persistent -from persistent.mapping import PersistentMapping - -class Wiki(PersistentMapping): - __name__ = None - __parent__ = None - -class Page(Persistent): - def __init__(self, data): - self.data = data - -def appmaker(zodb_root): - if 'app_root' not in zodb_root: - app_root = Wiki() - frontpage = Page('This is the front page') - app_root['FrontPage'] = frontpage - frontpage.__name__ = 'FrontPage' - frontpage.__parent__ = app_root - zodb_root['app_root'] = app_root - return zodb_root['app_root'] diff --git a/docs/tutorials/wiki/src/models/tutorial/models/__init__.py b/docs/tutorials/wiki/src/models/tutorial/models/__init__.py new file mode 100644 index 000000000..7c6597afa --- /dev/null +++ b/docs/tutorials/wiki/src/models/tutorial/models/__init__.py @@ -0,0 +1,20 @@ +from persistent import Persistent +from persistent.mapping import PersistentMapping + +class Wiki(PersistentMapping): + __name__ = None + __parent__ = None + +class Page(Persistent): + def __init__(self, data): + self.data = data + +def appmaker(zodb_root): + if 'app_root' not in zodb_root: + app_root = Wiki() + frontpage = Page('This is the front page') + app_root['FrontPage'] = frontpage + frontpage.__name__ = 'FrontPage' + frontpage.__parent__ = app_root + zodb_root['app_root'] = app_root + return zodb_root['app_root'] diff --git a/docs/tutorials/wiki/src/models/tutorial/pshell.py b/docs/tutorials/wiki/src/models/tutorial/pshell.py index 3d026291b..a7cfa6a27 100644 --- a/docs/tutorials/wiki/src/models/tutorial/pshell.py +++ b/docs/tutorials/wiki/src/models/tutorial/pshell.py @@ -1,5 +1,6 @@ from . import models + def setup(env): request = env['request'] diff --git a/docs/tutorials/wiki/src/models/tutorial/routes.py b/docs/tutorials/wiki/src/models/tutorial/routes.py new file mode 100644 index 000000000..3c0a37992 --- /dev/null +++ b/docs/tutorials/wiki/src/models/tutorial/routes.py @@ -0,0 +1,2 @@ +def includeme(config): + config.add_static_view('static', 'static', cache_max_age=3600) diff --git a/docs/tutorials/wiki/src/models/tutorial/templates/404.pt b/docs/tutorials/wiki/src/models/tutorial/templates/404.pt new file mode 100644 index 000000000..07298940c --- /dev/null +++ b/docs/tutorials/wiki/src/models/tutorial/templates/404.pt @@ -0,0 +1,10 @@ +
+
+ +
+

Pyramid Starter project

+

404 Page Not Found

+
+ +
+
diff --git a/docs/tutorials/wiki/src/models/tutorial/templates/layout.pt b/docs/tutorials/wiki/src/models/tutorial/templates/layout.pt new file mode 100644 index 000000000..9fdaef00f --- /dev/null +++ b/docs/tutorials/wiki/src/models/tutorial/templates/layout.pt @@ -0,0 +1,62 @@ + + + + + + + + + + + Cookiecutter Starter project for the Pyramid Web Framework + + + + + + + + + + + + + +
+
+
+
+ +
+
+
No content
+
+
+ +
+ +
+
+
+ + + + + + + + diff --git a/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt index d63ea8c45..adac4fe35 100644 --- a/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt @@ -1,65 +1,11 @@ - - - - - - - - - +
+
- Cookiecutter ZODB project for the Pyramid Web Framework - - - - - - - - - - - - - -
-
-
-
- -
-
-
-

Pyramid ZODB Project

-

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

-
-
+
+

Pyramid Starter project

+

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

- -
- -
-
-
- - - - - - - +
+
diff --git a/docs/tutorials/wiki/src/models/tutorial/tests.py b/docs/tutorials/wiki/src/models/tutorial/tests.py index ca7a47279..6279d9f66 100644 --- a/docs/tutorials/wiki/src/models/tutorial/tests.py +++ b/docs/tutorials/wiki/src/models/tutorial/tests.py @@ -11,7 +11,8 @@ class ViewTests(unittest.TestCase): testing.tearDown() def test_my_view(self): - from .views import my_view + from .views.default import my_view request = testing.DummyRequest() info = my_view(request) self.assertEqual(info['project'], 'myproj') + diff --git a/docs/tutorials/wiki/src/models/tutorial/views.py b/docs/tutorials/wiki/src/models/tutorial/views.py deleted file mode 100644 index c1878bdd0..000000000 --- a/docs/tutorials/wiki/src/models/tutorial/views.py +++ /dev/null @@ -1,7 +0,0 @@ -from pyramid.view import view_config -from .models import MyModel - - -@view_config(context=MyModel, renderer='templates/mytemplate.pt') -def my_view(request): - return {'project': 'myproj'} diff --git a/docs/tutorials/wiki/src/models/tutorial/views/__init__.py b/docs/tutorials/wiki/src/models/tutorial/views/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/docs/tutorials/wiki/src/models/tutorial/views/default.py b/docs/tutorials/wiki/src/models/tutorial/views/default.py new file mode 100644 index 000000000..5d708d15c --- /dev/null +++ b/docs/tutorials/wiki/src/models/tutorial/views/default.py @@ -0,0 +1,8 @@ +from pyramid.view import view_config + +from ..models import MyModel + + +@view_config(context=MyModel, renderer='../templates/mytemplate.pt') +def my_view(request): + return {'project': 'myproj'} diff --git a/docs/tutorials/wiki/src/models/tutorial/views/notfound.py b/docs/tutorials/wiki/src/models/tutorial/views/notfound.py new file mode 100644 index 000000000..728791d0a --- /dev/null +++ b/docs/tutorials/wiki/src/models/tutorial/views/notfound.py @@ -0,0 +1,7 @@ +from pyramid.view import notfound_view_config + + +@notfound_view_config(renderer='../templates/404.pt') +def notfound_view(request): + request.response.status = 404 + return {} -- cgit v1.2.3 From 3f6ea7a313943ff69b4b66210ed299ed01bac3ed Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 24 Nov 2018 04:09:56 -0800 Subject: Update models and view application sections --- docs/tutorials/wiki/definingmodels.rst | 114 +++++++++++++++++++-------------- 1 file changed, 67 insertions(+), 47 deletions(-) diff --git a/docs/tutorials/wiki/definingmodels.rst b/docs/tutorials/wiki/definingmodels.rst index 3f1cdf007..81dd25862 100644 --- a/docs/tutorials/wiki/definingmodels.rst +++ b/docs/tutorials/wiki/definingmodels.rst @@ -25,65 +25,85 @@ It is always fine to do this as long as you don't care about the content of the The database itself will be recreated as necessary. -Edit ``models.py`` ------------------- +Edit ``models`` package +----------------------- .. note:: - There is nothing special about the filename ``models.py``. A - project may have many models throughout its codebase in arbitrarily named - files. Files implementing models often have ``model`` in their filenames - or they may live in a Python subpackage of your application package named - ``models``, but this is only by convention. + There is nothing special about the package name ``models``. + A project may have many models throughout its codebase in arbitrarily named files and directories. + Files that implement models often have ``model`` in their names, or they may live in a Python subpackage of your application package named ``models``, but this is only by convention. -Open ``tutorial/models.py`` file and edit it to look like the following: +Open ``tutorial/models/__init__.py`` file and edit it to look like the following: -.. literalinclude:: src/models/tutorial/models.py +.. literalinclude:: src/models/tutorial/models/__init__.py :linenos: :language: python -The first thing we want to do is remove the ``MyModel`` class from the -generated ``models.py`` file. The ``MyModel`` class is only a sample and -we're not going to use it. - -Then we'll add an import at the top for the :class:`persistent.Persistent` class. We'll use this for a new ``Page`` class in a moment. - -Then we'll add a ``Wiki`` class. We want it to inherit from the -:class:`persistent.mapping.PersistentMapping` class because it provides -mapping behavior, and it makes sure that our Wiki page is stored as a -"first-class" persistent object in our ZODB database. - -Our ``Wiki`` class should have two attributes set to ``None`` at -class scope: ``__parent__`` and ``__name__``. If a model has a -``__parent__`` attribute of ``None`` in a traversal-based :app:`Pyramid` -application, it means that it's the :term:`root` model. The ``__name__`` -of the root model is also always ``None``. - -Then we'll add a ``Page`` class. This class should inherit from the -:class:`persistent.Persistent` class. We'll also give it an ``__init__`` -method that accepts a single parameter named ``data``. This parameter will -contain the :term:`reStructuredText` body representing the wiki page content. -Note that ``Page`` objects don't have an initial ``__name__`` or -``__parent__`` attribute. All objects in a traversal graph must have a -``__name__`` and a ``__parent__`` attribute. We don't specify these here -because both ``__name__`` and ``__parent__`` will be set by a :term:`view` -function when a Page is added to our Wiki mapping. - -As a last step, we want to change the ``appmaker`` function in our -``models.py`` file so that the :term:`root` :term:`resource` of our -application is a Wiki instance. We'll also slot a single page object (the -front page) into the Wiki within the ``appmaker``. This will provide -:term:`traversal` a :term:`resource tree` to work against when it attempts to -resolve URLs to resources. +Remove the ``MyModel`` class from the generated ``models/__init__.py`` file. +The ``MyModel`` class is only a sample and we're not going to use it. + +Next we add an import at the top for the :class:`persistent.Persistent` class. +We will use this for a new ``Page`` class in a moment. + +.. literalinclude:: src/models/tutorial/models/__init__.py + :lines: 1-2 + :lineno-match: + :emphasize-lines: 1 + :language: py + +Then we add a ``Wiki`` class. + +.. literalinclude:: src/models/tutorial/models/__init__.py + :lines: 4-6 + :lineno-match: + :language: py + +We want it to inherit from the :class:`persistent.mapping.PersistentMapping` class because it provides mapping behavior. +It also makes sure that our ``Wiki`` page is stored as a "first-class" persistent object in our ZODB database. + +Our ``Wiki`` class should have two attributes set to ``None`` at class scope: ``__parent__`` and ``__name__``. +If a model has a ``__parent__`` attribute of ``None`` in a traversal-based :app:`Pyramid` application, it means that it is the :term:`root` model. +The ``__name__`` of the root model is also always ``None``. + +Now we add a ``Page`` class. + +.. literalinclude:: src/models/tutorial/models/__init__.py + :lines: 8-10 + :lineno-match: + :language: py + +This class should inherit from the :class:`persistent.Persistent` class. +We will give it an ``__init__`` method that accepts a single parameter named ``data``. +This parameter will contain the :term:`reStructuredText` body representing the wiki page content. + +Note that ``Page`` objects don't have an initial ``__name__`` or ``__parent__`` attribute. +All objects in a traversal graph must have a ``__name__`` and a ``__parent__`` attribute. +We do not specify these here. +Instead both ``__name__`` and ``__parent__`` will be set by a :term:`view` function when a ``Page`` is added to our ``Wiki`` mapping. +We will create this function in the next chapter. + +As a last step, edit the ``appmaker`` function. + +.. literalinclude:: src/models/tutorial/models/__init__.py + :lines: 12-20 + :lineno-match: + :emphasize-lines: 4-8 + :language: py + +The :term:`root` :term:`resource` of our application is a Wiki instance. + +We will also slot a single page object (the front page) into the Wiki within the ``appmaker``. +This will provide :term:`traversal` a :term:`resource tree` to work against when it attempts to resolve URLs to resources. + View the application in a browser --------------------------------- -We can't. At this point, our system is in a "non-runnable" state; we'll need -to change view-related files in the next chapter to be able to start the -application successfully. If you try to start the application (See -:ref:`wiki-start-the-application`), you'll wind -up with a Python traceback on your console that ends with this exception: +We cannot. +At this point, our system is in a "non-runnable" state +We will need to change view-related files in the next chapter to be able to start the application successfully. +If you try to start the application (See :ref:`wiki-start-the-application`), you will wind up with a Python traceback on your console that ends with this exception: .. code-block:: text -- cgit v1.2.3 From 19553330cccff6f81a7f4db766525a0bacf9a76c Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 24 Nov 2018 04:17:18 -0800 Subject: rewrap introduction, spell out contractions --- docs/tutorials/wiki/definingviews.rst | 35 +++++++++++++---------------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/docs/tutorials/wiki/definingviews.rst b/docs/tutorials/wiki/definingviews.rst index d584a1b41..9bafa87c7 100644 --- a/docs/tutorials/wiki/definingviews.rst +++ b/docs/tutorials/wiki/definingviews.rst @@ -4,31 +4,22 @@ Defining Views ============== -A :term:`view callable` in a :term:`traversal`-based :app:`Pyramid` -application is typically a simple Python function that accepts two -parameters: :term:`context` and :term:`request`. A view callable is -assumed to return a :term:`response` object. +A :term:`view callable` in a :term:`traversal`-based :app:`Pyramid` application is typically a simple Python function that accepts two parameters: :term:`context` and :term:`request`. +A view callable is assumed to return a :term:`response` object. .. note:: - A :app:`Pyramid` view can also be defined as callable - which accepts *only* a :term:`request` argument. You'll see - this one-argument pattern used in other :app:`Pyramid` tutorials - and applications. Either calling convention will work in any - :app:`Pyramid` application; the calling conventions can be used - interchangeably as necessary. In :term:`traversal`-based applications, - URLs are mapped to a context :term:`resource`, and since our - :term:`resource tree` also represents our application's - "domain model", we're often interested in the context because - it represents the persistent storage of our application. For - this reason, in this tutorial we define views as callables that - accept ``context`` in the callable argument list. If you do - need the ``context`` within a view function that only takes - the request as a single argument, you can obtain it via - ``request.context``. - -We're going to define several :term:`view callable` functions, then wire them -into :app:`Pyramid` using some :term:`view configuration`. + A :app:`Pyramid` view can also be defined as callable which accepts *only* a :term:`request` argument. + You will see this one-argument pattern used in other :app:`Pyramid` tutorials and applications. + Either calling convention will work in any :app:`Pyramid` application. + The calling conventions can be used interchangeably as necessary. + + In :term:`traversal`-based applications, URLs are mapped to a context :term:`resource`. + Since our :term:`resource tree` also represents our application's "domain model", we are often interested in the context because it represents the persistent storage of our application. + For this reason, in this tutorial we define views as callables that accept ``context`` in the callable argument list. + If you do need the ``context`` within a view function that only takes the request as a single argument, you can obtain it via ``request.context``. + +We will define several :term:`view callable` functions, then wire them into :app:`Pyramid` using some :term:`view configuration`. Declaring Dependencies in Our ``setup.py`` File -- cgit v1.2.3 From 5a2a68d508c3c433dcb6a8ed5824bd99a0b6d8d4 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 24 Nov 2018 04:18:14 -0800 Subject: Synch non-view and non-template src/views files --- docs/tutorials/wiki/src/views/MANIFEST.in | 2 +- docs/tutorials/wiki/src/views/pytest.ini | 2 +- docs/tutorials/wiki/src/views/setup.py | 4 ++-- docs/tutorials/wiki/src/views/tutorial/__init__.py | 8 ++++---- docs/tutorials/wiki/src/views/tutorial/models.py | 20 -------------------- .../wiki/src/views/tutorial/models/__init__.py | 20 ++++++++++++++++++++ docs/tutorials/wiki/src/views/tutorial/pshell.py | 1 + docs/tutorials/wiki/src/views/tutorial/routes.py | 2 ++ docs/tutorials/wiki/src/views/tutorial/tests.py | 3 ++- 9 files changed, 33 insertions(+), 29 deletions(-) delete mode 100644 docs/tutorials/wiki/src/views/tutorial/models.py create mode 100644 docs/tutorials/wiki/src/views/tutorial/models/__init__.py create mode 100644 docs/tutorials/wiki/src/views/tutorial/routes.py diff --git a/docs/tutorials/wiki/src/views/MANIFEST.in b/docs/tutorials/wiki/src/views/MANIFEST.in index 81beba1b1..05cc195d9 100644 --- a/docs/tutorials/wiki/src/views/MANIFEST.in +++ b/docs/tutorials/wiki/src/views/MANIFEST.in @@ -1,2 +1,2 @@ include *.txt *.ini *.cfg *.rst -recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml +recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2 diff --git a/docs/tutorials/wiki/src/views/pytest.ini b/docs/tutorials/wiki/src/views/pytest.ini index 8b76bc410..a3489cdf8 100644 --- a/docs/tutorials/wiki/src/views/pytest.ini +++ b/docs/tutorials/wiki/src/views/pytest.ini @@ -1,3 +1,3 @@ [pytest] testpaths = tutorial -python_files = *.py +python_files = test*.py diff --git a/docs/tutorials/wiki/src/views/setup.py b/docs/tutorials/wiki/src/views/setup.py index 3c19db7b9..6f3cae397 100644 --- a/docs/tutorials/wiki/src/views/setup.py +++ b/docs/tutorials/wiki/src/views/setup.py @@ -10,15 +10,15 @@ with open(os.path.join(here, 'CHANGES.txt')) as f: requires = [ 'plaster_pastedeploy', - 'pyramid >= 1.9a', + 'pyramid', 'pyramid_chameleon', 'pyramid_debugtoolbar', + 'waitress', 'pyramid_retry', 'pyramid_tm', 'pyramid_zodbconn', 'transaction', 'ZODB3', - 'waitress', 'docutils', ] diff --git a/docs/tutorials/wiki/src/views/tutorial/__init__.py b/docs/tutorials/wiki/src/views/tutorial/__init__.py index f2b3c9568..bd0c71f5b 100644 --- a/docs/tutorials/wiki/src/views/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/views/tutorial/__init__.py @@ -11,13 +11,13 @@ def root_factory(request): def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ - settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' with Configurator(settings=settings) as config: - config.include('pyramid_chameleon') + settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' config.include('pyramid_tm') config.include('pyramid_retry') config.include('pyramid_zodbconn') config.set_root_factory(root_factory) - config.add_static_view('static', 'static', cache_max_age=3600) + config.include('pyramid_chameleon') + config.include('.routes') config.scan() - return config.make_wsgi_app() + return config.make_wsgi_app() diff --git a/docs/tutorials/wiki/src/views/tutorial/models.py b/docs/tutorials/wiki/src/views/tutorial/models.py deleted file mode 100644 index 7c6597afa..000000000 --- a/docs/tutorials/wiki/src/views/tutorial/models.py +++ /dev/null @@ -1,20 +0,0 @@ -from persistent import Persistent -from persistent.mapping import PersistentMapping - -class Wiki(PersistentMapping): - __name__ = None - __parent__ = None - -class Page(Persistent): - def __init__(self, data): - self.data = data - -def appmaker(zodb_root): - if 'app_root' not in zodb_root: - app_root = Wiki() - frontpage = Page('This is the front page') - app_root['FrontPage'] = frontpage - frontpage.__name__ = 'FrontPage' - frontpage.__parent__ = app_root - zodb_root['app_root'] = app_root - return zodb_root['app_root'] diff --git a/docs/tutorials/wiki/src/views/tutorial/models/__init__.py b/docs/tutorials/wiki/src/views/tutorial/models/__init__.py new file mode 100644 index 000000000..7c6597afa --- /dev/null +++ b/docs/tutorials/wiki/src/views/tutorial/models/__init__.py @@ -0,0 +1,20 @@ +from persistent import Persistent +from persistent.mapping import PersistentMapping + +class Wiki(PersistentMapping): + __name__ = None + __parent__ = None + +class Page(Persistent): + def __init__(self, data): + self.data = data + +def appmaker(zodb_root): + if 'app_root' not in zodb_root: + app_root = Wiki() + frontpage = Page('This is the front page') + app_root['FrontPage'] = frontpage + frontpage.__name__ = 'FrontPage' + frontpage.__parent__ = app_root + zodb_root['app_root'] = app_root + return zodb_root['app_root'] diff --git a/docs/tutorials/wiki/src/views/tutorial/pshell.py b/docs/tutorials/wiki/src/views/tutorial/pshell.py index 3d026291b..a7cfa6a27 100644 --- a/docs/tutorials/wiki/src/views/tutorial/pshell.py +++ b/docs/tutorials/wiki/src/views/tutorial/pshell.py @@ -1,5 +1,6 @@ from . import models + def setup(env): request = env['request'] diff --git a/docs/tutorials/wiki/src/views/tutorial/routes.py b/docs/tutorials/wiki/src/views/tutorial/routes.py new file mode 100644 index 000000000..3c0a37992 --- /dev/null +++ b/docs/tutorials/wiki/src/views/tutorial/routes.py @@ -0,0 +1,2 @@ +def includeme(config): + config.add_static_view('static', 'static', cache_max_age=3600) diff --git a/docs/tutorials/wiki/src/views/tutorial/tests.py b/docs/tutorials/wiki/src/views/tutorial/tests.py index ca7a47279..6279d9f66 100644 --- a/docs/tutorials/wiki/src/views/tutorial/tests.py +++ b/docs/tutorials/wiki/src/views/tutorial/tests.py @@ -11,7 +11,8 @@ class ViewTests(unittest.TestCase): testing.tearDown() def test_my_view(self): - from .views import my_view + from .views.default import my_view request = testing.DummyRequest() info = my_view(request) self.assertEqual(info['project'], 'myproj') + -- cgit v1.2.3 From dc97176989ab06d260f1561e89681b607c1b9fc9 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 24 Nov 2018 04:31:43 -0800 Subject: Clean up introduction to views, synch views files --- docs/tutorials/wiki/definingviews.rst | 49 ++++++++---------- .../wiki/src/views/tutorial/views/__init__.py | 0 .../wiki/src/views/tutorial/views/default.py | 60 ++++++++++++++++++++++ .../wiki/src/views/tutorial/views/notfound.py | 7 +++ 4 files changed, 87 insertions(+), 29 deletions(-) create mode 100644 docs/tutorials/wiki/src/views/tutorial/views/__init__.py create mode 100644 docs/tutorials/wiki/src/views/tutorial/views/default.py create mode 100644 docs/tutorials/wiki/src/views/tutorial/views/notfound.py diff --git a/docs/tutorials/wiki/definingviews.rst b/docs/tutorials/wiki/definingviews.rst index 9bafa87c7..7773fbe0f 100644 --- a/docs/tutorials/wiki/definingviews.rst +++ b/docs/tutorials/wiki/definingviews.rst @@ -25,14 +25,11 @@ We will define several :term:`view callable` functions, then wire them into :app Declaring Dependencies in Our ``setup.py`` File =============================================== -The view code in our application will depend on a package which is not a -dependency of the original "tutorial" application. The original "tutorial" -application was generated by the cookiecutter; it doesn't know -about our custom application requirements. +The view code in our application will depend on a package which is not a dependency of the original "tutorial" application. +The original "tutorial" application was generated by the cookiecutter. +It does not know about our custom application requirements. -We need to add a dependency on the ``docutils`` package to our ``tutorial`` -package's ``setup.py`` file by assigning this dependency to the ``requires`` -parameter in the ``setup()`` function. +We need to add a dependency on the ``docutils`` 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: @@ -43,17 +40,15 @@ 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 .`` ============================ -Since a new software dependency was added, you will need to run ``pip install --e .`` again inside the root of the ``tutorial`` package to obtain and register -the newly added dependency distribution. +Since a new software dependency was added, you need to run ``pip install -e .`` again inside the root of the ``tutorial`` package to obtain and register the newly added dependency distribution. -Make sure your current working directory is the root of the project (the -directory in which ``setup.py`` lives) and execute the following command. +Make sure your current working directory is the root of the project (the directory in which ``setup.py`` lives) and execute the following command. On Unix: @@ -69,47 +64,43 @@ On Windows: cd tutorial %VENV%\Scripts\pip install -e . -Success executing this command will end with a line to the console something -like: +Success executing this command will end with a line to the console similar to the following: .. code-block:: text - Successfully installed docutils-0.13.1 tutorial + Successfully installed docutils-0.14 tutorial Adding view functions in ``views.py`` ===================================== -It's time for a major change. Open ``tutorial/views.py`` and edit it to look -like the following: +It is time for a major change. +Open ``tutorial/views/default.py`` and edit it to look like the following: -.. literalinclude:: src/views/tutorial/views.py +.. literalinclude:: src/views/tutorial/views/default.py :linenos: :language: python We added some imports and created a regular expression to find "WikiWords". -We got rid of the ``my_view`` view function and its decorator that was added -when originally rendered after we selected the ``zodb`` backend option in the -cookiecutter. It was only an example and isn't relevant to our application. +We got rid of the ``my_view`` view function and its decorator that was added when originally rendered after we selected the ``zodb`` backend option in the cookiecutter. +It was only an example and is not relevant to our application. -Then we added four :term:`view callable` functions to our ``views.py`` -module: +Then we added four :term:`view callable` functions to our ``views.py`` module: * ``view_wiki()`` - Displays the wiki itself. It will answer on the root URL. * ``view_page()`` - Displays an individual page. * ``add_page()`` - Allows the user to add a page. * ``edit_page()`` - Allows the user to edit a page. -We'll describe each one briefly in the following sections. +We will describe each one briefly in the following sections. .. note:: - There is nothing special about the filename ``views.py``. A project may - have many view callables throughout its codebase in arbitrarily named - files. Files implementing view callables often have ``view`` in their - filenames (or may live in a Python subpackage of your application package - named ``views``), but this is only by convention. + There is nothing special about the filename ``views.py``. + A project may have many view callables throughout its codebase in arbitrarily named files. + Files that implement view callables often have ``view`` in their names (or may live in a Python subpackage of your application package named ``views``), but this is only by convention. + The ``view_wiki`` view function ------------------------------- diff --git a/docs/tutorials/wiki/src/views/tutorial/views/__init__.py b/docs/tutorials/wiki/src/views/tutorial/views/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/docs/tutorials/wiki/src/views/tutorial/views/default.py b/docs/tutorials/wiki/src/views/tutorial/views/default.py new file mode 100644 index 000000000..dfc42c96a --- /dev/null +++ b/docs/tutorials/wiki/src/views/tutorial/views/default.py @@ -0,0 +1,60 @@ +from docutils.core import publish_parts +import re + +from pyramid.httpexceptions import HTTPFound +from pyramid.view import view_config + +from .models import Page + +# regular expression used to find WikiWords +wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)") + +@view_config(context='.models.Wiki') +def view_wiki(context, request): + return HTTPFound(location=request.resource_url(context, 'FrontPage')) + +@view_config(context='.models.Page', renderer='templates/view.pt') +def view_page(context, request): + wiki = context.__parent__ + + def check(match): + word = match.group(1) + if word in wiki: + page = wiki[word] + view_url = request.resource_url(page) + return '%s' % (view_url, word) + else: + add_url = request.application_url + '/add_page/' + word + return '%s' % (add_url, word) + + content = publish_parts(context.data, writer_name='html')['html_body'] + content = wikiwords.sub(check, content) + edit_url = request.resource_url(context, 'edit_page') + return dict(page=context, content=content, edit_url=edit_url) + +@view_config(name='add_page', context='.models.Wiki', + renderer='templates/edit.pt') +def add_page(context, request): + pagename = request.subpath[0] + if 'form.submitted' in request.params: + body = request.params['body'] + page = Page(body) + page.__name__ = pagename + page.__parent__ = context + context[pagename] = page + return HTTPFound(location=request.resource_url(page)) + save_url = request.resource_url(context, 'add_page', pagename) + page = Page('') + page.__name__ = pagename + page.__parent__ = context + return dict(page=page, save_url=save_url) + +@view_config(name='edit_page', context='.models.Page', + renderer='templates/edit.pt') +def edit_page(context, request): + if 'form.submitted' in request.params: + context.data = request.params['body'] + return HTTPFound(location=request.resource_url(context)) + + return dict(page=context, + save_url=request.resource_url(context, 'edit_page')) diff --git a/docs/tutorials/wiki/src/views/tutorial/views/notfound.py b/docs/tutorials/wiki/src/views/tutorial/views/notfound.py new file mode 100644 index 000000000..728791d0a --- /dev/null +++ b/docs/tutorials/wiki/src/views/tutorial/views/notfound.py @@ -0,0 +1,7 @@ +from pyramid.view import notfound_view_config + + +@notfound_view_config(renderer='../templates/404.pt') +def notfound_view(request): + request.response.status = 404 + return {} -- cgit v1.2.3 From af7c9c4a94aa26e910efb5177aa48bdf7e3c711c Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 24 Nov 2018 04:42:21 -0800 Subject: Update view_wiki narrative --- docs/tutorials/wiki/definingviews.rst | 55 +++++++++++++++-------------------- 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/docs/tutorials/wiki/definingviews.rst b/docs/tutorials/wiki/definingviews.rst index 7773fbe0f..5a6b6c8e9 100644 --- a/docs/tutorials/wiki/definingviews.rst +++ b/docs/tutorials/wiki/definingviews.rst @@ -107,45 +107,38 @@ The ``view_wiki`` view function Following is the code for the ``view_wiki`` view function and its decorator: -.. literalinclude:: src/views/tutorial/views.py +.. literalinclude:: src/views/tutorial/views/default.py :lines: 12-14 :lineno-match: :language: python -.. note:: In our code, we use an *import* that is *relative* to our package - named ``tutorial``, meaning we can omit the name of the package in the - ``import`` and ``context`` statements. In our narrative, however, we refer - to a *class* and thus we use the *absolute* form, meaning that the name of - the package is included. - -``view_wiki()`` is the :term:`default view` that gets called when a request is -made to the root URL of our wiki. It always redirects to an URL which -represents the path to our "FrontPage". - -We provide it with a ``@view_config`` decorator which names the class -``tutorial.models.Wiki`` as its context. This means that when a Wiki resource -is the context and no :term:`view name` exists in the request, then this view -will be used. The view configuration associated with ``view_wiki`` does not -use a ``renderer`` because the view callable always returns a :term:`response` -object rather than a dictionary. No renderer is necessary when a view returns -a response object. - -The ``view_wiki`` view callable always redirects to the URL of a Page resource -named "FrontPage". To do so, it returns an instance of the -:class:`pyramid.httpexceptions.HTTPFound` class (instances of which implement -the :class:`pyramid.interfaces.IResponse` interface, like -:class:`pyramid.response.Response` does). It uses the -:meth:`pyramid.request.Request.route_url` API to construct an URL to the -``FrontPage`` page resource (i.e., ``http://localhost:6543/FrontPage``), and -uses it as the "location" of the ``HTTPFound`` response, forming an HTTP -redirect. +.. note:: + + In our code, we use an *import* that is *relative* to our package named ``tutorial``. + This means we can omit the name of the package in the ``import`` and ``context`` statements. + In our narrative, however, we refer to a *class* and thus we use the *absolute* form. + This means that the name of the package is included. + +``view_wiki()`` is the :term:`default view` that gets called when a request is made to the root URL of our wiki. +It always redirects to an URL which represents the path to our ``FrontPage``. + +We provide it with a ``@view_config`` decorator which names the class ``tutorial.models.Wiki`` as its context. +This means that when a ``Wiki`` resource is the context and no :term:`view name` exists in the request, then this view will be used. +The view configuration associated with ``view_wiki`` does not use a ``renderer`` because the view callable always returns a :term:`response` object rather than a dictionary. +No renderer is necessary when a view returns a response object. + +The ``view_wiki`` view callable always redirects to the URL of a ``Page`` resource named ``FrontPage``. +To do so, it returns an instance of the :class:`pyramid.httpexceptions.HTTPFound` class. +Instances of this class implement the :class:`pyramid.interfaces.IResponse` interface, similar to :class:`pyramid.response.Response`. +It uses the :meth:`pyramid.request.Request.route_url` API to construct an URL to the ``FrontPage`` page resource (in other words, ``http://localhost:6543/FrontPage``), and uses it as the ``location`` of the ``HTTPFound`` response, forming an HTTP redirect. + The ``view_page`` view function ------------------------------- Here is the code for the ``view_page`` view function and its decorator: -.. literalinclude:: src/views/tutorial/views.py +.. literalinclude:: src/views/tutorial/views/default.py :lines: 16-33 :lineno-match: :language: python @@ -198,7 +191,7 @@ The ``add_page`` view function Here is the code for the ``add_page`` view function and its decorator: -.. literalinclude:: src/views/tutorial/views.py +.. literalinclude:: src/views/tutorial/views/default.py :lines: 35-50 :lineno-match: :language: python @@ -252,7 +245,7 @@ The ``edit_page`` view function Here is the code for the ``edit_page`` view function and its decorator: -.. literalinclude:: src/views/tutorial/views.py +.. literalinclude:: src/views/tutorial/views/default.py :lines: 52-60 :lineno-match: :language: python -- cgit v1.2.3 From bed4277cdf2469709c078c6b0991a0975028f197 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 24 Nov 2018 05:12:56 -0800 Subject: Add trailing slashes to intersphinx in hopes of fixing failed docs builds --- docs/conf.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 15cffb1eb..3b7eb261a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -61,26 +61,26 @@ extensions = [ # Looks for objects in external projects intersphinx_mapping = { - 'colander': ('https://docs.pylonsproject.org/projects/colander/en/latest', None), + 'colander': ('https://docs.pylonsproject.org/projects/colander/en/latest/', None), 'cookbook': ('https://docs.pylonsproject.org/projects/pyramid-cookbook/en/latest/', None), 'cookiecutter': ('https://cookiecutter.readthedocs.io/en/latest/', None), - 'deform': ('https://docs.pylonsproject.org/projects/deform/en/latest', None), + 'deform': ('https://docs.pylonsproject.org/projects/deform/en/latest/', None), 'jinja2': ('https://docs.pylonsproject.org/projects/pyramid-jinja2/en/latest/', None), 'plaster': ('https://docs.pylonsproject.org/projects/plaster/en/latest/', None), 'pylonswebframework': ('https://docs.pylonsproject.org/projects/pylons-webframework/en/latest/', None), - 'python': ('https://docs.python.org/3', None), + 'python': ('https://docs.python.org/3/', None), 'pytest': ('https://docs.pytest.org/en/latest/', None), - 'sphinx': ('http://www.sphinx-doc.org/en/latest', None), - 'sqla': ('https://docs.sqlalchemy.org/en/latest', None), + 'sphinx': ('http://www.sphinx-doc.org/en/latest/', None), + 'sqla': ('https://docs.sqlalchemy.org/en/latest/', None), 'tm': ('https://docs.pylonsproject.org/projects/pyramid-tm/en/latest/', None), - 'toolbar': ('https://docs.pylonsproject.org/projects/pyramid-debugtoolbar/en/latest', None), - 'tstring': ('https://docs.pylonsproject.org/projects/translationstring/en/latest', None), + 'toolbar': ('https://docs.pylonsproject.org/projects/pyramid-debugtoolbar/en/latest/', None), + 'tstring': ('https://docs.pylonsproject.org/projects/translationstring/en/latest/', None), 'tutorials': ('https://docs.pylonsproject.org/projects/pyramid-tutorials/en/latest/', None), - 'venusian': ('https://docs.pylonsproject.org/projects/venusian/en/latest', None), + 'venusian': ('https://docs.pylonsproject.org/projects/venusian/en/latest/', None), 'webob': ('https://docs.pylonsproject.org/projects/webob/en/latest/', None), 'webtest': ('https://docs.pylonsproject.org/projects/webtest/en/latest/', None), - 'who': ('https://repozewho.readthedocs.io/en/latest', None), - 'zcml': ('https://docs.pylonsproject.org/projects/pyramid-zcml/en/latest', None), + 'who': ('https://repozewho.readthedocs.io/en/latest/', None), + 'zcml': ('https://docs.pylonsproject.org/projects/pyramid-zcml/en/latest/', None), 'zcomponent': ('https://zopecomponent.readthedocs.io/en/latest/', None), 'zinterface': ('https://zopeinterface.readthedocs.io/en/latest/', None), } -- cgit v1.2.3 From 90036b6778992f896ccf4aa52c241fafb1c82f24 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 24 Nov 2018 05:30:47 -0800 Subject: Revise view_page narrative --- docs/tutorials/wiki/definingviews.rst | 70 ++++++++++++++--------------------- 1 file changed, 28 insertions(+), 42 deletions(-) diff --git a/docs/tutorials/wiki/definingviews.rst b/docs/tutorials/wiki/definingviews.rst index 5a6b6c8e9..03bdd1080 100644 --- a/docs/tutorials/wiki/definingviews.rst +++ b/docs/tutorials/wiki/definingviews.rst @@ -143,48 +143,34 @@ Here is the code for the ``view_page`` view function and its decorator: :lineno-match: :language: python -The ``view_page`` function is configured to respond as the default view -of a Page resource. We provide it with a ``@view_config`` decorator which -names the class ``tutorial.models.Page`` as its context. This means that -when a Page resource is the context, and no :term:`view name` exists in the -request, this view will be used. We inform :app:`Pyramid` this view will use -the ``templates/view.pt`` template file as a ``renderer``. - -The ``view_page`` function generates the :term:`reStructuredText` body of a -page (stored as the ``data`` attribute of the context passed to the view; the -context will be a ``Page`` resource) as HTML. Then it substitutes an HTML -anchor for each *WikiWord* reference in the rendered HTML using a compiled -regular expression. - -The curried function named ``check`` is used as the first argument to -``wikiwords.sub``, indicating that it should be called to provide a value for -each WikiWord match found in the content. If the wiki (our page's -``__parent__``) already contains a page with the matched WikiWord name, the -``check`` function generates a view link to be used as the substitution value -and returns it. If the wiki does not already contain a page with the -matched WikiWord name, the function generates an "add" link as the -substitution value and returns it. - -As a result, the ``content`` variable is now a fully formed bit of HTML -containing various view and add links for WikiWords based on the content of -our current page resource. - -We then generate an edit URL because it's easier to do here than in the -template, and we wrap up a number of arguments in a dictionary and return -it. - -The arguments we wrap into a dictionary include ``page``, ``content``, and -``edit_url``. As a result, the *template* associated with this view callable -(via ``renderer=`` in its configuration) will be able to use these names to -perform various rendering tasks. The template associated with this view -callable will be a template which lives in ``templates/view.pt``. - -Note the contrast between this view callable and the ``view_wiki`` view -callable. In the ``view_wiki`` view callable, we unconditionally return a -:term:`response` object. In the ``view_page`` view callable, we return a -*dictionary*. It is *always* fine to return a :term:`response` object from a -:app:`Pyramid` view. Returning a dictionary is allowed only when there is a -:term:`renderer` associated with the view callable in the view configuration. +The ``view_page`` function is configured to respond as the default view of a ``Page`` resource. +We provide it with a ``@view_config`` decorator which names the class ``tutorial.models.Page`` as its context. +This means that when a ``Page`` resource is the context, and no :term:`view name` exists in the request, this view will be used. +We inform :app:`Pyramid` this view will use the ``templates/view.pt`` template file as a ``renderer``. + +The ``view_page`` function generates the :term:`reStructuredText` body of a page as HTML. +The body is stored as the ``data`` attribute of the context passed to the view. +The context will be a ``Page`` resource. +Then it substitutes an HTML anchor for each *WikiWord* reference in the rendered HTML using a compiled regular expression. + +The curried function named ``check`` is used as the first argument to ``wikiwords.sub``, indicating that it should be called to provide a value for each WikiWord match found in the content. +If the wiki (our page's ``__parent__``) already contains a page with the matched WikiWord name, the ``check`` function generates a view link to be used as the substitution value and returns it. +If the wiki does not already contain a page with the matched WikiWord name, the function generates an "add" link as the substitution value and returns it. + +As a result, the ``content`` variable is now a fully formed bit of HTML containing various view and add links for WikiWords based on the content of our current page resource. + +We then generate an edit URL because it is easier to do here than in the template. +Finally we wrap up a number of arguments in a dictionary and return it. + +The arguments we wrap into a dictionary include ``page``, ``content``, and ``edit_url``. +As a result, the *template* associated with this view callable (via ``renderer=`` in its configuration) will be able to use these names to perform various rendering tasks. +The template associated with this view callable will be a template which lives in ``templates/view.pt``. + +Note the contrast between this view callable and the ``view_wiki`` view callable. +In the ``view_wiki`` view callable, we unconditionally return a :term:`response` object. +In the ``view_page`` view callable, we return a *dictionary*. It is *always* fine to return a :term:`response` object from a :app:`Pyramid` view. +Returning a dictionary is allowed only when there is a :term:`renderer` associated with the view callable in the view configuration. + The ``add_page`` view function ------------------------------ -- cgit v1.2.3 From 3d77f50790bdd5a63513c82b53d677dfe31c6494 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 24 Nov 2018 11:44:28 -0800 Subject: Add a note for resources and traversal chapters. --- docs/tutorials/wiki/definingmodels.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/tutorials/wiki/definingmodels.rst b/docs/tutorials/wiki/definingmodels.rst index 81dd25862..2ea81b005 100644 --- a/docs/tutorials/wiki/definingmodels.rst +++ b/docs/tutorials/wiki/definingmodels.rst @@ -10,9 +10,14 @@ We will do this inside our ``models.py`` file. Because we are using :term:`ZODB` to represent our :term:`resource tree`, each of these resource constructors represents a :term:`domain model` object. We will call these constructors "model constructors". -Both our Page and Wiki constructors will be class objects. +Both our ``Page`` and ``Wiki`` constructors will be class objects. A single instance of the "Wiki" class will serve as a container for "Page" objects, which will be instances of the "Page" class. +.. note:: + + We will introduce a lot of concepts throughout the remainder of this tutorial. + See also the chapter :ref:`resources_chapter` for a complete description of resources and the chapter :ref:`traversal_chapter` for the technical details of how traversal works in Pyramid. + Delete the database ------------------- -- cgit v1.2.3 From abba3f32fa3a0e91b2780724123e63b0ce38adc6 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 24 Nov 2018 11:46:04 -0800 Subject: Update models and view application sections --- docs/tutorials/wiki/definingviews.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/tutorials/wiki/definingviews.rst b/docs/tutorials/wiki/definingviews.rst index 03bdd1080..625716de9 100644 --- a/docs/tutorials/wiki/definingviews.rst +++ b/docs/tutorials/wiki/definingviews.rst @@ -21,6 +21,11 @@ A view callable is assumed to return a :term:`response` object. We will define several :term:`view callable` functions, then wire them into :app:`Pyramid` using some :term:`view configuration`. +.. note:: + + This chapter will introduce more concepts, as did the previous. + See also the chapter :ref:`resources_chapter` for a complete description of resources and the chapter :ref:`traversal_chapter` for the technical details of how traversal works in Pyramid. + Declaring Dependencies in Our ``setup.py`` File =============================================== -- cgit v1.2.3 From af62fedf04d9f7e3c5eec98bc018da6ad2b7d88b Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 24 Nov 2018 11:46:52 -0800 Subject: Use double backticks on objects --- docs/tutorials/wiki/definingviews.rst | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/tutorials/wiki/definingviews.rst b/docs/tutorials/wiki/definingviews.rst index 625716de9..d7575e815 100644 --- a/docs/tutorials/wiki/definingviews.rst +++ b/docs/tutorials/wiki/definingviews.rst @@ -158,11 +158,11 @@ The body is stored as the ``data`` attribute of the context passed to the view. The context will be a ``Page`` resource. Then it substitutes an HTML anchor for each *WikiWord* reference in the rendered HTML using a compiled regular expression. -The curried function named ``check`` is used as the first argument to ``wikiwords.sub``, indicating that it should be called to provide a value for each WikiWord match found in the content. -If the wiki (our page's ``__parent__``) already contains a page with the matched WikiWord name, the ``check`` function generates a view link to be used as the substitution value and returns it. -If the wiki does not already contain a page with the matched WikiWord name, the function generates an "add" link as the substitution value and returns it. +The curried function named ``check`` is used as the first argument to ``wikiwords.sub``, indicating that it should be called to provide a value for each ``WikiWord`` match found in the content. +If the wiki (our page's ``__parent__``) already contains a page with the matched ``WikiWord`` name, the ``check`` function generates a view link to be used as the substitution value and returns it. +If the wiki does not already contain a page with the matched ``WikiWord`` name, the function generates an "add" link as the substitution value and returns it. -As a result, the ``content`` variable is now a fully formed bit of HTML containing various view and add links for WikiWords based on the content of our current page resource. +As a result, the ``content`` variable is now a fully formed bit of HTML containing various view and add links for ``WikiWord``s based on the content of our current page resource. We then generate an edit URL because it is easier to do here than in the template. Finally we wrap up a number of arguments in a dictionary and return it. @@ -242,11 +242,11 @@ Here is the code for the ``edit_page`` view function and its decorator: :language: python The ``edit_page`` function is configured to respond when the context is -a Page resource and the :term:`view name` is ``edit_page``. We provide it +a ``Page`` resource and the :term:`view name` is ``edit_page``. We provide it with a ``@view_config`` decorator which names the string ``edit_page`` as its :term:`view name` (via ``name=``), the class ``tutorial.models.Page`` as its context, and the renderer named ``templates/edit.pt``. This means that when -a Page resource is the context, and a :term:`view name` exists as the result +a ``Page`` resource is the context, and a :term:`view name` exists as the result of traversal named ``edit_page``, this view will be used. We inform :app:`Pyramid` this view will use the ``templates/edit.pt`` template file as a ``renderer``. @@ -254,7 +254,7 @@ a ``renderer``. The ``edit_page`` function will be invoked when a user clicks the "Edit this Page" button on the view form. It renders an edit form but it also acts as the form post view callable for the form it renders. The ``context`` of the -``edit_page`` view will *always* be a Page resource (never a Wiki resource). +``edit_page`` view will *always* be a ``Page`` resource (never a ``Wiki`` resource). If the view execution is *not* a result of a form submission (if the expression ``'form.submitted' in request.params`` is ``False``), the view @@ -345,16 +345,16 @@ We can finally examine our application in a browser (See each of the following URLs, checking that the result is as expected: - http://localhost:6543/ invokes the ``view_wiki`` view. This always - redirects to the ``view_page`` view of the ``FrontPage`` Page resource. + redirects to the ``view_page`` view of the ``FrontPage`` ``Page`` resource. - http://localhost:6543/FrontPage/ invokes the ``view_page`` view of the front page resource. This is because it's the :term:`default view` (a view - without a ``name``) for Page resources. + without a ``name``) for ``Page`` resources. - http://localhost:6543/FrontPage/edit_page invokes the edit view for the - ``FrontPage`` Page resource. + ``FrontPage`` ``Page`` resource. -- http://localhost:6543/add_page/SomePageName invokes the add view for a Page. +- http://localhost:6543/add_page/SomePageName invokes the add view for a ``Page``. - To generate an error, visit http://localhost:6543/add_page which will generate an ``IndexError: tuple index out of range`` error. You'll see an -- cgit v1.2.3 From b33a1777e5347677d1ee0dcec2a5da277ee7d094 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 24 Nov 2018 11:51:38 -0800 Subject: Revise the add_page view section narrative --- docs/tutorials/wiki/definingviews.rst | 80 ++++++++++++++++------------------- 1 file changed, 37 insertions(+), 43 deletions(-) diff --git a/docs/tutorials/wiki/definingviews.rst b/docs/tutorials/wiki/definingviews.rst index d7575e815..449b9d9c2 100644 --- a/docs/tutorials/wiki/definingviews.rst +++ b/docs/tutorials/wiki/definingviews.rst @@ -187,49 +187,43 @@ Here is the code for the ``add_page`` view function and its decorator: :lineno-match: :language: python -The ``add_page`` function is configured to respond when the context resource -is a Wiki and the :term:`view name` is ``add_page``. We provide it with a -``@view_config`` decorator which names the string ``add_page`` as its -:term:`view name` (via ``name=``), the class ``tutorial.models.Wiki`` as its -context, and the renderer named ``templates/edit.pt``. This means that when a -Wiki resource is the context, and a :term:`view name` named ``add_page`` -exists as the result of traversal, this view will be used. We inform -:app:`Pyramid` this view will use the ``templates/edit.pt`` template file as a -``renderer``. We share the same template between add and edit views, thus -``edit.pt`` instead of ``add.pt``. - -The ``add_page`` function will be invoked when a user clicks on a WikiWord -which isn't yet represented as a page in the system. The ``check`` function -within the ``view_page`` view generates URLs to this view. It also acts as a -handler for the form that is generated when we want to add a page resource. -The ``context`` of the ``add_page`` view is always a Wiki resource (*not* a -Page resource). - -The request :term:`subpath` in :app:`Pyramid` is the sequence of names that -are found *after* the :term:`view name` in the URL segments given in the -``PATH_INFO`` of the WSGI request as the result of :term:`traversal`. If our -add view is invoked via, e.g., ``http://localhost:6543/add_page/SomeName``, -the :term:`subpath` will be a tuple: ``('SomeName',)``. - -The add view takes the zero\ :sup:`th` element of the subpath (the wiki page name), -and aliases it to the name attribute in order to know the name of the page -we're trying to add. - -If the view rendering is *not* a result of a form submission (if the -expression ``'form.submitted' in request.params`` is ``False``), the view -renders a template. To do so, it generates a "save url" which the template -uses as the form post URL during rendering. We're lazy here, so we're trying -to use the same template (``templates/edit.pt``) for the add view as well as -the page edit view. To do so, we create a dummy Page resource object in -order to satisfy the edit form's desire to have *some* page object exposed as -``page``, and we'll render the template to a response. - -If the view rendering *is* a result of a form submission (if the expression -``'form.submitted' in request.params`` is ``True``), we grab the page body -from the form data, create a Page object using the name in the subpath and -the page body, and save it into "our context" (the Wiki) using the -``__setitem__`` method of the context. We then redirect back to the -``view_page`` view (the default view for a page) for the newly created page. +The ``add_page`` function is configured to respond when the context resource is a ``Wiki`` and the :term:`view name` is ``add_page``. +We provide it with a ``@view_config`` decorator which names the string ``add_page`` as its :term:`view name` (via ``name=``), the class ``tutorial.models.Wiki`` as its context, and the renderer named ``templates/edit.pt``. +This means that when a ``Wiki`` resource is the context, and a :term:`view name` named ``add_page`` exists as the result of traversal, then this view will be used. +We inform :app:`Pyramid` this view will use the ``templates/edit.pt`` template file as a ``renderer``. +We share the same template between add and edit views, thus ``edit.pt`` instead of ``add.pt``. + +The ``add_page`` function will be invoked when a user clicks on a ``WikiWord`` that is not yet represented as a page in the system. +The ``check`` function within the ``view_page`` view generates URLs to this view. +It also acts as a handler for the form that is generated when we want to add a page resource. +The ``context`` of the ``add_page`` view is always a ``Wiki`` resource (*not* a ``Page`` resource). + +The request :term:`subpath` in :app:`Pyramid` is the sequence of names that are found *after* the :term:`view name` in the URL segments given in the ``PATH_INFO`` of the WSGI request as the result of :term:`traversal`. +If our add view is invoked via, for example, ``http://localhost:6543/add_page/SomeName``, then the :term:`subpath` will be a tuple ``('SomeName',)``. + +The add view takes the zero\ :sup:`th` element of the subpath (the wiki page name), then aliases it to the name attribute to know the name of the page we are trying to add. + +If the view rendering is *not* a result of a form submission (if the expression ``'form.submitted' in request.params`` is ``False``), then the view renders a template. +To do so, it generates a ``save_url`` which the template uses as the form post URL during rendering. +We are lazy here, so we try to use the same template (``templates/edit.pt``) for both the add and edit views. +To do so, we create a dummy ``Page`` resource object to satisfy the edit form's desire to have *some* page object exposed as ``page``. +We then set the ``Page`` object's ``__name__`` and ``__parent__``. +Then we will render the template to a response. + +If the view rendering *is* a result of a form submission (if the expression ``'form.submitted' in request.params`` is ``True``), then do the following: + +- Grab the page body from the form data as ``body``. +- Create a ``Page`` object using the name in the subpath and the page body as ``page``. +- Set the ``Page`` object's ``__name__`` and ``__parent__``. +- Save it into "our context" (the ``Wiki``) using the ``__setitem__`` method of the context. +- We then redirect back to the ``view_page`` view (the default view for a page) for the newly created page. + +.. seealso:: + + In the :ref:`previous chapter `, we mentioned that all objects in a traversal graph must have a ``__name__`` and a ``__parent__`` attribute. + That provides location awareness for resources. + See also the section on :ref:`location-aware-resources` in the :ref:`resources_chapter` chapter for a complete discussion. + The ``edit_page`` view function ------------------------------- -- cgit v1.2.3 From 07c5639c50814320e572d1bb478854bc4fcd33cc Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 24 Nov 2018 11:56:30 -0800 Subject: Use proper reST syntax and directives --- docs/tutorials/wiki/definingmodels.rst | 2 +- docs/tutorials/wiki/definingviews.rst | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/tutorials/wiki/definingmodels.rst b/docs/tutorials/wiki/definingmodels.rst index 2ea81b005..3a340e6f7 100644 --- a/docs/tutorials/wiki/definingmodels.rst +++ b/docs/tutorials/wiki/definingmodels.rst @@ -13,7 +13,7 @@ We will call these constructors "model constructors". Both our ``Page`` and ``Wiki`` constructors will be class objects. A single instance of the "Wiki" class will serve as a container for "Page" objects, which will be instances of the "Page" class. -.. note:: +.. seealso:: We will introduce a lot of concepts throughout the remainder of this tutorial. See also the chapter :ref:`resources_chapter` for a complete description of resources and the chapter :ref:`traversal_chapter` for the technical details of how traversal works in Pyramid. diff --git a/docs/tutorials/wiki/definingviews.rst b/docs/tutorials/wiki/definingviews.rst index 449b9d9c2..8d291a5e7 100644 --- a/docs/tutorials/wiki/definingviews.rst +++ b/docs/tutorials/wiki/definingviews.rst @@ -21,7 +21,7 @@ A view callable is assumed to return a :term:`response` object. We will define several :term:`view callable` functions, then wire them into :app:`Pyramid` using some :term:`view configuration`. -.. note:: +.. seealso:: This chapter will introduce more concepts, as did the previous. See also the chapter :ref:`resources_chapter` for a complete description of resources and the chapter :ref:`traversal_chapter` for the technical details of how traversal works in Pyramid. @@ -162,7 +162,7 @@ The curried function named ``check`` is used as the first argument to ``wikiword If the wiki (our page's ``__parent__``) already contains a page with the matched ``WikiWord`` name, the ``check`` function generates a view link to be used as the substitution value and returns it. If the wiki does not already contain a page with the matched ``WikiWord`` name, the function generates an "add" link as the substitution value and returns it. -As a result, the ``content`` variable is now a fully formed bit of HTML containing various view and add links for ``WikiWord``s based on the content of our current page resource. +As a result, the ``content`` variable is now a fully formed bit of HTML containing various view and add links for ``WikiWord``\s based on the content of our current page resource. We then generate an edit URL because it is easier to do here than in the template. Finally we wrap up a number of arguments in a dictionary and return it. @@ -222,7 +222,7 @@ If the view rendering *is* a result of a form submission (if the expression ``'f In the :ref:`previous chapter `, we mentioned that all objects in a traversal graph must have a ``__name__`` and a ``__parent__`` attribute. That provides location awareness for resources. - See also the section on :ref:`location-aware-resources` in the :ref:`resources_chapter` chapter for a complete discussion. + See also the section on :ref:`location_aware` in the :ref:`resources_chapter` chapter for a complete discussion. The ``edit_page`` view function -- cgit v1.2.3 From b4c32b1763e53d5fba939f6abd0111c81561a3c7 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 25 Nov 2018 01:36:54 -0800 Subject: Rewrap edit_page view section --- docs/tutorials/wiki/definingviews.rst | 40 +++++++++++++---------------------- 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/docs/tutorials/wiki/definingviews.rst b/docs/tutorials/wiki/definingviews.rst index 8d291a5e7..164bc4326 100644 --- a/docs/tutorials/wiki/definingviews.rst +++ b/docs/tutorials/wiki/definingviews.rst @@ -235,31 +235,21 @@ Here is the code for the ``edit_page`` view function and its decorator: :lineno-match: :language: python -The ``edit_page`` function is configured to respond when the context is -a ``Page`` resource and the :term:`view name` is ``edit_page``. We provide it -with a ``@view_config`` decorator which names the string ``edit_page`` as its -:term:`view name` (via ``name=``), the class ``tutorial.models.Page`` as its -context, and the renderer named ``templates/edit.pt``. This means that when -a ``Page`` resource is the context, and a :term:`view name` exists as the result -of traversal named ``edit_page``, this view will be used. We inform -:app:`Pyramid` this view will use the ``templates/edit.pt`` template file as -a ``renderer``. - -The ``edit_page`` function will be invoked when a user clicks the "Edit this -Page" button on the view form. It renders an edit form but it also acts as -the form post view callable for the form it renders. The ``context`` of the -``edit_page`` view will *always* be a ``Page`` resource (never a ``Wiki`` resource). - -If the view execution is *not* a result of a form submission (if the -expression ``'form.submitted' in request.params`` is ``False``), the view -simply renders the edit form, passing the page resource, and a ``save_url`` -which will be used as the action of the generated form. - -If the view execution *is* a result of a form submission (if the expression -``'form.submitted' in request.params`` is ``True``), the view grabs the -``body`` element of the request parameter and sets it as the ``data`` -attribute of the page context. It then redirects to the default view of the -context (the page), which will always be the ``view_page`` view. +The ``edit_page`` function is configured to respond when the context is a ``Page`` resource and the :term:`view name` is ``edit_page``. +We provide it with a ``@view_config`` decorator which names the string ``edit_page`` as its :term:`view name` (via ``name=``), the class ``tutorial.models.Page`` as its context, and the renderer named ``templates/edit.pt``. +This means that when a ``Page`` resource is the context, and a :term:`view name` exists as the result of traversal named ``edit_page``, this view will be used. +We inform :app:`Pyramid` this view will use the ``templates/edit.pt`` template file as a ``renderer``. + +The ``edit_page`` function will be invoked when a user clicks the "Edit this Page" button on the view form. +It renders an edit form. +It also acts as the form post view callable for the form it renders. +The ``context`` of the ``edit_page`` view will *always* be a ``Page`` resource (never a ``Wiki`` resource). + +If the view execution is *not* a result of a form submission (if the expression ``'form.submitted' in request.params`` is ``False``), then the view renders the edit form, passing the page resource, and a ``save_url`` which will be used as the action of the generated form. + +If the view execution *is* a result of a form submission (if the expression ``'form.submitted' in request.params`` is ``True``), the view grabs the ``body`` element of the request parameter and sets it as the ``data`` attribute of the page context. +It then redirects to the default view of the context (the page), which will always be the ``view_page`` view. + Adding templates ================ -- cgit v1.2.3 From faa25d660effa044c0324aded75a37b225299a9b Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 25 Nov 2018 03:30:08 -0800 Subject: Clean up templates narrative. --- docs/tutorials/wiki/definingviews.rst | 120 +++++++++++++++++++--------------- 1 file changed, 69 insertions(+), 51 deletions(-) diff --git a/docs/tutorials/wiki/definingviews.rst b/docs/tutorials/wiki/definingviews.rst index 164bc4326..7d3c65e58 100644 --- a/docs/tutorials/wiki/definingviews.rst +++ b/docs/tutorials/wiki/definingviews.rst @@ -76,8 +76,8 @@ Success executing this command will end with a line to the console similar to th Successfully installed docutils-0.14 tutorial -Adding view functions in ``views.py`` -===================================== +Adding view functions in the ``views`` package +============================================== It is time for a major change. Open ``tutorial/views/default.py`` and edit it to look like the following: @@ -162,12 +162,12 @@ The curried function named ``check`` is used as the first argument to ``wikiword If the wiki (our page's ``__parent__``) already contains a page with the matched ``WikiWord`` name, the ``check`` function generates a view link to be used as the substitution value and returns it. If the wiki does not already contain a page with the matched ``WikiWord`` name, the function generates an "add" link as the substitution value and returns it. -As a result, the ``content`` variable is now a fully formed bit of HTML containing various view and add links for ``WikiWord``\s based on the content of our current page resource. +As a result, the ``page_text`` variable is now a fully formed bit of HTML containing various view and add links for ``WikiWord``\s based on the content of our current page resource. We then generate an edit URL because it is easier to do here than in the template. Finally we wrap up a number of arguments in a dictionary and return it. -The arguments we wrap into a dictionary include ``page``, ``content``, and ``edit_url``. +The arguments we wrap into a dictionary include ``page``, ``page_text``, and ``edit_url``. As a result, the *template* associated with this view callable (via ``renderer=`` in its configuration) will be able to use these names to perform various rendering tasks. The template associated with this view callable will be a template which lives in ``templates/view.pt``. @@ -254,11 +254,36 @@ It then redirects to the default view of the context (the page), which will alwa Adding templates ================ -The ``view_page``, ``add_page`` and ``edit_page`` views that we've added -reference a :term:`template`. Each template is a :term:`Chameleon` -:term:`ZPT` template. These templates will live in the ``templates`` -directory of our tutorial package. Chameleon templates must have a ``.pt`` -extension to be recognized as such. +The ``view_page``, ``add_page``, and ``edit_page`` views that we added reference a :term:`template`. +Each template is a :term:`Chameleon` :term:`ZPT` template. +These templates will live in the ``templates`` directory of our tutorial package. +Chameleon templates must have a ``.pt`` extension to be recognized as such. + + +The ``layout.pt`` template +-------------------------- + +Update ``tutorial/templates/layout.pt`` with the following content, as indicated by the emphasized lines: + +.. literalinclude:: src/views/tutorial/templates/layout.pt + :linenos: + :emphasize-lines: 11-12 + :language: html + +Since we are using a templating engine, we can factor common boilerplate out of our page templates into reusable components. +We can do this via :term:`METAL` macros and slots. + +- The cookiecutter defined a macro named ``layout`` (Line 1). + This macro consists of the entire document. +- The cookiecutter defined a macro customization point or `slot` (Line 36). + This slot is inside the macro ``layout``. + Therefore it can be replaced by content, customizing the macro. +- We removed the row of icons and links from the original cookiecutter. + +.. seealso:: + + Please refer to the Chameleon documentation for more information about using `METAL `_ for defining and using macros and slots. + The ``view.pt`` template ------------------------ @@ -268,16 +293,19 @@ Rename ``tutorial/templates/mytemplate.pt`` to ``tutorial/templates/view.pt`` an .. literalinclude:: src/views/tutorial/templates/view.pt :linenos: :language: html - :emphasize-lines: 11-12,37-52 + :emphasize-lines: 5-19 This template is used by ``view_page()`` for displaying a single wiki page. It includes: -- A ``div`` element that is replaced with the ``content`` value provided by - the view (lines 37-39). ``content`` contains HTML, so the ``structure`` - keyword is used to prevent escaping it (i.e., changing ">" to ">", etc.) -- A link that points at the "edit" URL which invokes the ``edit_page`` view - for the page being viewed (lines 41-43). +- The use of a macro to load the entire template ``layout.pt``. +- The template fills the slot named ``content`` (line 2) with a ``div`` element. +- A ``div`` element that is replaced with the ``page_text`` value provided by the view (line 5). + ``page_text`` contains HTML, so the ``structure`` keyword is used to prevent escaping HTML entities, such as changing ``>`` to ``>``. +- A link that points at the "edit" URL, which invokes the ``edit_page`` view for the page being viewed (lines 9-11). +- A ``span`` whose content is replaced by the name of the page, if present. +- A link to the FrontPage. + The ``edit.pt`` template ------------------------ @@ -288,58 +316,48 @@ Copy ``tutorial/templates/view.pt`` to ``tutorial/templates/edit.pt`` and edit t :linenos: :language: html -This template is used by ``add_page()`` and ``edit_page()`` for adding and -editing a wiki page. It displays a page containing a form that includes: +This template is used by ``add_page()`` and ``edit_page()`` for adding and editing a wiki page. +It displays a page containing a form that includes: + +- A 10-row by 60-column ``textarea`` field named ``body`` that is filled with any existing page data when it is rendered (lines 14-15). +- A submit button that has the name ``form.submitted`` (line 18). -- A 10-row by 60-column ``textarea`` field named ``body`` that is filled - with any existing page data when it is rendered (line 46). -- A submit button that has the name ``form.submitted`` (line 49). +When submitted, the form sends a POST request to the ``save_url`` argument supplied by the view (line 12). +The view will use the ``body`` and ``form.submitted`` values. -The form POSTs back to the ``save_url`` argument supplied by the view (line -44). The view will use the ``body`` and ``form.submitted`` values. +.. note:: -.. note:: Our templates use a ``request`` object that none of our tutorial - views return in their dictionary. ``request`` is one of several names that - are available "by default" in a template when a template renderer is used. - See :ref:`renderer_system_values` for information about other names that - are available by default when a template is used as a renderer. + Our templates use a ``request`` object that none of our tutorial views return in their dictionary. + ``request`` is one of several names that are available "by default" in a template when a template renderer is used. + See :ref:`renderer_system_values` for information about other names that are available by default when a template is used as a renderer. Static assets ------------- -Our templates name static assets, including CSS and images. We don't need -to create these files within our package's ``static`` directory because they -were provided at the time we created the project. +Our templates name static assets, including CSS and images. +We don't need to create these files within our package's ``static`` directory because they were provided by the cookiecutter at the time we created the project. -As an example, the CSS file will be accessed via -``http://localhost:6543/static/theme.css`` by virtue of the call to the -``add_static_view`` directive we've made in the ``__init__.py`` file. Any -number and type of static assets can be placed in this directory (or -subdirectories) and are just referred to by URL or by using the convenience -method ``static_url``, e.g., -``request.static_url(':static/foo.css')`` within templates. +As an example, the CSS file will be accessed via ``http://localhost:6543/static/theme.css`` by virtue of the call to the ``add_static_view`` directive in the ``routes.py`` file. +Any number and type of static assets can be placed in this directory (or subdirectories) +They are referred to by either URL or using the convenience method ``static_url``, for example ``request.static_url(':static/foo.css')``, within templates. Viewing the application in a browser ==================================== -We can finally examine our application in a browser (See -:ref:`wiki-start-the-application`). Launch a browser and visit -each of the following URLs, checking that the result is as expected: +We can finally examine our application in a browser (See :ref:`wiki-start-the-application`). +Launch a browser and visit each of the following URLs, checking that the result is as expected: -- http://localhost:6543/ invokes the ``view_wiki`` view. This always - redirects to the ``view_page`` view of the ``FrontPage`` ``Page`` resource. +- http://localhost:6543/ invokes the ``view_wiki`` view. + This always redirects to the ``view_page`` view of the ``FrontPage`` ``Page`` resource. -- http://localhost:6543/FrontPage/ invokes the ``view_page`` view of the front - page resource. This is because it's the :term:`default view` (a view - without a ``name``) for ``Page`` resources. +- http://localhost:6543/FrontPage/ invokes the ``view_page`` view of the front page resource. + This is because it is the :term:`default view` (a view without a ``name``) for ``Page`` resources. -- http://localhost:6543/FrontPage/edit_page invokes the edit view for the - ``FrontPage`` ``Page`` resource. +- http://localhost:6543/FrontPage/edit_page invokes the edit view for the ``FrontPage`` ``Page`` resource. -- http://localhost:6543/add_page/SomePageName invokes the add view for a ``Page``. +- http://localhost:6543/add_page/SomePageName invokes the add view for a ``Page``. -- To generate an error, visit http://localhost:6543/add_page which will - generate an ``IndexError: tuple index out of range`` error. You'll see an - interactive traceback facility provided by :term:`pyramid_debugtoolbar`. +- To generate an error, visit http://localhost:6543/add_page which will generate an ``IndexError: tuple index out of range`` error. + You will see an interactive traceback facility provided by :term:`pyramid_debugtoolbar`. -- cgit v1.2.3 From 9161c6a4a2e5b96043a4b4f9171a6cd2fc7ec337 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 25 Nov 2018 03:35:54 -0800 Subject: Fix imports, contexts, paths to templates, and refactor `content` to `page_text` (there's too many "content" already and is confusing) --- .../wiki/src/views/tutorial/views/default.py | 24 +++++++++++++--------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/docs/tutorials/wiki/src/views/tutorial/views/default.py b/docs/tutorials/wiki/src/views/tutorial/views/default.py index dfc42c96a..b4b65a49b 100644 --- a/docs/tutorials/wiki/src/views/tutorial/views/default.py +++ b/docs/tutorials/wiki/src/views/tutorial/views/default.py @@ -4,16 +4,18 @@ import re from pyramid.httpexceptions import HTTPFound from pyramid.view import view_config -from .models import Page +from ..models import Page # regular expression used to find WikiWords wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)") -@view_config(context='.models.Wiki') + +@view_config(context='..models.Wiki') def view_wiki(context, request): return HTTPFound(location=request.resource_url(context, 'FrontPage')) -@view_config(context='.models.Page', renderer='templates/view.pt') + +@view_config(context='..models.Page', renderer='../templates/view.pt') def view_page(context, request): wiki = context.__parent__ @@ -27,13 +29,14 @@ def view_page(context, request): add_url = request.application_url + '/add_page/' + word return '%s' % (add_url, word) - content = publish_parts(context.data, writer_name='html')['html_body'] - content = wikiwords.sub(check, content) + page_text = publish_parts(context.data, writer_name='html')['html_body'] + page_text = wikiwords.sub(check, page_text) edit_url = request.resource_url(context, 'edit_page') - return dict(page=context, content=content, edit_url=edit_url) + return dict(page=context, page_text=page_text, edit_url=edit_url) -@view_config(name='add_page', context='.models.Wiki', - renderer='templates/edit.pt') + +@view_config(name='add_page', context='..models.Wiki', + renderer='../templates/edit.pt') def add_page(context, request): pagename = request.subpath[0] if 'form.submitted' in request.params: @@ -49,8 +52,9 @@ def add_page(context, request): page.__parent__ = context return dict(page=page, save_url=save_url) -@view_config(name='edit_page', context='.models.Page', - renderer='templates/edit.pt') + +@view_config(name='edit_page', context='..models.Page', + renderer='../templates/edit.pt') def edit_page(context, request): if 'form.submitted' in request.params: context.data = request.params['body'] -- cgit v1.2.3 From db221574993ace9ab0773fe746ff7ae35b474d41 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 25 Nov 2018 03:36:11 -0800 Subject: Delete obsolete views.py --- docs/tutorials/wiki/src/views/tutorial/views.py | 60 ------------------------- 1 file changed, 60 deletions(-) delete mode 100644 docs/tutorials/wiki/src/views/tutorial/views.py diff --git a/docs/tutorials/wiki/src/views/tutorial/views.py b/docs/tutorials/wiki/src/views/tutorial/views.py deleted file mode 100644 index fd2b0edc1..000000000 --- a/docs/tutorials/wiki/src/views/tutorial/views.py +++ /dev/null @@ -1,60 +0,0 @@ -from docutils.core import publish_parts -import re - -from pyramid.httpexceptions import HTTPFound -from pyramid.view import view_config - -from .models import Page - -# regular expression used to find WikiWords -wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)") - -@view_config(context='.models.Wiki') -def view_wiki(context, request): - return HTTPFound(location=request.resource_url(context, 'FrontPage')) - -@view_config(context='.models.Page', renderer='templates/view.pt') -def view_page(context, request): - wiki = context.__parent__ - - def check(match): - word = match.group(1) - if word in wiki: - page = wiki[word] - view_url = request.resource_url(page) - return '%s' % (view_url, word) - else: - add_url = request.application_url + '/add_page/' + word - return '%s' % (add_url, word) - - content = publish_parts(context.data, writer_name='html')['html_body'] - content = wikiwords.sub(check, content) - edit_url = request.resource_url(context, 'edit_page') - return dict(page=context, content=content, edit_url=edit_url) - -@view_config(name='add_page', context='.models.Wiki', - renderer='templates/edit.pt') -def add_page(context, request): - pagename = request.subpath[0] - if 'form.submitted' in request.params: - body = request.params['body'] - page = Page(body) - page.__name__ = pagename - page.__parent__ = context - context[pagename] = page - return HTTPFound(location=request.resource_url(page)) - save_url = request.resource_url(context, 'add_page', pagename) - page = Page('') - page.__name__ = pagename - page.__parent__ = context - return dict(page=page, save_url=save_url) - -@view_config(name='edit_page', context='.models.Page', - renderer='templates/edit.pt') -def edit_page(context, request): - if 'form.submitted' in request.params: - context.data = request.params['body'] - return HTTPFound(location=request.resource_url(context)) - - return dict(page=context, - save_url=request.resource_url(context, 'edit_page')) \ No newline at end of file -- cgit v1.2.3 From 047394ea8ee37fb9161e8dad7693e82112f40d31 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 25 Nov 2018 03:36:51 -0800 Subject: Add import and return a Page object for the notfound view --- docs/tutorials/wiki/src/views/tutorial/views/notfound.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/tutorials/wiki/src/views/tutorial/views/notfound.py b/docs/tutorials/wiki/src/views/tutorial/views/notfound.py index 728791d0a..214ec810e 100644 --- a/docs/tutorials/wiki/src/views/tutorial/views/notfound.py +++ b/docs/tutorials/wiki/src/views/tutorial/views/notfound.py @@ -1,7 +1,12 @@ from pyramid.view import notfound_view_config +from ..models import Page + @notfound_view_config(renderer='../templates/404.pt') def notfound_view(request): request.response.status = 404 - return {} + pagename = request.subpath + page = Page(pagename) + page.__name__ = pagename + return dict(page=page) -- cgit v1.2.3 From 3176aa197371a912f4fd9a90a4645074bccabfda Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 25 Nov 2018 03:37:29 -0800 Subject: Align line numbers with src in views --- docs/tutorials/wiki/definingviews.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/tutorials/wiki/definingviews.rst b/docs/tutorials/wiki/definingviews.rst index 7d3c65e58..0435a70d2 100644 --- a/docs/tutorials/wiki/definingviews.rst +++ b/docs/tutorials/wiki/definingviews.rst @@ -113,7 +113,7 @@ The ``view_wiki`` view function Following is the code for the ``view_wiki`` view function and its decorator: .. literalinclude:: src/views/tutorial/views/default.py - :lines: 12-14 + :lines: 13-15 :lineno-match: :language: python @@ -144,7 +144,7 @@ The ``view_page`` view function Here is the code for the ``view_page`` view function and its decorator: .. literalinclude:: src/views/tutorial/views/default.py - :lines: 16-33 + :lines: 18-35 :lineno-match: :language: python @@ -183,7 +183,7 @@ The ``add_page`` view function Here is the code for the ``add_page`` view function and its decorator: .. literalinclude:: src/views/tutorial/views/default.py - :lines: 35-50 + :lines: 38-53 :lineno-match: :language: python @@ -231,7 +231,7 @@ The ``edit_page`` view function Here is the code for the ``edit_page`` view function and its decorator: .. literalinclude:: src/views/tutorial/views/default.py - :lines: 52-60 + :lines: 56-64 :lineno-match: :language: python -- cgit v1.2.3 From 8af7634939d18c0fc1d2118ce46d9848dfe514bf Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 25 Nov 2018 03:39:01 -0800 Subject: Fix up templates to inherit from layout.pt --- .../wiki/src/views/tutorial/templates/404.pt | 10 +++ .../wiki/src/views/tutorial/templates/edit.pt | 76 ++++----------------- .../wiki/src/views/tutorial/templates/layout.pt | 59 ++++++++++++++++ .../wiki/src/views/tutorial/templates/view.pt | 78 ++++------------------ 4 files changed, 97 insertions(+), 126 deletions(-) create mode 100644 docs/tutorials/wiki/src/views/tutorial/templates/404.pt create mode 100644 docs/tutorials/wiki/src/views/tutorial/templates/layout.pt diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/404.pt b/docs/tutorials/wiki/src/views/tutorial/templates/404.pt new file mode 100644 index 000000000..07298940c --- /dev/null +++ b/docs/tutorials/wiki/src/views/tutorial/templates/404.pt @@ -0,0 +1,10 @@ +
+
+ +
+

Pyramid Starter project

+

404 Page Not Found

+
+ +
+
diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt b/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt index 2db3ca79c..620fcbfb7 100644 --- a/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt +++ b/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt @@ -1,69 +1,21 @@ - - - - - - - - - +
+
- ${page.__name__} - Pyramid tutorial wiki (based on - TurboGears 20-Minute Wiki) - - - - - - - - - - - - -
-
-
-
- -
-
-
-

- Editing - Page Name Goes Here -

-

You can return to the - FrontPage. -

- +
+

+ Editing + Page Name Goes Here +

+
- +
- +
- -
-
+
-
- -
-
-
- - - - - - - +
+
diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/layout.pt b/docs/tutorials/wiki/src/views/tutorial/templates/layout.pt new file mode 100644 index 000000000..ba40fd6f4 --- /dev/null +++ b/docs/tutorials/wiki/src/views/tutorial/templates/layout.pt @@ -0,0 +1,59 @@ + + + + + + + + + + + ${page.__name__} - Pyramid tutorial wiki (based on + TurboGears 20-Minute Wiki) + + + + + + + + + + + + + +
+
+
+
+ +
+
+
No content
+
+

You can return to the + FrontPage. +

+
+
+
+
+ +
+
+
+ + + + + + + + diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/view.pt b/docs/tutorials/wiki/src/views/tutorial/templates/view.pt index 1feeab5ef..b8a6fbbaf 100644 --- a/docs/tutorials/wiki/src/views/tutorial/templates/view.pt +++ b/docs/tutorials/wiki/src/views/tutorial/templates/view.pt @@ -1,70 +1,20 @@ - - - - - - - - - +
+
- ${page.__name__} - Pyramid tutorial wiki (based on - TurboGears 20-Minute Wiki) - - - - - - - - - - - - - -
-
-
-
- -
-
-
-
- Page text goes here. -
-

+

+
+ Page text goes here. +
+

Edit this page -

-

- Viewing - Page Name Goes Here -

-

You can return to the - FrontPage. -

-
-
-
-
- +

+

+ Viewing + Page Name Goes Here +

-
-
- - - - - - - +
+
-- cgit v1.2.3 From 57d8d1fd670b156439ac522fe17988116754d42c Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 25 Nov 2018 03:53:25 -0800 Subject: Fix up template for display on docs --- docs/tutorials/wiki/src/views/tutorial/templates/edit.pt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt b/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt index 620fcbfb7..488e7a6af 100644 --- a/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt +++ b/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt @@ -9,10 +9,13 @@
+ class="form-control" name="body" + rows="10" cols="60">
- +
-- cgit v1.2.3 From 890ecc61836cc2caddd9c5aeacd8e99880ec0237 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 25 Nov 2018 03:53:50 -0800 Subject: Fix up line numbers, add a not found error check --- docs/tutorials/wiki/definingviews.rst | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/docs/tutorials/wiki/definingviews.rst b/docs/tutorials/wiki/definingviews.rst index 0435a70d2..3194d2df7 100644 --- a/docs/tutorials/wiki/definingviews.rst +++ b/docs/tutorials/wiki/definingviews.rst @@ -267,17 +267,19 @@ Update ``tutorial/templates/layout.pt`` with the following content, as indicated .. literalinclude:: src/views/tutorial/templates/layout.pt :linenos: - :emphasize-lines: 11-12 + :emphasize-lines: 11-12, 37-41 :language: html Since we are using a templating engine, we can factor common boilerplate out of our page templates into reusable components. We can do this via :term:`METAL` macros and slots. -- The cookiecutter defined a macro named ``layout`` (Line 1). - This macro consists of the entire document. -- The cookiecutter defined a macro customization point or `slot` (Line 36). +- The cookiecutter defined a macro named ``layout`` (line 1). + This macro consists of the entire template. +- We changed the ``title`` tag to use the ``name`` attribute of a ``page`` object (lines 11-12). +- The cookiecutter defined a macro customization point or `slot` (line 36). This slot is inside the macro ``layout``. Therefore it can be replaced by content, customizing the macro. +- We added a ``div`` element with a link to allow the user to return to the front page (lines 37-41). - We removed the row of icons and links from the original cookiecutter. .. seealso:: @@ -293,7 +295,7 @@ Rename ``tutorial/templates/mytemplate.pt`` to ``tutorial/templates/view.pt`` an .. literalinclude:: src/views/tutorial/templates/view.pt :linenos: :language: html - :emphasize-lines: 5-19 + :emphasize-lines: 5-16 This template is used by ``view_page()`` for displaying a single wiki page. It includes: @@ -304,7 +306,6 @@ wiki page. It includes: ``page_text`` contains HTML, so the ``structure`` keyword is used to prevent escaping HTML entities, such as changing ``>`` to ``>``. - A link that points at the "edit" URL, which invokes the ``edit_page`` view for the page being viewed (lines 9-11). - A ``span`` whose content is replaced by the name of the page, if present. -- A link to the FrontPage. The ``edit.pt`` template @@ -315,14 +316,15 @@ Copy ``tutorial/templates/view.pt`` to ``tutorial/templates/edit.pt`` and edit t .. literalinclude:: src/views/tutorial/templates/edit.pt :linenos: :language: html + :emphasize-lines: 5-20 This template is used by ``add_page()`` and ``edit_page()`` for adding and editing a wiki page. It displays a page containing a form that includes: -- A 10-row by 60-column ``textarea`` field named ``body`` that is filled with any existing page data when it is rendered (lines 14-15). -- A submit button that has the name ``form.submitted`` (line 18). +- A 10-row by 60-column ``textarea`` field named ``body`` that is filled with any existing page data when it is rendered (lines 11-13). +- A submit button that has the name ``form.submitted`` (lines 16-18). -When submitted, the form sends a POST request to the ``save_url`` argument supplied by the view (line 12). +When submitted, the form sends a POST request to the ``save_url`` argument supplied by the view (line 9). The view will use the ``body`` and ``form.submitted`` values. .. note:: @@ -361,3 +363,5 @@ Launch a browser and visit each of the following URLs, checking that the result - To generate an error, visit http://localhost:6543/add_page which will generate an ``IndexError: tuple index out of range`` error. You will see an interactive traceback facility provided by :term:`pyramid_debugtoolbar`. + +- To generate a not found error, visit http://localhost:6543/wakawaka which will invoke the ``notfound_view`` view provided by the cookiecutter. -- cgit v1.2.3 From 51c36cffdf86f22a3a50549f459fe4b8e500db94 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 25 Nov 2018 04:02:00 -0800 Subject: Add section for notfound --- docs/tutorials/wiki/definingviews.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/tutorials/wiki/definingviews.rst b/docs/tutorials/wiki/definingviews.rst index 3194d2df7..81ef4f5c4 100644 --- a/docs/tutorials/wiki/definingviews.rst +++ b/docs/tutorials/wiki/definingviews.rst @@ -251,6 +251,21 @@ If the view execution *is* a result of a form submission (if the expression ``'f It then redirects to the default view of the context (the page), which will always be the ``view_page`` view. +Modifying the ``notfound_view`` in ``notfound.py`` +-------------------------------------------------- + +We have one more view to modify. +Open ``tutorial/views/notfound.py`` and make the changes shown by the emphasized lines. + +.. literalinclude:: src/views/tutorial/views/notfound.py + :linenos: + :language: python + :emphasize-lines: 3-4, 9-12 + +We need to import the ``Page`` from our models. +We eventually return a ``Page`` object as ``page`` into the template ``layout.pt`` to display its name in the title tag. + + Adding templates ================ -- cgit v1.2.3 From 65deab3b3aa370217d504c2a64e9cf0b7b3d84f3 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 25 Nov 2018 04:06:32 -0800 Subject: Rewrap intro and add dependencies to authorization --- docs/tutorials/wiki/authorization.rst | 47 ++++++++++++++++------------------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst index b7eeb19ae..f224a072a 100644 --- a/docs/tutorials/wiki/authorization.rst +++ b/docs/tutorials/wiki/authorization.rst @@ -4,36 +4,30 @@ Adding authorization ==================== -:app:`Pyramid` provides facilities for :term:`authentication` and -:term:`authorization`. We'll make use of both features to provide security to -our application. Our application currently allows anyone with access to the -server to view, edit, and add pages to our wiki. We'll change that to allow -only people who are members of a *group* named ``group:editors`` to add and -edit wiki pages, but we'll continue allowing anyone with access to the server -to view pages. - -We will also add a login page and a logout link on all the pages. The login -page will be shown when a user is denied access to any of the views that +:app:`Pyramid` provides facilities for :term:`authentication` and :term:`authorization`. +We will make use of both features to provide security to our application. +Our application currently allows anyone with access to the server to view, edit, and add pages to our wiki. +We will change that to allow only people who are members of a *group* named ``group:editors`` to add and edit wiki pages. +We will continue to allow anyone with access to the server to view pages. + +We will also add a login page and a logout link on all the pages. +The login page will be shown when a user is denied access to any of the views that 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` - (``__init__.py``). -* Add :term:`permission` declarations to the ``edit_page`` and ``add_page`` - views (``views.py``). +- 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` (``__init__.py``). +- Add :term:`permission` declarations to the ``edit_page`` and ``add_page`` views (``views.py``). Then we will add the login and logout features: -* Add ``login`` and ``logout`` views (``views.py``). -* Add a login template (``login.pt``). -* Make the existing views return a ``logged_in`` flag to the renderer - (``views.py``). -* Add a "Logout" link to be shown when logged in and viewing or editing a page - (``view.pt``, ``edit.pt``). +- Add ``login`` and ``logout`` views (``views.py``). +- Add a login template (``login.pt``). +- Make the existing views return a ``logged_in`` flag to the renderer (``views.py``). +- Add a "Logout" link to be shown when logged in and viewing or editing a page (``view.pt``, ``edit.pt``). Access control @@ -43,7 +37,8 @@ Access control Add dependencies ~~~~~~~~~~~~~~~~ -Just like in :ref:`wiki_defining_views`, we need a new dependency. We need to add the `bcrypt `_ package, to our tutorial package's ``setup.py`` file by assigning this dependency to the ``requires`` parameter in the ``setup()`` function. +Just like in :ref:`wiki_defining_views`, we need a new dependency. +We need to add the `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: @@ -58,7 +53,9 @@ Do not forget to run ``pip install -e .`` just like in :ref:`wiki-running-pip-in .. 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. + 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 is an algorithm approved for storing passwords versus a generic one-way hash. Add users and groups -- cgit v1.2.3 From ef5bbad0b84c4abeda5ad6bd9b6e2295c19e7c80 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 25 Nov 2018 04:07:31 -0800 Subject: We're adding both authn and authz --- docs/tutorials/wiki/authorization.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst index f224a072a..beea1b804 100644 --- a/docs/tutorials/wiki/authorization.rst +++ b/docs/tutorials/wiki/authorization.rst @@ -1,8 +1,8 @@ .. _wiki_adding_authorization: -==================== -Adding authorization -==================== +======================================= +Adding authorization and authentication +======================================= :app:`Pyramid` provides facilities for :term:`authentication` and :term:`authorization`. We will make use of both features to provide security to our application. -- cgit v1.2.3 From c54405f9ebeadf339912f226beb13daa3d653e32 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 25 Nov 2018 04:13:27 -0800 Subject: rewrap users and groups section, shorter sentences, fix grammar --- docs/tutorials/wiki/authorization.rst | 40 +++++++++++++++++------------------ 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst index beea1b804..f027617ec 100644 --- a/docs/tutorials/wiki/authorization.rst +++ b/docs/tutorials/wiki/authorization.rst @@ -67,34 +67,34 @@ Create a new ``tutorial/security.py`` module with the following content: :linenos: :language: python -The ``groupfinder`` function accepts a userid and a request and -returns one of these values: +The ``groupfinder`` function accepts a ``userid`` and a ``request`` +It returns one of these values: -- If ``userid`` exists in the system, it will return a sequence of group - identifiers (or an empty sequence if the user isn't a member of any groups). -- If the userid *does not* exist in the system, it will return ``None``. +- If ``userid`` exists in the system, it will return either a sequence of group identifiers, or an empty sequence if the user is not a member of any groups. +- If the userid *does not* exist in the system, it will return ``None``. -For example, ``groupfinder('editor', request )`` returns ``['group:editor']``, -``groupfinder('viewer', request)`` returns ``[]``, and ``groupfinder('admin', -request)`` returns ``None``. We will use ``groupfinder()`` as an -:term:`authentication policy` "callback" that will provide the -:term:`principal` or principals for a user. +For example: + +- ``groupfinder('editor', request )`` returns ``['group:editor']``. +- ``groupfinder('viewer', request)`` returns ``[]``. +- ``groupfinder('admin', request)`` returns ``None``. + +We will use ``groupfinder()`` as an :term:`authentication policy` "callback" that will provide the :term:`principal` or principals for a user. 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. +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. +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. +database. +Here we use "dummy" data to represent user and groups sources. + Add an ACL ~~~~~~~~~~ -- cgit v1.2.3 From 9548bae2badbb6333f7bfd855ce3f2f393cd3420 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 25 Nov 2018 04:56:56 -0800 Subject: rewrap add an ACL section, shorter sentences, fix grammar --- docs/tutorials/wiki/authorization.rst | 74 +++++++++++++++++------------------ 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst index f027617ec..93d7fb21f 100644 --- a/docs/tutorials/wiki/authorization.rst +++ b/docs/tutorials/wiki/authorization.rst @@ -43,9 +43,9 @@ We need to add the `bcrypt `_ package to our t Open ``setup.py`` and edit it to look like the following: .. literalinclude:: src/authorization/setup.py - :linenos: - :emphasize-lines: 23 - :language: python + :linenos: + :emphasize-lines: 23 + :language: python Only the highlighted line needs to be added. @@ -64,8 +64,8 @@ Add users and groups Create a new ``tutorial/security.py`` module with the following content: .. literalinclude:: src/authorization/tutorial/security.py - :linenos: - :language: python + :linenos: + :language: python The ``groupfinder`` function accepts a ``userid`` and a ``request`` It returns one of these values: @@ -99,43 +99,43 @@ Here we use "dummy" data to represent user and groups sources. Add an ACL ~~~~~~~~~~ -Open ``tutorial/models.py`` and add the following import -statement near the top: +Open ``tutorial/models.py`` and add the following import statement near the top: -.. literalinclude:: src/authorization/tutorial/models.py - :lines: 4-8 - :lineno-match: - :language: python +.. literalinclude:: src/authorization/tutorial/models/__init__.py + :lines: 4-8 + :lineno-match: + :language: python Add the following lines to the ``Wiki`` class: -.. literalinclude:: src/authorization/tutorial/models.py - :lines: 9-13 - :lineno-match: - :emphasize-lines: 4-5 - :language: python +.. literalinclude:: src/authorization/tutorial/models/__init__.py + :lines: 9-13 + :lineno-match: + :emphasize-lines: 4-5 + :language: python + +We import :data:`~pyramid.security.Allow`, an action which means that +permission is allowed. +We also import :data:`~pyramid.security.Everyone`, a special :term:`principal` that is associated to all requests. +Both are used in the :term:`ACE` entries that make up the ACL. + +The ACL is a list that needs to be named ``__acl__`` and be an attribute of a class. +We define an :term:`ACL` with two :term:`ACE` entries. +The first entry allows any user the ``view`` permission. +The second entry allows the ``group:editors`` principal the ``edit`` permission. + +The ``Wiki`` class that contains the ACL is the :term:`resource` constructor for the :term:`root` resource, which is a ``Wiki`` instance. +The ACL is provided to each view in the :term:`context` of the request as the ``context`` attribute. + +It is only happenstance that we assigned this ACL at class scope. +An ACL can be attached to an object *instance* too. +This is how "row level security" can be achieved in :app:`Pyramid` applications. +We actually need only *one* ACL for the entire system, however, because our security requirements are simple, so this feature is not demonstrated. + +.. seealso:: + + See :ref:`assigning_acls` for more information about what an :term:`ACL` represents. -We import :data:`~pyramid.security.Allow`, an action that means that -permission is allowed, and :data:`~pyramid.security.Everyone`, a special -:term:`principal` that is associated to all requests. Both are used in the -:term:`ACE` entries that make up the ACL. - -The ACL is a list that needs to be named ``__acl__`` and be an attribute of a -class. We define an :term:`ACL` with two :term:`ACE` entries: the first entry -allows any user the ``view`` permission. The second entry allows the -``group:editors`` principal the ``edit`` permission. - -The ``Wiki`` class that contains the ACL is the :term:`resource` constructor -for the :term:`root` resource, which is a ``Wiki`` instance. The ACL is -provided to each view in the :term:`context` of the request as the ``context`` -attribute. - -It's only happenstance that we're assigning this ACL at class scope. An ACL -can be attached to an object *instance* too; this is how "row level security" -can be achieved in :app:`Pyramid` applications. We actually need only *one* -ACL for the entire system, however, because our security requirements are -simple, so this feature is not demonstrated. See :ref:`assigning_acls` for -more information about what an :term:`ACL` represents. Add authentication and authorization policies ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- cgit v1.2.3 From 99d91941cdabb2bee707f740f763465e588a4f49 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 25 Nov 2018 05:09:56 -0800 Subject: rewrap policies and permissions sections, shorter sentences, fix grammar --- docs/tutorials/wiki/authorization.rst | 46 ++++++++++++++++------------------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst index 93d7fb21f..2d9d73d46 100644 --- a/docs/tutorials/wiki/authorization.rst +++ b/docs/tutorials/wiki/authorization.rst @@ -152,44 +152,40 @@ statements: Now add those policies to the configuration: .. literalinclude:: src/authorization/tutorial/__init__.py - :lines: 18-25 + :lines: 15-25 :lineno-match: - :emphasize-lines: 2-4,6-7 + :emphasize-lines: 4-6,9-10 :language: python Only the highlighted lines need to be added. -We are enabling an ``AuthTktAuthenticationPolicy``, which is based in an auth -ticket that may be included in the request. We are also enabling an -``ACLAuthorizationPolicy``, which uses an ACL to determine the *allow* or -*deny* outcome for a view. +We enabled an ``AuthTktAuthenticationPolicy`` which is based in an auth ticket that may be included in the request. +We also enabled an ``ACLAuthorizationPolicy`` which uses an ACL to determine the *allow* or *deny* outcome for a view. + +Note that the :class:`pyramid.authentication.AuthTktAuthenticationPolicy` constructor accepts two arguments: ``secret`` and ``callback``. +``secret`` is a string representing an encryption key used by the "authentication ticket" machinery represented by this policy. +It is required. +The ``callback`` is the ``groupfinder()`` function that we created earlier. -Note that the :class:`pyramid.authentication.AuthTktAuthenticationPolicy` -constructor accepts two arguments: ``secret`` and ``callback``. ``secret`` is -a string representing an encryption key used by the "authentication ticket" -machinery represented by this policy: it is required. The ``callback`` is the -``groupfinder()`` function that we created before. Add permission declarations ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Open ``tutorial/views.py`` and add a ``permission='edit'`` parameter -to the ``@view_config`` decorators for ``add_page()`` and ``edit_page()``: -.. literalinclude:: src/authorization/tutorial/views.py +Open ``tutorial/views/default.py`` and add a ``permission='edit'`` parameter to the ``@view_config`` decorators for ``add_page()`` and ``edit_page()``: + +.. literalinclude:: src/authorization/tutorial/views/default.py :lines: 49-51 :emphasize-lines: 2-3 :language: python -.. literalinclude:: src/authorization/tutorial/views.py +.. literalinclude:: src/authorization/tutorial/views/default.py :lines: 68-70 :emphasize-lines: 2-3 :language: python -Only the highlighted lines, along with their preceding commas, need to be -edited and added. +Only the highlighted lines, along with their preceding commas, need to be edited and added. -The result is that only users who possess the ``edit`` permission at the time -of the request may invoke those two views. +The result is that only users who possess the ``edit`` permission at the time of the request may invoke those two views. Add a ``permission='view'`` parameter to the ``@view_config`` decorator for ``view_wiki()`` and ``view_page()`` as follows: @@ -204,22 +200,22 @@ Add a ``permission='view'`` parameter to the ``@view_config`` decorator for :emphasize-lines: 1-2 :language: python -Only the highlighted lines, along with their preceding commas, need to be -edited and added. +Only the highlighted lines, along with their preceding commas, need to be edited and added. This allows anyone to invoke these two views. -We are done with the changes needed to control access. The changes that -follow will add the login and logout feature. +We are done with the changes needed to control access. +The changes that follow will add the login and logout feature. + Login, logout ------------- + Add login and logout views ~~~~~~~~~~~~~~~~~~~~~~~~~~ -We'll add a ``login`` view which renders a login form and processes the post -from the login form, checking credentials. +We will add a ``login`` view which renders a login form and processes the post from the login form, checking credentials. We'll also add a ``logout`` view callable to our application and provide a link to it. This view will clear the credentials of the logged in user and -- cgit v1.2.3 From 097381f8e8fa112bfb859fcf4938611b94a498bf Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 25 Nov 2018 11:47:03 -0800 Subject: indent literalincludes, add login.pt template and view --- docs/tutorials/wiki/authorization.rst | 147 ++++++++++++++++------------------ 1 file changed, 71 insertions(+), 76 deletions(-) diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst index 2d9d73d46..60c83c871 100644 --- a/docs/tutorials/wiki/authorization.rst +++ b/docs/tutorials/wiki/authorization.rst @@ -144,18 +144,18 @@ Open ``tutorial/__init__.py`` and add the highlighted import statements: .. literalinclude:: src/authorization/tutorial/__init__.py - :lines: 1-8 - :linenos: - :emphasize-lines: 3-6,8 - :language: python + :lines: 1-8 + :linenos: + :emphasize-lines: 3-6,8 + :language: python Now add those policies to the configuration: .. literalinclude:: src/authorization/tutorial/__init__.py - :lines: 15-25 - :lineno-match: - :emphasize-lines: 4-6,9-10 - :language: python + :lines: 15-25 + :lineno-match: + :emphasize-lines: 4-6,9-10 + :language: python Only the highlighted lines need to be added. @@ -174,14 +174,14 @@ Add permission declarations Open ``tutorial/views/default.py`` and add a ``permission='edit'`` parameter to the ``@view_config`` decorators for ``add_page()`` and ``edit_page()``: .. literalinclude:: src/authorization/tutorial/views/default.py - :lines: 49-51 - :emphasize-lines: 2-3 - :language: python + :lines: 49-51 + :emphasize-lines: 2-3 + :language: python .. literalinclude:: src/authorization/tutorial/views/default.py - :lines: 68-70 - :emphasize-lines: 2-3 - :language: python + :lines: 68-70 + :emphasize-lines: 2-3 + :language: python Only the highlighted lines, along with their preceding commas, need to be edited and added. @@ -191,14 +191,14 @@ Add a ``permission='view'`` parameter to the ``@view_config`` decorator for ``view_wiki()`` and ``view_page()`` as follows: .. literalinclude:: src/authorization/tutorial/views.py - :lines: 23-24 - :emphasize-lines: 1-2 - :language: python + :lines: 23-24 + :emphasize-lines: 1-2 + :language: python .. literalinclude:: src/authorization/tutorial/views.py - :lines: 28-29 - :emphasize-lines: 1-2 - :language: python + :lines: 28-29 + :emphasize-lines: 1-2 + :language: python Only the highlighted lines, along with their preceding commas, need to be edited and added. @@ -217,46 +217,41 @@ Add login and logout views We will add a ``login`` view which renders a login form and processes the post from the login form, checking credentials. -We'll also add a ``logout`` view callable to our application and provide a -link to it. This view will clear the credentials of the logged in user and -redirect back to the front page. +We will also add a ``logout`` view callable to our application and provide a link to it. +This view will clear the credentials of the logged in user and redirect back to the front page. -Add the following import statements to the head of -``tutorial/views.py``: +Add the following import statements to the head of ``tutorial/views/default.py``: -.. literalinclude:: src/authorization/tutorial/views.py - :lines: 6-17 - :emphasize-lines: 1-12 - :language: python +.. literalinclude:: src/authorization/tutorial/views/default.py + :lines: 4-15 + :emphasize-lines: 2-10,12 + :lineno-match: + :language: python All the highlighted lines need to be added or edited. -:meth:`~pyramid.view.forbidden_view_config` will be used to customize the -default 403 Forbidden page. :meth:`~pyramid.security.remember` and -:meth:`~pyramid.security.forget` help to create and expire an auth ticket -cookie. +:meth:`~pyramid.view.forbidden_view_config` will be used to customize the default 403 Forbidden page. +:meth:`~pyramid.security.remember` and :meth:`~pyramid.security.forget` help to create and expire an auth ticket cookie. Now add the ``login`` and ``logout`` views at the end of the file: -.. literalinclude:: src/authorization/tutorial/views.py - :lines: 80- - :lineno-match: - :language: python +.. literalinclude:: src/authorization/tutorial/views/default.py + :lines: 78- + :lineno-match: + :language: python ``login()`` has two decorators: -- a ``@view_config`` decorator which associates it with the ``login`` route - and makes it visible when we visit ``/login``, -- a ``@forbidden_view_config`` decorator which turns it into a - :term:`forbidden view`. ``login()`` will be invoked when a user tries to - execute a view callable for which they lack authorization. For example, if - a user has not logged in and tries to add or edit a Wiki page, they will be - shown the login form before being allowed to continue. +- A ``@view_config`` decorator which associates it with the ``login`` route and makes it visible when we visit ``/login``. +- A ``@forbidden_view_config`` decorator which turns it into a :term:`forbidden view`. + ``login()`` will be invoked when a user tries to execute a view callable for which they lack authorization. + For example, if a user has not logged in and tries to add or edit a Wiki page, then they will be shown the login form before being allowed to continue. The order of these two :term:`view configuration` decorators is unimportant. -``logout()`` is decorated with a ``@view_config`` decorator which associates -it with the ``logout`` route. It will be invoked when we visit ``/logout``. +``logout()`` is decorated with a ``@view_config`` decorator which associates it with the ``logout`` route. +It will be invoked when we visit ``/logout``. + Add the ``login.pt`` Template ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -264,10 +259,10 @@ Add the ``login.pt`` Template Create ``tutorial/templates/login.pt`` with the following content: .. literalinclude:: src/authorization/tutorial/templates/login.pt - :language: html + :language: html + +The above template is referenced in the login view that we just added in ``views.py``. -The above template is referenced in the login view that we just added in -``views.py``. Return a ``logged_in`` flag to the renderer ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -277,19 +272,19 @@ the return value of ``view_page()``, ``add_page()``, and ``edit_page()`` as follows: .. literalinclude:: src/authorization/tutorial/views.py - :lines: 46-47 - :emphasize-lines: 1-2 - :language: python + :lines: 46-47 + :emphasize-lines: 1-2 + :language: python .. literalinclude:: src/authorization/tutorial/views.py - :lines: 65-66 - :emphasize-lines: 1-2 - :language: python + :lines: 65-66 + :emphasize-lines: 1-2 + :language: python .. literalinclude:: src/authorization/tutorial/views.py - :lines: 76-78 - :emphasize-lines: 2-3 - :language: python + :lines: 76-78 + :emphasize-lines: 2-3 + :language: python Only the highlighted lines need to be added or edited. @@ -304,9 +299,9 @@ Open ``tutorial/templates/edit.pt`` and indicated by the highlighted lines. .. literalinclude:: src/authorization/tutorial/templates/edit.pt - :lines: 35-39 - :emphasize-lines: 2-4 - :language: html + :lines: 35-39 + :emphasize-lines: 2-4 + :language: html The attribute ``tal:condition="logged_in"`` will make the element be included when ``logged_in`` is any user id. The link will invoke the logout view. The @@ -319,27 +314,27 @@ Reviewing our changes Our ``tutorial/__init__.py`` will look like this when we're done: .. literalinclude:: src/authorization/tutorial/__init__.py - :linenos: - :emphasize-lines: 4-5,8,19-21,23-24 - :language: python + :linenos: + :emphasize-lines: 4-5,8,19-21,23-24 + :language: python Only the highlighted lines need to be added or edited. Our ``tutorial/models.py`` will look like this when we're done: .. literalinclude:: src/authorization/tutorial/models.py - :linenos: - :emphasize-lines: 4-7,12-13 - :language: python + :linenos: + :emphasize-lines: 4-7,12-13 + :language: python Only the highlighted lines need to be added or edited. Our ``tutorial/views.py`` will look like this when we're done: .. literalinclude:: src/authorization/tutorial/views.py - :linenos: - :emphasize-lines: 8,11-15,17,24,29,47,51,66,70,78,80- - :language: python + :linenos: + :emphasize-lines: 8,11-15,17,24,29,47,51,66,70,78,80- + :language: python Only the highlighted lines need to be added or edited. @@ -347,9 +342,9 @@ Our ``tutorial/templates/edit.pt`` template will look like this when we're done: .. literalinclude:: src/authorization/tutorial/templates/edit.pt - :linenos: - :emphasize-lines: 36-38 - :language: html + :linenos: + :emphasize-lines: 36-38 + :language: html Only the highlighted lines need to be added or edited. @@ -357,9 +352,9 @@ Our ``tutorial/templates/view.pt`` template will look like this when we're done: .. literalinclude:: src/authorization/tutorial/templates/view.pt - :linenos: - :emphasize-lines: 36-38 - :language: html + :linenos: + :emphasize-lines: 36-38 + :language: html Only the highlighted lines need to be added or edited. -- cgit v1.2.3 From 622a98df9f46805617481c573073901493e601c4 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 25 Nov 2018 12:26:58 -0800 Subject: Use path for pagename, not subpath --- docs/tutorials/wiki/src/views/tutorial/views/notfound.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/wiki/src/views/tutorial/views/notfound.py b/docs/tutorials/wiki/src/views/tutorial/views/notfound.py index 214ec810e..d44b4d0e6 100644 --- a/docs/tutorials/wiki/src/views/tutorial/views/notfound.py +++ b/docs/tutorials/wiki/src/views/tutorial/views/notfound.py @@ -6,7 +6,7 @@ from ..models import Page @notfound_view_config(renderer='../templates/404.pt') def notfound_view(request): request.response.status = 404 - pagename = request.subpath + pagename = request.path page = Page(pagename) page.__name__ = pagename return dict(page=page) -- cgit v1.2.3 From 7672298e3877e36f12832cc6d6099aefa6173160 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 25 Nov 2018 13:19:22 -0800 Subject: Update theme.css to display text in inputs - See #3431 --- docs/tutorials/wiki/src/basiclayout/tutorial/static/theme.css | 3 +++ docs/tutorials/wiki/src/installation/tutorial/static/theme.css | 3 +++ docs/tutorials/wiki/src/models/tutorial/static/theme.css | 3 +++ docs/tutorials/wiki/src/views/tutorial/static/theme.css | 3 +++ 4 files changed, 12 insertions(+) diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/static/theme.css b/docs/tutorials/wiki/src/basiclayout/tutorial/static/theme.css index 0f4b1a4d4..a70ee557a 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/static/theme.css +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/static/theme.css @@ -17,6 +17,9 @@ h6 { p { font-weight: 300; } +button, input, optgroup, select, textarea { + color: black; +} .font-normal { font-weight: 400; } diff --git a/docs/tutorials/wiki/src/installation/tutorial/static/theme.css b/docs/tutorials/wiki/src/installation/tutorial/static/theme.css index 0f4b1a4d4..a70ee557a 100644 --- a/docs/tutorials/wiki/src/installation/tutorial/static/theme.css +++ b/docs/tutorials/wiki/src/installation/tutorial/static/theme.css @@ -17,6 +17,9 @@ h6 { p { font-weight: 300; } +button, input, optgroup, select, textarea { + color: black; +} .font-normal { font-weight: 400; } diff --git a/docs/tutorials/wiki/src/models/tutorial/static/theme.css b/docs/tutorials/wiki/src/models/tutorial/static/theme.css index 0f4b1a4d4..a70ee557a 100644 --- a/docs/tutorials/wiki/src/models/tutorial/static/theme.css +++ b/docs/tutorials/wiki/src/models/tutorial/static/theme.css @@ -17,6 +17,9 @@ h6 { p { font-weight: 300; } +button, input, optgroup, select, textarea { + color: black; +} .font-normal { font-weight: 400; } diff --git a/docs/tutorials/wiki/src/views/tutorial/static/theme.css b/docs/tutorials/wiki/src/views/tutorial/static/theme.css index 0f4b1a4d4..a70ee557a 100644 --- a/docs/tutorials/wiki/src/views/tutorial/static/theme.css +++ b/docs/tutorials/wiki/src/views/tutorial/static/theme.css @@ -17,6 +17,9 @@ h6 { p { font-weight: 300; } +button, input, optgroup, select, textarea { + color: black; +} .font-normal { font-weight: 400; } -- cgit v1.2.3 From 6f16b8f8f8a2747115cc6e2b3633b841cf2c17dc Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 25 Nov 2018 13:20:16 -0800 Subject: rewrap review, shorter sentences, fix grammar. align line numbers with code. --- docs/tutorials/wiki/authorization.rst | 118 ++++++++++++++++------------------ 1 file changed, 55 insertions(+), 63 deletions(-) diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst index 60c83c871..207b19d10 100644 --- a/docs/tutorials/wiki/authorization.rst +++ b/docs/tutorials/wiki/authorization.rst @@ -190,12 +190,12 @@ The result is that only users who possess the ``edit`` permission at the time of Add a ``permission='view'`` parameter to the ``@view_config`` decorator for ``view_wiki()`` and ``view_page()`` as follows: -.. literalinclude:: src/authorization/tutorial/views.py +.. literalinclude:: src/authorization/tutorial/views/default.py :lines: 23-24 :emphasize-lines: 1-2 :language: python -.. literalinclude:: src/authorization/tutorial/views.py +.. literalinclude:: src/authorization/tutorial/views/default.py :lines: 28-29 :emphasize-lines: 1-2 :language: python @@ -225,7 +225,6 @@ Add the following import statements to the head of ``tutorial/views/default.py`` .. literalinclude:: src/authorization/tutorial/views/default.py :lines: 4-15 :emphasize-lines: 2-10,12 - :lineno-match: :language: python All the highlighted lines need to be added or edited. @@ -267,93 +266,90 @@ The above template is referenced in the login view that we just added in ``views Return a ``logged_in`` flag to the renderer ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Open ``tutorial/views.py`` again. Add a ``logged_in`` parameter to -the return value of ``view_page()``, ``add_page()``, and ``edit_page()`` as -follows: +Open ``tutorial/views/default.py`` again. +Add a ``logged_in`` parameter to the return value of ``view_page()``, ``add_page()``, and ``edit_page()`` as follows: -.. literalinclude:: src/authorization/tutorial/views.py - :lines: 46-47 +.. literalinclude:: src/authorization/tutorial/views/default.py + :lines: 45-46 :emphasize-lines: 1-2 :language: python -.. literalinclude:: src/authorization/tutorial/views.py +.. literalinclude:: src/authorization/tutorial/views/default.py :lines: 65-66 :emphasize-lines: 1-2 :language: python -.. literalinclude:: src/authorization/tutorial/views.py - :lines: 76-78 +.. literalinclude:: src/authorization/tutorial/views/default.py + :lines: 77-79 :emphasize-lines: 2-3 :language: python Only the highlighted lines need to be added or edited. -The :meth:`pyramid.request.Request.authenticated_userid` will be ``None`` if -the user is not authenticated, or a userid if the user is authenticated. +The :meth:`pyramid.request.Request.authenticated_userid` will be ``None`` if the user is not authenticated, or a ``userid`` if the user is authenticated. + Add a "Logout" link when logged in ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Open ``tutorial/templates/edit.pt`` and -``tutorial/templates/view.pt`` and add the following code as -indicated by the highlighted lines. +Open ``tutorial/templates/edit.pt`` and ``tutorial/templates/view.pt``. +Add the following code as indicated by the highlighted lines. .. literalinclude:: src/authorization/tutorial/templates/edit.pt - :lines: 35-39 + :lines: 4-8 :emphasize-lines: 2-4 :language: html -The attribute ``tal:condition="logged_in"`` will make the element be included -when ``logged_in`` is any user id. The link will invoke the logout view. The -above element will not be included if ``logged_in`` is ``None``, such as when +The attribute ``tal:condition="logged_in"`` will make the element be included when ``logged_in`` is any user id. +The link will invoke the logout view. +The above element will not be included if ``logged_in`` is ``None``, such as when a user is not authenticated. + Reviewing our changes --------------------- -Our ``tutorial/__init__.py`` will look like this when we're done: +Our ``tutorial/__init__.py`` will look like this when we are done: .. literalinclude:: src/authorization/tutorial/__init__.py :linenos: - :emphasize-lines: 4-5,8,19-21,23-24 + :emphasize-lines: 3-6,8,18-20,23-24 :language: python Only the highlighted lines need to be added or edited. -Our ``tutorial/models.py`` will look like this when we're done: +Our ``tutorial/models.py`` will look like this when we are done: -.. literalinclude:: src/authorization/tutorial/models.py +.. literalinclude:: src/authorization/tutorial/models/__init__.py :linenos: - :emphasize-lines: 4-7,12-13 + :emphasize-lines: 4-8,12-13 :language: python Only the highlighted lines need to be added or edited. -Our ``tutorial/views.py`` will look like this when we're done: +Our ``tutorial/views/default.py`` will look like this when we are done: -.. literalinclude:: src/authorization/tutorial/views.py +.. literalinclude:: src/authorization/tutorial/views/default.py :linenos: - :emphasize-lines: 8,11-15,17,24,29,47,51,66,70,78,80- + :emphasize-lines: 5-12,15,21-22,27-28,45-46,50-51,65-66,70-71,78- :language: python Only the highlighted lines need to be added or edited. -Our ``tutorial/templates/edit.pt`` template will look like this when -we're done: +Our ``tutorial/templates/edit.pt`` template will look like this when we are done: .. literalinclude:: src/authorization/tutorial/templates/edit.pt :linenos: - :emphasize-lines: 36-38 + :emphasize-lines: 5-7 :language: html Only the highlighted lines need to be added or edited. -Our ``tutorial/templates/view.pt`` template will look like this when -we're done: +Our ``tutorial/templates/view.pt`` template will look like this when we are done: .. literalinclude:: src/authorization/tutorial/templates/view.pt :linenos: - :emphasize-lines: 36-38 + :emphasize-lines: 5-7 :language: html Only the highlighted lines need to be added or edited. @@ -361,32 +357,28 @@ Only the highlighted lines need to be added or edited. Viewing the application in a browser ------------------------------------ -We can finally examine our application in a browser (See -:ref:`wiki-start-the-application`). Launch a browser and visit each of the -following URLs, checking that the result is as expected: - -- http://localhost:6543/ invokes the ``view_wiki`` view. This always - redirects to the ``view_page`` view of the ``FrontPage`` Page resource. It - is executable by any user. - -- http://localhost:6543/FrontPage invokes the ``view_page`` view of the - ``FrontPage`` Page resource. This is because it's the :term:`default view` - (a view without a ``name``) for ``Page`` resources. It is executable by any - user. - -- http://localhost:6543/FrontPage/edit_page invokes the edit view for the - FrontPage object. It is executable by only the ``editor`` user. If a - different user (or the anonymous user) invokes it, a login form will be - displayed. Supplying the credentials with the username ``editor``, password - ``editor`` will display the edit page form. - -- http://localhost:6543/add_page/SomePageName invokes the add view for a page. - It is executable by only the ``editor`` user. If a different user (or the - anonymous user) invokes it, a login form will be displayed. Supplying the - credentials with the username ``editor``, password ``editor`` will display - the edit page form. - -- After logging in (as a result of hitting an edit or add page and submitting - the login form with the ``editor`` credentials), we'll see a Logout link in - the upper right hand corner. When we click it, we're logged out, and - redirected back to the front page. +We can finally examine our application in a browser (See :ref:`wiki-start-the-application`). +Launch a browser and visit each of the following URLs, checking that the result is as expected: + +- http://localhost:6543/ invokes the ``view_wiki`` view. + This always redirects to the ``view_page`` view of the ``FrontPage`` Page resource. + It is executable by any user. + +- http://localhost:6543/FrontPage invokes the ``view_page`` view of the ``FrontPage`` Page resource. + This is because it is the :term:`default view` (a view without a ``name``) for ``Page`` resources. + It is executable by any user. + +- http://localhost:6543/FrontPage/edit_page invokes the edit view for the FrontPage object. + It is executable by only the ``editor`` user. + If a different user (or the anonymous user) invokes it, then a login form will be displayed. + Supplying the credentials with the username ``editor`` and password ``editor`` will display the edit page form. + +- http://localhost:6543/add_page/SomePageName invokes the add view for a page. + It is executable by only the ``editor`` user. + If a different user (or the anonymous user) invokes it, a login form will be displayed. + Supplying the credentials with the username ``editor``, password ``editor`` will display the edit page form. + +- After logging in (as a result of hitting an edit or add page and submitting the login form with the ``editor`` credentials), we will see a Logout link in the upper right hand corner. + When we click it, we are logged out, and redirected back to the front page. + +- To generate a not found error, visit http://localhost:6543/wakawaka which will invoke the ``notfound_view`` view provided by the cookiecutter. -- cgit v1.2.3 From 9495e5d7e65b138b0b209635efd00ae02c2afd27 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 25 Nov 2018 13:21:11 -0800 Subject: synch up src/authorization files --- docs/tutorials/wiki/src/authorization/MANIFEST.in | 2 +- docs/tutorials/wiki/src/authorization/pytest.ini | 2 +- docs/tutorials/wiki/src/authorization/setup.py | 4 +- .../wiki/src/authorization/tutorial/__init__.py | 8 +- .../wiki/src/authorization/tutorial/models.py | 27 ----- .../src/authorization/tutorial/models/__init__.py | 27 +++++ .../wiki/src/authorization/tutorial/pshell.py | 1 + .../wiki/src/authorization/tutorial/routes.py | 2 + .../src/authorization/tutorial/static/theme.css | 3 + .../src/authorization/tutorial/templates/404.pt | 10 ++ .../src/authorization/tutorial/templates/edit.pt | 83 ++++----------- .../src/authorization/tutorial/templates/layout.pt | 59 +++++++++++ .../src/authorization/tutorial/templates/login.pt | 57 +--------- .../src/authorization/tutorial/templates/view.pt | 81 +++----------- .../wiki/src/authorization/tutorial/tests.py | 3 +- .../wiki/src/authorization/tutorial/views.py | 114 -------------------- .../src/authorization/tutorial/views/__init__.py | 0 .../src/authorization/tutorial/views/default.py | 116 +++++++++++++++++++++ .../src/authorization/tutorial/views/notfound.py | 12 +++ 19 files changed, 280 insertions(+), 331 deletions(-) delete mode 100644 docs/tutorials/wiki/src/authorization/tutorial/models.py create mode 100644 docs/tutorials/wiki/src/authorization/tutorial/models/__init__.py create mode 100644 docs/tutorials/wiki/src/authorization/tutorial/routes.py create mode 100644 docs/tutorials/wiki/src/authorization/tutorial/templates/404.pt create mode 100644 docs/tutorials/wiki/src/authorization/tutorial/templates/layout.pt delete mode 100644 docs/tutorials/wiki/src/authorization/tutorial/views.py create mode 100644 docs/tutorials/wiki/src/authorization/tutorial/views/__init__.py create mode 100644 docs/tutorials/wiki/src/authorization/tutorial/views/default.py create mode 100644 docs/tutorials/wiki/src/authorization/tutorial/views/notfound.py diff --git a/docs/tutorials/wiki/src/authorization/MANIFEST.in b/docs/tutorials/wiki/src/authorization/MANIFEST.in index 81beba1b1..05cc195d9 100644 --- a/docs/tutorials/wiki/src/authorization/MANIFEST.in +++ b/docs/tutorials/wiki/src/authorization/MANIFEST.in @@ -1,2 +1,2 @@ include *.txt *.ini *.cfg *.rst -recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml +recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2 diff --git a/docs/tutorials/wiki/src/authorization/pytest.ini b/docs/tutorials/wiki/src/authorization/pytest.ini index 8b76bc410..a3489cdf8 100644 --- a/docs/tutorials/wiki/src/authorization/pytest.ini +++ b/docs/tutorials/wiki/src/authorization/pytest.ini @@ -1,3 +1,3 @@ [pytest] testpaths = tutorial -python_files = *.py +python_files = test*.py diff --git a/docs/tutorials/wiki/src/authorization/setup.py b/docs/tutorials/wiki/src/authorization/setup.py index 7b405745e..fa5948acb 100644 --- a/docs/tutorials/wiki/src/authorization/setup.py +++ b/docs/tutorials/wiki/src/authorization/setup.py @@ -10,15 +10,15 @@ with open(os.path.join(here, 'CHANGES.txt')) as f: requires = [ 'plaster_pastedeploy', - 'pyramid >= 1.9a', + 'pyramid', 'pyramid_chameleon', 'pyramid_debugtoolbar', + 'waitress', 'pyramid_retry', 'pyramid_tm', 'pyramid_zodbconn', 'transaction', 'ZODB3', - 'waitress', 'docutils', 'bcrypt', ] diff --git a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py b/docs/tutorials/wiki/src/authorization/tutorial/__init__.py index 58635ea74..196c0f311 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/authorization/tutorial/__init__.py @@ -15,18 +15,18 @@ def root_factory(request): def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ - settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' authn_policy = AuthTktAuthenticationPolicy( 'sosecret', callback=groupfinder, hashalg='sha512') authz_policy = ACLAuthorizationPolicy() with Configurator(settings=settings) as config: + settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' config.set_authentication_policy(authn_policy) config.set_authorization_policy(authz_policy) - config.include('pyramid_chameleon') config.include('pyramid_tm') config.include('pyramid_retry') config.include('pyramid_zodbconn') config.set_root_factory(root_factory) - config.add_static_view('static', 'static', cache_max_age=3600) + config.include('pyramid_chameleon') + config.include('.routes') config.scan() - return config.make_wsgi_app() + return config.make_wsgi_app() diff --git a/docs/tutorials/wiki/src/authorization/tutorial/models.py b/docs/tutorials/wiki/src/authorization/tutorial/models.py deleted file mode 100644 index ebd70e912..000000000 --- a/docs/tutorials/wiki/src/authorization/tutorial/models.py +++ /dev/null @@ -1,27 +0,0 @@ -from persistent import Persistent -from persistent.mapping import PersistentMapping - -from pyramid.security import ( - Allow, - Everyone, - ) - -class Wiki(PersistentMapping): - __name__ = None - __parent__ = None - __acl__ = [ (Allow, Everyone, 'view'), - (Allow, 'group:editors', 'edit') ] - -class Page(Persistent): - def __init__(self, data): - self.data = data - -def appmaker(zodb_root): - if 'app_root' not in zodb_root: - app_root = Wiki() - frontpage = Page('This is the front page') - app_root['FrontPage'] = frontpage - frontpage.__name__ = 'FrontPage' - frontpage.__parent__ = app_root - zodb_root['app_root'] = app_root - return zodb_root['app_root'] diff --git a/docs/tutorials/wiki/src/authorization/tutorial/models/__init__.py b/docs/tutorials/wiki/src/authorization/tutorial/models/__init__.py new file mode 100644 index 000000000..ebd70e912 --- /dev/null +++ b/docs/tutorials/wiki/src/authorization/tutorial/models/__init__.py @@ -0,0 +1,27 @@ +from persistent import Persistent +from persistent.mapping import PersistentMapping + +from pyramid.security import ( + Allow, + Everyone, + ) + +class Wiki(PersistentMapping): + __name__ = None + __parent__ = None + __acl__ = [ (Allow, Everyone, 'view'), + (Allow, 'group:editors', 'edit') ] + +class Page(Persistent): + def __init__(self, data): + self.data = data + +def appmaker(zodb_root): + if 'app_root' not in zodb_root: + app_root = Wiki() + frontpage = Page('This is the front page') + app_root['FrontPage'] = frontpage + frontpage.__name__ = 'FrontPage' + frontpage.__parent__ = app_root + zodb_root['app_root'] = app_root + return zodb_root['app_root'] diff --git a/docs/tutorials/wiki/src/authorization/tutorial/pshell.py b/docs/tutorials/wiki/src/authorization/tutorial/pshell.py index 3d026291b..a7cfa6a27 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/pshell.py +++ b/docs/tutorials/wiki/src/authorization/tutorial/pshell.py @@ -1,5 +1,6 @@ from . import models + def setup(env): request = env['request'] diff --git a/docs/tutorials/wiki/src/authorization/tutorial/routes.py b/docs/tutorials/wiki/src/authorization/tutorial/routes.py new file mode 100644 index 000000000..3c0a37992 --- /dev/null +++ b/docs/tutorials/wiki/src/authorization/tutorial/routes.py @@ -0,0 +1,2 @@ +def includeme(config): + config.add_static_view('static', 'static', cache_max_age=3600) diff --git a/docs/tutorials/wiki/src/authorization/tutorial/static/theme.css b/docs/tutorials/wiki/src/authorization/tutorial/static/theme.css index 0f4b1a4d4..a70ee557a 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/static/theme.css +++ b/docs/tutorials/wiki/src/authorization/tutorial/static/theme.css @@ -17,6 +17,9 @@ h6 { p { font-weight: 300; } +button, input, optgroup, select, textarea { + color: black; +} .font-normal { font-weight: 400; } diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/404.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/404.pt new file mode 100644 index 000000000..07298940c --- /dev/null +++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/404.pt @@ -0,0 +1,10 @@ +
+
+ +
+

Pyramid Starter project

+

404 Page Not Found

+
+ +
+
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt index eedb83da4..6438b1569 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt +++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt @@ -1,72 +1,27 @@ - - - - - - - - - +
+
- ${page.__name__} - Pyramid tutorial wiki (based on - TurboGears 20-Minute Wiki) - - - - - - - - - - - - -
-
-
-
- -
-
-
-

+

+

Logout -

-

- Editing - Page Name Goes Here -

-

You can return to the - FrontPage. -

-
+

+

+ Editing + Page Name Goes Here +

+
- +
- +
-
-
-
+
-
- -
-
-
- - - - - - - +
+
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/layout.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/layout.pt new file mode 100644 index 000000000..ba40fd6f4 --- /dev/null +++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/layout.pt @@ -0,0 +1,59 @@ + + + + + + + + + + + ${page.__name__} - Pyramid tutorial wiki (based on + TurboGears 20-Minute Wiki) + + + + + + + + + + + + + +
+
+
+
+ +
+
+
No content
+
+

You can return to the + FrontPage. +

+
+
+
+
+ +
+
+
+ + + + + + + + diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt index 626db6637..acc4876cf 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt +++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt @@ -1,39 +1,7 @@ - - - - - - - - - +
+
- Login - Pyramid tutorial wiki (based on - TurboGears 20-Minute Wiki) - - - - - - - - - - - - - -
-
-
-
- -
-
-
+

Login @@ -54,22 +22,7 @@

-
-
-
- -
-
-
- - - - - - - +
+
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt index f2a9249ef..911ab0c99 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt +++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt @@ -1,72 +1,23 @@ - - - - - - - - - +
+
- ${page.__name__} - Pyramid tutorial wiki (based on - TurboGears 20-Minute Wiki) - - - - - - - - - - - - -
-
-
-
- -
-
-
-

+

+

Logout -

-
- Page text goes here. -
-

+

+
+ Page text goes here. +
+

Edit this page -

-

- Viewing - Page Name Goes Here -

-

You can return to the - FrontPage. -

-
-
-
-
- +

+

+ Viewing + Page Name Goes Here +

-
-
- - - - - - - +
+
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/tests.py b/docs/tutorials/wiki/src/authorization/tutorial/tests.py index ca7a47279..6279d9f66 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/tests.py +++ b/docs/tutorials/wiki/src/authorization/tutorial/tests.py @@ -11,7 +11,8 @@ class ViewTests(unittest.TestCase): testing.tearDown() def test_my_view(self): - from .views import my_view + from .views.default import my_view request = testing.DummyRequest() info = my_view(request) self.assertEqual(info['project'], 'myproj') + diff --git a/docs/tutorials/wiki/src/authorization/tutorial/views.py b/docs/tutorials/wiki/src/authorization/tutorial/views.py deleted file mode 100644 index ea2da01af..000000000 --- a/docs/tutorials/wiki/src/authorization/tutorial/views.py +++ /dev/null @@ -1,114 +0,0 @@ -from docutils.core import publish_parts -import re - -from pyramid.httpexceptions import HTTPFound - -from pyramid.view import ( - view_config, - forbidden_view_config, - ) - -from pyramid.security import ( - remember, - forget, - ) - - -from .security import USERS, check_password -from .models import Page - -# regular expression used to find WikiWords -wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)") - -@view_config(context='.models.Wiki', - permission='view') -def view_wiki(context, request): - return HTTPFound(location=request.resource_url(context, 'FrontPage')) - -@view_config(context='.models.Page', renderer='templates/view.pt', - permission='view') -def view_page(context, request): - wiki = context.__parent__ - - def check(match): - word = match.group(1) - if word in wiki: - page = wiki[word] - view_url = request.resource_url(page) - return '%s' % (view_url, word) - else: - add_url = request.application_url + '/add_page/' + word - return '%s' % (add_url, word) - - content = publish_parts(context.data, writer_name='html')['html_body'] - content = wikiwords.sub(check, content) - edit_url = request.resource_url(context, 'edit_page') - return dict(page=context, content=content, edit_url=edit_url, - logged_in=request.authenticated_userid) - -@view_config(name='add_page', context='.models.Wiki', - renderer='templates/edit.pt', - permission='edit') -def add_page(context, request): - pagename = request.subpath[0] - if 'form.submitted' in request.params: - body = request.params['body'] - page = Page(body) - page.__name__ = pagename - page.__parent__ = context - context[pagename] = page - return HTTPFound(location=request.resource_url(page)) - save_url = request.resource_url(context, 'add_page', pagename) - page = Page('') - page.__name__ = pagename - page.__parent__ = context - return dict(page=page, save_url=save_url, - logged_in=request.authenticated_userid) - -@view_config(name='edit_page', context='.models.Page', - renderer='templates/edit.pt', - permission='edit') -def edit_page(context, request): - if 'form.submitted' in request.params: - context.data = request.params['body'] - return HTTPFound(location=request.resource_url(context)) - - return dict(page=context, - save_url=request.resource_url(context, 'edit_page'), - logged_in=request.authenticated_userid) - -@view_config(context='.models.Wiki', name='login', - renderer='templates/login.pt') -@forbidden_view_config(renderer='templates/login.pt') -def login(request): - login_url = request.resource_url(request.context, 'login') - referrer = request.url - if referrer == login_url: - referrer = '/' # never use the login form itself as came_from - came_from = request.params.get('came_from', referrer) - message = '' - login = '' - password = '' - if 'form.submitted' in request.params: - login = request.params['login'] - password = request.params['password'] - if check_password(USERS.get(login), password): - headers = remember(request, login) - return HTTPFound(location=came_from, - headers=headers) - message = 'Failed login' - - return dict( - message=message, - url=request.application_url + '/login', - came_from=came_from, - login=login, - password=password, - ) - - -@view_config(context='.models.Wiki', name='logout') -def logout(request): - headers = forget(request) - return HTTPFound(location=request.resource_url(request.context), - headers=headers) diff --git a/docs/tutorials/wiki/src/authorization/tutorial/views/__init__.py b/docs/tutorials/wiki/src/authorization/tutorial/views/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/docs/tutorials/wiki/src/authorization/tutorial/views/default.py b/docs/tutorials/wiki/src/authorization/tutorial/views/default.py new file mode 100644 index 000000000..a44aca786 --- /dev/null +++ b/docs/tutorials/wiki/src/authorization/tutorial/views/default.py @@ -0,0 +1,116 @@ +from docutils.core import publish_parts +import re + +from pyramid.httpexceptions import HTTPFound +from pyramid.security import ( + forget, + remember, +) +from pyramid.view import ( + forbidden_view_config, + view_config, + ) + +from ..models import Page +from ..security import check_password, USERS + +# regular expression used to find WikiWords +wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)") + + +@view_config(context='..models.Wiki', + permission='view') +def view_wiki(context, request): + return HTTPFound(location=request.resource_url(context, 'FrontPage')) + + +@view_config(context='..models.Page', renderer='../templates/view.pt', + permission='view') +def view_page(context, request): + wiki = context.__parent__ + + def check(match): + word = match.group(1) + if word in wiki: + page = wiki[word] + view_url = request.resource_url(page) + return '%s' % (view_url, word) + else: + add_url = request.application_url + '/add_page/' + word + return '%s' % (add_url, word) + + page_text = publish_parts(context.data, writer_name='html')['html_body'] + page_text = wikiwords.sub(check, page_text) + edit_url = request.resource_url(context, 'edit_page') + return dict(page=context, page_text=page_text, edit_url=edit_url, + logged_in=request.authenticated_userid) + + +@view_config(name='add_page', context='..models.Wiki', + renderer='../templates/edit.pt', + permission='edit') +def add_page(context, request): + pagename = request.subpath[0] + if 'form.submitted' in request.params: + body = request.params['body'] + page = Page(body) + page.__name__ = pagename + page.__parent__ = context + context[pagename] = page + return HTTPFound(location=request.resource_url(page)) + save_url = request.resource_url(context, 'add_page', pagename) + page = Page('') + page.__name__ = pagename + page.__parent__ = context + return dict(page=page, save_url=save_url, + logged_in=request.authenticated_userid) + + +@view_config(name='edit_page', context='..models.Page', + renderer='../templates/edit.pt', + permission='edit') +def edit_page(context, request): + if 'form.submitted' in request.params: + context.data = request.params['body'] + return HTTPFound(location=request.resource_url(context)) + + return dict(page=context, + save_url=request.resource_url(context, 'edit_page'), + logged_in=request.authenticated_userid) + + +@view_config(context='..models.Wiki', name='login', + renderer='templates/login.pt') +@forbidden_view_config(renderer='../templates/login.pt') +def login(request): + login_url = request.resource_url(request.context, 'login') + referrer = request.url + if referrer == login_url: + referrer = '/' # never use the login form itself as came_from + came_from = request.params.get('came_from', referrer) + message = '' + login = '' + password = '' + if 'form.submitted' in request.params: + login = request.params['login'] + password = request.params['password'] + if check_password(USERS.get(login), password): + headers = remember(request, login) + return HTTPFound(location=came_from, + headers=headers) + message = 'Failed login' + + return dict( + message=message, + url=request.application_url + '/login', + came_from=came_from, + login=login, + password=password, + ) + + +@view_config(context='..models.Wiki', name='logout') +def logout(request): + headers = forget(request) + return HTTPFound(location=request.resource_url(request.context), + headers=headers) diff --git a/docs/tutorials/wiki/src/authorization/tutorial/views/notfound.py b/docs/tutorials/wiki/src/authorization/tutorial/views/notfound.py new file mode 100644 index 000000000..d44b4d0e6 --- /dev/null +++ b/docs/tutorials/wiki/src/authorization/tutorial/views/notfound.py @@ -0,0 +1,12 @@ +from pyramid.view import notfound_view_config + +from ..models import Page + + +@notfound_view_config(renderer='../templates/404.pt') +def notfound_view(request): + request.response.status = 404 + pagename = request.path + page = Page(pagename) + page.__name__ = pagename + return dict(page=page) -- cgit v1.2.3 From f5355df850ff5abb2e184f74f3acc1bd193d5d72 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 25 Nov 2018 13:29:31 -0800 Subject: Fix template to include either the page name or a title for the title tag --- docs/tutorials/wiki/definingviews.rst | 2 +- docs/tutorials/wiki/src/authorization/tutorial/templates/layout.pt | 2 +- docs/tutorials/wiki/src/authorization/tutorial/views/default.py | 1 + docs/tutorials/wiki/src/views/tutorial/templates/layout.pt | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/tutorials/wiki/definingviews.rst b/docs/tutorials/wiki/definingviews.rst index 81ef4f5c4..bd8dc6ecf 100644 --- a/docs/tutorials/wiki/definingviews.rst +++ b/docs/tutorials/wiki/definingviews.rst @@ -290,7 +290,7 @@ We can do this via :term:`METAL` macros and slots. - The cookiecutter defined a macro named ``layout`` (line 1). This macro consists of the entire template. -- We changed the ``title`` tag to use the ``name`` attribute of a ``page`` object (lines 11-12). +- We changed the ``title`` tag to use the ``name`` attribute of a ``page`` object, or if it does not exist then the page title (lines 11-12). - The cookiecutter defined a macro customization point or `slot` (line 36). This slot is inside the macro ``layout``. Therefore it can be replaced by content, customizing the macro. diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/layout.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/layout.pt index ba40fd6f4..b606e8dad 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/templates/layout.pt +++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/layout.pt @@ -8,7 +8,7 @@ - ${page.__name__} - Pyramid tutorial wiki (based on + <title><span tal:replace="page.__name__ | title"></span> - Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki) diff --git a/docs/tutorials/wiki/src/authorization/tutorial/views/default.py b/docs/tutorials/wiki/src/authorization/tutorial/views/default.py index a44aca786..0725f7660 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/views/default.py +++ b/docs/tutorials/wiki/src/authorization/tutorial/views/default.py @@ -106,6 +106,7 @@ def login(request): came_from=came_from, login=login, password=password, + title='Login', ) diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/layout.pt b/docs/tutorials/wiki/src/views/tutorial/templates/layout.pt index ba40fd6f4..b606e8dad 100644 --- a/docs/tutorials/wiki/src/views/tutorial/templates/layout.pt +++ b/docs/tutorials/wiki/src/views/tutorial/templates/layout.pt @@ -8,7 +8,7 @@ - ${page.__name__} - Pyramid tutorial wiki (based on + <title><span tal:replace="page.__name__ | title"></span> - Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki) -- cgit v1.2.3 From 248d92e49510206b82397e954de3af93c239891b Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 25 Nov 2018 13:37:45 -0800 Subject: Fix path to template --- docs/tutorials/wiki/src/authorization/tutorial/views/default.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/wiki/src/authorization/tutorial/views/default.py b/docs/tutorials/wiki/src/authorization/tutorial/views/default.py index 0725f7660..3a3b170e2 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/views/default.py +++ b/docs/tutorials/wiki/src/authorization/tutorial/views/default.py @@ -80,7 +80,7 @@ def edit_page(context, request): @view_config(context='..models.Wiki', name='login', - renderer='templates/login.pt') + renderer='../templates/login.pt') @forbidden_view_config(renderer='../templates/login.pt') def login(request): login_url = request.resource_url(request.context, 'login') -- cgit v1.2.3 From 9a47083da48d6967e329ff7e1730854760e66ca6 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 25 Nov 2018 23:31:05 -0800 Subject: Remove settings for tm.manager_hook from __init__.py --- docs/tutorials/wiki/src/authorization/tutorial/__init__.py | 1 - docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py | 1 - docs/tutorials/wiki/src/installation/tutorial/__init__.py | 1 - docs/tutorials/wiki/src/models/tutorial/__init__.py | 1 - docs/tutorials/wiki/src/views/tutorial/__init__.py | 1 - 5 files changed, 5 deletions(-) diff --git a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py b/docs/tutorials/wiki/src/authorization/tutorial/__init__.py index 196c0f311..935a5d6d2 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/authorization/tutorial/__init__.py @@ -19,7 +19,6 @@ def main(global_config, **settings): 'sosecret', callback=groupfinder, hashalg='sha512') authz_policy = ACLAuthorizationPolicy() with Configurator(settings=settings) as config: - settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' config.set_authentication_policy(authn_policy) config.set_authorization_policy(authz_policy) config.include('pyramid_tm') diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py b/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py index bd0c71f5b..830a607f3 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py @@ -12,7 +12,6 @@ def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ with Configurator(settings=settings) as config: - settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' config.include('pyramid_tm') config.include('pyramid_retry') config.include('pyramid_zodbconn') diff --git a/docs/tutorials/wiki/src/installation/tutorial/__init__.py b/docs/tutorials/wiki/src/installation/tutorial/__init__.py index bd0c71f5b..830a607f3 100644 --- a/docs/tutorials/wiki/src/installation/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/installation/tutorial/__init__.py @@ -12,7 +12,6 @@ def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ with Configurator(settings=settings) as config: - settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' config.include('pyramid_tm') config.include('pyramid_retry') config.include('pyramid_zodbconn') diff --git a/docs/tutorials/wiki/src/models/tutorial/__init__.py b/docs/tutorials/wiki/src/models/tutorial/__init__.py index bd0c71f5b..830a607f3 100644 --- a/docs/tutorials/wiki/src/models/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/models/tutorial/__init__.py @@ -12,7 +12,6 @@ def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ with Configurator(settings=settings) as config: - settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' config.include('pyramid_tm') config.include('pyramid_retry') config.include('pyramid_zodbconn') diff --git a/docs/tutorials/wiki/src/views/tutorial/__init__.py b/docs/tutorials/wiki/src/views/tutorial/__init__.py index bd0c71f5b..830a607f3 100644 --- a/docs/tutorials/wiki/src/views/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/views/tutorial/__init__.py @@ -12,7 +12,6 @@ def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ with Configurator(settings=settings) as config: - settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' config.include('pyramid_tm') config.include('pyramid_retry') config.include('pyramid_zodbconn') -- cgit v1.2.3 From c2092ff372f49efb73a923d97d641b0561594b3f Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 25 Nov 2018 23:37:10 -0800 Subject: Align line numbers with code - See https://github.com/Pylons/pyramid-cookiecutter-starter/pull/66 --- docs/tutorials/wiki/authorization.rst | 4 ++-- docs/tutorials/wiki/basiclayout.rst | 24 ++++++++---------------- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst index 207b19d10..ef914cab5 100644 --- a/docs/tutorials/wiki/authorization.rst +++ b/docs/tutorials/wiki/authorization.rst @@ -154,7 +154,7 @@ Now add those policies to the configuration: .. literalinclude:: src/authorization/tutorial/__init__.py :lines: 15-25 :lineno-match: - :emphasize-lines: 4-6,9-10 + :emphasize-lines: 4-6,8-9 :language: python Only the highlighted lines need to be added. @@ -313,7 +313,7 @@ Our ``tutorial/__init__.py`` will look like this when we are done: .. literalinclude:: src/authorization/tutorial/__init__.py :linenos: - :emphasize-lines: 3-6,8,18-20,23-24 + :emphasize-lines: 3-6,8,18-20,22-23 :language: python Only the highlighted lines need to be added or edited. diff --git a/docs/tutorials/wiki/basiclayout.rst b/docs/tutorials/wiki/basiclayout.rst index ab9b7df5c..52d3f4670 100644 --- a/docs/tutorials/wiki/basiclayout.rst +++ b/docs/tutorials/wiki/basiclayout.rst @@ -65,53 +65,45 @@ See also :term:`Deployment settings`. This will be a dictionary of settings parsed from the ``.ini`` file, which contains deployment-related values, such as ``pyramid.reload_templates``, ``zodbconn.uri``, and so on. -Next use an explicit transaction manager for our application. -This prevents new transactions from being implicitly created when touching the manager outside of the ``pyramid_tm`` lifecycle. - -.. literalinclude:: src/basiclayout/tutorial/__init__.py - :lines: 15 - :lineno-match: - :language: py - Next include support for ``pyramid_tm``, allowing Pyramid requests to join the active transaction as provided by the `transaction `_ package. .. literalinclude:: src/basiclayout/tutorial/__init__.py - :lines: 16 + :lines: 15 :lineno-match: :language: py Next include support for ``pyramid_retry`` to retry a request when transient exceptions occur. .. literalinclude:: src/basiclayout/tutorial/__init__.py - :lines: 17 + :lines: 16 :lineno-match: :language: py Next include support for ``pyramid_zodbconn``, providing integration between :term:`ZODB` and a Pyramid application. .. literalinclude:: src/basiclayout/tutorial/__init__.py - :lines: 18 + :lines: 17 :lineno-match: :language: py Next set a root factory using our function named ``root_factory``. .. literalinclude:: src/basiclayout/tutorial/__init__.py - :lines: 19 + :lines: 18 :lineno-match: :language: py Next include support for the :term:`Chameleon` template rendering bindings, allowing us to use the ``.pt`` templates. .. literalinclude:: src/basiclayout/tutorial/__init__.py - :lines: 20 + :lines: 19 :lineno-match: :language: py Next include routes from the ``.routes`` module. .. literalinclude:: src/basiclayout/tutorial/__init__.py - :lines: 21 + :lines: 20 :lineno-match: :language: py @@ -132,7 +124,7 @@ The third argument is an optional ``cache_max_age`` which specifies the number o Next perform a :term:`scan`. .. literalinclude:: src/basiclayout/tutorial/__init__.py - :lines: 22 + :lines: 21 :lineno-match: :language: py @@ -144,7 +136,7 @@ The cookiecutter could have equivalently said ``config.scan('tutorial')``, but i Finally use the :meth:`pyramid.config.Configurator.make_wsgi_app` method to return a :term:`WSGI` application. .. literalinclude:: src/basiclayout/tutorial/__init__.py - :lines: 23 + :lines: 22 :lineno-match: :language: py -- cgit v1.2.3 From b2c7fc1bcf8bd12c24314f4fb09d194a28489a7d Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 25 Nov 2018 23:54:08 -0800 Subject: Synch src files between auth and tests --- docs/tutorials/wiki/src/tests/MANIFEST.in | 2 +- docs/tutorials/wiki/src/tests/pytest.ini | 2 +- docs/tutorials/wiki/src/tests/setup.py | 4 +- docs/tutorials/wiki/src/tests/tutorial/__init__.py | 7 +- docs/tutorials/wiki/src/tests/tutorial/models.py | 27 ----- .../wiki/src/tests/tutorial/models/__init__.py | 27 +++++ docs/tutorials/wiki/src/tests/tutorial/pshell.py | 1 + docs/tutorials/wiki/src/tests/tutorial/routes.py | 2 + .../wiki/src/tests/tutorial/static/theme.css | 3 + .../wiki/src/tests/tutorial/templates/404.pt | 10 ++ .../wiki/src/tests/tutorial/templates/edit.pt | 83 ++++----------- .../wiki/src/tests/tutorial/templates/layout.pt | 59 +++++++++++ .../wiki/src/tests/tutorial/templates/login.pt | 57 +--------- .../wiki/src/tests/tutorial/templates/view.pt | 81 +++----------- docs/tutorials/wiki/src/tests/tutorial/views.py | 114 -------------------- .../wiki/src/tests/tutorial/views/__init__.py | 0 .../wiki/src/tests/tutorial/views/default.py | 117 +++++++++++++++++++++ .../wiki/src/tests/tutorial/views/notfound.py | 12 +++ 18 files changed, 278 insertions(+), 330 deletions(-) delete mode 100644 docs/tutorials/wiki/src/tests/tutorial/models.py create mode 100644 docs/tutorials/wiki/src/tests/tutorial/models/__init__.py create mode 100644 docs/tutorials/wiki/src/tests/tutorial/routes.py create mode 100644 docs/tutorials/wiki/src/tests/tutorial/templates/404.pt create mode 100644 docs/tutorials/wiki/src/tests/tutorial/templates/layout.pt delete mode 100644 docs/tutorials/wiki/src/tests/tutorial/views.py create mode 100644 docs/tutorials/wiki/src/tests/tutorial/views/__init__.py create mode 100644 docs/tutorials/wiki/src/tests/tutorial/views/default.py create mode 100644 docs/tutorials/wiki/src/tests/tutorial/views/notfound.py diff --git a/docs/tutorials/wiki/src/tests/MANIFEST.in b/docs/tutorials/wiki/src/tests/MANIFEST.in index 81beba1b1..05cc195d9 100644 --- a/docs/tutorials/wiki/src/tests/MANIFEST.in +++ b/docs/tutorials/wiki/src/tests/MANIFEST.in @@ -1,2 +1,2 @@ include *.txt *.ini *.cfg *.rst -recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml +recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2 diff --git a/docs/tutorials/wiki/src/tests/pytest.ini b/docs/tutorials/wiki/src/tests/pytest.ini index 8b76bc410..a3489cdf8 100644 --- a/docs/tutorials/wiki/src/tests/pytest.ini +++ b/docs/tutorials/wiki/src/tests/pytest.ini @@ -1,3 +1,3 @@ [pytest] testpaths = tutorial -python_files = *.py +python_files = test*.py diff --git a/docs/tutorials/wiki/src/tests/setup.py b/docs/tutorials/wiki/src/tests/setup.py index 7b405745e..fa5948acb 100644 --- a/docs/tutorials/wiki/src/tests/setup.py +++ b/docs/tutorials/wiki/src/tests/setup.py @@ -10,15 +10,15 @@ with open(os.path.join(here, 'CHANGES.txt')) as f: requires = [ 'plaster_pastedeploy', - 'pyramid >= 1.9a', + 'pyramid', 'pyramid_chameleon', 'pyramid_debugtoolbar', + 'waitress', 'pyramid_retry', 'pyramid_tm', 'pyramid_zodbconn', 'transaction', 'ZODB3', - 'waitress', 'docutils', 'bcrypt', ] diff --git a/docs/tutorials/wiki/src/tests/tutorial/__init__.py b/docs/tutorials/wiki/src/tests/tutorial/__init__.py index 58635ea74..935a5d6d2 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/tests/tutorial/__init__.py @@ -15,18 +15,17 @@ def root_factory(request): def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ - settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' authn_policy = AuthTktAuthenticationPolicy( 'sosecret', callback=groupfinder, hashalg='sha512') authz_policy = ACLAuthorizationPolicy() with Configurator(settings=settings) as config: config.set_authentication_policy(authn_policy) config.set_authorization_policy(authz_policy) - config.include('pyramid_chameleon') config.include('pyramid_tm') config.include('pyramid_retry') config.include('pyramid_zodbconn') config.set_root_factory(root_factory) - config.add_static_view('static', 'static', cache_max_age=3600) + config.include('pyramid_chameleon') + config.include('.routes') config.scan() - return config.make_wsgi_app() + return config.make_wsgi_app() diff --git a/docs/tutorials/wiki/src/tests/tutorial/models.py b/docs/tutorials/wiki/src/tests/tutorial/models.py deleted file mode 100644 index ebd70e912..000000000 --- a/docs/tutorials/wiki/src/tests/tutorial/models.py +++ /dev/null @@ -1,27 +0,0 @@ -from persistent import Persistent -from persistent.mapping import PersistentMapping - -from pyramid.security import ( - Allow, - Everyone, - ) - -class Wiki(PersistentMapping): - __name__ = None - __parent__ = None - __acl__ = [ (Allow, Everyone, 'view'), - (Allow, 'group:editors', 'edit') ] - -class Page(Persistent): - def __init__(self, data): - self.data = data - -def appmaker(zodb_root): - if 'app_root' not in zodb_root: - app_root = Wiki() - frontpage = Page('This is the front page') - app_root['FrontPage'] = frontpage - frontpage.__name__ = 'FrontPage' - frontpage.__parent__ = app_root - zodb_root['app_root'] = app_root - return zodb_root['app_root'] diff --git a/docs/tutorials/wiki/src/tests/tutorial/models/__init__.py b/docs/tutorials/wiki/src/tests/tutorial/models/__init__.py new file mode 100644 index 000000000..ebd70e912 --- /dev/null +++ b/docs/tutorials/wiki/src/tests/tutorial/models/__init__.py @@ -0,0 +1,27 @@ +from persistent import Persistent +from persistent.mapping import PersistentMapping + +from pyramid.security import ( + Allow, + Everyone, + ) + +class Wiki(PersistentMapping): + __name__ = None + __parent__ = None + __acl__ = [ (Allow, Everyone, 'view'), + (Allow, 'group:editors', 'edit') ] + +class Page(Persistent): + def __init__(self, data): + self.data = data + +def appmaker(zodb_root): + if 'app_root' not in zodb_root: + app_root = Wiki() + frontpage = Page('This is the front page') + app_root['FrontPage'] = frontpage + frontpage.__name__ = 'FrontPage' + frontpage.__parent__ = app_root + zodb_root['app_root'] = app_root + return zodb_root['app_root'] diff --git a/docs/tutorials/wiki/src/tests/tutorial/pshell.py b/docs/tutorials/wiki/src/tests/tutorial/pshell.py index 3d026291b..a7cfa6a27 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/pshell.py +++ b/docs/tutorials/wiki/src/tests/tutorial/pshell.py @@ -1,5 +1,6 @@ from . import models + def setup(env): request = env['request'] diff --git a/docs/tutorials/wiki/src/tests/tutorial/routes.py b/docs/tutorials/wiki/src/tests/tutorial/routes.py new file mode 100644 index 000000000..3c0a37992 --- /dev/null +++ b/docs/tutorials/wiki/src/tests/tutorial/routes.py @@ -0,0 +1,2 @@ +def includeme(config): + config.add_static_view('static', 'static', cache_max_age=3600) diff --git a/docs/tutorials/wiki/src/tests/tutorial/static/theme.css b/docs/tutorials/wiki/src/tests/tutorial/static/theme.css index 0f4b1a4d4..a70ee557a 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/static/theme.css +++ b/docs/tutorials/wiki/src/tests/tutorial/static/theme.css @@ -17,6 +17,9 @@ h6 { p { font-weight: 300; } +button, input, optgroup, select, textarea { + color: black; +} .font-normal { font-weight: 400; } diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/404.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/404.pt new file mode 100644 index 000000000..07298940c --- /dev/null +++ b/docs/tutorials/wiki/src/tests/tutorial/templates/404.pt @@ -0,0 +1,10 @@ +
+
+ +
+

Pyramid Starter project

+

404 Page Not Found

+
+ +
+
diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt index eedb83da4..6438b1569 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt +++ b/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt @@ -1,72 +1,27 @@ - - - - - - - - - +
+
- ${page.__name__} - Pyramid tutorial wiki (based on - TurboGears 20-Minute Wiki) - - - - - - - - - - - - -
-
-
-
- -
-
-
-

+

+

Logout -

-

- Editing - Page Name Goes Here -

-

You can return to the - FrontPage. -

-
+

+

+ Editing + Page Name Goes Here +

+
- +
- +
-
-
-
+
-
- -
-
-
- - - - - - - +
+
diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/layout.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/layout.pt new file mode 100644 index 000000000..b606e8dad --- /dev/null +++ b/docs/tutorials/wiki/src/tests/tutorial/templates/layout.pt @@ -0,0 +1,59 @@ + + + + + + + + + + + <span tal:replace="page.__name__ | title"></span> - Pyramid tutorial wiki (based on + TurboGears 20-Minute Wiki) + + + + + + + + + + + + + +
+
+
+
+ +
+
+
No content
+
+

You can return to the + FrontPage. +

+
+
+
+
+ +
+
+
+ + + + + + + + diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt index 626db6637..acc4876cf 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt +++ b/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt @@ -1,39 +1,7 @@ - - - - - - - - - +
+
- Login - Pyramid tutorial wiki (based on - TurboGears 20-Minute Wiki) - - - - - - - - - - - - - -
-
-
-
- -
-
-
+

Login @@ -54,22 +22,7 @@

-
-
-
- -
-
-
- - - - - - - +
+
diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt index f2a9249ef..911ab0c99 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt +++ b/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt @@ -1,72 +1,23 @@ - - - - - - - - - +
+
- ${page.__name__} - Pyramid tutorial wiki (based on - TurboGears 20-Minute Wiki) - - - - - - - - - - - - -
-
-
-
- -
-
-
-

+

+

Logout -

-
- Page text goes here. -
-

+

+
+ Page text goes here. +
+

Edit this page -

-

- Viewing - Page Name Goes Here -

-

You can return to the - FrontPage. -

-
-
-
-
- +

+

+ Viewing + Page Name Goes Here +

-
-
- - - - - - - +
+
diff --git a/docs/tutorials/wiki/src/tests/tutorial/views.py b/docs/tutorials/wiki/src/tests/tutorial/views.py deleted file mode 100644 index ea2da01af..000000000 --- a/docs/tutorials/wiki/src/tests/tutorial/views.py +++ /dev/null @@ -1,114 +0,0 @@ -from docutils.core import publish_parts -import re - -from pyramid.httpexceptions import HTTPFound - -from pyramid.view import ( - view_config, - forbidden_view_config, - ) - -from pyramid.security import ( - remember, - forget, - ) - - -from .security import USERS, check_password -from .models import Page - -# regular expression used to find WikiWords -wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)") - -@view_config(context='.models.Wiki', - permission='view') -def view_wiki(context, request): - return HTTPFound(location=request.resource_url(context, 'FrontPage')) - -@view_config(context='.models.Page', renderer='templates/view.pt', - permission='view') -def view_page(context, request): - wiki = context.__parent__ - - def check(match): - word = match.group(1) - if word in wiki: - page = wiki[word] - view_url = request.resource_url(page) - return '%s' % (view_url, word) - else: - add_url = request.application_url + '/add_page/' + word - return '%s' % (add_url, word) - - content = publish_parts(context.data, writer_name='html')['html_body'] - content = wikiwords.sub(check, content) - edit_url = request.resource_url(context, 'edit_page') - return dict(page=context, content=content, edit_url=edit_url, - logged_in=request.authenticated_userid) - -@view_config(name='add_page', context='.models.Wiki', - renderer='templates/edit.pt', - permission='edit') -def add_page(context, request): - pagename = request.subpath[0] - if 'form.submitted' in request.params: - body = request.params['body'] - page = Page(body) - page.__name__ = pagename - page.__parent__ = context - context[pagename] = page - return HTTPFound(location=request.resource_url(page)) - save_url = request.resource_url(context, 'add_page', pagename) - page = Page('') - page.__name__ = pagename - page.__parent__ = context - return dict(page=page, save_url=save_url, - logged_in=request.authenticated_userid) - -@view_config(name='edit_page', context='.models.Page', - renderer='templates/edit.pt', - permission='edit') -def edit_page(context, request): - if 'form.submitted' in request.params: - context.data = request.params['body'] - return HTTPFound(location=request.resource_url(context)) - - return dict(page=context, - save_url=request.resource_url(context, 'edit_page'), - logged_in=request.authenticated_userid) - -@view_config(context='.models.Wiki', name='login', - renderer='templates/login.pt') -@forbidden_view_config(renderer='templates/login.pt') -def login(request): - login_url = request.resource_url(request.context, 'login') - referrer = request.url - if referrer == login_url: - referrer = '/' # never use the login form itself as came_from - came_from = request.params.get('came_from', referrer) - message = '' - login = '' - password = '' - if 'form.submitted' in request.params: - login = request.params['login'] - password = request.params['password'] - if check_password(USERS.get(login), password): - headers = remember(request, login) - return HTTPFound(location=came_from, - headers=headers) - message = 'Failed login' - - return dict( - message=message, - url=request.application_url + '/login', - came_from=came_from, - login=login, - password=password, - ) - - -@view_config(context='.models.Wiki', name='logout') -def logout(request): - headers = forget(request) - return HTTPFound(location=request.resource_url(request.context), - headers=headers) diff --git a/docs/tutorials/wiki/src/tests/tutorial/views/__init__.py b/docs/tutorials/wiki/src/tests/tutorial/views/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/docs/tutorials/wiki/src/tests/tutorial/views/default.py b/docs/tutorials/wiki/src/tests/tutorial/views/default.py new file mode 100644 index 000000000..3a3b170e2 --- /dev/null +++ b/docs/tutorials/wiki/src/tests/tutorial/views/default.py @@ -0,0 +1,117 @@ +from docutils.core import publish_parts +import re + +from pyramid.httpexceptions import HTTPFound +from pyramid.security import ( + forget, + remember, +) +from pyramid.view import ( + forbidden_view_config, + view_config, + ) + +from ..models import Page +from ..security import check_password, USERS + +# regular expression used to find WikiWords +wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)") + + +@view_config(context='..models.Wiki', + permission='view') +def view_wiki(context, request): + return HTTPFound(location=request.resource_url(context, 'FrontPage')) + + +@view_config(context='..models.Page', renderer='../templates/view.pt', + permission='view') +def view_page(context, request): + wiki = context.__parent__ + + def check(match): + word = match.group(1) + if word in wiki: + page = wiki[word] + view_url = request.resource_url(page) + return '%s' % (view_url, word) + else: + add_url = request.application_url + '/add_page/' + word + return '%s' % (add_url, word) + + page_text = publish_parts(context.data, writer_name='html')['html_body'] + page_text = wikiwords.sub(check, page_text) + edit_url = request.resource_url(context, 'edit_page') + return dict(page=context, page_text=page_text, edit_url=edit_url, + logged_in=request.authenticated_userid) + + +@view_config(name='add_page', context='..models.Wiki', + renderer='../templates/edit.pt', + permission='edit') +def add_page(context, request): + pagename = request.subpath[0] + if 'form.submitted' in request.params: + body = request.params['body'] + page = Page(body) + page.__name__ = pagename + page.__parent__ = context + context[pagename] = page + return HTTPFound(location=request.resource_url(page)) + save_url = request.resource_url(context, 'add_page', pagename) + page = Page('') + page.__name__ = pagename + page.__parent__ = context + return dict(page=page, save_url=save_url, + logged_in=request.authenticated_userid) + + +@view_config(name='edit_page', context='..models.Page', + renderer='../templates/edit.pt', + permission='edit') +def edit_page(context, request): + if 'form.submitted' in request.params: + context.data = request.params['body'] + return HTTPFound(location=request.resource_url(context)) + + return dict(page=context, + save_url=request.resource_url(context, 'edit_page'), + logged_in=request.authenticated_userid) + + +@view_config(context='..models.Wiki', name='login', + renderer='../templates/login.pt') +@forbidden_view_config(renderer='../templates/login.pt') +def login(request): + login_url = request.resource_url(request.context, 'login') + referrer = request.url + if referrer == login_url: + referrer = '/' # never use the login form itself as came_from + came_from = request.params.get('came_from', referrer) + message = '' + login = '' + password = '' + if 'form.submitted' in request.params: + login = request.params['login'] + password = request.params['password'] + if check_password(USERS.get(login), password): + headers = remember(request, login) + return HTTPFound(location=came_from, + headers=headers) + message = 'Failed login' + + return dict( + message=message, + url=request.application_url + '/login', + came_from=came_from, + login=login, + password=password, + title='Login', + ) + + +@view_config(context='..models.Wiki', name='logout') +def logout(request): + headers = forget(request) + return HTTPFound(location=request.resource_url(request.context), + headers=headers) diff --git a/docs/tutorials/wiki/src/tests/tutorial/views/notfound.py b/docs/tutorials/wiki/src/tests/tutorial/views/notfound.py new file mode 100644 index 000000000..d44b4d0e6 --- /dev/null +++ b/docs/tutorials/wiki/src/tests/tutorial/views/notfound.py @@ -0,0 +1,12 @@ +from pyramid.view import notfound_view_config + +from ..models import Page + + +@notfound_view_config(renderer='../templates/404.pt') +def notfound_view(request): + request.response.status = 404 + pagename = request.path + page = Page(pagename) + page.__name__ = pagename + return dict(page=page) -- cgit v1.2.3 From 2e1da5700d30519d5e9f0a6feaaefabcfd145249 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 25 Nov 2018 23:54:33 -0800 Subject: rewrap narrative, adjust grammar --- docs/tutorials/wiki/tests.rst | 52 +++++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/docs/tutorials/wiki/tests.rst b/docs/tutorials/wiki/tests.rst index fdd218add..9dacc5f96 100644 --- a/docs/tutorials/wiki/tests.rst +++ b/docs/tutorials/wiki/tests.rst @@ -4,58 +4,56 @@ Adding Tests ============ -We will now add tests for the models and the views and a few functional tests -in ``tests.py``. Tests ensure that an application works, and that it -continues to work when changes are made in the future. +We will now add tests for the models and the views and a few functional tests in ``tests.py``. +Tests ensure that an application works, and that it continues to work when changes are made in the future. + Test the models =============== -We write tests for the ``model`` classes and the ``appmaker``. Changing -``tests.py``, we'll write a separate test class for each ``model`` class, and -we'll write a test class for the ``appmaker``. +We write tests for the ``model`` classes and the ``appmaker``. +Changing ``tests.py``, we will write a separate test class for each ``model`` class +We will also write a test class for the ``appmaker``. + +To do so, we will retain the ``tutorial.tests.ViewTests`` class that was generated from choosing the ``zodb`` backend option. +We will add three test classes, one for each of the following: + +- the ``Page`` model named ``PageModelTests`` +- the ``Wiki`` model named ``WikiModelTests`` +- the appmaker named ``AppmakerTests`` -To do so, we'll retain the ``tutorial.tests.ViewTests`` class that was -generated from choosing the ``zodb`` backend option. We'll add three test -classes: one for the ``Page`` model named ``PageModelTests``, one for the -``Wiki`` model named ``WikiModelTests``, and one for the appmaker named -``AppmakerTests``. Test the views ============== -We'll modify our ``tests.py`` file, adding tests for each view function we -added previously. As a result, we'll delete the ``ViewTests`` class that the -``zodb`` backend option provided, and add four other test classes: -``ViewWikiTests``, ``ViewPageTests``, ``AddPageTests``, and ``EditPageTests``. -These test the ``view_wiki``, ``view_page``, ``add_page``, and ``edit_page`` -views. +We will modify our ``tests.py`` file, adding tests for each view function that we added previously. +As a result, we will delete the ``ViewTests`` class that the ``zodb`` backend option provided, and add four other test classes: ``ViewWikiTests``, ``ViewPageTests``, ``AddPageTests``, and ``EditPageTests``. +These test the ``view_wiki``, ``view_page``, ``add_page``, and ``edit_page`` views. + Functional tests ================ -We'll test the whole application, covering security aspects that are not -tested in the unit tests, like logging in, logging out, checking that -the ``viewer`` user cannot add or edit pages, but the ``editor`` user -can, and so on. +We will test the whole application, covering security aspects that are not tested in the unit tests, such as logging in, logging out, checking that the ``viewer`` user cannot add or edit pages, but the ``editor`` user can, and so on. + View the results of all our edits to ``tests.py`` ================================================= -Open the ``tutorial/tests.py`` module, and edit it such that it appears as -follows: +Open the ``tutorial/tests.py`` module, and edit it such that it appears as follows: .. literalinclude:: src/tests/tutorial/tests.py :linenos: :language: python + Running the tests ================= -We can run these tests by using ``pytest`` similarly to how we did in -:ref:`running_tests`. Courtesy of the cookiecutter, our testing dependencies have -already been satisfied and ``pytest`` and coverage have already been -configured, so we can jump right to running tests. +We can run these tests by using ``pytest`` similarly to how we did in :ref:`running_tests`. +Courtesy of the cookiecutter, our testing dependencies have already been satisfied. +``pytest`` and coverage have already been configured. +We can jump right to running tests. On Unix: -- cgit v1.2.3 From 971e764b0d8cfbd544d6a2adfa91100ddb68f752 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Mon, 26 Nov 2018 00:50:50 -0800 Subject: Add note about docutils deprecation warnings. Submitted a PR to fix the warnings in WebTest https://github.com/Pylons/webtest/pull/207, which usually gets reviewed and merged rapidly, so didn't bother to include a warning for it. --- docs/tutorials/wiki/tests.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/tutorials/wiki/tests.rst b/docs/tutorials/wiki/tests.rst index 9dacc5f96..a0872e605 100644 --- a/docs/tutorials/wiki/tests.rst +++ b/docs/tutorials/wiki/tests.rst @@ -73,3 +73,6 @@ The expected result should look like the following: ......................... 25 passed in 6.87 seconds + +If you use Python 3.7, you may see deprecation warnings from the docutils 0.14 package. +You can apply a [patch](https://sourceforge.net/p/docutils/patches/144/) to fix the issue, or ignore it and wait for the next release of docutils. -- cgit v1.2.3 From 766bdda32a1db5940e637c29ef91b3ba89ffe484 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Mon, 26 Nov 2018 00:52:12 -0800 Subject: Fix imports. Change info key from content to page_text. --- docs/tutorials/wiki/src/tests/tutorial/tests.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/tutorials/wiki/src/tests/tutorial/tests.py b/docs/tutorials/wiki/src/tests/tutorial/tests.py index d713d3fdd..ff1c07b7c 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/tests.py +++ b/docs/tutorials/wiki/src/tests/tutorial/tests.py @@ -43,7 +43,7 @@ class AppmakerTests(unittest.TestCase): class ViewWikiTests(unittest.TestCase): def test_it(self): - from .views import view_wiki + from .views.default import view_wiki context = testing.DummyResource() request = testing.DummyRequest() response = view_wiki(context, request) @@ -51,7 +51,7 @@ class ViewWikiTests(unittest.TestCase): class ViewPageTests(unittest.TestCase): def _callFUT(self, context, request): - from .views import view_page + from .views.default import view_page return view_page(context, request) def test_it(self): @@ -64,7 +64,7 @@ class ViewPageTests(unittest.TestCase): info = self._callFUT(context, request) self.assertEqual(info['page'], context) self.assertEqual( - info['content'], + info['page_text'], '
\n' '

Hello ' 'CruelWorld ' @@ -77,7 +77,7 @@ class ViewPageTests(unittest.TestCase): class AddPageTests(unittest.TestCase): def _callFUT(self, context, request): - from .views import add_page + from .views.default import add_page return add_page(context, request) def test_it_notsubmitted(self): @@ -103,7 +103,7 @@ class AddPageTests(unittest.TestCase): class EditPageTests(unittest.TestCase): def _callFUT(self, context, request): - from .views import edit_page + from .views.default import edit_page return edit_page(context, request) def test_it_notsubmitted(self): -- cgit v1.2.3 From 05292fb519ad33c74663221e7bd6da68eedceb72 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Mon, 26 Nov 2018 00:57:22 -0800 Subject: Update distributing with terms, rewrap --- docs/tutorials/wiki/distributing.rst | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/docs/tutorials/wiki/distributing.rst b/docs/tutorials/wiki/distributing.rst index 36d00adb4..c23f79b5a 100644 --- a/docs/tutorials/wiki/distributing.rst +++ b/docs/tutorials/wiki/distributing.rst @@ -4,10 +4,8 @@ Distributing Your Application ============================= -Once your application works properly, you can create a "tarball" from it by -using the ``setup.py sdist`` command. The following commands assume your -current working directory contains the ``tutorial`` package and the -``setup.py`` file. +Once your application works properly, you can create a :term:`distribution` from it by using the ``setup.py sdist`` command. +The following commands assume your current working directory contains the ``tutorial`` package and the ``setup.py`` file. On Unix: @@ -31,10 +29,8 @@ The output of such a command will be something like: Creating tar archive removing 'tutorial-0.0' (and everything under it) -Note that this command creates a tarball in the "dist" subdirectory named -``tutorial-0.0.tar.gz``. You can send this file to your friends to show them -your cool new application. They should be able to install it by pointing the -``pip install`` command directly at it. Or you can upload it to `PyPI -`_ and share it with the rest of the world, where -it can be downloaded via ``pip install`` remotely like any other package people -download from PyPI. +This command creates a subdirectory named ``dist``. +Inside that is a tarball named ``tutorial-0.0.tar.gz``, which is the :term:`distribution` of your application. +You can send this file to your friends to show them your cool new application. +They should be able to install it by pointing the ``pip install`` command directly at it. +Or you can upload it to `PyPI `_ and share it with the rest of the world, where it can be downloaded via ``pip install`` remotely like any other package people download from PyPI. -- cgit v1.2.3