Name | clabate JSON |
Version |
0.5.0
JSON |
| download |
home_page | https://declassed.art/repository/clabate |
Summary | Minimalistic class-based templates |
upload_time | 2022-12-09 10:54:52 |
maintainer | |
docs_url | None |
author | AXY |
requires_python | >=3.6 |
license | |
keywords |
|
VCS |
|
bugtrack_url |
|
requirements |
No requirements were recorded.
|
Travis-CI |
No Travis.
|
coveralls test coverage |
No coveralls.
|
=======================================================
Clabate: minimalistic class-based templates for Python.
=======================================================
Clabate is a minimalistic template system for Python language.
Clabate does not offer yet another mini-language for templates.
It is based on class inheritance and `PEP 3101 <https://www.python.org/dev/peps/pep-3101>`_
string formatting.
The basic idea is simple: declare template strings as class attributes
and render them in the right order in some container called ``context``.
Subclasses may re-define template strings and define new ones.
Dynamic content can be generated using class properties and values
passed as *kwargs* to the constructor and/or ``render`` method.
In the very basic layer Clabate implements bare textual templates
which can be used, for example, to generate configuration files.
And that basic layer is extended by ``MarkupTemplate``, which escapes
everything by default, trying to minimize chances to overlook
unescaped substitutions.
========
Examples
========
.. code:: python
from clabate import MarkupTemplate, Markup
from datetime import datetime
class HtmlPage(MarkupTemplate):
html = Markup('''
<html>
<head>
<title>{title}</title>
</head>
<body>
<header>
{header}
</header>
<main>
{main}
</main>
<footer>
{footer}
</footer>
</body>
</html>
''')
class MyPage(HtmlPage):
title = 'My web page'
header = 'Today is {now:%Y-%m-%d}'
main = '<<<Hello, world!>>>'
footer = Markup('<span style="color:grey">Here we go!</span>')
@property
def now(self, context):
return datetime.now()
my_page = MyPage()
context = my_page.render()
print(context.html)
Output:
.. code:: html
<html>
<head>
<title>My web page</title>
</head>
<body>
<header>
Today is 2022-12-09
</header>
<main>
<<<Hello, world!>>>
</main>
<footer>
<span style="color:grey">Here we go!</span>
</footer>
</body>
</html>
How escaping works, an example from
`markup_examples.py <https://declassed.art/repository/clabate/file/tip/clabate/examples/markup_examples.py>`_:
.. code:: python
from types import SimpleNamespace
from clabate import MarkupTemplate, Markup
class MarkupExample(MarkupTemplate):
'''
All values are escaped unless they are Markup.
This applies to everything: complex data, properties, kwargs, and missing values.
'''
attr = 'String attributes are <strong>escaped</strong>'
markup_attr = Markup('Markup attributes are <strong>unchanged</strong>')
snippet1 = Markup('''
{attr}
{markup_attr}
{prop}
{markup_prop}
{constructor_kwarg}
{markup_constructor_kwarg}
{render_kwarg}
{markup_render_kwarg}
What is <a.b[name].c[1].d>? It's {a.b[name].c[1].d}!
What is <a.b[name].c[1].e>? It's {a.b[name].c[1].e} and it is escaped!
And what is foo.bar? {foo.bar}
Wait, what is bar[foo][1]? {bar[foo][1]}
''')
a = SimpleNamespace(
b = dict(
name = SimpleNamespace(
c = [
0,
SimpleNamespace(
d = Markup('<span style="color:green">wow</span>'),
e = '<span style="color:green">wow</span>'
)
]
)
)
)
@property
def prop(self, context):
return 'Property values are <strong>escaped</strong>'
@property
def markup_prop(self, context):
return Markup('Markup property values are <strong>unchanged</strong>')
def missing(self, name):
return Markup(f'<span style="color:red">{name}</span> is missing from the rendering context!')
# And values are escaped only once!
snippet2 = Markup('''
Values are escaped only once:
{snippet1}
Otherwise we'd get lots of & instead of bare &
''')
# Render MarkupExample
template = MarkupExample(
constructor_kwarg='Constructor kwargs are <strong>escaped</strong>',
markup_constructor_kwarg=Markup('Markup constructor kwargs are <strong>unchanged</strong>')
)
context = template.render(
render_kwarg='Render kwargs are <strong>escaped</strong>',
markup_render_kwarg=Markup('Markup render kwargs are <strong>unchanged</strong>')
)
print(context.snippet1)
print()
print(context.snippet2)
Output:
.. code:: text
String attributes are <strong>escaped</strong>
Markup attributes are <strong>unchanged</strong>
Property values are <strong>escaped</strong>
Markup property values are <strong>unchanged</strong>
Constructor kwargs are <strong>escaped</strong>
Markup constructor kwargs are <strong>unchanged</strong>
Render kwargs are <strong>escaped</strong>
Markup render kwargs are <strong>unchanged</strong>
What is <a.b[name].c[1].d>? It's <span style="color:green">wow</span>!
What is <a.b[name].c[1].e>? It's <span style="color:green">wow</span> and it is escaped!
And what is foo.bar? <span style="color:red">foo</span> is missing from the rendering context!
Wait, what is bar[foo][1]? <span style="color:red">bar</span> is missing from the rendering context!
Values are escaped only once:
String attributes are <strong>escaped</strong>
Markup attributes are <strong>unchanged</strong>
Property values are <strong>escaped</strong>
Markup property values are <strong>unchanged</strong>
Constructor kwargs are <strong>escaped</strong>
Markup constructor kwargs are <strong>unchanged</strong>
Render kwargs are <strong>escaped</strong>
Markup render kwargs are <strong>unchanged</strong>
What is <a.b[name].c[1].d>? It's <span style="color:green">wow</span>!
What is <a.b[name].c[1].e>? It's <span style="color:green">wow</span> and it is escaped!
And what is foo.bar? <span style="color:red">foo</span> is missing from the rendering context!
Wait, what is bar[foo][1]? <span style="color:red">bar</span> is missing from the rendering context!
Otherwise we'd get lots of & instead of bare &
Plain text example:
.. code:: python
from clabate import Template
import time
from types import SimpleNamespace
class ZoneFileBoilerplate(Template):
zone_config = '''
$TTL 3600
@ IN SOA (
{primary_ns.hostname}.{idna_domain}. ; MNAME
{rname} ; RNAME
{timestamp} ; SERIAL
3600 ; REFRESH
60 ; RETRY
1W ; EXPIRY
60 ; MINIMUM Negative Cache TTL
)
{nameservers}
{resource_records}
'''
@property
def timestamp(self, context):
return int(time.time())
@property
def nameservers(self, context):
ns_template = self.dedent('''
@ IN NS {ns.hostname}.{idna_domain}.
{ns.hostname} IN A {ns.ipv4_addr}
''')
result = []
for ns in [self.primary_ns, self.secondary_ns]:
result.append(self.render_str(context, ns_template, ns=ns))
return ''.join(result)
resource_records = '''
@ IN A {main_server_ipv4}
* IN A {main_server_ipv4}
'''
class DeclassedZone(ZoneFileBoilerplate):
primary_ns = SimpleNamespace(hostname='ns1', ipv4_addr='1.2.3.4')
secondary_ns = SimpleNamespace(hostname='ns2', ipv4_addr='5.6.7.8')
my_zone = DeclassedZone(idna_domain='declassed.art', rname='axy.{idna_domain}.')
context = my_zone.render(main_server_ipv4='9.10.11.12')
print(context.zone_config)
Output:
.. code::
$TTL 3600
@ IN SOA (
ns1.declassed.art. ; MNAME
axy.declassed.art. ; RNAME
1656230266 ; SERIAL
3600 ; REFRESH
60 ; RETRY
1W ; EXPIRY
60 ; MINIMUM Negative Cache TTL
)
@ IN NS ns1.declassed.art.
ns1 IN A 1.2.3.4
@ IN NS ns2.declassed.art.
ns2 IN A 5.6.7.8
@ IN A 9.10.11.12
* IN A 9.10.11.12
There are more examples in `clabate/examples <https://declassed.art/repository/clabate/file/tip/clabate/examples>`_:
* `Basic examples <https://declassed.art/repository/clabate/file/tip/clabate/examples/basic_examples.py>`_
* `Markup examples <https://declassed.art/repository/clabate/file/tip/clabate/examples/markup_examples.py>`_
* `Invoke <https://declassed.art/repository/clabate/file/tip/clabate/examples/invoke_examples.py>`_
* `Pygments <https://declassed.art/repository/clabate/file/tip/clabate/examples/pygments_examples.py>`_
* `Comments <https://declassed.art/repository/clabate/file/tip/clabate/examples/comments_examples.py>`_
* `Sequences <https://declassed.art/repository/clabate/file/tip/clabate/examples/sequence_examples.py>`_
* `File inclusion <https://declassed.art/repository/clabate/file/tip/clabate/examples/file_inclusion_examples.py>`_
See also: https://declassed.art/en/blog/2022/06/29/clabate-class-based-templates
Raw data
{
"_id": null,
"home_page": "https://declassed.art/repository/clabate",
"name": "clabate",
"maintainer": "",
"docs_url": null,
"requires_python": ">=3.6",
"maintainer_email": "",
"keywords": "",
"author": "AXY",
"author_email": "axy@declassed.art",
"download_url": "https://files.pythonhosted.org/packages/f1/bf/05ee914917b9c6b5e27d15c0505305ab5879cfc12d7a1523a2e5d2ffcd6a/clabate-0.5.0.tar.gz",
"platform": null,
"description": "=======================================================\nClabate: minimalistic class-based templates for Python.\n=======================================================\n\nClabate is a minimalistic template system for Python language.\n\nClabate does not offer yet another mini-language for templates.\nIt is based on class inheritance and `PEP 3101 <https://www.python.org/dev/peps/pep-3101>`_\nstring formatting.\n\nThe basic idea is simple: declare template strings as class attributes\nand render them in the right order in some container called ``context``.\n\nSubclasses may re-define template strings and define new ones.\n\nDynamic content can be generated using class properties and values\npassed as *kwargs* to the constructor and/or ``render`` method.\n\nIn the very basic layer Clabate implements bare textual templates\nwhich can be used, for example, to generate configuration files.\n\nAnd that basic layer is extended by ``MarkupTemplate``, which escapes\neverything by default, trying to minimize chances to overlook\nunescaped substitutions.\n\n========\nExamples\n========\n\n.. code:: python\n\n from clabate import MarkupTemplate, Markup\n from datetime import datetime\n\n class HtmlPage(MarkupTemplate):\n html = Markup('''\n <html>\n <head>\n <title>{title}</title>\n </head>\n <body>\n <header>\n {header}\n </header>\n <main>\n {main}\n </main>\n <footer>\n {footer}\n </footer>\n </body>\n </html>\n ''')\n\n class MyPage(HtmlPage):\n title = 'My web page'\n header = 'Today is {now:%Y-%m-%d}'\n main = '<<<Hello, world!>>>'\n footer = Markup('<span style=\"color:grey\">Here we go!</span>')\n\n @property\n def now(self, context):\n return datetime.now()\n\n my_page = MyPage()\n context = my_page.render()\n print(context.html)\n\nOutput:\n\n.. code:: html\n\n <html>\n <head>\n <title>My web page</title>\n </head>\n <body>\n <header>\n Today is 2022-12-09\n </header>\n <main>\n <<<Hello, world!>>>\n </main>\n <footer>\n <span style=\"color:grey\">Here we go!</span>\n </footer>\n </body>\n </html>\n\nHow escaping works, an example from\n`markup_examples.py <https://declassed.art/repository/clabate/file/tip/clabate/examples/markup_examples.py>`_:\n\n.. code:: python\n\n from types import SimpleNamespace\n from clabate import MarkupTemplate, Markup\n\n class MarkupExample(MarkupTemplate):\n '''\n All values are escaped unless they are Markup.\n This applies to everything: complex data, properties, kwargs, and missing values.\n '''\n attr = 'String attributes are <strong>escaped</strong>'\n markup_attr = Markup('Markup attributes are <strong>unchanged</strong>')\n snippet1 = Markup('''\n {attr}\n {markup_attr}\n {prop}\n {markup_prop}\n {constructor_kwarg}\n {markup_constructor_kwarg}\n {render_kwarg}\n {markup_render_kwarg}\n What is <a.b[name].c[1].d>? It's {a.b[name].c[1].d}!\n What is <a.b[name].c[1].e>? It's {a.b[name].c[1].e} and it is escaped!\n And what is foo.bar? {foo.bar}\n Wait, what is bar[foo][1]? {bar[foo][1]}\n ''')\n\n a = SimpleNamespace(\n b = dict(\n name = SimpleNamespace(\n c = [\n 0,\n SimpleNamespace(\n d = Markup('<span style=\"color:green\">wow</span>'),\n e = '<span style=\"color:green\">wow</span>'\n )\n ]\n )\n )\n )\n\n @property\n def prop(self, context):\n return 'Property values are <strong>escaped</strong>'\n\n @property\n def markup_prop(self, context):\n return Markup('Markup property values are <strong>unchanged</strong>')\n\n def missing(self, name):\n return Markup(f'<span style=\"color:red\">{name}</span> is missing from the rendering context!')\n\n # And values are escaped only once!\n snippet2 = Markup('''\n Values are escaped only once:\n {snippet1}\n Otherwise we'd get lots of & instead of bare &\n ''')\n\n # Render MarkupExample\n template = MarkupExample(\n constructor_kwarg='Constructor kwargs are <strong>escaped</strong>',\n markup_constructor_kwarg=Markup('Markup constructor kwargs are <strong>unchanged</strong>')\n )\n context = template.render(\n render_kwarg='Render kwargs are <strong>escaped</strong>',\n markup_render_kwarg=Markup('Markup render kwargs are <strong>unchanged</strong>')\n )\n print(context.snippet1)\n print()\n print(context.snippet2)\n\nOutput:\n\n.. code:: text\n\n String attributes are <strong>escaped</strong>\n Markup attributes are <strong>unchanged</strong>\n Property values are <strong>escaped</strong>\n Markup property values are <strong>unchanged</strong>\n Constructor kwargs are <strong>escaped</strong>\n Markup constructor kwargs are <strong>unchanged</strong>\n Render kwargs are <strong>escaped</strong>\n Markup render kwargs are <strong>unchanged</strong>\n What is <a.b[name].c[1].d>? It's <span style=\"color:green\">wow</span>!\n What is <a.b[name].c[1].e>? It's <span style=\"color:green\">wow</span> and it is escaped!\n And what is foo.bar? <span style=\"color:red\">foo</span> is missing from the rendering context!\n Wait, what is bar[foo][1]? <span style=\"color:red\">bar</span> is missing from the rendering context!\n\n Values are escaped only once:\n String attributes are <strong>escaped</strong>\n Markup attributes are <strong>unchanged</strong>\n Property values are <strong>escaped</strong>\n Markup property values are <strong>unchanged</strong>\n Constructor kwargs are <strong>escaped</strong>\n Markup constructor kwargs are <strong>unchanged</strong>\n Render kwargs are <strong>escaped</strong>\n Markup render kwargs are <strong>unchanged</strong>\n What is <a.b[name].c[1].d>? It's <span style=\"color:green\">wow</span>!\n What is <a.b[name].c[1].e>? It's <span style=\"color:green\">wow</span> and it is escaped!\n And what is foo.bar? <span style=\"color:red\">foo</span> is missing from the rendering context!\n Wait, what is bar[foo][1]? <span style=\"color:red\">bar</span> is missing from the rendering context!\n Otherwise we'd get lots of & instead of bare &\n\nPlain text example:\n\n.. code:: python\n\n from clabate import Template\n import time\n from types import SimpleNamespace\n\n class ZoneFileBoilerplate(Template):\n\n zone_config = '''\n $TTL 3600\n @ IN SOA (\n {primary_ns.hostname}.{idna_domain}. ; MNAME\n {rname} ; RNAME\n {timestamp} ; SERIAL\n 3600 ; REFRESH\n 60 ; RETRY\n 1W ; EXPIRY\n 60 ; MINIMUM Negative Cache TTL\n )\n {nameservers}\n {resource_records}\n '''\n\n @property\n def timestamp(self, context):\n return int(time.time())\n\n @property\n def nameservers(self, context):\n ns_template = self.dedent('''\n @ IN NS {ns.hostname}.{idna_domain}.\n {ns.hostname} IN A {ns.ipv4_addr}\n ''')\n result = []\n for ns in [self.primary_ns, self.secondary_ns]:\n result.append(self.render_str(context, ns_template, ns=ns))\n return ''.join(result)\n\n resource_records = '''\n @ IN A {main_server_ipv4}\n * IN A {main_server_ipv4}\n '''\n\n class DeclassedZone(ZoneFileBoilerplate):\n\n primary_ns = SimpleNamespace(hostname='ns1', ipv4_addr='1.2.3.4')\n secondary_ns = SimpleNamespace(hostname='ns2', ipv4_addr='5.6.7.8')\n\n\n my_zone = DeclassedZone(idna_domain='declassed.art', rname='axy.{idna_domain}.')\n context = my_zone.render(main_server_ipv4='9.10.11.12')\n print(context.zone_config)\n\nOutput:\n\n.. code::\n\n $TTL 3600\n @ IN SOA (\n ns1.declassed.art. ; MNAME\n axy.declassed.art. ; RNAME\n 1656230266 ; SERIAL\n 3600 ; REFRESH\n 60 ; RETRY\n 1W ; EXPIRY\n 60 ; MINIMUM Negative Cache TTL\n )\n @ IN NS ns1.declassed.art.\n ns1 IN A 1.2.3.4\n @ IN NS ns2.declassed.art.\n ns2 IN A 5.6.7.8\n @ IN A 9.10.11.12\n * IN A 9.10.11.12\n\nThere are more examples in `clabate/examples <https://declassed.art/repository/clabate/file/tip/clabate/examples>`_:\n\n* `Basic examples <https://declassed.art/repository/clabate/file/tip/clabate/examples/basic_examples.py>`_\n* `Markup examples <https://declassed.art/repository/clabate/file/tip/clabate/examples/markup_examples.py>`_\n* `Invoke <https://declassed.art/repository/clabate/file/tip/clabate/examples/invoke_examples.py>`_\n* `Pygments <https://declassed.art/repository/clabate/file/tip/clabate/examples/pygments_examples.py>`_\n* `Comments <https://declassed.art/repository/clabate/file/tip/clabate/examples/comments_examples.py>`_\n* `Sequences <https://declassed.art/repository/clabate/file/tip/clabate/examples/sequence_examples.py>`_\n* `File inclusion <https://declassed.art/repository/clabate/file/tip/clabate/examples/file_inclusion_examples.py>`_\n\nSee also: https://declassed.art/en/blog/2022/06/29/clabate-class-based-templates\n\n\n",
"bugtrack_url": null,
"license": "",
"summary": "Minimalistic class-based templates",
"version": "0.5.0",
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"md5": "b3ea3ce20bec951228210d05665aa9f2",
"sha256": "382a49a993e788990ef778166e18af34db5be99b5f8dc4ab9bc55643d861d197"
},
"downloads": -1,
"filename": "clabate-0.5.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "b3ea3ce20bec951228210d05665aa9f2",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.6",
"size": 31046,
"upload_time": "2022-12-09T10:54:50",
"upload_time_iso_8601": "2022-12-09T10:54:50.632697Z",
"url": "https://files.pythonhosted.org/packages/02/ab/cf1f65916998e8ab13dbb7ab6b657a1ac00171c0479e421a7835c7e09153/clabate-0.5.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"md5": "88e0603075a8fefe64c1ec9de754f44a",
"sha256": "feb7b1cc67f9c05c5a3061955adf5779e34339311f8e35bfca231d15512f06bf"
},
"downloads": -1,
"filename": "clabate-0.5.0.tar.gz",
"has_sig": false,
"md5_digest": "88e0603075a8fefe64c1ec9de754f44a",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.6",
"size": 27772,
"upload_time": "2022-12-09T10:54:52",
"upload_time_iso_8601": "2022-12-09T10:54:52.645787Z",
"url": "https://files.pythonhosted.org/packages/f1/bf/05ee914917b9c6b5e27d15c0505305ab5879cfc12d7a1523a2e5d2ffcd6a/clabate-0.5.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2022-12-09 10:54:52",
"github": false,
"gitlab": false,
"bitbucket": false,
"lcname": "clabate"
}