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

Drop 'transform', and remove 'inplace' transformations. #154

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
27 changes: 8 additions & 19 deletions coreapi/client.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
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 +24,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 +40,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 @@ -140,8 +132,8 @@ def reload(self, document, format=None, force_codec=False):
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):
action=None, encoding=None):
if (action is not None) or (encoding 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:
Expand All @@ -150,8 +142,6 @@ def action(self, document, keys, params=None, validate=True, overrides=None,
overrides['action'] = action
if encoding is not None:
overrides['encoding'] = encoding
if transform is not None:
overrides['transform'] = transform

if isinstance(keys, string_types):
keys = [keys]
Expand All @@ -160,7 +150,7 @@ def action(self, document, keys, params=None, validate=True, overrides=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)

Expand All @@ -169,10 +159,9 @@ def action(self, document, keys, params=None, validate=True, overrides=None,
url = overrides.get('url', link.url)
action = overrides.get('action', link.action)
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, action=action, 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)
5 changes: 1 addition & 4 deletions coreapi/codecs/corejson.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,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 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, action=action, encoding=encoding,
title=title, description=description, fields=fields
)

Expand Down
2 changes: 0 additions & 2 deletions coreapi/codecs/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ def _to_repr(node):
args += ", action=%s" % repr(node.action)
if node.encoding:
args += ", encoding=%s" % repr(node.encoding)
if node.transform:
args += ", transform=%s" % repr(node.transform)
if node.description:
args += ", description=%s" % repr(node.description)
if node.fields:
Expand Down
10 changes: 1 addition & 9 deletions coreapi/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,15 +187,13 @@ class Link(itypes.Object):
"""
Links represent the actions that a client may perform.
"""
def __init__(self, url=None, action=None, encoding=None, transform=None, title=None, description=None, fields=None):
def __init__(self, url=None, action=None, encoding=None, title=None, description=None, fields=None):
if (url is not None) and (not isinstance(url, string_types)):
raise TypeError("Argument 'url' must be a string.")
if (action is not None) and (not isinstance(action, string_types)):
raise TypeError("Argument 'action' must be a string.")
if (encoding is not None) and (not isinstance(encoding, string_types)):
raise TypeError("Argument 'encoding' must be a string.")
if (transform is not None) and (not isinstance(transform, string_types)):
raise TypeError("Argument 'transform' must be a string.")
if (title is not None) and (not isinstance(title, string_types)):
raise TypeError("Argument 'title' must be a string.")
if (description is not None) and (not isinstance(description, string_types)):
Expand All @@ -211,7 +209,6 @@ def __init__(self, url=None, action=None, encoding=None, transform=None, title=N
self._url = '' if (url is None) else url
self._action = '' if (action is None) else action
self._encoding = '' if (encoding is None) else encoding
self._transform = '' if (transform is None) else transform
self._title = '' if (title is None) else title
self._description = '' if (description is None) else description
self._fields = () if (fields is None) else tuple([
Expand All @@ -231,10 +228,6 @@ def action(self):
def encoding(self):
return self._encoding

@property
def transform(self):
return self._transform

@property
def title(self):
return self._title
Expand All @@ -253,7 +246,6 @@ def __eq__(self, other):
self.url == other.url and
self.action == other.action and
self.encoding == other.encoding and
self.transform == other.transform and
self.description == other.description and
sorted(self.fields, key=lambda f: f.name) == sorted(other.fields, key=lambda f: f.name)
)
Expand Down
31 changes: 1 addition & 30 deletions coreapi/transports/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,32 +305,6 @@ def _decode_result(response, decoders, force_codec=False):
return result


def _handle_inplace_replacements(document, link, link_ancestors):
"""
Given a new document, and the link/ancestors it was created,
determine if we should:

* Make an inline replacement and then return the modified document tree.
* Return the new document as-is.
"""
if not link.transform:
if link.action.lower() in ('put', 'patch', 'delete'):
transform = 'inplace'
else:
transform = 'new'
else:
transform = link.transform

if transform == 'inplace':
root = link_ancestors[0].document
keys_to_link_parent = link_ancestors[-1].keys
if document is None:
return root.delete_in(keys_to_link_parent)
return root.set_in(keys_to_link_parent, document)

return document


class HTTPTransport(BaseTransport):
schemes = ['http', 'https']

Expand Down Expand Up @@ -366,7 +340,7 @@ def __init__(self, credentials=None, headers=None, auth=None, session=None, requ
def headers(self):
return self._headers

def transition(self, link, decoders, params=None, link_ancestors=None, force_codec=False):
def transition(self, link, decoders, params=None, force_codec=False):
session = self._session
method = _get_method(link.action)
encoding = _get_encoding(link.encoding)
Expand All @@ -380,9 +354,6 @@ def transition(self, link, decoders, params=None, link_ancestors=None, force_cod
response = session.send(request, **settings)
result = _decode_result(response, decoders, force_codec)

if isinstance(result, Document) and link_ancestors:
result = _handle_inplace_replacements(result, link, link_ancestors)

if isinstance(result, Error):
raise exceptions.ErrorMessage(result)

Expand Down
2 changes: 0 additions & 2 deletions tests/test_codecs.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,6 @@ def test_link_encodings(json_codec):
doc = Document(content={
'link': Link(
action='post',
transform='inplace',
fields=['optional', Field('required', required=True, location='path')]
)
})
Expand All @@ -204,7 +203,6 @@ def test_link_encodings(json_codec):
"link": {
"_type": "link",
"action": "post",
"transform": "inplace",
"fields": [
{
"name": "optional"
Expand Down
9 changes: 1 addition & 8 deletions tests/test_document.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ def doc():
'link': Link(
url='/',
action='post',
transform='inplace',
fields=['optional', Field('required', required=True, location='path')]
),
'nested': {'child': Link(url='/123')}
Expand Down Expand Up @@ -224,7 +223,7 @@ def test_document_repr(doc):
"'integer': 123, "
"'list': [1, 2, 3], "
"'nested': {'child': Link(url='/123')}, "
"'link': Link(url='/', action='post', transform='inplace', "
"'link': Link(url='/', action='post', "
"fields=['optional', Field('required', required=True, location='path')])"
"})"
)
Expand Down Expand Up @@ -345,7 +344,6 @@ def test_document_equality(doc):
'link': Link(
url='/',
action='post',
transform='inplace',
fields=['optional', Field('required', required=True, location='path')]
),
'nested': {'child': Link(url='/123')}
Expand Down Expand Up @@ -424,11 +422,6 @@ def test_link_action_must_be_string():
Link(action=123)


def test_link_transform_must_be_string():
with pytest.raises(TypeError):
Link(transform=123)


def test_link_fields_must_be_list():
with pytest.raises(TypeError):
Link(fields=123)
Expand Down
52 changes: 12 additions & 40 deletions tests/test_transitions.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,14 @@
# coding: utf-8
from coreapi import Document, Link, Client
from coreapi.transports import HTTPTransport
from coreapi.transports.http import _handle_inplace_replacements
import pytest


class MockTransport(HTTPTransport):
schemes = ['mock']

def transition(self, link, decoders, params=None, link_ancestors=None):
if link.action == 'get':
document = Document(title='new', content={'new': 123})
elif link.action in ('put', 'post'):
if params is None:
params = {}
document = Document(title='new', content={'new': 123, 'foo': params.get('foo')})
else:
document = None

return _handle_inplace_replacements(document, link, link_ancestors)
def transition(self, link, decoders, params=None):
return {'action': link.action, 'params': params}


client = Client(transports=[MockTransport()])
Expand All @@ -29,7 +19,6 @@ 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')
Expand All @@ -40,44 +29,27 @@ def doc():
# Test valid transitions.

def test_get(doc):
new = client.action(doc, ['nested', 'follow'])
assert new == {'new': 123}
assert new.title == 'new'


def test_inline_post(doc):
new = client.action(doc, ['nested', 'action'], params={'foo': 123})
assert new == {'nested': {'new': 123, 'foo': 123}}
assert new.title == 'original'
data = client.action(doc, ['nested', 'follow'])
assert data == {'action': 'get', 'params': {}}


def test_post(doc):
new = client.action(doc, ['nested', 'create'], params={'foo': 456})
assert new == {'new': 123, 'foo': 456}
assert new.title == 'new'
data = client.action(doc, ['nested', 'create'], params={'foo': 456})
assert data == {'action': 'post', 'params': {'foo': 456}}


def test_put(doc):
new = client.action(doc, ['nested', 'update'], params={'foo': 789})
assert new == {'nested': {'new': 123, 'foo': 789}}
assert new.title == 'original'
data = client.action(doc, ['nested', 'update'], params={'foo': 789})
assert data == {'action': 'put', 'params': {'foo': 789}}


def test_delete(doc):
new = client.action(doc, ['nested', 'delete'])
assert new == {}
assert new.title == 'original'
data = client.action(doc, ['nested', 'delete'])
assert data == {'action': 'delete', 'params': {}}


# Test overrides

def test_override_action(doc):
new = client.action(doc, ['nested', 'follow'], overrides={'action': 'put'})
assert new == {'nested': {'new': 123, 'foo': None}}
assert new.title == 'original'


def test_override_transform(doc):
new = client.action(doc, ['nested', 'update'], params={'foo': 456}, overrides={'transform': 'new'})
assert new == {'new': 123, 'foo': 456}
assert new.title == 'new'
data = client.action(doc, ['nested', 'follow'], overrides={'action': 'put'})
assert data == {'action': 'put', 'params': {}}