Poast: Python OAS Toolkit

Poast [1] is an OpenAPI 3.0 specification parser and client library for Python.

Danger

This project is still in alpha.

Quickstart

Specs

Loading

OpenAPI specifications are loaded using OpenApiObject.

Specs can be loaded from an io object, a file path, or a URI. Both json and yaml files are supported.

First, get the thing imported:

>>> from poast.openapi3.spec import OpenApiObject

Then, load a spec (all of the following work):

>>> doc_from_url = OpenApiObject(
...     'https://petstore3.swagger.io/api/v3/openapi.json')

>>> doc_from_filepath = OpenApiObject('./my/openapi.yml')

>>> with open('./my/other/openapi.json', 'rb') as f:
>>>   from_io = OpenApiObject(f)

Validation

Validation is performed using the validate() method on the returned OpenApiObject, e.g.:

>>> my_doc = OpenApiObject('./path/to/my/openapi.yml')
>>> my_doc.validate()
  • Loading or validation errors will raise DocumentParsingException.
  • Errors with the document itself, will raise MalformedDocumentException

See also

poast.openapi3.spec.model.exceptions

Clients

Generating

Clients are created from a OpenApiObject using get_client_cls() function, e.g.:

>>> from poast.openapi3.spec import OpenApiObject
>>> from poast.openapi3.client import gen_client_cls
>>>
>>> # api_spec = OpenApiObject(...).validate()
>>>
>>> # Generate a client class from the spec:
>>> MyApiClient = gen_client_cls('MyApiClient', api_spec)

The return value is a subclass of OpenApiClient, objects of which can be instantiated in the usual fashion:

>>> client = MyApiClient('https://myservice.com/api/root')

API Operations

The client classes created using get_client_cls() have a special attribute, op (an API-specific subclass of OpenApiOperations), with one method for each API operation defined in the spec. For example, if an api describes two operations, someAction and anotherAction, client instances will have the following two methods in their op object:

>>> client.op.someAction()
>>> client.op.anotherAction()
Invoking Operations

Methods defined on the OpenApiOperations subclass generally mimic the call signature of requests.Request(), with path parameters passed as keyword arguments.

Request Parameters
Spec "in": Passed as:
path **kwargs
query params (dict)
header headers (dict)
cookie cookies (dict)
Request Body

The the request body can passed - requests-style as either json, data, stream, or files.

Note

In the event that a path parameter collides with a Python keyword, builtin, or existing named parameter to the underlying requests.Request object method, both the URI template and the parameter name are adjusted to include the suffix '_'.

Example

Consider, for instance, a utility provider which provides a customer API. Let’s suppose that the API provides a getUsage operation which requires us to make a GET request with:

  • a customerId parameter specified in the URI path
  • a days parameter, specified as a query arg
  • an auth token, in the X-API-Key HTTP header

Finding the resource usage for user #123 over the last 30 days might look like:

>>> resp = my_client.getUsage(
...   customerId=123,
...   params={'days': 30},
...   headers{'X-API-KEY': MY_SECRET_API_KEY}
... ).execute()

The resp object here is a standard requests.Response object. We can get the body as text like so:

>>> print(resp.text)

Or (if it’s JSON), like so:

>>> print(resp.json())

API Help

Operations for generated clients include docstrings for each method that indicates the type and location of all required parameters, e.g.:

>>> help(my_client.op.getPetById)
Help on method getPetById in module poast.openapi3.client.genop:

getPetById(headers=None, params=None, cookies=None, data=None, json=None, files=None, hooks=None, **path_params) method of poast.openapi3.client.genops.PoastExampleClientOperations instance
    http: GET /pet/{petId}
    summary: Find pet by ID
    description: Returns a single pet

    path parameters (keyword args):
      petId:
        description: ID of pet to return
        required: True
        deprecated: False
        allowEmptyValue: False

    Security Requirements:
      api_key: []
      petstore_auth: ['write:pets', 'read:pets']

Advanced Usage

Prepared Requests

API operations on OpenApiOperations objects return standard requests.PreparedRequest object, patched to include an execute() method (an instance of RequestExecutor).

This allows the client opportunity to make last minute modifications to a request before it is sent, e.g.:

>>> req = my_client.someAction()
>>> # Make some adjustment to the body:
>>> req.body += r'This has to get appended'

>>> # Fire it off and get a standard requests.Response:
>>> resp = req.execute()

Client Configuration

Custom Sessions

Using a specific session

A particular requests.Session (or compatible) session instance can be passed in at the time of client instantiation, using the session parameter:

>>> my_session = requests.Session()
>>> my_client = MyClientClass(root_url='http://something.com',
...   session=my_session)

Using a custom class

To use a specific class of session object for a client:

>>> my_client = MyClientClass(root_url='http://something.com',
...   config=ClientConfig(session_cls=SpecialSessionClass))

Custom Requests

To use a specific class of request object for a client:

>>> my_client = MyClientClass(root_url='http://something.com',
...   config=ClientConfig(request_cls=SpecialRequestClass))

Client API

OpenApiClient

ClientConfig

class poast.openapi3.client.config.ClientConfig(logger=None, session_cls=None, request_cls=None, headers: dict = None, cookies: dict = None)[source]

Bases: object

Configuration object used to customize OpenApiClient creation.

logger

optional logger client created using this config

Type:logging.Logger
session_cls

a requests.Session-like class used to create HTTP sessions

Type:type
request_cls

a requests.Request-like class used to create HTTP requests

Type:type
headers

a list of headers common to all requests for client created from this config

Type:dict
cookies

a list of cookies common to all requests for client created from this config

Type:dict

Notes

  • headers and cookies are copied via the copy module!
__init__(logger=None, session_cls=None, request_cls=None, headers: dict = None, cookies: dict = None)[source]

Utility class to package up client configuration for re-use across multiple clients.

NOTE: headers and cookies are copied via the copy module!

__setattr__(name: str, value)[source]

Prevent anything but logger from being set to None.

OpenApiOperations

RequestExecutor

Spec API

Object Interface

All fields in OAS 3.0 objects are accessed using python’s [] operator. This is done to avoid any confusion between which fields are in the spec vs attributes of the implementation class and to avoid workarounds wherein spec fields which conflict with keywords or builtins - e.g. in - can retain their original names.

For example, accessing the paths field in a document is done like so:

my_doc = OpenApiObject(...)
paths = my_doc['paths']

OpenApiBaseObject

class poast.openapi3.spec.model.baseobj.OpenApiBaseObject(data, doc_path='#')[source]

Bases: poast.openapi3.spec.model.entity.OpenApiEntity, dict

Base class for OpenAPI specification objects.

__init__(data, doc_path='#')[source]

Invoke child class initialization.

__missing__(key)[source]

If a value is missing from the spec, return the default, as defined by the standard, if possible.

accept(visitor)[source]

Depth first traversal, via visitor.

Parameters:visitor (func) – a function that takes a single OpenApiEntity as an argument.
validate()[source]

Validate the data in this object against the spec.

value(show_unset=False)[source]

Gnarly (in the bad way) convenience/debug function used to return the document as a python dictionary for pprinting and dev validation.

__weakref__

list of weak references to the object (if defined)

Primitive Types

OpenApi 3.0 Primitive datatype definitions.

See also: - https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#data-types

Container Types

This module defines the OpenApi 3.0 container types

For more info, see:

Document Objects

Planned Features

Note

All of the following are either planned or current WIP.

Coming Soon!

On the Horizon

Maybe

  • Request validation
  • Response validation

History

0.0.1 (2020-12-01)

  • Initial public push
  • Still TODO:
    • Travis setup
    • Readthedocs
    • Unit tests

Maintainer Docs

Poast maintainer documentation can be found below. The canonical source can be found at poast on github.

Parser Object Model

Data Objects

Inheritance diagram of poast.openapi3.spec.model.containers, poast.openapi3.spec.model.primitives, poast.openapi3.spec.model.reference

Field Specs

Inheritance diagram of poast.openapi3.spec.model.field

Features

  • OpenAPI 3.0.3 compliant parsing and validation
  • Runtime client generation
  • Utilizes requests for underlying HTTP request/response
  • Compatible with requests_futures and requests_oauthlib sessions

Demo

Creating a client from a spec::

>>> from poast.openapi3.spec import OpenApiObject
>>> from poast.openapi3.client import gen_client_cls

>>> # Load the spec (path, io stream, url, or string!)
>>> api_spec = OpenApiObject(
...     'https://petstore3.swagger.io/api/v3/openapi.json')

>>> # [Optionally, perform validation]
>>> api_spec.validate()

>>> # Generate a client class from the spec:
>>> PetStoreClient = gen_client_cls('PetStoreClient', api_spec)

>>> # Instantiate a client and start using the API!
>>> client = PetStoreClient('https://petstore3.swagger.io/api/v3')

>>> print(client.op.getInventory().execute().json())
{'approved': 57, 'placed': 100, 'delivered': 50}

>>> print(client.op.getPetById(petId=2).execute().json())
{'id': 2, 'category': {'id': 2, 'name': 'Cats'}, 'name': 'Cat 2', 'photoUrls': ['url1', 'url2'], 'tags': [{'id': 1, 'name': 'tag2'}, {'id': 2, 'name': 'tag3'}], 'status': 'sold'}
[1]Python OpenAPI Specification Toolkit