Files
pillar-python-sdk/pillarsdk/resource.py

289 lines
8.0 KiB
Python

import uuid
from . import utils
from .api import Api
class Resource(object):
"""Base class for all REST services
"""
convert_resources = {}
def __init__(self, attributes=None):
attributes = attributes or {}
self.__dict__['api'] = Api.Default()
super(Resource, self).__setattr__('__data__', {})
super(Resource, self).__setattr__('error', None)
super(Resource, self).__setattr__('headers', {})
super(Resource, self).__setattr__('header', {})
super(Resource, self).__setattr__('request_id', None)
self.merge(attributes)
def generate_request_id(self):
"""Generate unique request id
"""
if self.request_id is None:
self.request_id = str(uuid.uuid4())
return self.request_id
def http_headers(self):
"""Generate HTTP header
"""
return utils.merge_dict(self.header, self.headers,
{'Pillar-Request-Id': self.generate_request_id()})
def __str__(self):
return self.__data__.__str__()
def __repr__(self):
return self.__data__.__str__()
def __getattr__(self, name):
return self.__data__.get(name)
def __setattr__(self, name, value):
try:
# Handle attributes(error, header, request_id)
super(Resource, self).__getattribute__(name)
super(Resource, self).__setattr__(name, value)
except AttributeError:
self.__data__[name] = self.convert(name, value)
def success(self):
return self.error is None
def merge(self, new_attributes):
"""Merge new attributes e.g. response from a post to Resource
"""
for key, val in new_attributes.items():
setattr(self, key, val)
def convert(self, name, value):
"""Convert the attribute values to configured class
"""
if isinstance(value, dict):
cls = self.convert_resources.get(name, Resource)
return cls(value)
elif isinstance(value, list):
new_list = []
for obj in value:
new_list.append(self.convert(name, obj))
return new_list
else:
return value
def __getitem__(self, key):
return self.__data__[key]
def __setitem__(self, key, value):
self.__data__[key] = self.convert(key, value)
def to_dict(self):
def parse_object(value):
if isinstance(value, Resource):
return value.to_dict()
elif isinstance(value, list):
new_list = []
for obj in value:
new_list.append(parse_object(obj))
return new_list
else:
return value
data = {}
for key in self.__data__:
data[key] = parse_object(self.__data__[key])
return data
class Find(Resource):
@classmethod
def find(cls, resource_id, api=None):
"""Locate resource, usually using ObjectID
Usage::
>>> Node.find("507f1f77bcf86cd799439011")
"""
api = api or Api.Default()
url = utils.join_url(cls.path, str(resource_id))
return cls(api.get(url))
@classmethod
def find_first(cls, params, api=None):
"""Get list of resources, allowing some parameters such as:
- count
- start_time
- sort_by
- sort_order
Usage::
>>> shots = Nodes.all({'count': 2, 'type': 'shot'})
"""
api = api or Api.Default()
# Force delivery of only 1 result
params['max_results'] = 1
url = utils.join_url_params(cls.path, params)
response = api.get(url)
res = cls(response)
if res._items:
return res._items[0]
else:
return None
class List(Resource):
list_class = Resource
@classmethod
def all(cls, params=None, api=None):
"""Get list of resources, allowing some parameters such as:
- count
- start_time
- sort_by
- sort_order
Usage::
>>> shots = Nodes.all({'count': 2, 'type': 'shot'})
"""
api = api or Api.Default()
if params is None:
url = cls.path
else:
url = utils.join_url_params(cls.path, params)
try:
response = api.get(url)
return cls.list_class(response)
except AttributeError:
# To handle the case when response is JSON Array
if isinstance(response, list):
new_resp = [cls.list_class(elem) for elem in response]
return new_resp
class Create(Resource):
def create(self, api=None):
"""Create a resource
Usage::
>>> node = Node({})
>>> node.create()
"""
api = api or self.api
headers = self.http_headers()
new_attributes = api.post(self.path, self.to_dict(), headers)
self.error = None
self.merge(new_attributes)
return self.success()
class Update(Resource):
"""Update a resource
"""
def update(self, attributes=None, api=None):
api = api or self.api
attributes = attributes or self.to_dict()
etag = attributes['_etag']
attributes.pop('_id')
attributes.pop('_etag')
attributes.pop('_created')
attributes.pop('_updated')
attributes.pop('_links')
url = utils.join_url(self.path, str(self['_id']))
headers = utils.merge_dict(
self.http_headers(),
{'If-Match': str(etag)})
new_attributes = api.put(url, attributes, headers)
self.error = None
self.merge(new_attributes)
return self.success()
class Replace(Resource):
"""Partial update or modify resource
see http://williamdurand.fr/2014/02/14/please-do-not-patch-like-an-idiot/
Usage::
>>> node = Node.find("507f1f77bcf86cd799439011")
>>> node.replace([{'op': 'replace', 'path': '/name', 'value': 'Renamed Shot 2' }])
"""
def replace(self, attributes=None, files=None, api=None):
api = api or self.api
attributes = attributes or self.to_dict()
etag = attributes['_etag']
attributes.pop('_id')
attributes.pop('_etag')
attributes.pop('_created')
attributes.pop('_updated')
attributes.pop('_links')
if 'parent' in attributes:
attributes.pop('parent')
url = utils.join_url(self.path, str(self['_id']))
headers = utils.merge_dict(
self.http_headers(),
{'If-Match': str(etag)})
new_attributes = api.patch(url, attributes, headers, files)
self.error = None
self.merge(new_attributes)
return self.success()
class Delete(Resource):
def delete(self, api=None):
"""Delete a resource
Usage::
>>> node = Node.find("507f1f77bcf86cd799439011")
>>> node.delete()
"""
api = api or self.api
url = utils.join_url(self.path, str(self['_id']))
etag = self['_etag']
headers = {'If-Match': str(etag)}
new_attributes = api.delete(url, headers)
self.error = None
self.merge(new_attributes)
return self.success()
class Post(Resource):
def post(self, attributes=None, files=None, api=None):
"""Constructs url with passed in headers and makes post request via
post method in api class.
"""
api = api or self.api
attributes = attributes or {}
url = utils.join_url(self.path)
"""if not isinstance(attributes, Resource):
attributes = Resource(attributes, api=self.api)"""
#files = files or {}
new_attributes = api.post(url, attributes, {}, files)
"""if isinstance(cls, Resource):
cls.error = None
cls.merge(new_attributes)
return self.success()
else:
return cls(new_attributes, api=self.api)"""
self.merge(new_attributes)
return self.success()