Update docstrings

Change-Id: I2be4437a5afb221f56f4d93a24be0356e7abc033
This commit is contained in:
Mike Fedosin
2017-02-14 23:43:28 +03:00
parent 19a1260a94
commit 3b45faece9
22 changed files with 357 additions and 293 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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()

View File

@@ -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')

View File

@@ -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

View File

@@ -16,7 +16,7 @@
"""
Glare (Glare Artifact Repository) API service
Glare (Glare Artifact Repository) API service.
"""
import os

View File

@@ -14,7 +14,7 @@
# under the License.
"""
Routines for configuring Glare
Routines for configuring Glare.
"""
import logging.config

View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -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):

View File

@@ -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
"""

View File

@@ -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"),

View File

@@ -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

View File

@@ -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

View File

@@ -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 <host_name>/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,

View File

@@ -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()

View File

@@ -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)

View File

@@ -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()

View File

@@ -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.