def __init__(self, access_key, secret_key, endpoint = None): """ Initialize client for given query endpoint. None as endpoint means default endpoint. """ self.access_key = access_key self.kws = KWSSigner(secret_key) api_path = '/api' if endpoint is not None: self.api_url = endpoint+api_path else: self.api_url = QUERY_ENDPOINT+api_path self.debugging = False
class BasicDataUploadClient(object): """ Client for kooaba Data Upload API. """ def __init__(self, access_key, secret_key, endpoint = None): """ Initialize client for given query endpoint. None as endpoint means default endpoint. """ self.access_key = access_key self.kws = KWSSigner(secret_key) api_path = '/api' if endpoint is not None: self.api_url = endpoint+api_path else: self.api_url = QUERY_ENDPOINT+api_path self.debugging = False def activate_image(self, image_id): """ Activate the image. Returns current status of the image. """ return self._set_image_status(image_id, 'ACTIVE') def add_resource_file(self, item_id, title, section, filename): """ Add a file resource to an item. Raises exception on error. """ upload_id = self.upload_from_file(filename) xml = self._generate_basic_resource_xml(title, section) file_elem = ET.SubElement(xml, "file") self._add_xml_subelement(file_elem, "upload_id", upload_id) (_response, _body) = self._post_data("/items/%s/item_resources.xml" % item_id, self._serialize_xml(xml), 'application/xml') def add_resource_uri(self, item_id, title, section, uri, locale=None): """ Add a URI resource to an item. Raises exception on error. """ xml = self._generate_basic_resource_xml(title, section) self._add_xml_subelement(xml, "uri", uri) if locale: self._add_xml_subelement(xml, "locale", locale) (_response, _body) = self._post_data("/items/%s/item_resources.xml" % item_id, self._serialize_xml(xml), 'application/xml') def change_item_medium_type(self, item_id, medium_type, title, metadata): """ Change the medium type of the item and upload new metadata. The title is a required metadata title, in general identical to the item title. The metadata is a dictionary with the metadata entries. When medium_type is None, the curent medium type is removed from the item. Raises exception on error. """ xml = ET.Element("medium") if (medium_type is not None) and (medium_type != ""): self._add_xml_subelement(xml, "type", 'Medium::'+medium_type) self._add_xml_subelement(xml, 'title', title) for (key, value) in metadata.items(): self._add_xml_subelement(xml, key, value) (_response, _body) = self._put_data("/items/%s/medium.xml" % item_id, self._serialize_xml(xml), 'application/xml') def create_image_from_upload(self, item_id, upload_id): """ Associate uploaded image with an item. Returns image ID. """ xml = ET.Element("image") file_elem = ET.SubElement(xml, "file") self._add_xml_subelement(file_elem, "upload-id", upload_id) (_response, body) = self._post_data("/items/%s/images.xml" % item_id, self._serialize_xml(xml), 'application/xml') return self._id_from_xml_string(body) def create_item(self, group_id, title, extras=None): """ Create a new item with title in the group with group_id. Optional metadata entries (external_id, reference_id, locale) are specified as dictionary in extras (any entries with value None are ignored). Returns ID of the created item. Raises exception: - RuntimeError: API call failed with an error. """ xml = ET.Element("item") self._add_xml_subelement(xml, "title", title) if extras is None: extras = {} for (key, value) in extras.items(): if value is not None: self._add_xml_subelement(xml, key, value) (_response, body) = self._post_data("/groups/%s/items.xml" % group_id, self._serialize_xml(xml), 'application/xml') return self._id_from_xml_string(body) def deactivate_image(self, image_id): """ Deactivate the image. Returns current status of the image. """ return self._set_image_status(image_id, 'INACTIVE') def delete_image(self, image_id): """ Delete the image. Raises exception on error. """ (_response, _body) = self._send_request('DELETE', "/images/%s.xml" % image_id) def delete_item(self, item_id): """ Delete the item. Raises exception on error. """ (_response, _body) = self._send_request('DELETE', "/items/%s.xml" % item_id) def get_group(self, group_id): """ Return description of the group. """ (_response, body) = self._send_request('GET', "/groups/%s.xml" % group_id) return body def get_group_items(self, group_id): """ Return items from the group. """ (_response, body) = self._send_request('GET', "/groups/%s/items.xml" % group_id) return body def get_image(self, image_id): """ Return description of the image. """ (_response, body) = self._send_request('GET', "/images/%s.xml" % image_id) return body def get_image_status(self, image_id): """ Return current image status. """ (_response, body) = self._send_request('GET', "/images/%s.xml" % image_id) return self._status_from_xml_string(body) def get_item(self, item_id): """ Return description of the item. """ (_response, body) = self._send_request('GET', "/items/%s.xml" % item_id) return body def get_item_resources(self, item_id): """ Return resources of the item. """ (_response, body) = self._send_request('GET', "/items/%s/item_resources.xml" % item_id) return body def set_debug(self, flag): """ Enable/disable debugging printouts according to the flag. """ self.debugging = flag def update_item(self, item_id, extras = None): """ Update the item according to metadata in extras. The metadata entries (title, external_id, reference_id, locale) are specified as dictionary (any entries with value None are ignored). Raises exception: - RuntimeError: API call failed with an error. """ if extras is None: extras = {} xml = ET.Element("item") for (key, value) in extras.items(): if value is not None: self._add_xml_subelement(xml, key, value) (_response, _body) = self._put_data("/items/%s.xml" % item_id, self._serialize_xml(xml), 'application/xml') def upload_data(self, data, content_type): """ Upload data from memory. Returns upload ID. """ (_response, body) = self._post_data('/uploads.xml', data, content_type) return self._id_from_xml_string(body) def upload_from_file(self, filename, content_type = None): """ Upload a file. Returns upload ID. """ if content_type is None: (content_type, _encoding) = mimetypes.guess_type(filename) with open(filename, 'rb') as f: return self.upload_data(f.read(), content_type) def _add_xml_subelement(self, root, name, text): """ Add a text sub-element to root. """ elem = ET.SubElement(root, name) elem.text = unicode(text, "UTF-8") def _element_from_xml(self, xml, name): """ Extract element name from supplied XML string. Returns the element text content as string. Raises KeyError if there is no such element. """ elem = xml.find(name) if elem is None: raise KeyError("No '"+name+"' element in the supplied XML: "+ET.tostring(xml)) return elem.text def _generate_basic_resource_xml(self, title, section): """ Return resource upload XML fragment with title and section. """ xml = ET.Element("resource") self._add_xml_subelement(xml, "title", title) self._add_xml_subelement(xml, "section", section) return xml def _id_from_xml(self, xml): """ Extract id element from supplied XML string. Returns the ID as string. Raises KeyError if there is no ID element. """ return self._element_from_xml(xml, "id") def _id_from_xml_string(self, xml_string): """ Extract id element from supplied XML string. Returns the ID as string. Raises KeyError if there is no ID element. """ xml = ET.fromstring(xml_string) return self._id_from_xml(xml) def _post_data(self, api_path, data, content_type): """ Post data to an API node specified by api_path. See _send_request() for further details. """ return self._send_request('POST', api_path, data, content_type) def _put_data(self, api_path, data, content_type): """ Put data to an API node specified by api_path. See _send_request() for further details. """ return self._send_request('PUT', api_path, data, content_type) def _send_request(self, method, api_path, data=None, content_type=None): """ Send (POST/PUT/GET/DELETE according to the method) data to an API node specified by api_path. Returns tuple (response, body) as returned by the API call. The response is a HttpResponse object describint HTTP headers and status line. Raises exception on error: - IOError: Failure performing HTTP call - RuntimeError: Unsupported transport scheme. - RuntimeError: API call returned an error. """ if self.debugging: if data is None: print "%s ...%s" % (method, api_path) elif len(data) < 4096: print "%s ...%s:\n%s" % (method, api_path, data) else: print "%s ...%s: %sB" % (method, api_path, len(data)) if '://' not in self.api_url: # endpoint as a host or host:port parsed_url = urlparse('http://'+self.api_url+api_path) else: parsed_url = urlparse(self.api_url+api_path) if (parsed_url.scheme != 'http') and (parsed_url.scheme != 'https'): raise RuntimeError("URL scheme '%s' not supported" % parsed_url.scheme) port = parsed_url.port if port is None: port = 80 host = parsed_url.hostname http = httplib.HTTPConnection(host, port) try: date = email.utils.formatdate(None, localtime=False, usegmt=True) if data is not None: signature = self.kws.sign_with_content(method, data, content_type, date, parsed_url.path) else: signature = self.kws.sign_with_no_content(method, content_type, date, parsed_url.path) headers = { 'Authorization': 'KWS %s:%s' % (self.access_key, signature), 'Date': date } if content_type is not None: headers['Content-Type'] = content_type if data is not None: headers['Content-Length'] = str(len(data)) try: http.request(method, parsed_url.path, headers=headers, body=data) except Exception, e: raise IOError("Error during request: %s: %s" % (type(e), e)) response = http.getresponse() # we have to read the response before the http connection is closed body = response.read() if self.debugging: print "HTTP response status:", response.status, response.reason print "Body:" print body if (response.status < 200) or (response.status > 299): raise RuntimeError("API call returned status %s %s. Message: %s" % (response.status, response.reason, body)) return (response, body) finally: