diecutter¶
Templates as a service.
diecutter exposes an API where you manage templates as resources. The most common operation is to POST data to templates in order to retrieve generated files.
Files and directories are supported. Directories are rendered as archives.
Note
Diecutter is under active development: some (killer) features have not been implemented yet, or they are not mature. Check milestones and vision for details.
That said, features documented below actually work, so give it a try!
Example¶
GET raw content of a template:
$ curl -X GET http://diecutter.io/api/greetings.txt
{{ greetings|default('Hello') }} {{ name }}!
POST data to the template and retrieve generated content:
$ curl -X POST -d name=world http://diecutter.io/api/greetings.txt
Hello world!
Ressources¶
- Documentation: http://diecutter.readthedocs.org
- Online demo: http://diecutter.io
- PyPI page: http://pypi.python.org/pypi/diecutter
- Code repository: https://github.com/novagile/diecutter
- Bugtracker: https://github.com/novagile/diecutter/issues
- Continuous integration: https://travis-ci.org/novagile/diecutter
Contents¶
Demo¶
Let’s try diecutter!
Online demo¶
There is an online server hosting diecutter’s demo:
- index page: http://diecutter.io/
- API: http://diecutter.io/api/
- demo client for Sphinx documentation template: http://diecutter.io/sphinx-docs.html
In sourcecode¶
The demo/ directory in diecutter’s sourcecode [1] contains:
- templates in templates/ folder.
- some HTML-based client, specialized for the sphinx-docs template, as sphinx-docs.html file.
- presets (input data) in presets/ folder.
Feel free to use it as a sandbox.
Note
the online demo uses templates from the source code!
Local demo server¶
System requirements:
Python [2] version 2.6 or 2.7, available as python command.
Note
You may use Virtualenv [3] to make sure the active python is the right one.
make and wget to use the provided Makefile.
Execute:
git clone git@github.com:novagile/diecutter.git
cd diecutter/
make develop
make serve
The last command runs diecutter service on localhost, port 8106. Check it at http://localhost:8106/
Tip
If you cannot execute the Makefile, read it and adapt the few commands it contains to your needs.
Examples¶
The following examples use the Local demo server. But you should be able to execute most of them on the online demo too.
The following examples are doctested [4], using Python’s WebTest [5] to run a diecutter server. Adapt the following setup to your needs:
>>> diecutter_url = diecutter_server.application_url
In the following examples, we use Python’s requests [6] as HTTP client.
>>> import requests
greetings.txt¶
Let’s work on file resource greetings.txt.
>>> greetings_url = diecutter_url + 'greetings.txt'
GET raw content of a template:
>>> response = requests.get(greetings_url)
>>> print response.content
{{ greetings|default('Hello') }} {{ name }}!
POST data to the template and retrieve generated content:
>>> response = requests.post(greetings_url, {'name': u'world'})
>>> print response.content
Hello world!
>>> response = requests.post(greetings_url, {'greetings': u'Greetings',
... 'name': u'professor Falken'})
>>> print response.content
Greetings professor Falken!
See Files for details about working with file resources.
django_admin.py startproject¶
Let’s work on directory resource +django_project+/.
>>> django_project_url = diecutter_url + '+django_project+'
It’s a directory. GET lists the templates it contains:
>>> response = requests.get(django_project_url)
>>> print response.content
+django_project+/manage.py
+django_project+/+django_project+/__init__.py
+django_project+/+django_project+/settings.py
+django_project+/+django_project+/urls.py
+django_project+/+django_project+/wsgi.py
Every template in a directory can be handled as a single file. But let’s focus on the directory feature...
POST returns an archive, with TAR.GZ format by default:
>>> django_project_url = diecutter_url + '+django_project+'
>>> from StringIO import StringIO
>>> import tarfile
>>> response = requests.post(django_project_url,
... {'django_project': u'demo'})
>>> response.headers['Content-Type']
'application/gzip; charset=UTF-8'
>>> archive = tarfile.open(fileobj=StringIO(response.content), mode='r|gz')
>>> print '\n'.join(archive.getnames())
demo/manage.py
demo/demo/__init__.py
demo/demo/settings.py
demo/demo/urls.py
demo/demo/wsgi.py
>>> archive.close()
Without trailing slash, directory name is used as a prefix for filenames. Let’s try the same requests with a trailing slash:
>>> django_project_url = diecutter_url + '+django_project+/'
>>> response = requests.get(django_project_url)
>>> print response.content
manage.py
+django_project+/__init__.py
+django_project+/settings.py
+django_project+/urls.py
+django_project+/wsgi.py
>>> response = requests.post(django_project_url,
... {'django_project': u'demo'})
>>> archive = tarfile.open(fileobj=StringIO(response.content), mode='r|gz')
>>> print '\n'.join(archive.getnames())
manage.py
demo/__init__.py
demo/settings.py
demo/urls.py
demo/wsgi.py
>>> archive.close()
You can get the content as a ZIP archive instead with the “accept” header:
>>> from zipfile import ZipFile
>>> response = requests.post(django_project_url,
... {'django_project': u'demo'},
... headers={'accept': 'application/zip'})
>>> print response.headers['Content-Type']
application/zip; charset=UTF-8
>>> archive = ZipFile(StringIO(response.content))
>>> print '\n'.join(archive.namelist())
manage.py
demo/__init__.py
demo/settings.py
demo/urls.py
demo/wsgi.py
>>> archive.close()
Tip
You can see all supported “accept” headers by requesting an unknown mime type:
>>> response = requests.post(django_project_url,
... {'django_project': u'demo'},
... headers={'accept': 'fake/mime-type'})
>>> response.status_code
406
>>> print response.content
406 Not Acceptable
The server could not comply with the request since it is either malformed or otherwise incorrect.
Supported mime types: */*, application/gzip, application/x-gzip, application/zip
See Directories for details about directory resources.
Dynamic tree template¶
Let’s work on directory resource dynamic-tree/.
>>> dynamic_tree_url = diecutter_url + 'dynamic-tree/'
It’s a directory. GET lists the templates it contains:
>>> response = requests.get(dynamic_tree_url)
>>> print response.content
.diecutter-tree
greeter.txt
greeter.txt is the same as greetings.txt example above:
>>> response = requests.get(dynamic_tree_url + 'greeter.txt')
>>> print response.content
{{ greeter|default('Hello') }} {{ name|default('world') }}!
diecutter-tree is a normal template, with a special name:
>>> response = requests.get(dynamic_tree_url + '.diecutter-tree')
>>> print response.content
[
{% for greeter in greeting_list|default(['hello', 'goodbye']) %}
{
"template": "greeter.txt",
"filename": "{{ greeter }}.txt",
"context": {"greeter": "{{ greeter }}"}
}{% if not loop.last %},{% endif %}
{% endfor %}
]
It renders a list of templates, in JSON:
>>> response = requests.post(dynamic_tree_url + '.diecutter-tree',
... {'greeting_list': [u'bonjour', u'bonsoir']})
>>> print response.content
[
{
"template": "greeter.txt",
"filename": "bonjour.txt",
"context": {"greeter": "bonjour"}
},
{
"template": "greeter.txt",
"filename": "bonsoir.txt",
"context": {"greeter": "bonsoir"}
}
]
And guess what, this list of templates is used to render the directory resource:
>>> response = requests.post(dynamic_tree_url,
... {'name': u'Remy',
... 'greeting_list': [u'bonjour', u'bonsoir']})
>>> archive = tarfile.open(fileobj=StringIO(response.content), mode='r:gz')
>>> print '\n'.join(archive.getnames())
bonjour.txt
bonsoir.txt
>>> print archive.extractfile('bonjour.txt').read()
bonjour Remy!
>>> print archive.extractfile('bonsoir.txt').read()
bonsoir Remy!
>>> archive.close()
Here, the greeter.txt template has been rendered several times, with different context data.
See Dynamic directory trees templates for details about dynamic tree templates.
References
[1] | https://github.com/novagile/diecutter/ |
[2] | http://python.org |
[3] | http://virtualenv.org |
[4] | http://sphinx-doc.org/ext/doctest.html |
[5] | https://pypi.python.org/pypi/WebTest |
[6] | https://pypi.python.org/pypi/requests |
Client howto¶
This section focus on usage of diecutter service API, i.e. usage of diecutter from client’s point of view.
Files¶
Files are the main type of template resource.
Here is service’s API overview:
- PUT: upload template file from client to server.
- GET: retrieve raw template content.
- POST: render single template file against context.
- other HTTP verbs aren’t implemented yet.
Note
In the examples below, we’ll consider that a diecutter service is running at http://localhost:8106.
PUT¶
Put your template to the service:
$ echo "Hello {{ name }}" | curl -X PUT -F "file=@-" http://localhost:8106/hello.txt
{"diecutter": "Ok"}
Warning
Sometimes, you don’t want users to be able to PUT files on your server. That’s why diecutter service can be configured as “read only”. In that case, it is up to you to manage files that live in diecutter’s template directory (which is just a directory in the filesystem).
As an example, diecutter’s online demo is readonly, and templates are synchronized with source code on project’s repository.
GET¶
We can get the raw template we just created via PUT:
$ curl http://localhost:8106/hello.txt
Hello {{ name }}
POST¶
And we can render the template against some variables:
$ curl -X POST -d name=world http://localhost:8106/hello.txt
Hello world
Directories¶
Directories are the second type of template resource. They are collections of files and directories resources.
Here is service’s API overview:
- PUT: directories are automatically created when you PUT a file in it. There is currently no way to create a whole directory with one request (uploading an archive as an example).
- GET: list files in directory.
- POST: render templates in directory against context, as an archive.
- other HTTP verbs aren’t implemented yet.
Note
In the examples below, we’ll consider that a diecutter service is running at http://localhost:8106.
PUT¶
Let’s create some “greetings” directory with “hello.txt” and “goodbye.txt” inside:
$ echo "Hello {{ name }}!" | curl -X PUT -F "file=@-" http://localhost:8106/greetings/hello.txt
{"diecutter": "Ok"}
$ echo "Good bye {{ name }}!" | curl -X PUT -F "file=@-" http://localhost:8106/greetings/goodbye.txt
{"diecutter": "Ok"}
GET¶
GET lists files in directory:
$ curl http://localhost:8106/greetings/
hello.txt
goodbye.txt
Notice that, if you don’t set the trailing slash, you get the same list with folder name as prefix:
$ curl http://localhost:8106/greetings
greetings/hello.txt
greetings/goodbye.txt
POST¶
POST renders directory against a context:
$ curl -X POST -d name="world" -s http://localhost:8106/greetings | tar -zx
$ tree greetings
greetings
├── hello.txt
└── goodbye.txt
0 directory, 2 files
$ cat greetings/hello.txt
Hello world!
$ cat greetings/goodbye.txt
Goodbye world!
All files in directory are rendered with the same context data.
Like with GET, the trailing slash affects filenames: without trailing slash, filenames are prefixed with directory name.
Supported archives types: “accept” header¶
By default, POST requests return GZIP archives.
You can get the content as a TAR.GZ archive with the “accept” header:
$ curl -X POST --header "accept:application/zip" -d name="world" http://localhost:8106/greetings > greetings.zip
$ unzip greetings.zip
$ tree greetings
greetings
├── hello.txt
└── goodbye.txt
0 directory, 2 files
Tip
You can see all supported “accept” headers by requesting an unknown mime type:
$ curl -X POST --header "accept:fake/mime-type" -d name="world" http://localhost:8106/greetings
406 Not Acceptable
The server could not comply with the request since it is either malformed or
otherwise incorrect.
Supported mime types: */*, application/gzip, application/x-gzip,
application/zip
Posting input context data¶
When you perform POST requests on resources, you provide context data, i.e. variables and values.
Diecutter has builtin support for the following input content-types:
- application/x-www-form-urlencoded [1]: the default when you perform POST requests with wget or curl ;
- application/json [2]: JSON encoded data ;
- text/plain: INI-style plain text files [3].
Diecutter expects data to be provided as the body of the request. “multipart/form-data” requests aren’t supported currently.
URL-encoded¶
# Default (implicit application/x-www-form-urlencoded content type).
curl -X POST -d 'who=world' http://localhost:8106/hello
# Explicit "application/x-www-form-urlencoded" content-type.
curl -X POST -d 'who=world' -H "Content-Type: application/x-www-form-urlencoded" http://localhost:8106/hello
JSON¶
curl -X POST -d '{"who": "world"}' -H "Content-Type: application/json" http://localhost:8106/hello
INI¶
# Flat.
curl -X POST -d 'who=world' -H "Content-Type: text/plain" http://localhost:8106/hello
# With sections.
cat > input.ini <<EOF
hello = world
[foo]
bar = baz
EOF
curl -X POST --data-binary '@input.ini' -H "Content-Type: text/plain" http://localhost:8106/foo
curl tips¶
- Pass content of a file using @.
- Pass content from standard input using @-.
- When posting JSON or INI, use --data-binary.
References
[1] | http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1 |
[2] | http://json.org/ |
[3] | https://en.wikipedia.org/wiki/INI_file |
Variable-based output filenames¶
When rendering a directory, it is sometimes useful to use variables in the filenames to output.
Note
This is useless for single files, since most clients allow you to choose the name of the output file.
Setup templates with +variable+ in the filename:
echo "Focus on filename" | curl -X PUT -F "file=@-" http://localhost:8106/foo/+bar+.txt
The variables within + will be resolved and replaced by their value:
$ curl -X POST -d bar=baz http://localhost:8106/foo | tar -zt
foo/baz.txt
Note
This behaviour is disabled when using the template tree template.
This feature is meant for simple cases. If you need advanced things for filenames, use the template tree template.
Dynamic directory trees templates¶
Directory trees can be computed from templates.
Thus you can use all features of template engines to render filenames, select templates or even alter context.
Use cases¶
While rendering a directory...
- skip some files based on variables ;
- render a single template several times with different output filenames ;
- alter template context data for some templates ;
- use loops, conditions... and all template-engine features...
The template tree template¶
When you POST to a directory, diecutter looks for special ”.diecutter-tree” template in that directory. If present, it renders ”.diecutter-tree” against context, decodes JSON, then iterates over items to actually render the directory.
Except when rendering a directory resource, ”.diecutter-tree” template is a normal template resource file. Manage it as any other template file.
Example¶
Let’s explain this feature with an example...
Create a “dynamic-greetings” directory resource, which contains only one “greetings.txt” template:
echo '{{ greeting }} {{ who }}!' | curl -X PUT -F "file=@-" http://localhost:8106/dynamic-greetings/greetings.txt
Let’s make “dynamic-greetings” directory dynamic, by putting a .diecutter-tree template:
echo '[
{% for greeting in greetings %}
{
"template": "greetings.txt",
"filename": "{{ greeting }}.txt",
"context": {"greeting": "{{ greeting }}"}
}{% if not loop.last %},{% endif %}
{% endfor %}
]
' | curl -X PUT -F "file=@-" http://localhost:8106/dynamic-greetings/.diecutter-tree
POST to .diecutter-tree returns a JSON-encoded list:
curl -X POST -d greetings=hello -d greetings=goodbye http://localhost:8106/dynamic-greetings/.diecutter-tree
[
{
"template": "greetings.txt",
"filename": "hello.txt",
"context": {"greeting": "hello"}
},
{
"template": "greetings.txt",
"filename": "goodbye.txt",
"context": {"greeting": "goodbye"}
}
]
JSON-encoded list items are dictionaries with the following keys:
- “template”: relative path to a template, i.e. content to be rendered ;
- “filename”: filename to return to the client ;
- “context”: optional dictionary of context overrides.
Now let’s render the directory as an archive:
$ curl -X POST \
-d name=Remy -d greetings=hello -d greetings=goodbye \
http://localhost:8106/dynamic-greetings \
| tar -zxv
$ tree dynamic-greetings
dynamic-greetings
├── hello.txt
└── goodbye.txt
0 directory, 2 files
$ cat hello.txt
hello Remy!
$ cat goodbye.txt
goodbye Remy!
Server howto¶
This section explains how to setup, configure and run a diecutter server.
Install, configure, run¶
This project is open-source, published under BSD license. See License for details.
If you want to install a development environment, you should go to Contributor guide documentation.
Install¶
Install “diecutter” package with your favorite tool. As an example with pip [2]:
pip install diecutter
Configure¶
Use diecutter’s online demo [3] to generate your local diecutter configuration:
# Adapt "YOUR_TEMPLATE_DIR"!
wget -O diecutter.ini --post-data "template_dir=YOUR_TEMPLATE_DIR" http://diecutter.io/api/diecutter.ini
Run¶
pserve (paster’s server) should have been installed automatically as part of diecutter’s dependencies. Use it to run the service:
pserve diecutter.ini --reload
Check¶
Check it works:
curl http://localhost:8106
You should get an “hello” with diecutter’s version.
References
[1] | http://python.org |
[2] | https://pypi.python.org/pypi/pip/ |
[3] | http://diecutter.io/ |
Framework¶
Diecutter is developped with extensibility in mind. Even if not stable yet, the idea is to build a default implementation upon a flexible framework. So that anyone can adapt the “templates as a service” pattern to his specific needs.
This section dives into diecutter’s internals and explains how to use or extend parts of it.
API¶
Here is API documentation, generated from code.
diecutter¶
diecutter Package¶
Diecutter provides API to manage and render templates.
Utilities to extract context dictionary from request.
- diecutter.contextextractors.CONTEXT_EXTRACTORS = {'': <function extract_post_context at 0x1db49b0>, 'application/json': <function extract_json_context at 0x1db4aa0>, 'text/plain': <function extract_ini_context at 0x1db4b18>, 'application/x-www-form-urlencoded': <function extract_post_context at 0x1db49b0>}¶
Default context extractors configuration.
This configuration is used as fallback value if EXTRACTORS_SETTINGS is not in Pyramid’s registry.
This is a dictionary where:
- keys are (lowercase) content-types.
- values are callables which accept one request argument and return a dictionary (or dictionary-like object).
- diecutter.contextextractors.EXTRACTORS_SETTING = 'diecutter.context_extractors'¶
Key in Pyramid registry where context extractors configuration lives.
- diecutter.contextextractors.extract_context(request)¶
Extract context dictionary from request and return it.
Raise NotImplementedError if request input (content-type) is not supported.
- diecutter.contextextractors.extract_ini_context(request)¶
Extract and return context from a text/ini (ConfigParser) request.
- diecutter.contextextractors.extract_json_context(request)¶
Extract and return context from a application/json request.
- diecutter.contextextractors.extract_post_context(request)¶
Extract and return context from a standard POST request.
- diecutter.contextextractors.get_context_extractors(request)¶
Return context extractors configuration from request.
- exception diecutter.exceptions.DataParsingError¶
Bases: exceptions.Exception
Input data failed to be parsed.
- exception diecutter.exceptions.TemplateError¶
Bases: exceptions.Exception
A template failed to be rendered.
Service implementation for local files.
- class diecutter.local.LocalService¶
Bases: diecutter.service.Service
A service that loads templates on local filesystem.
- get(request)¶
- get_resource(request)¶
Return the resource matching request.
Return value is a FileResource or :py:class`DirResource`.
- get_resource_path(request)¶
Return validated (absolute) resource path from request.
Checks that resource path is inside request’s template_dir.
- get_template_dir(request)¶
Return validated template directory configuration for request.
- post(request)¶
- put(request)¶
Resources exposed on APIs.
- class diecutter.resources.DirResource(path='', engine=None, filename_engine=None)¶
Bases: diecutter.resources.Resource
Container for other files and directories resources.
- content_type¶
- exists¶
- get_file_resource(path)¶
Factory for internal FileResources.
- get_tree_template()¶
Return FileResource that holds directory tree.
- has_tree_template()¶
Return True if .diecutter-tree file exists.
- is_file = False¶
- read()¶
Return directory tree as a list of paths of file resources.
- read_tree()¶
Generate list of paths to contained resources.
- relative_filename(filename, with_prefix=True)¶
Return filename relative to path.
>>> from diecutter.resources import DirResource >>> resource = DirResource(path='abs/path/no-trailing') >>> resource.relative_filename('abs/path/no-trailing/name') 'no-trailing/name' >>> resource.relative_filename('abs/path/no-trailing/nested/name') 'no-trailing/nested/name'
Trailing slash in path affects returned value.
>>> resource = DirResource(path='abs/path/trailing/') >>> resource.relative_filename('abs/path/trailing/name') 'name' >>> resource.relative_filename('abs/path/trailing/nested/name') 'nested/name'
- render(context)¶
Return archive of files in tree rendered against context.
- render_file(template, context)¶
Render a file with context.
- render_tree(context)¶
Generate list of (resource_path, filename, context).
Included resources may depend on context, i.e. some resources may be used several times, or skipped.
Rendered filenames may depend on context, i.e. variables may be used to render filenames.
Context may change for each resource.
- render_tree_from_template(template, context)¶
Generate directory tree from a template file resource.
- class diecutter.resources.FileResource(path='', engine=None, filename_engine=None)¶
Bases: diecutter.resources.Resource
- content_type¶
- exists¶
- is_file = True¶
- read()¶
Return the template source file.
- render(context)¶
Return the template rendered against context.
- class diecutter.resources.Resource(path='', engine=None, filename_engine=None)¶
Bases: object
- content_type¶
- exists¶
- is_dir¶
Return True if resource is a collection of files.
- is_file¶
Return True if resource is a single file.
- read()¶
Return resource content.
- render(context)¶
Return resource rendered against context.
- render_filename(path, context)¶
Return rendered filename against context using FilenameEngine.
Services expose diecutter API.
- class diecutter.service.Service¶
Bases: object
Base class for diecutter services.
- get(request)¶
- get_dispatcher(request, resource, context, writers)¶
Return simple dispatcher (later, would read configuration).
- get_engine(request)¶
Return configured template engine to render templates.
- get_filename_engine(request)¶
Return configured template engine to render filenames.
This is not used for dynamic trees.
- get_resource(request)¶
Return the resource object (instance) matching request.
- get_writers(request, resource, context)¶
Return iterable of writers.
- hello(request)¶
Returns Hello and API version in JSON.
- is_readonly(request)¶
Return “readonly” flag status (boolean) for request.
As an example, PUT operations should be forbidden if readonly flag is On.
- post(request)¶
- put(request)¶
- diecutter.service.register_service(config, name, service, path)¶
Register a diecutter service in Pyramid routing.
Parse settings, set defaults.
- diecutter.settings.defaults = {'diecutter.filename_template_engine': 'diecutter.engines.filename:FilenameEngine', 'diecutter.template_engine': 'diecutter.engines.jinja:Jinja2Engine', 'diecutter.service': 'diecutter.local:LocalService'}¶
Default values for settings.
- diecutter.settings.normalize(settings={})¶
Return a copy of settings dictionary with normalized values.
Sets default values if necessary.
Writers: utilities that write template output as response, files...
- diecutter.writers.file_response(request, resource, context)¶
Render file resource against context, return plain text response.
- diecutter.writers.targz_directory(directory_content)¶
Return a tar.gz file built from iterable directory_content.
directory_content has the same format as diecutter.resources.DirResource.render() output.
- diecutter.writers.targz_directory_response(request, resource, context)¶
Render dir resource against context, return result as tar.gz response.
- diecutter.writers.zip_directory(directory_content)¶
Return a zip file built from iterable directory_contents.
directory_content has the same format as diecutter.resources.DirResource.render() output.
- diecutter.writers.zip_directory_response(request, resource, context)¶
Render dir resource against context, return result as zip response.
WSGI integration.
- diecutter.wsgi.for_file(settings_file)¶
Return WSGI application using settings file.
- diecutter.wsgi.for_modwsgi(settings_file, virtualenv_dir)¶
Return WSGI application for use with mod_wsgi.
- diecutter.wsgi.for_paste(global_config, **settings)¶
Return WSGI application using global_config and settings.
Template engines.
- class diecutter.engines.Engine¶
Bases: object
Base class for template engines.
Mostly used to document engine API.
Subclasses must implement render():
>>> from diecutter.engines import Engine >>> engine = Engine() >>> engine.render('fake-template', {'fake': 1}) ... # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE Traceback (most recent call last): ... NotImplementedError: Subclasses of "diecutter.engines.Engine" must implement render() method.
- render(template, context)¶
Return the rendered template against context.
Django template engine.
- class diecutter.engines.django.DjangoEngine¶
Bases: diecutter.engines.Engine
Django template engine.
- render(template, context)¶
Return the rendered template against context.
Template engine specialized to render filenames.
- class diecutter.engines.filename.FilenameEngine¶
Bases: diecutter.engines.Engine
- render(template, context)¶
Return rendered filename template against context.
Warning
Only flat string variables are accepted. Other variables are ignored silently!
Jinja2 template engine.
- class diecutter.engines.jinja.Jinja2Engine(environment=None)¶
Bases: diecutter.engines.Engine
Jinja2 template engine.
- register_environment_functions()¶
Populate self.environment.globals with some global functions.
- render(template, context)¶
Return the rendered template against context.
- diecutter.engines.jinja.path_join(*args, **kwargs)¶
Return args joined as file paths like with os.path.join().
>>> from diecutter.engines.jinja import path_join >>> path_join('foo', 'bar') 'foo/bar'
Paths are normalized.
>>> path_join('foo', '..', 'bar') 'bar'
You can pass an extra keyword argument ‘target_os’: a value in os.name capabilities.
>>> path_join('foo', 'bar', target_os='posix') 'foo/bar'
Currently, this is using os.path, i.e. the separator and rules for the computer running Jinja2 engine. A NotImplementedError exception will be raised if ‘os’ argument differs from ‘os.name’.
>>> import os >>> os.name == 'posix' # Sorry if you are running tests on another OS. True >>> path_join('foo', 'bar', target_os='nt') # Doctest: +ELLIPSIS Traceback (most recent call last): ... NotImplementedError: Cannot join path with "nt" style. Host OS is "posix".
- diecutter.engines.jinja.path_normalize(path, target_os=None)¶
Normalize path (like os.path.normpath) for given os.
>>> from diecutter.engines.jinja import path_normalize >>> path_normalize('foo/bar') 'foo/bar' >>> path_normalize('foo/toto/../bar') 'foo/bar'
Currently, this is using os.path, i.e. the separator and rules for the computer running Jinja2 engine. A NotImplementedError exception will be raised if ‘os’ argument differs from ‘os.name’.
>>> import os >>> os.name == 'posix' # Sorry if you are running tests on another OS. True >>> path_normalize('foo/bar', target_os='nt') # Doctest: +ELLIPSIS Traceback (most recent call last): ... NotImplementedError: Cannot join path with "nt" style. Host OS is "posix".
Mock template engine, for use in tests.
- class diecutter.engines.mock.MockEngine(render_result=u'RENDER WITH ARGS={args!s} AND KWARGS={kwargs!s}', fail=None)¶
Bases: diecutter.engines.Engine
Template engine mock.
Typical usage:
>>> from diecutter.engines.mock import MockEngine >>> mock_result = u'this is expected result' >>> mock = MockEngine(mock_result) >>> args = ('arg 1', 'arg 2') >>> kwargs = {'kwarg1': 'kwarg 1', 'kwarg2': 'kwarg 2'} >>> mock.render(*args, **kwargs) == mock_result True >>> mock.args == args True >>> mock.kwargs == kwargs True
You can use {args} and {kwargs} in mock result, because render() uses self.render_result.format(args=args, kwargs=kwargs). This feature is used by default:
>>> mock = MockEngine() >>> mock.render_result u'RENDER WITH ARGS={args!s} AND KWARGS={kwargs!s}' >>> mock.render() u'RENDER WITH ARGS=() AND KWARGS={}'
If you setup an exception as fail attribute, then render() will raise that exception.
>>> mock = MockEngine(fail=Exception('An error occured')) >>> mock.render() # Doctest: +ELLIPSIS Traceback (most recent call last): ... Exception: An error occured
- fail = None¶
Whether to raise a TemplateError or not. Also, value used as message in the exception.
- render(*args, **kwargs)¶
Return self.render_result + populates args and kwargs.
If self.fail is not None, then raises a TemplateError(self.fail).
- diecutter.engines.mock.default_render_result = u'RENDER WITH ARGS={args!s} AND KWARGS={kwargs!s}'¶
Default value used as MockEngine.render_result
Tests.
- diecutter.tests.demo_server()¶
Return (running) WebTest’s StopableWSGIServer for demo.
- diecutter.tests.demo_settings(**settings)¶
- diecutter.tests.demo_template_dir()¶
Return absolute path to diecutter’s demo template dir.
- diecutter.tests.webtest_server(application)¶
Return (running) WebTest’s StopableWSGIServer for application.
Tests around diecutter.contextextractors.
- class diecutter.tests.contextextractors.ExtractContextTestCase(methodName='runTest')¶
Bases: unittest.case.TestCase
Test diecutter.contextextractors.extract_context().
- extractor_factory(output)¶
Return callable that takes a request and returns output.
- request_factory(extractors={}, content_type='')¶
Return request with extractors setting and content_type.
- test_mapping()¶
extract_context() uses extractor matching content-type.
- test_no_mapping()¶
extract_context() raises exception if content-type isn’t supported.
- class diecutter.tests.contextextractors.GetContextExtractorsTestCase(methodName='runTest')¶
Bases: unittest.case.TestCase
Test contextextractors.get_context_extractors().
- request_factory(settings={})¶
Return mock request instance.
- test_custom_configuration()¶
get_context_extractors() reads request.registry.settings.
- test_default_configuration()¶
get_context_extractors() with no settings returns default ones.
- class diecutter.tests.contextextractors.IniTestCase(methodName='runTest')¶
Bases: unittest.case.TestCase
Test diecutter.contextextractors.extract_ini_context.
- request_factory(data={})¶
Return mock request instance.
- test_config_parser()¶
extract_ini_context() accepts standard ConfigParser input.
- test_empty()¶
extract_ini_context() with empty data returns empty context.
- test_globals()¶
extract_ini_context() accepts input with no sections.
- test_parsing_error()¶
extract_ini_context() raises DataParsingError in case of error.
- class diecutter.tests.contextextractors.JsonTestCase(methodName='runTest')¶
Bases: unittest.case.TestCase
Test diecutter.contextextractors.extract_json_context.
- request_factory(data={})¶
Return mock request instance.
- test_copy()¶
extract_post_request() returns copy of request’s json_body.
- test_data()¶
extract_post_request() returns request.json_body data.
- class diecutter.tests.contextextractors.PostTestCase(methodName='runTest')¶
Bases: unittest.case.TestCase
Test diecutter.contextextractors.extract_post_context.
- request_factory(data={})¶
Return mock request instance.
- test_copy()¶
extract_post_request() returns copy of request’s data.
- test_data()¶
extract_post_request() returns request.post data.
- test_multiple_values()¶
extract_post_request() handles lists.
Functional tests: run test server and make requests on it.
- class diecutter.tests.functional.FunctionalTestCase(methodName='runTest')¶
Bases: unittest.case.TestCase
- setUp()¶
Run test server with temporary settings.
- tearDown()¶
Cleanup temporary template dir.
- test_get_directory()¶
GET a directory resource returns directory listing.
- test_get_file()¶
GET a file resource returns file contents.
- test_get_file_404()¶
GET a file resource that doesn’t exist returns HTTP 404.
- test_post_directory_targz()¶
POST context for directory returns TAR.GZ file content.
- test_post_directory_zip()¶
POST context for directory with accept header returns ZIP file.
- test_post_file()¶
POST context for template returns rendered content.
- test_put_file()¶
PUT a file as attachment writes file in templates directory.
- test_put_file_subdirs()¶
PUT a file in subdirectories creates those directories.
- test_version()¶
GET on root displays “hello” and version number as JSON.
Tests around diecutter.resources.
- class diecutter.tests.resources.DirResourceTestCase(methodName='runTest')¶
Bases: unittest.case.TestCase
Test diecutter.resources.DirResource.
- test_content_type()¶
DirResource.content_type is ‘application/zip’.
- test_exists_dir()¶
DirResource.exists is True if path points a directory.
- test_exists_false()¶
DirResource.exists is False if dir doesn’t exist at path.
- test_exists_file()¶
DirResource.exists is False if path points a file.
- test_has_tree_template()¶
DirResource.has_tree_template() checks if .diecutter-tree exists.
- test_no_trailing_slash()¶
DirResource with no trailing slash uses dirname as prefix.
- test_read_empty()¶
DirResource.read() empty dir returns empty string.
- test_read_nested()¶
DirResource.read() recurses nested files and directories.
- test_read_one_flat()¶
DirResource.read() one file returns one filename.
- test_read_two_flat()¶
DirResource.read() two files returns two filenames.
- test_render()¶
DirResource.render() returns an iterable of rendered templates.
- test_render_dynamic_tree()¶
DirResource.render_tree() uses .diecutter-tree template.
- test_render_dynamic_tree_relative_paths()¶
Raises exception if .diecutter-tree contains some non relative path.
Warning
This is a security test!
Since dynamic tree templates can be defined by user, we have to check templates path. We don’t want users to be able to render arbitrary locations on the filesystem.
- test_render_template_error()¶
- test_render_tree()¶
DirResource.render_tree() recurses nested files and directories.
- test_trailing_slash()¶
DirResource with trailing slash uses dirname as prefix.
- class diecutter.tests.resources.FileResourceTestCase(methodName='runTest')¶
Bases: unittest.case.TestCase
Test diecutter.resources.FileResource.
- test_content_type()¶
FileResource.content_type is ‘text/plain’.
- test_exists_dir()¶
FileResource.exists is False if path points a directory.
- test_exists_false()¶
FileResource.exists is False if file doesn’t exist at path.
- test_exists_file()¶
FileResource.exists is True if path points a file.
- test_read_empty()¶
FileResource.read() empty file returns empty string.
- test_read_utf8()¶
FileResource.read() decodes UTF-8 files.
- test_render()¶
FileResource.render() generates rendered template against context.
It returns an iteratable or generator.
- test_render_error()¶
FileResource.render() raises TemplateError in case of fail.
- class diecutter.tests.resources.ResourceTestCase(methodName='runTest')¶
Bases: unittest.case.TestCase
Test diecutter.resources.Resource.
- test_content_type()¶
Resource.content_type property must be overriden by subclasses.
- test_exists()¶
Resource.exists property must be overriden by subclasses.
- test_init()¶
Resource constructor accepts optional path and engine.
- test_read()¶
Resource.read() must be overriden by subclasses.
- test_render()¶
Resource.render() must be overriden by subclasses.
- test_render_filename()¶
Resource.render_filename() renders filename against context.
Tests around diecutter.engines package.
Tests around diecutter.engines.django.
- class diecutter.tests.engines.django.DjangoTestCase(methodName='runTest')¶
Bases: unittest.case.TestCase
Test diecutter.engines.django.DjangoEngine.
- test_render_noop()¶
DjangoEngine correctly renders Hello world! template.
- test_render_simple()¶
DjangoEngine correctly renders Hello {{ name }}! template.
- test_template_error()¶
DjangoEngine raises TemplateError in case of exception.
Tests around diecutter.engines.filename.
- class diecutter.tests.engines.filename.FilenameTestCase(methodName='runTest')¶
Bases: unittest.case.TestCase
Test diecutter.engines.filename.FilenameEngine.
- test_render()¶
FilenameEngine.render() renders filename against context.
- test_render_error()¶
FilenameEngine.render() only accepts flat string variables.
Warning
Only flat string variables are accepted. Other variables are ignored silently!
Tests around diecutter.engines.jinja.
- class diecutter.tests.engines.jinja.Jinja2TestCase(methodName='runTest')¶
Bases: unittest.case.TestCase
Test diecutter.engines.jinja.Jinja2Engine.
- test_environment()¶
Jinja2Engine’s environment contains additional functions.
- test_render_noop()¶
Jinja2Engine correctly renders Hello world! template.
- test_render_simple()¶
Jinja2Engine correctly renders Hello {{ name }}! template.
- test_template_error()¶
Jinja2Engine raises TemplateError in case of exception.
Utilities that could be packaged in separate project.
- class diecutter.utils.chdir(new_dir)¶
Bases: object
Context manager that change current working directory.
- class diecutter.utils.temporary_directory¶
Bases: object
Create, yield, and finally delete a temporary directory.
>>> with temporary_directory() as directory: ... os.path.isdir(directory) True >>> os.path.exists(directory) False
Deletion of temporary directory is recursive.
>>> with temporary_directory() as directory: ... filename = os.path.join(directory, 'sample.txt') ... __ = open(filename, 'w').close() ... os.path.isfile(filename) True >>> os.path.isfile(filename) False
- diecutter.utils.to_boolean(value)¶
Convert value string to boolean.
>>> from diecutter.utils.forms import to_boolean >>> to_boolean('1') True >>> to_boolean('0') False >>> to_boolean('yes') True >>> to_boolean('no') False
- diecutter.utils.accepted_types(request)¶
Return list of accepted content types from request’s ‘accept’ header.
- diecutter.utils.execute(command)¶
Run command (a list of arguments) and return (code, stdout, stderr).
>>> from diecutter.utils.sh import execute >>> code, stdout, stderr = execute(['echo', '-n', 'Hello world!']) >>> code 0 >>> stdout 'Hello world!' >>> stderr ''
Execution control.
- class diecutter.utils.dispatchers.FirstResultDispatcher(runners=[])¶
Bases: object
A dispatcher that return the first result got from callables.
Manage temporary directories.
- class diecutter.utils.files.chdir(new_dir)¶
Bases: object
Context manager that change current working directory.
- new_dir = None¶
New directory.
- previous_dir = None¶
Remember previous value of os.getcwd().
- class diecutter.utils.files.temporary_directory¶
Bases: object
Create, yield, and finally delete a temporary directory.
>>> with temporary_directory() as directory: ... os.path.isdir(directory) True >>> os.path.exists(directory) False
Deletion of temporary directory is recursive.
>>> with temporary_directory() as directory: ... filename = os.path.join(directory, 'sample.txt') ... __ = open(filename, 'w').close() ... os.path.isfile(filename) True >>> os.path.isfile(filename) False
Forms and data validation.
- diecutter.utils.forms.to_boolean(value)¶
Convert value string to boolean.
>>> from diecutter.utils.forms import to_boolean >>> to_boolean('1') True >>> to_boolean('0') False >>> to_boolean('yes') True >>> to_boolean('no') False
Tools around webob requests.
- diecutter.utils.http.accepted_types(request)¶
Return list of accepted content types from request’s ‘accept’ header.
Manage shell commands.
- diecutter.utils.sh.execute(command)¶
Run command (a list of arguments) and return (code, stdout, stderr).
>>> from diecutter.utils.sh import execute >>> code, stdout, stderr = execute(['echo', '-n', 'Hello world!']) >>> code 0 >>> stdout 'Hello world!' >>> stderr ''
diecutter¶
Diecutter provides API to manage and render templates.
Utilities to extract context dictionary from request.
- diecutter.contextextractors.CONTEXT_EXTRACTORS = {'': <function extract_post_context at 0x1db49b0>, 'application/json': <function extract_json_context at 0x1db4aa0>, 'text/plain': <function extract_ini_context at 0x1db4b18>, 'application/x-www-form-urlencoded': <function extract_post_context at 0x1db49b0>}¶
Default context extractors configuration.
This configuration is used as fallback value if EXTRACTORS_SETTINGS is not in Pyramid’s registry.
This is a dictionary where:
- keys are (lowercase) content-types.
- values are callables which accept one request argument and return a dictionary (or dictionary-like object).
- diecutter.contextextractors.EXTRACTORS_SETTING = 'diecutter.context_extractors'¶
Key in Pyramid registry where context extractors configuration lives.
- diecutter.contextextractors.extract_context(request)¶
Extract context dictionary from request and return it.
Raise NotImplementedError if request input (content-type) is not supported.
- diecutter.contextextractors.extract_ini_context(request)¶
Extract and return context from a text/ini (ConfigParser) request.
- diecutter.contextextractors.extract_json_context(request)¶
Extract and return context from a application/json request.
- diecutter.contextextractors.extract_post_context(request)¶
Extract and return context from a standard POST request.
- diecutter.contextextractors.get_context_extractors(request)¶
Return context extractors configuration from request.
- exception diecutter.exceptions.DataParsingError¶
Bases: exceptions.Exception
Input data failed to be parsed.
- exception diecutter.exceptions.TemplateError¶
Bases: exceptions.Exception
A template failed to be rendered.
Service implementation for local files.
- class diecutter.local.LocalService¶
Bases: diecutter.service.Service
A service that loads templates on local filesystem.
- get(request)¶
- get_resource(request)¶
Return the resource matching request.
Return value is a FileResource or :py:class`DirResource`.
- get_resource_path(request)¶
Return validated (absolute) resource path from request.
Checks that resource path is inside request’s template_dir.
- get_template_dir(request)¶
Return validated template directory configuration for request.
- post(request)¶
- put(request)¶
Resources exposed on APIs.
- class diecutter.resources.DirResource(path='', engine=None, filename_engine=None)¶
Bases: diecutter.resources.Resource
Container for other files and directories resources.
- content_type¶
- exists¶
- get_file_resource(path)¶
Factory for internal FileResources.
- get_tree_template()¶
Return FileResource that holds directory tree.
- has_tree_template()¶
Return True if .diecutter-tree file exists.
- is_file = False¶
- read()¶
Return directory tree as a list of paths of file resources.
- read_tree()¶
Generate list of paths to contained resources.
- relative_filename(filename, with_prefix=True)¶
Return filename relative to path.
>>> from diecutter.resources import DirResource >>> resource = DirResource(path='abs/path/no-trailing') >>> resource.relative_filename('abs/path/no-trailing/name') 'no-trailing/name' >>> resource.relative_filename('abs/path/no-trailing/nested/name') 'no-trailing/nested/name'
Trailing slash in path affects returned value.
>>> resource = DirResource(path='abs/path/trailing/') >>> resource.relative_filename('abs/path/trailing/name') 'name' >>> resource.relative_filename('abs/path/trailing/nested/name') 'nested/name'
- render(context)¶
Return archive of files in tree rendered against context.
- render_file(template, context)¶
Render a file with context.
- render_tree(context)¶
Generate list of (resource_path, filename, context).
Included resources may depend on context, i.e. some resources may be used several times, or skipped.
Rendered filenames may depend on context, i.e. variables may be used to render filenames.
Context may change for each resource.
- render_tree_from_template(template, context)¶
Generate directory tree from a template file resource.
- class diecutter.resources.FileResource(path='', engine=None, filename_engine=None)¶
Bases: diecutter.resources.Resource
- content_type¶
- exists¶
- is_file = True¶
- read()¶
Return the template source file.
- render(context)¶
Return the template rendered against context.
- class diecutter.resources.Resource(path='', engine=None, filename_engine=None)¶
Bases: object
- content_type¶
- exists¶
- is_dir¶
Return True if resource is a collection of files.
- is_file¶
Return True if resource is a single file.
- read()¶
Return resource content.
- render(context)¶
Return resource rendered against context.
- render_filename(path, context)¶
Return rendered filename against context using FilenameEngine.
Services expose diecutter API.
- class diecutter.service.Service¶
Bases: object
Base class for diecutter services.
- get(request)¶
- get_dispatcher(request, resource, context, writers)¶
Return simple dispatcher (later, would read configuration).
- get_engine(request)¶
Return configured template engine to render templates.
- get_filename_engine(request)¶
Return configured template engine to render filenames.
This is not used for dynamic trees.
- get_resource(request)¶
Return the resource object (instance) matching request.
- get_writers(request, resource, context)¶
Return iterable of writers.
- hello(request)¶
Returns Hello and API version in JSON.
- is_readonly(request)¶
Return “readonly” flag status (boolean) for request.
As an example, PUT operations should be forbidden if readonly flag is On.
- post(request)¶
- put(request)¶
- diecutter.service.register_service(config, name, service, path)¶
Register a diecutter service in Pyramid routing.
Parse settings, set defaults.
- diecutter.settings.defaults = {'diecutter.filename_template_engine': 'diecutter.engines.filename:FilenameEngine', 'diecutter.template_engine': 'diecutter.engines.jinja:Jinja2Engine', 'diecutter.service': 'diecutter.local:LocalService'}¶
Default values for settings.
- diecutter.settings.normalize(settings={})¶
Return a copy of settings dictionary with normalized values.
Sets default values if necessary.
Writers: utilities that write template output as response, files...
- diecutter.writers.file_response(request, resource, context)¶
Render file resource against context, return plain text response.
- diecutter.writers.targz_directory(directory_content)¶
Return a tar.gz file built from iterable directory_content.
directory_content has the same format as diecutter.resources.DirResource.render() output.
- diecutter.writers.targz_directory_response(request, resource, context)¶
Render dir resource against context, return result as tar.gz response.
- diecutter.writers.zip_directory(directory_content)¶
Return a zip file built from iterable directory_contents.
directory_content has the same format as diecutter.resources.DirResource.render() output.
- diecutter.writers.zip_directory_response(request, resource, context)¶
Render dir resource against context, return result as zip response.
WSGI integration.
- diecutter.wsgi.for_file(settings_file)¶
Return WSGI application using settings file.
- diecutter.wsgi.for_modwsgi(settings_file, virtualenv_dir)¶
Return WSGI application for use with mod_wsgi.
- diecutter.wsgi.for_paste(global_config, **settings)¶
Return WSGI application using global_config and settings.
Template engines.
- class diecutter.engines.Engine¶
Bases: object
Base class for template engines.
Mostly used to document engine API.
Subclasses must implement render():
>>> from diecutter.engines import Engine >>> engine = Engine() >>> engine.render('fake-template', {'fake': 1}) ... # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE Traceback (most recent call last): ... NotImplementedError: Subclasses of "diecutter.engines.Engine" must implement render() method.
- render(template, context)¶
Return the rendered template against context.
Django template engine.
- class diecutter.engines.django.DjangoEngine¶
Bases: diecutter.engines.Engine
Django template engine.
- render(template, context)¶
Return the rendered template against context.
Template engine specialized to render filenames.
- class diecutter.engines.filename.FilenameEngine¶
Bases: diecutter.engines.Engine
- render(template, context)¶
Return rendered filename template against context.
Warning
Only flat string variables are accepted. Other variables are ignored silently!
Jinja2 template engine.
- class diecutter.engines.jinja.Jinja2Engine(environment=None)¶
Bases: diecutter.engines.Engine
Jinja2 template engine.
- register_environment_functions()¶
Populate self.environment.globals with some global functions.
- render(template, context)¶
Return the rendered template against context.
- diecutter.engines.jinja.path_join(*args, **kwargs)¶
Return args joined as file paths like with os.path.join().
>>> from diecutter.engines.jinja import path_join >>> path_join('foo', 'bar') 'foo/bar'
Paths are normalized.
>>> path_join('foo', '..', 'bar') 'bar'
You can pass an extra keyword argument ‘target_os’: a value in os.name capabilities.
>>> path_join('foo', 'bar', target_os='posix') 'foo/bar'
Currently, this is using os.path, i.e. the separator and rules for the computer running Jinja2 engine. A NotImplementedError exception will be raised if ‘os’ argument differs from ‘os.name’.
>>> import os >>> os.name == 'posix' # Sorry if you are running tests on another OS. True >>> path_join('foo', 'bar', target_os='nt') # Doctest: +ELLIPSIS Traceback (most recent call last): ... NotImplementedError: Cannot join path with "nt" style. Host OS is "posix".
- diecutter.engines.jinja.path_normalize(path, target_os=None)¶
Normalize path (like os.path.normpath) for given os.
>>> from diecutter.engines.jinja import path_normalize >>> path_normalize('foo/bar') 'foo/bar' >>> path_normalize('foo/toto/../bar') 'foo/bar'
Currently, this is using os.path, i.e. the separator and rules for the computer running Jinja2 engine. A NotImplementedError exception will be raised if ‘os’ argument differs from ‘os.name’.
>>> import os >>> os.name == 'posix' # Sorry if you are running tests on another OS. True >>> path_normalize('foo/bar', target_os='nt') # Doctest: +ELLIPSIS Traceback (most recent call last): ... NotImplementedError: Cannot join path with "nt" style. Host OS is "posix".
Mock template engine, for use in tests.
- class diecutter.engines.mock.MockEngine(render_result=u'RENDER WITH ARGS={args!s} AND KWARGS={kwargs!s}', fail=None)¶
Bases: diecutter.engines.Engine
Template engine mock.
Typical usage:
>>> from diecutter.engines.mock import MockEngine >>> mock_result = u'this is expected result' >>> mock = MockEngine(mock_result) >>> args = ('arg 1', 'arg 2') >>> kwargs = {'kwarg1': 'kwarg 1', 'kwarg2': 'kwarg 2'} >>> mock.render(*args, **kwargs) == mock_result True >>> mock.args == args True >>> mock.kwargs == kwargs True
You can use {args} and {kwargs} in mock result, because render() uses self.render_result.format(args=args, kwargs=kwargs). This feature is used by default:
>>> mock = MockEngine() >>> mock.render_result u'RENDER WITH ARGS={args!s} AND KWARGS={kwargs!s}' >>> mock.render() u'RENDER WITH ARGS=() AND KWARGS={}'
If you setup an exception as fail attribute, then render() will raise that exception.
>>> mock = MockEngine(fail=Exception('An error occured')) >>> mock.render() # Doctest: +ELLIPSIS Traceback (most recent call last): ... Exception: An error occured
- fail = None¶
Whether to raise a TemplateError or not. Also, value used as message in the exception.
- render(*args, **kwargs)¶
Return self.render_result + populates args and kwargs.
If self.fail is not None, then raises a TemplateError(self.fail).
- diecutter.engines.mock.default_render_result = u'RENDER WITH ARGS={args!s} AND KWARGS={kwargs!s}'¶
Default value used as MockEngine.render_result
Tests.
- diecutter.tests.demo_server()¶
Return (running) WebTest’s StopableWSGIServer for demo.
- diecutter.tests.demo_settings(**settings)¶
- diecutter.tests.demo_template_dir()¶
Return absolute path to diecutter’s demo template dir.
- diecutter.tests.webtest_server(application)¶
Return (running) WebTest’s StopableWSGIServer for application.
Tests around diecutter.contextextractors.
- class diecutter.tests.contextextractors.ExtractContextTestCase(methodName='runTest')¶
Bases: unittest.case.TestCase
Test diecutter.contextextractors.extract_context().
- extractor_factory(output)¶
Return callable that takes a request and returns output.
- request_factory(extractors={}, content_type='')¶
Return request with extractors setting and content_type.
- test_mapping()¶
extract_context() uses extractor matching content-type.
- test_no_mapping()¶
extract_context() raises exception if content-type isn’t supported.
- class diecutter.tests.contextextractors.GetContextExtractorsTestCase(methodName='runTest')¶
Bases: unittest.case.TestCase
Test contextextractors.get_context_extractors().
- request_factory(settings={})¶
Return mock request instance.
- test_custom_configuration()¶
get_context_extractors() reads request.registry.settings.
- test_default_configuration()¶
get_context_extractors() with no settings returns default ones.
- class diecutter.tests.contextextractors.IniTestCase(methodName='runTest')¶
Bases: unittest.case.TestCase
Test diecutter.contextextractors.extract_ini_context.
- request_factory(data={})¶
Return mock request instance.
- test_config_parser()¶
extract_ini_context() accepts standard ConfigParser input.
- test_empty()¶
extract_ini_context() with empty data returns empty context.
- test_globals()¶
extract_ini_context() accepts input with no sections.
- test_parsing_error()¶
extract_ini_context() raises DataParsingError in case of error.
- class diecutter.tests.contextextractors.JsonTestCase(methodName='runTest')¶
Bases: unittest.case.TestCase
Test diecutter.contextextractors.extract_json_context.
- request_factory(data={})¶
Return mock request instance.
- test_copy()¶
extract_post_request() returns copy of request’s json_body.
- test_data()¶
extract_post_request() returns request.json_body data.
- class diecutter.tests.contextextractors.PostTestCase(methodName='runTest')¶
Bases: unittest.case.TestCase
Test diecutter.contextextractors.extract_post_context.
- request_factory(data={})¶
Return mock request instance.
- test_copy()¶
extract_post_request() returns copy of request’s data.
- test_data()¶
extract_post_request() returns request.post data.
- test_multiple_values()¶
extract_post_request() handles lists.
Functional tests: run test server and make requests on it.
- class diecutter.tests.functional.FunctionalTestCase(methodName='runTest')¶
Bases: unittest.case.TestCase
- setUp()¶
Run test server with temporary settings.
- tearDown()¶
Cleanup temporary template dir.
- test_get_directory()¶
GET a directory resource returns directory listing.
- test_get_file()¶
GET a file resource returns file contents.
- test_get_file_404()¶
GET a file resource that doesn’t exist returns HTTP 404.
- test_post_directory_targz()¶
POST context for directory returns TAR.GZ file content.
- test_post_directory_zip()¶
POST context for directory with accept header returns ZIP file.
- test_post_file()¶
POST context for template returns rendered content.
- test_put_file()¶
PUT a file as attachment writes file in templates directory.
- test_put_file_subdirs()¶
PUT a file in subdirectories creates those directories.
- test_version()¶
GET on root displays “hello” and version number as JSON.
Tests around diecutter.resources.
- class diecutter.tests.resources.DirResourceTestCase(methodName='runTest')¶
Bases: unittest.case.TestCase
Test diecutter.resources.DirResource.
- test_content_type()¶
DirResource.content_type is ‘application/zip’.
- test_exists_dir()¶
DirResource.exists is True if path points a directory.
- test_exists_false()¶
DirResource.exists is False if dir doesn’t exist at path.
- test_exists_file()¶
DirResource.exists is False if path points a file.
- test_has_tree_template()¶
DirResource.has_tree_template() checks if .diecutter-tree exists.
- test_no_trailing_slash()¶
DirResource with no trailing slash uses dirname as prefix.
- test_read_empty()¶
DirResource.read() empty dir returns empty string.
- test_read_nested()¶
DirResource.read() recurses nested files and directories.
- test_read_one_flat()¶
DirResource.read() one file returns one filename.
- test_read_two_flat()¶
DirResource.read() two files returns two filenames.
- test_render()¶
DirResource.render() returns an iterable of rendered templates.
- test_render_dynamic_tree()¶
DirResource.render_tree() uses .diecutter-tree template.
- test_render_dynamic_tree_relative_paths()¶
Raises exception if .diecutter-tree contains some non relative path.
Warning
This is a security test!
Since dynamic tree templates can be defined by user, we have to check templates path. We don’t want users to be able to render arbitrary locations on the filesystem.
- test_render_template_error()¶
- test_render_tree()¶
DirResource.render_tree() recurses nested files and directories.
- test_trailing_slash()¶
DirResource with trailing slash uses dirname as prefix.
- class diecutter.tests.resources.FileResourceTestCase(methodName='runTest')¶
Bases: unittest.case.TestCase
Test diecutter.resources.FileResource.
- test_content_type()¶
FileResource.content_type is ‘text/plain’.
- test_exists_dir()¶
FileResource.exists is False if path points a directory.
- test_exists_false()¶
FileResource.exists is False if file doesn’t exist at path.
- test_exists_file()¶
FileResource.exists is True if path points a file.
- test_read_empty()¶
FileResource.read() empty file returns empty string.
- test_read_utf8()¶
FileResource.read() decodes UTF-8 files.
- test_render()¶
FileResource.render() generates rendered template against context.
It returns an iteratable or generator.
- test_render_error()¶
FileResource.render() raises TemplateError in case of fail.
- class diecutter.tests.resources.ResourceTestCase(methodName='runTest')¶
Bases: unittest.case.TestCase
Test diecutter.resources.Resource.
- test_content_type()¶
Resource.content_type property must be overriden by subclasses.
- test_exists()¶
Resource.exists property must be overriden by subclasses.
- test_init()¶
Resource constructor accepts optional path and engine.
- test_read()¶
Resource.read() must be overriden by subclasses.
- test_render()¶
Resource.render() must be overriden by subclasses.
- test_render_filename()¶
Resource.render_filename() renders filename against context.
Tests around diecutter.engines package.
Tests around diecutter.engines.django.
- class diecutter.tests.engines.django.DjangoTestCase(methodName='runTest')¶
Bases: unittest.case.TestCase
Test diecutter.engines.django.DjangoEngine.
- test_render_noop()¶
DjangoEngine correctly renders Hello world! template.
- test_render_simple()¶
DjangoEngine correctly renders Hello {{ name }}! template.
- test_template_error()¶
DjangoEngine raises TemplateError in case of exception.
Tests around diecutter.engines.filename.
- class diecutter.tests.engines.filename.FilenameTestCase(methodName='runTest')¶
Bases: unittest.case.TestCase
Test diecutter.engines.filename.FilenameEngine.
- test_render()¶
FilenameEngine.render() renders filename against context.
- test_render_error()¶
FilenameEngine.render() only accepts flat string variables.
Warning
Only flat string variables are accepted. Other variables are ignored silently!
Tests around diecutter.engines.jinja.
- class diecutter.tests.engines.jinja.Jinja2TestCase(methodName='runTest')¶
Bases: unittest.case.TestCase
Test diecutter.engines.jinja.Jinja2Engine.
- test_environment()¶
Jinja2Engine’s environment contains additional functions.
- test_render_noop()¶
Jinja2Engine correctly renders Hello world! template.
- test_render_simple()¶
Jinja2Engine correctly renders Hello {{ name }}! template.
- test_template_error()¶
Jinja2Engine raises TemplateError in case of exception.
Utilities that could be packaged in separate project.
- class diecutter.utils.chdir(new_dir)¶
Bases: object
Context manager that change current working directory.
- class diecutter.utils.temporary_directory¶
Bases: object
Create, yield, and finally delete a temporary directory.
>>> with temporary_directory() as directory: ... os.path.isdir(directory) True >>> os.path.exists(directory) False
Deletion of temporary directory is recursive.
>>> with temporary_directory() as directory: ... filename = os.path.join(directory, 'sample.txt') ... __ = open(filename, 'w').close() ... os.path.isfile(filename) True >>> os.path.isfile(filename) False
- diecutter.utils.to_boolean(value)¶
Convert value string to boolean.
>>> from diecutter.utils.forms import to_boolean >>> to_boolean('1') True >>> to_boolean('0') False >>> to_boolean('yes') True >>> to_boolean('no') False
- diecutter.utils.accepted_types(request)¶
Return list of accepted content types from request’s ‘accept’ header.
- diecutter.utils.execute(command)¶
Run command (a list of arguments) and return (code, stdout, stderr).
>>> from diecutter.utils.sh import execute >>> code, stdout, stderr = execute(['echo', '-n', 'Hello world!']) >>> code 0 >>> stdout 'Hello world!' >>> stderr ''
Execution control.
- class diecutter.utils.dispatchers.FirstResultDispatcher(runners=[])¶
Bases: object
A dispatcher that return the first result got from callables.
Manage temporary directories.
- class diecutter.utils.files.chdir(new_dir)¶
Bases: object
Context manager that change current working directory.
- new_dir = None¶
New directory.
- previous_dir = None¶
Remember previous value of os.getcwd().
- class diecutter.utils.files.temporary_directory¶
Bases: object
Create, yield, and finally delete a temporary directory.
>>> with temporary_directory() as directory: ... os.path.isdir(directory) True >>> os.path.exists(directory) False
Deletion of temporary directory is recursive.
>>> with temporary_directory() as directory: ... filename = os.path.join(directory, 'sample.txt') ... __ = open(filename, 'w').close() ... os.path.isfile(filename) True >>> os.path.isfile(filename) False
Forms and data validation.
- diecutter.utils.forms.to_boolean(value)¶
Convert value string to boolean.
>>> from diecutter.utils.forms import to_boolean >>> to_boolean('1') True >>> to_boolean('0') False >>> to_boolean('yes') True >>> to_boolean('no') False
Tools around webob requests.
- diecutter.utils.http.accepted_types(request)¶
Return list of accepted content types from request’s ‘accept’ header.
Manage shell commands.
- diecutter.utils.sh.execute(command)¶
Run command (a list of arguments) and return (code, stdout, stderr).
>>> from diecutter.utils.sh import execute >>> code, stdout, stderr = execute(['echo', '-n', 'Hello world!']) >>> code 0 >>> stdout 'Hello world!' >>> stderr ''
Contributor guide¶
This document provides guidelines for people who want to contribute to the project.
Create tickets¶
Please use the bugtracker [1] before starting some work:
- check if the bug or feature request has already been filed. It may have been answered too!
- else create a new ticket.
- if you plan to contribute, tell us, so that we are given an opportunity to give feedback as soon as possible.
- Then, in your commit messages, reference the ticket with some refs #TICKET-ID syntax.
Fork and branch¶
Work in forks and branches.
- Prefix your branch with the ticket ID corresponding to the issue. As an example, if you are working on ticket #23 which is about contribute documentation, name your branch like 23-contribute-doc.
- If you work in a development branch and want to refresh it with changes from master, please rebase [2] or merge-based rebase [3], i.e. don’t merge master.
Setup a development environment¶
System requirements:
- Virtualenv [5] for Python [4] version 2.7, available as virtualenv command.
- make and wget to use the provided Makefile.
Execute:
If you cannot execute the Makefile, read it and adapt the few commands it contains to your needs.
The Makefile¶
A Makefile is provided to ease development.
Use it to run common development tasks, such as:
- setup the development environment: make develop ;
- run tests: make test ;
- build documentation: make documentation ;
- run demo server on port 8106: make serve...
The Makefile is intended to be a live reference for the development environment.
About diecutter¶
This section is about the diecutter project itself.
Vision¶
Diecutter is about file generation. Its primary goal is to provide an easy way to render templates against data.
Some leitmotivs:
- focus on file generation.
- in most use cases, don’t bother users with software installation and configuration.
- when users have specific needs, they can easily setup a custom service.
API¶
API design drives diecutter’s development.
Server software¶
Diecutter provides a default implementation of the service, built with Python.
Framework¶
Diecutter is a framework. It must provide material that makes it easy to connect to other tools and easy to extend.
SAAS platform¶
Diecutter is developed with SAAS in mind. The online demo is a draft.
License¶
Copyright (c) 2012, Rémy Hubscher, Benoît Bryon. See Authors and contributors. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the diecutter software nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Authors and contributors¶
- Benoît Bryon <benoit@marmelune.net>
- Rémy Hubscher <remy.hubscher@novapost.fr>
- Korantin Auguste <contact@palkeo.com>
Changelog¶
This document describes changes between each past release. For information about future releases, check milestones [1] and Vision.
0.5 (2013-07-19)¶
- Features #27 and #28 - Experimental support of remote templates, where templates are hosted in Github public repositories. The github service is published at http://diecutter.io/github. It accepts URLs like http://diecutter.io/github/<owner>/<project>/<revision>/<path/to/template/resource>
- Features #57 and #29 - Public online SAAS at http://diecutter.io
- Feature #66 - Introduced support of Django template engine. You can choose the (unique) template engine with diecutter.template_engine configuration directive. Default value is diecutter.engines.jinja:Jinja2Engine.
- Bug #60 - PyPI renders README as HTML (was plain text).
- Bug #65 - Contributor guide mentions dependency to virtualenv (was missing).
- Refactoring #68 - Code follows strict PEP8. Using flake8 in tests.
0.4 (2013-07-17)¶
New feature (tar.gz archives) and marketing (talks).
- Feature #4 - Added support of “accept” header for POST requests on directories: accepted types are ZIP (application/zip) and TAR.GZ (application/gzip).
- Feature #53 - GZIP is now the default archive format when rendering directories. Use “diecutter.default_archive_type = application/zip” in configuration file if you need ZIP format as a default.
- Refactoring #55 - Dropped support of Python 2.6. Tests are run against Python 2.7 only.
- Refactoring #20 - Render functions return generator ; moved response composition (file/archive) into views via writers.
- Feature #46 - Added content of talks in documentation: AFPY event and EuroPython 2013.
- Feature #58 - Highlighted roadmap and vision in README.
See also milestone 0.4 on bugtracker [2].
0.3 (2013-04-16)¶
New features, documentation, bugfixes.
- Bug #44 - Accepted arrays in URL-encoded POST.
- Bug #40 - Setup CORS to allow AJAX requests on diecutter’s API.
- Refactoring #37 - Used Jinja’s environment.
- Bug #34 - Frozen buildout configuration file for development environment.
- Features #31 and #43 - Published diecutter’s demo online. Online API URL changed.
- Feature #24 - Added Sphinx documentation template in diecutter’s demo.
- Feature #23 - Added diecutter’s Sphinx documentation.
- Feature #10 - Added dynamic tree template.
See also milestone 0.3 on bugtracker [3].
0.2 (2013-02-22)¶
Maintenance release, implementation refactoring, tests.
- Refactoring #22 - Added tests.
- Bug #17 - Sort directories alphabetically.
- Bug #13 - Fixed “diecutter.readonly” which was always True.
See also milestone 0.2 on bugtracker [4].
0.1 (2013-01-29)¶
Initial release.
- Bug #11 - On POST requests, handle empty content-type as “application/x-www-form-urlencoded”.
- Feature #8 - Support INI files as input for POST requests.
- Feature #3 - Use a configuration file outside diecutter’s code.
- Feature #2 - If “readonly” option is True, forbid PUT requests.
- Feature #1 - Pass a “diecutter” context variable to templates, containing data such as “diecutter.api_url”, “diecutter.version” and “diecutter.now”.
- Feature - Diecutter service renders directories as ZIP archives.
- Feature - Diecutter service renders files.
See also milestone 0.1 on bugtracker [5].
Notes & references
[1] | https://github.com/novagile/diecutter/issues/milestones |
[2] | https://github.com/novagile/diecutter/issues?milestone=7&state=closed |
[3] | https://github.com/novagile/diecutter/issues?milestone=6&state=closed |
[4] | https://github.com/novagile/diecutter/issues?milestone=2&state=closed |
[5] | https://github.com/novagile/diecutter/issues?milestone=1&state=closed |
Presentations¶
Here are some presentations (mostly slides) about diecutter.
Diecutter: templates as a service¶
Presentation at an AFPY event, march 2013, Paris.
By Rémy Hubscher and Benoit Bryon.
Slides available on http://diecutter.readthedocs.org
This presentation is published under CC BY 3.0
Render templates¶
Render directories¶
Generate configuration¶
Setup template “diecutter.ini” server-side:
diecutter.template_dir = {{ template_dir|default('') }}
diecutter.token = {{ token|default('') }}
diecutter.readonly = {{ readonly|default('false') }}
Generate configuration client-side:
wget --post-data="template_dir=src/templates" \
--output-document="diecutter.ini" \
http://diecutter.io/diecutter.ini
Generate code¶
django startproject reloaded:
curl --data "django_project=demo" \
http://diecutter.io/+django_project+ \
> demo.zip
PasteScript is complicated¶
- needs PasteScript and templates installed locally
- template registration is a pain:
- create template files (Cheetah markup)
- create template class (Cheetah Template)
- update template package’s entry points (setup.py)
- update your environment (reinstall/update)
- Cheetah?
Provisioners are overkill¶
- I just want to render a template against data!
- I don’t need Chef, Salt, Puppet & co.
- Templates aren’t only useful in provisioning.
And I’d like to reuse templates whatever the provisioning tool!
Diecutter’s REST API¶
- POST data and retrieve generated files
- GET raw content of templates
- PUT templates from client to server
- DELETE template (1)
- OPTIONS lists template variables (1)
- PATCH alters default values of variables (1)
(1) Work in progress.
Encapsulated implementation(s)¶
- template resources backend
- Today: local filesystem.
- Tomorrow: remote Git/Hg/..., NoSQL, include/extend URLs
- template engines
- Today: Jinja.
- Tomorrow: Cheetah, ERB, XSLT...
- response writers
- Today: return single file or ZIP archive.
- Tomorrow: tar.gz, write to storage, attachments, streaming, asynchronous generation...
Roadmap¶
- today: proof of concept, focus on API
- tomorrow: rock-solid defaults, focus on stability & performance
- later: make your own, focus on extensibility
Resources¶
- PyPI: https://pypi.python.org/pypi/diecutter
- Code repository: https://github.com/novagile/diecutter/
- Bugs and features: https://github.com/novagile/diecutter/issues
- Slides: http://tech.novapost.fr
diecutter: templates as a service¶
This is a draft for diecutter poster session at EuroPython 2013, Florence.
Here is the poster: poster.pdf.
Generate configuration¶
- Easy to automate
- POST context data in file
- Pick a single file in directory
- Need to see the diff? Use mercurial or git!
Generate code¶
Run:
You get:
.
├── demo/
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
└── manage.py
Run:
You get:
docs/
├── conf.py
├── demo.txt
├── index.txt
└── Makefile
- All you need is an HTTP client
- Use community templates or your own
- Dynamic filenames
- Dynamic trees
- Online HTML client => user-friendly form
Adapt¶
Multiple template engines¶
- Jinja
- Cheetah
- Mako
- XML+XSLT
- ERB...
Templates anywhere¶
- Local files
- Online code repositories
- URL
KISS¶
Authentication, data validation, client... can be setup by combination with other tools (frontends, wsgi...)
Easy to deploy¶
- Ready to use SAAS platform
- Simple setup of private or local servers
- Open source
REST API¶
- GET, PUT, DELETE templates
- POST data
- Display OPTIONS, PATCH defaults
Built as a framework¶
- Consistent defaults
- Fully configurable
- Extensible
Credits, license¶
https://diecutter.readthedocs.org
diecutter is licensed under BSD
- 2012-2013, Benoît Bryon & Rémy Hubscher
FAQ¶
Here are some frequently asked questions...
Why “diecutter” name?¶
A “die cutter” is a machine that cuts materials to produce shapes, from molds.
Here, the diecutter web service produces files from data and templates.
Does it replaces my web framework (Django) templates?¶
diecutter is not meant to replace web framework’s template engines! Diecutter is meant to replace file generators for configuration or scaffolding use cases.
Even if processes are really close, environment and needs aren’t. As an example, diecutter service won’t be as efficient as web frameworks internal templating systems.
So, even if, in theory, you could use diecutter as a template engine to render pages of a website, it is discouraged.