Name | trolskgen JSON |
Version |
0.0.14
JSON |
| download |
home_page | None |
Summary | Cute Python codegen |
upload_time | 2025-08-01 09:09:54 |
maintainer | None |
docs_url | None |
author | None |
requires_python | >=3.12 |
license | None |
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"
}