Skip to content

Commit

Permalink
Refactored code to reduce boilerplate
Browse files Browse the repository at this point in the history
  • Loading branch information
Dorthu committed May 6, 2018
1 parent 7eba9ac commit 8d6edfa
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 31 deletions.
25 changes: 12 additions & 13 deletions openapi/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,12 @@ class Info(ObjectBase):
.. _the spec: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#infoObject
"""
__slots__ = ['title','description','termsOfService','contact','license','version']
required_fields = ['title','version']

def __init__(self, path, raw_element):
def _parse_data(self):
"""
Implementation of :any:`ObjectBase._parse_data`
"""
super().__init__(path, raw_element)

self._required_fields('title', 'version')

self.title = self._get('title', str)
self.description = self._get('description', str)
self.termsOfService = self._get('termsOfService', str)
Expand All @@ -30,9 +28,10 @@ class Contact(ObjectBase):
"""
__slots__ = ['name','url','email']

def __init__(self, path, raw_element):
super().__init__(path, raw_element)

def _parse_data(self):
"""
Implementation of :any:`ObjectBase._parse_data`
"""
self.name = self._get('name', str)
self.url = self._get('url', str)
self.email = self._get('email', str)
Expand All @@ -44,11 +43,11 @@ class License(ObjectBase):
.. _here: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#license-object
"""
__slots__ = ['name','url']
required_fields = ['name']

def __init__(self, path, raw_element):
super().__init__(path, raw_element)

self._required_fields('name')

def _parse_data(self):
"""
Implementation of :any:`ObjectBase._parse_data`
"""
self.name = self._get('name', str)
self.url = self._get('url', str)
30 changes: 30 additions & 0 deletions openapi/object_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ class ObjectBase:
related functions.
"""
__slots__ = ['path','raw_element','_accessed_members','strict','extensions']
required_fields = []

def __init__(self, path, raw_element):
"""
Expand All @@ -19,8 +20,14 @@ def __init__(self, path, raw_element):
self.strict = False # TODO - add a strict mode that errors if all members were not accessed
self.extensions = {}

# parse our own element
self._required_fields(*type(self).required_fields)
self._parse_data()

self._parse_spec_extensions() # TODO - this may not be appropriate in all cases

# TODO - assert that all keys of raw_element were accessed

def _required_fields(self, *fields):
"""
Given a list of require fields for this object, raises a SpecError if any
Expand All @@ -40,6 +47,20 @@ def _required_fields(self, *fields):
raise SpecError("Missing required fields: {}".format(
', '.join(missing_fields)))

def _parse_data(self):
"""
Parses the raw_element into this object. This is not implemented here,
but is called in the constructor and _must_ be implemented in all
subclasses.
An implementation of this method should use :any:`_get` to retrieve
values from the raw_element, which has the side-effect of noting that
those members were accessed. After this is executed, spec extensions
are parsed and then an assertion is made that all keys in the
raw_element were accessed - if not, the schema is considered invalid.
"""
raise NotImplemented("You must implement this method in subclasses!")

def _get(self, field, object_type, list_type=None):
"""
Retrieves a value from this object's raw element, and returns None if
Expand All @@ -58,6 +79,7 @@ def _get(self, field, object_type, list_type=None):
:returns: object_type if given, otherwise the type parsed from the spec
file
"""
# TODO - accept object_types as a list
self._accessed_members.append(field)

ret = self.raw_element.get(field, None)
Expand Down Expand Up @@ -149,3 +171,11 @@ def parse_list(self, raw_list, object_type):
self.path+[i], cur))

return result


class Map:
"""
The Map object wraps a python dict and parses its values into the chosen
type or types.
"""
# TODO
10 changes: 9 additions & 1 deletion openapi/openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,24 @@ class OpenAPI(ObjectBase):
.. _the spec: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#openapi-object
"""
__slots__ = ['openapi','info','servers','paths','components','security','tags','externalDocs']
required_fields=['openapi','info','paths']

def __init__(self, raw_document):
"""
Creates a new OpenAPI document from a loaded spec file
Creates a new OpenAPI document from a loaded spec file. This is
overridden here because we need to specify the path in the parent
class' constructor.
:param raw_document: The raw OpenAPI file loaded into python
:type raw_document: dct
"""
super().__init__([], raw_document) # as the document root, we have no path


def _parse_data(self):
"""
Implementation of :any:`ObjectBase._parse_data`
"""
self._required_fields('openapi', 'info', 'paths')

self.openapi = self._get('openapi', str)
Expand Down
18 changes: 11 additions & 7 deletions openapi/paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@ class Path(ObjectBase):
.. _here: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#paths-object
"""
def __init__(self, path, raw_element):
super().__init__(path, raw_element)
__slots__ = ['summary','description','get','put','post','delete','options',
'head', 'patch','trace','servers','parameters']

def _parse_data(self):
"""
Implementation of :any:`ObjectBase._parse_data`
"""
# TODO - handle possible $ref
self.summary = self._get("summary", str)
self.description = self._get("description", str)
Expand All @@ -36,12 +40,12 @@ class Operation(ObjectBase):
__slots__ = ['tags','summary','description','externalDocs','operationId',
'parameters','requedtBody','responses','callbacks','deprecated',
'security','servers']
required_fields = ['responses']

def __init__(self, path, raw_element):
super().__init__(path, raw_element)

self._required_fields("responses")

def _parse_data(self):
"""
Implementation of :any:`ObjectBase._parse_data`
"""
self.tags = self._get('tags', list)
self.summary = self._get('summary', str)
self.description = self._get('description', str)
Expand Down
20 changes: 10 additions & 10 deletions openapi/servers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ class Server(ObjectBase):
.. _here: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#serverObject
"""
__slots__ = ['url','description','variables']
required_fields=['url']

def __init__(self, path, raw_element):
super().__init__(path, raw_element)

self._required_fields('url')

def _parse_data(self):
"""
Implementation of :any:`ObjectBase._parse_data`
"""
self.url = self._get('url', str)
self.description = self._get('description', str)
self.variables = self._get('variables', dict)
Expand All @@ -33,12 +33,12 @@ class ServerVariable(ObjectBase):
.. _here: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#server-variable-object
"""
__slots__ = ['enum','default','description']
required_fields = ['default']

def __init__(self, path, raw_element):
super().__init__(path, raw_element)

self._required_fields('default')

def _parse_data(self):
"""
Implementation of :any:`ObjectBase._parse_data`
"""
self.enum = self._get('enum', list, list_type=str)
self.default = self._get('default', str)
self.description = self._get('description', str)

0 comments on commit 8d6edfa

Please sign in to comment.