def doc(): return Document(title='original', content={ 'nested': Document( content={ 'follow': Link(url='mock://example.com', action='get'), 'action': Link(url='mock://example.com', action='post', transform='inplace', fields=['foo']), 'create': Link(url='mock://example.com', action='post', fields=['foo']), 'update': Link(url='mock://example.com', action='put', fields=['foo']), 'delete': Link(url='mock://example.com', action='delete') }) })
def get_response_object(self, response_serializer_class, description): fields = [] serializer = response_serializer_class() nested_obj = {} for field in serializer.fields.values(): # If field is a serializer, attempt to get its schema. if isinstance(field, serializers.Serializer): subfield_schema = self.get_response_object( field.__class__, None)[0].get('schema') # If the schema exists, use it as the nested_obj if subfield_schema is not None: nested_obj[field.field_name] = subfield_schema nested_obj[ field.field_name]['description'] = field.help_text continue # Otherwise, carry-on and use the field's schema. fallback_schema = self.fallback_schema_from_field(field) fields.append( Field( name=field.field_name, location='form', required=field.required, schema=fallback_schema if fallback_schema else field_to_schema(field), )) res = _get_parameters(Link(fields=fields), None) if not res: if nested_obj: return { 'description': description, 'schema': { 'type': 'object', 'properties': nested_obj } }, {} else: return {}, {} schema = res[0]['schema'] schema['properties'].update(nested_obj) response_schema = {'description': description, 'schema': schema} error_status_codes = {} response_meta = getattr(response_serializer_class, 'Meta', None) for status_code, description in getattr(response_meta, 'error_status_codes', {}).items(): error_status_codes[status_code] = {'description': description} return response_schema, error_status_codes
def test_delete(monkeypatch, http): def mockreturn(self, request): return MockResponse(b'') monkeypatch.setattr(requests.Session, 'send', mockreturn) link = Link(url='http://example.org', action='delete') doc = http.transition(link, decoders) assert doc is None
def test_get(monkeypatch, http): def mockreturn(self, request): return MockResponse(b'{"_type": "document", "example": 123}') monkeypatch.setattr(requests.Session, 'send', mockreturn) link = Link(url='http://example.org', action='get') doc = http.transition(link, decoders) assert doc == {'example': 123}
def test_get_with_parameters(monkeypatch, http): def mockreturn(self, request): insert = request.path_url.encode('utf-8') return MockResponse(b'{"_type": "document", "url": "' + insert + b'"}') monkeypatch.setattr(requests.Session, 'send', mockreturn) link = Link(url='http://example.org', action='get') doc = http.transition(link, decoders, params={'example': 'abc'}) assert doc == {'url': '/?example=abc'}
def test_document_equality(doc): assert doc == { 'integer': 123, 'dict': { 'key': 'value' }, 'list': [1, 2, 3], 'link': Link(url='/', action='post', transform='inplace', fields=[ 'optional', Field('required', required=True, location='path') ]), 'nested': { 'child': Link(url='/123') } }
def get_note(identifier): """ Return a Document object for a single note instance. """ note = notes[identifier] return Document( url='/' + identifier, title='Note', content={ 'description': note['description'], 'complete': note['complete'], 'edit': Link(action='put', fields=[Field(name='description'), Field(name='complete')]), 'delete': Link(action='delete') })
def test_post(monkeypatch, http): def mockreturn(self, request): codec = CoreJSONCodec() body = force_text(request.body) content = codec.encode(Document(content={'data': json.loads(body)})) return MockResponse(content) monkeypatch.setattr(requests.Session, 'send', mockreturn) link = Link(url='http://example.org', action='post') doc = http.transition(link, decoders, params={'example': 'abc'}) assert doc == {'data': {'example': 'abc'}}
def test_get_with_path_parameter(monkeypatch, http): def mockreturn(self, request): insert = request.url.encode('utf-8') return MockResponse(b'{"_type": "document", "example": "' + insert + b'"}') monkeypatch.setattr(requests.Session, 'send', mockreturn) link = Link(url='http://example.org/{user_id}/', action='get', fields=[Field(name='user_id', location='path')]) doc = http.transition(link, decoders, params={'user_id': 123}) assert doc == {'example': 'http://example.org/123/'}
def _get_link_from_method(method): spore_name, spore_method = method action, *keys = spore_name.split('_') link = Link(url=spore_method.get('path'), action=spore_method.get('method').lower(), title=spore_method.get( 'description', spore_name.replace('_', ' ').capitalize()), authentication=spore_method.get('authentication', False), formats=spore_method.get('formats', []), fields=_get_fields_from_method(spore_method), description=spore_method.get('documentation', '')) return action, keys, link
def get_notes(): """ Return the top level Document object, containing all the note instances. """ return Document( url='/', title='Notes', content={ 'notes': [get_note(identifier) for identifier in reversed(notes.keys())], 'add_note': Link(action='post', fields=[Field(name='description', required=True)]) })
def doc(): return Document(url='http://example.org', title='Example', content={ 'integer': 123, 'dict': { 'key': 'value' }, 'list': [1, 2, 3], 'link': Link(url='/', action='post', transform='inplace', fields=[ 'optional', Field('required', required=True, location='path') ]), 'nested': { 'child': Link(url='/123') } })
def get_link(route: Route) -> Link: """ Given a single route, return a Link instance containing all the information needed to expose that route in an API Schema. """ path, method, view = route view_signature = inspect.signature(view) uritemplate = URITemplate(path) description = _get_link_description(view) fields = [] for param in view_signature.parameters.values(): if param.annotation is inspect.Signature.empty: annotated_type = str else: annotated_type = param.annotation location = None required = False param_schema = _annotated_type_to_coreschema(annotated_type) if param.name in uritemplate.variable_names: location = 'path' required = True elif (annotated_type in primitive_types) or issubclass( annotated_type, schema_types): if method in ('POST', 'PUT', 'PATCH'): if issubclass(annotated_type, schema.Object): location = 'body' required = True else: location = 'form' else: location = 'query' if location is not None: field = Field(name=param.name, location=location, required=required, schema=param_schema) fields.append(field) return Link(url=path, action=method, description=description, fields=fields)
def get_response_object(self, response_serializer_class, description): fields = [] serializer = response_serializer_class() nested_obj = {} for field in serializer.fields.values(): if isinstance(field, serializers.Serializer): nested_obj[field.field_name] = self.get_response_object( field.__class__, None)[0]['schema'] nested_obj[field.field_name]['description'] = field.help_text continue fields.append( Field(name=field.field_name, location='form', required=field.required, schema=field_to_schema(field))) res = _get_parameters(Link(fields=fields), None) if not res: if nested_obj: return { 'description': description, 'schema': { 'type': 'object', 'properties': nested_obj } }, {} else: return {}, {} schema = res[0]['schema'] schema['properties'].update(nested_obj) response_schema = {'description': description, 'schema': schema} error_status_codes = {} response_meta = getattr(response_serializer_class, 'Meta', None) for status_code, description in getattr(response_meta, 'error_status_codes', {}).items(): error_status_codes[status_code] = {'description': description} return response_schema, error_status_codes
def test_mocking(self): content = { 'test': { 'post_data': Link(url='/post_data/', action='post', fields=[Field('data', location='body')]), } } schema = Document(title='test', content=content) mock.add(schema, ['test', 'post_data'], {"a": 1}) client = DjangoCoreAPIClient() doc = client.action(schema, ['test', 'post_data'], params={'data': { 'test': 'cat' }}) self.assertEqual(doc, {"a": 1})
def test_post_data(self): content = { 'test': { 'post_data': Link(url='/post_data/', action='post', fields=[Field('data', location='body')]), } } schema = Document(title='test', content=content) client = DjangoCoreAPIClient() doc = client.action(schema, ['test', 'post_data'], params={'data': { 'test': 'cat' }}) self.assertIsNotNone(doc)
def get_link(self, path, method, base_url): link = super().get_link(path, method, base_url) fields = [ Field('time_type', location='query', required=True, schema=coreschema.Enum(enum=['year', 'month'])), Field('time_value', location='query', required=True, schema=coreschema.String()), ] fields = tuple(fields) link = Link(url=link.url, action=link.action, encoding=link.encoding, fields=fields, description=link.description) document.Link() return link
def get_link(self, path, method, base_url): link = super().get_link(path, method, base_url) # 无法指定bill_ids数组中的元素类型,这里元素定义为int但实际为str,无法处理... batch_del_fields = [ Field('bill_ids', location='form', required=True, schema=coreschema.Array(items=coreschema.Integer(), unique_items=True)) ] fields = link.fields if link.url == '/api/v1/bills/batch/' and method.lower() == 'delete': fields = tuple(batch_del_fields) link = Link(url=link.url, action=link.action, encoding=link.encoding, fields=fields, description=link.description) document.Link() return link
def test_document_data_and_links_properties(): doc = Document(content={'a': 1, 'b': 2, 'c': Link(), 'd': Link()}) assert sorted(list(doc.data.keys())) == ['a', 'b'] assert sorted(list(doc.links.keys())) == ['c', 'd']
def test_object_data_and_links_properties(): obj = Object({'a': 1, 'b': 2, 'c': Link(), 'd': Link()}) assert sorted(list(obj.data.keys())) == ['a', 'b'] assert sorted(list(obj.links.keys())) == ['c', 'd']
from coreapi import Document, Link, Field CONTENT_BASE_URL = '/api/v1/content/' SCHEMA = Document( title='YouTube Lab API', content={ 'Content Source': { 'list_all_sources': Link( url=CONTENT_BASE_URL + 'sources/', action='get', description=""" List all Content sources in the database Returns a list with all playlists and channels in the database """, ), 'add_new_source': Link(url=CONTENT_BASE_URL + 'sources/', action='post', description=""" Add a new content source ex. playlist Provide a url to the playlist or channel you want to Crawl """, fields=[ Field( name='url', required=True, location='formData',
def _parse_document(data, base_url=None): schema_url = base_url base_url = _get_document_base_url(data, base_url) info = _get_dict(data, 'info') title = _get_string(info, 'title') paths = _get_dict(data, 'paths') content = {} for path in paths.keys(): url = urlparse.urljoin(base_url, path.lstrip('/')) spec = _get_dict(paths, path) default_parameters = get_dicts(_get_list(spec, 'parameters')) for action in spec.keys(): action = action.lower() if action not in ('get', 'put', 'post', 'delete', 'options', 'head', 'patch'): continue operation = _get_dict(spec, action) # Determine any fields on the link. fields = [] parameters = get_dicts(_get_list(operation, 'parameters', default_parameters), dereference_using=data) for parameter in parameters: name = _get_string(parameter, 'name') location = _get_string(parameter, 'in') required = _get_bool(parameter, 'required', default=(location == 'path')) description = _get_string(parameter, 'description') if location == 'body': schema = _get_dict(parameter, 'schema', dereference_using=data) expanded = _expand_schema(schema) if expanded is not None: expanded_fields = [ Field(name=field_name, location='form', required=is_required, description=description) for field_name, is_required in expanded if not any([field.name == name for field in fields]) ] fields += expanded_fields else: field = Field(name=name, location='body', required=True, description=description) fields.append(field) else: field = Field(name=name, location=location, required=required, description=description) fields.append(field) link = Link(url=url, action=action, fields=fields) # Add the link to the document content. tags = get_strings(_get_list(operation, 'tags')) operation_id = _get_string(operation, 'operationId') if tags: for tag in tags: if tag not in content: content[tag] = {} content[tag][operation_id] = link else: content[operation_id] = link return Document(url=schema_url, title=title, content=content)
def _parse_document(data, base_url=None): schema_url = base_url base_url = _get_document_base_url(data, base_url) info = _get_dict(data, 'info') title = _get_string(info, 'title') description = _get_string(info, 'description') consumes = get_strings(_get_list(data, 'consumes')) paths = _get_dict(data, 'paths') content = {} for path in paths.keys(): url = base_url + path.lstrip('/') spec = _get_dict(paths, path) default_parameters = get_dicts(_get_list(spec, 'parameters')) for action in spec.keys(): action = action.lower() if action not in ('get', 'put', 'post', 'delete', 'options', 'head', 'patch'): continue operation = _get_dict(spec, action) # Determine any fields on the link. has_body = False has_form = False fields = [] parameters = get_dicts(_get_list(operation, 'parameters', default_parameters), dereference_using=data) for parameter in parameters: name = _get_string(parameter, 'name') location = _get_string(parameter, 'in') required = _get_bool(parameter, 'required', default=(location == 'path')) if location == 'body': has_body = True schema = _get_dict(parameter, 'schema', dereference_using=data) expanded = _expand_schema(schema) if expanded is not None: # TODO: field schemas. expanded_fields = [ Field(name=field_name, location='form', required=is_required, schema=coreschema.String( description=field_description)) for field_name, is_required, field_description in expanded if not any( [field.name == field_name for field in fields]) ] fields += expanded_fields else: # TODO: field schemas. field_description = _get_string( parameter, 'description') field = Field(name=name, location='body', required=required, schema=coreschema.String( description=field_description)) fields.append(field) else: if location == 'formData': has_form = True location = 'form' field_description = _get_string(parameter, 'description') # TODO: field schemas. field = Field(name=name, location=location, required=required, schema=coreschema.String( description=field_description)) fields.append(field) link_consumes = get_strings( _get_list(operation, 'consumes', consumes)) encoding = '' if has_body: encoding = _select_encoding(link_consumes) elif has_form: encoding = _select_encoding(link_consumes, form=True) link_title = _get_string(operation, 'summary') link_description = _get_string(operation, 'description') link = Link(url=url, action=action, encoding=encoding, fields=fields, title=link_title, description=link_description) # Add the link to the document content. tags = get_strings(_get_list(operation, 'tags')) operation_id = _get_string(operation, 'operationId') if tags: tag = tags[0] prefix = tag + '_' if operation_id.startswith(prefix): operation_id = operation_id[len(prefix):] if tag not in content: content[tag] = {} content[tag][operation_id] = link else: content[operation_id] = link return Document(url=schema_url, title=title, description=description, content=content, media_type='application/openapi+json')
Route('/static/{path}', 'GET', serve_static) ] app = App(routes=routes) client = TestClient(app) expected = Schema( url='/schema/', content={ 'list_todo': Link(url='/todo/', action='GET', description='list_todo description', fields=[ Field(name='search', location='query', required=False, schema=coreschema.String()) ]), 'add_todo': Link(url='/todo/', action='POST', description='add_todo description\nMultiple indented lines', fields=[ Field(name='note', location='body', required=True, schema=coreschema.String()) ]), 'show_todo':
def test_dotted_path_notation_with_invalid_key(): doc = Document(content={'rows': [Document(content={'edit': Link()})]}) keys = coerce_key_types(doc, ['dummy', '0', 'edit']) assert keys == ['dummy', '0', 'edit']
def test_dotted_path_notation_with_invalid_array_lookup(): doc = Document(content={'rows': [Document(content={'edit': Link()})]}) keys = coerce_key_types(doc, ['rows', 'zero', 'edit']) assert keys == ['rows', 'zero', 'edit']
def test_dotted_path_notation(): doc = Document(content={'rows': [Document(content={'edit': Link()})]}) keys = coerce_key_types(doc, ['rows', 0, 'edit']) assert keys == ['rows', 0, 'edit']
def test_html_link_rendering(): doc = Document(content={'link': Link(url='/test/')}) content = HTMLCodec().dump(doc) assert 'coreapi-link' in content assert 'href="/test/"' in content
def test_link_does_not_support_property_assignment(): link = Link() with pytest.raises(TypeError): link.integer = 456
def to_representation(self, value): url = super(CoreLinkField, self).to_representation(value) return Link(url=url, action=self.action, fields=self.fields, transition=self.transition)
'register': Link(url=BASE_AUTH_URL + 'users/create/', action='post', description=""" Register a new user Note: You have to activate your email address, before logging in. """, fields=[ Field( name='email', required=True, location='formData', ), Field( name='password', required=True, location='formData', ), Field( name='first_name', required=False, location='formData', ), Field( name='last_name', required=False, location='formData', ), ]), 'activate':