clabate


Nameclabate JSON
Version 0.5.0 PyPI version JSON
download
home_pagehttps://declassed.art/repository/clabate
SummaryMinimalistic class-based templates
upload_time2022-12-09 10:54:52
maintainer
docs_urlNone
authorAXY
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>
            &lt;&lt;&lt;Hello, world!&gt;&gt;&gt;
        </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 &amp; 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 &lt;strong&gt;escaped&lt;/strong&gt;
    Markup attributes are <strong>unchanged</strong>
    Property values are &lt;strong&gt;escaped&lt;/strong&gt;
    Markup property values are <strong>unchanged</strong>
    Constructor kwargs are &lt;strong&gt;escaped&lt;/strong&gt;
    Markup constructor kwargs are <strong>unchanged</strong>
    Render kwargs are &lt;strong&gt;escaped&lt;/strong&gt;
    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 &lt;span style="color:green"&gt;wow&lt;/span&gt; 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 &lt;strong&gt;escaped&lt;/strong&gt;
    Markup attributes are <strong>unchanged</strong>
    Property values are &lt;strong&gt;escaped&lt;/strong&gt;
    Markup property values are <strong>unchanged</strong>
    Constructor kwargs are &lt;strong&gt;escaped&lt;/strong&gt;
    Markup constructor kwargs are <strong>unchanged</strong>
    Render kwargs are &lt;strong&gt;escaped&lt;/strong&gt;
    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 &lt;span style="color:green"&gt;wow&lt;/span&gt; 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 &amp; 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            &lt;&lt;&lt;Hello, world!&gt;&gt;&gt;\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 &amp; 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 &lt;strong&gt;escaped&lt;/strong&gt;\n    Markup attributes are <strong>unchanged</strong>\n    Property values are &lt;strong&gt;escaped&lt;/strong&gt;\n    Markup property values are <strong>unchanged</strong>\n    Constructor kwargs are &lt;strong&gt;escaped&lt;/strong&gt;\n    Markup constructor kwargs are <strong>unchanged</strong>\n    Render kwargs are &lt;strong&gt;escaped&lt;/strong&gt;\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 &lt;span style=\"color:green\"&gt;wow&lt;/span&gt; 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 &lt;strong&gt;escaped&lt;/strong&gt;\n    Markup attributes are <strong>unchanged</strong>\n    Property values are &lt;strong&gt;escaped&lt;/strong&gt;\n    Markup property values are <strong>unchanged</strong>\n    Constructor kwargs are &lt;strong&gt;escaped&lt;/strong&gt;\n    Markup constructor kwargs are <strong>unchanged</strong>\n    Render kwargs are &lt;strong&gt;escaped&lt;/strong&gt;\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 &lt;span style=\"color:green\"&gt;wow&lt;/span&gt; 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 &amp; 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"
}
        
AXY
Elapsed time: 0.01812s