REST API Generator

BESSER offers a code generator for REST API utilizing the FastAPI framework. This tool automatically transforms classes and relationships defined in a Structural model into a RESTful service.

Let’s generate the code for the REST model of our Structural model example structural model example. You should create a RESTAPIGenerator object, provide the Structural model, and use the generate method as follows:

from besser.generators.rest_api import RESTAPIGenerator

rest_api = RESTAPIGenerator(model=library_model, http_methods=["GET", "POST", "PUT", "PATCH", "DELETE"], backend=False)
rest_api.generate()

Parameters

  • model: The input B-UML structural model (required).

  • http_methods: List of HTTP methods to generate (default: ["GET", "POST", "PUT", "PATCH", "DELETE"]).

  • backend: When True, generates main_api.py suitable for BackendGenerator integration instead of standalone rest_api.py (default: False).

  • port: Port number for the generated FastAPI app (optional).

  • output_dir: Output directory (default: output/ in the current directory).

Note

REST API vs. Backend Generator: The REST API generator produces API endpoints and Pydantic models only. The Backend generator builds on top of this by also generating SQLAlchemy ORM models and database setup. Use the REST API generator when you only need API scaffolding; use the Backend generator when you need a complete backend with database.

Output Files

The generator creates two files in the output directory:

  • rest_api.py (or main_api.py when backend=True): FastAPI application with CRUD endpoints for each class.

  • pydantic_classes.py: Pydantic validation models generated by the Pydantic generator.

The generated output for the library example is shown below.

  1import os, json
  2from fastapi import FastAPI, HTTPException
  3from pydantic_classes import *
  4
  5app = FastAPI()
  6
  7############################################
  8#
  9# Lists to store the data (json)
 10#
 11############################################
 12
 13book_list = []
 14author_list = []
 15library_list = []
 16
 17
 18############################################
 19#
 20#   Book functions
 21#
 22############################################
 23@app.get("/book/", response_model=List[Book], tags=["book"])
 24def get_book():
 25    return book_list
 26
 27@app.get("/book/{attribute_id}/", response_model=Book, tags=["book"])
 28def get_book(attribute_id : str):   
 29    for book in book_list:
 30        if book.id_to_change== attribute_id:
 31            return book
 32    raise HTTPException(status_code=404, detail="Book not found")
 33
 34@app.post("/book/", response_model=Book, tags=["book"])
 35def create_book(book: Book):
 36    book_list.append(book)
 37    return book
 38
 39@app.put("/book/{attribute_id}/", response_model=Book, tags=["book"]) 
 40def change_book(attribute_id : str, updated_book: Book): 
 41    for index, book in enumerate(book_list): 
 42        if book.id_to_change == attribute_id:
 43            book_list[index] = updated_book
 44            return updated_book
 45    raise HTTPException(status_code=404, detail="Book not found")
 46
 47@app.patch("/book/{attribute_id}/{attribute_to_change}", response_model=Book, tags=["book"])
 48def update_book(attribute_id : str,  attribute_to_change: str, updated_data: str):
 49    for book in book_list:
 50        if book.id_to_change == attribute_id:
 51            if hasattr(book, attribute_to_change):
 52                setattr(book, attribute_to_change, updated_data)
 53                return book
 54            else:
 55                raise HTTPException(status_code=400, detail=f"Attribute '{attribute_to_change}' does not exist")
 56    raise HTTPException(status_code=404, detail="Book not found")
 57
 58@app.delete("/book/{attribute_id}/", tags=["book"])
 59def delete_book(attribute_id : str):   
 60    for index, book in enumerate(book_list):
 61        if book.id_to_change == attribute_id:
 62            book_list.pop(index)
 63            return {"message": "Item deleted successfully"}
 64    raise HTTPException(status_code=404, detail="Book not found") 
 65
 66############################################
 67#
 68#   Author functions
 69#
 70############################################
 71@app.get("/author/", response_model=List[Author], tags=["author"])
 72def get_author():
 73    return author_list
 74
 75@app.get("/author/{attribute_id}/", response_model=Author, tags=["author"])
 76def get_author(attribute_id : str):   
 77    for author in author_list:
 78        if author.id_to_change== attribute_id:
 79            return author
 80    raise HTTPException(status_code=404, detail="Author not found")
 81
 82@app.post("/author/", response_model=Author, tags=["author"])
 83def create_author(author: Author):
 84    author_list.append(author)
 85    return author
 86
 87@app.put("/author/{attribute_id}/", response_model=Author, tags=["author"]) 
 88def change_author(attribute_id : str, updated_author: Author): 
 89    for index, author in enumerate(author_list): 
 90        if author.id_to_change == attribute_id:
 91            author_list[index] = updated_author
 92            return updated_author
 93    raise HTTPException(status_code=404, detail="Author not found")
 94
 95@app.patch("/author/{attribute_id}/{attribute_to_change}", response_model=Author, tags=["author"])
 96def update_author(attribute_id : str,  attribute_to_change: str, updated_data: str):
 97    for author in author_list:
 98        if author.id_to_change == attribute_id:
 99            if hasattr(author, attribute_to_change):
100                setattr(author, attribute_to_change, updated_data)
101                return author
102            else:
103                raise HTTPException(status_code=400, detail=f"Attribute '{attribute_to_change}' does not exist")
104    raise HTTPException(status_code=404, detail="Author not found")
105
106@app.delete("/author/{attribute_id}/", tags=["author"])
107def delete_author(attribute_id : str):   
108    for index, author in enumerate(author_list):
109        if author.id_to_change == attribute_id:
110            author_list.pop(index)
111            return {"message": "Item deleted successfully"}
112    raise HTTPException(status_code=404, detail="Author not found") 
113
114############################################
115#
116#   Library functions
117#
118############################################
119@app.get("/library/", response_model=List[Library], tags=["library"])
120def get_library():
121    return library_list
122
123@app.get("/library/{attribute_id}/", response_model=Library, tags=["library"])
124def get_library(attribute_id : str):   
125    for library in library_list:
126        if library.id_to_change== attribute_id:
127            return library
128    raise HTTPException(status_code=404, detail="Library not found")
129
130@app.post("/library/", response_model=Library, tags=["library"])
131def create_library(library: Library):
132    library_list.append(library)
133    return library
134
135@app.put("/library/{attribute_id}/", response_model=Library, tags=["library"]) 
136def change_library(attribute_id : str, updated_library: Library): 
137    for index, library in enumerate(library_list): 
138        if library.id_to_change == attribute_id:
139            library_list[index] = updated_library
140            return updated_library
141    raise HTTPException(status_code=404, detail="Library not found")
142
143@app.patch("/library/{attribute_id}/{attribute_to_change}", response_model=Library, tags=["library"])
144def update_library(attribute_id : str,  attribute_to_change: str, updated_data: str):
145    for library in library_list:
146        if library.id_to_change == attribute_id:
147            if hasattr(library, attribute_to_change):
148                setattr(library, attribute_to_change, updated_data)
149                return library
150            else:
151                raise HTTPException(status_code=400, detail=f"Attribute '{attribute_to_change}' does not exist")
152    raise HTTPException(status_code=404, detail="Library not found")
153
154@app.delete("/library/{attribute_id}/", tags=["library"])
155def delete_library(attribute_id : str):   
156    for index, library in enumerate(library_list):
157        if library.id_to_change == attribute_id:
158            library_list.pop(index)
159            return {"message": "Item deleted successfully"}
160    raise HTTPException(status_code=404, detail="Library not found") 
161
162
163
164############################################
165# Maintaining the server
166############################################
167if __name__ == "__main__":
168    import uvicorn
169    openapi_schema = app.openapi()
170    output_dir = os.path.join(os.getcwd(), 'output')
171    os.makedirs(output_dir, exist_ok=True)
172    output_file = os.path.join(output_dir, 'openapi_specs.json')
173    print(f"Writing OpenAPI schema to {output_file}")
174    with open(output_file, 'w') as file:
175        json.dump(openapi_schema, file)
176    uvicorn.run(app, host="0.0.0.0", port=8000)
177
178
179

When you run the code generated, the OpenAPI specifications will be generated:

{
"openapi": "3.1.0",
"info": {
    "title": "FastAPI",
    "version": "0.1.0"
},
"paths": {
    "/author/": {
        "get": {
            "tags": [
                "author"
            ],
            "summary": "Get Author",
            "operationId": "get_author_author__get",
            "responses": {
                "200": {
                    "description": "Successful Response",
                    "content": {
                        "application/json": {
                            "schema": {
                                "items": {
                                    "$ref": "#/components/schemas/Author"
                                },
                                "type": "array",
                                "title": "Response Get Author Author Get"
                            }}}}}}}}}

Note

The full OpenAPI specification is available at http://localhost:8000/docs when you run the generated FastAPI application with uvicorn rest_api:app.