Lightweight API framework using an OpenAPI spec for routing and validation.
PyAPI Server is a Python library for serving REST APIs based on OpenAPI specifications. It is based on Starlette and is functionally very similar to connexion, except that it aims to be fully ASGI-compliant.
WARNING: This is still very much work in progress and not quite ready for production usage. Until version 1.0 is released, any version can be expected to break backward compatibility.
Why PyAPI Server?
The main advantage of PyAPI Server is that it uses the OpenAPI specification to both prepare and validate requests and responses.
Specifically, when PyAPI Server receives a request it validates it against the URLs defined in the specification, raising an error in case of an invalid request. On a valid request it looks for an "endpoint function" matching the request URL's
operationId; this function is supposed to process a request and return a response, which is then also validated based on the specification rules before being sent back to the client.
The minimal setup consists of three files:
- A REST(ful) API specification in the form of an OpenAPI file, usually in YAML or JSON format.
- A Python file, e.g.
server.py, which initiates your application.
- Another Python file containing the individual endpoint functions.
src/examples/server/ contains a working example PyAPI Server application, using the specification at
src/examples/petstore.yaml -- which is a copy of the standard OpenAPI
To run the example, follow these steps inside a Python
- Install PyAPI server with optional dependencies:
PDM install -G uvicorn
- Start the PyAPI example server:
uvicorn example.server:app --reload --host 0.0.0.0 --port 5000 --log-level debug
A PyAPI Server application is an instance of the
pyapi.server.Application class, which is a subclass of
starlette.applications.Starlette; this means it is fully ASGI-compatible and can be used as any other ASGI app.
When initialising a PyAPI Server app, it is necessary to provide an OpenAPI specification file.
The value of
spec is either a Python dictionary of the OpenAPI spec, or an
Spec object. There is a helper class method which will load the spec provided a path in a specification file:
Optionally, a module containing endpoint functions (see below) can be added as a keyword argument. It can be specified as the dot-separated path to the module location; in the above example, it might be the file
myserver/endpoints.py or the directory
module can be the actual imported module:
Application constructor also accepts the following keyword arguments:
validate_responses: Boolean (defaults to
True, each response will be validated against the spec before being sent back to the caller.
enforce_case: Boolean (defaults to
operationIdvalues will be normalized to snake case when setting endpoint functions. For example,
fooBarwill expect the function named
Any other keyword arguments provided to the
Application constructor will be passed directly into the
Starlette application class.
An endpoint is a standard Python function, which needs to conform to the following requirements:
- It needs to accept a single positional argument, a request object compatible with the Starlette
- It has to return either a Python dictionary, or an object compatible with the Starlette
Response. If it is a dictionary, PyAPI Server will convert it into a
- It doesn't have to be a coroutine function (defined using
async defsyntax), but it is highly recommended, especially if it needs to perform any asynchronous operations itself (e.g. if it makes a call to an external API).
A basic example of an endpoint function:
Setting Endpoints on Application
The OpenAPI spec defines the endpoints ("paths") that the API handles, as well as the requests and responses it can recognise. Each endpoint has a field called
operationId, which is supposed to be globally unique; PyAPI server takes advantage of this field to find the corresponding endpoint function.
Endpoint functions can be defined in several ways:
A Python Module
The first way is as a Python module that contains the endpoint functions. For example, assume that we have the module
server/endpoints.py, looking something like this:
We can then define our application in the following way:
Assuming, of course, that our OpenAPI spec contains
A Python Module Path
Alternatively, instead of an imported module we can pass a string in the form of a dot-separated path; for example,
myserver.endpoints. The equivalent of the example above would now be:
PyAPI Server will try to locate the endpoint module by combining the
module argument and the
operationId value, converting the function name to snake case if necessary. E.g. if the base is
myserver.endpoints and the
fooEndpoint, it will import the
foo_endpoint function located in either
myserver/endpoints/__init__.py). Also, if the
operationId value itself contains dots it will try to build the full path, so
some.extra.levels.fooBar will look for the module
Setting Individual Endpoints
The endpoints can also be set individually, using the
PyAPI Server determines the operation from the function name: in the example above it would be set on the
someEndpoint. Alternatively, the
operationId can be provided explicitly:
The Endpoint Decorator
It is also possible to define endpoints as they are defines, using the
endpoint decorator, which works analogous
Custom Format Validators
If the OpenAPI spec contains custom string formats, the server can be configured to recognize them via the
custom_format_validators keyword argument. This argument is a mapping, where the keys are format names, while the values are callables which take a string and return
False, depending on whether the string is in valid format.