openapi-to-fastapi


Nameopenapi-to-fastapi JSON
Version 0.15.0 PyPI version JSON
download
home_pagehttps://github.com/ioxiocom/openapi-to-fastapi
SummaryCreate FastAPI routes from OpenAPI spec
upload_time2024-04-08 08:14:07
maintainerNone
docs_urlNone
authorIOXIO Ltd
requires_python<4.0.0,>=3.8
licenseBSD-3-Clause
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            ## Reasoning

[FastAPI](https://github.com/tiangolo/fastapi) is an awesome framework that simplifies
the process of creating APIs. One of the most exciting features is that it can generate
OpenAPI specs out of the box. But what if.. you have an OpenAPI spec and you need to
create an API from it?

One day we faced that problem — we had to create an API from multiple OpenAPI specs, and
make sure that the incoming requests and the outgoing responses were aligned with the
models defined the specs.

> ⚠️ This library was created to cover only our own needs first. So for now it's not
> suitable for everyone and has a lot of technical restrictions. Please consider it as
> experimental stuff

## Installation

The package is available on PyPi:

```bash
pip install openapi-to-fastapi
```

## Generating FastAPI routes

The main purpose of this library is to generate FastAPI routes from OpenAPI specs. This
is done by:

```python
from pathlib import Path
from openapi_to_fastapi.routes import SpecRouter

specs = Path("./specs")

router = SpecRouter(specs).to_fastapi_router()
```

The code above will create a FastAPI router that can be either included into the main
router, or used as the default one.

Imagine you have a following spec (some parts are cut off for brevity):

```json
{
  "openapi": "3.0.2",
  "paths": {
    "/Company/BasicInfo": {
      "post": {
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BasicCompanyInfoRequest",
                "responses": {
                  "200": {
                    "content": {
                      "application/json": {
                        "schema": {
                          "$ref": "#/components/schemas/BasicCompanyInfoResponse"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "components": {
        "schemas": {
          "BasicCompanyInfoRequest": {
            "title": "BasicCompanyInfoRequest",
            "required": ["companyId"],
            "type": "object",
            "properties": {
              "companyId": {
                "title": "Company Id",
                "type": "string",
                "example": "2464491-9"
              }
            }
          },
          "BasicCompanyInfoResponse": {
            "title": "BasicCompanyInfoResponse",
            "required": ["name", "companyId", "companyForm"],
            "type": "object",
            "properties": {
              "name": {
                "title": "Name of the company",
                "type": "string"
              },
              "companyId": {
                "title": "ID of the company",
                "type": "string"
              },
              "companyForm": {
                "title": "The company form of the company",
                "type": "string"
              }
            }
          }
        }
      }
    }
  }
}
```

The FastAPI route equivalent could look like this:

```python
class BasicCompanyInfoRequest(pydantic.BaseModel):
    companyId: str

class BasicCompanyInfoResponse(pydantic.BaseModel):
    name: str
    companyId: str
    companyForm: str


@router.post("/Company/BasicInfo", response_model=BasicCompanyInfoResponse)
def _route(request: BasicCompanyInfoRequest):
    return {}

```

And `openapi-to-fastapi` can create it automagically.

### Custom routes

In most cases it makes no sense to create an API without any business logic.

Here's how to define it:

```python
from fastapi import Header, HTTPException
from openapi_to_fastapi.routes import SpecRouter

spec_router = SpecRouter("./specs")

# Default handler for all POST endpoints found in the spec
@spec_router.post()
def hello_world(params, x_my_token: str = Header(...)):
    if x_my_token != "my_token":
        raise HTTPException(status_code=403, detail="Sorry")
    return {"Hello": "World"}

# Specific endpoint for a "/pet" route
@spec_router.post("/pet")
def create_pet(params):
    pet = db.make_pet(name=params.name)
    return pet.to_dict()

router = spec_router.to_fastapi_router()
```

### API Documentation

Now after you have a lot of routes, you might want to leverage another great feature of
FastAPI — auto documentation.

Request and response models are already handled. But to display documentation nicely,
FastAPI needs to assign a name for each endpoint. Here is how you can provide such name:

```python
from openapi_to_fastapi.routes import SpecRouter

spec_router = SpecRouter("./specs")

@spec_router.post(
    "/pet",
    name="Create a pet",
    description="Create a pet",
    response_description="A Pet",
    tags=["pets"],
)
def create_pet(params):
    return {}

# Or you can set the dynamic name based on API path
def name_factory(path: str, **kwargs):
    return path.replace("/", " ")

@spec_router.post(name_factory=name_factory)
def create_pet(params):
    return {}

```

## OpenAPI validation

This package also provides a CLI entrypoint to validate OpenAPI specs. It's especially
useful when you need to define you own set of rules for validation.

Imagine your API specs are stored in a separate repository and maintained by another
team. You also expect that all OpenAPI specs have only one endpoint defined (some
internal agreement).

Now you can set up a CI check and validate them on every push.

Firstly create a file with a custom validator:

```python
# my_validator.py

from openapi_to_fastapi.validator import BaseValidator, OpenApiValidationError

class CustomError(OpenApiValidationError):
    pass

# important: must be inherited from BaseValidator
class MyValidator(BaseValidator):

    # implement this single method
    def validate_spec(self, spec: dict):
        if len(spec["paths"]) != 1:
            raise CustomError("Only one endpoint allowed")
```

Then run the tool:

```
openapi-validator --path ./standards -m my_validator.py -v MyValidator

===============================================================================
OpenAPI specs root path: ./standards
Validators: DefaultValidator, MyValidator
===============================================================================
File: ./standards/Current.json
[PASSED]
-------------------------------------------------------------------------------
File: ./standards/Metric.json
[PASSED]
-------------------------------------------------------------------------------
File: ./standards/BasicInfo.json
[PASSED]
-------------------------------------------------------------------------------
===============================================================================
Summary:
Total : 3
Passed: 3
Failed: 0
===============================================================================
```

This validator can also be reused when generating routes:

```python
router = SpecRouter(specs, validators=[MyValidator])
```

## Development

You will need:

- Python 3.8+ (though 3.11+ might have some issues with dependencies)
- [pre-commit](https://pre-commit.com/#install)

Before working on the project, make sure you run:

```shell
pre-commit install
```

After making changes you can run tests:

```shell
poetry run invoke test
```

## License

This code is released under the BSD 3-Clause license. Details in the
[LICENSE](./LICENSE) file.


            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/ioxiocom/openapi-to-fastapi",
    "name": "openapi-to-fastapi",
    "maintainer": null,
    "docs_url": null,
    "requires_python": "<4.0.0,>=3.8",
    "maintainer_email": null,
    "keywords": null,
    "author": "IOXIO Ltd",
    "author_email": null,
    "download_url": "https://files.pythonhosted.org/packages/7f/2a/b3aabecdf7829c7d97f815058f7b2cf9b011bdf89b8f1a998ebca61dc6fd/openapi_to_fastapi-0.15.0.tar.gz",
    "platform": null,
    "description": "## Reasoning\n\n[FastAPI](https://github.com/tiangolo/fastapi) is an awesome framework that simplifies\nthe process of creating APIs. One of the most exciting features is that it can generate\nOpenAPI specs out of the box. But what if.. you have an OpenAPI spec and you need to\ncreate an API from it?\n\nOne day we faced that problem \u2014 we had to create an API from multiple OpenAPI specs, and\nmake sure that the incoming requests and the outgoing responses were aligned with the\nmodels defined the specs.\n\n> \u26a0\ufe0f This library was created to cover only our own needs first. So for now it's not\n> suitable for everyone and has a lot of technical restrictions. Please consider it as\n> experimental stuff\n\n## Installation\n\nThe package is available on PyPi:\n\n```bash\npip install openapi-to-fastapi\n```\n\n## Generating FastAPI routes\n\nThe main purpose of this library is to generate FastAPI routes from OpenAPI specs. This\nis done by:\n\n```python\nfrom pathlib import Path\nfrom openapi_to_fastapi.routes import SpecRouter\n\nspecs = Path(\"./specs\")\n\nrouter = SpecRouter(specs).to_fastapi_router()\n```\n\nThe code above will create a FastAPI router that can be either included into the main\nrouter, or used as the default one.\n\nImagine you have a following spec (some parts are cut off for brevity):\n\n```json\n{\n  \"openapi\": \"3.0.2\",\n  \"paths\": {\n    \"/Company/BasicInfo\": {\n      \"post\": {\n        \"requestBody\": {\n          \"required\": true,\n          \"content\": {\n            \"application/json\": {\n              \"schema\": {\n                \"$ref\": \"#/components/schemas/BasicCompanyInfoRequest\",\n                \"responses\": {\n                  \"200\": {\n                    \"content\": {\n                      \"application/json\": {\n                        \"schema\": {\n                          \"$ref\": \"#/components/schemas/BasicCompanyInfoResponse\"\n                        }\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          }\n        }\n      },\n      \"components\": {\n        \"schemas\": {\n          \"BasicCompanyInfoRequest\": {\n            \"title\": \"BasicCompanyInfoRequest\",\n            \"required\": [\"companyId\"],\n            \"type\": \"object\",\n            \"properties\": {\n              \"companyId\": {\n                \"title\": \"Company Id\",\n                \"type\": \"string\",\n                \"example\": \"2464491-9\"\n              }\n            }\n          },\n          \"BasicCompanyInfoResponse\": {\n            \"title\": \"BasicCompanyInfoResponse\",\n            \"required\": [\"name\", \"companyId\", \"companyForm\"],\n            \"type\": \"object\",\n            \"properties\": {\n              \"name\": {\n                \"title\": \"Name of the company\",\n                \"type\": \"string\"\n              },\n              \"companyId\": {\n                \"title\": \"ID of the company\",\n                \"type\": \"string\"\n              },\n              \"companyForm\": {\n                \"title\": \"The company form of the company\",\n                \"type\": \"string\"\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n}\n```\n\nThe FastAPI route equivalent could look like this:\n\n```python\nclass BasicCompanyInfoRequest(pydantic.BaseModel):\n    companyId: str\n\nclass BasicCompanyInfoResponse(pydantic.BaseModel):\n    name: str\n    companyId: str\n    companyForm: str\n\n\n@router.post(\"/Company/BasicInfo\", response_model=BasicCompanyInfoResponse)\ndef _route(request: BasicCompanyInfoRequest):\n    return {}\n\n```\n\nAnd `openapi-to-fastapi` can create it automagically.\n\n### Custom routes\n\nIn most cases it makes no sense to create an API without any business logic.\n\nHere's how to define it:\n\n```python\nfrom fastapi import Header, HTTPException\nfrom openapi_to_fastapi.routes import SpecRouter\n\nspec_router = SpecRouter(\"./specs\")\n\n# Default handler for all POST endpoints found in the spec\n@spec_router.post()\ndef hello_world(params, x_my_token: str = Header(...)):\n    if x_my_token != \"my_token\":\n        raise HTTPException(status_code=403, detail=\"Sorry\")\n    return {\"Hello\": \"World\"}\n\n# Specific endpoint for a \"/pet\" route\n@spec_router.post(\"/pet\")\ndef create_pet(params):\n    pet = db.make_pet(name=params.name)\n    return pet.to_dict()\n\nrouter = spec_router.to_fastapi_router()\n```\n\n### API Documentation\n\nNow after you have a lot of routes, you might want to leverage another great feature of\nFastAPI \u2014 auto documentation.\n\nRequest and response models are already handled. But to display documentation nicely,\nFastAPI needs to assign a name for each endpoint. Here is how you can provide such name:\n\n```python\nfrom openapi_to_fastapi.routes import SpecRouter\n\nspec_router = SpecRouter(\"./specs\")\n\n@spec_router.post(\n    \"/pet\",\n    name=\"Create a pet\",\n    description=\"Create a pet\",\n    response_description=\"A Pet\",\n    tags=[\"pets\"],\n)\ndef create_pet(params):\n    return {}\n\n# Or you can set the dynamic name based on API path\ndef name_factory(path: str, **kwargs):\n    return path.replace(\"/\", \" \")\n\n@spec_router.post(name_factory=name_factory)\ndef create_pet(params):\n    return {}\n\n```\n\n## OpenAPI validation\n\nThis package also provides a CLI entrypoint to validate OpenAPI specs. It's especially\nuseful when you need to define you own set of rules for validation.\n\nImagine your API specs are stored in a separate repository and maintained by another\nteam. You also expect that all OpenAPI specs have only one endpoint defined (some\ninternal agreement).\n\nNow you can set up a CI check and validate them on every push.\n\nFirstly create a file with a custom validator:\n\n```python\n# my_validator.py\n\nfrom openapi_to_fastapi.validator import BaseValidator, OpenApiValidationError\n\nclass CustomError(OpenApiValidationError):\n    pass\n\n# important: must be inherited from BaseValidator\nclass MyValidator(BaseValidator):\n\n    # implement this single method\n    def validate_spec(self, spec: dict):\n        if len(spec[\"paths\"]) != 1:\n            raise CustomError(\"Only one endpoint allowed\")\n```\n\nThen run the tool:\n\n```\nopenapi-validator --path ./standards -m my_validator.py -v MyValidator\n\n===============================================================================\nOpenAPI specs root path: ./standards\nValidators: DefaultValidator, MyValidator\n===============================================================================\nFile: ./standards/Current.json\n[PASSED]\n-------------------------------------------------------------------------------\nFile: ./standards/Metric.json\n[PASSED]\n-------------------------------------------------------------------------------\nFile: ./standards/BasicInfo.json\n[PASSED]\n-------------------------------------------------------------------------------\n===============================================================================\nSummary:\nTotal : 3\nPassed: 3\nFailed: 0\n===============================================================================\n```\n\nThis validator can also be reused when generating routes:\n\n```python\nrouter = SpecRouter(specs, validators=[MyValidator])\n```\n\n## Development\n\nYou will need:\n\n- Python 3.8+ (though 3.11+ might have some issues with dependencies)\n- [pre-commit](https://pre-commit.com/#install)\n\nBefore working on the project, make sure you run:\n\n```shell\npre-commit install\n```\n\nAfter making changes you can run tests:\n\n```shell\npoetry run invoke test\n```\n\n## License\n\nThis code is released under the BSD 3-Clause license. Details in the\n[LICENSE](./LICENSE) file.\n\n",
    "bugtrack_url": null,
    "license": "BSD-3-Clause",
    "summary": "Create FastAPI routes from OpenAPI spec",
    "version": "0.15.0",
    "project_urls": {
        "Homepage": "https://github.com/ioxiocom/openapi-to-fastapi",
        "Repository": "https://github.com/ioxiocom/openapi-to-fastapi"
    },
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "ea400edf9a5fa5ff9bde8c13aafc7c088ecc325340bf524890b859d52d6b322e",
                "md5": "1d38755c1cd1ce19983112ba9db01bb9",
                "sha256": "1369dda7ec3f549a680051eb4c85492a3032c32e096a83edd705e9f19254e80d"
            },
            "downloads": -1,
            "filename": "openapi_to_fastapi-0.15.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "1d38755c1cd1ce19983112ba9db01bb9",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": "<4.0.0,>=3.8",
            "size": 22865,
            "upload_time": "2024-04-08T08:14:06",
            "upload_time_iso_8601": "2024-04-08T08:14:06.610456Z",
            "url": "https://files.pythonhosted.org/packages/ea/40/0edf9a5fa5ff9bde8c13aafc7c088ecc325340bf524890b859d52d6b322e/openapi_to_fastapi-0.15.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "7f2ab3aabecdf7829c7d97f815058f7b2cf9b011bdf89b8f1a998ebca61dc6fd",
                "md5": "029a83fc656c1b981799cc6a7bf1989f",
                "sha256": "713b77e4d53a37c591498f5071d66ff9b3a56a742c3716de826e94959cce89c3"
            },
            "downloads": -1,
            "filename": "openapi_to_fastapi-0.15.0.tar.gz",
            "has_sig": false,
            "md5_digest": "029a83fc656c1b981799cc6a7bf1989f",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": "<4.0.0,>=3.8",
            "size": 18589,
            "upload_time": "2024-04-08T08:14:07",
            "upload_time_iso_8601": "2024-04-08T08:14:07.919188Z",
            "url": "https://files.pythonhosted.org/packages/7f/2a/b3aabecdf7829c7d97f815058f7b2cf9b011bdf89b8f1a998ebca61dc6fd/openapi_to_fastapi-0.15.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-04-08 08:14:07",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "ioxiocom",
    "github_project": "openapi-to-fastapi",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "openapi-to-fastapi"
}
        
Elapsed time: 0.31474s