8000 Merge pull request #59 from core-api/refactoring · core-api/python-client@e2b0dfc · GitHub
[go: up one dir, main page]

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

Commit e2b0dfc

Browse files
committed
Merge pull request #59 from core-api/refactoring
Refactoring
2 parents d8ed976 + d44ef80 commit e2b0dfc

14 files changed

+185
-281
lines changed

coreapi/__init__.py

Lines changed: 21 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,51 @@
11
# coding: utf-8
2-
from coreapi.codecs import BaseCodec, CoreJSONCodec, HALCodec, HTMLCodec, PlainTextCodec, PythonCodec
2+
from coreapi.client import Client
33
from coreapi.document import Array, Document, Link, Object, Error, Field
44
from coreapi.exceptions import ParseError, TransportError, ErrorMessage
5-
from coreapi.history import History
6-
from coreapi.sessions import Session
7-
from coreapi.transport import BaseTransport, HTTPTransport
5+
from coreapi import codecs, history, transports
86

97

10-
__version__ = '1.11.4'
8+
__version__ = '1.12.0'
119
__all__ = [
12-
'BaseCodec', 'CoreJSONCodec', 'HALCodec', 'HTMLCodec', 'PlainTextCodec', 'PythonCodec',
13-
'negotiate_encoder', 'negotiate_decoder',
1410
'Array', 'Document', 'Link', 'Object', 'Error', 'Field',
1511
'ParseError', 'NotAcceptable', 'TransportError', 'ErrorMessage',
16-
'BaseTransport', 'HTTPTransport', 'Session', 'History',
17-
'load', 'dump', 'get', 'get_default_session'
12+
'Client',
13+
'negotiate_encoder', 'negotiate_decoder',
14+
'get', 'action', 'reload', 'load', 'dump',
15+
'codecs', 'history', 'transports'
1816
]
1917

2018

21-
_default_session = Session(
22-
codecs=[CoreJSONCodec(), HALCodec(), HTMLCodec(), PlainTextCodec()],
23-
transports=[HTTPTransport()]
24-
)
25-
26-
27-
def get_default_session():
28-
return _default_session
29-
30-
31-
def get_session(credentials=None, headers=None):
32-
return Session(
33-
codecs=_default_session.codecs,
34-
transports=[HTTPTransport(credentials=credentials, headers=headers)]
35-
)
36-
37-
3819
def negotiate_encoder(accept=None):
39-
session = _default_session
40-
return session.negotiate_encoder(accept)
20+
client = Client()
21+
return client.negotiate_encoder(accept)
4122

4223

4324
def negotiate_decoder(content_type=None):
44-
session = _default_session
45-
return session.negotiate_decoder(content_type)
25+
client = Client()
26+
return client.negotiate_decoder(content_type)
4627

4728

4829
def get(url):
49-
session = _default_session
50-
return session.get(url)
30+
client = Client()
31+
return client.get(url)
5132

5233

5334
def action(document, keys, params=None, action=None, inplace=None):
54-
session = _default_session
55-
return session.action(document, keys, params, action=action, inplace=inplace)
35+
client = Client()
36+
return client.action(document, keys, params, action=action, inplace=inplace)
5637

5738

5839
def reload(document):
59-
session = _default_session
60-
return session.reload(document)
40+
client = Client()
41+
return client.reload(document)
6142

6243

6344
def load(bytestring, content_type=None):
64-
session = _default_session
65-
codec = session.negotiate_decoder(content_type)
66-
return codec.load(bytestring)
45+
client = Client()
46+
return client.load(bytestring, content_type=content_type)
6747

6848

6949
def dump(document, accept=None, **kwargs):
70-
session = _default_session
71-
codec = session.negotiate_encoder(accept)
72-
content = codec.dump(document, **kwargs)
73-
return codec.media_type, content
50+
client = Client()
51+
return client.dump(document, accept=accept, **kwargs)

coreapi/sessions.py renamed to coreapi/client.py

Lines changed: 78 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,64 @@
1+
from coreapi.codecs import CoreJSONCodec, HALCodec, HTMLCodec, PlainTextCodec
12
from coreapi.compat import string_types, urlparse
2-
from coreapi.document import Link
3-
from coreapi.exceptions import NotAcceptable, UnsupportedContentType, TransportError
4-
from coreapi.validation import validate_keys_to_link, validate_parameters
3+
from coreapi.document import Document, Link
4+
from coreapi.exceptions import LinkLookupError, NotAcceptable, UnsupportedContentType, TransportError
5+
from coreapi.transports import HTTPTransport
6+
import collections
57
import itypes
68

79

8-
class Session(itypes.Object):
9-
def __init__(self, codecs, transports):
10+
LinkAncestor = collections.namedtuple('LinkAncestor', ['document', 'keys'])
11+
12+
13+
def lookup_link(document, keys):
14+
"""
15+
Validates that keys looking up a link are correct.
16+
17+
Returns a two-tuple of (link, link_ancestors).
18+
"""
19+
if not isinstance(keys, (list, tuple)):
20+
msg = "'keys' must be a list of strings or ints."
21+
raise TypeError(msg)
22+
if any([
23+
not isinstance(key, string_types) and not isinstance(key, int)
24+
for key in keys
25+
]):
26+
raise TypeError("'keys' must be a list of strings or ints.")
27+
28+
# Determine the link node being acted on, and its parent document.
29+
# 'node' is the link we're calling the action for.
30+
# 'document_keys' is the list of keys to the link's parent document.
31+
node = document
32+
link_ancestors = [LinkAncestor(document=document, keys=[])]
33+
for idx, key in enumerate(keys):
34+
try:
35+
node = node[key]
36+
except (KeyError, IndexError):
37+
index_string = ''.join('[%s]' % repr(key).strip('u') for key in keys)
38+
msg = 'Index %s did not reference a link. Key %s was not found.'
39+
raise LinkLookupError(msg % (index_string, repr(key).strip('u')))
40+
if isinstance(node, Document):
41+
ancestor = LinkAncestor(document=node, keys=keys[:idx + 1])
42+
link_ancestors.append(ancestor)
43+
44+
# Ensure that we've correctly indexed into a link.
45+
if not isinstance(node, Link):
46+
index_string = ''.join('[%s]' % repr(key).strip('u') for key in keys)
47+
msg = "Can only call 'action' on a Link. Index %s returned type '%s'."
48+
raise LinkLookupError(
49+
msg % (index_string, type(node).__name__)
50+
)
51+
52+
return (node, link_ancestors)
53+
54+
55+
class Client(itypes.Object):
56+
def __init__(self, codecs=None, transports=None):
57+
if codecs is None:
58+
codecs = [CoreJSONCodec(), HALCodec(), HTMLCodec(), PlainTextCodec()]
59+
if transports is None:
60+
transports = [HTTPTransport()]
61+
1062
self._codecs = itypes.List(codecs)
1163
self._transports = itypes.List(transports)
1264
self._decoders = [
@@ -110,13 +162,13 @@ def determine_transport(self, url):
110162
def get(self, url):
111163
transport = self.determine_transport(url)
112164
link = Link(url, action='get')
113-
return transport.transition(link, session=self)
165+
return transport.transition(link, client=self)
114166

115167
def reload(self, document):
116168
url = document.url
117169
transport = self.determine_transport(url)
118170
link = Link(url, action='get')
119-
return transport.transition(link, session=self)
171+
return transport.transition(link, client=self)
120172

121173
def action(self, document, keys, params=None, action=None, inplace=None):
122174
if isinstance(keys, string_types):
@@ -126,8 +178,7 @@ def action(self, document, keys, params=None, action=None, inplace=None):
126178
params = {}
127179

128180
# Validate the keys and link parameters.
129-
link, link_ancestors = validate_keys_to_link(document, keys)
130-
validate_parameters(link, params)
181+
link, link_ancestors = lookup_link(document, keys)
131182

132183
if action is not None or inplace is not None:
133184
# Handle any explicit overrides.
@@ -137,4 +188,21 @@ def action(self, document, keys, params=None, action=None, inplace=None):
137188

138189
# Perform the action, and return a new document.
139190
transport = self.determine_transport(link.url)
140-
return transport.transition(link, params, session=self, link_ancestors=link_ancestors)
191+
return transport.transition(link, params, client=self, link_ancestors=link_ancestors)
192+
193+
def load(self, bytestring, content_type=None):
194+
"""
195+
Given a bytestring and an optional content_type, return the
196+
parsed Document.
197+
"""
198+
codec = self.negotiate_decoder(content_type)
199+
return codec.load(bytestring)
200+
201+
def dump(self, document, accept=None, **kwargs):
202+
"""
203+
Given a document, and an optional accept header, return a two-tuple of
204+
the selected media type and encoded bytestring.
205+
"""
206+
codec = self.negotiate_encoder(accept)
207+
content = codec.dump(document, **kwargs)
208+
return (codec.media_type, content)

coreapi/commandline.py

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,11 @@ def get_document_string(doc):
6161
return '<%s %s>' % (doc.title, json.dumps(doc.url))
6262

6363

64-
def get_session():
64+
def get_client():
6565
credentials = get_credentials()
6666
headers = get_headers()
67-
return coreapi.get_session(credentials, headers)
67+
http_transport = coreapi.transports.HTTPTransport(credentials, headers)
68+
return coreapi.Client(transports=[http_transport])
6869

6970

7071
def get_document():
@@ -113,10 +114,10 @@ def client(ctx, version):
113114
@click.command(help='Fetch a document from the given URL.')
114115
@click.argument('url')
115116
def get(url):
116-
session = get_session()
117+
client = get_client()
117118
history = get_history()
118119
try:
119-
doc = session.get(url)
120+
doc = client.get(url)
120121
except coreapi.exceptions.ErrorMessage as exc:
121122
click.echo(display(exc.error))
122123
sys.exit(1)
@@ -203,15 +204,15 @@ def action(path, param, action, inplace):
203204
click.echo('No current document. Use `coreapi get` to fetch a document first.')
204205
sys.exit(1)
205206

206-
session = get_session()
207+
client = get_client()
207208
history = get_history()
208209
keys = coerce_key_types(doc, path)
209210
try:
210-
doc = session.action(doc, keys, params=param, action=action, inplace=inplace)
211+
doc = client.action(doc, keys, params=param, action=action, inplace=inplace)
211212
except coreapi.exceptions.ErrorMessage as exc:
212213
click.echo(display(exc.error))
213214
sys.exit(1)
214-
except coreapi.exceptions.NodeLookupError as exc:
215+
except coreapi.exceptions.LinkLookupError as exc:
215216
click.echo(exc)
216217
sys.exit(1)
217218
history = history.add(doc)
@@ -227,10 +228,10 @@ def reload_document():
227228
click.echo('No current document. Use `coreapi get` to fetch a document first.')
228229
sys.exit(1)
229230

230-
session = get_session()
231+
client = get_client()
231232
history = get_history()
232233
try:
233-
doc = session.reload(doc)
234+
doc = client.reload(doc)
234235
except coreapi.exceptions.ErrorMessage as exc:
235236
click.echo(display(exc.error))
236237
sys.exit(1)
@@ -428,9 +429,9 @@ def bookmarks_get(name):
428429
click.echo('Bookmark "%s" does not exist.' % name)
429430
return
430431

431-
session = get_session()
432+
client = get_client()
432433
history = get_history()
433-
doc = session.get(bookmark['url'])
434+
doc = client.get(bookmark['url'])
434435
history = history.add(doc)
435436
click.echo(display(doc))
436437
set_document(doc)
@@ -441,7 +442,7 @@ def bookmarks_get(name):
441442

442443
def get_history():
443444
if not os.path.isfile(history_path):
444-
return coreapi.History(max_items=20)
445+
return coreapi.history.History(max_items=20)
445446
history_file = open(history_path, 'rb')
446447
bytestring = history_file.read()
447448
history_file.close()
@@ -472,27 +473,27 @@ def history_show():
472473

473474
@click.command(help="Navigate back through the browser history.")
474475
def history_back():
475-
session = get_session()
476+
client = get_client()
476477
history = get_history()
477478
if history.is_at_oldest:
478479
click.echo("Currently at oldest point in history. Cannot navigate back.")
479480
return
480481
doc, history = history.back()
481-
doc = session.reload(doc)
482+
doc = client.reload(doc)
482483
click.echo(display(doc))
483484
set_history(history)
484485
set_document(doc)
485486

486487

487488
@click.command(help="Navigate forward through the browser history.")
488489
def history_forward():
489-
session = get_session()
490+
client = get_client()
490491
history = get_history()
491492
if history.is_at_most_recent:
492493
click.echo("Currently at most recent point in history. Cannot navigate forward.")
493494
return
494495
doc, history = history.forward()
495-
doc = session.reload(doc)
496+
doc = client.reload(doc)
496497
click.echo(display(doc))
497498
set_history(history)
498499
set_document(doc)

coreapi/exceptions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class TransportError(Exception):
3131
pass
3232

3333

34-
class NodeLookupError(Exception):
34+
class LinkLookupError(Exception):
3535
"""
3636
Raised when `.action` fails to index a link in the document.
3737
"""

coreapi/transports/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# coding: utf-8
2+
from coreapi.transports.base import BaseTransport
3+
from coreapi.transports.http import HTTPTransport
4+
5+
6+
__all__ = [
7+
'BaseTransport', 'HTTPTransport'
8+
]

coreapi/transports/base.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# coding: utf-8
2+
import itypes
3+
4+
5+
class BaseTransport(itypes.Object):
6+
schemes = None
7+
8+
def transition(self, link, params=None, client=None, link_ancestors=None):
9+
raise NotImplementedError() # pragma: nocover

0 commit comments

Comments
 (0)
0