From 97724aeb4fcb23ce04d3719b65f1f6c1d6a3e858 Mon Sep 17 00:00:00 2001 From: Kirill Matyunin Date: Sun, 11 Jun 2017 14:50:51 -0400 Subject: [PATCH 01/20] fixing #37 issue support of definitions for nested objects --- openapi_codec/encode.py | 65 +++++++++++++++++++++++++--- tests/test_encode.py | 94 ++++++++++++++++++++++++++++++++++++++++- tests/test_mappings.py | 1 - 3 files changed, 153 insertions(+), 7 deletions(-) diff --git a/openapi_codec/encode.py b/openapi_codec/encode.py index 4a6fbec..7ed41fc 100644 --- a/openapi_codec/encode.py +++ b/openapi_codec/encode.py @@ -1,5 +1,6 @@ import coreschema from collections import OrderedDict +from coreapi.document import Field from coreapi.compat import urlparse from openapi_codec.utils import get_method, get_encoding, get_location, get_links_from_document @@ -23,15 +24,61 @@ def generate_swagger_object(document): swagger['schemes'] = [parsed_url.scheme] swagger['paths'] = _get_paths_object(document) + swagger['definitions'] = _get_definitions(document) return swagger +def _get_definitions(document): + + definitions = dict() + links = _get_links(document) + + def get_field_def_data(field_item, defs): + + definition_data = { + 'type': 'object', + 'properties': {} + } + + if isinstance(field_item, coreschema.Object): + props = field_item.properties + else: + props = field_item.schema.properties + + for f_name, f_schema in props.iteritems(): + + if _get_field_type(f_schema) is 'object': + defs[f_name] = get_field_def_data(f_schema, defs) + definition_data['properties'][f_name] = { + '$ref': '#/definitions/{}'.format(f_name) + } + else: + definition_data['properties'][f_name] = { + 'type': _get_field_type(f_schema), + 'description': '' + } + + return definition_data + + for _, link, _ in links: + for field in link.fields: + field_type = _get_field_type(field) + if field_type == 'object': + definitions[field.name] = get_field_def_data(field, definitions) + + if field_type == 'array': + item_name = '{}_item'.format(field.name) + definitions[item_name] = get_field_def_data(field.schema.items, definitions) + + return definitions + + def _add_tag_prefix(item): operation_id, link, tags = item if tags: operation_id = tags[0] + '_' + operation_id - return (operation_id, link, tags) + return operation_id, link, tags def _get_links(document): @@ -114,8 +161,10 @@ def _get_field_type(field): # Deprecated return field.type - if field.schema is None: - return 'string' + if isinstance(field, Field): + cls = field.schema.__class__ + else: + cls = field.__class__ return { coreschema.String: 'string', @@ -124,7 +173,7 @@ def _get_field_type(field): coreschema.Boolean: 'boolean', coreschema.Array: 'array', coreschema.Object: 'object', - }.get(field.schema.__class__, 'string') + }.get(cls, 'string') def _get_parameters(link, encoding): @@ -160,8 +209,14 @@ def _get_parameters(link, encoding): 'description': field_description, 'type': field_type, } + if field_type == 'array': - schema_property['items'] = {'type': 'string'} + item_name = '{}_item'.format(field.name) + schema_property['items'] = {'$ref': '#/definitions/{}'.format(item_name)} + + if field_type == 'object': + schema_property = {'$ref': '#/definitions/{}'.format(field.name)} + properties[field.name] = schema_property if field.required: required.append(field.name) diff --git a/tests/test_encode.py b/tests/test_encode.py index 0ffd883..76adc3b 100644 --- a/tests/test_encode.py +++ b/tests/test_encode.py @@ -1,6 +1,8 @@ import coreapi import coreschema -from openapi_codec.encode import generate_swagger_object, _get_parameters + +from collections import OrderedDict +from openapi_codec.encode import generate_swagger_object, _get_parameters, _get_definitions from unittest import TestCase @@ -32,6 +34,11 @@ def test_schemes(self): expected = ['https'] self.assertEquals(self.swagger['schemes'], expected) + def test_definitions(self): + self.assertIn('definitions', self.swagger) + expected = dict() + self.assertEquals(self.swagger['definitions'], expected) + class TestPaths(TestCase): def setUp(self): @@ -101,3 +108,88 @@ def test_expected_fields(self): 'type': 'string' # Everything is a string for now. } self.assertEquals(self.swagger[0], expected) + + +class TestDefinitions(TestCase): + + def setUp(self): + + obj_props = OrderedDict() + obj_props['foo'] = coreschema.String() + obj_props['bar'] = coreschema.Integer() + + self.object_field = coreapi.Field( + name='dummy_object', + required=True, + location='form', + schema=coreschema.Object( + properties=obj_props + ) + ) + + self.array_field = coreapi.Field( + name='dummy_array', + location='form', + schema=coreschema.Array( + items=self.object_field + ) + ) + + self.link = coreapi.Link( + action='post', + url='/users/', + fields=[self.object_field, self.array_field] + ) + + self.document = coreapi.Document( + content={ + 'users': { + 'create': self.link, + } + } + ) + + self.definitions = _get_definitions(self.document) + self.parameters = _get_parameters(self.link, '') + self.swagger = generate_swagger_object(self.document) + + def test_basic_definitions(self): + + print self.definitions + + self.assertIn('definitions', self.swagger) + self.assertIn('dummy_object', self.definitions) + self.assertIn('dummy_array_item', self.definitions) + + expected_dummy_object_def = { + 'type': 'object', + 'properties': { + 'foo': {'type': 'string', 'description': ''}, + 'bar': {'type': 'integer', 'description': ''} + } + } + + self.assertEqual(self.definitions.get('dummy_object'), expected_dummy_object_def) + self.assertEqual(self.definitions.get('dummy_array_item'), expected_dummy_object_def) + + expected_dummy_parameters = [{ + 'schema': { + 'required': ['dummy_object'], + 'type': 'object', + 'properties': { + 'dummy_array': { + 'items': { + '$ref': '#/definitions/dummy_array_item' + }, + 'type': 'array', + 'description': '' + }, + 'dummy_object': { + '$ref': '#/definitions/dummy_object' + } + } + }, + 'name': 'data', + 'in': 'body' + }] + self.assertEqual(self.parameters, expected_dummy_parameters) diff --git a/tests/test_mappings.py b/tests/test_mappings.py index 87d23c2..8904543 100644 --- a/tests/test_mappings.py +++ b/tests/test_mappings.py @@ -202,7 +202,6 @@ def test_mapping(): coreapi.Field( name='example', location='body', - schema=coreschema.String() ) ] From a67ba3aed851fec952fc9694413df33c33c853ba Mon Sep 17 00:00:00 2001 From: Kirill Matyunin Date: Sun, 11 Jun 2017 14:55:34 -0400 Subject: [PATCH 02/20] fix unexpected print --- tests/test_encode.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_encode.py b/tests/test_encode.py index 76adc3b..5073f37 100644 --- a/tests/test_encode.py +++ b/tests/test_encode.py @@ -155,8 +155,6 @@ def setUp(self): def test_basic_definitions(self): - print self.definitions - self.assertIn('definitions', self.swagger) self.assertIn('dummy_object', self.definitions) self.assertIn('dummy_array_item', self.definitions) From 2391f7886ed97f1f0b98c1473b8b14c9440081d2 Mon Sep 17 00:00:00 2001 From: Kirill Matyunin Date: Sun, 11 Jun 2017 15:14:30 -0400 Subject: [PATCH 03/20] py2X<->py3X --- openapi_codec/encode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openapi_codec/encode.py b/openapi_codec/encode.py index 7ed41fc..fbb9906 100644 --- a/openapi_codec/encode.py +++ b/openapi_codec/encode.py @@ -46,7 +46,7 @@ def get_field_def_data(field_item, defs): else: props = field_item.schema.properties - for f_name, f_schema in props.iteritems(): + for f_name, f_schema in iter(props.items()): if _get_field_type(f_schema) is 'object': defs[f_name] = get_field_def_data(f_schema, defs) From aea9c8c24aa1e882947ffb61ca42532d10952ed3 Mon Sep 17 00:00:00 2001 From: Kirill Matyunin Date: Mon, 26 Jun 2017 10:28:12 -0400 Subject: [PATCH 04/20] OrderedDict instead of regular dict --- openapi_codec/encode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openapi_codec/encode.py b/openapi_codec/encode.py index fbb9906..cba60f5 100644 --- a/openapi_codec/encode.py +++ b/openapi_codec/encode.py @@ -31,7 +31,7 @@ def generate_swagger_object(document): def _get_definitions(document): - definitions = dict() + definitions = OrderedDict() links = _get_links(document) def get_field_def_data(field_item, defs): From 5a7bb2a8a7c2376dd224cb979ce6697ffd9f5360 Mon Sep 17 00:00:00 2001 From: Kirill Matyunin Date: Mon, 15 Jan 2018 09:44:13 -0500 Subject: [PATCH 05/20] adding py36 to tox envlist --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 3e9707d..9c2f2e1 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py35,py34,py27 +envlist = py35,py34,py36,py27 [testenv] deps = -rrequirements.txt commands = ./runtests From 532bfbdc04b8f412a9e2931e17bf7b12a6990235 Mon Sep 17 00:00:00 2001 From: Kirill Matyunin Date: Tue, 16 Jan 2018 19:11:29 -0500 Subject: [PATCH 06/20] preliminary changes --- openapi_codec/encode.py | 117 ++++++++++++++++++++++++++-------------- 1 file changed, 78 insertions(+), 39 deletions(-) diff --git a/openapi_codec/encode.py b/openapi_codec/encode.py index cba60f5..2b0038e 100644 --- a/openapi_codec/encode.py +++ b/openapi_codec/encode.py @@ -1,4 +1,7 @@ +import random +import string import coreschema + from collections import OrderedDict from coreapi.document import Field from coreapi.compat import urlparse @@ -23,53 +26,84 @@ def generate_swagger_object(document): if parsed_url.scheme: swagger['schemes'] = [parsed_url.scheme] - swagger['paths'] = _get_paths_object(document) swagger['definitions'] = _get_definitions(document) + swagger['paths'] = _get_paths_object(document, swagger['definitions']) return swagger -def _get_definitions(document): +def _get_or_update_definitions(update_def_data, update_def_name, definitions): - definitions = OrderedDict() - links = _get_links(document) + # Check if there's existing definition with same or props + clashing_def_names = filter(lambda d: d.startswith(update_def_name) or definitions.get(d) == update_def_data, definitions.keys()) - def get_field_def_data(field_item, defs): + for clashing_def_name in clashing_def_names: + clash_def_data = definitions.get(clashing_def_name) + if clash_def_data == update_def_data: + return clash_def_data + else: + if clashing_def_names: + rand_part = ''.join([random.choice(string.letters + string.digits) for _ in range(5)]) + update_def_name = '{}_{}'.format(update_def_name, rand_part) + definitions[update_def_name] = update_def_data + return update_def_data - definition_data = { - 'type': 'object', - 'properties': {} - } + return None - if isinstance(field_item, coreschema.Object): - props = field_item.properties + +def _get_field_definition_data(field_item, defs): + + definition_data = { + 'type': 'object', + 'properties': {} + } + + if isinstance(field_item, coreschema.Object): + props = field_item.properties + elif isinstance(field_item.schema, coreschema.schemas.Array): + props = field_item.schema.items.properties + else: + props = field_item.schema.properties + + for f_name, f_schema in iter(props.items()): + + if _get_field_type(f_schema) is 'object': + def_data = _get_or_update_definitions( + _get_field_definition_data(f_schema, defs), + '{}_def_item'.format(f_schema.name), + defs + ) + if def_data: + return def_data else: - props = field_item.schema.properties + definition_data['properties'][f_name] = { + 'type': _get_field_type(f_schema), + 'description': '' + } - for f_name, f_schema in iter(props.items()): + return definition_data - if _get_field_type(f_schema) is 'object': - defs[f_name] = get_field_def_data(f_schema, defs) - definition_data['properties'][f_name] = { - '$ref': '#/definitions/{}'.format(f_name) - } - else: - definition_data['properties'][f_name] = { - 'type': _get_field_type(f_schema), - 'description': '' - } - return definition_data +def _get_definitions(document): + + definitions = OrderedDict() + links = _get_links(document) for _, link, _ in links: for field in link.fields: field_type = _get_field_type(field) + + # Get field definition data if field_type == 'object': - definitions[field.name] = get_field_def_data(field, definitions) + def_data = _get_field_definition_data(field, definitions) + elif field_type == 'array': + def_data = _get_field_definition_data(field.schema.items, definitions) - if field_type == 'array': - item_name = '{}_item'.format(field.name) - definitions[item_name] = get_field_def_data(field.schema.items, definitions) + _get_or_update_definitions( + def_data, + '{}_def_item'.format(field.name), + definitions + ) return definitions @@ -107,7 +141,7 @@ def _get_links(document): return links -def _get_paths_object(document): +def _get_paths_object(document, definitions): paths = OrderedDict() links = _get_links(document) @@ -117,13 +151,13 @@ def _get_paths_object(document): paths[link.url] = OrderedDict() method = get_method(link) - operation = _get_operation(operation_id, link, tags) + operation = _get_operation(operation_id, link, tags, definitions) paths[link.url].update({method: operation}) return paths -def _get_operation(operation_id, link, tags): +def _get_operation(operation_id, link, tags, definitions): encoding = get_encoding(link) description = link.description.strip() summary = description.splitlines()[0] if description else None @@ -131,7 +165,7 @@ def _get_operation(operation_id, link, tags): operation = { 'operationId': operation_id, 'responses': _get_responses(link), - 'parameters': _get_parameters(link, encoding) + 'parameters': _get_parameters(link, encoding, definitions) } if description: @@ -176,7 +210,7 @@ def _get_field_type(field): }.get(cls, 'string') -def _get_parameters(link, encoding): +def _get_parameters(link, encoding, definitions): """ Generates Swagger Parameter Item object. """ @@ -210,12 +244,17 @@ def _get_parameters(link, encoding): 'type': field_type, } - if field_type == 'array': - item_name = '{}_item'.format(field.name) - schema_property['items'] = {'$ref': '#/definitions/{}'.format(item_name)} - - if field_type == 'object': - schema_property = {'$ref': '#/definitions/{}'.format(field.name)} + if field_type in ('object', 'array'): + definition_data = _get_field_definition_data(field, definitions).get('properties') + definition = filter(lambda d: definitions.get(d).get('properties') == definition_data, definitions)[0] + + schema_property = {'$ref': '#/definitions/{}'.format(definition)} + if field_type == 'array': + schema_property.pop('$ref') + schema_property['type'] = 'array' + schema_property['items'] = { + '$ref': '#/definitions/{}'.format(definition) + } properties[field.name] = schema_property if field.required: From 5be91820298fb97f0fc39e1c4125ac79e25d6bea Mon Sep 17 00:00:00 2001 From: Kirill Matyunin Date: Wed, 17 Jan 2018 21:31:46 -0500 Subject: [PATCH 07/20] bugfixing, enhancements, tests update --- openapi_codec/encode.py | 39 ++++++++------ tests/test_encode.py | 114 +++++++++++++++++++--------------------- 2 files changed, 79 insertions(+), 74 deletions(-) diff --git a/openapi_codec/encode.py b/openapi_codec/encode.py index 2b0038e..a8ec413 100644 --- a/openapi_codec/encode.py +++ b/openapi_codec/encode.py @@ -34,8 +34,11 @@ def generate_swagger_object(document): def _get_or_update_definitions(update_def_data, update_def_name, definitions): - # Check if there's existing definition with same or props - clashing_def_names = filter(lambda d: d.startswith(update_def_name) or definitions.get(d) == update_def_data, definitions.keys()) + # Check if there's existing definition with same name or props + clashing_def_names = filter( + lambda d: d.startswith(update_def_name) or definitions.get(d) == update_def_data, + definitions.keys() + ) for clashing_def_name in clashing_def_names: clash_def_data = definitions.get(clashing_def_name) @@ -48,8 +51,6 @@ def _get_or_update_definitions(update_def_data, update_def_name, definitions): definitions[update_def_name] = update_def_data return update_def_data - return None - def _get_field_definition_data(field_item, defs): @@ -63,7 +64,10 @@ def _get_field_definition_data(field_item, defs): elif isinstance(field_item.schema, coreschema.schemas.Array): props = field_item.schema.items.properties else: - props = field_item.schema.properties + try: + props = field_item.schema.properties + except AttributeError: + props = OrderedDict() for f_name, f_schema in iter(props.items()): @@ -98,6 +102,8 @@ def _get_definitions(document): def_data = _get_field_definition_data(field, definitions) elif field_type == 'array': def_data = _get_field_definition_data(field.schema.items, definitions) + else: + def_data = _get_field_definition_data(field, definitions) _get_or_update_definitions( def_data, @@ -245,16 +251,19 @@ def _get_parameters(link, encoding, definitions): } if field_type in ('object', 'array'): - definition_data = _get_field_definition_data(field, definitions).get('properties') - definition = filter(lambda d: definitions.get(d).get('properties') == definition_data, definitions)[0] - - schema_property = {'$ref': '#/definitions/{}'.format(definition)} - if field_type == 'array': - schema_property.pop('$ref') - schema_property['type'] = 'array' - schema_property['items'] = { - '$ref': '#/definitions/{}'.format(definition) - } + definition_data = _get_field_definition_data(field, definitions) + + definition_data = definition_data.get('properties') + defs = filter(lambda d: definitions.get(d).get('properties') == definition_data, definitions) + + if defs: + schema_property = {'$ref': '#/definitions/{}'.format(defs[0])} + if field_type == 'array': + schema_property.pop('$ref') + schema_property['type'] = 'array' + schema_property['items'] = { + '$ref': '#/definitions/{}'.format(defs[0]) + } properties[field.name] = schema_property if field.required: diff --git a/tests/test_encode.py b/tests/test_encode.py index 5073f37..39aec24 100644 --- a/tests/test_encode.py +++ b/tests/test_encode.py @@ -96,7 +96,7 @@ def setUp(self): location='query', schema=coreschema.String(description='A valid email address.') ) - self.swagger = _get_parameters(coreapi.Link(fields=[self.field]), encoding='') + self.swagger = _get_parameters(coreapi.Link(fields=[self.field]), encoding='', definitions=OrderedDict()) def test_expected_fields(self): self.assertEquals(len(self.swagger), 1) @@ -114,80 +114,76 @@ class TestDefinitions(TestCase): def setUp(self): - obj_props = OrderedDict() - obj_props['foo'] = coreschema.String() - obj_props['bar'] = coreschema.Integer() + # Clashing name + self.clashing_name = 'author' - self.object_field = coreapi.Field( - name='dummy_object', + # Schema objects + name_schema_obj = coreschema.schemas.Object( + properties=OrderedDict({'name': coreschema.schemas.String(description='name')}) + ) + bday_schema_obj = coreschema.schemas.Object( + properties=OrderedDict({'birthday': coreschema.schemas.String(description='birthday')}) + ) + + # Fields + author_field = coreapi.Field( + name='author', required=True, location='form', - schema=coreschema.Object( - properties=obj_props - ) + schema=name_schema_obj ) - - self.array_field = coreapi.Field( - name='dummy_array', + clashing_author_field = coreapi.Field( + name='author', + required=True, location='form', - schema=coreschema.Array( - items=self.object_field + schema=bday_schema_obj + ) + co_authoors_field = coreapi.Field( + name='co_authoors', + required=True, + location='form', + schema=coreschema.schemas.Array( + items=bday_schema_obj ) ) - self.link = coreapi.Link( - action='post', - url='/users/', - fields=[self.object_field, self.array_field] + # Link objects + v1_songs_link = coreapi.Link( + url='/api/v1/songs/', + action=u'post', + encoding=u'application/json', + fields=[author_field], + ) + v2_songs_link = coreapi.Link( + url='/api/v2/songs/', + action=u'post', + encoding=u'application/json', + fields=[clashing_author_field, co_authoors_field], ) + self.links = OrderedDict({ + 'v1': OrderedDict({'list': v1_songs_link}), + 'v2': OrderedDict({'list': v2_songs_link}) + }) + + # Coreapi document object self.document = coreapi.Document( - content={ - 'users': { - 'create': self.link, - } - } + 'test api', + content=self.links ) + # Init definitions and swagger object self.definitions = _get_definitions(self.document) - self.parameters = _get_parameters(self.link, '') self.swagger = generate_swagger_object(self.document) - def test_basic_definitions(self): + def test_clashing_names(self): + # Basic checks self.assertIn('definitions', self.swagger) - self.assertIn('dummy_object', self.definitions) - self.assertIn('dummy_array_item', self.definitions) - - expected_dummy_object_def = { - 'type': 'object', - 'properties': { - 'foo': {'type': 'string', 'description': ''}, - 'bar': {'type': 'integer', 'description': ''} - } - } + self.assertEqual(len(self.swagger['definitions'].keys()), 2, 'Unexpected definitions count') - self.assertEqual(self.definitions.get('dummy_object'), expected_dummy_object_def) - self.assertEqual(self.definitions.get('dummy_array_item'), expected_dummy_object_def) - - expected_dummy_parameters = [{ - 'schema': { - 'required': ['dummy_object'], - 'type': 'object', - 'properties': { - 'dummy_array': { - 'items': { - '$ref': '#/definitions/dummy_array_item' - }, - 'type': 'array', - 'description': '' - }, - 'dummy_object': { - '$ref': '#/definitions/dummy_object' - } - } - }, - 'name': 'data', - 'in': 'body' - }] - self.assertEqual(self.parameters, expected_dummy_parameters) + # Check nothing unexpected is in definitions + defs = filter( + lambda d: d.startswith('{}_def_item'.format(self.clashing_name)), self.swagger['definitions'].keys() + ) + self.assertEqual(len(defs), 2, 'Unexpected definitions count') \ No newline at end of file From fec8d0e2df833c608b71cfb54198c3df6770ba5b Mon Sep 17 00:00:00 2001 From: Kirill Matyunin Date: Wed, 17 Jan 2018 21:35:09 -0500 Subject: [PATCH 08/20] flake8 warning fix --- tests/test_encode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_encode.py b/tests/test_encode.py index 39aec24..f3432bc 100644 --- a/tests/test_encode.py +++ b/tests/test_encode.py @@ -186,4 +186,4 @@ def test_clashing_names(self): defs = filter( lambda d: d.startswith('{}_def_item'.format(self.clashing_name)), self.swagger['definitions'].keys() ) - self.assertEqual(len(defs), 2, 'Unexpected definitions count') \ No newline at end of file + self.assertEqual(len(defs), 2, 'Unexpected definitions count') From 41527828f5c53e4d016700d8ef233adde525b65d Mon Sep 17 00:00:00 2001 From: Kirill Matyunin Date: Wed, 17 Jan 2018 21:45:05 -0500 Subject: [PATCH 09/20] py2.7 <-> py3.X --- openapi_codec/encode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openapi_codec/encode.py b/openapi_codec/encode.py index a8ec413..04a6453 100644 --- a/openapi_codec/encode.py +++ b/openapi_codec/encode.py @@ -46,7 +46,7 @@ def _get_or_update_definitions(update_def_data, update_def_name, definitions): return clash_def_data else: if clashing_def_names: - rand_part = ''.join([random.choice(string.letters + string.digits) for _ in range(5)]) + rand_part = ''.join([random.choice(string.ascii_letters + string.digits) for _ in range(5)]) update_def_name = '{}_{}'.format(update_def_name, rand_part) definitions[update_def_name] = update_def_data return update_def_data From 09a06485fc063510eadfc8c537ac07ff8491f0ba Mon Sep 17 00:00:00 2001 From: Kirill Matyunin Date: Wed, 17 Jan 2018 21:59:36 -0500 Subject: [PATCH 10/20] tests run aftermath fixes --- openapi_codec/encode.py | 10 ++++++++-- tests/test_encode.py | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/openapi_codec/encode.py b/openapi_codec/encode.py index 04a6453..507869b 100644 --- a/openapi_codec/encode.py +++ b/openapi_codec/encode.py @@ -257,12 +257,18 @@ def _get_parameters(link, encoding, definitions): defs = filter(lambda d: definitions.get(d).get('properties') == definition_data, definitions) if defs: - schema_property = {'$ref': '#/definitions/{}'.format(defs[0])} + # Note: Python2.X <-> Python3.X + try: + def_name = defs[0] + except TypeError: + def_name = next(defs) + + schema_property = {'$ref': '#/definitions/{}'.format(def_name)} if field_type == 'array': schema_property.pop('$ref') schema_property['type'] = 'array' schema_property['items'] = { - '$ref': '#/definitions/{}'.format(defs[0]) + '$ref': '#/definitions/{}'.format(def_name) } properties[field.name] = schema_property diff --git a/tests/test_encode.py b/tests/test_encode.py index f3432bc..edc6569 100644 --- a/tests/test_encode.py +++ b/tests/test_encode.py @@ -186,4 +186,4 @@ def test_clashing_names(self): defs = filter( lambda d: d.startswith('{}_def_item'.format(self.clashing_name)), self.swagger['definitions'].keys() ) - self.assertEqual(len(defs), 2, 'Unexpected definitions count') + self.assertEqual(len(list(defs)), 2, 'Unexpected definitions count') From cd49ed42b1c791058620b8d72b47b8fb1aba7727 Mon Sep 17 00:00:00 2001 From: Kirill Matyunin Date: Wed, 17 Jan 2018 22:05:10 -0500 Subject: [PATCH 11/20] small change --- openapi_codec/encode.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openapi_codec/encode.py b/openapi_codec/encode.py index 507869b..2a4fe87 100644 --- a/openapi_codec/encode.py +++ b/openapi_codec/encode.py @@ -26,6 +26,9 @@ def generate_swagger_object(document): if parsed_url.scheme: swagger['schemes'] = [parsed_url.scheme] + if not parsed_url.netloc and not parsed_url.scheme: + swagger['host'] = document.url + swagger['definitions'] = _get_definitions(document) swagger['paths'] = _get_paths_object(document, swagger['definitions']) From 56dea307f89009b97eaccad2304bb73ca374df13 Mon Sep 17 00:00:00 2001 From: Kirill Matyunin Date: Wed, 17 Jan 2018 22:16:35 -0500 Subject: [PATCH 12/20] statement enhancement --- openapi_codec/encode.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openapi_codec/encode.py b/openapi_codec/encode.py index 2a4fe87..f019106 100644 --- a/openapi_codec/encode.py +++ b/openapi_codec/encode.py @@ -101,9 +101,7 @@ def _get_definitions(document): field_type = _get_field_type(field) # Get field definition data - if field_type == 'object': - def_data = _get_field_definition_data(field, definitions) - elif field_type == 'array': + if field_type == 'array': def_data = _get_field_definition_data(field.schema.items, definitions) else: def_data = _get_field_definition_data(field, definitions) From 2efbdcc961ec9d379d5942d8a5ad07351297188c Mon Sep 17 00:00:00 2001 From: Kirill Matyunin Date: Thu, 18 Jan 2018 07:02:28 -0500 Subject: [PATCH 13/20] adding docstrings --- openapi_codec/encode.py | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/openapi_codec/encode.py b/openapi_codec/encode.py index f019106..d88b53a 100644 --- a/openapi_codec/encode.py +++ b/openapi_codec/encode.py @@ -36,6 +36,10 @@ def generate_swagger_object(document): def _get_or_update_definitions(update_def_data, update_def_name, definitions): + """ + Updates definitions with provided data If definition is not present in map, returns found definition + data in case definition overlaps with existing one. + """ # Check if there's existing definition with same name or props clashing_def_names = filter( @@ -56,7 +60,9 @@ def _get_or_update_definitions(update_def_data, update_def_name, definitions): def _get_field_definition_data(field_item, defs): - + """ + Returns dictionary with field definition data. + """ definition_data = { 'type': 'object', 'properties': {} @@ -92,6 +98,9 @@ def _get_field_definition_data(field_item, defs): def _get_definitions(document): + """ + Returns dictionary with schema definitions. + """ definitions = OrderedDict() links = _get_links(document) @@ -116,6 +125,10 @@ def _get_definitions(document): def _add_tag_prefix(item): + """ + Returns tuple (operation_id, link, tags) with modified operation_id in case of tags. + """ + operation_id, link, tags = item if tags: operation_id = tags[0] + '_' + operation_id @@ -124,7 +137,7 @@ def _add_tag_prefix(item): def _get_links(document): """ - Return a list of (operation_id, link, [tags]) + Return a list of (operation_id, link, [tags]). """ # Extract all the links from the first or second level of the document. links = [] @@ -149,6 +162,9 @@ def _get_links(document): def _get_paths_object(document, definitions): + """ + Returns dictionary with document paths. + """ paths = OrderedDict() links = _get_links(document) @@ -165,6 +181,10 @@ def _get_paths_object(document, definitions): def _get_operation(operation_id, link, tags, definitions): + """ + Returns dictionary with operation parameters. + """ + encoding = get_encoding(link) description = link.description.strip() summary = description.splitlines()[0] if description else None @@ -187,6 +207,10 @@ def _get_operation(operation_id, link, tags, definitions): def _get_field_description(field): + """ + Returns field description. + """ + if getattr(field, 'description', None) is not None: # Deprecated return field.description @@ -198,6 +222,9 @@ def _get_field_description(field): def _get_field_type(field): + """ + Returns field string type by the given field schema. + """ if getattr(field, 'type', None) is not None: # Deprecated return field.type From 44ebcbed69d0f4b794e69a07bb0cd848e1b99aff Mon Sep 17 00:00:00 2001 From: Kirill Matyunin Date: Thu, 18 Jan 2018 07:23:42 -0500 Subject: [PATCH 14/20] more sophisticated checks added --- tests/test_encode.py | 49 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tests/test_encode.py b/tests/test_encode.py index edc6569..e78d4cd 100644 --- a/tests/test_encode.py +++ b/tests/test_encode.py @@ -187,3 +187,52 @@ def test_clashing_names(self): lambda d: d.startswith('{}_def_item'.format(self.clashing_name)), self.swagger['definitions'].keys() ) self.assertEqual(len(list(defs)), 2, 'Unexpected definitions count') + + v1_list_params = _get_parameters(self.links['v1']['list'], '', self.definitions) + v2_list_params = _get_parameters(self.links['v2']['list'], '', self.definitions) + + expected_def_name = filter( + lambda d: d.startswith('{}_def_item_'.format(self.clashing_name)), + self.definitions.keys() + )[0] + + expected_v1_list_params = [ + { + 'schema': { + 'required': ['author'], + 'type': 'object', + 'properties': { + 'author': { + '$ref': '#/definitions/author_def_item' + } + } + }, + 'name': 'data', + 'in': 'body' + } + ] + + expected_v2_list_params = [ + { + 'schema': { + 'required': ['author', 'co_authoors'], + 'type': 'object', + 'properties': { + 'co_authoors': { + 'items': { + '$ref': '#/definitions/{}'.format(expected_def_name) + }, + 'type': 'array' + }, + 'author': { + '$ref': '#/definitions/{}'.format(expected_def_name) + } + } + }, + 'name': 'data', + 'in': 'body' + } + ] + + self.assertEqual(v1_list_params, expected_v1_list_params, 'Unexpected definition params') + self.assertEqual(v2_list_params, expected_v2_list_params, 'Unexpected definition params') From 836aa93eb2a58b21dbf5c3e630ee6398927e517c Mon Sep 17 00:00:00 2001 From: Kirill Matyunin Date: Thu, 18 Jan 2018 07:26:36 -0500 Subject: [PATCH 15/20] fix typo --- tests/test_encode.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_encode.py b/tests/test_encode.py index e78d4cd..c2bedda 100644 --- a/tests/test_encode.py +++ b/tests/test_encode.py @@ -138,7 +138,7 @@ def setUp(self): location='form', schema=bday_schema_obj ) - co_authoors_field = coreapi.Field( + co_authors_field = coreapi.Field( name='co_authoors', required=True, location='form', @@ -158,7 +158,7 @@ def setUp(self): url='/api/v2/songs/', action=u'post', encoding=u'application/json', - fields=[clashing_author_field, co_authoors_field], + fields=[clashing_author_field, co_authors_field], ) self.links = OrderedDict({ From efd75161e856c98f16ee2e97dc17588864105a3a Mon Sep 17 00:00:00 2001 From: Kirill Matyunin Date: Thu, 18 Jan 2018 07:30:53 -0500 Subject: [PATCH 16/20] py2.X <-> py3.X --- tests/test_encode.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/test_encode.py b/tests/test_encode.py index c2bedda..a5bf06e 100644 --- a/tests/test_encode.py +++ b/tests/test_encode.py @@ -194,7 +194,13 @@ def test_clashing_names(self): expected_def_name = filter( lambda d: d.startswith('{}_def_item_'.format(self.clashing_name)), self.definitions.keys() - )[0] + ) + + # NOTE: mind the Python3 + try: + expected_def_name = expected_def_name[0] + except TypeError: + expected_def_name = next(expected_def_name) expected_v1_list_params = [ { From cb565a3de43c5c9b55d7af9eba76117f33691f48 Mon Sep 17 00:00:00 2001 From: Kirill Matyunin Date: Thu, 18 Jan 2018 10:31:39 -0500 Subject: [PATCH 17/20] final changes --- openapi_codec/encode.py | 10 ++++------ tests/test_encode.py | 12 ++---------- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/openapi_codec/encode.py b/openapi_codec/encode.py index d88b53a..bdb1d8f 100644 --- a/openapi_codec/encode.py +++ b/openapi_codec/encode.py @@ -42,17 +42,15 @@ def _get_or_update_definitions(update_def_data, update_def_name, definitions): """ # Check if there's existing definition with same name or props - clashing_def_names = filter( - lambda d: d.startswith(update_def_name) or definitions.get(d) == update_def_data, - definitions.keys() - ) + clashing_def_names = \ + [d for d in definitions.keys() if d.startswith(update_def_name) or definitions.get(d) == update_def_data] for clashing_def_name in clashing_def_names: clash_def_data = definitions.get(clashing_def_name) if clash_def_data == update_def_data: return clash_def_data else: - if clashing_def_names: + if list(clashing_def_names): rand_part = ''.join([random.choice(string.ascii_letters + string.digits) for _ in range(5)]) update_def_name = '{}_{}'.format(update_def_name, rand_part) definitions[update_def_name] = update_def_data @@ -282,7 +280,7 @@ def _get_parameters(link, encoding, definitions): definition_data = _get_field_definition_data(field, definitions) definition_data = definition_data.get('properties') - defs = filter(lambda d: definitions.get(d).get('properties') == definition_data, definitions) + defs = [d for d in definitions if definitions.get(d).get('properties') == definition_data] if defs: # Note: Python2.X <-> Python3.X diff --git a/tests/test_encode.py b/tests/test_encode.py index a5bf06e..4c68b44 100644 --- a/tests/test_encode.py +++ b/tests/test_encode.py @@ -191,16 +191,8 @@ def test_clashing_names(self): v1_list_params = _get_parameters(self.links['v1']['list'], '', self.definitions) v2_list_params = _get_parameters(self.links['v2']['list'], '', self.definitions) - expected_def_name = filter( - lambda d: d.startswith('{}_def_item_'.format(self.clashing_name)), - self.definitions.keys() - ) - - # NOTE: mind the Python3 - try: - expected_def_name = expected_def_name[0] - except TypeError: - expected_def_name = next(expected_def_name) + expected_def_name = \ + [d for d in self.definitions.keys() if d.startswith('{}_def_item_'.format(self.clashing_name))][0] expected_v1_list_params = [ { From ca20480bb762ebe5c6f3cd58181ee5091b57dc2f Mon Sep 17 00:00:00 2001 From: Kirill Matyunin Date: Thu, 18 Jan 2018 11:31:33 -0500 Subject: [PATCH 18/20] trying to fix flake8 issue --- tests/test_encode.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/tests/test_encode.py b/tests/test_encode.py index 4c68b44..5675c23 100644 --- a/tests/test_encode.py +++ b/tests/test_encode.py @@ -139,7 +139,7 @@ def setUp(self): schema=bday_schema_obj ) co_authors_field = coreapi.Field( - name='co_authoors', + name='co_authors', required=True, location='form', schema=coreschema.schemas.Array( @@ -200,9 +200,7 @@ def test_clashing_names(self): 'required': ['author'], 'type': 'object', 'properties': { - 'author': { - '$ref': '#/definitions/author_def_item' - } + 'author': {'$ref': '#/definitions/author_def_item'} } }, 'name': 'data', @@ -216,15 +214,11 @@ def test_clashing_names(self): 'required': ['author', 'co_authoors'], 'type': 'object', 'properties': { - 'co_authoors': { - 'items': { - '$ref': '#/definitions/{}'.format(expected_def_name) - }, + 'co_authors': { + 'items': {'$ref': '#/definitions/{}'.format(expected_def_name)}, 'type': 'array' }, - 'author': { - '$ref': '#/definitions/{}'.format(expected_def_name) - } + 'author': {'$ref': '#/definitions/{}'.format(expected_def_name)} } }, 'name': 'data', From 84a1d7b1ebd38a41a8d4940c7455706221c4c79c Mon Sep 17 00:00:00 2001 From: Kirill Matyunin Date: Thu, 18 Jan 2018 11:37:46 -0500 Subject: [PATCH 19/20] fixing tests once again --- openapi_codec/encode.py | 2 +- tests/test_encode.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openapi_codec/encode.py b/openapi_codec/encode.py index bdb1d8f..8a0042c 100644 --- a/openapi_codec/encode.py +++ b/openapi_codec/encode.py @@ -280,7 +280,7 @@ def _get_parameters(link, encoding, definitions): definition_data = _get_field_definition_data(field, definitions) definition_data = definition_data.get('properties') - defs = [d for d in definitions if definitions.get(d).get('properties') == definition_data] + defs = filter(lambda d: definitions.get(d).get('properties') == definition_data, definitions) if defs: # Note: Python2.X <-> Python3.X diff --git a/tests/test_encode.py b/tests/test_encode.py index 5675c23..2d8fe0b 100644 --- a/tests/test_encode.py +++ b/tests/test_encode.py @@ -211,7 +211,7 @@ def test_clashing_names(self): expected_v2_list_params = [ { 'schema': { - 'required': ['author', 'co_authoors'], + 'required': ['author', 'co_authors'], 'type': 'object', 'properties': { 'co_authors': { From 34d9cd5d9c292f4b18846cf02194a68a062c907d Mon Sep 17 00:00:00 2001 From: Kirill Matyunin Date: Thu, 18 Jan 2018 11:45:21 -0500 Subject: [PATCH 20/20] flake8 fix --- tests/test_encode.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/test_encode.py b/tests/test_encode.py index 2d8fe0b..1d5c43f 100644 --- a/tests/test_encode.py +++ b/tests/test_encode.py @@ -197,11 +197,13 @@ def test_clashing_names(self): expected_v1_list_params = [ { 'schema': { - 'required': ['author'], - 'type': 'object', - 'properties': { - 'author': {'$ref': '#/definitions/author_def_item'} - } + 'required': ['author'], + 'type': 'object', + 'properties': { + 'author': { + '$ref': '#/definitions/author_def_item' + } + } }, 'name': 'data', 'in': 'body'