# -*- 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'),
),
)