From 3b45faece9ca8a8d593b62829e19d4bc035313b0 Mon Sep 17 00:00:00 2001 From: Mike Fedosin Date: Tue, 14 Feb 2017 23:43:28 +0300 Subject: [PATCH] Update docstrings Change-Id: I2be4437a5afb221f56f4d93a24be0356e7abc033 --- glare/api/middleware/context.py | 4 +- glare/api/middleware/version_negotiation.py | 4 +- glare/api/v1/api_version_request.py | 14 +- glare/api/v1/api_versioning.py | 11 +- glare/api/v1/resource.py | 86 +++--- glare/api/v1/router.py | 6 +- glare/api/versions.py | 1 + glare/cmd/api.py | 2 +- glare/common/config.py | 2 +- glare/common/exception.py | 2 +- glare/common/policy.py | 2 +- glare/common/store_api.py | 4 +- glare/common/utils.py | 38 +-- glare/engine.py | 113 +++++--- glare/locking.py | 44 +-- glare/notification.py | 6 +- glare/objects/all.py | 2 +- glare/objects/base.py | 293 +++++++++++--------- glare/objects/meta/attribute.py | 4 +- glare/objects/meta/registry.py | 8 +- glare/objects/meta/validators.py | 2 +- glare/wsgi.py | 2 +- 22 files changed, 357 insertions(+), 293 deletions(-) diff --git a/glare/api/middleware/context.py b/glare/api/middleware/context.py index 796dea8..2f85583 100644 --- a/glare/api/middleware/context.py +++ b/glare/api/middleware/context.py @@ -67,7 +67,7 @@ class ContextMiddleware(base_middleware.ConfigurableMiddleware): @staticmethod def process_request(req): - """Convert authentication information into a request context + """Convert authentication information into a request context. Generate a RequestContext object from the available authentication headers and store on the 'context' attribute @@ -88,7 +88,7 @@ class ContextMiddleware(base_middleware.ConfigurableMiddleware): @staticmethod def _get_anonymous_context(): - """Anonymous user has only Read-Only grants""" + """Anonymous user has only Read-Only grants.""" return RequestContext(read_only=True, is_admin=False) @staticmethod diff --git a/glare/api/middleware/version_negotiation.py b/glare/api/middleware/version_negotiation.py index a032947..0e290f2 100644 --- a/glare/api/middleware/version_negotiation.py +++ b/glare/api/middleware/version_negotiation.py @@ -16,7 +16,7 @@ """ A filter middleware that inspects the requested URI for a version string and/or Accept headers and attempts to negotiate an API controller to -return +return. """ import microversion_parse @@ -32,7 +32,7 @@ LOG = logging.getLogger(__name__) def get_version_from_accept(accept_header, vnd_mime_type): - """Try to parse accept header to extract api version + """Try to parse accept header to extract api version. :param accept_header: accept header :return: version string in the request or None if not specified diff --git a/glare/api/v1/api_version_request.py b/glare/api/v1/api_version_request.py index 1dc445c..675b5ed 100644 --- a/glare/api/v1/api_version_request.py +++ b/glare/api/v1/api_version_request.py @@ -40,7 +40,7 @@ class APIVersionRequest(object): """Create an API version request object. :param version_string: String representation of APIVersionRequest. - Correct format is 'X.Y', where 'X' and 'Y' are int values. + Correct format is 'X.Y', where 'X' and 'Y' are int values. """ match = re.match(r"^([1-9]\d*)\.([1-9]\d*|0)$", version_string) if match: @@ -95,9 +95,9 @@ class APIVersionRequest(object): greater than or equal to the minimum version and less than or equal to the maximum version. - @param min_version: Minimum acceptable version. - @param max_version: Maximum acceptable version. - @returns: boolean + :param min_version: Minimum acceptable version. + :param max_version: Maximum acceptable version. + :returns: boolean """ return min_version <= self <= max_version @@ -109,15 +109,15 @@ class APIVersionRequest(object): @classmethod def min_version(cls): - """Minimal allowed api version""" + """Minimal allowed api version.""" return APIVersionRequest(cls._MIN_API_VERSION) @classmethod def max_version(cls): - """Maximal allowed api version""" + """Maximal allowed api version.""" return APIVersionRequest(cls._MAX_API_VERSION) @classmethod def default_version(cls): - """Default api version if no version in request""" + """Default api version if no version in request.""" return APIVersionRequest(cls._DEFAULT_API_VERSION) diff --git a/glare/api/v1/api_versioning.py b/glare/api/v1/api_versioning.py index e953df6..b7fcba5 100644 --- a/glare/api/v1/api_versioning.py +++ b/glare/api/v1/api_versioning.py @@ -22,13 +22,14 @@ from glare.i18n import _ class VersionedMethod(object): def __init__(self, name, start_version, end_version, func): - """Versioning information for a single method + """Versioning information for a single method. + :param name: Name of the method :param start_version: Minimum acceptable version :param end_version: Maximum acceptable_version :param func: Method to call - Minimum and maximums are inclusive """ + # NOTE(kairat): minimums and maximums are inclusive self.name = name self.start_version = start_version self.end_version = end_version @@ -41,7 +42,7 @@ class VersionedMethod(object): class VersionedResource(object): """Versioned mixin that provides ability to define versioned methods and - return appropriate methods based on user request + return appropriate methods based on user request. """ # prefix for all versioned methods in class @@ -52,6 +53,7 @@ class VersionedResource(object): """Determines whether function list contains version intervals intersections or not. General algorithm: https://en.wikipedia.org/wiki/Intersection_algorithm + :param func_list: list of VersionedMethod objects :return: boolean """ @@ -128,9 +130,10 @@ class VersionedResource(object): def version_select(*args, **kwargs): """Look for the method which matches the name supplied and version constraints and calls it with the supplied arguments. + :returns: Returns the result of the method called :raises: VersionNotFoundForAPIMethod if there is no method which - matches the name and version constraints + matches the name and version constraints """ # versioning is used in 3 classes: request deserializer and # controller have request as first argument diff --git a/glare/api/v1/resource.py b/glare/api/v1/resource.py index 6788825..e136c3a 100644 --- a/glare/api/v1/resource.py +++ b/glare/api/v1/resource.py @@ -50,11 +50,12 @@ supported_versions = api_versioning.VersionedResource.supported_versions class RequestDeserializer(api_versioning.VersionedResource, wsgi.JSONRequestDeserializer): - """Glare deserializer for incoming webop Requests. - Deserializer converts incoming request into bunch of python primitives. - So other components doesn't work with requests at all. Deserializer also - executes primary API validation without any knowledge about Artifact - structure. + """Glare deserializer for incoming webob requests. + + Deserializer checks and converts incoming request into a bunch of Glare + primitives. So other service components don't work with requests at all. + Deserializer also performs primary API validation without any knowledge + about concrete artifact type structure. """ @staticmethod @@ -74,6 +75,7 @@ class RequestDeserializer(api_versioning.VersionedResource, return content_type def _get_request_body(self, req): + """Get request json body and convert it to python structures.""" return self.from_json(req.body) @supported_versions(min_ver='1.0') @@ -137,7 +139,7 @@ class RequestDeserializer(api_versioning.VersionedResource, patch = jsonpatch.JsonPatch(body) try: # Initially patch object doesn't validate input. It's only checked - # we call get operation on each method + # when we call get operation on each method tuple(map(patch._get_operation, patch.patch)) except (jsonpatch.InvalidJsonPatch, TypeError): msg = _("Json Patch body is malformed") @@ -152,7 +154,7 @@ class RequestDeserializer(api_versioning.VersionedResource, data = self._get_request_body(req) if 'url' not in data: msg = _("url is required when specifying external location. " - "Cannot find url in body: %s") % str(data) + "Cannot find 'url' in request body: %s") % str(data) raise exc.BadRequest(msg) else: data = req.body_file @@ -176,10 +178,10 @@ def log_request_progress(f): class ArtifactsController(api_versioning.VersionedResource): """API controller for Glare Artifacts. + Artifact Controller prepares incoming data for Glare Engine and redirects - data to appropriate engine method (so only controller is working with - Engine. Once the data returned from Engine Controller returns data - in appropriate format for Response Serializer. + data to the appropriate engine method. Once the response data is returned + from the engine Controller passes it next to Response Serializer. """ def __init__(self): @@ -188,12 +190,21 @@ class ArtifactsController(api_versioning.VersionedResource): @supported_versions(min_ver='1.0') @log_request_progress def list_type_schemas(self, req): + """List of detailed descriptions of enabled artifact types. + + :return: list of json-schemas of all enabled artifact types. + """ type_schemas = self.engine.list_type_schemas(req.context) return type_schemas @supported_versions(min_ver='1.0') @log_request_progress def show_type_schema(self, req, type_name): + """Get detailed artifact type description. + + :param type_name: artifact type name + :return: json-schema representation of artifact type + """ type_schema = self.engine.show_type_schema(req.context, type_name) return {type_name: type_schema} @@ -202,10 +213,10 @@ class ArtifactsController(api_versioning.VersionedResource): def create(self, req, type_name, values): """Create artifact record in Glare. - :param req: User request - :param type_name: Artifact type name - :param values: dict with artifact fields {field_name: field_value} - :return definition of created artifact + :param req: user request + :param type_name: artifact type name + :param values: dict with artifact fields + :return: definition of created artifact """ return self.engine.create(req.context, type_name, values) @@ -218,14 +229,14 @@ class ArtifactsController(api_versioning.VersionedResource): :param type_name: Artifact type name :param artifact_id: id of artifact to update :param patch: json patch with artifact changes - :return definition of updated artifact + :return: definition of updated artifact """ return self.engine.update(req.context, type_name, artifact_id, patch) @supported_versions(min_ver='1.0') @log_request_progress def delete(self, req, type_name, artifact_id): - """Delete artifact from Glare + """Delete artifact from Glare. :param req: User request :param type_name: Artifact type name @@ -236,7 +247,7 @@ class ArtifactsController(api_versioning.VersionedResource): @supported_versions(min_ver='1.0') @log_request_progress def show(self, req, type_name, artifact_id): - """Show detailed artifact info + """Show detailed artifact info. :param req: User request :param type_name: Artifact type name @@ -249,7 +260,7 @@ class ArtifactsController(api_versioning.VersionedResource): @log_request_progress def list(self, req, type_name, filters, marker=None, limit=None, sort=None, latest=False): - """List available artifacts + """List available artifacts. :param req: User request :param type_name: Artifact type name @@ -261,7 +272,7 @@ class ArtifactsController(api_versioning.VersionedResource): :param sort: sorting options :param latest: flag that indicates, that only artifacts with highest versions should be returned in output - :return: list of artifacts + :return: list of requested artifact definitions """ artifacts = self.engine.list(req.context, type_name, filters, marker, limit, sort, latest) @@ -273,18 +284,19 @@ class ArtifactsController(api_versioning.VersionedResource): @supported_versions(min_ver='1.0') @log_request_progress - def upload_blob(self, req, type_name, artifact_id, blob_name, data, + def upload_blob(self, req, type_name, artifact_id, blob_path, data, content_type): - """Upload blob into Glare repo + """Upload blob into Glare repo. :param req: User request :param type_name: Artifact type name - :param artifact_id: id of Artifact to reactivate - :param blob_name: name of blob field in artifact - :param data: Artifact payload + :param artifact_id: id of artifact where to perform upload + :param blob_path: path to artifact blob + :param data: blob payload :param content_type: data content-type + :return: definition of requested artifact with uploaded blob """ - field_name, _sep, blob_key = blob_name.partition('/') + field_name, _sep, blob_key = blob_path.partition('/') if not blob_key: blob_key = None if content_type == ('application/vnd+openstack.glare-custom-location' @@ -300,16 +312,16 @@ class ArtifactsController(api_versioning.VersionedResource): @supported_versions(min_ver='1.0') @log_request_progress - def download_blob(self, req, type_name, artifact_id, blob_name): - """Download blob data from Artifact + def download_blob(self, req, type_name, artifact_id, blob_path): + """Download blob data from Artifact. :param req: User request - :param type_name: Artifact type name - :param artifact_id: id of Artifact to reactivate - :param blob_name: name of blob field in artifact - :return: iterator that returns blob data + :param type_name: artifact type name + :param artifact_id: id of artifact from where to perform download + :param blob_path: path to artifact blob + :return: requested blob data """ - field_name, _sep, blob_key = blob_name.partition('/') + field_name, _sep, blob_key = blob_path.partition('/') if not blob_key: blob_key = None data, meta = self.engine.download_blob( @@ -320,10 +332,10 @@ class ArtifactsController(api_versioning.VersionedResource): class ResponseSerializer(api_versioning.VersionedResource, wsgi.JSONResponseSerializer): - """Glare Response Serializer converts data received from Glare Engine - (it consists from plain data types - dict, int, string, file descriptors, - etc) to WSGI Requests. It also specifies proper response status and - content type as specified by API design. + """Glare serializer for outgoing responses. + + Converts data received from the engine to WSGI responses. It also + specifies proper response status and content type as declared in the API. """ @staticmethod @@ -425,7 +437,7 @@ class ResponseSerializer(api_versioning.VersionedResource, def create_resource(): - """Artifact resource factory method""" + """Artifact resource factory method.""" deserializer = RequestDeserializer() serializer = ResponseSerializer() controller = ArtifactsController() diff --git a/glare/api/v1/router.py b/glare/api/v1/router.py index c12ba45..d7ed798 100644 --- a/glare/api/v1/router.py +++ b/glare/api/v1/router.py @@ -83,16 +83,16 @@ class API(wsgi.Router): allowed_methods='GET, PATCH, DELETE') # ---blobs--- - mapper.connect('/artifacts/{type_name}/{artifact_id}/{blob_name:.*?}', + mapper.connect('/artifacts/{type_name}/{artifact_id}/{blob_path:.*?}', controller=glare_resource, action='download_blob', conditions={'method': ['GET']}, body_reject=True) - mapper.connect('/artifacts/{type_name}/{artifact_id}/{blob_name:.*?}', + mapper.connect('/artifacts/{type_name}/{artifact_id}/{blob_path:.*?}', controller=glare_resource, action='upload_blob', conditions={'method': ['PUT']}) - mapper.connect('/artifacts/{type_name}/{artifact_id}/{blob_name:.*?}', + mapper.connect('/artifacts/{type_name}/{artifact_id}/{blob_path:.*?}', controller=reject_method_resource, action='reject', allowed_methods='GET, PUT') diff --git a/glare/api/versions.py b/glare/api/versions.py index 9efdfad..09c08c7 100644 --- a/glare/api/versions.py +++ b/glare/api/versions.py @@ -60,6 +60,7 @@ class Controller(object): @staticmethod def index(req, is_multi): """Respond to a request for all OpenStack API versions. + :param is_multi: defines if multiple choices should be response status or not :param req: user request object diff --git a/glare/cmd/api.py b/glare/cmd/api.py index 9e7be6a..41e3d07 100755 --- a/glare/cmd/api.py +++ b/glare/cmd/api.py @@ -16,7 +16,7 @@ """ -Glare (Glare Artifact Repository) API service +Glare (Glare Artifact Repository) API service. """ import os diff --git a/glare/common/config.py b/glare/common/config.py index 516c472..e004f65 100644 --- a/glare/common/config.py +++ b/glare/common/config.py @@ -14,7 +14,7 @@ # under the License. """ -Routines for configuring Glare +Routines for configuring Glare. """ import logging.config diff --git a/glare/common/exception.py b/glare/common/exception.py index a8f6a9e..e3c4543 100644 --- a/glare/common/exception.py +++ b/glare/common/exception.py @@ -24,7 +24,7 @@ LOG = logging.getLogger(__name__) class GlareException(Exception): """ - Base Glare Exception + Base Glare Exception class. To correctly use this class, inherit from it and define a 'message' property. That message will get printf'd diff --git a/glare/common/policy.py b/glare/common/policy.py index 719cd87..6af5ce6 100644 --- a/glare/common/policy.py +++ b/glare/common/policy.py @@ -97,7 +97,7 @@ def reset(): def authorize(policy_name, target, context, do_raise=True): - """Method checks that user action can be executed according to policies + """Method checks that user action can be executed according to policies. :param policy_name: policy name :param target: diff --git a/glare/common/store_api.py b/glare/common/store_api.py index 51f04a4..8784646 100644 --- a/glare/common/store_api.py +++ b/glare/common/store_api.py @@ -53,7 +53,7 @@ error_map = [{'catch': store_exc.NotFound, @utils.error_handler(error_map) def save_blob_to_store(blob_id, blob, context, max_size, store_type=None, verifier=None): - """Save file to specified store type and return location info to the user + """Save file to specified store type and return location info to the user. :param store_type: type of the store, None means save to default store. :param blob_id: id of artifact @@ -84,7 +84,7 @@ def load_from_store(uri, context): @utils.error_handler(error_map) def delete_blob(uri, context): - """Delete blob from backend store + """Delete blob from backend store. :param uri: blob uri :param context: user context diff --git a/glare/common/utils.py b/glare/common/utils.py index 5682ed1..e98cac9 100644 --- a/glare/common/utils.py +++ b/glare/common/utils.py @@ -68,7 +68,7 @@ def chunkreadable(iter, chunk_size=65536): def chunkiter(fp, chunk_size=65536): """ - Return an iterator to a file-like obj which yields fixed size chunks + Return an iterator to a file-like obj which yields fixed size chunks. :param fp: a file-like object :param chunk_size: maximum size of chunk @@ -361,7 +361,7 @@ except re.error: def no_4byte_params(f): """ Checks that no 4 byte unicode characters are allowed - in dicts' keys/values and string's parameters + in dicts' keys/values and string's parameters. """ def wrapper(*args, **kwargs): @@ -423,8 +423,7 @@ def split_filter_op(expression): When no operator is found, default to an equality comparison. :param expression: the expression to parse - - :returns: a tuple (operator, threshold) parsed from expression + :return: a tuple (operator, threshold) parsed from expression """ left, sep, right = expression.partition(':') if sep: @@ -500,11 +499,8 @@ def evaluate_filter_op(value, operator, threshold): :param value: evaluated against the operator, as left side of expression :param operator: any supported filter operation :param threshold: to compare value against, as right side of expression - :raises: InvalidFilterOperatorValue if an unknown operator is provided - - :returns: boolean result of applied comparison - + :return: boolean result of applied comparison """ if operator == 'gt': return value > threshold @@ -525,21 +521,25 @@ def evaluate_filter_op(value, operator, threshold): class error_handler(object): def __init__(self, error_map, default_exception=None): + """Init method of the class. + + :param error_map: dict of exception that can be raised + in func and exceptions that must be raised for these exceptions. + For example, if sqlalchemy NotFound might be raised and we need + re-raise it as glare NotFound exception then error_map must + contain {"catch": SQLAlchemyNotFound, + "raise": exceptions.NotFound} + :param default_exception: default exception that must be raised if + exception that cannot be found in error map was raised + :return: func + """ self.error_map = error_map self.default_exception = default_exception def __call__(self, f): - """Decorator that catches exception that came from func or method - :param f: targer func - :param error_map: dict of exception that can be raised - in func and exceptions that must be raised for these exceptions. - For example, if sqlalchemy NotFound might be raised and we need - re-raise it as glare NotFound exception then error_map must - contain {"catch": SQLAlchemyNotFound, - "raise": exceptions.NotFound} - :param default_exception: default exception that must be raised if - exception that cannot be found in error map was raised - :return: func + """Decorator that catches exception that came from func or method. + + :param f: target func """ def new_function(*args, **kwargs): diff --git a/glare/engine.py b/glare/engine.py index c8e1090..db76447 100644 --- a/glare/engine.py +++ b/glare/engine.py @@ -39,15 +39,16 @@ class Engine(object): """Engine is responsible for executing different helper operations when processing incoming requests from Glare API. Engine receives incoming data and does the following: - - check basic policy permissions - - requests artifact definition from registry - - check access permission(ro, rw) - - lock artifact for update if needed + - check basic policy permissions; + - requests artifact definition from artifact type registry; + - check access permission(ro, rw); + - lock artifact for update if needed; - pass data to base artifact to execute all business logic operations + with database; - notify other users about finished operation. Engine should not include any business logic and validation related - to Artifacts. Engine should not know any internal details of Artifacts - because it controls access to Artifacts in common. + to Artifacts. Engine should not know any internal details of artifact + type, because this part of the work is done by Base artifact type. """ def __init__(self): # register all artifact types @@ -69,28 +70,32 @@ class Engine(object): @classmethod def _get_artifact(cls, context, type_name, artifact_id, read_only=False): - """Return artifact for users + """Return artifact requested by user. + Check access permissions and policies. - Return artifact for reading/modification by users. Check - access permissions and policies for artifact. + :param context: user context + :param type_name: artifact type name + :param artifact_id: id of the artifact to be updated + :param read_only: flag, if set to True only read access is checked, + if False then engine checks if artifact can be modified by the user """ def _check_read_write_access(ctx, af): - """Check if artifact can be modified by user + """Check if artifact can be modified by user. :param ctx: user context :param af: artifact definition - :raise Forbidden if access is not allowed + :raise: Forbidden if access is not allowed """ if not ctx.is_admin and ctx.tenant != af.owner or ctx.read_only: raise exception.Forbidden() def _check_read_only_access(ctx, af): - """Check if user has read only access to artifact + """Check if user has read only access to artifact. :param ctx: user context :param af: artifact definition - :raise Forbidden if access is not allowed + :raise: Forbidden if access is not allowed """ private = af.visibility != 'public' if (private and @@ -125,13 +130,19 @@ class Engine(object): return schemas[type_name] @classmethod - def create(cls, context, type_name, field_values): - """Create new artifact in Glare""" + def create(cls, context, type_name, values): + """Create artifact record in Glare. + + :param context: user context + :param type_name: artifact type name + :param values: dict with artifact fields + :return: dict representation of created artifact + """ action_name = "artifact:create" - policy.authorize(action_name, field_values, context) + policy.authorize(action_name, values, context) artifact_type = registry.ArtifactRegistry.get_artifact_type(type_name) # acquire version lock and execute artifact create - af = artifact_type.create(context, field_values) + af = artifact_type.create(context, values) # notify about new artifact Notifier.notify(context, action_name, af) # return artifact to the user @@ -142,24 +153,23 @@ class Engine(object): """Update artifact with json patch. Apply patch to artifact and validate artifact before updating it - in database. If there is request for visibility change or custom - location change then call specific method for that. + in database. If there is request for visibility or status change + then call specific method for that. :param context: user context :param type_name: name of artifact type :param artifact_id: id of the artifact to be updated - :param patch: json patch - :return: updated artifact + :param patch: json patch object + :return: dict representation of updated artifact """ - def get_updates(af_dict, patch_with_upd): - """Get updated values for artifact and json patch + def _get_updates(af_dict, patch_with_upd): + """Get updated values for artifact and json patch. :param af_dict: current artifact definition as dict - :param patch_with_upd: json-patch - :return: dict of updated attributes and their values + :param patch_with_upd: json-patch object + :return dict of updated attributes and their values """ - try: af_dict_patched = patch_with_upd.apply(af_dict) diff = utils.DictDiffer(af_dict_patched, af_dict) @@ -187,7 +197,7 @@ class Engine(object): with base.BaseArtifact.lock_engine.acquire(context, lock_key): artifact = cls._get_artifact(context, type_name, artifact_id) af_dict = artifact.to_dict() - updates = get_updates(af_dict, patch) + updates = _get_updates(af_dict, patch) LOG.debug("Update diff successfully calculated for artifact " "%(af)s %(diff)s", {'af': artifact_id, 'diff': updates}) @@ -196,6 +206,8 @@ class Engine(object): else: action = artifact.get_action_for_updates( context, artifact, updates, registry.ArtifactRegistry) + LOG.debug("Action %(action)s was defined to values %(val)s.", + {'action': action.__name__, 'val': updates}) action_name = "artifact:%s" % action.__name__ policy.authorize(action_name, af_dict, context) modified_af = action(context, artifact, updates) @@ -204,7 +216,13 @@ class Engine(object): @classmethod def get(cls, context, type_name, artifact_id): - """Return artifact representation from artifact repo.""" + """Show detailed artifact info. + + :param context: user context + :param type_name: Artifact type name + :param artifact_id: id of artifact to show + :return: definition of requested artifact + """ policy.authorize("artifact:get", {}, context) af = cls._get_artifact(context, type_name, artifact_id, read_only=True) @@ -225,7 +243,7 @@ class Engine(object): :param sort: sorting options :param latest: flag that indicates, that only artifacts with highest versions should be returned in output - :return: list of artifacts + :return: list of artifact definitions """ policy.authorize("artifact:list", {}, context) artifact_type = registry.ArtifactRegistry.get_artifact_type(type_name) @@ -237,7 +255,12 @@ class Engine(object): @classmethod def delete(cls, context, type_name, artifact_id): - """Delete artifact from glare""" + """Delete artifact from Glare. + + :param context: User context + :param type_name: Artifact type name + :param artifact_id: id of artifact to delete + """ af = cls._get_artifact(context, type_name, artifact_id) policy.authorize("artifact:delete", af.to_dict(), context) af.delete(context, af) @@ -254,9 +277,9 @@ class Engine(object): :param field_name: name of blob or blob dict field :param location: external blob url :param blob_meta: dictionary containing blob metadata like md5 checksum - :param blob_key: if field_name is blob dict it specifies concrete key + :param blob_key: if field_name is blob dict it specifies key in this dict - :return updated artifact + :return: dict representation of updated artifact """ af = cls._get_artifact(context, type_name, artifact_id) action_name = 'artifact:set_location' @@ -299,11 +322,10 @@ class Engine(object): :param artifact_id: id of the artifact to be updated :param field_name: name of blob or blob dict field :param fd: file descriptor that Glare uses to upload the file - :param field_name: name of blob dict field :param content_type: data content-type - :param blob_key: if field_name is blob dict it specifies concrete key - in this dict - :return file iterator for requested file + :param blob_key: if field_name is blob dict it specifies key + in this dictionary + :return: dict representation of updated artifact """ af = cls._get_artifact(context, type_name, artifact_id) action_name = "artifact:upload" @@ -348,12 +370,12 @@ class Engine(object): # if upload failed remove blob from db and storage with excutils.save_and_reraise_exception(logger=LOG): if blob_key is None: - af.update_blob(context, af.id, {field_name: None}) + af.update_blob(context, af.id, field_name, None) else: blob_dict_attr = modified_af[field_name] del blob_dict_attr[blob_key] af.update_blob(context, af.id, - {field_name: blob_dict_attr}) + field_name, blob_dict_attr) blob_name = "%s[%s]" % (field_name, blob_key) \ if blob_key else field_name LOG.info(_LI("Successfully finished blob upload for artifact " @@ -378,40 +400,43 @@ class Engine(object): def update_blob(cls, context, type_name, artifact_id, blob, field_name, blob_key=None, validate=False): """Update blob info. + :param context: user context :param type_name: name of artifact type :param artifact_id: id of the artifact to be updated :param blob: blob representation in dict format :param field_name: name of blob or blob dict field - :param blob_key: if field_name is blob dict it specifies concrete key + :param blob_key: if field_name is blob dict it specifies key in this dict :param validate: enable validation of possibility of blob uploading - :return updated artifact + + :return: dict representation of updated artifact """ lock_key = "%s:%s" % (type_name, artifact_id) with base.BaseArtifact.lock_engine.acquire(context, lock_key): af = cls._get_artifact(context, type_name, artifact_id) if validate: - af.validate_upload_allowed(context, af, field_name, blob_key) + af.validate_upload_allowed(af, field_name, blob_key) if blob_key is None: setattr(af, field_name, blob) return af.update_blob( - context, af.id, {field_name: getattr(af, field_name)}) + context, af.id, field_name, getattr(af, field_name)) else: blob_dict_attr = getattr(af, field_name) blob_dict_attr[blob_key] = blob return af.update_blob( - context, af.id, {field_name: blob_dict_attr}) + context, af.id, field_name, blob_dict_attr) @classmethod def download_blob(cls, context, type_name, artifact_id, field_name, blob_key=None): """Download binary data from Glare Artifact. + :param context: user context :param type_name: name of artifact type :param artifact_id: id of the artifact to be updated :param field_name: name of blob or blob dict field - :param blob_key: if field_name is blob dict it specifies concrete key + :param blob_key: if field_name is blob dict it specifies key in this dict :return: file iterator for requested file """ diff --git a/glare/locking.py b/glare/locking.py index 3120595..d79bf8c 100644 --- a/glare/locking.py +++ b/glare/locking.py @@ -20,11 +20,10 @@ LOG = logging.getLogger(__name__) class LockApiBase(object): - """Lock Api Base class that responsible for acquiring/releasing locks - """ + """Lock Api Base class that responsible for acquiring/releasing locks.""" def create_lock(self, context, lock_key): - """Acquire lock for current user + """Acquire lock for current user. :param context user context :param lock_key: unique lock identifier that defines lock scope @@ -33,22 +32,21 @@ class LockApiBase(object): raise NotImplementedError() def delete_lock(self, context, lock_id): - """Delete acquired user lock + """Delete acquired user lock. :param context: user context :param lock_id: lock internal identifier - :return: """ raise NotImplementedError() class Lock(object): """Object that stores lock context for users. This class is internal - and used only for Lock Engine. So users shouldn't use this class directly + and used only in lock engine, so users shouldn't use this class directly. """ def __init__(self, context, lock_id, lock_key, release_method): - """Initialize lock context""" + """Initialize lock context.""" self.context = context self.lock_id = lock_id self.lock_key = lock_key @@ -64,31 +62,33 @@ class Lock(object): class LockEngine(object): """Glare lock engine. + Defines how artifact updates must be synchronized with each other. When - some user obtains lock for the same piece of data then other user cannot - request that lock and get Conflict error. - This little engine also allows to encapsulate lock logic in one place so - we can potentially add tooz functionality in future to Glare. Right now - there are troubles with locks in Galera (especially in mysql) and zookeeper - requires additional work from IT engineers. So we need support production - ready DB locks in our implementation. + some user obtains a lock for the same artifact then other user cannot + request that lock and gets a Conflict error. """ + # NOTE(kairat): Lock Engine also allows to encapsulate lock logic in one + # place so we can potentially add tooz functionality in future to Glare. + # Right now there are troubles with locks in Galera (especially in mysql) + # and zookeeper requires additional work from IT engineers. So we need + # support production ready DB locks in our implementation. MAX_LOCK_LENGTH = 255 def __init__(self, lock_api): - """Initialize lock engine with some lock api + """Initialize lock engine with some lock api. - :param lock_api: api that allows to create/delete locks. It must be - db_api but it might be replaced with DLM in near future. + :param lock_api: api that allows to create/delete locks """ + # NOTE(kairat): lock_api is db_api now but it might be + # replaced with DLM in near future. self.lock_api = lock_api def acquire(self, context, lock_key): - """Acquire lock to update whole artifact + """Acquire lock for artifact. - Acquire lock to update artifact. If there is some other - lock for the same artifact then raise Conflict Error. + If there is some other lock with the same key then + raise Conflict Error. :param context: user context :param lock_key: lock key @@ -106,6 +106,10 @@ class LockEngine(object): return Lock(context, lock_id, lock_key, self.release) def release(self, lock): + """Release lock for artifact. + + :param lock: Lock object + """ if lock.lock_id is not None: self.lock_api.delete_lock(lock.context, lock.lock_id) LOG.info(_LI("Lock %(lock_id)s released for lock_key %(key)s"), diff --git a/glare/notification.py b/glare/notification.py index 04a2e3c..277ee4d 100644 --- a/glare/notification.py +++ b/glare/notification.py @@ -35,9 +35,7 @@ def set_defaults(control_exchange='glare'): class Notifier(object): - """Simple interface to receive Glare notifier - - """ + """Simple interface to receive Glare notifier.""" SERVICE_NAME = 'artifact' GLARE_NOTIFIER = None @@ -52,7 +50,7 @@ class Notifier(object): @classmethod def notify(cls, context, event_type, body, level='INFO'): - """Notify Glare listeners with some useful info + """Notify Glare listeners with some useful info. :param context: User request context :param event_type: type of event diff --git a/glare/objects/all.py b/glare/objects/all.py index 700baa4..73fe8aa 100644 --- a/glare/objects/all.py +++ b/glare/objects/all.py @@ -63,7 +63,7 @@ class All(base.BaseArtifact): raise exception.Forbidden("This type is read only.") @classmethod - def update_blob(cls, context, af_id, values): + def update_blob(cls, context, af_id, field_name, values): raise exception.Forbidden("This type is read only.") @classmethod diff --git a/glare/objects/base.py b/glare/objects/base.py index 2aa54a9..ccda855 100644 --- a/glare/objects/base.py +++ b/glare/objects/base.py @@ -170,19 +170,19 @@ class BaseArtifact(base.VersionedObject): @classmethod def is_blob(cls, field_name): - """Helper to check that field is blob + """Helper to check that a field is a blob. - :param field_name: name of field - :return: True if field is a blob, False otherwise + :param field_name: name of the field + :return: True if the field is a blob, False otherwise """ return isinstance(cls.fields.get(field_name), glare_fields.BlobField) @classmethod def is_blob_dict(cls, field_name): - """Helper to check that field is blob dict + """Helper to check that field is a blob dict. - :param field_name: name of field - :return: True if field is a blob dict, False otherwise + :param field_name: name of the field + :return: True if the field is a blob dict, False otherwise """ return (isinstance(cls.fields.get(field_name), glare_fields.Dict) and cls.fields[field_name].element_type == @@ -190,18 +190,18 @@ class BaseArtifact(base.VersionedObject): @classmethod def is_link(cls, field_name): - """Helper to check that field is link + """Helper to check that a field is a link. - :param field_name: name of field + :param field_name: name of the field :return: True if field is a link, False otherwise """ return isinstance(cls.fields.get(field_name), glare_fields.Link) @classmethod def is_link_dict(cls, field_name): - """Helper to check that field is link dict + """Helper to check that a field is a link dict. - :param field_name: name of field + :param field_name: name of the field :return: True if field is a link dict, False otherwise """ return (isinstance(cls.fields.get(field_name), glare_fields.Dict) and @@ -210,10 +210,10 @@ class BaseArtifact(base.VersionedObject): @classmethod def is_link_list(cls, field_name): - """Helper to check that field is link list + """Helper to check that a field is a link list. - :param field_name: name of field - :return: True if field is a link list, False otherwise + :param field_name: name of the field + :return: True if the field is a link list, False otherwise """ return (isinstance(cls.fields.get(field_name), glare_fields.List) and cls.fields[field_name].element_type == @@ -221,11 +221,11 @@ class BaseArtifact(base.VersionedObject): @classmethod def _init_artifact(cls, context, values): - """Initialize an empty versioned object with values + """Initialize an empty versioned object with values. Initialize vo object with default values and values specified by user. - Also reset all changes for initialized object so user of the method - can track own changes. + Also reset all changes of initialized object so user can track own + changes. :param context: user context :param values: values needs to be set @@ -239,31 +239,28 @@ class BaseArtifact(base.VersionedObject): default_attrs.append(attr) if default_attrs: af.obj_set_defaults(*default_attrs) + + # apply values specified by user for name, value in six.iteritems(values): setattr(af, name, value) return af @classmethod def get_type_name(cls): - """Return type name that allows to find Artifact Type in Glare + """Return type name that allows to find artifact type in Glare - Type name allows to find Artifact Type definition in Glare registry - so Engine can instantiate Artifacts. Artifact also becomes available - with artifact type in Glare API. - For example, when get_type_name returns 'my_artifact' then - users can list artifacts by GET /v1/artifacts/my_artifact. - This type name is also used in glare configuration when turning on/off - specific Artifact Types. - :return: string that identifies current Artifact Type. + Type name allows to find artifact type definition in Glare registry. + + :return: string that identifies current artifact type """ raise NotImplementedError() @classmethod def _lock_version(cls, context, values): - """Calculate version scope for new artifact + """Create scope lock for artifact creation. - :param values: af values - :return: string that identifies af version or None + :param values: artifact values + :return: Lock object """ name = values.get('name') version = values.get('version', cls.DEFAULT_ARTIFACT_VERSION) @@ -274,10 +271,15 @@ class BaseArtifact(base.VersionedObject): return cls.lock_engine.acquire(context, scope_id) @classmethod - def _lock_updated_version(cls, af, updates): - name = updates.get('name', af.name) - version = updates.get('version', af.version) - visibility = updates.get('visibility', af.visibility) + def _lock_updated_version(cls, af, values): + """Create scope lock for artifact update. + + :param values: artifact values + :return: Lock object + """ + name = values.get('name', af.name) + version = values.get('version', af.version) + visibility = values.get('visibility', af.visibility) scope_id = None if (name, version, visibility) != (af.name, af.version, af.visibility): # no version change == no lock for version @@ -289,11 +291,11 @@ class BaseArtifact(base.VersionedObject): @classmethod def create(cls, context, values): - """Create new Artifact in Glare repo + """Create new artifact in Glare repo. :param context: user context - :param values: Dict with specified artifact properties - :return: definition of create Artifact + :param values: dictionary with specified artifact fields + :return: created artifact object """ if context.tenant is None or context.read_only: msg = _("It's forbidden to anonymous users to create artifacts.") @@ -317,11 +319,18 @@ class BaseArtifact(base.VersionedObject): LOG.info(_LI("Parameters validation for artifact creation " "passed for request %s."), context.request_id) af_vals = cls.db_api.create( - context, af.obj_changes_to_primitive(), cls.get_type_name()) + context, af._obj_changes_to_primitive(), cls.get_type_name()) return cls._init_artifact(context, af_vals) @classmethod def _validate_versioning(cls, context, name, version, is_public=False): + """Validate if artifact with given name and version already exists. + + :param context: user context + :param name: name of artifact to be checked + :param version: version of artifact + :param is_public: flag that indicates to search artifact globally + """ if version is not None and name not in (None, ""): filters = [('name', name), ('version', version), ('status', 'neq:deleted')] @@ -341,7 +350,7 @@ class BaseArtifact(base.VersionedObject): @classmethod def _validate_change_allowed(cls, field_names, af=None, validate_blob_names=True): - """Validate if fields can be updated in artifact""" + """Validate if fields can be updated in artifact.""" af_status = cls.STATUS.DRAFTED if af is None else af.status if af_status not in (cls.STATUS.ACTIVE, cls.STATUS.DRAFTED): msg = _("Forbidden to change attributes " @@ -350,15 +359,15 @@ class BaseArtifact(base.VersionedObject): for field_name in field_names: if field_name not in cls.fields: - msg = _("%s property does not exist") % field_name + msg = _("%s field does not exist") % field_name raise exception.BadRequest(msg) field = cls.fields[field_name] if field.system is True: - msg = _("Cannot specify system property %s. It is not " + msg = _("Cannot specify system field %s. It is not " "available for modifying by users.") % field_name raise exception.Forbidden(msg) if af_status == cls.STATUS.ACTIVE and not field.mutable: - msg = (_("Forbidden to change property '%s' after activation.") + msg = (_("Forbidden to change field '%s' after activation.") % field_name) raise exception.Forbidden(message=msg) if validate_blob_names and \ @@ -369,12 +378,12 @@ class BaseArtifact(base.VersionedObject): @classmethod def update(cls, context, af, values): - """Update Artifact in Glare repo + """Update artifact in Glare repo. - :param context: user Context - :param af: current definition of Artifact in Glare + :param context: user context + :param af: current definition of artifact :param values: dictionary with changes for artifact - :return: definition of updated Artifact + :return: updated artifact object """ # reset all changes of artifact to reuse them after update af.obj_reset_changes() @@ -396,35 +405,39 @@ class BaseArtifact(base.VersionedObject): "update passed for request %(request)s."), {'artifact': af.id, 'request': context.request_id}) updated_af = cls.db_api.update( - context, af.id, af.obj_changes_to_primitive()) + context, af.id, af._obj_changes_to_primitive()) return cls._init_artifact(context, updated_af) @classmethod - def get_action_for_updates(cls, context, artifact, updates, registry): - """The method defines how to detect appropriate action based on update + def get_action_for_updates(cls, context, af, values, registry): + """Define the appropriate method for artifact update. - Validate request for update and determine if it is request for action. - Also do a validation for request for action if it is an action. + Based on update params this method defines what action engine should + call for artifact update: activate, deactivate, reactivate, publish or + just a regular update of artifact fields. - :return: action reference for updates dict + :param context: user context + :param af: current definition of artifact + :param values: dictionary with changes for artifact + :param registry: registry of enabled artifact types + :return: method reference for updates dict """ - action = cls.update - if 'visibility' in updates: + if 'visibility' in values: # validate publish action format - action = cls.publish - elif 'status' in updates: - status = updates['status'] + return cls.publish + elif 'status' in values: + status = values['status'] if status == cls.STATUS.DEACTIVATED: - action = cls.deactivate + return cls.deactivate elif status == cls.STATUS.ACTIVE: - if artifact.status == artifact.STATUS.DEACTIVATED: - action = cls.reactivate + if af.status == af.STATUS.DEACTIVATED: + return cls.reactivate else: - action = cls.activate + return cls.activate # check updates for links and validate them try: - for key, value in six.iteritems(updates): + for key, value in six.iteritems(values): if cls.is_link(key) and value is not None: cls._validate_link(key, value, context, registry) elif cls.is_link_dict(key) and value: @@ -434,14 +447,11 @@ class BaseArtifact(base.VersionedObject): for l in value: cls._validate_link(key, l, context, registry) except Exception as e: - msg = (_("Bad link in artifact %(af)s: %(msg)s") - % {"af": artifact.id, "msg": str(e)}) + msg = (_("Broken link in artifact %(af)s: %(msg)s") + % {"af": af.id, "msg": str(e)}) raise exception.BadRequest(msg) - LOG.debug("Action %(action)s defined to updates %(updates)s.", - {'action': action.__name__, 'updates': updates}) - - return action + return cls.update @classmethod def _validate_link(cls, key, value, context, registry): @@ -450,36 +460,29 @@ class BaseArtifact(base.VersionedObject): # check containment if glare_fields.LinkFieldType.is_external(value): # validate external link - cls._validate_external_link(value) + with urlrequest.urlopen(value) as data: + data.read(1) else: type_name = (glare_fields.LinkFieldType. get_type_name(value)) af_type = registry.get_artifact_type(type_name) - cls._validate_soft_link(context, value, af_type) - - @classmethod - def _validate_external_link(cls, link): - with urlrequest.urlopen(link) as data: - data.read(1) - - @classmethod - def _validate_soft_link(cls, context, link, af_type): - af_id = link.split('/')[3] - af_type.get(context, af_id) + af_id = value.split('/')[3] + af_type.get(context, af_id) @classmethod def get(cls, context, artifact_id): """Return Artifact from Glare repo :param context: user context - :param artifact_id: id of requested Artifact - :return: Artifact definition + :param artifact_id: id of requested artifact + :return: requested artifact object """ af = cls.db_api.get(context, artifact_id) return cls._init_artifact(context, af) @classmethod def _get_field_type(cls, obj): + """Get string representation of field type for filters.""" if isinstance(obj, fields.IntegerField) or obj is fields.Integer: return 'int' elif isinstance(obj, fields.FloatField) or obj is fields.Float: @@ -491,6 +494,7 @@ class BaseArtifact(base.VersionedObject): @classmethod def _parse_sort_values(cls, sort): + """Prepare sorting parameters for database.""" new_sort = [] for key, direction in sort: if key not in cls.fields: @@ -504,12 +508,6 @@ class BaseArtifact(base.VersionedObject): cls.fields.get(key)))) return new_sort - @classmethod - def _validate_filter_name(cls, filter_name): - if cls.fields.get(filter_name) is None: - msg = _("Unable filter '%s'") % filter_name - raise exception.BadRequest(msg) - @classmethod def _validate_filter_ops(cls, filter_name, op): field = cls.fields.get(filter_name) @@ -550,7 +548,10 @@ class BaseArtifact(base.VersionedObject): msg = _("Field %s is not Dict") % filter_name raise exception.BadRequest(msg) - cls._validate_filter_name(filter_name) + if cls.fields.get(filter_name) is None: + msg = _("Unable filter '%s'") % filter_name + raise exception.BadRequest(msg) + field_type = cls.fields.get(filter_name) if isinstance(field_type, glare_fields.List) or isinstance( @@ -581,16 +582,18 @@ class BaseArtifact(base.VersionedObject): @classmethod def list(cls, context, filters=None, marker=None, limit=None, sort=None, latest=False): - """List all available Artifacts in Glare repo + """Return list of artifacts requested by user. :param context: user context - :param filters: filtering conditions to Artifact list - :param marker: id of Artifact that identifies where Glare should - start listing Artifacts. So all Artifacts before that Artifact in - resulting list must be ignored. It is useful for Artifact pagination. - :param limit: maximum number of Artifact items in list. - :param sort: sorting preferences when requesting Artifact list. - :return: list of Artifacts + :param filters: filters that need to be applied to artifact + :param marker: the artifact that considered as begin of the list + so all artifacts before marker (including marker itself) will not be + added to artifact list + :param limit: maximum number of items in the list + :param sort: sorting options + :param latest: flag that indicates, that only artifacts with highest + versions should be returned in output + :return: list of artifact objects """ if sort is not None: sort = cls._parse_sort_values(sort) @@ -635,14 +638,11 @@ class BaseArtifact(base.VersionedObject): @classmethod def delete(cls, context, af): - """Delete Artifact and all blobs from Glare. + """Delete artifact and all its blobs from Glare. :param context: user context - :param af: definition of artifact targeted to delete + :param af: artifact object targeted for deletion """ - if af.visibility == 'public' and not context.is_admin: - msg = _("Only admins are allowed to delete public artifacts") - raise exception.Forbidden(msg) # marking artifact as deleted cls.db_api.update(context, af.id, {'status': cls.STATUS.DELETED}) @@ -669,17 +669,17 @@ class BaseArtifact(base.VersionedObject): # delete blobs one by one cls._delete_blobs(blobs, context, af) LOG.info(_LI("Blobs successfully deleted for artifact %s"), af.id) - - # delete the artifact itself + # delete artifact itself cls.db_api.delete(context, af.id) @classmethod def activate(cls, context, af, values): - """Activate Artifact and make it available for users + """Activate artifact and make it available for usage. - :param context: User Context - :param af: current Artifact definition in Glare - :return: definition of activated Artifact + :param context: user context + :param af: current artifact object + :param values: dictionary with changes for artifact + :return: artifact object with changed status """ # validate that came to artifact as updates if values != {'status': cls.STATUS.ACTIVE}: @@ -700,16 +700,17 @@ class BaseArtifact(base.VersionedObject): LOG.info(_LI("Parameters validation for artifact %(artifact)s " "activate passed for request %(request)s."), {'artifact': af.id, 'request': context.request_id}) - active_af = cls.db_api.update(context, af.id, values) - return cls._init_artifact(context, active_af) + af = cls.db_api.update(context, af.id, {'status': cls.STATUS.ACTIVE}) + return cls._init_artifact(context, af) @classmethod def reactivate(cls, context, af, values): - """Make Artifact active after de-activation + """Make Artifact active after deactivation :param context: user context - :param af: definition of de-activated Artifact - :return: definition of active Artifact + :param af: current artifact object + :param values: dictionary with changes for artifact + :return: artifact object with changed status """ # validate that came to artifact as updates if values != {'status': cls.STATUS.ACTIVE}: @@ -723,21 +724,23 @@ class BaseArtifact(base.VersionedObject): LOG.info(_LI("Parameters validation for artifact %(artifact)s " "reactivate passed for request %(request)s."), {'artifact': af.id, 'request': context.request_id}) - af = cls.db_api.update(context, af.id, values) + af = cls.db_api.update(context, af.id, {'status': cls.STATUS.ACTIVE}) return cls._init_artifact(context, af) @classmethod def deactivate(cls, context, af, values): - """Deny Artifact downloading due to security concerns + """Deny Artifact downloading due to security concerns. - If user uploaded suspicious Artifact then Cloud Admins(or other users - - it depends on policy configurations) can deny Artifact download by - users by making Artifact de-activated. After additional investigation - Artifact can be re-activated or deleted from Glare. + If user uploaded suspicious artifact then administrators(or other + users - it depends on policy configurations) can deny artifact data + to be downloaded by regular users by making artifact deactivated. + After additional investigation artifact can be reactivated or + deleted from Glare. :param context: user context - :param af: Artifact definition in Glare - :return: definition of de-activated Artifact + :param af: current artifact object + :param values: dictionary with changes for artifact + :return: artifact object with changed status """ if values != {'status': cls.STATUS.DEACTIVATED}: msg = _("Only {'status': %s} is allowed in a request " @@ -751,16 +754,18 @@ class BaseArtifact(base.VersionedObject): LOG.info(_LI("Parameters validation for artifact %(artifact)s " "deactivate passed for request %(request)s."), {'artifact': af.id, 'request': context.request_id}) - af = cls.db_api.update(context, af.id, values) + af = cls.db_api.update(context, af.id, + {'status': cls.STATUS.DEACTIVATED}) return cls._init_artifact(context, af) @classmethod def publish(cls, context, af, values): - """Make Artifact available for everyone + """Make artifact available for all tenants. :param context: user context - :param af: definition of published Artifact - :return: definition of active Artifact + :param af: current artifact object + :param values: dictionary with changes for artifact + :return: artifact object with changed visibility """ if values != {'visibility': 'public'}: msg = _("Only {'visibility': 'public'} is allowed in a request " @@ -778,17 +783,27 @@ class BaseArtifact(base.VersionedObject): LOG.info(_LI("Parameters validation for artifact %(artifact)s " "publish passed for request %(request)s."), {'artifact': af.id, 'request': context.request_id}) - af = cls.db_api.update(context, af.id, values) + af = cls.db_api.update(context, af.id, {'visibility': 'public'}) return cls._init_artifact(context, af) @classmethod def get_max_blob_size(cls, field_name): + """Get the maximum allowed blob size in bytes. + + :param field_name: blob or blob dict field name + :return: maximum blob size in bytes + """ return getattr(cls.fields[field_name], 'max_blob_size', attribute.BlobAttribute.DEFAULT_MAX_BLOB_SIZE) @classmethod - def validate_upload_allowed(cls, context, af, field_name, blob_key=None): - """Validate if given blob is ready for uploading.""" + def validate_upload_allowed(cls, af, field_name, blob_key=None): + """Validate if given blob is ready for uploading. + + :param af: current artifact object + :param field_name: blob or blob dict field name + :param blob_key: indicates key name if field_name is a blob dict + """ blob_name = "%s[%s]" % (field_name, blob_key)\ if blob_key else field_name @@ -818,35 +833,40 @@ class BaseArtifact(base.VersionedObject): {'artifact': af.id, 'blob_name': blob_name}) @classmethod - def update_blob(cls, context, af_id, values): - """Upload binary object as artifact property + def update_blob(cls, context, af_id, field_name, values): + """Update blob info in database. :param context: user context :param af_id: id of modified artifact + :param field_name: blob or blob dict field name :param values: updated blob values - :return updated Artifact definition in Glare + :return updated artifact definition in Glare """ - af_upd = cls.db_api.update_blob(context, af_id, values) + af_upd = cls.db_api.update_blob(context, af_id, {field_name: values}) return cls._init_artifact(context, af_upd) @classmethod def validate_activate(cls, context, af, values=None): + """Validation hook for activation.""" pass @classmethod def validate_upload(cls, context, af, field_name, fd): + """Validation hook for uploading.""" return fd, None @classmethod def validate_publish(cls, context, af): + """Validation hook for publishing.""" pass @classmethod def get_default_store(cls, context, af, field_name, blob_key): + """Return a default store type for artifact type.""" pass def to_notification(self): - """Return notification body that can be send to listeners + """Return notification body that can be send to listeners. :return: dict with notification information """ @@ -865,13 +885,13 @@ class BaseArtifact(base.VersionedObject): } def to_dict(self): - """Convert oslo versioned object to dictionary + """Convert oslo versioned object to dictionary. :return: dict with field names and field values """ return self.obj_to_primitive()['versioned_object.data'] - def obj_changes_to_primitive(self): + def _obj_changes_to_primitive(self): changes = self.obj_get_changes() res = {} for key, val in six.iteritems(changes): @@ -882,7 +902,7 @@ class BaseArtifact(base.VersionedObject): return res @classmethod - def schema_attr(cls, attr, attr_name=''): + def _schema_attr(cls, attr, attr_name=''): attr_type = utils.get_schema_type(attr) schema = {} @@ -968,10 +988,11 @@ class BaseArtifact(base.VersionedObject): @classmethod def gen_schemas(cls): + """Return json schema representation of the artifact type.""" schemas_prop = {} for attr_name, attr in six.iteritems(cls.fields): - schemas_prop[attr_name] = cls.schema_attr(attr, - attr_name=attr_name) + schemas_prop[attr_name] = cls._schema_attr( + attr, attr_name=attr_name) schemas = {'properties': schemas_prop, 'name': cls.get_type_name(), 'version': cls.VERSION, diff --git a/glare/objects/meta/attribute.py b/glare/objects/meta/attribute.py index a769797..9d9efe1 100644 --- a/glare/objects/meta/attribute.py +++ b/glare/objects/meta/attribute.py @@ -29,7 +29,7 @@ class Attribute(object): def __init__(self, field_class, mutable=False, required_on_activate=True, system=False, validators=None, nullable=True, default=None, sortable=False, filter_ops=None, description=""): - """Init and validate attribute""" + """Init and validate attribute.""" if not issubclass(field_class, fields.AutoTypedField): raise exc.IncorrectArtifactType( "Field class %s must be sub-class of AutoTypedField." % @@ -115,7 +115,7 @@ class Attribute(object): @classmethod def init(cls, *args, **kwargs): - """Fabric to build attributes""" + """Fabric to build attributes.""" return cls(*args, **kwargs).get_field() diff --git a/glare/objects/meta/registry.py b/glare/objects/meta/registry.py index faed47e..d7a44a0 100644 --- a/glare/objects/meta/registry.py +++ b/glare/objects/meta/registry.py @@ -49,7 +49,7 @@ CONF.register_opts(registry_options) def import_submodules(module): - """Import all submodules of a module + """Import all submodules of a module. :param module: Package name :return list of imported modules @@ -92,7 +92,7 @@ class ArtifactRegistry(vo_base.VersionedObjectRegistry): @classmethod def register_all_artifacts(cls): - """Register all artifacts in glare""" + """Register all artifacts in Glare.""" # get all submodules in glare.objects # please note that we registering trusted modules first # and applying custom modules after that to allow custom modules @@ -114,7 +114,7 @@ class ArtifactRegistry(vo_base.VersionedObjectRegistry): @classmethod def get_artifact_type(cls, type_name): - """Return artifact type based on artifact type name + """Return artifact type based on artifact type name. :param type_name: name of artifact type :return: artifact class @@ -126,5 +126,5 @@ class ArtifactRegistry(vo_base.VersionedObjectRegistry): @classmethod def reset_registry(cls): - """Resets all registered artifact type classes""" + """Resets all registered artifact type classes.""" cls._registry._obj_classes = collections.defaultdict(list) diff --git a/glare/objects/meta/validators.py b/glare/objects/meta/validators.py index 7699c34..8278168 100644 --- a/glare/objects/meta/validators.py +++ b/glare/objects/meta/validators.py @@ -26,7 +26,7 @@ LOG = logging.getLogger(__name__) class Validator(object): - """Common interface for all validators""" + """Common interface for all validators.""" def validate(self, value): raise NotImplementedError() diff --git a/glare/wsgi.py b/glare/wsgi.py index 4dce78e..61a005e 100644 --- a/glare/wsgi.py +++ b/glare/wsgi.py @@ -12,7 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. -"""Glare WSGI module +"""Glare WSGI module. Use this module to deploy glare as WSGI application.