flask-openapi

repository·main·Indexed 42 days ago

https://github.com/luolingchun/flask-openapi

A lightweight web API framework built on top of Flask that leverages Pydantic for data validation and automatically generates interactive OpenAPI-compliant documentation. It supports class-based API views via APIView, modular organization with APIBlueprint, and optional async support. It provides multiple UI plugin options including Swagger, ReDoc, Rapidoc, Scalar, and Elements.

Tokens
22.1K
Snippets
81
Records
97
Agent score
53%

What's inside flask-openapi

  1. Extend OpenAPI Parameter Objects using Pydantic Field

    main

    You can extend the OpenAPI Parameter Object by using the json_schema_extra argument within Pydantic's Field. This allows you to add metadata like example or deprecated to your generated documentation.

    Example usage:

    • json_schema_extra={'example': 3}
    • json_schema_extra={'deprecated': True}
    from pydantic import BaseModel, Field
    
    class BookQuery(BaseModel):
        # Adds an example to the OpenAPI spec
        age: int = Field(..., ge=2, le=4, description='Age', json_schema_extra={"example": 3})
        
        # Marks the field as deprecated in the OpenAPI spec
        author: str = Field(None, description='Author', json_schema_extra={"deprecated": True})
  2. Use APIBlueprint to define OpenAPI-compliant routes

    main

    The flask_openapi.APIBlueprint class is the primary mechanism for defining API routes that are automatically documented using the OpenAPI specification. Instead of using standard Flask Blueprint objects, you use APIBlueprint to associate metadata (like descriptions, parameters, and response schemas) directly with your route definitions. This ensures that your Flask application's routing logic and its OpenAPI documentation remain synchronized.

    from flask_openapi import APIBlueprint
    
    # Create an APIBlueprint instead of a standard Flask Blueprint
    api_bp = APIBlueprint("my_api", __name__)
    
    @api_bp.route("/hello", methods=["GET"])
    def hello():
        return {"message": "world"}
  3. Customize Operation ID

    main

    The operation_id is used to uniquely identify an API operation. By default, it is generated automatically. You can set it manually via the operation_id parameter or globally using an operation_id_callback in the OpenAPI, APIBlueprint, or APIView constructor.

    # Manual setting
    @app.get('/book/<int:bid>', operation_id="get_book_id")
    def get_book(path: BookPath):
        ...
    
    # Custom callback for all operations in a Blueprint
    def get_operation_id_for_path(*, bp_name: str = None, name: str, path: str, method: str) -> str:
        return name
    
    api = APIBlueprint('book', __name__, operation_id_callback=get_operation_id_for_path)
  4. Nest APIBlueprints

    main

    You can nest APIBlueprint instances within one another. An APIBlueprint can register another APIBlueprint using its own register_api method, allowing for hierarchical route organization.

    from flask_openapi import OpenAPI, APIBlueprint
    
    app = OpenAPI(__name__)
    
    api = APIBlueprint('book', __name__, url_prefix='/api/book')
    api_english = APIBlueprint('english', __name__)
    api_chinese = APIBlueprint('chinese', __name__)
    
    @api_english.post('/english')
    def create_english_book():
        return {"message": "english"}
    
    @api_chinese.post('/chinese')
    def create_chinese_book():
        return {"message": "chinese"}
    
    # Register nested blueprints to the parent blueprint
    api.register_api(api_english)
    api.register_api(api_chinese)
    
    # Register the parent blueprint to the app
    app.register_api(api)
    
    if __name__ == '__main__':
        app.run(debug=True)
  5. Configure Security Schemes (JWT, API Key, OAuth2, Basic)

    main

    To secure your endpoints, you must follow a two-step process:

    1. Define security_schemes and security in the OpenAPI constructor. The security_schemes dictionary defines the mechanism, and the security list defines which schemes are applied globally or used as references.
    2. Apply security to routes by passing the security variable to the route decorator.

    Example: JWT Bearer Authentication

    jwt = {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT"
    }
    security_schemes = {"jwt": jwt}
    security = [{"jwt": []}]
    
    app = OpenAPI(__name__, info=info, security_schemes=security_schemes)
    
    @app.get('/book/<int:bid>', security=security)
    def get_book(path: Path, query: BookBody):
        ...
    # JWT Bearer Sample
    jwt = {
      "type": "http",
      "scheme": "bearer",
      "bearerFormat": "JWT"
    }
    
    # API Key Sample
    api_key = {
      "type": "apiKey",
      "name": "api_key",
      "in": "header"
    }
    
    # Implicit OAuth2 Sample
    oauth2 = {
      "type": "oauth2",
      "flows": {
        "implicit": {
          "authorizationUrl": "https://example.com/api/oauth/dialog",
          "scopes": {
            "write:pets": "modify pets in your account",
            "read:pets": "read your pets"
          }
        }
      }
    }
    
    # Basic Authentication Sample
    basic = {
      "type": "http",
      "scheme": "basic"
    }
    
    security_schemes = {"jwt": jwt, "api_key": api_key, "oauth2": oauth2, "basic": basic}
  6. Add Summary and Description to APIs

    main

    Documentation for an API can be provided in two ways:

    1. Docstrings: The first line of the view function's docstring is treated as the summary, and the subsequent lines are the description.
    2. Keyword Arguments: You can explicitly pass summary and description to the route decorator. If provided, these keyword arguments take precedence over the docstring.
    # Using docstrings
    @app.get('/book/<int:bid>')
    def get_book(path: BookPath):
        """Get book
        Get some book by id
        """
        return {}
    
    # Using keyword arguments (takes precedence)
    @app.get('/book/<int:bid>', summary="new summary", description='new description')
    def get_book(path: BookPath):
        """Get book
        This docstring will be ignored for summary/description
        """
        return {}
  7. Use APIBlueprint for modular routing

    main

    For organizing your application into modules, use APIBlueprint (based on Flask Blueprints). Note that you must use app.register_api(api) instead of the standard Flask register_blueprint method to register an APIBlueprint.

    from flask_openapi import OpenAPI, APIBlueprint
    
    app = OpenAPI(__name__)
    
    api = APIBlueprint('book', __name__, url_prefix='/api')
    
    @api.post('/book')
    def create_book():
        return {"message": "success"}
    
    # Use register_api instead of register_blueprint
    app.register_api(api)
    
    if __name__ == '__main__':
        app.run()
  8. Disable field aliases in Pydantic models using by_alias

    main

    When using Pydantic BaseModel within flask-openapi, you might want to use the original field names instead of their defined aliases (for example, when generating API responses). You can control this behavior by setting by_alias=False in the model_config dictionary of your model class.

    class MessageResponse(BaseModel):
        message: str = Field(..., description="The message")
        metadata: dict[str, str] = Field(alias="metadata_")
    
        model_config = dict(
            by_alias=False
        )
  9. Use APIBlueprint for modular API organization

    main

    The APIBlueprint class allows you to group related routes, tags, security requirements, and responses into a modular unit. This is useful for large projects. After defining the blueprint, you must register it with the main application using app.register_api(blueprint).

    from flask_openapi import APIBlueprint
    
    api = APIBlueprint(
        "/book",
        __name__,
        url_prefix="/api",
        abp_tags=[tag],
        abp_security=security,
        abp_responses={"401": Unauthorized}
    )
    
    @api.get("/list")
    def list_books():
        return {"code": 0, "message": "ok"}
    
    # Register with the main app
    app.register_api(api)
  10. Use Class-based API Views with APIView

    main

    For more complex routing, use APIView to group related endpoints under a common url_prefix and view_tags.

    1. Create an APIView instance.
    2. Define classes for your logic.
    3. Use @api_view.route("/path") to register the class.
    4. Use @api_view.doc(...) on methods within the class to provide OpenAPI metadata (like summary or deprecated status).
    5. Register the view with the app using app.register_api_view(api_view).

    This approach allows for shared state within the view class and cleaner organization of RESTful resources.

    from pydantic import BaseModel, Field
    from flask_openapi import OpenAPI, Tag, Info, APIView
    
    info = Info(title="book API", version="1.0.0")
    app = OpenAPI(__name__, info=info)
    
    api_view = APIView(url_prefix="/api/v1", view_tags=[Tag(name="book")])
    
    class BookPath(BaseModel):
        id: int = Field(..., description="book ID")
    
    class BookQuery(BaseModel):
        age: int | None = Field(None, description="Age")
    
    class BookBody(BaseModel):
        age: int | None = Field(..., ge=2, le=4, description="Age")
        author: str = Field(None, min_length=2, max_length=4, description="Author")
    
    @api_view.route("/book")
    class BookListAPIView:
        a = 1
    
        @api_view.doc(summary="get book list")
        def get(self, query: BookQuery):
            return query.model_dump_json()
    
        @api_view.doc(summary="create book")
        def post(self, body: BookBody):
            return body.model_dump_json()
    
    @api_view.route("/book/<id>")
    class BookAPIView:
        @api_view.doc(summary="get book")
        def get(self, path: BookPath):
            return "get"
    
        @api_view.doc(summary="update book")
        def put(self, path: BookPath):
            return "put"
    
        @api_view.doc(summary="delete book", deprecated=True)
        def delete(self, path: BookPath):
            return "delete"
    
    app.register_api_view(api_view)
    
    if __name__ == "__main__":
        app.run(debug=True)
  11. Define asynchronous APIs

    main

    To define asynchronous endpoints, use the async def syntax.

    Requirement: You must manually install asgiref to support async functionality.

    pip install flask-openapi[async]
    
    # or
    pip install asgiref
    @app.post('/open/api')
    async def post_openapi(body: Query):
        print(body)
        return 'POST, OpenAPI!'
  12. Use `orjson` as the JSON provider

    main

    To improve JSON serialization performance, you can replace the default Flask JSON provider with a custom OrJSONProvider using the orjson library. This involves subclassing flask.json.provider.JSONProvider, implementing dumps and loads, and assigning the provider instance to app.json.

    import orjson
    from flask.json.provider import JSONProvider
    
    class OrJSONProvider(JSONProvider):
        # https://github.com/ijl/orjson#option
        option = orjson.OPT_INDENT_2
    
        def dumps(self, obj, **kwargs):
            return orjson.dumps(obj, option=self.option).decode()
    
        def loads(self, s, **kwargs):
            return orjson.loads(s)
        
    app = OpenAPI(__name__, info=info)
    # use orjson
    orjson_provider = OrJSONProvider(app)
    app.json = orjson_provider