Skip to content

Repository files navigation

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

About

Backend DSL Compiler is a domain-specific language and code generation tool that lets you define your data models, enums, relationships, and REST endpoints in a simple, human-readable syntax. Write your backend structure once, and let the compiler generate clean, framework-specific code automatically.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages