django-unicorn-playground


Namedjango-unicorn-playground JSON
Version 0.1.1 PyPI version JSON
download
home_pagehttps://github.com/adamghill/django-unicorn-playground/
SummaryPrototype and debug `Unicorn` components without creating a complete Django application.
upload_time2024-07-27 00:39:02
maintainerNone
docs_urlNone
authoradamghill
requires_python<4.0,>=3.10
licenseMIT
keywords django python css html
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # django-unicorn-playground 🦄🛝

The `Unicorn Playground` provides a way to prototype and debug [`Unicorn`](https://www.django-unicorn.com) components without creating a complete Django application. It can either be run as a standalone script or by installing the library.

## Standalone script

The benefit of the standalone script is that [inline script metadata](https://packaging.python.org/en/latest/specifications/inline-script-metadata/) provides the information for `pipx` to create a virtual environment and install any dependencies automatically.

### Create an example component

1. Install [`pipx`](https://pipx.pypa.io/latest/installation/)
2. Create a new file called `counter.py` with the following code

```python
# /// script
# requires-python = ">=3.10"
# dependencies = [
#   "django-unicorn-playground"
# ]
# ///

from django_unicorn.components import UnicornView

class CounterView(UnicornView):
    template_html = """<div>
    <div>
        Count: {{ count }}
    </div>

    <button unicorn:click="increment">+</button>
    <button unicorn:click="decrement">-</button>
</div>
"""

    count: int

    def increment(self):
        count += 1
    
    def decrement(self):
        count -= 1

if __name__ == "__main__":
    from django_unicorn_playground import UnicornPlayground

    UnicornPlayground(__file__).runserver()
```

3. `pipx run counter.py`
4. Go to https://localhost:8000

## Library CLI

The `django-unicorn-playground` library can also be installed to provide a command-line interface to try a component. This approach uses basically the same code, but doesn't require the script inline metadata or the call to `runserver` at the end -- the CLI takes care of running the development server directly. Those portions can be included, though, and will not prevent the CLI from working as expected.

### Create an example component

1. `pipx install django-unicorn-playground`
2. Create `counter.py` with the following code:

```python
from django_unicorn.components import UnicornView

class CounterView(UnicornView):
    template_html = """<div>
    <div>
        Count: {{ count }}
    </div>

    <button unicorn:click="increment">+</button>
    <button unicorn:click="decrement">-</button>
</div>
"""

    count: int

    def increment(self):
        count += 1
    
    def decrement(self):
        count -= 1
```

3. `unicorn counter.py`
4. Go to https://localhost:8000

### Command-line options

#### port

Port for the developer webserver. Defaults to 8000. Required to be an integer.

#### template_dir

Directory for templates. Required to be a string path.

#### version

Shows the current version.

#### help

Show the available CLI options.

## Example components

There are a few example components in the `examples` directory.

They can be run with something like `pipx run --no-cache examples/counter.py`.

## Template HTML

The component's HTML can be initialized in a few ways.

### UnicornView.template_html attribute

The HTML can be set with a class-level `template_html` field.

```python
from django_unicorn.components import UnicornView

class TestView(UnicornView):
    template_html = """<div>
    <div>
        Count: {{ count }}
    </div>

    <button unicorn:click="increment">+</button>
    <button unicorn:click="decrement">-</button>
</div>
"""

    ...
```

### UnicornView.template_html method

The HTML can be returned from a `template_html` instance method.

```python
from django_unicorn.components import UnicornView

class TestView(UnicornView):
    def template_html(self): 
        return """<div>
    <div>
        Count: {{ count }}
    </div>

    <button unicorn:click="increment">+</button>
    <button unicorn:click="decrement">-</button>
</div>
"""

    ...
```

### HTML file

Similar to a typical `django-unicorn` setup, the component HTML can be a separate template file. This is the fallback and will only be searched for if the `template_view` field or method is not defined on the component.

1. `cd` to the same directory as the component Python file you created
2. `mkdir -p templates/unicorn`
3. `touch {COMPONENT-NAME}.html`, e.g. for a component Python named `counter.py` create `counter.html`
4. Add the component HTML to the newly created file

### Template directory

By default `django-unicorn-playground` will look in a `templates/unicorn` folder for templates. The template location can be changed by passing in `template_dir` into the `UnicornPlayground()` constructor or adding a `--template_dir` argument to the CLI.

### index.html

The root URL dynamically creates `index.html` which creates HTML for the component. It looks something like the following.

```html
{% extends "base.html" %}
{% block content %}
{% unicorn 'COMPONENT-NAME' %}
{% endblock content %}
```

It can be overridden by creating a custom `index.html` in the template directory.

### base.html

By default, `index.html` extends `base.html`. It can be overridden by creating a custom `base.html` in the template directory.

## Using a Python HTML builder

Any Python library that generates normal HTML strings works great with `django-unicorn-playground`.

### [haitch](https://pypi.org/project/haitch/)

 ```python
 from django_unicorn.components import UnicornView
 import haitch

 class TestComponent(UnicornView)
    def template_html(self):
        return haitch.div()(
            haitch.button("Increment +", **{"unicorn:click": "increment"}),
            haitch.button("Decrement -", **{"unicorn:click": "decrement"}),
            haitch.div("{{ count }}"),
        )
    
    ...
 ```

 ### [htpy](https://pypi.org/project/htpy/)

 ```python
 from django_unicorn.components import UnicornView
 import htpy

 class TestComponent(UnicornView)
    def template_html(self):
        return htpy.div()[
            htpy.button({"unicorn:click": "increment"})["Increment +"],
            htpy.button({"unicorn:click": "decrement"})["Decrement -"],
            htpy.div()["{{ count }}"],
        ]
    
    ...
 ```

 ### [dominate](https://pypi.org/project/dominate/)

 ```python
 from django_unicorn.components import UnicornView
 from dominate import tags as dom

 class TestComponent(UnicornView)
    def template_html(self):
        return dom.div(
            dom.button("Increment +", **unicorn.click(self.increment)),
            dom.button("Decrement -", **unicorn.click(self.decrement)),
            dom.div("{{ count }}"),
        )
    
    ...
 ```

## Unicorn HTML helpers

When using an HTML builder, `django-unicorn-playground` provides a few helper methods which make it a little cleaner to create `Unicorn`-specific HTML.

For example, if using `haitch` instead of doing this:

 ```python
 import haitch
 from django_unicorn.components import UnicornView

 class TestComponent(UnicornView)
    def template_html(self):
        return haitch.div()(
            haitch.button("Increment +", **{"unicorn:click": "increment"}),
            haitch.button("Decrement -", **{"unicorn:click": "decrement"}),
            haitch.div("{{ count }}"),
        )
    
    ...
 ```

This is how it would work with the helper methods:

 ```python
 import haitch
 from django_unicorn.components import UnicornView
 from django_unicorn_playground.html import django, unicorn

 class TestComponent(UnicornView)
    def template_html(self):
        return haitch.div()(
            haitch.button("Increment +", **unicorn.click(self.increment)),
            haitch.button("Decrement -", **unicorn.click(self.decrement)),
            haitch.div(django.variable("count")),
        )
    
    ...
 ```

## Local development

### Standalone script

Using the inline script metadata with `pipx` seems a little quirky and I could not get editable installs working reliably. I also tried `hatch run` which had its own issues. Not sure if there are other (read: better) approaches.

As far as I can tell, the best approach is to use an absolute file path like `"django_unicorn_playground @ file:///Users/adam/Source/adamghill/django-unicorn-playground/dist/django_unicorn_playground-0.1.0-py3-none-any.whl"` (note the triple forward-slash after "file:") as a dependency, and rebuilding and re-running the script without any caching like this: `poetry build && pipx run --no-cache examples/counter.py` any time you make a code change.

Note: you will need to update the component's dependency so it points to the path and version on your machine.

There is a `just` command to make testing a standalone script _slightly_ less painful during local development.

1. [Install just](https://just.systems/man/en/chapter_4.html)
1. `just serve {COMPONENT-FILE-PATH}`, e.g. `just serve examples/counter.py`

### CLI

1. `poetry install`
1. `poetry run unicorn {COMPONENT-FILE-PATH}`, e.g. `poetry run unicorn examples/counter.py`

## Acknowledgments

- [phoenix_playground](https://github.com/phoenix-playground/phoenix_playground)

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/adamghill/django-unicorn-playground/",
    "name": "django-unicorn-playground",
    "maintainer": null,
    "docs_url": null,
    "requires_python": "<4.0,>=3.10",
    "maintainer_email": null,
    "keywords": "django, python, css, html",
    "author": "adamghill",
    "author_email": "adamghill@yahoo.com",
    "download_url": "https://files.pythonhosted.org/packages/a3/32/750bfb234e9351687ae02df56f0468f6b4f5df127e5f8aa4df89f1a3daf2/django_unicorn_playground-0.1.1.tar.gz",
    "platform": null,
    "description": "# django-unicorn-playground \ud83e\udd84\ud83d\udedd\n\nThe `Unicorn Playground` provides a way to prototype and debug [`Unicorn`](https://www.django-unicorn.com) components without creating a complete Django application. It can either be run as a standalone script or by installing the library.\n\n## Standalone script\n\nThe benefit of the standalone script is that [inline script metadata](https://packaging.python.org/en/latest/specifications/inline-script-metadata/) provides the information for `pipx` to create a virtual environment and install any dependencies automatically.\n\n### Create an example component\n\n1. Install [`pipx`](https://pipx.pypa.io/latest/installation/)\n2. Create a new file called `counter.py` with the following code\n\n```python\n# /// script\n# requires-python = \">=3.10\"\n# dependencies = [\n#   \"django-unicorn-playground\"\n# ]\n# ///\n\nfrom django_unicorn.components import UnicornView\n\nclass CounterView(UnicornView):\n    template_html = \"\"\"<div>\n    <div>\n        Count: {{ count }}\n    </div>\n\n    <button unicorn:click=\"increment\">+</button>\n    <button unicorn:click=\"decrement\">-</button>\n</div>\n\"\"\"\n\n    count: int\n\n    def increment(self):\n        count += 1\n    \n    def decrement(self):\n        count -= 1\n\nif __name__ == \"__main__\":\n    from django_unicorn_playground import UnicornPlayground\n\n    UnicornPlayground(__file__).runserver()\n```\n\n3. `pipx run counter.py`\n4. Go to https://localhost:8000\n\n## Library CLI\n\nThe `django-unicorn-playground` library can also be installed to provide a command-line interface to try a component. This approach uses basically the same code, but doesn't require the script inline metadata or the call to `runserver` at the end -- the CLI takes care of running the development server directly. Those portions can be included, though, and will not prevent the CLI from working as expected.\n\n### Create an example component\n\n1. `pipx install django-unicorn-playground`\n2. Create `counter.py` with the following code:\n\n```python\nfrom django_unicorn.components import UnicornView\n\nclass CounterView(UnicornView):\n    template_html = \"\"\"<div>\n    <div>\n        Count: {{ count }}\n    </div>\n\n    <button unicorn:click=\"increment\">+</button>\n    <button unicorn:click=\"decrement\">-</button>\n</div>\n\"\"\"\n\n    count: int\n\n    def increment(self):\n        count += 1\n    \n    def decrement(self):\n        count -= 1\n```\n\n3. `unicorn counter.py`\n4. Go to https://localhost:8000\n\n### Command-line options\n\n#### port\n\nPort for the developer webserver. Defaults to 8000. Required to be an integer.\n\n#### template_dir\n\nDirectory for templates. Required to be a string path.\n\n#### version\n\nShows the current version.\n\n#### help\n\nShow the available CLI options.\n\n## Example components\n\nThere are a few example components in the `examples` directory.\n\nThey can be run with something like `pipx run --no-cache examples/counter.py`.\n\n## Template HTML\n\nThe component's HTML can be initialized in a few ways.\n\n### UnicornView.template_html attribute\n\nThe HTML can be set with a class-level `template_html` field.\n\n```python\nfrom django_unicorn.components import UnicornView\n\nclass TestView(UnicornView):\n    template_html = \"\"\"<div>\n    <div>\n        Count: {{ count }}\n    </div>\n\n    <button unicorn:click=\"increment\">+</button>\n    <button unicorn:click=\"decrement\">-</button>\n</div>\n\"\"\"\n\n    ...\n```\n\n### UnicornView.template_html method\n\nThe HTML can be returned from a `template_html` instance method.\n\n```python\nfrom django_unicorn.components import UnicornView\n\nclass TestView(UnicornView):\n    def template_html(self): \n        return \"\"\"<div>\n    <div>\n        Count: {{ count }}\n    </div>\n\n    <button unicorn:click=\"increment\">+</button>\n    <button unicorn:click=\"decrement\">-</button>\n</div>\n\"\"\"\n\n    ...\n```\n\n### HTML file\n\nSimilar to a typical `django-unicorn` setup, the component HTML can be a separate template file. This is the fallback and will only be searched for if the `template_view` field or method is not defined on the component.\n\n1. `cd` to the same directory as the component Python file you created\n2. `mkdir -p templates/unicorn`\n3. `touch {COMPONENT-NAME}.html`, e.g. for a component Python named `counter.py` create `counter.html`\n4. Add the component HTML to the newly created file\n\n### Template directory\n\nBy default `django-unicorn-playground` will look in a `templates/unicorn` folder for templates. The template location can be changed by passing in `template_dir` into the `UnicornPlayground()` constructor or adding a `--template_dir` argument to the CLI.\n\n### index.html\n\nThe root URL dynamically creates `index.html` which creates HTML for the component. It looks something like the following.\n\n```html\n{% extends \"base.html\" %}\n{% block content %}\n{% unicorn 'COMPONENT-NAME' %}\n{% endblock content %}\n```\n\nIt can be overridden by creating a custom `index.html` in the template directory.\n\n### base.html\n\nBy default, `index.html` extends `base.html`. It can be overridden by creating a custom `base.html` in the template directory.\n\n## Using a Python HTML builder\n\nAny Python library that generates normal HTML strings works great with `django-unicorn-playground`.\n\n### [haitch](https://pypi.org/project/haitch/)\n\n ```python\n from django_unicorn.components import UnicornView\n import haitch\n\n class TestComponent(UnicornView)\n    def template_html(self):\n        return haitch.div()(\n            haitch.button(\"Increment +\", **{\"unicorn:click\": \"increment\"}),\n            haitch.button(\"Decrement -\", **{\"unicorn:click\": \"decrement\"}),\n            haitch.div(\"{{ count }}\"),\n        )\n    \n    ...\n ```\n\n ### [htpy](https://pypi.org/project/htpy/)\n\n ```python\n from django_unicorn.components import UnicornView\n import htpy\n\n class TestComponent(UnicornView)\n    def template_html(self):\n        return htpy.div()[\n            htpy.button({\"unicorn:click\": \"increment\"})[\"Increment +\"],\n            htpy.button({\"unicorn:click\": \"decrement\"})[\"Decrement -\"],\n            htpy.div()[\"{{ count }}\"],\n        ]\n    \n    ...\n ```\n\n ### [dominate](https://pypi.org/project/dominate/)\n\n ```python\n from django_unicorn.components import UnicornView\n from dominate import tags as dom\n\n class TestComponent(UnicornView)\n    def template_html(self):\n        return dom.div(\n            dom.button(\"Increment +\", **unicorn.click(self.increment)),\n            dom.button(\"Decrement -\", **unicorn.click(self.decrement)),\n            dom.div(\"{{ count }}\"),\n        )\n    \n    ...\n ```\n\n## Unicorn HTML helpers\n\nWhen using an HTML builder, `django-unicorn-playground` provides a few helper methods which make it a little cleaner to create `Unicorn`-specific HTML.\n\nFor example, if using `haitch` instead of doing this:\n\n ```python\n import haitch\n from django_unicorn.components import UnicornView\n\n class TestComponent(UnicornView)\n    def template_html(self):\n        return haitch.div()(\n            haitch.button(\"Increment +\", **{\"unicorn:click\": \"increment\"}),\n            haitch.button(\"Decrement -\", **{\"unicorn:click\": \"decrement\"}),\n            haitch.div(\"{{ count }}\"),\n        )\n    \n    ...\n ```\n\nThis is how it would work with the helper methods:\n\n ```python\n import haitch\n from django_unicorn.components import UnicornView\n from django_unicorn_playground.html import django, unicorn\n\n class TestComponent(UnicornView)\n    def template_html(self):\n        return haitch.div()(\n            haitch.button(\"Increment +\", **unicorn.click(self.increment)),\n            haitch.button(\"Decrement -\", **unicorn.click(self.decrement)),\n            haitch.div(django.variable(\"count\")),\n        )\n    \n    ...\n ```\n\n## Local development\n\n### Standalone script\n\nUsing the inline script metadata with `pipx` seems a little quirky and I could not get editable installs working reliably. I also tried `hatch run` which had its own issues. Not sure if there are other (read: better) approaches.\n\nAs far as I can tell, the best approach is to use an absolute file path like `\"django_unicorn_playground @ file:///Users/adam/Source/adamghill/django-unicorn-playground/dist/django_unicorn_playground-0.1.0-py3-none-any.whl\"` (note the triple forward-slash after \"file:\") as a dependency, and rebuilding and re-running the script without any caching like this: `poetry build && pipx run --no-cache examples/counter.py` any time you make a code change.\n\nNote: you will need to update the component's dependency so it points to the path and version on your machine.\n\nThere is a `just` command to make testing a standalone script _slightly_ less painful during local development.\n\n1. [Install just](https://just.systems/man/en/chapter_4.html)\n1. `just serve {COMPONENT-FILE-PATH}`, e.g. `just serve examples/counter.py`\n\n### CLI\n\n1. `poetry install`\n1. `poetry run unicorn {COMPONENT-FILE-PATH}`, e.g. `poetry run unicorn examples/counter.py`\n\n## Acknowledgments\n\n- [phoenix_playground](https://github.com/phoenix-playground/phoenix_playground)\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Prototype and debug `Unicorn` components without creating a complete Django application.",
    "version": "0.1.1",
    "project_urls": {
        "Documentation": "https://github.com/adamghill/django-unicorn-playground/",
        "Funding": "https://github.com/sponsors/adamghill",
        "Homepage": "https://github.com/adamghill/django-unicorn-playground/",
        "Repository": "https://github.com/adamghill/django-unicorn-playground/"
    },
    "split_keywords": [
        "django",
        " python",
        " css",
        " html"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "57ac2f81d9b728ab738a8505f3b6da54856485130248e772c27f5af1c226967f",
                "md5": "9bb03872d7cbd52799024c11cb0f43c1",
                "sha256": "fce7b4ffb72b1d732e45e60b8aa81883bbba339007a0060f72ad0f9220e77a58"
            },
            "downloads": -1,
            "filename": "django_unicorn_playground-0.1.1-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "9bb03872d7cbd52799024c11cb0f43c1",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": "<4.0,>=3.10",
            "size": 10791,
            "upload_time": "2024-07-27T00:39:00",
            "upload_time_iso_8601": "2024-07-27T00:39:00.875254Z",
            "url": "https://files.pythonhosted.org/packages/57/ac/2f81d9b728ab738a8505f3b6da54856485130248e772c27f5af1c226967f/django_unicorn_playground-0.1.1-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "a332750bfb234e9351687ae02df56f0468f6b4f5df127e5f8aa4df89f1a3daf2",
                "md5": "18afb7be8b0c6d034d35f03dbbdd371a",
                "sha256": "11d09e254c9cfb3c8490a7fc760f609b5f99d8651d04c197ed5dc6390ac45f65"
            },
            "downloads": -1,
            "filename": "django_unicorn_playground-0.1.1.tar.gz",
            "has_sig": false,
            "md5_digest": "18afb7be8b0c6d034d35f03dbbdd371a",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": "<4.0,>=3.10",
            "size": 10918,
            "upload_time": "2024-07-27T00:39:02",
            "upload_time_iso_8601": "2024-07-27T00:39:02.151355Z",
            "url": "https://files.pythonhosted.org/packages/a3/32/750bfb234e9351687ae02df56f0468f6b4f5df127e5f8aa4df89f1a3daf2/django_unicorn_playground-0.1.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-07-27 00:39:02",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "adamghill",
    "github_project": "django-unicorn-playground",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "lcname": "django-unicorn-playground"
}
        
Elapsed time: 0.30230s