Learn more  » Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Bower components Debian packages RPM packages NuGet packages

nickfrez / unb-python-client   python

Repository URL to install this package:

/ unb / __init__.py

# -*- coding: utf-8 -*-
"""
################################
UNB Platform API Client Library
################################


"""

from __future__ import print_function

import urlparse
import requests


# =============================================================================
# Errors
# =============================================================================

class UNBPlatformError(Exception):
    """Base error for UNBPlatform client."""
    pass


class UNBPlatformUnauthorized(UNBPlatformError):
    """401"""
    pass


# =============================================================================
# Client
# =============================================================================

class TokenAPIClient(object):
    def __init__(self, api_root, client_id, client_secret, token_url,
                 token=None, refresh_token=None):
        if api_root[-1] != '/':
          api_root = api_root + '/'
        self.__api_root = api_root
        self.__client_id = client_id
        self.__client_secret = client_secret

        self.__token_url = token_url
        self.__token = token
        self.__refresh_token = refresh_token

    def _make_path(self, path):
        """Create a path."""

        if not isinstance(path, basestring):
            # Path is (hopefully) an iterable
            #
            path = '/'.join([str(part) for part in path])

        if not path.endswith('/'):
            path = path + '/'

        return urlparse.urljoin(self.__api_root, path)

    def get_token(self, username, password):
        """Login a user and obtain an access token.

        Parameters
        ----------
        username : str
            The user's username, email or other identifier used for logging in.
        password : str
            The user's plain-text password.

        Returns
        -------
        str
            The access token that should be used on subsequent requests.

        Raises
        ------
        UNBPlatformUnauthorized
            If the login attempt was unsuccessful.

        """
        data = {
            'grant_type': 'password',
            'username': username,
            'password': password,
        }
        r = requests.post(self.__token_url,
                          auth=(self.__client_id, self.__client_secret),
                          data=data)
        if r.status_code == 401:
            raise UNBPlatformUnauthorized
        if r.status_code == 200:
            d = r.json()
            self.__token = d.get('access_token')
            self.__refresh_token = d.get('refresh_token')
            return self.__token
        raise UNBPlatformError

    def request(self, method, path, **kwargs):
        """Make an API request.

        Parameters
        ----------
        method : str
            HTTP method to use ('get', 'post', 'patch', 'options', 'etc.')
        path : Union(str : list)
            The path to request.  The path can be an absolute url, relative
            url, or an iterable of path-parts.
        params : dict
            Query arguments to be urlencoded and added to the path.

            .. NOTE:: Any key set to ``None`` will not be included.
        **kwargs
            Additional keyword arguments that will be passed to
            ``requests.request``.

        Returns
        -------
        requests.Response
            The response received.

        """

        # Convert data to json (this may not be necessary?)
        #
        # if 'data' in kwargs and isinstance(kwargs['data'], (dict, list)):
        #     kwargs['data'] = json.dumps(kwargs['data'])

        full_path = self._make_path(path)

        # TODO(nick): The headers should be merged... but I don't want to think
        #             about that at the moment.
        #
        if 'headers' in kwargs:
            raise NotImplemented

        # Create the (default) headers for the request
        #
        headers = {
            # The content-type we're sending
            #
            'Content-Type': 'application/json',

            # The content-type we will accept as a response
            #
            'Accept': 'application/json',
        }
        if self.__token:
            headers['Authorization'] = 'Bearer ' + self.__token

        # TODO(nick): What to do about requests exceptions (Timeout,
        #             ConnectionError, etc.)?
        #
        #             http://docs.python-requests.org/en/latest/api/#exceptions
        #
        # add params
        response = requests.request(method, full_path, headers=headers,
                                    **kwargs)
        if response.status_code == 401:
            raise UNBPlatformUnauthorized
        return response


# =============================================================================
# Resource
# =============================================================================

class SimpleResource(object):
    def __init__(self, data=None):  # , client=None, resource_path=None):
        self._data = data or {}

    @staticmethod
    def _decode(response):
        return response.json()

    @classmethod
    def _list(cls, **kwargs):
        resp = cls._client.request('get', cls._resource_path, params=kwargs)
        return cls(cls._decode(resp))

    @classmethod
    def _create(cls, data, params=None, **kwargs):
        resp = cls._client.request('post', cls._resource_path, params=params,
                                   data=data, **kwargs)
        return cls(cls._decode(resp))

    @classmethod
    def _read(cls, pk, params=None, **kwargs):
        resp = cls._client.request('get', [cls._resource_path, pk],
                                   params=params, **kwargs)
        return cls(cls._decode(resp))

    def _update(self, pk, data, params=None, **kwargs):
        raise NotImplemented
        # resp = self._client.request('patch', [self._resource_path, pk],
        #                             data=data, params=params, **kwargs)
        # return cls(cls._decode(resp))


class HALResource(SimpleResource):
    def __init__(self, hal=None):
        self._hal = hal or {}

    def __getattr__(self, attr):
        return self._hal.get(attr)

    def _get_link(self, name):
        return self._hal.get('_links').get(name)

    def _get_embedded(self, name=None, resource=None):
        embedded_resources = self._hal.get('_embedded')

        if name:
            embedded_resource = embedded_resources.get(
                name, embedded_resources.get('items'))
        else:
            return embedded_resources

        if resource:
            return [resource(hal=r) for r in embedded_resource]
        else:
            if not name:
                return None
            return embedded_resource

    @classmethod
    def _list(cls, _path=None, **kwargs):
        path = _path or cls._resource_path
        resp = cls._client.request('get', path, params=kwargs)
        hal = cls._decode(resp)
        hal_resource_list = HALResourceList(hal, cls._profile, cls)
        return hal_resource_list


class HALResourceList(HALResource):
    def __init__(self, hal, profile, resource_cls):
        super(HALResourceList, self).__init__(hal=hal)
        self._hal = hal
        self._resource_cls = resource_cls
        self._profile = profile
        embedded_resource = self._get_embedded(profile, resource_cls)
        self._resource_list = embedded_resource

    def __getitem__(self, key):
        return self._resource_list.__getitem__(key)

    def __getslice__(self, i, j):
        return self._resource_list.__getslice__(i, j)

    def __iter__(self):
        return self._resource_list.__iter__()


# ======================================================================
# UNB Platform Resources
# ======================================================================

class Article(HALResource):
    _profile = 'unb:article'
    _resource_path = 'write/articles'

    @classmethod
    def _list_public(cls, **kwargs):
        public_path = 'write/articles/public'
        return cls._list(_path=public_path, **kwargs)


class ArticleComment(HALResource):
    _profile = 'unb:article-comment'
    _resource_path = 'write/article-comments'


class UNBPlatform(object):
    def __init__(self, client_config=None):
        client = self.make_client(client_config)
        self._client = client
        self.resources = {}

        self.Article = self._make_resource(Article)
        self.ArticleComment = self._make_resource(ArticleComment)

    def _make_resource(self, resource_cls, profile=None, path=None):
        if profile:
            setattr(resource_cls, '_profile', profile)
        if path:
            setattr(resource_cls, '_resource_path', path)

        setattr(resource_cls, '_client', self._client)
        self.resources[resource_cls._profile] = resource_cls
        return resource_cls

    def make_client(self, client_config):
        client = TokenAPIClient(**client_config)
        # client.get_token(None, None)
        return client

    def get_token(self, username, password):
        return self._client.get_token(username, password)