Skip to main content

title: Model API DSL Generator description: Describe your backend once — models, enums, endpoints — and generate a working API from it. tags: [Python, Compiler, DSL, ANTLR4, Code-Generation, Django]

🧬 Model API DSL Generator

Describe your backend once. Generate it everywhere.

Python ANTLR4 License Stars

Getting StartedDSL ReferenceExamplesArchitectureContributing


📖 Table of Contents


✨ About The Project

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.

🚀 Features

  • 📦 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

🛠️ Getting Started

Dependencies

  • Python 3.9+
  • antlr4-python3-runtime (must match the ANTLR version the parser in gen/ was generated with)
  • pipx (recommended for running this as a CLI tool)

Installing

With pipx (recommended):

pipx install model-api-dsl-generator

With pip, inside a virtualenv:

pip install model-api-dsl-generator

From 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.txt

First Program

A 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

Command Line Options

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

📚 DSL Reference

A spec file is just a sequence of model, enum, and endpoint declarations, in any order.

Comments

// a single-line comment

"""
a multi-line comment,
opening and closing on separate lines
"""

""" a multi-line comment fully on one line """

Enums

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.

Models

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.

Field Types

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

Field Annotations

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

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"}]

Endpoints

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.
  • response and input can appear in either order; input is optional.
  • input holds a raw JSON string that is not grammar-checked — malformed JSON fails at runtime, not at compile time.

Relational Algebra

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)

🧾 Examples

1. Model & Enum

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"];
}

2. Model Relations (foreign keys)

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.

3. Simple Endpoints

endpoint listUsers : GET "/users" {
    response : User;
}

endpoint sign_in : POST "/users/sign-in" {
    input : "
    {
       name : 'mamad',
       age  : 18,
       sex  : 'Male'
    }
    ";
    response : User;
}

4. Query Endpoints (relational algebra)

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/.


🏗️ Architecture

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.


🤝 Contributing

Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contribution you make is greatly appreciated.

  1. Fork the project
  2. Create your feature branch (git checkout -b feature/AmazingFeature)
  3. Commit your changes (git commit -m 'Add some AmazingFeature')
  4. Push to the branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

You can also open an issue with the enhancement tag. Don't forget to star the project ⭐

✍️ Authors

Matin Hasanali Baki GitHub · Email · Telegram

Mani Zamani GitHub · Email · Telegram

📄 License

This project is licensed under the MIT License — see LICENSE for details.

🙏 Acknowledgments

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

model_api_dsl_generator-1.0.0.tar.gz (56.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

model_api_dsl_generator-1.0.0-py3-none-any.whl (59.6 kB view details)

Uploaded Python 3

File details

Details for the file model_api_dsl_generator-1.0.0.tar.gz.

File metadata

  • Download URL: model_api_dsl_generator-1.0.0.tar.gz
  • Upload date:
  • Size: 56.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.3

File hashes

Hashes for model_api_dsl_generator-1.0.0.tar.gz
Algorithm Hash digest
SHA256 4f36c72c2a3bca9231ebb39c0f795892c741ba51e02aa2257aefe2155e2767db
MD5 3bb340bdebbe1b1dc6a0f7c0ca55287c
BLAKE2b-256 90882ee6625151248d6fa01bb42d4eaca0841ee407629e412360996b327bf49a

See more details on using hashes here.

File details

Details for the file model_api_dsl_generator-1.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for model_api_dsl_generator-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9b7d28f327e37e438388b6a035d8d549614f66e827669a1274b1d9b457c00a76
MD5 4052ede57e6fe9ffd7d94a8813ec2a0e
BLAKE2b-256 e28dbbf8e75df8afbcd7473e269a72bbb96fad45e0a28a36f536e665e62da55a

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page