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__
__trolskgen_cls__
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:

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)"

You can also add a __trolskgen_cls__ @classmethod:

@dataclass
class MyJustName:
    a: str

    @classmethod
    def __trolskgen_cls__(cls, f: trolskgen.F) -> ast.AST:
        return f(t("Foo"))

trolskgen.to_source(MyJustName("bar")) == "Foo(a='bar')"

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.18.tar.gz (15.5 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.18-py3-none-any.whl (12.3 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: trolskgen-0.0.18.tar.gz
  • Upload date:
  • Size: 15.5 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.18.tar.gz
Algorithm Hash digest
SHA256 ceeb649636ef9c30f82b2aae6c8fe5fe2acb82c237d9812c114ef6170fb96c83
MD5 1083a14e7764d30a867087995df3f141
BLAKE2b-256 7c69d4c04c0257e3a8cfdf2c6d782189adf6d14e4357851a202b0ac282f5d0ae

See more details on using hashes here.

File details

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

File metadata

  • Download URL: trolskgen-0.0.18-py3-none-any.whl
  • Upload date:
  • Size: 12.3 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.18-py3-none-any.whl
Algorithm Hash digest
SHA256 40265459abeeaf0b48fa986f2635daf5bf0c30beb3b765d4ff36d5e232f5ca3e
MD5 e1d91d8af76e1b2b8c907248771bc1dd
BLAKE2b-256 5c51f68a9f026f51bce8e9a25eac4b959f9e2713c00c3c77273b928208be85ef

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