Source code for swagger_spec_compatibility.spec_utils

# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import print_function
from __future__ import unicode_literals

import typing
from enum import Enum
from itertools import chain

from bravado.client import SwaggerClient
from bravado_core.operation import Operation
from bravado_core.spec import Spec
from bravado_core.util import determine_object_type
from bravado_core.util import ObjectType
from six import iterkeys
from swagger_spec_validator.validator20 import get_collapsed_properties_type_mappings

from swagger_spec_compatibility.cache import typed_lru_cache
from swagger_spec_compatibility.util import EntityMapping


[docs]class HTTPVerb(Enum): DELETE = 'delete' GET = 'get' HEAD = 'head' OPTIONS = 'options' PARAMETERS = 'parameters' PATCH = 'patch' POST = 'post' PUT = 'put'
[docs] @staticmethod def from_swagger_operation(operation): # type: (Operation) -> 'HTTPVerb' return HTTPVerb(operation.http_method)
[docs]class Endpoint(typing.NamedTuple( '_Endpoint', ( ('http_verb', HTTPVerb), ('path', typing.Text), ('operation', Operation), ), )):
[docs] @staticmethod def from_swagger_operation(operation): # type: (Operation) -> 'Endpoint' return Endpoint( http_verb=HTTPVerb.from_swagger_operation(operation), path=operation.path_name, operation=operation, )
def __hash__(self): # type: () -> int return hash((hash(self.http_verb), hash(self.path))) def __eq__(self, other): # type: (typing.Any) -> bool return isinstance(other, self.__class__) and self.http_verb == other.http_verb and self.path == other.path
[docs]@typed_lru_cache(maxsize=2) def load_spec_from_uri(uri): # type: (typing.Text) -> Spec return SwaggerClient.from_url(uri, config={'internally_dereference_refs': True}).swagger_spec
[docs]def load_spec_from_spec_dict(spec_dict): # type: (typing.Mapping[typing.Text, typing.Any]) -> Spec return SwaggerClient.from_spec(spec_dict, origin_url='', config={'internally_dereference_refs': True}).swagger_spec
[docs]@typed_lru_cache(maxsize=2) def get_operations(spec): # type: (Spec) -> typing.List[Operation] return [ operation for resource in spec.resources.values() for operation in resource.operations.values() ]
[docs]@typed_lru_cache(maxsize=2) def get_endpoints(spec): # type: (Spec) -> typing.Set[Endpoint] return { Endpoint.from_swagger_operation(operation) for operation in get_operations(spec) }
[docs]def get_operation_mappings(old_spec, new_spec): # type: (Spec, Spec) -> typing.Set[EntityMapping[Operation]] old_endpoints = get_endpoints(old_spec) old_endpoints_map = { # Small hack to make endpoint search easy endpoint: endpoint for endpoint in old_endpoints } new_endpoints = get_endpoints(new_spec) new_endpoints_map = { # Small hack to make endpoint search easy endpoint: endpoint for endpoint in new_endpoints } return { EntityMapping(old_endpoints_map[endpoint].operation, new_endpoints_map[endpoint].operation) for endpoint in old_endpoints.intersection(new_endpoints) }
[docs]def get_required_properties(swagger_spec, schema): # type: (Spec, typing.Optional[typing.Mapping[typing.Text, typing.Any]]) -> typing.Optional[typing.Set[typing.Text]] if schema is None or determine_object_type(schema) != ObjectType.SCHEMA: return None required, _ = get_collapsed_properties_type_mappings(definition=schema, deref=swagger_spec.deref) return set(iterkeys(required))
[docs]def get_properties(swagger_spec, schema): # type: (Spec, typing.Optional[typing.Mapping[typing.Text, typing.Any]]) -> typing.Optional[typing.Set[typing.Text]] if schema is None or determine_object_type(schema) != ObjectType.SCHEMA: return None required, not_required = get_collapsed_properties_type_mappings(definition=schema, deref=swagger_spec.deref) return set(chain(iterkeys(required), iterkeys(not_required)))
StatusCodeSchema = typing.NamedTuple( 'StatusCodeSchema', ( ('status_code', typing.Text), ('mapping', EntityMapping[typing.Optional[typing.Mapping[typing.Text, typing.Any]]]), ), )
[docs]def iterate_on_responses_status_codes( old_operation, # type: typing.Mapping[typing.Text, typing.Any] new_operation, # type: typing.Mapping[typing.Text, typing.Any] ): # type: (...) -> typing.Generator[StatusCodeSchema, None, None] old_status_code_schema_mapping = old_operation.get('responses') or {} new_status_code_schema_mapping = new_operation.get('responses') or {} common_response_codes = set(iterkeys(old_status_code_schema_mapping)).intersection( set(iterkeys(new_status_code_schema_mapping)), ) # Compare schemas for the same status code only (TODO: what to do for old=default and new=404?) for status_code in common_response_codes: yield StatusCodeSchema( status_code=status_code, mapping=EntityMapping( old=old_status_code_schema_mapping[status_code].get('schema'), new=new_status_code_schema_mapping[status_code].get('schema'), ), )