skdsl-py
is a utility to translate a domain-specific language (DSL) for API descriptions into Python FastAPI code. It helps in rapidly prototyping and generating the boilerplate for FastAPI applications based on a simple, text-based API contract.
This tool is inspired by its Rust counterpart SK DSL and aims to provide similar functionality for the Python ecosystem, specifically targeting FastAPI for web framework generation and Pydantic for data validation.
- Simple DSL: Define your API contract using a straightforward, line-based DSL.
- Code Generation: Automatically generates FastAPI routers, Pydantic models (or type hints), and endpoint function shells.
- Type Definitions: Support for defining custom data types, which can be translated to Pydantic models or Python type aliases.
- Complex Requirements: Define reusable sets of parameters (like headers or cookies) to apply across multiple endpoints or entire API tags.
- OpenAPI Integration: Generated FastAPI code automatically benefits from OpenAPI schema generation (
include_in_schema
for hidden elements). - Versioned Output: Organizes generated code into versioned directories.
- CLI Interface: Easy-to-use command-line options for specifying input, output, versioning, and regeneration.
The skdsl-py
client parses a text input file, processing lines that begin with specific keywords: type
, req
, or api
.
Define types used in your API. These can be aliases to standard Python types/Pydantic models or hints for generating new Pydantic models.
- Import-like usage:
# Becomes: from your_project.custom_types import MyCustomType type MyCustomType your_project.custom_types.MyCustomType
# Becomes: from your_project.models import ComplexRequestModel as MyType type MyType your_project.models.ComplexRequestModel
- Alias to Python/Pydantic types:
# Becomes: MyTypeList = List[int] (in Python with typing.List) type MyTypeList Vec<i32>
(The translator will map DSL primitive types like# Becomes: StringKeyMap = Dict[str, float] (in Python with typing.Dict) type StringKeyMap HashMap<String, f64>
i32
,String
,Vec
,HashMap
to their Python equivalents:int
,str
,List
,Dict
).
Define incoming request parameters and outgoing response characteristics.
Incoming Requirements:
- HTTP Method & Path:
<get|post|put|patch|delete>/<path_segment>[/{type/param_name}|/{**rest_param_name}]...
- Example:
get/users/{u64/id}
- Example:
post/files/upload/{**filepath}
- Note: Only one HTTP method/path can be specified per endpoint.
- Example:
- Request Header:
h/<type>/<name>
(e.g.,h/String/X-API-Key
) - Request Cookie:
c/<key>
(e.g.,c/session_id
) - Request Query Parameter:
q/<type>/<key>
(e.g.,q/i32/page_number
) - Request Body:
b/json/<TypeName>
(e.g.,b/json/UserData
-UserData
should be a defined Pydantic model).b/file/<form_key>
(e.g.,b/file/upload
- for file uploads).
- Request Form Key:
f/<type>/<key>
(e.g.,f/String/username
- for form data). - Note: You cannot use a general request body (
b/...
) and form keys (f/...
) in the same endpoint.
Outgoing Requirements:
- Response Body:
ok
(HTTP 200/204 with no specific body content, or a simple success message).b/plain
(Plain text response).b/html
(HTML response).b/file
(File download response).b/json/<TypeName>
(e.g.,b/json/UserProfile
-UserProfile
should be a Pydantic model).
- Response Header:
h/<type>/<name>
(e.g.,h/String/X-Request-ID
). - Response Cookie:
c/<key>
(e.g.,c/tracking_cookie
).
- Define an API Tag:
All endpoints listed after this line will be grouped under this tag. This typically translates to a FastAPI
api tag <tag_name> [req/<requirement_name>...]
APIRouter
and a Python file named{tag_name}.py
. - Define an Endpoint:
Example:
api [req/<requirement_name>...] <incoming_http_method_and_path> [other_incoming_requirements...] -> <outgoing_response_body> [other_outgoing_requirements...]
api tag chat api get/chats q/i64/offset q/i32/limit -> b/json/Vec<ChatData> api get/chat/{u64/id} -> b/json/ChatData api post/chat/{str/room_id}/message b/json/MessagePayload -> ok h/String/X-Message-ID
Define reusable sets of incoming and/or outgoing requirements (excluding body, form, or path parameters).
req <requirement_name> <incoming_headers_cookies_queries...> [-> <outgoing_headers_cookies...>]
Example:
req auth_tokens h/String/X-Auth-Token c/session_token -> h/String/X-Session-Status
Use them in API tags or individual endpoints:
api tag user_profiles req/auth_tokens
api req/auth_tokens get/users/me -> b/json/UserPrivateProfile
api tag public_data
api get/items -> b/json/Vec<Item>
# Endpoint-specific complex requirement
api req/pagination_params get/items_paged q/i32/custom_filter -> b/json/PagedItems
5. Hidden APIs and Requirements
- Hidden API Endpoint:
api/hidden ...
- Excludes the endpoint from the generated OpenAPI specification (FastAPI's
include_in_schema=False
).
- Excludes the endpoint from the generated OpenAPI specification (FastAPI's
- Hidden Complex Requirement:
req/hidden <requirement_name> ...
- Defines requirements primarily for OpenAPI documentation without necessarily enforcing them as strict, usable parameters in the generated function signature if they are not directly referenced. The exact behavior might depend on the translator's implementation for parameter generation.
-
Prerequisites:
- Python 3.8+
pip
-
Setup:
git clone <repository_url_for_skdsl-py> cd skdsl-py pip install . # or for development # pip install -e .
(If it's a single script, you might run it directly:
python skdsl_py/main.py ...
)
The translator is used via the skdsl-py
command-line interface.
Usage: skdsl-py [OPTIONS] --input <FILE> --output <FOLDER>
Options:
-i, --input <FILE> Input DSL file
-o, --output <FOLDER> Output folder for generated FastAPI code
-v, --version <VERSION> API version (e.g., v1, v2). Optional.
-r, --regenerate Don't attempt to bump the API version automatically;
instead, use the specified version (or latest/default)
and rewrite all generated files.
-h, --help Print help
Examples:
-
Generate API version
v1
into thegenerated_api
folder:skdsl-py -i my_api_v1.dsl -o generated_api
-
Manually specify API version
v2
using a different DSL file:skdsl-py -i my_api_v2.dsl -o generated_api -v v2
-
Regenerate API version
v1
from scratch, overwriting existing files:skdsl-py -i my_api_v1.dsl -o generated_api -v v1 -r
skdsl-py
generates a directory structure for your FastAPI application:
<output_folder>/
└──v1/ # API version
├── __init__.py # Makes the version folder a Python package
├── models.py # Pydantic models and type aliases from 'type' definitions
├── main_app.py # Main FastAPI app for this version, includes all routers
├── users.py # FastAPI router for 'users' tag
├── chats.py # FastAPI router for 'chats' tag
└── files.py # FastAPI router for 'files' tag
└──v2/
├── ...
└──.api.json # (Potentially per version) Serialized DSL for version tracking
{tag_name}.py
: Contains a FastAPIAPIRouter
with endpoints defined under that tag.models.py
: Contains Pydantic model definitions and Python type aliases derived fromtype
directives in the DSL.main_app.py
(or similar): A central FastAPI application file that includes all the generated routers for that specific API version.__init__.py
: Standard Python file to mark directories as packages.
The original skdsl
tool has a mechanism to detect breaking changes and suggest version bumps. For non-breaking changes, you can generally:
- Add new API tags or new endpoints.
- Modify existing endpoints by:
- Removing incoming requirements (making them optional or removing them).
- Adding new outgoing requirements (e.g., a new optional response header).
The Python version aims for similar considerations, though automatic breaking change detection might be implemented progressively. Using the -r
(regenerate) flag allows for explicit control over file generation.