Skip to content
This repository was archived by the owner on Feb 15, 2018. It is now read-only.
Merged
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
6 changes: 4 additions & 2 deletions helium/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
from .util import (
from_iso_date, to_iso_date,
response_json, response_boolean,
build_resource_attributes, build_resource_relationship,
build_request_attributes, build_request_relationship,
build_request_include,
)
from .resource import Base, Resource, ResourceMeta
from .relations import RelationType, to_many, to_one
Expand All @@ -28,7 +29,8 @@

__all__ = (
from_iso_date, to_iso_date, response_json, response_boolean,
build_resource_attributes, build_resource_relationship,
build_request_attributes, build_request_relationship,
build_request_include,
Error,
ServerError,
ClientError,
Expand Down
12 changes: 6 additions & 6 deletions helium/metadata.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""The metadata resource."""

from __future__ import unicode_literals
from . import Resource, response_json, build_resource_attributes
from . import Resource, response_json, build_request_attributes


class Metadata(Resource):
Expand Down Expand Up @@ -35,12 +35,12 @@ def _publish_metadata(self, publish, **kwargs):
session = self._session
resource_type = self.__class__._resource_type()
target_resource_type = self._target_resource_type
target_resource_id = None if hasattr(self, '_singleton') else self.id
target_resource_id = None if self.is_singleton() else self.id
url = session._build_url(target_resource_type, target_resource_id,
resource_type)
attributes = build_resource_attributes(resource_type,
target_resource_id,
kwargs)
attributes = build_request_attributes(resource_type,
target_resource_id,
kwargs)
data = publish(url, json=attributes)
return Metadata(response_json(data, 200), session,
target_resource_type)
Expand Down Expand Up @@ -101,7 +101,7 @@ def method_builder(cls):

def method(self):
session = self._session
resource_id = None if hasattr(self, '_singleton') else self.id
resource_id = None if self.is_singleton() else self.id
resource_type = cls._resource_type()
url = session._build_url(resource_type, resource_id, 'metadata')
data = session.get(url)
Expand Down
51 changes: 35 additions & 16 deletions helium/relations.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

from __future__ import unicode_literals
import inflection
from builtins import filter as _filter
from . import response_boolean, response_json
from . import build_resource_relationship
from . import build_request_relationship, build_request_include


class RelationType(object):
Expand Down Expand Up @@ -78,11 +79,11 @@ def method_builder(cls):

def method(self):
session = self._session
id = None if hasattr(self, '_singleton') else self.id
id = None if self.is_singleton() else self.id
url = session._build_url(cls._resource_type(), id,
dest_resource_type)
json = response_json(session.get(url), 200)
return dest_class(json, session)
json = response_json(session.get(url), 200, extract=None)
return dest_class._mk_many(session, json)

method.__doc__ = method_doc
setattr(cls, dest_method_name, method)
Expand Down Expand Up @@ -161,23 +162,41 @@ def method_builder(cls):
iterable({to_class}): The {to_name} of :class:`{from_class}`
"""

def fetch_relationship_include(self):
def _fetch_relationship_included(self):
session = self._session
id = None if hasattr(self, '_singleton') else self.id
include = self._include
if include is None or dest_class not in include:
# You requested an included relationship that was
# not originally included
error = "{} was not included".format(dest_class.__name__)
raise AttributeError(error)
included = self._included

def _resource_filter(resource):
return resource.get('type', None) == dest_resource_type

included = _filter(_resource_filter, included)
return [dest_class(entry, session) for entry in included]

def fetch_relationship_include(self, use_included=False):
if use_included:
return _fetch_relationship_included(self)
session = self._session
id = None if self.is_singleton() else self.id
url = session._build_url(src_resource_type, id)
params = {
'include': dest_resource_type
}
params = build_request_include([dest_class], None)
json = response_json(session.get(url, params=params), 200,
extract="included")
extract='included')
return [dest_class(entry, session) for entry in json]

def fetch_relationship_direct(self):
def fetch_relationship_direct(self, use_included=False):
if use_included:
return _fetch_relationship_included(self)
session = self._session
id = None if hasattr(self, '_singleton') else self.id
id = None if self.is_singleton() else self.id
url = session._build_url(src_resource_type, id, dest_resource_type)
json = response_json(session.get(url), 200)
return [dest_class(entry, session) for entry in json]
json = response_json(session.get(url), 200, extract=None)
return dest_class._mk_many(session, json)

if type == RelationType.DIRECT:
fetch_relationship = fetch_relationship_direct
Expand All @@ -190,11 +209,11 @@ def fetch_relationship_direct(self):

def _update_relatonship(self, objs):
session = self._session
id = None if hasattr(self, '_singleton') else self.id
id = None if self.is_singleton() else self.id
url = session._build_url(src_resource_type, id,
'relationships', dest_resource_type)
ids = [obj.id for obj in objs]
json = build_resource_relationship(dest_resource_type, ids)
json = build_request_relationship(dest_resource_type, ids)
response_boolean(session.patch(url, json=json), 200)
return objs

Expand Down
126 changes: 99 additions & 27 deletions helium/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
from __future__ import unicode_literals
from future.utils import iteritems
from . import response_boolean, response_json
from . import build_resource_attributes, from_iso_date
from . import build_request_attributes, build_request_include
from . import from_iso_date


class Base(object):
Expand Down Expand Up @@ -89,57 +90,117 @@ class Resource(Base):
A resource will at least have an ``id`` attribute, which is
promoted from the underlying json data on creation.

A resource can be requested to include relation resources in its
response using the include request parameter. The ``include``
argument allows relationship lookups to validate whether the
relationship was originally requested. You normally don't need to
specify this since the Resource retrieval methods like ``all`` and
``find`` take care of this behavior.

Args:

json(dict): The json to construct the resource from.

session(Session): The session use for this resource

Keyword Args:

include([Resource class]): Resource classes that are included

included([json]): A list of all included json resources

"""

def __init__(self, json, session):
def __init__(self, json, session, include=None, included=None):
self._session = session
self._included = included
self._include = include
super(Resource, self).__init__(json)

@classmethod
def all(cls, session):
def _mk_one(cls, session, json, singleton=False, include=None):
included = json.get('included') if include else None
data = json.get('data')
result = cls(data, session, include=include, included=included)
if singleton:
setattr(result, '_singleton', True)
return result

@classmethod
def _mk_many(cls, session, json, include=None):
included = json.get('included') if include else None
data = json.get('data')
return [cls(entry, session, include=include, included=included)
for entry in data]

@classmethod
def all(cls, session, include=None):
"""Get all resources of the given resource class.

This should be called on sub-classes only.

The include argument allows relationship fetches to be
optimized by including the target resources in the request of
the containing resource. For example::

.. code-block:: python

org = Organization.singleton(include=[Sensor])
org.sensors(use_included=True)

Will fetch the sensors for the authorized organization as part
of retrieving the organization. The ``use_included`` forces
the use of included resources and avoids making a separate
request to get the sensors for the organization.

Args:

session(Session): The session to look up the resources in
session(Session): The session to look up the resources in

Keyword Args:

incldue: A list of resource classes to include in the
request.

Returns:

iterable(Resource): An iterator over all the resources of
this type
iterable(Resource): An iterator over all the resources of
this type

"""
url = session._build_url(cls._resource_type())
json = response_json(session.get(url), 200)
return [cls(entry, session) for entry in json]
params = build_request_include(include, None)
json = response_json(session.get(url, params=params), 200,
extract=None)
return cls._mk_many(session, json, include=include)

@classmethod
def find(cls, session, resource_id):
def find(cls, session, resource_id, include=None):
"""Retrieve a single resource.

This should only be called from sub-classes.

Args:

session(Session): The session to find the resource in
resource_id: The ``id`` for the resource to look up
session(Session): The session to find the resource in

resource_id: The ``id`` for the resource to look up

Keyword Args:

include: Resource classes to include

Returns:

Resource: An instance of a resource, or throws a
:class:`NotFoundError` if the resource can not be found.
Resource: An instance of a resource, or throws a
:class:`NotFoundError` if the resource can not be found.

"""
url = session._build_url(cls._resource_type(), resource_id)
json = response_json(session.get(url), 200)
return cls(json, session)
params = build_request_include(include, None)
json = response_json(session.get(url, params=params), 200,
extract=None)
return cls._mk_one(session, json, include=include)

@classmethod
def create(cls, session, **kwargs):
Expand All @@ -160,12 +221,13 @@ def create(cls, session, **kwargs):
"""
resource_type = cls._resource_type()
url = session._build_url(resource_type)
attributes = build_resource_attributes(resource_type, None, kwargs)
json = response_json(session.post(url, json=attributes), 201)
return cls(json, session)
attributes = build_request_attributes(resource_type, None, kwargs)
json = response_json(session.post(url, json=attributes), 201,
extract=None)
return cls._mk_one(session, json)

@classmethod
def singleton(cls, session):
def singleton(cls, session, include=None):
"""Get the a singleton API resource.

Some Helium API resources are singletons. The authorized user
Expand All @@ -178,12 +240,16 @@ def singleton(cls, session):
will retrieve the authorized user for the given
:class:`Session`

Keyword Args:

include: Resource classes to include

"""
params = build_request_include(include, None)
url = session._build_url(cls._resource_type())
json = response_json(session.get(url), 200)
result = cls(json, session)
setattr(result, '_singleton', True)
return result
json = response_json(session.get(url, params=params), 200,
extract=None)
return cls._mk_one(session, json, singleton=True, include=include)

@classmethod
def _resource_type(cls):
Expand Down Expand Up @@ -224,6 +290,10 @@ def __repr__(self):
"""The string representation of the resource."""
return '<{s.__class__.__name__} {{ id: {s.id} }}>'.format(s=self)

def is_singleton(self):
"""Whether this instance is a singleton."""
return hasattr(self, '_singleton')

def update(self, **kwargs):
"""Update attributes of this resource.

Expand All @@ -242,11 +312,13 @@ def update(self, **kwargs):
"""
resource_type = self._resource_type()
session = self._session
id = None if hasattr(self, '_singleton') else self.id
singleton = self.is_singleton()
id = None if singleton else self.id
url = session._build_url(resource_type, id)
attributes = build_resource_attributes(resource_type, self.id, kwargs)
json = response_json(session.patch(url, json=attributes), 200)
return self.__class__(json, session)
attributes = build_request_attributes(resource_type, self.id, kwargs)
json = response_json(session.patch(url, json=attributes), 200,
extract=None)
return self._mk_one(session, json, singleton=singleton)

def delete(self):
"""Delete the resource.
Expand Down
6 changes: 3 additions & 3 deletions helium/timeseries.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from . import Resource, response_json
from . import to_iso_date
from . import build_resource_attributes
from . import build_request_attributes
from collections import Iterable


Expand Down Expand Up @@ -168,7 +168,7 @@ def post(self, port, value, timestamp=None):
}
if timestamp is not None:
attributes['timestamp'] = to_iso_date(timestamp)
attributes = build_resource_attributes('data-point', None, attributes)
attributes = build_request_attributes('data-point', None, attributes)
data = session.post(self._base_url, json=attributes)
return datapoint_class(response_json(data, 201), session)

Expand All @@ -195,7 +195,7 @@ def method_builder(cls):
""".format(cls.__name__)

def method(self, **kwargs):
resource_id = None if hasattr(self, '_singleton') else self.id
resource_id = None if self.is_singleton() else self.id
return Timeseries(self._session, cls._resource_type(), resource_id,
**kwargs)

Expand Down
Loading