{"id":24081519,"url":"https://github.com/hrabal/tempy","last_synced_at":"2025-04-05T20:04:16.600Z","repository":{"id":21880367,"uuid":"92857015","full_name":"Hrabal/TemPy","owner":"Hrabal","description":"Python Object Oriented Html Templating System","archived":false,"fork":false,"pushed_at":"2022-07-18T15:42:44.000Z","size":15121,"stargazers_count":143,"open_issues_count":0,"forks_count":65,"subscribers_count":9,"default_branch":"master","last_synced_at":"2025-03-29T19:01:42.756Z","etag":null,"topics":["dom","html","manipulation-api","oop","oot","python","python3","template-engine","templating","tempy","tree-structure"],"latest_commit_sha":null,"homepage":"https://hrabal.github.io/TemPy/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Hrabal.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-05-30T17:08:40.000Z","updated_at":"2025-02-09T18:44:05.000Z","dependencies_parsed_at":"2022-08-07T10:01:17.281Z","dependency_job_id":null,"html_url":"https://github.com/Hrabal/TemPy","commit_stats":null,"previous_names":[],"tags_count":32,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hrabal%2FTemPy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hrabal%2FTemPy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hrabal%2FTemPy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hrabal%2FTemPy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Hrabal","download_url":"https://codeload.github.com/Hrabal/TemPy/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247393566,"owners_count":20931812,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dom","html","manipulation-api","oop","oot","python","python3","template-engine","templating","tempy","tree-structure"],"created_at":"2025-01-09T23:17:04.533Z","updated_at":"2025-04-05T20:04:16.580Z","avatar_url":"https://github.com/Hrabal.png","language":"Python","readme":"# Disclamer\n\nSince server-side rendering is getting old by the minute, I guess TemPy can be useful in a bunch of fast and simple cases, but this is not how people should generate HTML. Therefore this project is no longer mantained and there is no intention to evolve further.\n\n\nThis was a nicely ended excercise, I wasn't expecting that much contrib\u0026use. I'm non longer accepting pr's, if someone is interested on this repo maintenance PM me.\n\n\n# TemPy\n![TemPy Logo](tempy.png)\n\n[![Release](https://img.shields.io/github/release/Hrabal/TemPy.svg)](https://img.shields.io/github/release/Hrabal/TemPy.svg)\n[![PyPI version](https://badge.fury.io/py/tem-py.svg)](https://badge.fury.io/py/tem-py) [![PyPI version](https://img.shields.io/badge/python-%3E%3D%203.3-blue.svg)](https://pypi.org/project/tem-py/) [![Build Status](https://travis-ci.org/Hrabal/TemPy.svg?branch=master)](https://travis-ci.org/Hrabal/TemPy) [![Coverage Status](https://coveralls.io/repos/github/Hrabal/TemPy/badge.svg?branch=master)](https://coveralls.io/github/Hrabal/TemPy?branch=master) [![codebeat badge](https://codebeat.co/badges/d8820f91-3b3e-422b-b5cf-062cb7c8d805)](https://codebeat.co/projects/github-com-hrabal-tempy-master) [![Code Climate](https://codeclimate.com/github/Hrabal/TemPy.png)](https://codeclimate.com/github/Hrabal/TemPy) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/b35654867b91423e98511db6c22b6317)](https://www.codacy.com/app/federicocerchiari/TemPy?utm_source=github.com\u0026amp;utm_medium=referral\u0026amp;utm_content=Hrabal/TemPy\u0026amp;utm_campaign=Badge_Grade) [![GitHub license](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://raw.githubusercontent.com/Hrabal/TemPy/master/LICENSE) [![Say Thanks!](https://img.shields.io/badge/Say%20Thanks!-%F0%9F%A6%89-1EAEDB.svg)](https://saythanks.io/to/Hrabal) [![Downloads](http://pepy.tech/badge/tem-py)](http://pepy.tech/count/tem-py)\n\n### Fast Object-Oriented HTML templating With Python!\n\n### What?\nBuild HTML without writing a single tag.\nTemPy dynamically generates HTML and accesses it in a pure Python, or jQuery fashion. Navigating the DOM, and manipulating tags is also possible in a Python and/or jQuery-similar syntax.\n\n### Why?\nHTML is like SQL. We all use it, we all know how it works, and we all recognize its importance. Our biggest dream is to never write a single line of it again. For SQL we have ORMs, but we are not there yet for HTML.\nTemplating systems are awesome (Python syntax in HTML code), but they are not awesome enough because you still have to write HTML. Thus the idea of TemPy emerged!\n\n### Weeeeeeee!\nNo parsing and a simple structure makes TemPy incredibly fast. TemPy simply adds HTML tags around your data, and the actual HTML string exists only at render time.\n\n### Read the full documentation here: [https://hrabal.github.io/TemPy/](https://hrabal.github.io/TemPy/)\n*****\n\n### Build, manipulate, and navigate HTML documents, with no HTML involved.\n\nOverview:\n\n* [Building the DOM, basic usage](#basic-usage)\n* [Building separate blocks, nesting and re-usability](#building-blocks)\n* [OOT - Object-Oriented Templating](#oot---object-oriented-templating)\n* [TemPy REPR, set and forget](#tempy-reprs)\n* [DOM elements API, change your page dynamically](#elements-api)\n* [DOM navigation, it's a tree!](#dom-navigation)\n* [Tag attributes, it's a tag!](#tag-attributes)\n\n# Usage\n### Installation\nTemPy is available on PyPi: `pip3 install tem-py`.\n\nOr clone/download this repository, and run `python3 setup.py install`\n\n### Building the DOM\n\n#### Basic Usage\n\nTemPy offers clean syntax for building pages in pure python:\n```python\nfrom tempy.tags import Html, Head, Body, Meta, Link, Div, P, A\nmy_text_list = ['This is Foo.', 'This is Bar.', 'Have you met my friend Baz?']\nanother_list = ['Lorem ipsum ', 'dolor sit amet, ', 'consectetur adipiscing elit']\n\n# make tags instantiating TemPy objects\npage = Html()(  # add tags inside the one you created calling the parent\n    Head()(  # add multiple tags in one call\n        Meta(charset='utf-8'),  # add tag attributes using kwargs in tag initialization\n        Link(href=\"my.css\", typ=\"text/css\", rel=\"stylesheet\")\n    ),\n    body=Body()(  # give them a name so you can navigate the DOM with those names\n        Div(klass='linkBox')(\n            A(href='www.foo.com')\n        ),\n        (P()(text) for text in my_text_list),  # tag insertion accepts generators\n        another_list  # add text from a list, str.join is used in rendering\n    )\n)\n\n# add tags and content later\npage[1][0](A(href='www.bar.com'))  # calling the tag\npage[1][0].append(A(href='www.baz.com'))  # using the API\nlink = A().append_to(page.body[0]) # access the body as if it's a page attribute\npage.body(testDiv=Div()) # WARNING! Correct ordering with named Tag insertion is ensured with Python \u003e= 3.5 (because kwargs are ordered)\nlink.attr(href='www.python.org')('This is a link to Python.') # Add attributes and content to already placed tags\n\npage.render()\n\u003e\u003e\u003e \u003chtml\u003e\n\u003e\u003e\u003e     \u003chead\u003e\n\u003e\u003e\u003e         \u003cmeta charset=\"utf-8\"/\u003e\n\u003e\u003e\u003e         \u003clink href=\"my.css\" type=\"text/css\" rel=\"stylesheet\"/\u003e\n\u003e\u003e\u003e     \u003c/head\u003e\n\u003e\u003e\u003e     \u003cbody\u003e\n\u003e\u003e\u003e         \u003cdiv class=\"linkBox\"\u003e\n\u003e\u003e\u003e             \u003ca href=\"www.foo.com\"\u003ewww.foo.com\u003c/a\u003e\n\u003e\u003e\u003e             \u003ca href=\"www.bar.com\"\u003ewww.bar.com\u003c/a\u003e\n\u003e\u003e\u003e             \u003ca href=\"www.baz.com\"\u003ewww.baz.com\u003c/a\u003e\n\u003e\u003e\u003e             \u003ca href=\"www.python.org\"\u003eThis is a link to Python.\u003c/a\u003e\n\u003e\u003e\u003e         \u003c/div\u003e\n\u003e\u003e\u003e         \u003cp\u003eThis is Foo.\u003c/p\u003e\n\u003e\u003e\u003e         \u003cp\u003eThis is Bar.\u003c/p\u003e\n\u003e\u003e\u003e         \u003cp\u003eHave you met my friend Baz?\u003c/p\u003e\n\u003e\u003e\u003e         Lorem ipsum dolor sit amet, consectetur adipiscing elit\n\u003e\u003e\u003e         \u003cdiv\u003e\u003c/div\u003e\n\u003e\u003e\u003e     \u003c/body\u003e\n\u003e\u003e\u003e \u003c/html\u003e\n```\n\n#### Building Blocks\nYou can also create blocks, and put them together using the manipulation API. Each TemPy object can be used later inside another TemPy object:\n```python\n# --- file: base_elements.py\nfrom somewhere import links, foot_imgs\n# define some common blocks\nheader = Div(klass='header')(title=Div()('My website'), logo=Img(src='img.png'))\nmenu = Div(klass='menu')(Li()(A(href=link)) for link in links)\nfooter = Div(klass='coolFooterClass')(Img(src=img) for img in foot_imgs)\n```\n```python\n# --- file: pages.py\nfrom base_elements import header, menu, footer\n\n# import the common blocks and use them inside your page\nhome_page = Html()(Head(), body=Body()(header, menu, content='Hello world.', footer=footer))\ncontent_page = Html()(Head(), body=Body()(header, menu, container=Div(klass='container'), footer=footer))\n```\n```python\n# --- file: my_controller.py\nfrom tempy.tags import Div\nfrom pages import home_page, content_page\n\n@controller_framework_decorator\ndef my_home_controller(url='/'):\n    return home_page.render()\n\n@controller_framework_decorator\ndef my_content_controller(url='/content'):\n    content = Div()('This is my content!')\n    return content_page.body.container.append(content).render()\n```\n\n#### OOT - Object-Oriented Templating\nTemPy is designed to provide Object-Oriented Templating. You can subclass TemPy classes, and add custom HTML tree structures to use as blocks.\n\n```python\nfrom tempy.widgets import TempyPage\n\nclass BasePage(TempyPage):\n    def js(self):\n        return [\n            Script(src=\"//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js\"),\n        ]\n\n    def css(self):\n        return [\n            Link(href=url_for('static', filename='style.css'),\n                 rel=\"stylesheet\",\n                 typ=\"text/css\"),\n            Link(href='https://fonts.googleapis.com/css?family=Quicksand:300',\n                 rel=\"stylesheet\"),\n            Link(href=url_for('static',\n                              filename='/resources/font-awesome-4.7.0/css/font-awesome.min.css'),\n                 rel=\"stylesheet\"),\n        ]\n\n    # Define the init method as a constructor of your block structure\n    def init(self):\n        self.head(self.css(), self.js())\n        self.body(\n            container=Div(id='container')(\n                title=Div(id='title')(\n                    Div(id='page_title')(A(href='/')('MySite')),\n                    menu=self.make_menu('MAIN')\n                ),\n                content=Div(id='content')(Hr())\n            )\n        )\n\n    # Your subclass can have its own methods like any other class\n    def make_menu(self, typ):\n        return Div(klass='menu')(\n                            Nav()(\n                                Ul()(\n                                    Li()(\n                                        A(href=item[1])(item[0]))\n                                    for item in self.get_menu(typ)\n                                )\n                            ),\n                        )\n\n    def get_menu(self, typ):\n        return [(mi.name, mi.link)\n                for mi in Menu.query.filter_by(active=True, menu=typ\n                                               ).order_by(Menu.order).all()]\n\n```\n\n...you can then sublass your custom TemPy object to add a specific behavior:\n```python\nclass HomePage(BasePage):\n\n    def init(self):\n        self.body.container.content(\n            Div()(\n                Br(),\n                'This is my home page content', Br(),\n                H3()('Hame page important content'),\n                'Look, I\\'m a string!', Br(),\n                H3()('H3 is big, really big'),\n                H1()('Today\\'s content:'),\n                self.get_dynamic_content()\n            )\n        )\n\n    def get_dynamic_content(self):\n        # Here using SQLAlchemy:\n        current_content = Content.query.outerjoin(Content.comments).order_by(Content.date.desc(), Content.id.desc()).limit(1).first()\n        if not current_content:\n            return 'No content today!'\n        return Div()(Span()(current_content.title),\n                     Span()(current_content.text)),\n                     Div()(comment for comment in current_content.comments))\n```\n\nTemPy executes each base class `init` method in reverse MRO, so your subclass can access all the elements defined in its parent classes.\n\n#### TemPy Reprs\n\nAnother way to use TemPy is to define a nested `TempyREPR` class inside your classes:\n```python\nclass MyClass:\n    def __init__(self):\n        self.foo = 'foo'\n        self.bar = 'bar'\n\n    class HtmlREPR(TempyREPR):\n        def repr(self):\n            self(\n                Div()(self.foo),\n                Div()(self.bar)\n            )\n```\nYou can think the `TempyREPR` as a `__repr__` equivalent, so when an instance is placed inside a TemPy tree, the `TempyREPR` subclass is used to render the instance.\n\nYou can define several `TempyREPR` nested classes, and when dealing with a non-TemPy object, TemPy will search for a `TempyREPR` subclass following this priority:\n* a `TempyREPR` subclass with the same name of its TemPy container.\n* a `TempyREPR` subclass with the same name of its TemPy container's root.\n* a `TempyREPR` subclass named `HtmlREPR`.\n* the first `TempyREPR` found.\n* if none of the previous ones are found, the object will be rendered calling its `__str__` method.\n\nYou can use this order to set different renderings for different situations/pages:\n\n```python\nclass MyClass:\n    def __init__(self):\n        self.foo = 'foo'\n        self.bar = 'bar'\n        self.link = 'www.foobar.com'\n\n    # If an instance on MyClass is found inside a div\n    class Div(TempyREPR):\n        def repr(self):\n            self(\n                Div()(self.foo),\n                Div()(self.bar)\n            )\n\n    # If an instance on MyClass is found inside a link\n    class A(TempyREPR):\n        def repr(self):\n            self.parent.attrs['href'] = self.link\n            self('Link to ', self.bar)\n\n    # If an instance on MyClass is found inside a table cell\n    class Td(TempyREPR):\n        def repr(self):\n            self(self.bar.upper())\n\n    # If an instance on MyClass is found when rendering the a TempyPage called 'HomePage'\n    class HomePage(TempyREPR):\n        def repr(self):  # note: here self is the object's parent, not the root\n            self('Hello World, this is bar: ', self.bar)\n```\n\n### Elements API\nCreate DOM elements by instantiating tags:\n```python\npage = Html()\n\u003e\u003e\u003e \u003chtml\u003e\u003c/html\u003e\n```\n\nAdd elements or content by calling them like a function...\n```python\npage(Head())\n\u003e\u003e\u003e \u003chtml\u003e\u003chead\u003e\u003c/head\u003e\u003c/html\u003e\n```\n...or use one of the jQuery-like APIs:\n```python\nbody = Body()\npage.append(body)\n\u003e\u003e\u003e \u003chtml\u003e\u003chead\u003e\u003c/head\u003e\u003cbody\u003e\u003c/body\u003e\u003c/html\u003e\n\ndiv = Div().append_to(body)\n\u003e\u003e\u003e \u003chtml\u003e\u003chead\u003e\u003c/head\u003e\u003cbody\u003e\u003cdiv\u003e\u003c/div\u003e\u003c/body\u003e\u003c/html\u003e\ndiv.append('This is some content', Br(), 'And some Other')\n\u003e\u003e\u003e \u003chtml\u003e\u003chead\u003e\u003c/head\u003e\u003cbody\u003e\u003cdiv\u003eThis is some content\u003cbr\u003eAnd some Other\u003c/div\u003e\u003c/body\u003e\u003c/html\u003e\n```\n...same for removing:\n```python\nhead.remove()\n\u003e\u003e\u003e \u003chtml\u003e\u003cbody\u003e\u003cdiv\u003e\u003c/div\u003e\u003c/body\u003e\u003c/html\u003e\nbody.empty()\n\u003e\u003e\u003e \u003chtml\u003e\u003cbody\u003e\u003c/body\u003e\u003c/html\u003e\npage.pop()\n\u003e\u003e\u003e \u003chtml\u003e\u003c/html\u003e\n```\n\nSeveral APIs are provided to modify your existing DOM elements:\n```python\ndiv1 = Div()\ndiv2 = Div()\ndiv1.after(div2)\ndiv1.before(div2)\ndiv1.prepend(div2)\ndiv1.prepend_to(div2)\ndiv1.append(div2)\ndiv1.append_to(div2)\ndiv1.wrap(div2)\ndiv1.wrap_inner(div2)\ndiv1.replace_with(div2)\ndiv1.remove(div2)\ndiv1.move_childs(div2)\ndiv1.move(div2)\ndiv1.pop(div2)\ndiv1.empty(div2)\ndiv1.children(div2)\ndiv1.contents(div2)\ndiv1.first(div2)\ndiv1.last(div2)\ndiv1.next(div2)\ndiv1.next_all(div2)\ndiv1.prev(div2)\ndiv1.prev_all(div2)\ndiv1.siblings(div2)\ndiv1.slice(div2)\n```\n\n## Tag Attributes\nAdd attributes to every element at definition time or later:\n```python\ndiv = Div(id='my_html_id', klass='someHtmlClass') # 'klass' because 'class' is a Python's buildin keyword\n\u003e\u003e\u003e \u003cdiv id=\"my_dom_id\" class=\"someHtmlClass\"\u003e\u003c/div\u003e\n\na = A(klass='someHtmlClass')('text of this link')\na.attr(id='another_dom_id')\na.attr({'href': 'www.thisisalink.com'})\n\u003e\u003e\u003e \u003ca id=\"another_dom_id\" class=\"someHtmlClass\" href=\"www.thisisalink.com\"\u003etext of this link\u003c/a\u003e\n```\n\nStyles are editable in the jQuery fashion:\n```python\ndiv2.css(width='100px', float='left')\ndiv2.css({'height': '100em'})\ndiv2.css({'background-color': 'blue'})\n\u003e\u003e\u003e \u003cdiv id=\"another_dom_id\" class=\"someHtmlClass comeOtherClass\" style=\"width: 100px; float: left; height: 100em; background-color: blue\"\u003e\u003c/div\u003e\n```\n\n### DOM Navigation\n\nAll of the TemPy tag contents are iterable and accessible which is similar to a\nPython list. For example:\n```python\ndivs = [Div(id=div, klass='inner') for div in range(10)]\nps = (P() for _ in range(10))\ncontainer_div = Div()(divs)\n\nfor i, div in enumerate(container_div):\n    div.attr(id='divId'+str(i))\ncontainer_div[0].append(ps)\ncontainer_div[0][4].attr(id='pId')\n\u003e\u003e\u003e \u003cdiv\u003e\n\u003e\u003e\u003e     \u003cdiv id=\"divId0\"\u003e\n\u003e\u003e\u003e         \u003cp\u003e\u003c/p\u003e\n\u003e\u003e\u003e         \u003cp\u003e\u003c/p\u003e\n\u003e\u003e\u003e         \u003cp\u003e\u003c/p\u003e\n\u003e\u003e\u003e         \u003cp\u003e\u003c/p\u003e\n\u003e\u003e\u003e         \u003cp id=\"pId\"\u003e\u003c/p\u003e\n\u003e\u003e\u003e         \u003cp\u003e\u003c/p\u003e\n\u003e\u003e\u003e         \u003cp\u003e\u003c/p\u003e\n\u003e\u003e\u003e         \u003cp\u003e\u003c/p\u003e\n\u003e\u003e\u003e         \u003cp\u003e\u003c/p\u003e\n\u003e\u003e\u003e         \u003cp\u003e\u003c/p\u003e\n\u003e\u003e\u003e     \u003c/div\u003e\n\u003e\u003e\u003e     \u003cdiv id=\"divId1\"\u003e\u003c/div\u003e\n\u003e\u003e\u003e     \u003cdiv id=\"divId2\"\u003e\u003c/div\u003e\n\u003e\u003e\u003e     \u003cdiv id=\"divId3\"\u003e\u003c/div\u003e\n\u003e\u003e\u003e     \u003cdiv id=\"divId4\"\u003e\u003c/div\u003e\n\u003e\u003e\u003e     \u003cdiv id=\"divId5\"\u003e\u003c/div\u003e\n\u003e\u003e\u003e     \u003cdiv id=\"divId6\"\u003e\u003c/div\u003e\n\u003e\u003e\u003e     \u003cdiv id=\"divId7\"\u003e\u003c/div\u003e\n\u003e\u003e\u003e     \u003cdiv id=\"divId8\"\u003e\u003c/div\u003e\n\u003e\u003e\u003e     \u003cdiv id=\"divId9\"\u003e\u003c/div\u003e\n\u003e\u003e\u003e \u003c/div\u003e\n```\n\n...or access elements inside a container as if they were attributes:\n```python\ncontainer_div = Div()\ncontainer_div(content_div=Div())\n\ncontainer_div.content_div('Some content')\n\u003e\u003e\u003e \u003cdiv\u003e\u003cdiv\u003eSome content\u003c/div\u003e\u003c/div\u003e\n```\n\n\n...or if you feel jQuery-ish you can use:\n```python\ncontainer_div.children()\ncontainer_div.first()\ncontainer_div.last()\ncontainer_div.next()\ncontainer_div.prev()\ncontainer_div.prev_all()\ncontainer_div.parent()\ncontainer_div.slice()\n```\n\n## Credits: made and maintained by Federico Cerchiari / Hrabal\n### Contribute.\nAll contributions are welcome. Please refer to the [contributing page](CONTRIBUTING.md).\n\n## Python versions compatibility\nPython \u003e= 3.3 needed, ask [Travis](https://travis-ci.org/Hrabal/TemPy) [![Build Status](https://travis-ci.org/Hrabal/TemPy.svg?branch=master)](https://travis-ci.org/Hrabal/TemPy)\n\n### Apache 2.0 license, see LICENSE for details.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhrabal%2Ftempy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhrabal%2Ftempy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhrabal%2Ftempy/lists"}