Skip to main content

Cute Python codegen

Project description

trolskgen

Ergonomic codegen for Python - pip install trolskgen. Blog post with a motivating example.

Note on Python 3.14 template strings.

From Python 3.14 upwards, there are template strings, these make trolskgen significantly more succinct.

Where previously you'd do:

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

As of Python 3.14, you can do:

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.


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 it afterwards. If you want comments, sorry, instead use a docstring or some Annotated[] wizardry.

Quick example:

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:

def f():
    ...

And the ast.AST:

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

A more complete example:

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:

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:

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.


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:

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/*

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

trolskgen-0.0.16.tar.gz (15.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

trolskgen-0.0.16-py3-none-any.whl (12.2 kB view details)

Uploaded Python 3

File details

Details for the file trolskgen-0.0.16.tar.gz.

File metadata

  • Download URL: trolskgen-0.0.16.tar.gz
  • Upload date:
  • Size: 15.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.5

File hashes

Hashes for trolskgen-0.0.16.tar.gz
Algorithm Hash digest
SHA256 f215346fa60aef5f567c8f175200a905a1abafc2ef1194c2a2fc09822a538a3d
MD5 00c5a7be2e291fbeefec7a0206ec7e36
BLAKE2b-256 00ba7e0087efcca8719ee8fda68f3d913630804695ad158717ca4797ee3bb263

See more details on using hashes here.

File details

Details for the file trolskgen-0.0.16-py3-none-any.whl.

File metadata

  • Download URL: trolskgen-0.0.16-py3-none-any.whl
  • Upload date:
  • Size: 12.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.5

File hashes

Hashes for trolskgen-0.0.16-py3-none-any.whl
Algorithm Hash digest
SHA256 1940e1506db2d47fc5b0f43a545a5df839cb50c316f07f424a8ce37589ff17e0
MD5 5cec0c0edda2fbeaeb555b9c93942ce5
BLAKE2b-256 a85d1d9dcf6237e1e807e5c7403e4ddc9dc7b8dcbdf8167d0a1541e83fffe452

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page