hrepr


Namehrepr JSON
Version 0.6.1 PyPI version JSON
download
home_pagehttps://github.com/breuleux/hrepr
SummaryExtensible HTML representation for Python objects.
upload_time2023-12-13 18:37:41
maintainer
docs_urlNone
authorOlivier Breuleux
requires_python>=3.7,<4.0
licenseMIT
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # hrepr

`hrepr` outputs HTML/pretty representations for Python objects.

✅ Nice, colourful representations of lists, dicts, dataclasses, booleans...<br/>
✅ Ridiculously extensible and configurable<br/>
✅ Handles recursive data structures<br/>
✅ Compatible with Jupyter notebooks<br/>

<img src="https://raw.githubusercontent.com/breuleux/hrepr/master/images/hrepr1.png" width="400px"><img src="https://raw.githubusercontent.com/breuleux/hrepr/master/images/hrepr2.png" width="400px">

I suggest studying the example file to learn `hrepr`:

* `python examples/exhibit.py > exhibit.html` (and then view the HTML file)

Also see the Jupyter notebook at `examples/Basics.ipynb`, but keep in mind that GitHub can't display it properly because of the injected styles/scripts.


## Install

```python
pip install hrepr
```


## Usage

```python
from hrepr import hrepr
obj = {'potatoes': [1, 2, 3], 'bananas': {'cantaloups': 8}}

# Print the HTML representation of obj
print(hrepr(obj))

# Wrap the representation in <html><body> tags and embed the default
# css style files in a standalone page, which is saved to obj.html
hrepr.page(obj, file="obj.html")
```

In a Jupyter Notebook, you can return `hrepr(obj)` from any cell and it will show its representation for you. You can also write `display_html(hrepr(obj))`.


## Custom representations

A custom representation for an object can be defined using the following three methods (it is not necessary to define all of them, only those that are relevant to your case):

* `__hrepr__(self, H, hrepr)` returns the normal HTML representation.
    * Use `H.span["some-class"](some-content, some_attr=some_value)` to generate HTML.
    * Use `hrepr(self.x)` to generate the representation for some subfield `x`.
    * `hrepr.config` contains any keyword arguments given in the top level call to `hrepr`. For instance, if you call `hrepr(obj, blah=3)`, then `hrepr.config.blah == 3` in all calls to `__hrepr__` down the line (the default value for all keys is `None`).
* `__hrepr_short__(self, H, hrepr)` returns a *short* representation, ideally of a constant size.
    * The output of this method is used when we hit max depth, or for repeated references.
    * Only include bare minimum information. Short means short.
* `__hrepr_resources__(cls, H)` is a **classmethod** that returns resources common to all instances of the class (typically a stylesheet or a script).
    * When generating a page, the resources will go in `<head>`.
    * You can return a list of resources.

No dependency on `hrepr` is necessary.

For example:

```python
class Person:
    def __init__(self, name, age, job):
        self.name = name
        self.age = age
        self.job = job

    @classmethod
    def __hrepr_resources__(cls, H):
        # Note: you might need to add "!important" next to some rules if
        # they conflict with defaults from hrepr's own CSS.
        return H.style("""
            .person {
                background: magenta !important;
                border-color: magenta !important;
            }
            .person-short { font-weight: bold; color: green; }
        """)

    def __hrepr__(self, H, hrepr):
        # hrepr.make.instance is a helper to show a table with a header that
        # describes some kind of object
        return hrepr.make.instance(
            title=self.name,
            fields=[["age", self.age], ["job", self.job]],
            delimiter=" ↦ ",
            type="person",
        )

    def __hrepr_short__(self, H, hrepr):
        return H.span["person-short"](self.name)
```

<img src="https://raw.githubusercontent.com/breuleux/hrepr/master/images/hrepr3.png" width="600px">


## References

`hrepr` can handle circular references. Furthermore, if an object is found at several places in a structure, only the first occurrence will be printed in full, and any other will be a numeric reference mapped to the short representation for the object. It looks like this:

<img src="https://raw.githubusercontent.com/breuleux/hrepr/master/images/hrepr4.png" width="600px">

The `shortrefs` and `norefs` configuration keys control the representation of references:

<img src="https://raw.githubusercontent.com/breuleux/hrepr/master/images/hrepr5.png" width="600px">

`norefs` is ignored when there are circular references.


## HTML generation

Generate HTML using the `H` parameter to `__hrepr__`, or import it and use it directly:

```python
from hrepr import H
html = H.span["bear"](
    "Only ", H.b("YOU"), " can prevent forest fires!",
    style="color: brown;"
)
print(html)
# <span class="bear" style="color: brown;">Only <b>YOU</b> can prevent forest fires!</span>
```

`H` can be built incrementally: if you have an element, you can call it to add children, index it to add classes, and so on. For instance:

```python
from hrepr import H
html = H.span()
html = html("Only ")
html = html(style="color: brown;")["bear"]
html = html(H.b("YOU"), " can prevent forest fires!")
print(html)
# <span class="bear" style="color: brown;">Only <b>YOU</b> can prevent forest fires!</span>
```

This can be handy if you want to tweak generated HTML a little. For example, `hrepr(obj)["fox"]` will tack on the class `fox` to the representation of the object.


### Helpers

* `hrepr.make.instance(title, fields, delimiter=None, type=None)`: formats the fields like a dataclass, with title on top.
* `hrepr.make.bracketed(body, start, end, type=None)`: formats the body with the specified start/end bracket.


### Constructed elements

To make it a bit easier to include and use JavaScript libraries, you can use the special `__constructor` attribute.

For example, you can load Plotly and create a plot like this:

```python
def Plot(data):
    return H.div(
        __constructor={
            "script": "https://cdn.plot.ly/plotly-latest.min.js",
            "symbol": "Plotly.newPlot",
            "options": [{"x": list(range(len(data))), "y": list(data)}],
        }
    )
print(Plot([math.sin(x / 10) for x in range(100)]))
```

The above will:

* Load the specified script.
* Get the `Plotly.newPlot` function in the global namespace.
* Call it with the `div` element as the first argument, and the `options` as the second argument.

It will look like this:

<img src="https://raw.githubusercontent.com/breuleux/hrepr/master/images/hrepr6.png" width="600px">


### Modules

Another example, this time using ESM (modules):

```python
node = H.div(
    style="width:500px;height:500px;border:1px solid black;",
    __constructor={
        "module": "https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.23.0/cytoscape.esm.min.js",
        "arguments": {
            "container": H.self(),
            "elements": [
                {"data": {"id": "A"}},
                {"data": {"id": "B"}},
                {"data": {"id": "C"}},
                {"data": {"source": "A", "target": "B"}},
                {"data": {"source": "B", "target": "C"}},
                {"data": {"source": "C", "target": "A"}},
            ],
            "style": cystyle,
            "layout": {"name": "cose"},
        },
    },
)
print(node)
```

The above will:

* Import the specified module.
* Call the module's default export with `arguments`.
  * Note the use of `H.self()` to refer to the target `div` in the arguments.

If you wish to use a non-default export, set the `symbol` key in the `__constructor` attribute to the name of the export you want.


## Customize hrepr

### Mixins

If you want to *really* customize hrepr, you can use mixins. They look like a bit of black magic, but they're simple enough:

```python
# ovld is one of the dependencies of hrepr
from ovld import ovld, extend_super, has_attribute, OvldMC
from hrepr import hrepr

class MyMixin(metaclass=OvldMC):
    # Change the representation of integers

    @extend_super
    def hrepr_resources(self, cls: int):
        # Note: in hrepr_resources, cls is the int type, not an integer
        return self.H.style(".my-integer { color: fuchsia; }")

    @extend_super
    def hrepr(self, n: int):
        return self.H.span["my-integer"]("The number ", str(n))

    # Specially handle any object with a "quack" method

    def hrepr(self, duck: has_attribute("quack")):
        return self.H.span("🦆")
```

<img src="https://raw.githubusercontent.com/breuleux/hrepr/master/images/hrepr7.png" width="600px">

The annotation for a rule can either be a type, `ovld.has_attribute`, or pretty much any function wrapped with the `ovld.meta` decorator, as long as the function operates on classes. See the documentation for [ovld](https://github.com/breuleux/ovld#other-features) for more information.

And yes, you can define `hrepr` multiple times inside the class, as long as they have distinct annotations and you inherit from `Hrepr`. You can also define `hrepr_short` or `hrepr_resources` the same way.

### Postprocessors

`hrepr` can be given a postprocessor that is called on the representation of any object. You can use this to do things like highlighting specific objects:

```python
from hrepr import H

style = H.style(".highlight { border: 3px solid red !important; }")

def highlight(x):
    def postprocess(element, obj, hrepr):
        if obj == x:
            # Adds the "highlight" class and attaches a style
            return element["highlight"].fill(resources=style)
        else:
            return element

    return postprocess

hrepr([1, 2, [3, 4, 2]], postprocess=highlight(2))
```

<img src="https://raw.githubusercontent.com/breuleux/hrepr/master/images/hrepr8.png" width="600px">


### hrepr variants

To put this all together, you can create a *variant* of `hrepr`:

```python
hrepr2 = hrepr.variant(mixins=MyMixin, postprocess=highlight(2))
hrepr2([1, 2, 3])  # Will use the mixins and postprocessor
```


### Configure the hrepr function itself

Alternatively, you can configure the main `hrepr`:

```python
hrepr.configure(mixins=MyMixin, postprocess=highlight(2))
```

But keep in mind that unlike the variant, the above will modify `hrepr` for everything else as well.

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/breuleux/hrepr",
    "name": "hrepr",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.7,<4.0",
    "maintainer_email": "",
    "keywords": "",
    "author": "Olivier Breuleux",
    "author_email": "breuleux@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/e5/b2/947e450eda4a5fa0426e381183a2db4bd47e441974d4624218b16edc7063/hrepr-0.6.1.tar.gz",
    "platform": null,
    "description": "# hrepr\n\n`hrepr` outputs HTML/pretty representations for Python objects.\n\n\u2705 Nice, colourful representations of lists, dicts, dataclasses, booleans...<br/>\n\u2705 Ridiculously extensible and configurable<br/>\n\u2705 Handles recursive data structures<br/>\n\u2705 Compatible with Jupyter notebooks<br/>\n\n<img src=\"https://raw.githubusercontent.com/breuleux/hrepr/master/images/hrepr1.png\" width=\"400px\"><img src=\"https://raw.githubusercontent.com/breuleux/hrepr/master/images/hrepr2.png\" width=\"400px\">\n\nI suggest studying the example file to learn `hrepr`:\n\n* `python examples/exhibit.py > exhibit.html` (and then view the HTML file)\n\nAlso see the Jupyter notebook at `examples/Basics.ipynb`, but keep in mind that GitHub can't display it properly because of the injected styles/scripts.\n\n\n## Install\n\n```python\npip install hrepr\n```\n\n\n## Usage\n\n```python\nfrom hrepr import hrepr\nobj = {'potatoes': [1, 2, 3], 'bananas': {'cantaloups': 8}}\n\n# Print the HTML representation of obj\nprint(hrepr(obj))\n\n# Wrap the representation in <html><body> tags and embed the default\n# css style files in a standalone page, which is saved to obj.html\nhrepr.page(obj, file=\"obj.html\")\n```\n\nIn a Jupyter Notebook, you can return `hrepr(obj)` from any cell and it will show its representation for you. You can also write `display_html(hrepr(obj))`.\n\n\n## Custom representations\n\nA custom representation for an object can be defined using the following three methods (it is not necessary to define all of them, only those that are relevant to your case):\n\n* `__hrepr__(self, H, hrepr)` returns the normal HTML representation.\n    * Use `H.span[\"some-class\"](some-content, some_attr=some_value)` to generate HTML.\n    * Use `hrepr(self.x)` to generate the representation for some subfield `x`.\n    * `hrepr.config` contains any keyword arguments given in the top level call to `hrepr`. For instance, if you call `hrepr(obj, blah=3)`, then `hrepr.config.blah == 3` in all calls to `__hrepr__` down the line (the default value for all keys is `None`).\n* `__hrepr_short__(self, H, hrepr)` returns a *short* representation, ideally of a constant size.\n    * The output of this method is used when we hit max depth, or for repeated references.\n    * Only include bare minimum information. Short means short.\n* `__hrepr_resources__(cls, H)` is a **classmethod** that returns resources common to all instances of the class (typically a stylesheet or a script).\n    * When generating a page, the resources will go in `<head>`.\n    * You can return a list of resources.\n\nNo dependency on `hrepr` is necessary.\n\nFor example:\n\n```python\nclass Person:\n    def __init__(self, name, age, job):\n        self.name = name\n        self.age = age\n        self.job = job\n\n    @classmethod\n    def __hrepr_resources__(cls, H):\n        # Note: you might need to add \"!important\" next to some rules if\n        # they conflict with defaults from hrepr's own CSS.\n        return H.style(\"\"\"\n            .person {\n                background: magenta !important;\n                border-color: magenta !important;\n            }\n            .person-short { font-weight: bold; color: green; }\n        \"\"\")\n\n    def __hrepr__(self, H, hrepr):\n        # hrepr.make.instance is a helper to show a table with a header that\n        # describes some kind of object\n        return hrepr.make.instance(\n            title=self.name,\n            fields=[[\"age\", self.age], [\"job\", self.job]],\n            delimiter=\" \u21a6 \",\n            type=\"person\",\n        )\n\n    def __hrepr_short__(self, H, hrepr):\n        return H.span[\"person-short\"](self.name)\n```\n\n<img src=\"https://raw.githubusercontent.com/breuleux/hrepr/master/images/hrepr3.png\" width=\"600px\">\n\n\n## References\n\n`hrepr` can handle circular references. Furthermore, if an object is found at several places in a structure, only the first occurrence will be printed in full, and any other will be a numeric reference mapped to the short representation for the object. It looks like this:\n\n<img src=\"https://raw.githubusercontent.com/breuleux/hrepr/master/images/hrepr4.png\" width=\"600px\">\n\nThe `shortrefs` and `norefs` configuration keys control the representation of references:\n\n<img src=\"https://raw.githubusercontent.com/breuleux/hrepr/master/images/hrepr5.png\" width=\"600px\">\n\n`norefs` is ignored when there are circular references.\n\n\n## HTML generation\n\nGenerate HTML using the `H` parameter to `__hrepr__`, or import it and use it directly:\n\n```python\nfrom hrepr import H\nhtml = H.span[\"bear\"](\n    \"Only \", H.b(\"YOU\"), \" can prevent forest fires!\",\n    style=\"color: brown;\"\n)\nprint(html)\n# <span class=\"bear\" style=\"color: brown;\">Only <b>YOU</b> can prevent forest fires!</span>\n```\n\n`H` can be built incrementally: if you have an element, you can call it to add children, index it to add classes, and so on. For instance:\n\n```python\nfrom hrepr import H\nhtml = H.span()\nhtml = html(\"Only \")\nhtml = html(style=\"color: brown;\")[\"bear\"]\nhtml = html(H.b(\"YOU\"), \" can prevent forest fires!\")\nprint(html)\n# <span class=\"bear\" style=\"color: brown;\">Only <b>YOU</b> can prevent forest fires!</span>\n```\n\nThis can be handy if you want to tweak generated HTML a little. For example, `hrepr(obj)[\"fox\"]` will tack on the class `fox` to the representation of the object.\n\n\n### Helpers\n\n* `hrepr.make.instance(title, fields, delimiter=None, type=None)`: formats the fields like a dataclass, with title on top.\n* `hrepr.make.bracketed(body, start, end, type=None)`: formats the body with the specified start/end bracket.\n\n\n### Constructed elements\n\nTo make it a bit easier to include and use JavaScript libraries, you can use the special `__constructor` attribute.\n\nFor example, you can load Plotly and create a plot like this:\n\n```python\ndef Plot(data):\n    return H.div(\n        __constructor={\n            \"script\": \"https://cdn.plot.ly/plotly-latest.min.js\",\n            \"symbol\": \"Plotly.newPlot\",\n            \"options\": [{\"x\": list(range(len(data))), \"y\": list(data)}],\n        }\n    )\nprint(Plot([math.sin(x / 10) for x in range(100)]))\n```\n\nThe above will:\n\n* Load the specified script.\n* Get the `Plotly.newPlot` function in the global namespace.\n* Call it with the `div` element as the first argument, and the `options` as the second argument.\n\nIt will look like this:\n\n<img src=\"https://raw.githubusercontent.com/breuleux/hrepr/master/images/hrepr6.png\" width=\"600px\">\n\n\n### Modules\n\nAnother example, this time using ESM (modules):\n\n```python\nnode = H.div(\n    style=\"width:500px;height:500px;border:1px solid black;\",\n    __constructor={\n        \"module\": \"https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.23.0/cytoscape.esm.min.js\",\n        \"arguments\": {\n            \"container\": H.self(),\n            \"elements\": [\n                {\"data\": {\"id\": \"A\"}},\n                {\"data\": {\"id\": \"B\"}},\n                {\"data\": {\"id\": \"C\"}},\n                {\"data\": {\"source\": \"A\", \"target\": \"B\"}},\n                {\"data\": {\"source\": \"B\", \"target\": \"C\"}},\n                {\"data\": {\"source\": \"C\", \"target\": \"A\"}},\n            ],\n            \"style\": cystyle,\n            \"layout\": {\"name\": \"cose\"},\n        },\n    },\n)\nprint(node)\n```\n\nThe above will:\n\n* Import the specified module.\n* Call the module's default export with `arguments`.\n  * Note the use of `H.self()` to refer to the target `div` in the arguments.\n\nIf you wish to use a non-default export, set the `symbol` key in the `__constructor` attribute to the name of the export you want.\n\n\n## Customize hrepr\n\n### Mixins\n\nIf you want to *really* customize hrepr, you can use mixins. They look like a bit of black magic, but they're simple enough:\n\n```python\n# ovld is one of the dependencies of hrepr\nfrom ovld import ovld, extend_super, has_attribute, OvldMC\nfrom hrepr import hrepr\n\nclass MyMixin(metaclass=OvldMC):\n    # Change the representation of integers\n\n    @extend_super\n    def hrepr_resources(self, cls: int):\n        # Note: in hrepr_resources, cls is the int type, not an integer\n        return self.H.style(\".my-integer { color: fuchsia; }\")\n\n    @extend_super\n    def hrepr(self, n: int):\n        return self.H.span[\"my-integer\"](\"The number \", str(n))\n\n    # Specially handle any object with a \"quack\" method\n\n    def hrepr(self, duck: has_attribute(\"quack\")):\n        return self.H.span(\"\ud83e\udd86\")\n```\n\n<img src=\"https://raw.githubusercontent.com/breuleux/hrepr/master/images/hrepr7.png\" width=\"600px\">\n\nThe annotation for a rule can either be a type, `ovld.has_attribute`, or pretty much any function wrapped with the `ovld.meta` decorator, as long as the function operates on classes. See the documentation for [ovld](https://github.com/breuleux/ovld#other-features) for more information.\n\nAnd yes, you can define `hrepr` multiple times inside the class, as long as they have distinct annotations and you inherit from `Hrepr`. You can also define `hrepr_short` or `hrepr_resources` the same way.\n\n### Postprocessors\n\n`hrepr` can be given a postprocessor that is called on the representation of any object. You can use this to do things like highlighting specific objects:\n\n```python\nfrom hrepr import H\n\nstyle = H.style(\".highlight { border: 3px solid red !important; }\")\n\ndef highlight(x):\n    def postprocess(element, obj, hrepr):\n        if obj == x:\n            # Adds the \"highlight\" class and attaches a style\n            return element[\"highlight\"].fill(resources=style)\n        else:\n            return element\n\n    return postprocess\n\nhrepr([1, 2, [3, 4, 2]], postprocess=highlight(2))\n```\n\n<img src=\"https://raw.githubusercontent.com/breuleux/hrepr/master/images/hrepr8.png\" width=\"600px\">\n\n\n### hrepr variants\n\nTo put this all together, you can create a *variant* of `hrepr`:\n\n```python\nhrepr2 = hrepr.variant(mixins=MyMixin, postprocess=highlight(2))\nhrepr2([1, 2, 3])  # Will use the mixins and postprocessor\n```\n\n\n### Configure the hrepr function itself\n\nAlternatively, you can configure the main `hrepr`:\n\n```python\nhrepr.configure(mixins=MyMixin, postprocess=highlight(2))\n```\n\nBut keep in mind that unlike the variant, the above will modify `hrepr` for everything else as well.\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Extensible HTML representation for Python objects.",
    "version": "0.6.1",
    "project_urls": {
        "Homepage": "https://github.com/breuleux/hrepr",
        "Repository": "https://github.com/breuleux/hrepr"
    },
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "7a021ac1a53f36203dfcc3232a0b11852c48e41532631ddd48381a803271da1d",
                "md5": "ae090e8b7e1f0eba2b0a88f41503246b",
                "sha256": "0791516735a87842e8ad9827cf78f712401549e75cac73a6f7238033729bdab5"
            },
            "downloads": -1,
            "filename": "hrepr-0.6.1-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "ae090e8b7e1f0eba2b0a88f41503246b",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.7,<4.0",
            "size": 22420,
            "upload_time": "2023-12-13T18:37:39",
            "upload_time_iso_8601": "2023-12-13T18:37:39.094774Z",
            "url": "https://files.pythonhosted.org/packages/7a/02/1ac1a53f36203dfcc3232a0b11852c48e41532631ddd48381a803271da1d/hrepr-0.6.1-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "e5b2947e450eda4a5fa0426e381183a2db4bd47e441974d4624218b16edc7063",
                "md5": "d77eedc2a63f1bbf7a99a265c89a85fb",
                "sha256": "77c02208004cc6ce714143c74e592371c3ee8b230f533d3d5d09c43a15a22908"
            },
            "downloads": -1,
            "filename": "hrepr-0.6.1.tar.gz",
            "has_sig": false,
            "md5_digest": "d77eedc2a63f1bbf7a99a265c89a85fb",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.7,<4.0",
            "size": 23754,
            "upload_time": "2023-12-13T18:37:41",
            "upload_time_iso_8601": "2023-12-13T18:37:41.243444Z",
            "url": "https://files.pythonhosted.org/packages/e5/b2/947e450eda4a5fa0426e381183a2db4bd47e441974d4624218b16edc7063/hrepr-0.6.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-12-13 18:37:41",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "breuleux",
    "github_project": "hrepr",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "hrepr"
}
        
Elapsed time: 0.16421s