| title | Model API DSL Generator | ||||||
|---|---|---|---|---|---|---|---|
| description | Describe your backend once — models, enums, endpoints — and generate a working API from it. | ||||||
| tags |
|
- About The Project
- Features
- Getting Started
- DSL Reference
- Examples
- Architecture
- Contributing
- Authors
- License
- Acknowledgments
Building a backend usually means writing the same boilerplate over and over — models, serializers, validators, routes, and hand-rolled queries — for every single project.
Model API DSL Generator skips that step. You write a small, human-readable specification describing:
- 🗂️ Models — your data schema, with field types, constraints, and relationships
- 🎭 Enums — closed sets of values, reusable across models
- 🌐 Endpoints — routes and HTTP methods, with the query logic behind them expressed directly as relational algebra
...and the compiler — an ANTLR4 grammar, a parse-tree listener, an AST, and a code generator — turns that spec into real, runnable backend code.
Currently targets Django. The compiler pipeline (grammar → AST → generator) is designed so new target frameworks can be plugged in later.
- 📦 Declarative models with types, primary keys, uniqueness, nullability, and validation rules
- 🔗 Foreign keys to express relationships between models
- 🎭 First-class enums, usable as field types
- 🌐 REST endpoints (
GET,POST,PUT,DELETE) with path parameters - 🧮 A built-in relational algebra query language for endpoint responses —
Select,Project,Join,Union,Intersection,Difference,Cartesian,Orderby,Limit,Len - 🧠 Arithmetic expressions (
+ - * /, parentheses) inside query conditions, with URL path parameters usable as variables - 📝 Raw JSON request bodies for endpoints that need custom input shapes
- 🏗️ A generated, inspectable AST (with a visualization helper) sitting between your spec and the generated code
- Python 3.9+
antlr4-python3-runtime(must match the ANTLR version the parser ingen/was generated with)pipx(recommended for running this as a CLI tool)
With pipx (recommended):
pipx install model-api-dsl-generatorWith pip, inside a virtualenv:
pip install model-api-dsl-generatorFrom source, for development:
git clone https://github.com/MatinHAB05/Model-API-DSL-Generator.git
cd Model-API-DSL-Generator
pipx install --editable .📌 See the pipx packaging guide below if you're setting this up for the first time — it walks through the exact steps to make the project installable.
Once installed, the compiler runs as a normal command — no more python -m ...:
modelapi path/to/spec.txtA minimal spec: one model, one endpoint, no foreign keys, no relational algebra.
model User {
username : String @pk @non-nullable @unique;
age : Int @nullable @valid[min=0,max=120];
}
endpoint getUsers : GET "/users" {
response : User;
}
Run it:
modelapi user_api.txt| Flag | Description | Default |
|---|---|---|
-i, --input |
Input DSL specification file path (Required) | None |
-o, --output |
Output directory name | generated_app |
--target |
Target framework for code generation from AST tree | Django |
--baseinput |
Base directory for input file | . |
--baseoutput |
Base directory for output file | . |
--generate |
Enable Code Generator | True |
--astimg |
Show AST visualization image after parsing | False |
--version |
Show program's version number and exit | None |
-h, --help |
Show help message and exit | None |
A spec file is just a sequence of model, enum, and endpoint declarations, in any order.
// a single-line comment
"""
a multi-line comment,
opening and closing on separate lines
"""
""" a multi-line comment fully on one line """
enum Role {
"ADMIN",
"USER",
"GUEST",
"MANAGER"
}
Enum values are just literals (usually strings). Once declared, an enum can be used as a field type in any model.
model Person {
username : String @pk @non-nullable @unique @valid[wildpattern="...[a-z]"];
age : Int @nullable @valid[min=8,max=14];
role : Role @non-nullable @valid[exclude={"ADMIN"}];
bth : Date @valid[min="2020-01-01", max="2024-06-11"];
}
Each field is name : type followed by zero or more @annotations.
| Type | Meaning |
|---|---|
String |
Text |
Int |
Integer |
Double |
Floating point number |
Date |
Calendar date |
Time |
Time of day |
DateTime |
Date + time |
<EnumName> |
Any enum declared elsewhere in the spec |
| Annotation | Meaning |
|---|---|
@pk |
Marks the field as (part of) the primary key |
@unique |
Enforces uniqueness |
@nullable / @non-nullable |
Whether the field accepts null |
@foreign-key(Model.field) |
References another model's field |
@valid[...] |
Attaches one or more validation rules |
Used inside @valid[...], comma-separated:
| Rule | Applies to | Example |
|---|---|---|
min=, max= |
numeric, date, or time bounds | @valid[min=8,max=14] |
wildpattern="..." |
string pattern matching | @valid[wildpattern="...[a-z]"] |
include={...} |
allow-list of values | @valid[include={"USER","MANAGER"}] |
exclude={...} |
deny-list of values | @valid[exclude={"ADMIN"}] |
endpoint <name> : <GET|POST|PUT|DELETE> "<path>" {
response : <ModelName> | relational { ... };
input : "<raw json>"; // optional
}
- Path parameters written as
{x}in the URL (e.g."/users/{x}/{y}") become variables you can reference inside the endpoint body — including inside relational-algebra conditions and arithmetic. responseandinputcan appear in either order;inputis optional.inputholds a raw JSON string that is not grammar-checked — malformed JSON fails at runtime, not at compile time.
Instead of hand-writing queries, an endpoint's response can be a relational { ... } block: a small sequence of named steps followed by a final -> ...; statement saying what to return.
relational {
step_1 = <expression>;
step_2 = <expression>;
-> <final expression>;
}
Built-in functions:
| Function | Signature | Purpose |
|---|---|---|
Select |
Select<field OP value, ...>(expr) |
Filter rows |
Project |
Project<field, ...>(expr) |
Pick columns |
Join_inner / Join_outter / Join_left / Join_right |
Join_x<field1,field2>(exprA, exprB) |
Join two relations |
Union / Intersection / Difference / Cartesian |
Fn(exprA, exprB) |
Set operations |
Orderby |
Orderby(expr, True|False) |
Sort ascending/descending |
Limit |
Limit<start,length,step>(expr) |
Slice/paginate a relation |
Len |
Len(expr) |
Row count |
Comparison operators for Select conditions: eq, lst (less than), grt (greater than), lsteq, grteq, and their negations not-eq, not-lst, not-grt, not-lsteq, not-grteq.
Arithmetic (+ - * /, with parentheses and standard precedence) can be used freely inside conditions, and combined with path parameters:
Select<age grt 18*x-(y/z)>(User)
enum Role {
"ADMIN",
"USER",
"GUEST",
"MANAGER"
}
model Person {
username : String @pk @non-nullable @unique @valid[wildpattern="...[a-z]"];
age : Int @nullable @valid[min=8,max=14];
role : Role @non-nullable @valid[exclude={"ADMIN"}];
second_role : Role @non-nullable @valid[include={"MANAGER","USER"}];
bth : Date @valid[min="2020-01-01", max="2024-06-11"];
}
model Attendance {
username : String @pk @foreign-key(Person.username);
entryTime : Time @valid[min="00:00", max="12:00"];
}
model Phone {
id : String @pk @foreign-key(Person.username);
phone : String @pk @unique @valid[wildpattern="..."];
}
Each Attendance and Phone row points back to a Person through its username — a one-to-many relationship expressed with a single annotation.
endpoint listUsers : GET "/users" {
response : User;
}
endpoint sign_in : POST "/users/sign-in" {
input : "
{
name : 'mamad',
age : 18,
sex : 'Male'
}
";
response : User;
}
endpoint first_User : GET "/users/{x}/{y}" {
response : relational {
r_1 = Select<name eq y, age lst x>(User);
r_2 = Project<name,lastname>(r_1);
r_3 = Select<>(Person);
r_4 = Join_inner<name,username>(r_2,r_3);
r_5 = Project<name,lastname,bth>(r_4);
len_temp = Len(r_5);
r_6 = Limit<1,len_temp,3>(r_5);
-> r_6;
};
}
Functions nest and combine freely:
endpoint sort_user : GET "/users/first/{x}-{y}-{z}" {
response : relational {
r1 = User;
r2 = Animals;
r10 = Union(r1,r2);
r20 = Cartesian(r1,r2);
r30 = Intersection(r1,r2);
r40 = Difference(r1,r2);
-> Orderby(Union(Cartesian(r1,r2),Difference(r10,r20)), False);
};
}
More annotated examples live in test_grammer_files/.
backendgrammer.g4
│ (ANTLR4)
▼
gen/ — generated Lexer, Parser, Listener, Visitor
│
▼
CustomListner_ast_tree.py — walks the parse tree
│
▼
ast_tree.py / ast_tree_node_info.py — the AST
│
▼
django_code_generator.py — emits framework code
│
▼
Generated Django project
helper_functions/ holds supporting tooling used along the way — debug.py and visualzation_ast.py for inspecting the AST while developing, and handling_build_ast_nodes_in_Listner.py for the listener's node-building logic.
Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contribution you make is greatly appreciated.
- Fork the project
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
You can also open an issue with the enhancement tag. Don't forget to star the project ⭐
Matin Hasanali Baki GitHub · Email · Telegram
Mani Zamani GitHub · Email · Telegram
This project is licensed under the MIT License — see LICENSE for details.
- ANTLR4 Official Documentation — Reference manual and documentation for the ANTLR4 parser generator
- ANTLR4 GitHub Repository — Official source repository and Python3 runtime library powering
backendgrammer.g4 - Domain-Specific Language (DSL) — Wikipedia — Conceptual overview of domain-specific languages and declarative design
- Compiler — Wikipedia — Theoretical background on parsing, AST generation, and code emission
- Graphviz Graph Visualization Software — Rendering engine used in
- DSL Specification Examples — Internal suite of test files demonstrating models, enums, relational algebra, and endpoints