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.12.tar.gz (15.1 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.12-py3-none-any.whl (12.0 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: trolskgen-0.0.12.tar.gz
  • Upload date:
  • Size: 15.1 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.12.tar.gz
Algorithm Hash digest
SHA256 47dfb5e859bf7476ef77ad50ca04f7381bd06e6a1a2b8004ed56a3955dbd4c51
MD5 f4cec031b5d4e91e2da1ded47a72d343
BLAKE2b-256 72feca507474febe103eaef825efc1d97b78db6c04e0735573ff9db7b2602694

See more details on using hashes here.

File details

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

File metadata

  • Download URL: trolskgen-0.0.12-py3-none-any.whl
  • Upload date:
  • Size: 12.0 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.12-py3-none-any.whl
Algorithm Hash digest
SHA256 64d51df2083779e4c3088e73db02932762cac74f4b66d0d8b49c1fef7a9e9b07
MD5 f01052790521e4d2592f941237726839
BLAKE2b-256 8427e5251d9774fb989015df051f88b95ec85c6c65560d0357b45bf77d845881

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