fluent-codegen 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,13 @@
1
+ Copyright 2026 Luke Plant
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
@@ -0,0 +1,124 @@
1
+ Metadata-Version: 2.4
2
+ Name: fluent-codegen
3
+ Version: 0.1.0
4
+ Summary: A Python library for generating Python code via AST construction.
5
+ Author-email: Luke Plant <luke@lukeplant.me.uk>
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Repository, https://github.com/spookylukey/fluent-codegen
8
+ Keywords: codegen,code-generation,ast,python,metaprogramming
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3 :: Only
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: Implementation :: CPython
16
+ Classifier: Topic :: Software Development :: Code Generators
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Requires-Python: >=3.12
19
+ Description-Content-Type: text/x-rst
20
+ License-File: LICENSE
21
+ Dynamic: license-file
22
+
23
+ fluent-codegen
24
+ ==============
25
+
26
+ A Python library for generating Python code via AST construction.
27
+
28
+ Overview
29
+ --------
30
+
31
+ ``fluent-codegen`` provides a set of classes that represent simplified
32
+ Python constructs (functions, assignments, expressions, control flow,
33
+ etc.) and can generate real Python ``ast`` nodes. This lets you build
34
+ correct Python code programmatically without manipulating raw AST or
35
+ worrying about string interpolation pitfalls.
36
+
37
+ Originally extracted from
38
+ `fluent-compiler <https://github.com/django-ftl/fluent-compiler>`__,
39
+ where it was used to compile Fluent localization files into Python
40
+ bytecode.
41
+
42
+ Key features
43
+ ------------
44
+
45
+ - **Safe by construction** — builds AST, not strings, eliminating
46
+ injection bugs
47
+ - **Scope management** — automatic name deduplication and scope
48
+ tracking
49
+ - **Simplified API** — high-level classes (``Function``, ``If``,
50
+ ``Try``, ``StringJoin``, etc.) that map to Python constructs without
51
+ requiring knowledge of the raw ``ast`` module
52
+ - **Security guardrails** — blocks calls to sensitive builtins
53
+ (``exec``, ``eval``, etc.)
54
+
55
+ Installation
56
+ ------------
57
+
58
+ .. code:: bash
59
+
60
+ pip install fluent-codegen
61
+
62
+ Requires Python 3.12+.
63
+
64
+ Quick example
65
+ -------------
66
+
67
+ This builds a FizzBuzz function entirely via the codegen API, using
68
+ fluent method-chaining for expressions:
69
+
70
+ .. code:: python
71
+
72
+ from fluent_codegen import codegen
73
+
74
+ # 1. Create a module and a function inside it
75
+ module = codegen.Module()
76
+ func, _ = module.create_function("fizzbuzz", args=["n"])
77
+
78
+ # 2. A Name reference to the "n" parameter (Function *is* a Scope)
79
+ n = codegen.Name("n", func)
80
+
81
+ # 3. Build an if / elif / else chain
82
+ if_stmt = func.body.create_if()
83
+
84
+ # if n % 15 == 0: return "FizzBuzz" — fluent chaining
85
+ branch = if_stmt.create_if_branch(n.mod(codegen.Number(15)).eq(codegen.Number(0)))
86
+ branch.create_return(codegen.String("FizzBuzz"))
87
+
88
+ # elif n % 3 == 0: return "Fizz"
89
+ branch = if_stmt.create_if_branch(n.mod(codegen.Number(3)).eq(codegen.Number(0)))
90
+ branch.create_return(codegen.String("Fizz"))
91
+
92
+ # elif n % 5 == 0: return "Buzz"
93
+ branch = if_stmt.create_if_branch(n.mod(codegen.Number(5)).eq(codegen.Number(0)))
94
+ branch.create_return(codegen.String("Buzz"))
95
+
96
+ # else: return str(n)
97
+ if_stmt.else_block.create_return(codegen.function_call("str", [n], {}, func))
98
+
99
+ # 4. Inspect the generated source
100
+ print(module.as_python_source())
101
+ # def fizzbuzz(n):
102
+ # if n % 15 == 0:
103
+ # return 'FizzBuzz'
104
+ # elif n % 3 == 0:
105
+ # return 'Fizz'
106
+ # elif n % 5 == 0:
107
+ # return 'Buzz'
108
+ # else:
109
+ # return str(n)
110
+
111
+ # 5. Compile, execute, and call the generated function
112
+ code = compile(module.as_ast(), "<fizzbuzz>", "exec")
113
+ ns: dict[str, object] = {}
114
+ exec(code, ns)
115
+ fizzbuzz = ns["fizzbuzz"]
116
+ assert fizzbuzz(15) == "FizzBuzz"
117
+ assert fizzbuzz(9) == "Fizz"
118
+ assert fizzbuzz(10) == "Buzz"
119
+ assert fizzbuzz(7) == "7"
120
+
121
+ License
122
+ -------
123
+
124
+ Apache License 2.0
@@ -0,0 +1,102 @@
1
+ fluent-codegen
2
+ ==============
3
+
4
+ A Python library for generating Python code via AST construction.
5
+
6
+ Overview
7
+ --------
8
+
9
+ ``fluent-codegen`` provides a set of classes that represent simplified
10
+ Python constructs (functions, assignments, expressions, control flow,
11
+ etc.) and can generate real Python ``ast`` nodes. This lets you build
12
+ correct Python code programmatically without manipulating raw AST or
13
+ worrying about string interpolation pitfalls.
14
+
15
+ Originally extracted from
16
+ `fluent-compiler <https://github.com/django-ftl/fluent-compiler>`__,
17
+ where it was used to compile Fluent localization files into Python
18
+ bytecode.
19
+
20
+ Key features
21
+ ------------
22
+
23
+ - **Safe by construction** — builds AST, not strings, eliminating
24
+ injection bugs
25
+ - **Scope management** — automatic name deduplication and scope
26
+ tracking
27
+ - **Simplified API** — high-level classes (``Function``, ``If``,
28
+ ``Try``, ``StringJoin``, etc.) that map to Python constructs without
29
+ requiring knowledge of the raw ``ast`` module
30
+ - **Security guardrails** — blocks calls to sensitive builtins
31
+ (``exec``, ``eval``, etc.)
32
+
33
+ Installation
34
+ ------------
35
+
36
+ .. code:: bash
37
+
38
+ pip install fluent-codegen
39
+
40
+ Requires Python 3.12+.
41
+
42
+ Quick example
43
+ -------------
44
+
45
+ This builds a FizzBuzz function entirely via the codegen API, using
46
+ fluent method-chaining for expressions:
47
+
48
+ .. code:: python
49
+
50
+ from fluent_codegen import codegen
51
+
52
+ # 1. Create a module and a function inside it
53
+ module = codegen.Module()
54
+ func, _ = module.create_function("fizzbuzz", args=["n"])
55
+
56
+ # 2. A Name reference to the "n" parameter (Function *is* a Scope)
57
+ n = codegen.Name("n", func)
58
+
59
+ # 3. Build an if / elif / else chain
60
+ if_stmt = func.body.create_if()
61
+
62
+ # if n % 15 == 0: return "FizzBuzz" — fluent chaining
63
+ branch = if_stmt.create_if_branch(n.mod(codegen.Number(15)).eq(codegen.Number(0)))
64
+ branch.create_return(codegen.String("FizzBuzz"))
65
+
66
+ # elif n % 3 == 0: return "Fizz"
67
+ branch = if_stmt.create_if_branch(n.mod(codegen.Number(3)).eq(codegen.Number(0)))
68
+ branch.create_return(codegen.String("Fizz"))
69
+
70
+ # elif n % 5 == 0: return "Buzz"
71
+ branch = if_stmt.create_if_branch(n.mod(codegen.Number(5)).eq(codegen.Number(0)))
72
+ branch.create_return(codegen.String("Buzz"))
73
+
74
+ # else: return str(n)
75
+ if_stmt.else_block.create_return(codegen.function_call("str", [n], {}, func))
76
+
77
+ # 4. Inspect the generated source
78
+ print(module.as_python_source())
79
+ # def fizzbuzz(n):
80
+ # if n % 15 == 0:
81
+ # return 'FizzBuzz'
82
+ # elif n % 3 == 0:
83
+ # return 'Fizz'
84
+ # elif n % 5 == 0:
85
+ # return 'Buzz'
86
+ # else:
87
+ # return str(n)
88
+
89
+ # 5. Compile, execute, and call the generated function
90
+ code = compile(module.as_ast(), "<fizzbuzz>", "exec")
91
+ ns: dict[str, object] = {}
92
+ exec(code, ns)
93
+ fizzbuzz = ns["fizzbuzz"]
94
+ assert fizzbuzz(15) == "FizzBuzz"
95
+ assert fizzbuzz(9) == "Fizz"
96
+ assert fizzbuzz(10) == "Buzz"
97
+ assert fizzbuzz(7) == "7"
98
+
99
+ License
100
+ -------
101
+
102
+ Apache License 2.0
@@ -0,0 +1,84 @@
1
+ [project]
2
+ name = "fluent-codegen"
3
+ description = "A Python library for generating Python code via AST construction."
4
+ license = "Apache-2.0"
5
+ authors = [
6
+ { name = "Luke Plant", email = "luke@lukeplant.me.uk" }
7
+ ]
8
+ requires-python = ">=3.12"
9
+ keywords = [
10
+ "codegen",
11
+ "code-generation",
12
+ "ast",
13
+ "python",
14
+ "metaprogramming",
15
+ ]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Developers",
19
+ "Operating System :: OS Independent",
20
+ "Programming Language :: Python :: 3 :: Only",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Programming Language :: Python :: Implementation :: CPython",
24
+ "Topic :: Software Development :: Code Generators",
25
+ "Topic :: Software Development :: Libraries :: Python Modules",
26
+ ]
27
+ dynamic = ["version"]
28
+
29
+ dependencies = []
30
+
31
+ [project.urls]
32
+ Repository = "https://github.com/spookylukey/fluent-codegen"
33
+
34
+
35
+ [project.readme]
36
+ file = "README.rst"
37
+ content-type = "text/x-rst"
38
+
39
+ [build-system]
40
+ requires = ["setuptools>=61.2"]
41
+ build-backend = "setuptools.build_meta"
42
+
43
+ [tool.setuptools]
44
+ package-dir = {"" = "src"}
45
+ include-package-data = false
46
+
47
+ [tool.setuptools.packages.find]
48
+ where = ["src"]
49
+ namespaces = false
50
+
51
+ [tool.setuptools.dynamic]
52
+ version = {attr = "fluent_codegen.__version__"}
53
+
54
+ [tool.ruff]
55
+ line-length = 120
56
+ target-version = "py312"
57
+
58
+ [tool.ruff.lint]
59
+ ignore = ["E501", "E731"]
60
+ extend-select = [
61
+ "UP",
62
+ "I",
63
+ "FLY",
64
+ ]
65
+
66
+ [tool.ruff.lint.isort]
67
+ known-first-party = ["fluent_codegen"]
68
+
69
+ [tool.pytest.ini_options]
70
+ testpaths = ["tests"]
71
+ addopts = "--cov=fluent_codegen --cov-fail-under=100"
72
+
73
+ [dependency-groups]
74
+ dev = [
75
+ "hypothesis>=4.9.0",
76
+ "pre-commit>=4.5.1",
77
+ "pyright==1.1.406",
78
+ "pytest>=7.4.4",
79
+ "pytest-cov>=7.0.0",
80
+ "ruff>=0.4.0",
81
+ "tox>=4.36.0",
82
+ "tox-uv>=1.29.0",
83
+ ]
84
+
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
@@ -0,0 +1,93 @@
1
+ """Compatibility module for generating Python AST.
2
+
3
+ Provides a curated subset of the stdlib `ast` module used by the codegen module.
4
+ """
5
+
6
+ import ast
7
+ from typing import TypedDict
8
+
9
+ # This is a very limited subset of Python AST:
10
+ # - only the things needed by codegen.py
11
+
12
+ Add = ast.Add
13
+ And = ast.And
14
+ Assign = ast.Assign
15
+ AnnAssign = ast.AnnAssign
16
+ BoolOp = ast.BoolOp
17
+ BinOp = ast.BinOp
18
+ Compare = ast.Compare
19
+ Dict = ast.Dict
20
+ Div = ast.Div
21
+ Eq = ast.Eq
22
+ ExceptHandler = ast.ExceptHandler
23
+ Expr = ast.Expr
24
+ FloorDiv = ast.FloorDiv
25
+ Gt = ast.Gt
26
+ GtE = ast.GtE
27
+ If = ast.If
28
+ In = ast.In
29
+ List = ast.List
30
+ Load = ast.Load
31
+ Lt = ast.Lt
32
+ LtE = ast.LtE
33
+ Mod = ast.Mod
34
+ Module = ast.Module
35
+ Mult = ast.Mult
36
+ MatMult = ast.MatMult
37
+ NotEq = ast.NotEq
38
+ NotIn = ast.NotIn
39
+ Or = ast.Or
40
+ Pow = ast.Pow
41
+ Sub = ast.Sub
42
+ boolop = ast.boolop
43
+ cmpop = ast.cmpop
44
+ operator = ast.operator
45
+ Pass = ast.Pass
46
+ Return = ast.Return
47
+ Set = ast.Set
48
+ Starred = ast.Starred
49
+ Store = ast.Store
50
+ Subscript = ast.Subscript
51
+ Tuple = ast.Tuple
52
+ arguments = ast.arguments
53
+ JoinedStr = ast.JoinedStr
54
+ FormattedValue = ast.FormattedValue
55
+ Attribute = ast.Attribute
56
+ Call = ast.Call
57
+ FunctionDef = ast.FunctionDef
58
+ Name = ast.Name
59
+ Try = ast.Try
60
+ With = ast.With
61
+ withitem = ast.withitem
62
+ arg = ast.arg
63
+ keyword = ast.keyword
64
+ ClassDef = ast.ClassDef
65
+ walk = ast.walk
66
+ fix_missing_locations = ast.fix_missing_locations
67
+ unparse = ast.unparse
68
+ Constant = ast.Constant
69
+ AST = ast.AST
70
+ stmt = ast.stmt
71
+ expr = ast.expr
72
+ Import = ast.Import
73
+ ImportFrom = ast.ImportFrom
74
+ alias = ast.alias
75
+
76
+ # `compile` builtin needs these attributes on AST nodes.
77
+ # It's hard to get something sensible we can put for line/col numbers so we put arbitrary values.
78
+
79
+
80
+ class DefaultAstArgs(TypedDict):
81
+ lineno: int
82
+ col_offset: int
83
+
84
+
85
+ DEFAULT_AST_ARGS: DefaultAstArgs = {"lineno": 1, "col_offset": 1}
86
+ # Some AST types have different requirements:
87
+ DEFAULT_AST_ARGS_MODULE: dict[str, object] = dict()
88
+ DEFAULT_AST_ARGS_ADD: dict[str, object] = dict()
89
+ DEFAULT_AST_ARGS_ARGUMENTS: dict[str, object] = dict()
90
+
91
+
92
+ def subscript_slice_object[T](value: T) -> T:
93
+ return value