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 |
trolskgen.e(s: str, **kwargs: Any) -> trolskgen.ASTModule |
Creates source templates. If you use the format string :*, it will splat in place - see above: {bases:*}, {fields:*}
e is for "eager" - this might be preferable to use, otherwise error messages can become pretty inscrutable.
This is redundant as of Python 3.14 - see above.
| Converting to AST/source |
|---|
trolskgen.to_ast(o: Any, *, config: Config) -> trolskgen.ASTModule |
trolskgen.to_source(o: Any, *, config: Config, ruff_format: bool, ruff_line_length: int) -> str |
Try to convert o into an ast.AST/str representation.
The following are special cases for the value of o:
ast.Module | ast.exprnodes - these just get passed straight back out.trolskgen.templates.Templateorstring.templatelib.Template- these get parsed as Python code.
trolskgen will generate sensible ASTs, for the following types:
Noneintfloatstrboollisttupledictsetclassesfunctionsdt.datetimedt.dateenum.EnumdataclassAnnotated,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.Module:
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.Module:
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.Module | 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]"
You can also set a global trolskgen.GLOBAL_CONFIG ContextVar.
with trolskgen.GLOBAL_CONFIG.set(config):
trolskgen.to_source([6, 9]) == "[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/**/trolskgen*
Metadata
Release files for trolskgen 0.0.23
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| trolskgen-0.0.23.tar.gz | 16.5 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| trolskgen-0.0.23-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 29.5 kB
Release files / trolskgen-0.0.23.tar.gz
| Download URL | trolskgen-0.0.23.tar.gz |
|---|---|
| Size | 16.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
dc9e37a58af0804aa864df78efc0e0f19d9c35f8cfccd3665c9344f7692628e2
|
|
BLAKE2b-256 checksum How to use checksums |
5ca15f9994c5359a2e0a1d94ede604a805ad86f12563444ecdd70e3c622d0e3b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.12 {"installer":{"name":"uv","version":"0.12.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / trolskgen-0.0.23-py3-none-any.whl
| Download URL | trolskgen-0.0.23-py3-none-any.whl |
|---|---|
| Size | 13.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
de70b59f05bbfc7b47430f594fa6ffd1ce75cceae99ede735e2d8eadbe2e2816
|
|
BLAKE2b-256 checksum How to use checksums |
2127f2d5a4abe4008e1dbcd2f7f9712cc968952cf5187378728b02a8dc9c3102
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.12 {"installer":{"name":"uv","version":"0.12.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for trolskgen 0.0.23
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.