Source code for topchef_client.api

"""
Base class for the library. Describes the API that should be consumed for
optimal TopChefiness.
"""
import requests
from abc import ABCMeta, abstractmethod
from six import add_metaclass
from copy import deepcopy
from topchef_client.models.service import Service
from topchef_client.exceptions import ServiceNotFoundError


[docs]class Client(object): """ The main entry point for the TopChef API. This is the base object which will be used to communicate with the API. """ CONTENT_TYPE_HEADER = {'Content-Type': 'application/json'}
[docs] def __init__(self, url, http_library=requests): """ :param str url: The base URL to the TopChef API :param mod http_library: The library to use for making HTTP requests. By default, this is ``requests``, but it can be overwritten for testing """ self.url = url self._http_library = http_library
@property def http_library(self): """ Provides read-only access to the library used to make HTTP calls :return: The current HTTP library """ return self._http_library @property def services(self): """ :return: The services available on the API. :rtype: Client.AbstractServicesGetter """ return self._TopChefServicesGetter(self) @property def services_url(self): """ :return: The URL where broad information is kept on the services registered with the API """ return '{0}/services'.format(self.url) @add_metaclass(ABCMeta)
[docs] class AbstractServicesGetter(object): """ Describes the interface for getting services from the TopChef API. This type should behave in a similar way to that of the ``dict`` type. One MUST be allowed to get services using the ``__getitem__`` method. Iteration should also be allowed over all services on the API. """ @abstractmethod
[docs] def __getitem__(self, service_uuid): """ :param str or UUID service_uuid: The UUID matching that of the service. :return: The service with ``service_id`` matching that of the UUID :rtype: :class:`Service` :raises: :exc:`ServiceNotFoundError` if the service cannot be found """ raise NotImplementedError()
@abstractmethod
[docs] def __len__(self): """ :return: The number of services registered on this API :rtype: int """ raise NotImplementedError()
@abstractmethod
[docs] def __iter__(self): """ :return: An iterator over all the services registered on the API """ raise NotImplementedError()
@abstractmethod
[docs] def __next__(self): """ :return: The next service to be iterated over """ raise NotImplementedError()
@abstractmethod def __dict__(self): """ :return: All the services registered with the API, returned in the form ``{service_id: Service}``. """ raise NotImplementedError()
[docs] class _TopChefServicesGetter(AbstractServicesGetter): """ Provides a means of getting services, implementing ``AbstractServicesGetter`` """
[docs] def __init__(self, client): """ :param Client client: The TopChef API client to which this instance is attached """ self.client = client self.http_headers = deepcopy(self.client.CONTENT_TYPE_HEADER) self._last_iterated_index = 0
[docs] def __getitem__(self, service_uuid): """ :param service_uuid: Return a service by a given UUID :return: The service with the given UUID, if it exists :rtype: :class:`Service` :raises: :exc:`ServiceNotFoundError` """ service = Service( service_uuid, self.client.url, self.client.http_library ) if not service.does_service_exist: self._handle_nonexistent_service(service) return service
[docs] def __len__(self): """ :return: The number of services on the API """ return len(self._data_from_services_request)
[docs] def __iter__(self): """ :return: Since this type is iterable, return itself """ return self
[docs] def __next__(self): """ :return: The next service in the mapping of iterables """ if self._last_iterated_index == len(self): self._handle_iterator_stop() else: data = self._data_from_services_request[ self._last_iterated_index ] self._last_iterated_index += 1 return Service( data['id'], self.client.url, self.client.http_library )
def __dict__(self): """ :return: All the services, collected into a dictionary of the form ``{service_id: service} :rtype: dict """ return {service.service_id: service for service in self} @property def content_type_header(self): """ :return: The header indicating that the HTTP request to the services endpoint is to be made in JSON """ return self.client.CONTENT_TYPE_HEADER @property def _http_library(self): """ :return: The library to use for HTTP requests """ return self.client.http_library @property def _services_url(self): """ :return: The URL to which requests for services are to be made """ return self.client.services_url
[docs] def _handle_nonexistent_service(self, service): """ :param service: The non-existent service :raises: :exc:`ServiceNotFoundError` """ raise ServiceNotFoundError( "A service with UUID %s does not exist at URL %s" % ( service.service_id, self.client.url ) )
@property def _data_from_services_request(self): """ :return: The parsed JSON from the request to the services endpoint, containing just the data :rtype: dict """ response = self._http_library.get( self._services_url, headers=self.content_type_header ) data = response.json()['data'] return data
[docs] def _handle_iterator_stop(self): """ Clear the last iterated index and throw ``StopIteration`` """ self._last_iterated_index = 0 raise StopIteration()