Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
216 changes: 166 additions & 50 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,50 +7,40 @@
[![Downloads](https://img.shields.io/pypi/dm/astlab.svg)](https://pypistats.org/packages/astlab)
[![GitHub stars](https://img.shields.io/github/stars/zerlok/astlab)](https://github.com/zerlok/astlab/stargazers)

**astlab** is a Python library that provides an intuitive API for building and manipulating Abstract Syntax Trees (ASTs) to generate Python code. With **astlab**, you can easily create Python modules, classes, fields, and more using a simple and readable syntax, and convert the AST back into executable Python code.
**astlab** is a Python library that provides an intuitive API for building and manipulating Abstract Syntax Trees (ASTs) to generate Python code. With **astlab**, you can easily construct Python modules, classes, functions, type aliases, and generics using a fluent API — then render them into valid, executable Python code.

## Features

- **Easy AST construction**: Build Python code using a fluent and intuitive API.
- **Code generation**: Convert your AST into valid Python code, forget about jinja templates.
- **Supports nested scopes & auto imports**: Create nested classes, methods, and fields effortlessly. Reference types from other modules easily.
- **Highly customizable**: Extend and modify the API to suit your needs.
* **Easy AST construction**: Build Python code using a fluent, structured API.
* **Code generation**: Generate fully valid, formatted Python source without templates.
* **Supports nested scopes & auto imports**: Create classes, methods, and nested modules with automatic import resolution.
* **Type system support**: Define and use **type variables**, **generic classes**, and **type aliases** compatible with Python 3.9–3.14 syntax.
* **Highly customizable**: Extend the builder model for any Python AST use case.

## Installation

You can install **astlab** from PyPI using `pip`:

```bash
pip install astlab
```

## Usage

### Simple example

Here's a basic example of how to use **astlab** to create a Python module with a dataclass.
### Simple Example

```python
import ast
import astlab

# Create a new Python module
with astlab.module("foo") as foo:
# Build a "Bar" dataclass
with foo.class_def("Bar").dataclass() as bar:
# Define a field "spam" of type int
bar.field_def("spam", int)

# Generate and print the Python code from the AST
print(foo.render())
# Or you can just get the AST
print(ast.dump(foo.build(), indent=4))
```

#### Output

Render:

```python
import builtins
import dataclasses
Expand All @@ -60,39 +50,9 @@ class Bar:
spam: builtins.int
```

Dump built AST:
---

```python
Module(
body=[
Import(
names=[
alias(name='builtins')]),
Import(
names=[
alias(name='dataclasses')]),
ClassDef(
name='Bar',
bases=[],
keywords=[],
body=[
AnnAssign(
target=Name(id='spam'),
annotation=Attribute(
value=Name(id='builtins'),
attr='int'),
simple=1)],
decorator_list=[
Call(
func=Attribute(
value=Name(id='dataclasses'),
attr='dataclass'),
args=[],
keywords=[])])],
type_ignores=[])
```

### Func def & call example
### Function Definition & Call Example

```python
import astlab
Expand All @@ -118,7 +78,9 @@ class Bar:
return result
```

### Type reference example
---

### Type Reference Example

```python
import astlab
Expand Down Expand Up @@ -146,4 +108,158 @@ class Eggs(main.foo.Bar):

def do_stuff(self) -> typing.Optional[main.foo.Bar]:
pass
```

---

### Generics and Type Variables

**astlab** supports defining type variables and generic classes.
Both the legacy (`typing.TypeVar`) and modern (`class Node[T: int]`) syntaxes are supported depending on Python version.

#### Example

```python
import astlab

with astlab.module("generic") as mod:
with mod.class_def("Node") as node, node.type_var("T").lower(int) as T:
node.field_def("value", T)
node.field_def("parent", node.ref().type_params(type_var).optional(), mod.none())

print(mod.render())
```

#### Output (python < 3.12)

```python
import builtins
import typing

T = typing.TypeVar('T', bound=builtins.int)

class Node(typing.Generic[T]):
value: T
parent: typing.Optional['Node[T]'] = None
```

#### Output (python 3.12, 3.13)

```python
import builtins
import typing

class Node[T: builtins.int]:
value: T
parent: typing.Optional['Node[T]'] = None
```

#### Output (python ≥ 3.14)

```python
import builtins
import typing

class Node[T: builtins.int]:
value: T
parent: typing.Optional[Node[T]] = None
```

---

### Type Aliases

**astlab** allows declarative creation of type aliases, including recursive and generic aliases.
It automatically emits valid syntax for both `typing.TypeAlias` (pre-3.12) and `type X = Y` (3.12+).

#### Example

```python
import astlab
from astlab.types import predef

with astlab.module("alias") as mod:
mod.type_alias("MyInt").assign(int)

with mod.type_alias("Json") as json_alias:
json_alias.assign(
json_alias.union_type(
None,
bool,
int,
float,
str,
mod.list_type(json_alias),
mod.dict_type(str, json_alias),
)
)

with (
mod.type_alias("Nested") as nested_alias,
nested_alias.type_var("T") as T,
):
nested_alias.assign(
nested_alias.union_type(
T
nested_alias.sequence_type(nested_alias.type_params(T)),
)
)
```

#### Output (python < 3.12)

```python
import builtins
import typing

MyInt: typing.TypeAlias = builtins.int
Json: typing.TypeAlias = typing.Union[
None,
builtins.bool,
builtins.int,
builtins.float,
builtins.str,
builtins.list['Json'],
builtins.dict[builtins.str, 'Json'],
]
T = typing.TypeVar("T")
Nested: typing.TypeAlias = typing.Union[T, typing.Sequence['Nested[T]']]
```

#### Output (python 3.12, 3.13)

```python
import builtins
import typing

type MyInt = builtins.int
type Json = typing.Union[
None,
builtins.bool,
builtins.int,
builtins.float,
builtins.str,
builtins.list['Json'],
builtins.dict[builtins.str, 'Json'],
]
type Nested[T] = typing.Union[T, typing.Sequence['Nested[T]']]
```

#### Output (python ≥ 3.14)

```python
import builtins
import typing

type MyInt = builtins.int
type Json = typing.Union[
None,
builtins.bool,
builtins.int,
builtins.float,
builtins.str,
builtins.list[Json],
builtins.dict[builtins.str, Json],
]
type Nested[T] = typing.Union[T, typing.Sequence[Nested[T]]]
```
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "astlab"
version = "0.4.5"
version = "0.5.0"
description = "provides an intuitive API for building and manipulating Abstract Syntax Trees (ASTs) to generate Python code."
authors = ["zerlok <danil.troshnev@gmail.com>"]
readme = "README.md"
Expand Down
19 changes: 4 additions & 15 deletions src/astlab/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"Expr",
"Stmt",
"TypeDefinitionBuilder",
"TypeExpr",
"TypeRef",
]

Expand All @@ -31,10 +32,6 @@ class ASTExpressionBuilder(metaclass=abc.ABCMeta):
def build_expr(self) -> ast.expr:
raise NotImplementedError

@abc.abstractmethod
def build_annotation(self) -> ast.expr:
raise NotImplementedError


class ASTStatementBuilder(metaclass=abc.ABCMeta):
@abc.abstractmethod
Expand All @@ -55,21 +52,13 @@ def ref(self) -> ASTExpressionBuilder:

Expr: TypeAlias = t.Union[ast.expr, ASTExpressionBuilder]
Stmt: TypeAlias = t.Union[ast.stmt, ASTStatementBuilder, Expr]
TypeRef: TypeAlias = t.Union[
Expr,
RuntimeType,
TypeInfo,
TypeDefinitionBuilder,
]
TypeRef: TypeAlias = t.Union[RuntimeType, TypeInfo, TypeDefinitionBuilder]
TypeExpr: TypeAlias = t.Union[Expr, TypeRef]


class ASTResolver(metaclass=abc.ABCMeta):
@abc.abstractmethod
def resolve_expr(self, ref: TypeRef, *tail: str) -> ast.expr:
raise NotImplementedError

@abc.abstractmethod
def resolve_annotation(self, ref: TypeRef) -> ast.expr:
def resolve_expr(self, expr: TypeExpr, *tail: str) -> ast.expr:
raise NotImplementedError

@abc.abstractmethod
Expand Down
Loading