diff options
| author | Chris McDonough <chrism@plope.com> | 2011-12-14 09:10:10 -0500 |
|---|---|---|
| committer | Chris McDonough <chrism@plope.com> | 2011-12-14 09:10:10 -0500 |
| commit | bfd4b39b3467681ad34b1dda74acd20294e81a86 (patch) | |
| tree | 9c77d9607dd1800f9d410bf0e57e5eb963035d22 | |
| parent | 78d1e4aefa4948904c354268abd53b9311f79b7d (diff) | |
| download | pyramid-bfd4b39b3467681ad34b1dda74acd20294e81a86.tar.gz pyramid-bfd4b39b3467681ad34b1dda74acd20294e81a86.tar.bz2 pyramid-bfd4b39b3467681ad34b1dda74acd20294e81a86.zip | |
- Changed scaffolding machinery around a bit to make it easier for people who
want to have extension scaffolds that can work across Pyramid 1.0.X, 1.1.X,
1.2.X and 1.3.X. See the new "Creating Pyramid Scaffolds" chapter in the
narrative documentation for more info.
- Added an API docs chapter for ``pyramid.scaffolds``.
- Added a narrative docs chapter named "Creating Pyramid Scaffolds".
- The ``template_renderer`` method of ``pyramid.scaffolds.PyramidScaffold``
was renamed to ``render_template``. If you were overriding it, you're a
bad person, because it wasn't an API before now. But we're nice so we're
letting you know.
| -rw-r--r-- | CHANGES.txt | 17 | ||||
| -rw-r--r-- | docs/api.rst | 1 | ||||
| -rw-r--r-- | docs/api/scaffolds.rst | 13 | ||||
| -rw-r--r-- | docs/index.rst | 1 | ||||
| -rw-r--r-- | docs/narr/scaffolding.rst | 171 | ||||
| -rw-r--r-- | docs/whatsnew-1.3.rst | 4 | ||||
| -rw-r--r-- | pyramid/scaffolds/__init__.py | 15 | ||||
| -rw-r--r-- | pyramid/scaffolds/copydir.py | 2 | ||||
| -rw-r--r-- | pyramid/scaffolds/template.py | 35 | ||||
| -rw-r--r-- | pyramid/tests/test_scaffolds/test_template.py | 30 |
10 files changed, 269 insertions, 20 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 977503948..3c6213001 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -13,6 +13,11 @@ Features - Added a backwards compatibility knob to ``pcreate`` to emulate ``paster create`` handling for the ``--list-templates`` option. +- Changed scaffolding machinery around a bit to make it easier for people who + want to have extension scaffolds that can work across Pyramid 1.0.X, 1.1.X, + 1.2.X and 1.3.X. See the new "Creating Pyramid Scaffolds" chapter in the + narrative documentation for more info. + Documentation ------------- @@ -21,6 +26,18 @@ Documentation - Added API docs for ``view_defaults`` class decorator. +- Added an API docs chapter for ``pyramid.scaffolds``. + +- Added a narrative docs chapter named "Creating Pyramid Scaffolds". + +Backwards Incompatibilities +--------------------------- + +- The ``template_renderer`` method of ``pyramid.scaffolds.PyramidScaffold`` + was renamed to ``render_template``. If you were overriding it, you're a + bad person, because it wasn't an API before now. But we're nice so we're + letting you know. + 1.3a1 (2011-12-09) ================== diff --git a/docs/api.rst b/docs/api.rst index 979e8f490..d510c0d27 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -26,6 +26,7 @@ documentation is organized alphabetically by module name. api/renderers api/request api/response + api/scaffolds api/scripting api/security api/session diff --git a/docs/api/scaffolds.rst b/docs/api/scaffolds.rst new file mode 100644 index 000000000..827962e19 --- /dev/null +++ b/docs/api/scaffolds.rst @@ -0,0 +1,13 @@ +.. _scaffolds_module: + +:mod:`pyramid.scaffolds` +------------------------ + +.. automodule:: pyramid.scaffolds + + .. autoclass:: pyramid.scaffolds.Template + :members: + + .. autoclass:: pyramid.scaffolds.PyramidTemplate + :members: + diff --git a/docs/index.rst b/docs/index.rst index df7a422d4..ca5f4aa30 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -93,6 +93,7 @@ Narrative documentation in chapter form explaining how to use narr/extending narr/advconfig narr/extconfig + narr/scaffolding narr/threadlocals narr/zca diff --git a/docs/narr/scaffolding.rst b/docs/narr/scaffolding.rst new file mode 100644 index 000000000..fda0632f8 --- /dev/null +++ b/docs/narr/scaffolding.rst @@ -0,0 +1,171 @@ +.. _scaffolding_chapter: + +Creating Pyramid Scaffolds +========================== + +You can extend Pyramid by creating a :term:`scaffold` template. A scaffold +template is useful if you'd like to distribute a customizable configuration +of Pyramid to other users. Once you've created a scaffold, and someone has +installed the distribution that houses the scaffold, they can use the +``pcreate`` script to create a custom version of your scaffold's template. +Pyramid itself uses scaffolds to allow people to bootstrap new projects. For +example, ``pcreate -s alchemy MyStuff`` causes Pyramid to render the +``alchemy`` scaffold template to the ``MyStuff`` directory. + +Basics +------ + +A scaffold template is just a bunch of source files and directories on disk. +A small definition class points at this directory; it is in turn pointed at +by a :term:`setuptools` "entry point" which registers the scaffold so it can +be found by the ``pcreate`` command. + +To create a scaffold template, create a Python :term:`distribution` to house +the scaffold which includes a ``setup.py`` that relies on the ``setuptools`` +package. See `Creating a Package +<http://guide.python-distribute.org/creation.html>`_ for more information +about how to do this. For the sake of example, we'll pretend the +distribution you create is named ``CoolExtension``, and it has a package +directory within it named ``coolextension`` + +Once you've created the distribution put a "scaffolds" directory within your +distribution's package directory, and create a file within that directory +named ``__init__.py`` with something like the following: + +.. code-block:: python + :linenos: + + # CoolExtension/coolextension/scaffolds/__init__.py + + from pyramid.scaffolds import PyramidTemplate + + class CoolExtensionTemplate(PyramidTemplate): + _template_dir = 'coolextension_scaffold' + summary = 'My cool extension' + +Once this is done, within the ``scaffolds`` directory, create a template +directory. Our example used a template directory named +``coolextension_scaffold``. + +As you create files and directories within the template directory, note that: + +- Files which have a name which are suffixed with the value ``_tmpl`` will be + rendered, and replacing any instance of the literal string ``{{var}}`` with + the string value of the variable named ``var`` provided to the scaffold. + +- Files and directories with filenames that contain the string ``+var+`` will + have that string replaced with the value of the ``var`` variable provided + to the scaffold. + +Otherwise, files and directories which live in the template directory will be +copied directly without modification to the ``pcreate`` output location. + +The variables provided by the default ``PyramidTemplate`` include ``project`` +(the project name provided by the user as an argument to ``pcreate``), +``package`` (a lowercasing and normalizing of the project name provided by +the user), ``random_string`` (a long random string), and ``package_logger`` +(the name of the package's logger). + +See Pyramid's "scaffolds" package +(https://github.com/Pylons/pyramid/tree/master/pyramid/scaffolds) for +concrete examples of scaffold directories (``zodb``, ``alchemy``, and +``starter``, for example). + +After you've created the template directory, add the following to the +``entry_points`` value of your distribution's ``setup.py``: + + [pyramid.scaffold] + coolextension=coolextension.scaffolds:CoolExtensionTemplate + +For example:: + + def setup( + ..., + entry_points = """\ + [pyramid.scaffold] + coolextension=coolextension.scaffolds:CoolExtensionTemplate + """ + ) + +Run your distribution's ``setup.py develop`` or ``setup.py install`` +command. After that, you should be able to see your scaffolding template +listed when you run ``pcreate -l``. It will be named ``coolextension`` +because that's the name we gave it in the entry point setup. Running +``pcreate -s coolextension MyStuff`` will then render your scaffold to an +output directory named ``MyStuff``. + +See the module documentation for :mod:`pyramid.scaffolds` for information +about the API of the :class:`pyramid.scaffolds.PyramidScaffold` class and +related classes. You can override methods of this class to get special +behavior. + +Supporting Older Pyramid Versions +--------------------------------- + +Because different versions of Pyramid handled scaffolding differently, if you +want to have extension scaffolds that can work across Pyramid 1.0.X, 1.1.X, +1.2.X and 1.3.X, you'll need to use something like this bit of horror while +defining your scaffold template: + +.. code-block:: python + :linenos: + + try: # pyramid 1.0.X + # "pyramid.paster_templates" doesn't exist past 1.0.X + from pyramid.paster_templates import PyramidTemplate + from pyramid.paster_templates import paste_script_template_renderer + except ImportError: + try: # pyramid 1.1.X, 1.2.X + # trying to import "paste_script_template_renderer" fails on 1.3.X + from pyramid.scaffolds import paste_script_template_renderer + from pyramid.scaffolds import PyramidTemplate + except ImportError: # pyramid >=1.3a2 + paste_script_template_renderer = None + from pyramid.scaffolds import PyramidTemplate + + class CoolExtensionTemplateTemplate(PyramidTemplate): + _template_dir = 'coolextension_scaffold' + summary = 'My cool extension' + template_renderer = staticmethod(paste_script_template_renderer) + +And then in the setup.py of the package that contains your scaffold, define +the template as a target of both ``paste.paster_create_template`` (for +``paster create``) and ``pyramid.scaffold`` (for ``pcreate``):: + + [paste.paster_create_template] + coolextension=coolextension.scaffolds:CoolExtensionTemplate + [pyramid.scaffold] + coolextension=coolextension.scaffolds:CoolExtensionTemplate + +Doing this hideousness will allow your scaffold to work as a ``paster +create`` target (under 1.0, 1.1, or 1.2) or as a ``pcreate`` target (under +1.3). If an invoker tries to run ``paster create`` against a scaffold +defined this way under 1.3, an error is raised instructing them to use +``pcreate`` instead. + +If you want only to support Pyramid 1.3 only, it's much cleaner, and the API +is stable: + +.. code-block:: python + :linenos: + + from pyramid.scaffolds import PyramidTemplate + + class CoolExtensionTemplate(PyramidTemplate): + _template_dir = 'coolextension_scaffold' + summary = 'My cool_extension' + +You only need to specify a ``paste.paster_create_template`` entry point +target in your ``setup.py`` if you want your scaffold to be consumable by +users of Pyramid 1.0, 1.1, or 1.2. To support only 1.3, specifying only the +``pyramid.scaffold`` entry point is good enough. If you want to support both +``paster create`` and ``pcreate`` (meaning you want to support Pyramid 1.2 +and some older version), you'll need to define both. + +Examples +-------- + +Existing third-party distributions which house scaffolding are available via +:term:`PyPI`. The ``pyramid_jqm``, ``pyramid_zcml`` and ``pyramid_jinja2`` +packages house scaffolds. You can install and examine these packages to see +how they work in the quest to develop your own scaffolding. diff --git a/docs/whatsnew-1.3.rst b/docs/whatsnew-1.3.rst index 608db74cd..12132e7fd 100644 --- a/docs/whatsnew-1.3.rst +++ b/docs/whatsnew-1.3.rst @@ -276,6 +276,10 @@ Documentation Enhancements - A narrative documentation chapter named :ref:`using_introspection` was added. It describes how to query the introspection system. +- Added an API docs chapter for :mod:`pyramid.scaffolds`. + +- Added a narrative docs chapter named :ref:`scaffolding_chapter`. + Dependency Changes ------------------ diff --git a/pyramid/scaffolds/__init__.py b/pyramid/scaffolds/__init__.py index 1906e0f51..ab2b3034a 100644 --- a/pyramid/scaffolds/__init__.py +++ b/pyramid/scaffolds/__init__.py @@ -3,10 +3,20 @@ import os from pyramid.compat import native_ -from pyramid.scaffolds.template import Template +from pyramid.scaffolds.template import Template # API class PyramidTemplate(Template): + """ + A class that can be used as a base class for Pyramid scaffolding + templates. + """ def pre(self, command, output_dir, vars): + """ Overrides :meth:`pyramid.scaffold.template.Template.pre`, adding + several variables to the default variables list (including + ``random_string``, and ``package_logger``). It also prevents common + misnamings (such as naming a package "site" or naming a package + logger "root". + """ if vars['package'] == 'site': raise ValueError('Sorry, you may not name your package "site". ' 'The package name "site" has a special meaning in ' @@ -20,6 +30,9 @@ class PyramidTemplate(Template): return Template.pre(self, command, output_dir, vars) def post(self, command, output_dir, vars): # pragma: no cover + """ Overrides :meth:`pyramid.scaffold.template.Template.post`, to + print "Welcome to Pyramid. Sorry for the convenience." after a + successful scaffolding rendering.""" self.out('Welcome to Pyramid. Sorry for the convenience.') return Template.post(self, command, output_dir, vars) diff --git a/pyramid/scaffolds/copydir.py b/pyramid/scaffolds/copydir.py index c99238d33..d55ea165a 100644 --- a/pyramid/scaffolds/copydir.py +++ b/pyramid/scaffolds/copydir.py @@ -5,8 +5,6 @@ import os import sys import pkg_resources -import cgi -import urllib from pyramid.compat import ( input_, diff --git a/pyramid/scaffolds/template.py b/pyramid/scaffolds/template.py index c7797cc1d..39d0e4b3f 100644 --- a/pyramid/scaffolds/template.py +++ b/pyramid/scaffolds/template.py @@ -16,13 +16,22 @@ from pyramid.scaffolds import copydir fsenc = sys.getfilesystemencoding() class Template(object): + """ Inherit from this base class and override methods to use the Pyramid + scaffolding system.""" copydir = copydir # for testing _template_dir = None def __init__(self, name): self.name = name - def template_renderer(self, content, vars, filename=None): + def render_template(self, content, vars, filename=None): + """ Return a bytestring representing a templated file based on the + input (content) and the variable names defined (vars). ``filename`` + is used for exception reporting.""" + # this method must not be named "template_renderer" fbo of extension + # scaffolds that need to work under pyramid 1.2 and 1.3, and which + # need to do "template_renderer = + # staticmethod(paste_script_template_renderer)" content = native_(content, fsenc) try: return bytes_( @@ -32,14 +41,20 @@ class Template(object): raise def module_dir(self): - """Returns the module directory of this template.""" mod = sys.modules[self.__class__.__module__] return os.path.dirname(mod.__file__) def template_dir(self): + """ Return the template directory of the scaffold. By default, it + returns the value of ``os.path.join(self.module_dir(), + self._template_dir)`` (``self.module_dir()`` returns the module in + which your subclass has been defined). If ``self._template_dir`` is + a tuple this method just returns the value instead of trying to + construct a path. If _template_dir is a tuple, it should be a + 2-element tuple: ``(package_name, package_relative_path)``.""" assert self._template_dir is not None, ( "Template %r didn't set _template_dir" % self) - if isinstance( self._template_dir, tuple): + if isinstance(self._template_dir, tuple): return self._template_dir else: return os.path.join(self.module_dir(), self._template_dir) @@ -78,7 +93,7 @@ class Template(object): interactive=command.options.interactive, overwrite=command.options.overwrite, indent=1, - template_renderer=self.template_renderer + template_renderer=self.render_template, ) def makedirs(self, dir): # pragma: no cover @@ -90,6 +105,18 @@ class Template(object): def out(self, msg): # pragma: no cover print(msg) + # hair for exit with usage when paster create is used under 1.3 instead + # of pcreate for extension scaffolds which need to support multiple + # versions of pyramid; the check_vars method is called by pastescript + # only as the result of "paster create"; pyramid doesn't use it. the + # required_templates tuple is required to allow it to get as far as + # calling check_vars. + required_templates = () + def check_vars(self, vars, other): + raise RuntimeError( + 'Under Pyramid 1.3, you should use the "pcreate" command rather ' + 'than "paster create"') + class TypeMapper(dict): def __getitem__(self, item): diff --git a/pyramid/tests/test_scaffolds/test_template.py b/pyramid/tests/test_scaffolds/test_template.py index e674a338d..d7cf638b6 100644 --- a/pyramid/tests/test_scaffolds/test_template.py +++ b/pyramid/tests/test_scaffolds/test_template.py @@ -7,34 +7,34 @@ class TestTemplate(unittest.TestCase): from pyramid.scaffolds.template import Template return Template(name) - def test_template_renderer_success(self): + def test_render_template_success(self): inst = self._makeOne() - result = inst.template_renderer('{{a}} {{b}}', {'a':'1', 'b':'2'}) + result = inst.render_template('{{a}} {{b}}', {'a':'1', 'b':'2'}) self.assertEqual(result, bytes_('1 2')) - def test_template_renderer_expr_failure(self): + def test_render_template_expr_failure(self): inst = self._makeOne() - self.assertRaises(AttributeError, inst.template_renderer, + self.assertRaises(AttributeError, inst.render_template, '{{a.foo}}', {'a':'1', 'b':'2'}) - def test_template_renderer_expr_success(self): + def test_render_template_expr_success(self): inst = self._makeOne() - result = inst.template_renderer('{{a.lower()}}', {'a':'A'}) + result = inst.render_template('{{a.lower()}}', {'a':'A'}) self.assertEqual(result, b'a') - def test_template_renderer_expr_success_via_pipe(self): + def test_render_template_expr_success_via_pipe(self): inst = self._makeOne() - result = inst.template_renderer('{{b|c|a.lower()}}', {'a':'A'}) + result = inst.render_template('{{b|c|a.lower()}}', {'a':'A'}) self.assertEqual(result, b'a') - def test_template_renderer_expr_success_via_pipe2(self): + def test_render_template_expr_success_via_pipe2(self): inst = self._makeOne() - result = inst.template_renderer('{{b|a.lower()|c}}', {'a':'A'}) + result = inst.render_template('{{b|a.lower()|c}}', {'a':'A'}) self.assertEqual(result, b'a') - def test_template_renderer_expr_value_is_None(self): + def test_render_template_expr_value_is_None(self): inst = self._makeOne() - result = inst.template_renderer('{{a}}', {'a':None}) + result = inst.render_template('{{a}}', {'a':None}) self.assertEqual(result, b'') def test_module_dir(self): @@ -83,7 +83,7 @@ class TestTemplate(unittest.TestCase): self.assertEqual(copydir.output_dir, 'output dir') self.assertEqual(copydir.vars, {'a':1}) self.assertEqual(copydir.kw, - {'template_renderer':inst.template_renderer, + {'template_renderer':inst.render_template, 'indent':1, 'verbosity':1, 'simulate':False, @@ -117,6 +117,10 @@ class TestTemplate(unittest.TestCase): inst.run(command, 'output dir', {'a':1}) self.assertEqual(L, ['output dir']) + def test_check_vars(self): + inst = self._makeOne() + self.assertRaises(RuntimeError, inst.check_vars, 'one', 'two') + class DummyCopydir(object): def copy_dir(self, template_dir, output_dir, vars, **kw): self.template_dir = template_dir |
