1101 lines
43 KiB
Python
1101 lines
43 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
|
|
@author: Fabio Erculiani <lxnay@sabayon.org>
|
|
@contact: lxnay@sabayon.org
|
|
@copyright: Fabio Erculiani
|
|
@license: GPL-2
|
|
|
|
B{Entropy Client Repository Web Services Client interface}.
|
|
|
|
"""
|
|
__all__ = ["ClientWebServiceFactory", "ClientWebService", "Document",
|
|
"DocumentList", "DocumentFactory"]
|
|
|
|
import os
|
|
import time
|
|
from entropy.const import const_get_stringtype
|
|
from entropy.i18n import _
|
|
from entropy.services.client import WebServiceFactory, WebService
|
|
|
|
class Document(dict):
|
|
"""
|
|
User Generated Content Document object. It inherits a dictionary and
|
|
contains metadata bound to a single content item (such as, a user comment,
|
|
image, etc).
|
|
Since the metadata format is "volatile" at the moment, you should use
|
|
Document.get() to retrieve metadata objects (this is a hash table) rather
|
|
than relying on __getitem__ (using obj[item_key]).
|
|
"""
|
|
|
|
# basic document types supported
|
|
UNKNOWN_TYPE_ID = -1
|
|
COMMENT_TYPE_ID = 1
|
|
# BBCODE_TYPE_ID = 2
|
|
IMAGE_TYPE_ID = 3
|
|
FILE_TYPE_ID = 4
|
|
VIDEO_TYPE_ID = 5
|
|
# This is a new ID, new documents will have this
|
|
ICON_TYPE_ID = 6
|
|
|
|
SUPPORTED_TYPES = (COMMENT_TYPE_ID, IMAGE_TYPE_ID, FILE_TYPE_ID,
|
|
VIDEO_TYPE_ID, ICON_TYPE_ID)
|
|
|
|
DESCRIPTION_PLURAL = {
|
|
UNKNOWN_TYPE_ID: _("Unknown documents"),
|
|
COMMENT_TYPE_ID: _('Comments'),
|
|
# BBCODE_TYPE_ID: _('BBcode Documents'),
|
|
IMAGE_TYPE_ID: _('Images/Screenshots'),
|
|
FILE_TYPE_ID: _('Generic Files'),
|
|
VIDEO_TYPE_ID: _('Videos'),
|
|
ICON_TYPE_ID: _('Icons'),
|
|
}
|
|
DESCRIPTION_SINGULAR = {
|
|
UNKNOWN_TYPE_ID: _("Unknown document"),
|
|
COMMENT_TYPE_ID: _('Comment'),
|
|
# BBCODE_TYPE_ID: _('BBcode Document'),
|
|
IMAGE_TYPE_ID: _('Image/Screenshot'),
|
|
FILE_TYPE_ID: _('Generic File'),
|
|
VIDEO_TYPE_ID: _('Video'),
|
|
ICON_TYPE_ID: _('Icon'),
|
|
}
|
|
|
|
# backward compatibility
|
|
PACKAGE_ICON_TITLE_ID = "__icon__"
|
|
|
|
# Document hash table key pointing to document repository id
|
|
# see Document.repository_id()
|
|
DOCUMENT_REPOSITORY_ID = "repository_id"
|
|
|
|
# Document hash table key pointing to document id
|
|
# see Document.document_id()
|
|
DOCUMENT_DOCUMENT_ID = "document_id"
|
|
|
|
# Document hash table key pointing to document type id
|
|
# see Document.document_type_id()
|
|
DOCUMENT_DOCUMENT_TYPE_ID = "document_type_id"
|
|
|
|
# Document hash table key pointing to document data
|
|
# see Document.document_data()
|
|
DOCUMENT_DATA_ID = "ddata"
|
|
|
|
# Document hash table key for the document title
|
|
DOCUMENT_TITLE_ID = "title"
|
|
|
|
# Document hash table key for the document description
|
|
DOCUMENT_DESCRIPTION_ID = "description"
|
|
|
|
# Document hash table key pointing to document keywords (tags...)
|
|
# see Document.document_keywords()
|
|
DOCUMENT_KEYWORDS_ID = "keywords"
|
|
|
|
# Document hash table key pointing to document timestamp (float)
|
|
DOCUMENT_TIMESTAMP_ID = "ts"
|
|
|
|
# Document hash table key pointing to document URL
|
|
DOCUMENT_URL_ID = "url"
|
|
|
|
def __init__(self, repository_id, document_id, document_type_id):
|
|
"""
|
|
Document constructor.
|
|
|
|
@param repository_id: repository identifier
|
|
@type repository_id: string
|
|
@param document_id: document unique identifier
|
|
@type document_id: int
|
|
@param document_id: document type identifier
|
|
@type document_id: int
|
|
"""
|
|
self._document_type_revmap = {
|
|
1: Document.COMMENT_TYPE_ID,
|
|
# 2: Document.BBCODE_TYPE_ID,
|
|
3: Document.IMAGE_TYPE_ID,
|
|
4: Document.FILE_TYPE_ID,
|
|
5: Document.VIDEO_TYPE_ID,
|
|
6: Document.ICON_TYPE_ID,
|
|
}
|
|
obj = {
|
|
Document.DOCUMENT_REPOSITORY_ID: repository_id,
|
|
Document.DOCUMENT_DOCUMENT_ID: document_id,
|
|
Document.DOCUMENT_DOCUMENT_TYPE_ID: document_type_id,
|
|
}
|
|
self.update(obj)
|
|
self._add_base_metadata()
|
|
|
|
def _add_base_metadata(self):
|
|
"""
|
|
Add base metadata to Document object.
|
|
"""
|
|
if Document.DOCUMENT_TIMESTAMP_ID not in self:
|
|
self[Document.DOCUMENT_TIMESTAMP_ID] = repr(time.time())
|
|
|
|
def repository_id(self):
|
|
"""
|
|
Return the currently set repository identifier.
|
|
|
|
@return: the repository identifier
|
|
@rtype: string
|
|
"""
|
|
return self[Document.DOCUMENT_REPOSITORY_ID]
|
|
|
|
def document_id(self):
|
|
"""
|
|
Return the currently set document unique identifier.
|
|
|
|
@return: document unique identifier
|
|
@rtype: int
|
|
"""
|
|
return self[Document.DOCUMENT_DOCUMENT_ID]
|
|
|
|
def document_type(self):
|
|
"""
|
|
Return the document type identifier, which is always one of:
|
|
Document.UNKNOWN_TYPE_ID, Document.COMMENT_TYPE_ID,
|
|
Document.BBCODE_TYPE_ID, Document.IMAGE_TYPE_ID,
|
|
Document.FILE_TYPE_ID, Document.VIDEO_TYPE_ID
|
|
|
|
@return: the document type identifier
|
|
@rtype: int
|
|
"""
|
|
return self._document_type_revmap.get(
|
|
self[Document.DOCUMENT_DOCUMENT_TYPE_ID],
|
|
Document.UNKNOWN_TYPE_ID)
|
|
|
|
def document_data(self):
|
|
"""
|
|
Return encapsulated document data. This is an opaque object, usually
|
|
string, that depends on the document type.
|
|
If there is no document data, None is returned.
|
|
|
|
@return: document data
|
|
@rtype: object or None
|
|
"""
|
|
return self.get(Document.DOCUMENT_DATA_ID)
|
|
|
|
def document_keywords(self):
|
|
"""
|
|
Return a string containing space separated keywords bound to this
|
|
document.
|
|
string is always returned, even if metadatum is not available.
|
|
|
|
@return: document keywords
|
|
@rtype: string
|
|
"""
|
|
return self.get(Document.DOCUMENT_KEYWORDS_ID, "")
|
|
|
|
def document_timestamp(self):
|
|
"""
|
|
Return the document timestamp. If value is not available (unlikely)
|
|
the returned value will be 0.0.
|
|
|
|
@return: document timestamp
|
|
@rtype: float
|
|
"""
|
|
return self.get(Document.DOCUMENT_TIMESTAMP_ID, 0.0)
|
|
|
|
def document_url(self):
|
|
"""
|
|
Return the document url, if any.
|
|
|
|
@return: document URL
|
|
@rtype: string or None
|
|
"""
|
|
return self.get(Document.DOCUMENT_URL_ID)
|
|
|
|
def is_icon(self):
|
|
"""
|
|
Return whether this Document is an Icon document.
|
|
|
|
@return: True, if Document is representing an icon
|
|
@rtype: bool
|
|
"""
|
|
if self.document_type() == Document.ICON_TYPE_ID:
|
|
return True
|
|
# backward compatibility
|
|
if not self.is_image():
|
|
return False
|
|
if self.get(Document.DOCUMENT_TITLE_ID) == \
|
|
Document.PACKAGE_ICON_TITLE_ID:
|
|
return True
|
|
return False
|
|
|
|
def is_image(self):
|
|
"""
|
|
Return whether this Document is an Image document.
|
|
|
|
@return: True, if Document is representing an image
|
|
@rtype: bool
|
|
"""
|
|
return self.document_type() == Document.IMAGE_TYPE_ID
|
|
|
|
def is_comment(self):
|
|
"""
|
|
Return whether this Document is a Comment document.
|
|
|
|
@return: True, if Document is representing a Comment
|
|
@rtype: bool
|
|
"""
|
|
return self.document_type() == Document.COMMENT_TYPE_ID
|
|
|
|
def is_file(self):
|
|
"""
|
|
Return whether this Document is a File document.
|
|
|
|
@return: True, if Document is representing a File
|
|
@rtype: bool
|
|
"""
|
|
return self.document_type() == Document.FILE_TYPE_ID
|
|
|
|
def is_video(self):
|
|
"""
|
|
Return whether this Document is a Video document.
|
|
|
|
@return: True, if Document is representing a Video
|
|
@rtype: bool
|
|
"""
|
|
return self.document_type() == Document.VIDEO_TYPE_ID
|
|
|
|
class DocumentList(list):
|
|
"""
|
|
DocumentList is a list object providing extra methods for obtaining extra
|
|
document list statuses, such as the number of elements found, the current
|
|
elements list offset, and if there are more elements on the remote service.
|
|
"""
|
|
|
|
def __init__(self, package_name, total, offset):
|
|
"""
|
|
DocumentList constructor.
|
|
|
|
@param package_name: package name string
|
|
@type package_name: string
|
|
@param total: number of total documents available
|
|
@type total: int
|
|
@param offset: list offset used by remote service
|
|
@type offset: int
|
|
"""
|
|
list.__init__(self)
|
|
self._package_name = package_name
|
|
self._total = total
|
|
self._offset = offset
|
|
|
|
def package_name(self):
|
|
"""
|
|
Return the package name bound to this object.
|
|
|
|
@return: the package name
|
|
@rtype: string
|
|
"""
|
|
return self._package_name
|
|
|
|
def total(self):
|
|
"""
|
|
Return the total amount of remotely available documents.
|
|
|
|
@return: the total amount of remotely available documents.
|
|
@rtype: int
|
|
"""
|
|
return self._total
|
|
|
|
def offset(self):
|
|
"""
|
|
Return the used offset for fetching this list.
|
|
|
|
@return: the used offset
|
|
@rtype: int
|
|
"""
|
|
return self._offset
|
|
|
|
def has_more(self):
|
|
"""
|
|
Return the amount of documents available after those listed here.
|
|
|
|
@return: the amount of documents still available on the service,
|
|
given the current offset
|
|
@rtype: int
|
|
"""
|
|
return (self._total - self._offset - len(self))
|
|
|
|
|
|
class DocumentFactory(object):
|
|
"""
|
|
Class to generate valid, new Document objects.
|
|
"""
|
|
|
|
# Document hash table key for the document username
|
|
DOCUMENT_USERNAME_ID = "username"
|
|
|
|
# Payload metadatum is only available on temporary, to-be-uploaded
|
|
# Document objects. Can contain a file object or any other pointer
|
|
# to a serializable resource.
|
|
DOCUMENT_PAYLOAD_ID = "payload"
|
|
|
|
# Maximum string length, used for input validation
|
|
MAX_STRING_LENGTH = 4000
|
|
|
|
def __init__(self, repository_id):
|
|
"""
|
|
DocumentFactory constructor.
|
|
|
|
@param repository_id: repository identifier
|
|
@type repository_id: string
|
|
"""
|
|
self._repository_id = repository_id
|
|
|
|
def _validate_strings(self, *strings):
|
|
"""
|
|
Validate input strings.
|
|
|
|
@raise AssertionError: if one of the input objects is invalid
|
|
"""
|
|
for string in strings:
|
|
if not isinstance(string, const_get_stringtype()):
|
|
raise AssertionError("invalid string type detected")
|
|
if len(string) > DocumentFactory.MAX_STRING_LENGTH:
|
|
raise AssertionError("string is too long")
|
|
|
|
def _validate_string_list(self, string_list):
|
|
"""
|
|
Validate input string list.
|
|
|
|
@raise AttributeError: if one of the input objects is invalid
|
|
"""
|
|
self._validate_strings(*string_list)
|
|
|
|
def _validate_file_object(self, f_obj):
|
|
"""
|
|
Validate input file object.
|
|
"""
|
|
if not isinstance(f_obj, file):
|
|
raise AssertionError("not a file object")
|
|
if f_obj.tell() != 0:
|
|
raise AssertionError("file position != 0")
|
|
if f_obj.closed:
|
|
raise AssertionError("file object is closed")
|
|
if "w" in f_obj.mode:
|
|
raise AssertionError("file object wrong file mode")
|
|
|
|
def comment(self, username, comment, title, keywords):
|
|
"""
|
|
Generate a new Comment Document.
|
|
|
|
@param username: username of the owner of the Document
|
|
@type username: string
|
|
@param comment: comment text
|
|
@type comment: string
|
|
@param title: comment title
|
|
@type title: string
|
|
@param keywords: space separated string containing keywords
|
|
@type keywords: string
|
|
@return: a new Document object
|
|
@rtype: Document
|
|
"""
|
|
self._validate_strings(username, comment, title)
|
|
doc = Document(self._repository_id, None, Document.COMMENT_TYPE_ID)
|
|
doc[DocumentFactory.DOCUMENT_USERNAME_ID] = username
|
|
doc[Document.DOCUMENT_DATA_ID] = comment
|
|
doc[Document.DOCUMENT_TITLE_ID] = title
|
|
doc[Document.DOCUMENT_KEYWORDS_ID] = keywords
|
|
return doc
|
|
|
|
def image(self, username, file_object, title, description, keywords):
|
|
"""
|
|
Generate a new Image Document.
|
|
|
|
@param username: username of the owner of the Document
|
|
@type username: string
|
|
@param file_object: file object pointing to the image file data. Note
|
|
that this resource must be closed by the caller once the object
|
|
lifecycle is over. Not doing so will cause the application running
|
|
out of resources, leading to crashes. To retrieve the filename,
|
|
the "name" attribute is read, this won't work for fdopened files.
|
|
@type file_object: string
|
|
@param title: comment title
|
|
@type title: string
|
|
@param keywords: space separated string containing keywords
|
|
@type keywords: string
|
|
@return: a new Document object
|
|
@rtype: Document
|
|
"""
|
|
self._validate_strings(username, title, description)
|
|
self._validate_string_list(keywords)
|
|
self._validate_file_object(file_object)
|
|
doc = Document(self._repository_id, None, Document.IMAGE_TYPE_ID)
|
|
doc[DocumentFactory.DOCUMENT_USERNAME_ID] = username
|
|
doc[DocumentFactory.DOCUMENT_PAYLOAD_ID] = \
|
|
(os.path.basename(file_object.name), file_object)
|
|
doc[Document.DOCUMENT_TITLE_ID] = title
|
|
doc[Document.DOCUMENT_DESCRIPTION_ID] = description
|
|
doc[Document.DOCUMENT_KEYWORDS_ID] = keywords
|
|
return doc
|
|
|
|
def icon(self, username, file_object, title, description, keywords):
|
|
"""
|
|
Generate a new Icon Document.
|
|
|
|
@param username: username of the owner of the Document
|
|
@type username: string
|
|
@param file_object: file object pointing to the image file data. Note
|
|
that this resource must be closed by the caller once the object
|
|
lifecycle is over. Not doing so will cause the application running
|
|
out of resources, leading to crashes. To retrieve the filename,
|
|
the "name" attribute is read, this won't work for fdopened files.
|
|
@type file_object: string
|
|
@param title: comment title
|
|
@type title: string
|
|
@param keywords: space separated string containing keywords
|
|
@type keywords: string
|
|
@return: a new Document object
|
|
@rtype: Document
|
|
"""
|
|
self._validate_strings(username, title, description)
|
|
self._validate_string_list(keywords)
|
|
self._validate_file_object(file_object)
|
|
doc = Document(self._repository_id, None, Document.ICON_TYPE_ID)
|
|
doc[DocumentFactory.DOCUMENT_USERNAME_ID] = username
|
|
doc[DocumentFactory.DOCUMENT_PAYLOAD_ID] = \
|
|
(os.path.basename(file_object.name), file_object)
|
|
doc[Document.DOCUMENT_TITLE_ID] = title
|
|
doc[Document.DOCUMENT_DESCRIPTION_ID] = description
|
|
doc[Document.DOCUMENT_KEYWORDS_ID] = keywords
|
|
return doc
|
|
|
|
def video(self, username, file_object, title, description, keywords):
|
|
"""
|
|
Generate a new Icon Document.
|
|
|
|
@param username: username of the owner of the Document
|
|
@type username: string
|
|
@param file_object: file object pointing to the image file data. Note
|
|
that this resource must be closed by the caller once the object
|
|
lifecycle is over. Not doing so will cause the application running
|
|
out of resources, leading to crashes. To retrieve the filename,
|
|
the "name" attribute is read, this won't work for fdopened files.
|
|
@type file_object: string
|
|
@param title: comment title
|
|
@type title: string
|
|
@param keywords: space separated string containing keywords
|
|
@type keywords: string
|
|
@return: a new Document object
|
|
@rtype: Document
|
|
"""
|
|
self._validate_strings(username, title, description)
|
|
self._validate_string_list(keywords)
|
|
self._validate_file_object(file_object)
|
|
doc = Document(self._repository_id, None, Document.VIDEO_TYPE_ID)
|
|
doc[DocumentFactory.DOCUMENT_USERNAME_ID] = username
|
|
doc[DocumentFactory.DOCUMENT_PAYLOAD_ID] = \
|
|
(os.path.basename(file_object.name), file_object)
|
|
doc[Document.DOCUMENT_TITLE_ID] = title
|
|
doc[Document.DOCUMENT_DESCRIPTION_ID] = description
|
|
doc[Document.DOCUMENT_KEYWORDS_ID] = keywords
|
|
return doc
|
|
|
|
def file(self, username, file_object, title, description, keywords):
|
|
"""
|
|
Generate a new File Document.
|
|
|
|
@param username: username of the owner of the Document
|
|
@type username: string
|
|
@param file_object: file object pointing to the image file data. Note
|
|
that this resource must be closed by the caller once the object
|
|
lifecycle is over. Not doing so will cause the application running
|
|
out of resources, leading to crashes. To retrieve the filename,
|
|
the "name" attribute is read, this won't work for fdopened files.
|
|
@type file_object: string
|
|
@param title: comment title
|
|
@type title: string
|
|
@param keywords: space separated string containing keywords
|
|
@type keywords: string
|
|
@return: a new Document object
|
|
@rtype: Document
|
|
"""
|
|
self._validate_strings(username, title, description)
|
|
self._validate_string_list(keywords)
|
|
self._validate_file_object(file_object)
|
|
doc = Document(self._repository_id, None, Document.FILE_TYPE_ID)
|
|
doc[DocumentFactory.DOCUMENT_USERNAME_ID] = username
|
|
doc[DocumentFactory.DOCUMENT_PAYLOAD_ID] = \
|
|
(os.path.basename(file_object.name), file_object)
|
|
doc[Document.DOCUMENT_TITLE_ID] = title
|
|
doc[Document.DOCUMENT_DESCRIPTION_ID] = description
|
|
doc[Document.DOCUMENT_KEYWORDS_ID] = keywords
|
|
return doc
|
|
|
|
|
|
class ClientWebServiceFactory(WebServiceFactory):
|
|
"""
|
|
Main Entropy Client Repository Web Service Factory. Generates
|
|
WebService objects that can be used to communicate with the established
|
|
web service.
|
|
This class should be instantiated by calling
|
|
"""
|
|
|
|
def __init__(self, entropy_client):
|
|
"""
|
|
Overridden constructor.
|
|
"""
|
|
WebServiceFactory.__init__(self, ClientWebService, entropy_client)
|
|
|
|
|
|
class ClientWebService(WebService):
|
|
|
|
# Package maximum and minimum vote boundaries
|
|
MAX_VOTE = 5.0
|
|
MIN_VOTE = 0.0
|
|
VALID_VOTES = (1, 2, 3, 4, 5)
|
|
|
|
class DocumentError(WebService.WebServiceException):
|
|
"""
|
|
Generic Document error object. Raised when Document object is
|
|
invalid.
|
|
"""
|
|
|
|
def document_factory(self):
|
|
"""
|
|
Return a new DocumentFactory object, used to create new Document
|
|
objects.
|
|
|
|
@return: a DocumentFactory object
|
|
@rtype: DocumentFactory
|
|
"""
|
|
return DocumentFactory(self._repository_id)
|
|
|
|
def get_votes(self, package_names, cache = True):
|
|
"""
|
|
For given package names, return the current vote. For missing votes
|
|
or invalid package_name, None is assigned.
|
|
|
|
@param package_names: list of package names, either atoms or keys
|
|
@type package_names: list
|
|
@keyword cache: True means use on-disk cache if available?
|
|
@type cache: bool
|
|
@return: mapping composed by package name as key and value as vote
|
|
(float)
|
|
@rtype: dict
|
|
|
|
@raise WebService.UnsupportedParameters: if input parameters are
|
|
invalid
|
|
@raise WebService.RequestError: if request cannot be satisfied
|
|
@raise WebService.MethodNotAvailable: if API method is not
|
|
available remotely and an error occured (error code passed as
|
|
exception argument)
|
|
@raise WebService.AuthenticationRequired: if require_credentials
|
|
is True and credentials are required.
|
|
@raise WebService.AuthenticationFailed: if credentials are
|
|
not valid
|
|
@raise WebService.MalformedResponse: if JSON response cannot be
|
|
converted back to dict.
|
|
@raise WebService.UnsupportedAPILevel: if client API and Web
|
|
Service API do not match
|
|
@raise WebService.MethodResponseError; if method execution failed
|
|
"""
|
|
params = {
|
|
"package_names": " ".join(package_names)
|
|
}
|
|
return self._method_getter("get_votes", params, cache = cache,
|
|
require_credentials = False)
|
|
|
|
def get_available_votes(self, cache = True):
|
|
"""
|
|
Return all the available votes for all the available packages.
|
|
|
|
@keyword cache: True means use on-disk cache if available?
|
|
@type cache: bool
|
|
@return: mapping composed by package name as key and value as vote
|
|
(float)
|
|
@rtype: dict
|
|
|
|
@raise WebService.UnsupportedParameters: if input parameters are
|
|
invalid
|
|
@raise WebService.RequestError: if request cannot be satisfied
|
|
@raise WebService.MethodNotAvailable: if API method is not
|
|
available remotely and an error occured (error code passed as
|
|
exception argument)
|
|
@raise WebService.AuthenticationRequired: if require_credentials
|
|
is True and credentials are required.
|
|
@raise WebService.AuthenticationFailed: if credentials are
|
|
not valid
|
|
@raise WebService.MalformedResponse: if JSON response cannot be
|
|
converted back to dict.
|
|
@raise WebService.UnsupportedAPILevel: if client API and Web
|
|
Service API do not match
|
|
@raise WebService.MethodResponseError; if method execution failed
|
|
"""
|
|
return self._method_getter("get_available_votes", {}, cache = cache,
|
|
require_credentials = False)
|
|
|
|
def get_downloads(self, package_names, cache = True):
|
|
"""
|
|
For given package names, return the current download counter.
|
|
Packages having no download info will get None instead of int.
|
|
|
|
@param package_names: list of package names, either atoms or keys
|
|
@type package_names: list
|
|
@keyword cache: True means use on-disk cache if available?
|
|
@type cache: bool
|
|
@return: mapping composed by package name as key and downloads as value
|
|
@rtype: dict
|
|
|
|
@raise WebService.UnsupportedParameters: if input parameters are
|
|
invalid
|
|
@raise WebService.RequestError: if request cannot be satisfied
|
|
@raise WebService.MethodNotAvailable: if API method is not
|
|
available remotely and an error occured (error code passed as
|
|
exception argument)
|
|
@raise WebService.AuthenticationRequired: if require_credentials
|
|
is True and credentials are required.
|
|
@raise WebService.AuthenticationFailed: if credentials are
|
|
not valid
|
|
@raise WebService.MalformedResponse: if JSON response cannot be
|
|
converted back to dict.
|
|
@raise WebService.UnsupportedAPILevel: if client API and Web
|
|
Service API do not match
|
|
@raise WebService.MethodResponseError; if method execution failed
|
|
"""
|
|
params = {
|
|
"package_names": " ".join(package_names)
|
|
}
|
|
return self._method_getter("get_downloads", params, cache = cache,
|
|
require_credentials = False)
|
|
|
|
def get_available_downloads(self, cache = True):
|
|
"""
|
|
Return all the available downloads for all the available packages.
|
|
|
|
@param package_names: list of package names, either atoms or keys
|
|
@type package_names: list
|
|
@keyword cache: True means use on-disk cache if available?
|
|
@type cache: bool
|
|
@return: mapping composed by package name as key and downloads as value
|
|
@rtype: dict
|
|
|
|
@raise WebService.UnsupportedParameters: if input parameters are
|
|
invalid
|
|
@raise WebService.RequestError: if request cannot be satisfied
|
|
@raise WebService.MethodNotAvailable: if API method is not
|
|
available remotely and an error occured (error code passed as
|
|
exception argument)
|
|
@raise WebService.AuthenticationRequired: if require_credentials
|
|
is True and credentials are required.
|
|
@raise WebService.AuthenticationFailed: if credentials are
|
|
not valid
|
|
@raise WebService.MalformedResponse: if JSON response cannot be
|
|
converted back to dict.
|
|
@raise WebService.UnsupportedAPILevel: if client API and Web
|
|
Service API do not match
|
|
@raise WebService.MethodResponseError; if method execution failed
|
|
"""
|
|
return self._method_getter("get_available_downloads", {}, cache = cache,
|
|
require_credentials = False)
|
|
|
|
def add_vote(self, package_name, vote):
|
|
"""
|
|
For given package name, add a vote.
|
|
|
|
@param package_name: package name, either atom or key
|
|
@type package_name: string
|
|
@return: True, if vote was recorded, False otherwise
|
|
@rtype: bool
|
|
|
|
@raise WebService.UnsupportedParameters: if input parameters are
|
|
invalid
|
|
@raise WebService.RequestError: if request cannot be satisfied
|
|
@raise WebService.MethodNotAvailable: if API method is not
|
|
available remotely and an error occured (error code passed as
|
|
exception argument)
|
|
@raise WebService.AuthenticationRequired: if require_credentials
|
|
is True and credentials are required.
|
|
@raise WebService.AuthenticationFailed: if credentials are
|
|
not valid
|
|
@raise WebService.MalformedResponse: if JSON response cannot be
|
|
converted back to dict.
|
|
@raise WebService.UnsupportedAPILevel: if client API and Web
|
|
Service API do not match
|
|
@raise WebService.MethodResponseError; if method execution failed
|
|
@raise WebService.RequestError: if vote is invalid.
|
|
@raise WebService.AuthenticationRequired: if login information are
|
|
not available (user interface should raise a login form, validate
|
|
the credentials and retry the function call here)
|
|
"""
|
|
valid = vote in ClientWebService.VALID_VOTES
|
|
if not valid:
|
|
raise WebService.RequestError("invalid vote", method = "add_vote")
|
|
params = {
|
|
"package_name": package_name,
|
|
"vote": vote,
|
|
}
|
|
valid = self._method_getter("add_vote", params, cache = False,
|
|
require_credentials = True)
|
|
if valid:
|
|
# NOTE: we can accept to be non-atomic in this case.
|
|
# TODO: cannot remove all the vote cache when just one element gets
|
|
# tained
|
|
self._drop_cached("get_votes")
|
|
return valid
|
|
|
|
def add_downloads(self, package_names):
|
|
"""
|
|
Notify that a list of packages have been downloaded successfully.
|
|
|
|
@param package_names: list of package names, either atoms or keys
|
|
@type package_names: list
|
|
@return: True, if download information was recorded, False otherwise
|
|
@rtype: bool
|
|
|
|
@raise WebService.UnsupportedParameters: if input parameters are
|
|
invalid
|
|
@raise WebService.RequestError: if request cannot be satisfied
|
|
@raise WebService.MethodNotAvailable: if API method is not
|
|
available remotely and an error occured (error code passed as
|
|
exception argument)
|
|
@raise WebService.AuthenticationRequired: if require_credentials
|
|
is True and credentials are required.
|
|
@raise WebService.AuthenticationFailed: if credentials are
|
|
not valid
|
|
@raise WebService.MalformedResponse: if JSON response cannot be
|
|
converted back to dict.
|
|
@raise WebService.UnsupportedAPILevel: if client API and Web
|
|
Service API do not match
|
|
@raise WebService.MethodResponseError; if method execution failed
|
|
@raise WebService.AuthenticationRequired: if login information are
|
|
not available (user interface should raise a login form, validate
|
|
the credentials and retry the function call here)
|
|
"""
|
|
params = {
|
|
"package_names": " ".join(package_names),
|
|
}
|
|
valid = self._method_getter("add_downloads", params, cache = False,
|
|
require_credentials = True)
|
|
if valid:
|
|
# NOTE: we can accept to be non-atomic in this case.
|
|
# TODO: cannot remove all the vote cache when just one element gets
|
|
# tained
|
|
self._drop_cached("get_downloads")
|
|
return valid
|
|
|
|
def get_icons(self, package_names, offset = 0, cache = True):
|
|
"""
|
|
For given package names, return the current Document icon object
|
|
DocumentList.
|
|
Packages having no icon will get empty DocumentList as value.
|
|
|
|
Results are paged, if offset is 0, the first page is returned.
|
|
For gathering information regarding pages (total documents,
|
|
offset provided, etc), see DocumentList API.
|
|
|
|
@param package_names: list of names of the packages to query,
|
|
either atom or key
|
|
@type package_names: list
|
|
@keyword offset: specify the offset document from where to start
|
|
for each package_name
|
|
@type offset: int
|
|
@keyword cache: True means use on-disk cache if available?
|
|
@type cache: bool
|
|
@return: mapping composed by package name as key and DocumentList
|
|
as value
|
|
@rtype: dict
|
|
|
|
@raise WebService.UnsupportedParameters: if input parameters are
|
|
invalid
|
|
@raise WebService.RequestError: if request cannot be satisfied
|
|
@raise WebService.MethodNotAvailable: if API method is not
|
|
available remotely and an error occured (error code passed as
|
|
exception argument)
|
|
@raise WebService.AuthenticationRequired: if require_credentials
|
|
is True and credentials are required.
|
|
@raise WebService.AuthenticationFailed: if credentials are
|
|
not valid
|
|
@raise WebService.MalformedResponse: if JSON response cannot be
|
|
converted back to dict.
|
|
@raise WebService.UnsupportedAPILevel: if client API and Web
|
|
Service API do not match
|
|
@raise WebService.MethodResponseError; if method execution failed
|
|
"""
|
|
document_type_filter = [Document.ICON_TYPE_ID]
|
|
return self.get_documents(package_names,
|
|
document_type_filter = document_type_filter,
|
|
offset = offset, cache = cache)
|
|
|
|
def get_comments(self, package_names, offset = 0, cache = True):
|
|
"""
|
|
For given package names, return the current Document Comment object
|
|
DocumentList.
|
|
Packages having no comments will get empty DocumentList as value.
|
|
|
|
Results are paged, if offset is 0, the first page is returned.
|
|
For gathering information regarding pages (total documents,
|
|
offset provided, etc), see DocumentList API.
|
|
|
|
@param package_names: list of names of the packages to query,
|
|
either atom or key
|
|
@type package_names: list
|
|
@keyword offset: specify the offset document from where to start
|
|
for each package_name
|
|
@type offset: int
|
|
@keyword cache: True means use on-disk cache if available?
|
|
@type cache: bool
|
|
@return: mapping composed by package name as key and DocumentList
|
|
as value
|
|
@rtype: dict
|
|
|
|
@raise WebService.UnsupportedParameters: if input parameters are
|
|
invalid
|
|
@raise WebService.RequestError: if request cannot be satisfied
|
|
@raise WebService.MethodNotAvailable: if API method is not
|
|
available remotely and an error occured (error code passed as
|
|
exception argument)
|
|
@raise WebService.AuthenticationRequired: if require_credentials
|
|
is True and credentials are required.
|
|
@raise WebService.AuthenticationFailed: if credentials are
|
|
not valid
|
|
@raise WebService.MalformedResponse: if JSON response cannot be
|
|
converted back to dict.
|
|
@raise WebService.UnsupportedAPILevel: if client API and Web
|
|
Service API do not match
|
|
@raise WebService.MethodResponseError; if method execution failed
|
|
"""
|
|
document_type_filter = [Document.COMMENT_TYPE_ID]
|
|
return self.get_documents(package_names,
|
|
document_type_filter = document_type_filter,
|
|
offset = offset, cache = cache)
|
|
|
|
def get_documents(self, package_names, document_type_filter = None,
|
|
offset = 0, cache = True):
|
|
"""
|
|
For given package names, return the current Document object
|
|
DocumentList.
|
|
Packages having no documents will get empty DocumentList as value.
|
|
|
|
Results are paged, if offset is 0, the first page is returned.
|
|
For gathering information regarding pages (total documents,
|
|
offset provided, etc), see DocumentList API.
|
|
|
|
@param package_names: list of names of the packages to query,
|
|
either atom or key
|
|
@type package_names: list
|
|
@keyword document_type_filter: list of document type identifiers (
|
|
see Document class) that are required.
|
|
@type document_type_filter: list
|
|
@keyword offset: specify the offset document from where to start
|
|
for each package_name
|
|
@type offset: int
|
|
@keyword cache: True means use on-disk cache if available?
|
|
@type cache: bool
|
|
@return: mapping composed by package name as key and DocumentList as
|
|
value
|
|
@rtype: dict
|
|
|
|
@raise WebService.UnsupportedParameters: if input parameters are
|
|
invalid
|
|
@raise WebService.RequestError: if request cannot be satisfied
|
|
@raise WebService.MethodNotAvailable: if API method is not
|
|
available remotely and an error occured (error code passed as
|
|
exception argument)
|
|
@raise WebService.AuthenticationRequired: if require_credentials
|
|
is True and credentials are required.
|
|
@raise WebService.AuthenticationFailed: if credentials are
|
|
not valid
|
|
@raise WebService.MalformedResponse: if JSON response cannot be
|
|
converted back to dict.
|
|
@raise WebService.UnsupportedAPILevel: if client API and Web
|
|
Service API do not match
|
|
@raise WebService.MethodResponseError; if method execution failed
|
|
"""
|
|
if document_type_filter is None:
|
|
document_type_filter = []
|
|
params = {
|
|
"package_names": " ".join(package_names),
|
|
"filter": " ".join([str(x) for x in document_type_filter]),
|
|
"offset": offset,
|
|
}
|
|
objs = self._method_getter("get_documents", params, cache = cache,
|
|
require_credentials = False)
|
|
data = {}
|
|
for package_name in package_names:
|
|
objs_map = objs.get(package_name)
|
|
if not objs_map:
|
|
data[package_name] = DocumentList(package_name, 0, offset)
|
|
continue
|
|
|
|
total, objs = objs_map['total'], objs_map['docs']
|
|
|
|
m_objs = data.setdefault(package_name,
|
|
DocumentList(package_name, total, offset))
|
|
for obj in objs:
|
|
d_obj = Document(self._repository_id,
|
|
obj[Document.DOCUMENT_DOCUMENT_ID],
|
|
obj[Document.DOCUMENT_DOCUMENT_TYPE_ID])
|
|
d_obj.update(obj)
|
|
m_objs.append(d_obj)
|
|
return data
|
|
|
|
def get_documents_by_id(self, document_ids, cache = True):
|
|
"""
|
|
For given Document object identifiers, return the respective Document
|
|
object.
|
|
Unavailable Document object identifiers will have None as dict value.
|
|
|
|
@param document_ids: list of document identifiers (int)
|
|
either atom or key
|
|
@type document_ids: list
|
|
@keyword cache: True means use on-disk cache if available?
|
|
@type cache: bool
|
|
@return: mapping composed by Document identifier as key and
|
|
Document as value
|
|
@rtype: dict
|
|
|
|
@raise WebService.UnsupportedParameters: if input parameters are
|
|
invalid
|
|
@raise WebService.RequestError: if request cannot be satisfied
|
|
@raise WebService.MethodNotAvailable: if API method is not
|
|
available remotely and an error occured (error code passed as
|
|
exception argument)
|
|
@raise WebService.AuthenticationRequired: if require_credentials
|
|
is True and credentials are required.
|
|
@raise WebService.AuthenticationFailed: if credentials are
|
|
not valid
|
|
@raise WebService.MalformedResponse: if JSON response cannot be
|
|
converted back to dict.
|
|
@raise WebService.UnsupportedAPILevel: if client API and Web
|
|
Service API do not match
|
|
@raise WebService.MethodResponseError; if method execution failed
|
|
"""
|
|
params = {
|
|
"document_ids": " ".join([str(x) for x in document_ids]),
|
|
}
|
|
objs = self._method_getter("get_documents_by_id", params, cache = cache,
|
|
require_credentials = False)
|
|
data = {}
|
|
for document_id in document_ids:
|
|
obj = objs.get(document_id)
|
|
if obj is not None:
|
|
d_obj = Document(self._repository_id,
|
|
obj[Document.DOCUMENT_DOCUMENT_ID],
|
|
obj[Document.DOCUMENT_DOCUMENT_TYPE_ID])
|
|
d_obj.update(obj)
|
|
obj = d_obj
|
|
data[package_name] = obj
|
|
return data
|
|
|
|
def _drop_document_cache(self):
|
|
"""
|
|
Drop all on-disk cache items related to document cache.
|
|
"""
|
|
self._drop_cached("get_documents")
|
|
self._drop_cached("get_documents_by_id")
|
|
self._drop_cached("get_comments")
|
|
self._drop_cached("get_icons")
|
|
|
|
def add_document(self, document):
|
|
"""
|
|
Send a new Document object to the service.
|
|
This method will return the newly created remote document object, or
|
|
raise exceptions in case the operation failed.
|
|
|
|
@param package_names: list of package names, either atoms or keys
|
|
@type package_names: list
|
|
@return: the newly created remote Document object
|
|
@rtype: Document
|
|
|
|
@raise WebService.UnsupportedParameters: if input parameters are
|
|
invalid
|
|
@raise WebService.RequestError: if request cannot be satisfied
|
|
@raise WebService.MethodNotAvailable: if API method is not
|
|
available remotely and an error occured (error code passed as
|
|
exception argument)
|
|
@raise WebService.AuthenticationRequired: if require_credentials
|
|
is True and credentials are required.
|
|
@raise WebService.AuthenticationFailed: if credentials are
|
|
not valid
|
|
@raise WebService.MalformedResponse: if JSON response cannot be
|
|
converted back to dict.
|
|
@raise WebService.UnsupportedAPILevel: if client API and Web
|
|
Service API do not match
|
|
@raise WebService.MethodResponseError; if method execution failed
|
|
@raise WebService.AuthenticationRequired: if login information are
|
|
not available (user interface should raise a login form, validate
|
|
the credentials and retry the function call here)
|
|
@raise ClientWebService.DocumentError: if document submitted is
|
|
invalid (contains invalid fields)
|
|
"""
|
|
if not isinstance(document, Document):
|
|
raise AttributeError("only accepting Document objects")
|
|
if document[Document.DOCUMENT_DOCUMENT_ID] is not None:
|
|
raise WebService.UnsupportedParameters("document is not new")
|
|
# This returns None if document is not accepted
|
|
remote_document = self._method_getter("add_document", document,
|
|
cache = False, require_credentials = True)
|
|
if remote_document is None:
|
|
raise ClientWebService.DocumentError("Document not accepted")
|
|
|
|
# NOTE: we can accept to be non-atomic in this case.
|
|
self._drop_document_cache()
|
|
return remote_document
|
|
|
|
def remove_document(self, document_id):
|
|
"""
|
|
Remove a Document (through its id) from the service repository.
|
|
|
|
@param document_id: Entropy Document identifier
|
|
@type document_id: int
|
|
@return: True if document has been removed, False if Document doesn't
|
|
exist
|
|
@rtype: bool
|
|
|
|
@raise WebService.UnsupportedParameters: if input parameters are
|
|
invalid
|
|
@raise WebService.RequestError: if request cannot be satisfied
|
|
@raise WebService.MethodNotAvailable: if API method is not
|
|
available remotely and an error occured (error code passed as
|
|
exception argument)
|
|
@raise WebService.AuthenticationRequired: if require_credentials
|
|
is True and credentials are required.
|
|
@raise WebService.AuthenticationFailed: if credentials are
|
|
not valid
|
|
@raise WebService.MalformedResponse: if JSON response cannot be
|
|
converted back to dict.
|
|
@raise WebService.UnsupportedAPILevel: if client API and Web
|
|
Service API do not match
|
|
@raise WebService.MethodResponseError; if method execution failed
|
|
@raise WebService.AuthenticationRequired: if login information are
|
|
not available (user interface should raise a login form, validate
|
|
the credentials and retry the function call here)
|
|
"""
|
|
params = {
|
|
Document.DOCUMENT_DOCUMENT_ID: document_id,
|
|
}
|
|
result = self._method_getter("remove_document", params,
|
|
cache = False, require_credentials = True)
|
|
# NOTE: we can accept to be non-atomic in this case.
|
|
self._drop_document_cache()
|
|
return result
|
|
|
|
def report_error(self, error_params):
|
|
"""
|
|
Entropy Client Error reporting method. This is mainly used by
|
|
Entropy internal code in order to submit a stacktrace and other data
|
|
to Entropy developers. "error_params" consinsts in a dictionary
|
|
containing error data in string format as values.
|
|
|
|
@param error_params: dictionary containing error report data
|
|
@type error_params: dict
|
|
|
|
@raise WebService.UnsupportedParameters: if input parameters are
|
|
invalid
|
|
@raise WebService.RequestError: if request cannot be satisfied
|
|
@raise WebService.MethodNotAvailable: if API method is not
|
|
available remotely and an error occured (error code passed as
|
|
exception argument)
|
|
@raise WebService.AuthenticationRequired: if require_credentials
|
|
is True and credentials are required.
|
|
@raise WebService.AuthenticationFailed: if credentials are
|
|
not valid
|
|
@raise WebService.MalformedResponse: if JSON response cannot be
|
|
converted back to dict.
|
|
@raise WebService.UnsupportedAPILevel: if client API and Web
|
|
Service API do not match
|
|
@raise WebService.MethodResponseError; if method execution failed
|
|
"""
|
|
self._method_getter("report_error", error_params,
|
|
require_credentials = False, cache = False)
|