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

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

Commit a31b020

Browse files
committed
Merge pull request #60 from core-api/refactor
Refactor client/transport interface
2 parents e2b0dfc + 82baa0c commit a31b020

File tree

9 files changed

+200
-224
lines changed
  • transports
  • tests
  • 9 files changed

    +200
    -224
    lines changed

    coreapi/__init__.py

    Lines changed: 3 additions & 22 deletions
    Original file line numberDiff line numberDiff line change
    @@ -1,4 +1,5 @@
    11
    # coding: utf-8
    2+
    from coreapi.codecs import dump, load, negotiate_decoder, negotiate_encoder
    23
    from coreapi.client import Client
    34
    from coreapi.document import Array, Document, Link, Object, Error, Field
    45
    from coreapi.exceptions import ParseError, TransportError, ErrorMessage
    @@ -10,22 +11,12 @@
    1011
    'Array', 'Document', 'Link', 'Object', 'Error', 'Field',
    1112
    'ParseError', 'NotAcceptable', 'TransportError', 'ErrorMessage',
    1213
    'Client',
    13-
    'negotiate_encoder', 'negotiate_decoder',
    14-
    'get', 'action', 'reload', 'load', 'dump',
    14+
    'load', 'dump', 'negotiate_encoder', 'negotiate_decoder',
    15+
    'get', 'action', 'reload',
    1516
    'codecs', 'history', 'transports'
    1617
    ]
    1718

    1819

    19-
    def negotiate_encoder(accept=None):
    20-
    client = Client()
    21-
    return client.negotiate_encoder(accept)
    22-
    23-
    24-
    def negotiate_decoder(content_type=None):
    25-
    client = Client()
    26-
    return client.negotiate_decoder(content_type)
    27-
    28-
    2920
    def get(url):
    3021
    client = Client()
    3122
    return client.get(url)
    @@ -39,13 +30,3 @@ def action(document, keys, params=None, action=None, inplace=None):
    3930
    def reload(document):
    4031
    client = Client()
    4132
    return client.reload(document)
    42-
    43-
    44-
    def load(bytestring, content_type=None):
    45-
    client = Client()
    46-
    return client.load(bytestring, content_type=content_type)
    47-
    48-
    49-
    def dump(document, accept=None, **kwargs):
    50-
    client = Client()
    51-
    return client.dump(document, accept=accept, **kwargs)

    coreapi/client.py

    Lines changed: 22 additions & 128 deletions
    Original file line numberDiff line numberDiff line change
    @@ -1,16 +1,16 @@
    1-
    from coreapi.codecs import CoreJSONCodec, HALCodec, HTMLCodec, PlainTextCodec
    2-
    from coreapi.compat import string_types, urlparse
    1+
    from coreapi.codecs import default_decoders
    2+
    from coreapi.compat import string_types
    33
    from coreapi.document import Document, Link
    4-
    from coreapi.exceptions import LinkLookupError, NotAcceptable, UnsupportedContentType, TransportError
    5-
    from coreapi.transports import HTTPTransport
    4+
    from coreapi.exceptions import LinkLookupError
    5+
    from coreapi.transports import default_transports, determine_transport
    66
    import collections
    77
    import itypes
    88

    99

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

    1212

    13-
    def lookup_link(document, keys):
    13+
    def _lookup_link(document, keys):
    1414
    """
    1515
    Validates that keys looking up a link are correct.
    1616
    @@ -53,30 +53,13 @@ def lookup_link(document, keys):
    5353

    5454

    5555
    class Client(itypes.Object):
    56-
    def __init__(self, codecs=None, transports=None):
    57-
    if codecs is None:
    58-
    codecs = [CoreJSONCodec(), HALCodec(), HTMLCodec(), PlainTextCodec()]
    56+
    def __init__(self, decoders=None, transports=None):
    57+
    if decoders is None:
    58+
    decoders = default_decoders
    5959
    if transports is None:
    60-
    transports = [HTTPTransport()]
    61-
    62-
    self._codecs = itypes.List(codecs)
    60+
    transports = default_transports
    61+
    self._decoders = itypes.List(decoders)
    6362
    self._transports = itypes.List(transports)
    64-
    self._decoders = [
    65-
    codec for codec in codecs
    66-
    if not getattr(codec.load, 'not_implemented', False)
    67-
    ]
    68-
    self._encoders = [
    69-
    codec for codec in codecs
    70-
    if not getattr(codec.dump, 'not_implemented', False)
    71-
    ]
    72-
    73-
    @property
    74-
    def codecs(self):
    75-
    return self._codecs
    76-
    77-
    @property
    78-
    def encoders(self):
    79-
    return self._encoders
    8063

    8164
    @property
    8265
    def decoders(self):
    @@ -86,123 +69,34 @@ def decoders(self):
    8669
    def transports(self):
    8770
    return self._transports
    8871

    89-
    def get_accept_header(self):
    90-
    """
    91-
    Return an 'Accept' header for the given codecs.
    92-
    """
    93-
    return ', '.join([codec.media_type for codec in self.decoders])
    94-
    95-
    def negotiate_decoder(self, content_type=None):
    96-
    """
    97-
    Given the value of a 'Content-Type' header, return the appropriate
    98-
    codec registered to decode the request content.
    99-
    """
    100-
    if content_type is None:
    101-
    return self.decoders[0]
    102-
    103-
    content_type = content_type.split(';')[0].strip().lower()
    104-
    for codec in self.decoders:
    105-
    if codec.media_type == content_type:
    106-
    break
    107-
    else:
    108-
    msg = "Unsupported media in Content-Type header '%s'" % content_type
    109-
    raise UnsupportedContentType(msg)
    110-
    111-
    return codec
    112-
    113-
    def negotiate_encoder(self, accept=None):
    114-
    """
    115-
    Given the value of a 'Accept' header, return a two tuple of the appropriate
    116-
    content type and codec registered to encode the response content.
    117-
    """
    118-
    if accept is None:
    119-
    return self.encoders[0]
    120-
    121-
    acceptable = set([
    122-
    item.split(';')[0].strip().lower()
    123-
    for item in accept.split(',')
    124-
    ])
    125-
    126-
    for codec in self.encoders:
    127-
    if codec.media_type in acceptable:
    128-
    return codec
    129-
    130-
    for codec in self.encoders:
    131-
    if codec.media_type.split('/')[0] + '/*' in acceptable:
    132-
    return codec
    133-
    134-
    if '*/*' in acceptable:
    135-
    return self.encoders[0]
    136-
    137-
    msg = "Unsupported media in Accept header '%s'" % accept
    138-
    raise NotAcceptable(msg)
    139-
    140-
    def determine_transport(self, url):
    141-
    """
    142-
    Given a URL determine the appropriate transport instance.
    143-
    """
    144-
    url_components = urlparse.urlparse(url)
    145-
    scheme = url_components.scheme.lower()
    146-
    netloc = url_components.netloc
    147-
    148-
    if not scheme:
    149-
    raise TransportError("URL missing scheme '%s'." % url)
    150-
    151-
    if not netloc:
    152-
    raise TransportError("URL missing hostname '%s'." % url)
    153-
    154-
    for transport in self.transports:
    155-
    if scheme in transport.schemes:
    156-
    break
    157-
    else:
    158-
    raise TransportError("Unsupported URL scheme '%s'." % scheme)
    159-
    160-
    return transport
    161-
    16272
    def get(self, url):
    163-
    transport = self.determine_transport(url)
    16473
    link = Link(url, action='get')
    165-
    return transport.transition(link, client=self)
    74+
    75+
    # Perform the action, and return a new document.
    76+
    transport = determine_transport(link.url, transports=self.transports)
    77+
    return transport.transition(link, decoders=self.decoders)
    16678

    16779
    def reload(self, document):
    16880
    url = document.url
    169-
    transport = self.determine_transport(url)
    17081
    link = Link(url, action='get')
    171-
    return transport.transition(link, client=self)
    82+
    83+
    # Perform the action, and return a new document.
    84+
    transport = determine_transport(link.url, transports=self.transports)
    85+
    return transport.transition(link, decoders=self.decoders)
    17286

    17387
    def action(self, document, keys, params=None, action=None, inplace=None):
    17488
    if isinstance(keys, string_types):
    17589
    keys = [keys]
    17690

    177-
    if params is None:
    178-
    params = {}
    179-
    18091
    # Validate the keys and link parameters.
    181-
    link, link_ancestors = lookup_link(document, keys)
    92+
    link, link_ancestors = _lookup_link(document, keys)
    18293

    183-
    if action is not None or inplace is not None:
    94+
    if (action is not None) or (inplace is not None):
    18495
    # Handle any explicit overrides.
    18596
    action = link.action if (action is None) else action
    18697
    inplace = link.inplace if (inplace is None) else inplace
    18798
    link = Link(link.url, action, inplace, link.fields)
    18899

    189100
    # Perform the action, and return a new document.
    190-
    transport = self.determine_transport(link.url)
    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)
    101+
    transport = determine_transport(link.url, transports=self.transports)
    102+
    return transport.transition(link, params, decoders=self.decoders, link_ancestors=link_ancestors)

    coreapi/codecs/__init__.py

    Lines changed: 77 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -5,8 +5,85 @@
    55
    from coreapi.codecs.html import HTMLCodec
    66
    from coreapi.codecs.plaintext import PlainTextCodec
    77
    from coreapi.codecs.python import PythonCodec
    8+
    from coreapi.exceptions import NotAcceptable, UnsupportedContentType
    9+
    import itypes
    810

    911

    1012
    __all__ = [
    1113
    'BaseCodec', 'CoreJSONCodec', 'HALCodec', 'HTMLCodec', 'PlainTextCodec', 'PythonCodec',
    1214
    ]
    15+
    16+
    default_decoders = itypes.List([CoreJSONCodec(), HALCodec()])
    17+
    default_encoders = itypes.List([CoreJSONCodec(), HALCodec(), HTMLCodec(), PlainTextCodec()])
    18+
    19+
    20+
    def negotiate_decoder(content_type=None, decoders=None):
    21+
    """
    22+
    Given the value of a 'Content-Type' header, return the appropriate
    23+
    codec registered to decode the request content.
    24+
    """
    25+
    if decoders is None:
    26+
    decoders = default_decoders
    27+
    28+
    if content_type is None:
    29+
    return decoders[0]
    30+
    31+
    content_type = content_type.split(';')[0].strip().lower()
    32+
    for codec in decoders:
    33+
    if codec.media_type == content_type:
    34+
    break
    35+
    else:
    36+
    msg = "Unsupported media in Content-Type header '%s'" % content_type
    37+
    raise UnsupportedContentType(msg)
    38+
    39+
    return codec
    40+
    41+
    42+
    def negotiate_encoder(accept=None, encoders=None):
    43+
    """
    44+
    Given the value of a 'Accept' header, return a two tuple of the appropriate
    45+
    content type and codec registered to encode the response content.
    46+
    """
    47+
    if encoders is None:
    48+
    encoders = default_encoders
    49+
    50+
    if accept is None:
    51+
    return encoders[0]
    52+
    53+
    acceptable = set([
    54+
    item.split(';')[0].strip().lower()
    55+
    for item in accept.split(',')
    56+
    ])
    57+
    58+
    for codec in encoders:
    59+
    if codec.media_type in acceptable:
    60+
    return codec
    61+
    62+
    for codec in encoders:
    63+
    if codec.media_type.split('/')[0] + '/*' in acceptable:
    64+
    return codec
    65+
    66+
    if '*/*' in acceptable:
    67+
    return encoders[0]
    68+
    69+
    msg = "Unsupported media in Accept header '%s'" % accept
    70+
    raise NotAcceptable(msg)
    71+
    72+
    73+
    def load(bytestring, content_type=None, base_url=None, decoders=None):
    74+
    """
    75+
    Given a bytestring and an optional content_type, return the
    76+
    parsed Document.
    77+
    """
    78+
    codec = negotiate_decoder(content_type, decoders=decoders)
    79+
    return codec.load(bytestring, base_url=base_url)
    80+
    81+
    82+
    def dump(document, accept=None, encoders=None, **kwargs):
    83+
    """
    84+
    Given a document, and an optional accept header, return a two-tuple of
    85+
    the selected media type and encoded bytestring.
    86+
    """
    87+
    codec = negotiate_encoder(accept, encoders=encoders)
    88+
    content = codec.dump(document, **kwargs)
    89+
    return (codec.media_type, content)

    coreapi/codecs/base.py

    Lines changed: 0 additions & 9 deletions
    Original file line numberDiff line numberDiff line change
    @@ -24,20 +24,11 @@ def _get_bool(item, key, default=False):
    2424
    return value if isinstance(value, bool) else default
    2525

    2626

    27-
    def _mark_as_not_implemented(method):
    28-
    # Mark the method as not implemented, for the purposes for determining
    29-
    # if a codec supports encoding only, decoding only, or both.
    30-
    method.not_implemented = True
    31-
    return method
    32-
    33-
    3427
    class BaseCodec(itypes.Object):
    3528
    media_type = None
    3629

    37-
    @_mark_as_not_implemented
    3830
    def load(self, bytes, base_url=None):
    3931
    raise NotImplementedError() # pragma: nocover
    4032

    41-
    @_mark_as_not_implemented
    4233
    def dump(self, document, **kwargs):
    4334
    raise NotImplementedError() # pragma: nocover

    coreapi/transports/__init__.py

    Lines changed: 26 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -1,8 +1,34 @@
    11
    # coding: utf-8
    2+
    from coreapi.compat import urlparse
    3+
    from coreapi.exceptions import TransportError
    24
    from coreapi.transports.base import BaseTransport
    35
    from coreapi.transports.http import HTTPTransport
    6+
    import itypes
    47

    58

    69
    __all__ = [
    710
    'BaseTransport', 'HTTPTransport'
    811
    ]
    12+
    13+
    default_transports = itypes.List([HTTPTransport()])
    14+
    15+
    16+
    def determine_transport(url, transports=default_transports):
    17+
    """
    18+
    Given a URL determine the appropriate transport instance.
    19+
    """
    20+
    url_components = urlparse.urlparse(url)
    21+
    scheme = url_components.scheme.lower()
    22+
    netloc = url_components.netloc
    23+
    24+
    if not scheme:
    25+
    raise TransportError("URL missing scheme '%s'." % url)
    26+
    27+
    if not netloc:
    28+
    raise TransportError("URL missing hostname '%s'." % url)
    29+
    30+
    for transport in transports:
    31+
    if scheme in transport.schemes:
    32+
    return transport
    33+
    34+
    raise TransportError("Unsupported URL scheme '%s'." % scheme)

    coreapi/transports/base.py

    Lines changed: 1 addition & 1 deletion
    Original file line numberDiff line numberDiff line change
    @@ -5,5 +5,5 @@
    55
    class BaseTransport(itypes.Object):
    66
    schemes = None
    77

    8-
    def transition(self, link, params=None, client=None, link_ancestors=None):
    8+
    def transition(self, link, params=None, decoders=None, link_ancestors=None):
    99
    raise NotImplementedError() # pragma: nocover

    0 commit comments

    Comments
     (0)
    0