Hide navigation sidebar
Hide table of contents sidebar
Skip to content
Back to top
Toggle table of contents sidebar

# Source code for betty.openapi

    """
    Provide the OpenAPI specification.
    """

    from betty import about, model
    from betty.locale.localizer import DEFAULT_LOCALIZER
    from betty.model import UserFacingEntity
    from betty.project import Project
    from betty.serde.dump import DumpMapping, Dump
    from betty.string import kebab_case_to_lower_camel_case

    class Specification:
        """
        Build OpenAPI specifications.
        """

        def __init__(self, project: Project):
            self._project = project

        async def build(self) -> DumpMapping[Dump]:
            """
            Build the OpenAPI specification.
            """
            specification: DumpMapping[Dump] = {
                "openapi": "3.1.0",
                "servers": [
                    {
                        "url": self._project.static_url_generator.generate(
                            "/", absolute=True
                        ),
                    }
                ],
                "info": {
                    "title": "Betty",
                    "version": await about.version_label(),
                },
                "paths": {},
                "components": {
                    "responses": {
                        "401": {
                            "description": "Unauthorized",
                            "content": {
                                "application/json": {
                                    "schema": {
                                        "$ref": "#/components/schemas/betty/response/error",
                                    },
                                },
                            },
                        },
                        "403": {
                            "description": "Forbidden",
                            "content": {
                                "application/json": {
                                    "schema": {
                                        "$ref": "#/components/schemas/betty/response/error",
                                    },
                                },
                            },
                        },
                        "404": {
                            "description": "Not found",
                            "content": {
                                "application/json": {
                                    "schema": {
                                        "$ref": "#/components/schemas/betty/response/error",
                                    },
                                },
                            },
                        },
                    },
                    "parameters": {
                        "id": {
                            "name": "id",
                            "in": "path",
                            "required": True,
                            "description": "The ID for the resource to retrieve.",
                            "schema": {
                                "type": "string",
                            },
                        },
                    },
                    "schemas": {
                        "betty": {
                            "$ref": self._project.static_url_generator.generate(
                                "/schema.json#/definitions"
                            ),
                        },
                    },
                },
            }

            # Add entity operations.
            for entity_type in await model.ENTITY_TYPE_REPOSITORY.select(UserFacingEntity):
                if self._project.configuration.clean_urls:
                    collection_path = f"/{entity_type.plugin_id()}/"
                    single_path = f"/{entity_type.plugin_id()}/{{id}}/"
                else:
                    collection_path = f"/{entity_type.plugin_id()}/index.json"
                    single_path = f"/{entity_type.plugin_id()}/{{id}}/index.json"
                entity_type_label = entity_type.plugin_label().localize(DEFAULT_LOCALIZER)
                specification["paths"].update(  # type: ignore[union-attr]
                    {
                        collection_path: {
                            "get": {
                                "summary": f"Retrieve the collection of {entity_type_label} entities.",
                                "responses": {
                                    "200": {
                                        "description": f"The collection of {entity_type_label} entities.",
                                        "content": {
                                            "application/json": {
                                                "schema": {
                                                    "$ref": f"#/components/schemas/betty/response/{kebab_case_to_lower_camel_case(entity_type.plugin_id())}Collection",
                                                },
                                            },
                                        },
                                    },
                                },
                            },
                        },
                        single_path: {
                            "get": {
                                "summary": f"Retrieve a single {entity_type_label} entity.",
                                "responses": {
                                    "200": {
                                        "description": f"The {entity_type_label} entity.",
                                        "content": {
                                            "application/json": {
                                                "schema": {
                                                    "$ref": f"#/components/schemas/betty/entity/{kebab_case_to_lower_camel_case(entity_type.plugin_id())}",
                                                },
                                            },
                                        },
                                    },
                                },
                            },
                        },
                    }
                )

            # Add default behavior to all requests.
            for path in specification["paths"]:  # type: ignore[union-attr]
                specification["paths"][path]["get"]["responses"].update(  # type: ignore[call-overload, index, union-attr]
                    {
                        "401": {
                            "$ref": "#/components/responses/401",
                        },
                        "403": {
                            "$ref": "#/components/responses/403",
                        },
                        "404": {
                            "$ref": "#/components/responses/404",
                        },
                    }
                )

            return specification