diff options
| -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 |
