8000 Minimal tests for decode · millerlucas/python-openapi-codec@15be2f3 · GitHub
[go: up one dir, main page]

Skip to content
< 65EB /div>

Commit 15be2f3

Browse files
committed
Minimal tests for decode
1 parent 7b9c818 commit 15be2f3

File tree

7 files changed

+1228
-176
lines changed

7 files changed

+1228
-176
lines changed

openapi_codec/__init__.py

Lines changed: 4 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -1,128 +1,16 @@
11
import json
22

33
from coreapi.codecs.base import BaseCodec
4-
from coreapi.compat import force_bytes, urlparse
5-
from coreapi.document import Document, Link, Field
4+
from coreapi.compat import force_bytes
5+
from coreapi.document import Document
66
from coreapi.exceptions import ParseError
7-
from openapi_codec.utils import _get_string, _get_dict, _get_list, _get_bool, get_strings, get_dicts
8-
from openapi_codec.converters import generate_swagger_object
7+
from openapi_codec.encode import generate_swagger_object
8+
from openapi_codec.decode import _parse_document
99

1010

1111
__version__ = "0.0.3"
1212

1313

14-
def _expand_schema(schema):
15-
"""
16-
When an OpenAPI parameter uses `in="body"`, and the schema type is "object",
17-
then we expand out the parameters of the object into individual fields.
18-
"""
19-
schema_type = schema.get('type')
20-
schema_properties = _get_dict(schema, 'properties')
21-
schema_required = _get_list(schema, 'required')
22-
if ((schema_type == ['object']) or (schema_type == 'object')) and schema_properties:
23-
return [
24-
(key, key in schema_required)
25-
for key in schema_properties.keys()
26-
]
27-
return None
28-
29-
30-
def _get_document_base_url(data, base_url=None):
31-
"""
32-
Get the base url to use when constructing absolute paths from the
33-
relative ones provided in the schema defination.
34-
"""
35-
prefered_schemes = ['http', 'https']
36-
if base_url:
37-
url_components = urlparse.urlparse(base_url)
38-
default_host = url_components.netloc
39-
default_scheme = url_components.scheme
40-
else:
41-
default_host = ''
42-
default_scheme = None
43-
44-
host = _get_string(data, 'host', default=default_host)
45-
path = _get_string(data, 'basePath', default='/')
46-
47-
if not host:
48-
# No host is provided, and we do not have an initial URL.
49-
return path.strip('/') + '/'
50-
51-
schemes = _get_list(data, 'schemes')
52-
53-
if not schemes:
54-
# No schemes provided, use the initial URL, or a fallback.
55-
scheme = default_scheme or prefered_schemes[0]
56-
elif default_scheme in schemes:
57-
# Schemes provided, the initial URL matches one of them.
58-
scheme = default_scheme
59-
else:
60-
# Schemes provided, the initial URL does not match, pick a fallback.
61-
for scheme in prefered_schemes:
62-
if scheme in schemes:
63-
break
64-
else:
65-
raise ParseError('Unsupported transport schemes "%s"' % schemes)
66-
67-
return '%s://%s/%s/' % (scheme, host, path.strip('/'))
68-
69-
70-
def _parse_document(data, base_url=None):
71-
schema_url = base_url
72-
base_url = _get_document_base_url(data, base_url)
73-
info = _get_dict(data, 'info')
74-
title = _get_string(info, 'title')
75-
paths = _get_dict(data, 'paths')
76-
content = {}
77-
for path in paths.keys():
78-
url = urlparse.urljoin(base_url, path.lstrip('/'))
79-
spec = _get_dict(paths, path)
80-
default_parameters = get_dicts(_get_list(spec, 'parameters'))
81-
for action in spec.keys():
82-
action = action.lower()
83-
if action not in ('get', 'put', 'post', 'delete', 'options', 'head', 'patch'):
84-
continue
85-
operation = _get_dict(spec, action)
86-
87-
# Determine any fields on the link.
88-
fields = []
89-
parameters = get_dicts(_get_list(operation, 'parameters', default_parameters), dereference_using=data)
90-
for parameter in parameters:
91-
name = _get_string(parameter, 'name')
92-
location = _get_string(parameter, 'in')
93-
required = _get_bool(parameter, 'required', default=(location == 'path'))
94-
if location == 'body':
95-
schema = _get_dict(parameter, 'schema', dereference_using=data)
96-
expanded = _expand_schema(schema)
97-
if expanded is not None:
98-
expanded_fields = [
99-
Field(name=field_name, location='form', required=is_required)
100-
for field_name, is_required in expanded
101-
if not any([field.name == name for field in fields])
102-
]
103-
fields += expanded_fields
104-
else:
105-
field = Field(name=name, location='body', required=True)
106-
fields.append(field)
107-
else:
108-
field = Field(name=name, location=location, required=required)
109-
fields.append(field)
110-
link = Link(url=url, action=action, fields=fields)
111-
112-
# Add the link to the document content.
113-
tags = get_strings(_get_list(operation, 'tags'))
114-
operation_id = _get_string(operation, 'operationId')
115-
if tags:
116-
for tag in tags:
117-
if tag not in content:
118-
content[tag] = {}
119-
content[tag][operation_id] = link
120-
else:
121-
content[operation_id] = link
122-
123-
return Document(url=schema_url, title=title, content=content)
124-
125-
12614
class OpenAPICodec(BaseCodec):
12715
media_type = "application/openapi+json"
12816

openapi_codec/decode.py

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
from coreapi import Document, Link, Field
2+
from coreapi.compat import string_types, urlparse
3+
from coreapi.exceptions import ParseError
4+
5+
6+
def _parse_document(data, base_url=None):
7+
schema_url = base_url
8+
base_url = _get_document_base_url(data, base_url)
9+
info = _get_dict(data, 'info')
10+
title = _get_string(info, 'title')
11+
paths = _get_dict(data, 'paths')
12+
content = {}
13+
for path in paths.keys():
14+
url = urlparse.urljoin(base_url, path.lstrip('/'))
15+
spec = _get_dict(paths, path)
16+
default_parameters = get_dicts(_get_list(spec, 'parameters'))
17+
for action in spec.keys():
18+
action = action.lower()
19+
if action not in ('get', 'put', 'post', 'delete', 'options', 'head', 'patch'):
20+
continue
21+
operation = _get_dict(spec, action)
22+
23+
# Determine any fields on the link.
24+
fields = []
25+
parameters = get_dicts(_get_list(operation, 'parameters', default_parameters), dereference_using=data)
26+
for parameter in parameters:
27+
name = _get_string(parameter, 'name')
28+
location = _get_string(parameter, 'in')
29+
required = _get_bool(parameter, 'required', default=(location == 'path'))
30+
if location == 'body':
31+
schema = _get_dict(parameter, 'schema', dereference_using=data)
32+
expanded = _expand_schema(schema)
33+
if expanded is not None:
34+
expanded_fields = [
35+
Field(name=field_name, location='form', required=is_required)
36+
for field_name, is_required in expanded
37+
if not any([field.name == name for field in fields])
38+
]
39+
fields += expanded_fields
40+
else:
41+
field = Field(name=name, location='body', required=True)
42+
fields.append(field)
43+
else:
44+
field = Field(name=name, location=location, required=required)
45+
fields.append(field)
46+
link = Link(url=url, action=action, fields=fields)
47+
48+
# Add the link to the document content.
49+
tags = get_strings(_get_list(operation, 'tags'))
50+
operation_id = _get_string(operation, 'operationId')
51+
if tags:
52+
for tag in tags:
53+
if tag not in content:
54+
content[tag] = {}
55+
content[tag][operation_id] = link
56+
else:
57+
content[operation_id] = link
58+
59+
return Document(url=schema_url, title=title, content=content)
60+
61+
62+
def _get_document_base_url(data, base_url=None):
63+
"""
64+
Get the base url to use when constructing absolute paths from the
65+
relative ones provided in the schema defination.
66+
"""
67+
prefered_schemes = ['http', 'https']
68+
if base_url:
69+
url_components = urlparse.urlparse(base_url)
70+
default_host = url_components.netloc
71+
default_scheme = url_components.scheme
72+
else:
73+
default_host = ''
74+
default_scheme = None
75+
76+
host = _get_string(data, 'host', default=default_host)
77+
path = _get_string(data, 'basePath', default='/')
78+
79+
if not host:
80+
# No host is provided, and we do not have an initial URL.
81+
return path.strip('/') + '/'
82+
83+
schemes = _get_list(data, 'schemes')
84+
85+
if not schemes:
86+
# No schemes provided, use the initial URL, or a fallback.
87+
scheme = default_scheme or prefered_schemes[0]
88+
elif default_scheme in schemes:
89+
# Schemes provided, the initial URL matches one of them.
90+
scheme = default_scheme
91+
else:
92+
# Schemes provided, the initial URL does not match, pick a fallback.
93+
for scheme in prefered_schemes:
94+
if scheme in schemes:
95+
break
96+
else:
97+
raise ParseError('Unsupported transport schemes "%s"' % schemes)
98+
99+
return '%s://%s/%s/' % (scheme, host, path.strip('/'))
100+
101+
102+
def _expand_schema(schema):
103+
"""
104+
When an OpenAPI parameter uses `in="body"`, and the schema type is "object",
105+
then we expand out the parameters of the object into individual fields.
106+
"""
107+
schema_type = schema.get('type')
108+
schema_properties = _get_dict(schema, 'properties')
109+
schema_required = _get_list(schema, 'required')
110+
if ((schema_type == ['object']) or (schema_type == 'object')) and schema_properties:
111+
return [
112+
(key, key in schema_required)
113+
for key in schema_properties.keys()
114+
]
115+
return None
116+
117+
118+
# Helper functions to get an expected type from a dictionary.
119+
120+
def dereference(lookup_string, struct):
121+
"""
122+
Dereference a JSON pointer.
123+
http://tools.ietf.org/html/rfc6901
124+
"""
125+
keys = lookup_string.strip('#/').split('/')
126+
node = struct
127+
for key in keys:
128+
node = _get_dict(node, key)
129+
return node
130+
131+
132+
def is_json_pointer(value):
133+
return isinstance(value, dict) and ('$ref' in value) and (len(value) == 1)
134+
135+
136+
def _get_string(item, key, default=''):
137+
value = item.get(key)
138+
return value if isinstance(value, string_types) else default
139+
140+
141+
def _get_dict(item, key, default={}, dereference_using=None):
142+
value = item.get(key)
143+
if isinstance(value, dict):
144+
if dereference_using and is_json_pointer(value):
145+
return dereference(value['$ref'], dereference_using)
146+
return value
147+
return default.copy()
148+
149+
150+
def _get_list(item, key, default=[]):
151+
value = item.get(key)
152+
return value if isinstance(value, list) else list(default)
153+
154+
155+
def _get_bool(item, key, default=False):
156+
value = item.get(key)
157+
return value if isinstance(value, bool) else default
158+
159+
160+
# Helper functions to get an expected type from a list.
161+
162+
def get_dicts(item, dereference_using=None):
163+
ret = [value for value in item if isinstance(value, dict)]
164+
if dereference_using:
165+
return [
166+
dereference(value['$ref'], dereference_using) if is_json_pointer(value) else value
167+
for value in ret
168+
]
169+
return ret
170+
171+
172+
def get_strings(item):
173+
return [value for value in item if isinstance(value, string_types)]
File renamed without changes.

openapi_codec/utils.py

Lines changed: 0 additions & 59 deletions
This file was deleted.

0 commit comments

Comments
 (0)
0