💻 Programming: recipe: response validation against OpenAPI schema in Python

Summary: code snippets and explanation of how to validate HTTP-response using openapi-core package

If you deal with some service based on OpenAPI Specification you might want to perform validation of HTTP responses returned by it.

One of the ways to do this an internet search provides for Python is to use openapi-core package.
If you look at their documentation (here or here) you will find it very stingy: couple of short code snippets and couple of sentences just stating what is done.
In particular for response validation we have exactly the following:
 
You can also validate against responses
 
from openapi_core.validation.response.validators import ResponseValidator
 
validator = ResponseValidator(spec)
result = validator.validate(request, response)
 
# raise errors if response invalid
result.raise_for_errors()
 
# get list of errors
errors = result.errors
...
 
Here we see some confusing things:
  • why do we need a request object to be able to validate the response?
  • and what type is it of?
If you are looking specifically for a response validation you probably quickly skip previous sections and miss the statement that request should be an OpenAPIRequest entity, but you still can find a sentence below that response object class is OpenAPIResponse following by a "See Integrations" reference which leads.. to a bit strange (in the context of actual subject) article about Django! So it's necessary to dig through the code base to find where and what it is. After all you can find that both are simple data-classes the question is only how to cook them — i.e. how to properly instantiate these entities. To validate response only we don't need a real request obj. so we can just simulate it.
Request object requires
  • full_url_pattern, which identifies the endpoint whose response we validate, for request simulation it's enough to pass a relative path of the form /aaa/bbb/ccc...
  • method = an HTTP request method (GET, POST etc.)
  • body = a body of HTTP request, but in our case it's not used, so it can be empty ('')
  • parameters = a MultiDict of key-value pairs representing param-s sent as part of url — can be empty too ({})
  • mimetype — in general a pure content type value (that what is passed in 'Content-Type' HTTP header, but without any attributes like charset) — here can be empty
Response requires
  • data = response.content (using requests lib.)
  • status_code = response.status_code
  • mimetype = value from response.headers['Content-Type'] , refined as described in previous section
And the code sample:
 
1. Getting request/response obj-s
 
resp = requests.post('http://..../aaa/bbb', ...)
 
o_req = OpenAPIRequest(full_url_pattern = '/aaa/bbb',
                       method = 'POST',
                       body = '',
                       mimetype = '',
                       parameters = {})
 
o_resp = OpenAPIResponse(resp.content,
                         status_code = resp.status_code,
                         mimetype = content_type_of(resp))
 
where content_type_of() is defined as following:
 
from cgi import parse_header
 
 
def content_type_of(resp):
 
    header, _ = parse_header(resp.headers
                                 .get('Content-Type', ''))
 
    return header
 
2. Schema initialization and validation
 
from openapi_core import create_spec
# (schema_dict_obj is an OpenAPI schema converted to Python's dict)
spec = create_spec(schema_dict_obj)
 
from openapi_core.validation.response.validators import ResponseValidator
 
validator = ResponseValidator(spec)
result = validator.validate(o_req, o_resp)
result.raise_for_errors()
 
 _________

Other articles ~ programming

Comments

Popular posts from this blog

Ï• Logic vs. ‘common sense’

💻 Programming: recipe: pytest: assertion after all tests run