trolskgen


Nametrolskgen JSON
Version 0.0.14 PyPI version JSON
download
home_pageNone
SummaryCute Python codegen
upload_time2025-08-01 09:09:54
maintainerNone
docs_urlNone
authorNone
requires_python>=3.12
licenseNone
keywords codegen
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # `trolskgen`

Ergonomic codegen for Python - `pip install trolskgen`. [Blog post](https://leontrolski.github.io/trolskgen.html) with a motivating example.

<details>
    <summary><em>Note on Python 3.14 template strings.</em></summary>

<br>

From Python 3.14 upwards, there are [template strings](https://peps.python.org/pep-0750/), these make `trolskgen` significantly more succinct.

Where previously you'd do:

```python
name = t("f")
func = t(
    """
    def {name}():
        ...
    """,
    name=name,
)
trolskgen.to_source(func)
```

As of Python 3.14, you can do:

```python
name = t"f"
func =  t"""
    def {name}():
        ...
"""
trolskgen.to_source(func)
```

There are some `if sys.version_info >= (3, 14)` flags around, but it _should_ just work come release date.

<hr>

</details>

`trolskgen` lets you easily build and compose `ast.AST` trees, and thereby easily generate source code. It doesn't handle any formatting concerns, just [`ruff format`](https://github.com/astral-sh/ruff) it afterwards. If you want comments, sorry, instead use a docstring or some `Annotated[]` wizardry.

Quick example:

```python
import trolskgen
from trolskgen import t

func = t(
    """
    def {name}():
        ...
    """,
    name="f",
)
trolskgen.to_source(func)
trolskgen.to_ast(func)
```

Gives you the source `str`:

```python
def f():
    ...
```

And the `ast.AST`:

```python
ast.Module(
    body=[
        ast.FunctionDef(
            name="f",
            args=ast.arguments(...),
            body=[ast.Expr(value=ast.Constant(value=Ellipsis))],
            decorator_list=[],
            type_params=[],
        )
    ],
)
```

<hr>

A more complete example:

```python
import datetime as dt

name = "MySpecialClass"
bases = [int, list]
field_name = "d"
fields = [
    t(
        "a: {type_}",
        type_=str,
    ),
    t(
        "{field_name}: dt.date = {default}",
        field_name=field_name,
        default=dt.date(2000, 1, 1),
    ),
]
method = t(
    """
    def inc(self) -> None:
        self.{field_name} += dt.timedelta(days=1)
    """,
    field_name=field_name,
)
my_special_class_source = t(
    """
    class {name}({bases:*}, float):
        {fields:*}
        {method}
    """,
    name=name,
    bases=bases,
    fields=fields,
    method=method,
)

trolskgen.to_source(my_special_class_source)
```

Gives you the source `str`:

```python
class MySpecialClass(int, list, float):
    a: str
    d: dt.date = dt.date(2000, 1, 1)

    def inc(self) -> None:
        self.d += dt.timedelta(days=1)
```

# API

| Building templates |
|---|
| `trolskgen.t(s: str, **kwargs: Any) -> trolskgen.templates.Template` |

Creates source templates. If you use the format string `:*`, it will splat in place - see above: `{bases:*}`, `{fields:*}`

_This is redundant as of Python 3.14 - see above._

| Converting to AST/source|
|---|
| `trolskgen.to_ast(o: Any, *, config: Config) -> ast.AST` |
| `trolskgen.to_source(o: Any, *, config: Config, ruff_format: bool) -> str` |

Try to convert `o` into an `ast.AST`/`str` representation.

The following are special cases for the value of `o`:
- `ast.AST` nodes - these just get passed straight back out.
- `trolskgen.templates.Template` or `string.templatelib.Template` - these get parsed as Python code.

`trolskgen` will generate sensible ASTs, for the following types:

- `None`
- `int`
- `float`
- `str`
- `bool`
- `list`
- `tuple`
- `dict`
- `set`
- `classes`
- `functions`
- `dt.datetime`
- `dt.date`
- `enum.Enum`
- `dataclass`
- `Annotated`, `T | U`, etc.
- `pydantic.BaseModel`

If you have `ruff` installed, you can call with `ruff_format=True`.

We can add our own classes/overrides using:

| Configuring/Overriding |
|---|
| `trolskgen.ConvertInterface` |
| `trolskgen.Converter` |
| `trolskgen.Config` |
| `trolskgen.Config().prepend_converter(converter: Converter, *, before: Converter \| None) -> Config` |

If you own the class, you can just add a `__trolskgen__` method that satisfies `trolskgen.ConvertInterface`.

For example:

```python
class MyInterfaceClass:
    def __trolskgen__(self, f: trolskgen.F) -> ast.AST:
        return f(t("MyInterfaceClass({values:*})", values=[1, 2, 3]))

trolskgen.to_source(MyInterfaceClass()) == "MyInterfaceClass(1, 2, 3)"
```

Note that we use `f` to recursively call `trolskgen.to_ast(...)` while preserving the current `Config`.

<hr>

If you don't own the class, you can build a `trolskgen.Config` with a custom `Converter` function.

For example, if you for some reason wanted to render all ints in the form `x + 1`, you could:

```python
def custom_int_converter(o: Any, f: trolskgen.F) -> ast.AST | None:
    if not isinstance(o, int):
        return None
    return f(t(f"{o - 1} + 1"))

config = trolskgen.Config().prepend_converter(custom_int_converter)
trolskgen.to_source([6, 9], config=config) == "[5 + 1, 8 + 1]"
```

# Development

```
uv pip install -e '.[dev]'
mypy .
pytest -vv
uv pip install build twine
python -m build
twine check dist/*
twine upload dist/*
```

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "trolskgen",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.12",
    "maintainer_email": "Oliver Russell <ojhrussell@gmail.com>",
    "keywords": "codegen",
    "author": null,
    "author_email": "Oliver Russell <ojhrussell@gmail.com>",
    "download_url": "https://files.pythonhosted.org/packages/76/8b/a85c3389565f0216c6561550c049073c43d4c5836a535f9b8e0589bedf75/trolskgen-0.0.14.tar.gz",
    "platform": null,
    "description": "# `trolskgen`\n\nErgonomic codegen for Python - `pip install trolskgen`. [Blog post](https://leontrolski.github.io/trolskgen.html) with a motivating example.\n\n<details>\n    <summary><em>Note on Python 3.14 template strings.</em></summary>\n\n<br>\n\nFrom Python 3.14 upwards, there are [template strings](https://peps.python.org/pep-0750/), these make `trolskgen` significantly more succinct.\n\nWhere previously you'd do:\n\n```python\nname = t(\"f\")\nfunc = t(\n    \"\"\"\n    def {name}():\n        ...\n    \"\"\",\n    name=name,\n)\ntrolskgen.to_source(func)\n```\n\nAs of Python 3.14, you can do:\n\n```python\nname = t\"f\"\nfunc =  t\"\"\"\n    def {name}():\n        ...\n\"\"\"\ntrolskgen.to_source(func)\n```\n\nThere are some `if sys.version_info >= (3, 14)` flags around, but it _should_ just work come release date.\n\n<hr>\n\n</details>\n\n`trolskgen` lets you easily build and compose `ast.AST` trees, and thereby easily generate source code. It doesn't handle any formatting concerns, just [`ruff format`](https://github.com/astral-sh/ruff) it afterwards. If you want comments, sorry, instead use a docstring or some `Annotated[]` wizardry.\n\nQuick example:\n\n```python\nimport trolskgen\nfrom trolskgen import t\n\nfunc = t(\n    \"\"\"\n    def {name}():\n        ...\n    \"\"\",\n    name=\"f\",\n)\ntrolskgen.to_source(func)\ntrolskgen.to_ast(func)\n```\n\nGives you the source `str`:\n\n```python\ndef f():\n    ...\n```\n\nAnd the `ast.AST`:\n\n```python\nast.Module(\n    body=[\n        ast.FunctionDef(\n            name=\"f\",\n            args=ast.arguments(...),\n            body=[ast.Expr(value=ast.Constant(value=Ellipsis))],\n            decorator_list=[],\n            type_params=[],\n        )\n    ],\n)\n```\n\n<hr>\n\nA more complete example:\n\n```python\nimport datetime as dt\n\nname = \"MySpecialClass\"\nbases = [int, list]\nfield_name = \"d\"\nfields = [\n    t(\n        \"a: {type_}\",\n        type_=str,\n    ),\n    t(\n        \"{field_name}: dt.date = {default}\",\n        field_name=field_name,\n        default=dt.date(2000, 1, 1),\n    ),\n]\nmethod = t(\n    \"\"\"\n    def inc(self) -> None:\n        self.{field_name} += dt.timedelta(days=1)\n    \"\"\",\n    field_name=field_name,\n)\nmy_special_class_source = t(\n    \"\"\"\n    class {name}({bases:*}, float):\n        {fields:*}\n        {method}\n    \"\"\",\n    name=name,\n    bases=bases,\n    fields=fields,\n    method=method,\n)\n\ntrolskgen.to_source(my_special_class_source)\n```\n\nGives you the source `str`:\n\n```python\nclass MySpecialClass(int, list, float):\n    a: str\n    d: dt.date = dt.date(2000, 1, 1)\n\n    def inc(self) -> None:\n        self.d += dt.timedelta(days=1)\n```\n\n# API\n\n| Building templates |\n|---|\n| `trolskgen.t(s: str, **kwargs: Any) -> trolskgen.templates.Template` |\n\nCreates source templates. If you use the format string `:*`, it will splat in place - see above: `{bases:*}`, `{fields:*}`\n\n_This is redundant as of Python 3.14 - see above._\n\n| Converting to AST/source|\n|---|\n| `trolskgen.to_ast(o: Any, *, config: Config) -> ast.AST` |\n| `trolskgen.to_source(o: Any, *, config: Config, ruff_format: bool) -> str` |\n\nTry to convert `o` into an `ast.AST`/`str` representation.\n\nThe following are special cases for the value of `o`:\n- `ast.AST` nodes - these just get passed straight back out.\n- `trolskgen.templates.Template` or `string.templatelib.Template` - these get parsed as Python code.\n\n`trolskgen` will generate sensible ASTs, for the following types:\n\n- `None`\n- `int`\n- `float`\n- `str`\n- `bool`\n- `list`\n- `tuple`\n- `dict`\n- `set`\n- `classes`\n- `functions`\n- `dt.datetime`\n- `dt.date`\n- `enum.Enum`\n- `dataclass`\n- `Annotated`, `T | U`, etc.\n- `pydantic.BaseModel`\n\nIf you have `ruff` installed, you can call with `ruff_format=True`.\n\nWe can add our own classes/overrides using:\n\n| Configuring/Overriding |\n|---|\n| `trolskgen.ConvertInterface` |\n| `trolskgen.Converter` |\n| `trolskgen.Config` |\n| `trolskgen.Config().prepend_converter(converter: Converter, *, before: Converter \\| None) -> Config` |\n\nIf you own the class, you can just add a `__trolskgen__` method that satisfies `trolskgen.ConvertInterface`.\n\nFor example:\n\n```python\nclass MyInterfaceClass:\n    def __trolskgen__(self, f: trolskgen.F) -> ast.AST:\n        return f(t(\"MyInterfaceClass({values:*})\", values=[1, 2, 3]))\n\ntrolskgen.to_source(MyInterfaceClass()) == \"MyInterfaceClass(1, 2, 3)\"\n```\n\nNote that we use `f` to recursively call `trolskgen.to_ast(...)` while preserving the current `Config`.\n\n<hr>\n\nIf you don't own the class, you can build a `trolskgen.Config` with a custom `Converter` function.\n\nFor example, if you for some reason wanted to render all ints in the form `x + 1`, you could:\n\n```python\ndef custom_int_converter(o: Any, f: trolskgen.F) -> ast.AST | None:\n    if not isinstance(o, int):\n        return None\n    return f(t(f\"{o - 1} + 1\"))\n\nconfig = trolskgen.Config().prepend_converter(custom_int_converter)\ntrolskgen.to_source([6, 9], config=config) == \"[5 + 1, 8 + 1]\"\n```\n\n# Development\n\n```\nuv pip install -e '.[dev]'\nmypy .\npytest -vv\nuv pip install build twine\npython -m build\ntwine check dist/*\ntwine upload dist/*\n```\n",
    "bugtrack_url": null,
    "license": null,
    "summary": "Cute Python codegen",
    "version": "0.0.14",
    "project_urls": {
        "homepage": "https://github.com/leontrolski/trolskgen"
    },
    "split_keywords": [
        "codegen"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "5fa8deafe1140c9b509abbe777c892ac8e93161c0c3d1cb6692c1e484a4eab7e",
                "md5": "af53a2df66f1208e38ed8a30b4155e3a",
                "sha256": "511732674ab35361b8d48f9d6331c055fa0c2ba8f0bf4f890532b4eefd49ef16"
            },
            "downloads": -1,
            "filename": "trolskgen-0.0.14-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "af53a2df66f1208e38ed8a30b4155e3a",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.12",
            "size": 12068,
            "upload_time": "2025-08-01T09:09:53",
            "upload_time_iso_8601": "2025-08-01T09:09:53.446880Z",
            "url": "https://files.pythonhosted.org/packages/5f/a8/deafe1140c9b509abbe777c892ac8e93161c0c3d1cb6692c1e484a4eab7e/trolskgen-0.0.14-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "768ba85c3389565f0216c6561550c049073c43d4c5836a535f9b8e0589bedf75",
                "md5": "ab35d707d0df032855db8245dccac3bb",
                "sha256": "be0938599ce6d25137a3f379c330992010122a6c847009fdcc1d0b5b15238b77"
            },
            "downloads": -1,
            "filename": "trolskgen-0.0.14.tar.gz",
            "has_sig": false,
            "md5_digest": "ab35d707d0df032855db8245dccac3bb",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.12",
            "size": 15150,
            "upload_time": "2025-08-01T09:09:54",
            "upload_time_iso_8601": "2025-08-01T09:09:54.257305Z",
            "url": "https://files.pythonhosted.org/packages/76/8b/a85c3389565f0216c6561550c049073c43d4c5836a535f9b8e0589bedf75/trolskgen-0.0.14.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-08-01 09:09:54",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "leontrolski",
    "github_project": "trolskgen",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "lcname": "trolskgen"
}
        
Elapsed time: 0.47216s