A Python library for generating Python code via AST construction.
Project description
A Python library for generating Python code via AST construction.
Overview
fluent-codegen provides a set of classes that represent simplified Python constructs (functions, assignments, expressions, control flow, etc.) and can generate real Python ast nodes. This lets you build correct Python code programmatically without manipulating raw AST or worrying about string interpolation pitfalls.
Originally extracted from fluent-compiler, where it was used to compile Fluent localization files into Python bytecode.
Key features
Safe by construction — builds AST, not strings, eliminating injection bugs
Scope management — automatic name deduplication and scope tracking
Simplified API — high-level classes (Function, If, Try, StringJoin, etc.) that map to Python constructs without requiring knowledge of the raw ast module, plus two levels of helpers for building up expressions:
Security guardrails — blocks calls to sensitive builtins (exec, eval, etc.)
Installation
pip install fluent-codegen
Requires Python 3.12+.
Quick example
This builds a FizzBuzz function entirely via the codegen API, using fluent method-chaining for expressions:
from fluent_codegen import codegen
# 1. Create a module and a function inside it
module = codegen.Module()
func, _ = module.create_function("fizzbuzz", args=["n"])
# 2. A Name reference to the "n" parameter (Function *is* a Scope)
n = func.name("n")
# 3. Build an if / elif / else chain
if_stmt = func.body.create_if()
# if n % 15 == 0: return "FizzBuzz" — fluent chaining
branch = if_stmt.create_if_branch(n.mod(codegen.Number(15)).eq(codegen.Number(0)))
branch.create_return(codegen.String("FizzBuzz"))
# elif n % 3 == 0: return "Fizz"
branch = if_stmt.create_if_branch(n.mod(codegen.Number(3)).eq(codegen.Number(0)))
branch.create_return(codegen.String("Fizz"))
# elif n % 5 == 0: return "Buzz"
branch = if_stmt.create_if_branch(n.mod(codegen.Number(5)).eq(codegen.Number(0)))
branch.create_return(codegen.String("Buzz"))
# else: return str(n)
if_stmt.else_block.create_return(module.scope.name("str").call([n]))
# 4. Inspect the generated source
print(module.as_python_source())
# def fizzbuzz(n):
# if n % 15 == 0:
# return 'FizzBuzz'
# elif n % 3 == 0:
# return 'Fizz'
# elif n % 5 == 0:
# return 'Buzz'
# else:
# return str(n)
# 5. Compile, execute, and call the generated function
code = compile(module.as_ast(), "<fizzbuzz>", "exec")
ns: dict[str, object] = {}
exec(code, ns)
fizzbuzz = ns["fizzbuzz"]
assert fizzbuzz(15) == "FizzBuzz"
assert fizzbuzz(9) == "Fizz"
assert fizzbuzz(10) == "Buzz"
assert fizzbuzz(7) == "7"
Even simpler with E-objects
The example above uses the method-chaining API (n.mod(...).eq(...)), which maps one-to-one to AST nodes. For expression-heavy code, where you know the names of functions/methods/attributes statically, the E-object API lets you use normal Python operators instead — the library intercepts them and builds the AST for you.
Here’s the same FizzBuzz with E-objects:
from fluent_codegen import codegen
module = codegen.Module()
func, _ = module.create_function("fizzbuzz", args=["n"])
n = func.name("n")
if_stmt = func.body.create_if()
# n.e enters "E-object mode" — then % and == are Python operators
branch = if_stmt.create_if_branch(n.e % 15 == 0)
branch.create_return(codegen.String("FizzBuzz"))
branch = if_stmt.create_if_branch(n.e % 3 == 0)
branch.create_return(codegen.String("Fizz"))
branch = if_stmt.create_if_branch(n.e % 5 == 0)
branch.create_return(codegen.String("Buzz"))
# Convenient access to builtins as E-objects via `Scope.enames`
if_stmt.else_block.create_return(module.enames.str(n))
The generated output is identical. The key difference is readability: n.e % 15 == 0 vs n.mod(codegen.Number(15)).eq(codegen.Number(0)).
E-objects really shine for math-heavy expressions:
module = codegen.Module()
_, math_lib = module.create_import("math")
func, _ = module.create_function("distance", args=["x", "y"])
x = func.name("x")
y = func.name("y")
# E-object — reads like the code it generates
func.body.create_return(math_lib.e.sqrt(x.e ** 2 + y.e ** 2))
print(module.as_python_source())
# import math
# def distance(x, y):
# return math.sqrt(x ** 2 + y ** 2)
Compare with the equivalent method-chaining version:
func.body.create_return(
math_lib.attr("sqrt").call([
x.pow(codegen.Number(2)).add(y.pow(codegen.Number(2)))
])
)
License
Apache License 2.0
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fluent_codegen-0.5.0.tar.gz.
File metadata
- Download URL: fluent_codegen-0.5.0.tar.gz
- Upload date:
- Size: 48.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.3 {"installer":{"name":"uv","version":"0.10.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Linux Mint","version":"22.2","id":"zara","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
287fef79cea51d6c6aa8f9101d4854cbbf4993fdd463c1a62a0a2009863e6a53
|
|
| MD5 |
2898be4f45b91f78630a7b9269a7345c
|
|
| BLAKE2b-256 |
0d8c3ae37fda4c35909455a4fad25fdea88bf0561234c01ccb04ccf8fd8fd02b
|
File details
Details for the file fluent_codegen-0.5.0-py3-none-any.whl.
File metadata
- Download URL: fluent_codegen-0.5.0-py3-none-any.whl
- Upload date:
- Size: 27.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.3 {"installer":{"name":"uv","version":"0.10.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Linux Mint","version":"22.2","id":"zara","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0ec66bf0b1423ee0e6d68218bcb33c25124caa3bcc609d925856ef7d72b59759
|
|
| MD5 |
0a8294b7e2ea90ac30753fab42759434
|
|
| BLAKE2b-256 |
fc9ba23d407981effe72d76fd2267982d0d7d2377b1d6275a42d9d558cdf961e
|