Skip to content
This repository was archived by the owner on Mar 18, 2019. It is now read-only.

Version 3 #156

Open
wants to merge 61 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
9bc4fbb
Drop 'transform', and remove 'inplace' transformations.
tomchristie Jan 29, 2018
30e4cb1
Merge branch 'master' into remove-transform
tomchristie Jan 30, 2018
b9f9dd7
Drop action= and encoding= parameters on client, in favor of override
tomchristie Jan 30, 2018
4fb5e88
Drop HTTPTransport 'credentials' in favor of 'auth'
tomchristie Jan 30, 2018
b0eb95c
Drop HTTP request_callback and response_callback in favor of custom s…
tomchristie Jan 30, 2018
ad00b0a
Drop unused import
tomchristie Jan 30, 2018
15dcdc9
Removing data from documents. Drop 'Array'.
tomchristie Jan 30, 2018
f3c8dde
Drop CallbackAdapater. We don't use it anywhere. Could later farm it …
tomchristie Jan 31, 2018
7d06d93
Drop DomainCredentials, now that we have proper auth classes
tomchristie Jan 31, 2018
ced53cf
Refactor to use session.request
tomchristie Jan 31, 2018
93946cd
Drop unneccessary default parameters
tomchristie Jan 31, 2018
e99f71b
Move _guess_extension out of utils and into codes/download
tomchristie Jan 31, 2018
61d4198
Drop 'Document.clone'
tomchristie Feb 13, 2018
bc6aa9e
Add typesys. Add OpenAPI, JSONSchema.
tomchristie Feb 14, 2018
df86966
.errors always a classmethod
tomchristie Feb 14, 2018
f106bd0
Create absolute URLs for Links from OpenAPI
tomchristie Feb 14, 2018
6b3296a
Drop coreschema
tomchristie Feb 14, 2018
8644689
Ordered schema representations. Support OpenAPI encoding.
tomchristie Feb 14, 2018
325d032
Include yaml in requirements
tomchristie Feb 14, 2018
5984a33
Drop coreschema
tomchristie Feb 14, 2018
9444bc0
Preserve ordering of OpenAPI 'paths' across python versions
tomchristie Feb 14, 2018
b3fa5c2
Default to JSON-flavoured OpenAPI
tomchristie Feb 15, 2018
235f50e
Schemas in tests should be bytestrings
tomchristie Feb 15, 2018
dc4edea
Ensure OpenAPI.encode returns bytestrings
tomchristie Feb 15, 2018
c85818c
dict is ordered in Python 3.6
tomchristie Feb 15, 2018
566abbe
Force ordering of document using in test case
tomchristie Feb 15, 2018
6530dc2
Enforced ordering in Object
tomchristie Feb 20, 2018
09518cd
Drop commented-out YAML code
tomchristie Feb 20, 2018
73c5015
Drop itypes from transports/codecs/client
tomchristie Feb 20, 2018
2d6e0bc
Drop BaseCodec dump/load/supports
tomchristie Feb 20, 2018
48784f4
Add document.version
tomchristie Feb 20, 2018
5e19106
Drop itypes from Error
tomchristie Feb 20, 2018
d1c006f
Drop itypes from Link
tomchristie Feb 20, 2018
b52553e
Drop itypes
tomchristie Feb 20, 2018
b1e3418
Drop client.reload
tomchristie Feb 20, 2018
7b0cf7d
_to_immutable -> _to_objects
tomchristie Feb 20, 2018
e663b9f
Drop reload from tests
tomchristie Feb 20, 2018
f09ca74
'enum' defined on types
tomchristie Feb 20, 2018
93482e9
Add openapi codec to setup
tomchristie Feb 20, 2018
54199e3
Add Field.title
tomchristie Feb 20, 2018
8c1748e
Use Link.method. Put .action towards deprecation.
tomchristie Feb 20, 2018
ae79ef5
Plain old classes for typesys
tomchristie Feb 21, 2018
01a0a51
Add typesys.Ref
tomchristie Feb 21, 2018
46a9559
assert types on __init__
tomchristie Feb 21, 2018
f772992
Tests and refinements to typesys
tomchristie Feb 21, 2018
a50e0e3
exact_items validation for Array
tomchristie Feb 21, 2018
b56b1d6
Add allow_null to typesys
tomchristie Feb 21, 2018
c58ad81
Work on JSON Schema and typesys
tomchristie Mar 1, 2018
15f24dc
Work on Union and JSONSchema
tomchristie Mar 1, 2018
cb8e273
Cleanups on typesys
tomchristie Mar 1, 2018
f19c680
Uniqueness checking always that always uses set()
tomchristie Mar 5, 2018
a87f66a
Add Section, Add Link.id
tomchristie Mar 5, 2018
382eef8
Slugify tag names to get section ids
tomchristie Mar 5, 2018
4a5993a
Add path_links and query_links to Section
tomchristie Mar 6, 2018
5e3de6c
Add path_fields and query_fields to Link
tomchristie Mar 6, 2018
1d7a121
Tweak
tomchristie Mar 6, 2018
58ec296
Fix 'security' in OpenAPI
tomchristie Mar 6, 2018
947e03b
If no operationId then use summary for Link.id
tomchristie Mar 6, 2018
7486932
Building out OpenAPI v3 spec
tomchristie Mar 6, 2018
69a5a69
First pass at parsing OpenAPI requestBody schemas
tomchristie Mar 6, 2018
3a793b9
Add initial requestBody schema support
tomchristie Mar 6, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions coreapi/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# coding: utf-8
from coreapi import auth, codecs, exceptions, transports, utils
from coreapi import auth, codecs, exceptions, transports, typesys, utils
from coreapi.client import Client
from coreapi.document import Array, Document, Link, Object, Error, Field
from coreapi.document import Document, Link, Object, Error, Field, Array


__version__ = '2.3.3'
__version__ = '3.0.0'
__all__ = [
'Array', 'Document', 'Link', 'Object', 'Error', 'Field',
'Document', 'Link', 'Object', 'Error', 'Field', 'Array',
'Client',
'auth', 'codecs', 'exceptions', 'transports', 'utils',
'auth', 'codecs', 'exceptions', 'transports', 'typesys', 'utils',
]
52 changes: 13 additions & 39 deletions coreapi/client.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
from coreapi import codecs, exceptions, transports
from coreapi.compat import string_types
from coreapi.document import Document, Link
from coreapi.document import Link
from coreapi.utils import determine_transport, get_installed_codecs
import collections
import itypes


LinkAncestor = collections.namedtuple('LinkAncestor', ['document', 'keys'])


def _lookup_link(document, keys):
"""
Validates that keys looking up a link are correct.

Returns a two-tuple of (link, link_ancestors).
Returns the Link.
"""
if not isinstance(keys, (list, tuple)):
msg = "'keys' must be a list of strings or ints."
Expand All @@ -28,17 +23,13 @@ def _lookup_link(document, keys):
# 'node' is the link we're calling the action for.
# 'document_keys' is the list of keys to the link's parent document.
node = document
link_ancestors = [LinkAncestor(document=document, keys=[])]
for idx, key in enumerate(keys):
try:
node = node[key]
except (KeyError, IndexError, TypeError):
index_string = ''.join('[%s]' % repr(key).strip('u') for key in keys)
msg = 'Index %s did not reference a link. Key %s was not found.'
raise exceptions.LinkLookupError(msg % (index_string, repr(key).strip('u')))
if isinstance(node, Document):
ancestor = LinkAncestor(document=node, keys=keys[:idx + 1])
link_ancestors.append(ancestor)

# Ensure that we've correctly indexed into a link.
if not isinstance(node, Link):
Expand All @@ -48,7 +39,7 @@ def _lookup_link(document, keys):
msg % (index_string, type(node).__name__)
)

return (node, link_ancestors)
return node


def _validate_parameters(link, parameters):
Expand Down Expand Up @@ -95,7 +86,7 @@ def get_default_transports(auth=None, session=None):
]


class Client(itypes.Object):
class Client(object):
def __init__(self, decoders=None, transports=None, auth=None, session=None):
assert transports is None or auth is None, (
"Cannot specify both 'auth' and 'transports'. "
Expand All @@ -106,8 +97,8 @@ def __init__(self, decoders=None, transports=None, auth=None, session=None):
decoders = get_default_decoders()
if transports is None:
transports = get_default_transports(auth=auth, session=session)
self._decoders = itypes.List(decoders)
self._transports = itypes.List(transports)
self._decoders = list(decoders)
self._transports = list(transports)

@property
def decoders(self):
Expand All @@ -118,7 +109,7 @@ def transports(self):
return self._transports

def get(self, url, format=None, force_codec=False):
link = Link(url, action='get')
link = Link(url, method='get')

decoders = self.decoders
if format:
Expand All @@ -135,44 +126,27 @@ def get(self, url, format=None, force_codec=False):
transport = determine_transport(self.transports, link.url)
return transport.transition(link, decoders, force_codec=force_codec)

def reload(self, document, format=None, force_codec=False):
# Fallback for v1.x. To be removed in favour of explict `get` style.
return self.get(document.url, format=format, force_codec=force_codec)

def action(self, document, keys, params=None, validate=True, overrides=None,
action=None, encoding=None, transform=None):
if (action is not None) or (encoding is not None) or (transform is not None):
# Fallback for v1.x overrides.
# Will be removed at some point, most likely in a 2.1 release.
if overrides is None:
overrides = {}
if action is not None:
overrides['action'] = action
if encoding is not None:
overrides['encoding'] = encoding
if transform is not None:
overrides['transform'] = transform

def action(self, document, keys, params=None, validate=True, overrides=None):
if isinstance(keys, string_types):
keys = [keys]

if params is None:
params = {}

# Validate the keys and link parameters.
link, link_ancestors = _lookup_link(document, keys)
link = _lookup_link(document, keys)
if validate:
_validate_parameters(link, params)

if overrides:
# Handle any explicit overrides.
url = overrides.get('url', link.url)
action = overrides.get('action', link.action)
method = overrides.get('method', link.method)
method = overrides.get('action', method) # TODO: Deprecate
encoding = overrides.get('encoding', link.encoding)
transform = overrides.get('transform', link.transform)
fields = overrides.get('fields', link.fields)
link = Link(url, action=action, encoding=encoding, transform=transform, fields=fields)
link = Link(url, method=method, encoding=encoding, fields=fields)

# Perform the action, and return a new document.
transport = determine_transport(self.transports, link.url)
return transport.transition(link, self.decoders, params=params, link_ancestors=link_ancestors)
return transport.transition(link, self.decoders, params=params)
7 changes: 5 additions & 2 deletions coreapi/codecs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@
from coreapi.codecs.display import DisplayCodec
from coreapi.codecs.download import DownloadCodec
from coreapi.codecs.jsondata import JSONCodec
from coreapi.codecs.jsonschema import JSONSchemaCodec
from coreapi.codecs.openapi import OpenAPICodec
from coreapi.codecs.python import PythonCodec
from coreapi.codecs.text import TextCodec


__all__ = [
'BaseCodec', 'CoreJSONCodec', 'DisplayCodec',
'JSONCodec', 'PythonCodec', 'TextCodec', 'DownloadCodec'
'BaseCodec', 'CoreJSONCodec', 'DisplayCodec', 'JSONCodec',
'JSONSchemaCodec', 'OpenAPICodec', 'PythonCodec', 'TextCodec',
'DownloadCodec'
]
27 changes: 1 addition & 26 deletions coreapi/codecs/base.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import itypes


class BaseCodec(itypes.Object):
class BaseCodec(object):
media_type = None

# We don't implement stubs, to ensure that we can check which of these
Expand All @@ -14,28 +11,6 @@ class BaseCodec(itypes.Object):
# def encode(self, document, **options):
# pass

# The following will be removed at some point, most likely in a 2.1 release:
def dump(self, *args, **kwargs):
# Fallback for v1.x interface
return self.encode(*args, **kwargs)

def load(self, *args, **kwargs):
# Fallback for v1.x interface
return self.decode(*args, **kwargs)

@property
def supports(self):
# Fallback for v1.x interface.
if '+' not in self.media_type:
return ['data']

ret = []
if hasattr(self, 'encode'):
ret.append('encoding')
if hasattr(self, 'decode'):
ret.append('decoding')
return ret

def get_media_types(self):
# Fallback, while transitioning from `application/vnd.coreapi+json`
# to simply `application/coreapi+json`.
Expand Down
56 changes: 24 additions & 32 deletions coreapi/codecs/corejson.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,25 @@
from __future__ import unicode_literals
from collections import OrderedDict
from coreapi import typesys
from coreapi.codecs.base import BaseCodec
from coreapi.compat import force_bytes, string_types, urlparse
from coreapi.compat import COMPACT_SEPARATORS, VERBOSE_SEPARATORS
from coreapi.document import Document, Link, Array, Object, Error, Field
from coreapi.document import Document, Link, Object, Error, Field
from coreapi.exceptions import ParseError
import coreschema
import json


# Schema encoding and decoding.
# Just a naive first-pass at this point.

SCHEMA_CLASS_TO_TYPE_ID = {
coreschema.Object: 'object',
coreschema.Array: 'array',
coreschema.Number: 'number',
coreschema.Integer: 'integer',
coreschema.String: 'string',
coreschema.Boolean: 'boolean',
coreschema.Null: 'null',
coreschema.Enum: 'enum',
coreschema.Anything: 'anything'
typesys.Object: 'object',
typesys.Array: 'array',
typesys.Number: 'number',
typesys.Integer: 'integer',
typesys.String: 'string',
typesys.Boolean: 'boolean',
typesys.Any: 'anything'
}

TYPE_ID_TO_SCHEMA_CLASS = {
Expand All @@ -32,16 +30,18 @@


def encode_schema_to_corejson(schema):
if hasattr(schema, 'typename'):
type_id = schema.typename
for cls, type_id in SCHEMA_CLASS_TO_TYPE_ID.items():
if isinstance(schema, cls):
break
else:
type_id = SCHEMA_CLASS_TO_TYPE_ID.get(schema.__class__, 'anything')
type_id = 'anything'

retval = {
'_type': type_id,
'title': schema.title,
'description': schema.description
'title': schema.title or '',
'description': schema.description or ''
}
if hasattr(schema, 'enum'):
if getattr(schema, 'enum', None) is not None:
retval['enum'] = schema.enum
return retval

Expand All @@ -51,12 +51,15 @@ def decode_schema_from_corejson(data):
title = _get_string(data, 'title')
description = _get_string(data, 'description')

kwargs = {}
kwargs = {'title': title, 'description': description}
if type_id == 'enum':
type_id = 'string'
kwargs['enum'] = _get_list(data, 'enum')
elif 'enum' in data:
kwargs['enum'] = data['enum']

schema_cls = TYPE_ID_TO_SCHEMA_CLASS.get(type_id, coreschema.Anything)
return schema_cls(title=title, description=description, **kwargs)
schema_cls = TYPE_ID_TO_SCHEMA_CLASS.get(type_id, typesys.Any)
return schema_cls(**kwargs)


# Robust dictionary lookups, that always return an item of the correct
Expand Down Expand Up @@ -196,8 +199,6 @@ def _document_to_primitive(node, base_url=None):
ret['action'] = node.action
if node.encoding:
ret['encoding'] = node.encoding
if node.transform:
ret['transform'] = node.transform
if node.title:
ret['title'] = node.title
if node.description:
Expand All @@ -224,9 +225,6 @@ def _document_to_primitive(node, base_url=None):
for key, value in node.items()
])

elif isinstance(node, Array):
return [_document_to_primitive(value) for value in node]

return node


Expand Down Expand Up @@ -264,7 +262,6 @@ def _primitive_to_document(data, base_url=None):
url = urlparse.urljoin(base_url, url)
action = _get_string(data, 'action')
encoding = _get_string(data, 'encoding')
transform = _get_string(data, 'transform')
title = _get_string(data, 'title')
description = _get_string(data, 'description')
fields = _get_list(data, 'fields')
Expand All @@ -278,7 +275,7 @@ def _primitive_to_document(data, base_url=None):
for item in fields if isinstance(item, dict)
]
return Link(
url=url, action=action, encoding=encoding, transform=transform,
url=url, method=action, encoding=encoding,
title=title, description=description, fields=fields
)

Expand All @@ -287,11 +284,6 @@ def _primitive_to_document(data, base_url=None):
content = _get_content(data, base_url=base_url)
return Object(content)

elif isinstance(data, list):
# Array
content = [_primitive_to_document(item, base_url) for item in data]
return Array(content)

# String, Integer, Number, Boolean, null.
return data

Expand Down
13 changes: 1 addition & 12 deletions coreapi/codecs/display.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from __future__ import unicode_literals
from coreapi.codecs.base import BaseCodec
from coreapi.compat import console_style, string_types
from coreapi.document import Document, Link, Array, Object, Error
from coreapi.document import Document, Link, Object, Error
import json


Expand Down Expand Up @@ -76,17 +76,6 @@ def _to_plaintext(node, indent=0, base_url=None, colorize=False, extra_offset=No

return head if (not body) else head + '\n' + body

elif isinstance(node, Array):
head_indent = ' ' * indent
body_indent = ' ' * (indent + 1)

body = ',\n'.join([
body_indent + _to_plaintext(value, indent + 1, base_url=base_url, colorize=colorize)
for value in node
])

return '[]' if (not body) else '[\n' + body + '\n' + head_indent + ']'

elif isinstance(node, Link):
return (
colorize_keys('link(') +
Expand Down
Loading