Add "fields" filter support on single objects

Change-Id: Iba0401445921f9ea7932628c05590472307ba83a
Closes-Bug: #1718214
Signed-off-by: Xavier Hardy <xavier.hardy@corp.ovh.com>
Co-Authored-By: Oleg Ovcharuk <vgvoleg@gmail.com>
Signed-off-by: Oleg Ovcharuk <vgvoleg@gmail.com>
This commit is contained in:
Xavier Hardy 2017-09-19 16:28:02 +02:00 committed by Vasudeo Nimbekar
parent c820d6f365
commit 9c5324ce9a
25 changed files with 362 additions and 122 deletions

View File

@ -51,8 +51,13 @@ class Resource(wtypes.Base):
return cls.from_tuples(d.items())
@classmethod
def from_db_model(cls, db_model):
return cls.from_tuples(db_model.iter_columns())
def from_db_model(cls, db_model, fields=()):
if isinstance(db_model, tuple):
db_tuples = zip(fields, db_model)
else:
db_tuples = db_model.iter_columns(fields=fields)
return cls.from_tuples(db_tuples)
def __str__(self):
"""WSME based implementation of __str__."""

View File

@ -79,6 +79,9 @@ class ActionsController(rest.RestController, hooks.HookController):
:param identifier: ID or name of the Action to get.
:param namespace: The namespace of the action.
:param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in
fields if it's not provided.
"""
acl.enforce('actions:get', context.ctx())
@ -251,7 +254,7 @@ class ActionsController(rest.RestController, hooks.HookController):
Default: asc.
:param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in
fields if it's provided, since it will be used when
fields if it's not provided, since it will be used when
constructing 'next' link.
:param name: Optional. Keep only resources with a specific name.
:param scope: Optional. Keep only resources with a specific scope.

View File

@ -52,25 +52,40 @@ def _load_deferred_output_field(action_ex):
# Use retries to prevent possible failures.
@rest_utils.rest_retry_on_db_error
def _get_action_execution(id):
def _get_action_execution(id, fields=()):
if fields and 'id' not in fields:
fields.insert(0, 'id')
with db_api.transaction():
return _get_action_execution_resource(db_api.get_action_execution(id))
return _get_action_execution_resource(
db_api.get_action_execution(id),
fields=fields
)
def _get_action_execution_resource(action_ex):
def _get_action_execution_resource(action_ex, fields=()):
_load_deferred_output_field(action_ex)
return _get_action_execution_resource_for_list(action_ex)
if fields and 'id' not in fields:
fields.insert(0, 'id')
return _get_action_execution_resource_for_list(action_ex, fields=fields)
def _get_action_execution_resource_for_list(action_ex):
def _get_action_execution_resource_for_list(action_ex, fields=()):
# TODO(nmakhotkin): Get rid of using dicts for constructing resources.
# TODO(nmakhotkin): Use db_model for this instead.
res = resources.ActionExecution.from_db_model(action_ex)
task_name = (action_ex.task_execution.name
if action_ex.task_execution else None)
setattr(res, 'task_name', task_name)
# field_task_name_needed = 'task_name' in fields
# if field_task_name_needed:
# fields.remove('task_name')
res = resources.ActionExecution.from_db_model(action_ex, fields=fields)
# if not fields or field_task_name_needed:
# task_name = (action_ex.task_execution.name
# if action_ex.task_execution else None)
# setattr(res, 'task_name', task_name)
return res
@ -95,7 +110,7 @@ def _get_action_executions(task_execution_id=None, marker=None, limit=None,
or less than that of sort_keys.
:param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in
fields if it's provided, since it will be used when
fields if it's not provided, since it will be used when
constructing 'next' link.
:param filters: Optional. A list of filters to apply to the result.
"""
@ -124,17 +139,21 @@ def _get_action_executions(task_execution_id=None, marker=None, limit=None,
class ActionExecutionsController(rest.RestController):
@rest_utils.wrap_wsme_controller_exception
@wsme_pecan.wsexpose(resources.ActionExecution, wtypes.text)
def get(self, id):
@wsme_pecan.wsexpose(resources.ActionExecution, wtypes.text,
types.uniquelist)
def get(self, id, fields=None):
"""Return the specified action_execution.
:param id: UUID of action execution to retrieve
:param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in
fields if it's not provided.
"""
acl.enforce('action_executions:get', context.ctx())
LOG.debug("Fetch action_execution [id=%s]", id)
return _get_action_execution(id)
return _get_action_execution(id, fields=fields)
@rest_utils.wrap_wsme_controller_exception
@wsme_pecan.wsexpose(resources.ActionExecution,
@ -248,7 +267,7 @@ class ActionExecutionsController(rest.RestController):
or less than that of sort_keys.
:param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in
fields if it's provided, since it will be used when
fields if it's not provided, since it will be used when
constructing 'next' link.
:param name: Optional. Keep only resources with a specific name.
:param workflow_name: Optional. Keep only resources with a specific
@ -379,7 +398,7 @@ class TasksActionExecutionController(rest.RestController):
or less than that of sort_keys.
:param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in
fields if it's provided, since it will be used when
fields if it's not provided, since it will be used when
constructing 'next' link.
:param name: Optional. Keep only resources with a specific name.
:param workflow_name: Optional. Keep only resources with a specific
@ -444,15 +463,19 @@ class TasksActionExecutionController(rest.RestController):
)
@rest_utils.wrap_wsme_controller_exception
@wsme_pecan.wsexpose(resources.ActionExecution, wtypes.text, wtypes.text)
def get(self, task_execution_id, action_ex_id):
@wsme_pecan.wsexpose(resources.ActionExecution, wtypes.text, wtypes.text,
types.uniquelist)
def get(self, task_execution_id, action_ex_id, fields=()):
"""Return the specified action_execution.
:param task_execution_id: Task execution UUID
:param action_ex_id: Action execution UUID
:param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in
fields if it's not provided.
"""
acl.enforce('action_executions:get', context.ctx())
LOG.debug("Fetch action_execution [id=%s]", action_ex_id)
return _get_action_execution(action_ex_id)
return _get_action_execution(action_ex_id, fields=fields)

View File

@ -187,8 +187,9 @@ class CodeSourcesController(rest.RestController, hooks.HookController):
)
@rest_utils.wrap_wsme_controller_exception
@wsme_pecan.wsexpose(resources.CodeSource, wtypes.text, wtypes.text)
def get(self, identifier, namespace=''):
@wsme_pecan.wsexpose(resources.CodeSource, wtypes.text,
wtypes.text, types.uniquelist)
def get(self, identifier, namespace='', fields=''):
"""Return a code source.
:param identifier: Name or UUID of the code source to retrieve.
@ -202,13 +203,17 @@ class CodeSourcesController(rest.RestController, hooks.HookController):
identifier,
namespace
)
if fields and 'id' not in fields:
fields.insert(0, 'id')
db_model = rest_utils.rest_retry_on_db_error(
db_api.get_code_source)(
identifier=identifier,
namespace=namespace
namespace=namespace,
fields=fields
)
if fields:
return resources.CodeSource.from_tuples(zip(fields, db_model))
return resources.CodeSource.from_db_model(db_model)
@rest_utils.wrap_pecan_controller_exception

View File

@ -31,22 +31,30 @@ LOG = logging.getLogger(__name__)
class CronTriggersController(rest.RestController):
@rest_utils.wrap_wsme_controller_exception
@wsme_pecan.wsexpose(resources.CronTrigger, wtypes.text)
def get(self, identifier):
@wsme_pecan.wsexpose(resources.CronTrigger,
wtypes.text, types.uniquelist)
def get(self, identifier, fields=''):
"""Returns the named cron_trigger.
:param identifier: Id or name of cron trigger to retrieve
:param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in
fields if it's not provided.
"""
acl.enforce('cron_triggers:get', context.ctx())
LOG.debug('Fetch cron trigger [identifier=%s]', identifier)
if fields and 'id' not in fields:
fields.insert(0, 'id')
# Use retries to prevent possible failures.
db_model = rest_utils.rest_retry_on_db_error(
db_api.get_cron_trigger
)(identifier)
return resources.CronTrigger.from_db_model(db_model)
)(identifier, fields=fields)
if fields:
return resources.CronTrigger.from_tuples(zip(fields, db_model))
return resources.CronTrigger.from_db_model(db_model, fields=fields)
@rest_utils.wrap_wsme_controller_exception
@wsme_pecan.wsexpose(
@ -124,7 +132,7 @@ class CronTriggersController(rest.RestController):
or less than that of sort_keys.
:param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in
fields if it's provided, since it will be used when
fields if it's not provided, since it will be used when
constructing 'next' link.
:param name: Optional. Keep only resources with a specific name.
:param workflow_name: Optional. Keep only resources with a specific

View File

@ -208,8 +208,9 @@ class DynamicActionsController(rest.RestController, hooks.HookController):
)
@rest_utils.wrap_wsme_controller_exception
@wsme_pecan.wsexpose(resources.DynamicAction, wtypes.text, wtypes.text)
def get(self, identifier, namespace=''):
@wsme_pecan.wsexpose(resources.DynamicAction, wtypes.text,
wtypes.text, types.uniquelist)
def get(self, identifier, namespace='', fields=''):
"""Return the named action.
:param identifier: Name or UUID of the action to retrieve.
@ -222,14 +223,19 @@ class DynamicActionsController(rest.RestController, hooks.HookController):
identifier,
namespace
)
if fields and 'id' not in fields:
fields.insert(0, 'id')
db_model = rest_utils.rest_retry_on_db_error(
db_api.get_dynamic_action_definition
)(
identifier=identifier,
namespace=namespace
namespace=namespace,
fields=fields
)
if fields:
return resources.DynamicAction.from_tuples(zip(fields, db_model))
return resources.DynamicAction.from_db_model(db_model)
@rest_utils.wrap_pecan_controller_exception

View File

@ -59,7 +59,7 @@ class EnvironmentController(rest.RestController):
or less than that of sort_keys.
:param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in
fields if it's provided, since it will be used when
fields if it's not provided, since it will be used when
constructing 'next' link.
:param name: Optional. Keep only resources with a specific name.
:param description: Optional. Keep only resources with a specific
@ -101,21 +101,28 @@ class EnvironmentController(rest.RestController):
)
@rest_utils.wrap_wsme_controller_exception
@wsme_pecan.wsexpose(resources.Environment, wtypes.text)
def get(self, name):
@wsme_pecan.wsexpose(resources.Environment, wtypes.text, types.uniquelist)
def get(self, name, fields=''):
"""Return the named environment.
:param name: Name of environment to retrieve
:param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in
fields if it's not provided.
"""
acl.enforce('environments:get', context.ctx())
LOG.debug("Fetch environment [name=%s]", name)
if fields and 'id' not in fields:
fields.insert(0, 'id')
# Use retries to prevent possible failures.
r = rest_utils.create_db_retry_object()
db_model = r.call(db_api.get_environment, name)
return resources.Environment.from_db_model(db_model)
db_model = r.call(db_api.get_environment, name, fields=fields)
if fields:
return resources.Environment.from_tuples(zip(fields, db_model))
return resources.Environment.from_db_model(db_model, fields=fields)
@rest_utils.wrap_wsme_controller_exception
@wsme_pecan.wsexpose(

View File

@ -34,18 +34,22 @@ CREATE_MANDATORY = set(['exchange', 'topic', 'event', 'workflow_id'])
class EventTriggersController(rest.RestController):
@rest_utils.wrap_wsme_controller_exception
@wsme_pecan.wsexpose(resources.EventTrigger, types.uuid)
def get(self, id):
@wsme_pecan.wsexpose(resources.EventTrigger, types.uuid, types.uniquelist)
def get(self, id, fields=''):
"""Returns the specified event_trigger."""
acl.enforce('event_triggers:get', auth_ctx.ctx())
LOG.debug('Fetch event trigger [id=%s]', id)
if fields and 'id' not in fields:
fields.insert(0, 'id')
# Use retries to prevent possible failures.
r = rest_utils.create_db_retry_object()
db_model = r.call(db_api.get_event_trigger, id)
return resources.EventTrigger.from_db_model(db_model)
db_model = r.call(db_api.get_event_trigger, id, fields=fields)
if fields:
return resources.EventTrigger.from_tuples(zip(fields, db_model))
return resources.EventTrigger.from_db_model(db_model, fields=fields)
@rest_utils.wrap_wsme_controller_exception
@wsme_pecan.wsexpose(resources.EventTrigger, body=resources.EventTrigger,

View File

@ -68,12 +68,20 @@ def _get_workflow_execution_resource(wf_ex):
# Use retries to prevent possible failures.
@rest_utils.rest_retry_on_db_error
def _get_workflow_execution(id, must_exist=True):
def _get_workflow_execution(id, must_exist=True, fields=None):
if fields and 'id' not in fields:
fields.insert(0, 'id')
fields_tuple = rest_utils.fields_list_to_cls_fields_tuple(
db_models.WorkflowExecution,
fields
)
with db_api.transaction():
if must_exist:
wf_ex = db_api.get_workflow_execution(id)
wf_ex = db_api.get_workflow_execution(id, fields=fields_tuple)
else:
wf_ex = db_api.load_workflow_execution(id)
wf_ex = db_api.load_workflow_execution(id, fields=fields_tuple)
return rest_utils.load_deferred_fields(
wf_ex,
@ -90,19 +98,25 @@ class ExecutionsController(rest.RestController):
executions = sub_execution.SubExecutionsController()
@rest_utils.wrap_wsme_controller_exception
@wsme_pecan.wsexpose(resources.Execution, wtypes.text)
def get(self, id):
@wsme_pecan.wsexpose(resources.Execution, wtypes.text, types.uniquelist)
def get(self, id, fields=None):
"""Return the specified Execution.
:param id: UUID of execution to retrieve.
:param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in
fields if it's not provided.
"""
acl.enforce("executions:get", context.ctx())
LOG.debug("Fetch execution [id=%s]", id)
wf_ex = _get_workflow_execution(id)
wf_ex = _get_workflow_execution(id, fields=fields)
resource = resources.Execution.from_db_model(wf_ex)
if fields:
return resources.Execution.from_tuples(zip(fields, wf_ex))
resource = resources.Execution.from_db_model(wf_ex, fields=fields)
resource.published_global = (
data_flow.get_workflow_execution_published_global(wf_ex)
@ -363,7 +377,7 @@ class ExecutionsController(rest.RestController):
or less than that of sort_keys.
:param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in
fields if it's provided, since it will be used when
fields if it's not provided, since it will be used when
constructing 'next' link.
:param workflow_name: Optional. Keep only resources with a specific
workflow name.

View File

@ -159,8 +159,8 @@ class Workflow(resource.Resource, ScopedResource):
return obj
@classmethod
def from_db_model(cls, db_model):
obj = super(Workflow, cls).from_db_model(db_model)
def from_db_model(cls, db_model, fields=()):
obj = super(Workflow, cls).from_db_model(db_model, fields=fields)
obj.set_attributes_from_spec(db_model.get('spec'))

View File

@ -49,19 +49,22 @@ STATE_TYPES = wtypes.Enum(
)
def _get_task_resource_with_result(task_ex):
task = resources.Task.from_db_model(task_ex)
task.result = json.dumps(data_flow.get_task_execution_result(task_ex))
def _get_task_resource_with_result(task_ex, fields=()):
task = resources.Task.from_db_model(task_ex, fields=fields)
if 'result' in fields or not fields:
task.result = json.dumps(data_flow.get_task_execution_result(task_ex))
return task
# Use retries to prevent possible failures.
@rest_utils.rest_retry_on_db_error
def _get_task_execution(id):
def _get_task_execution(id, fields=()):
if fields and 'id' not in fields:
fields.insert(0, 'id')
with db_api.transaction():
task_ex = db_api.get_task_execution(id)
task_ex = db_api.get_task_execution(id, fields=fields)
rest_utils.load_deferred_fields(task_ex, ['workflow_execution'])
rest_utils.load_deferred_fields(
@ -74,7 +77,7 @@ def _get_task_execution(id):
['params']
)
return _get_task_resource_with_result(task_ex), task_ex
return _get_task_resource_with_result(task_ex, fields), task_ex
def get_published_global(task_ex, wf_ex=None):
@ -141,7 +144,7 @@ class TaskExecutionsController(rest.RestController):
or less than that of sort_keys.
:param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in
fields if it's provided, since it will be used when
fields if it's not provided, since it will be used when
constructing 'next' link.
:param workflow_name: Optional. Keep only resources with a specific
workflow name.
@ -204,18 +207,26 @@ class TasksController(rest.RestController):
executions = sub_execution.SubExecutionsController()
@rest_utils.wrap_wsme_controller_exception
@wsme_pecan.wsexpose(resources.Task, wtypes.text)
def get(self, id):
@wsme_pecan.wsexpose(resources.Task, wtypes.text, types.uniquelist)
def get(self, id, fields=''):
"""Return the specified task.
:param id: UUID of task to retrieve
:param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in
fields if it's not provided.
"""
acl.enforce('tasks:get', context.ctx())
LOG.debug("Fetch task [id=%s]", id)
task, task_ex = _get_task_execution(id)
return _task_with_published_global(task, task_ex)
task, task_ex = _get_task_execution(id, ())
task = _task_with_published_global(task, task_ex)
if fields:
if 'id' not in fields:
fields.insert(0, 'id')
task_dict = {field: task.to_dict()[field] for field in fields}
task = resources.Task.from_dict(task_dict)
return task
@rest_utils.wrap_wsme_controller_exception
@wsme_pecan.wsexpose(resources.Tasks, types.uuid, int, types.uniquelist,
@ -249,7 +260,7 @@ class TasksController(rest.RestController):
or less than that of sort_keys.
:param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in
fields if it's provided, since it will be used when
fields if it's not provided, since it will be used when
constructing 'next' link.
:param name: Optional. Keep only resources with a specific name.
:param workflow_name: Optional. Keep only resources with a specific
@ -412,7 +423,7 @@ class ExecutionTasksController(rest.RestController):
or less than that of sort_keys.
:param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in
fields if it's provided, since it will be used when
fields if it's not provided, since it will be used when
constructing 'next' link.
:param name: Optional. Keep only resources with a specific name.
:param workflow_name: Optional. Keep only resources with a specific

View File

@ -44,27 +44,36 @@ class WorkbooksController(rest.RestController, hooks.HookController):
)
@rest_utils.wrap_wsme_controller_exception
@wsme_pecan.wsexpose(resources.Workbook, wtypes.text, wtypes.text)
def get(self, name, namespace=''):
@wsme_pecan.wsexpose(resources.Workbook, wtypes.text, wtypes.text,
types.uniquelist)
def get(self, name, namespace='', fields=''):
"""Return the named workbook.
:param name: Name of workbook to retrieve.
:param namespace: Optional. Namespace of workbook to retrieve.
:param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in
fields if it's not provided.
"""
acl.enforce('workbooks:get', context.ctx())
LOG.debug("Fetch workbook [name=%s, namespace=%s]", name, namespace)
if fields and 'id' not in fields:
fields.insert(0, 'id')
# Use retries to prevent possible failures.
r = rest_utils.create_db_retry_object()
db_model = r.call(
db_api.get_workbook,
name,
namespace=namespace
namespace=namespace,
fields=fields
)
return resources.Workbook.from_db_model(db_model)
if fields:
return resources.Workbook.from_tuples(zip(fields, db_model))
return resources.Workbook.from_db_model(db_model, fields=fields)
@rest_utils.wrap_pecan_controller_exception
@pecan.expose(content_type="text/plain")
@ -171,7 +180,7 @@ class WorkbooksController(rest.RestController, hooks.HookController):
Default: asc.
:param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in
fields if it's provided, since it will be used when
fields if it's not provided, since it will be used when
constructing 'next' link.
:param name: Optional. Keep only resources with a specific name.
:param definition: Optional. Keep only resources with a specific

View File

@ -77,26 +77,36 @@ class WorkflowsController(rest.RestController, hooks.HookController):
)
@rest_utils.wrap_wsme_controller_exception
@wsme_pecan.wsexpose(resources.Workflow, wtypes.text, wtypes.text)
def get(self, identifier, namespace=''):
@wsme_pecan.wsexpose(resources.Workflow, wtypes.text, wtypes.text,
types.uniquelist)
def get(self, identifier, namespace='', fields=''):
"""Return the named workflow.
:param identifier: Name or UUID of the workflow to retrieve.
:param namespace: Optional. Namespace of the workflow to retrieve.
:param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in
fields if it's not provided.
"""
acl.enforce('workflows:get', context.ctx())
LOG.debug("Fetch workflow [identifier=%s]", identifier)
if fields and 'id' not in fields:
fields.insert(0, 'id')
# Use retries to prevent possible failures.
r = rest_utils.create_db_retry_object()
db_model = r.call(
db_api.get_workflow_definition,
identifier,
namespace=namespace
namespace=namespace,
fields=fields,
)
return resources.Workflow.from_db_model(db_model)
if fields:
return resources.Workflow.from_tuples(zip(fields, db_model))
return resources.Workflow.from_db_model(db_model, fields=fields)
@rest_utils.wrap_pecan_controller_exception
@pecan.expose(content_type="text/plain")
@ -240,7 +250,7 @@ class WorkflowsController(rest.RestController, hooks.HookController):
Default: asc.
:param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in
fields if it's provided, since it will be used when
fields if it's not provided, since it will be used when
constructing 'next' link.
:param name: Optional. Keep only resources with a specific name.
:param namespace: Optional. Keep only resources with a specific

View File

@ -87,15 +87,18 @@ class _MistralModelBase(oslo_models.ModelBase, oslo_models.TimestampMixin):
if col.name not in unloaded and hasattr(self, col.name):
yield col.name
def iter_columns(self):
def iter_columns(self, fields=()):
"""Returns an iterator for loaded columns.
:param fields: names of fields to return
:type fields: tuple, list or set
:return: A generator function that generates
tuples (column name, column value).
"""
for col_name in self.iter_column_names():
yield col_name, getattr(self, col_name)
if not fields or col_name in fields:
yield col_name, getattr(self, col_name)
def get_clone(self):
"""Clones current object, loads all fields and returns the result."""

View File

@ -524,17 +524,17 @@ def get_scheduled_jobs_count(**kwargs):
# Cron triggers.
def get_cron_trigger(identifier):
return IMPL.get_cron_trigger(identifier)
def get_cron_trigger(identifier, fields=()):
return IMPL.get_cron_trigger(identifier, fields=fields)
def get_cron_trigger_by_id(id):
return IMPL.get_cron_trigger_by_id(id)
def get_cron_trigger_by_id(id, fields=()):
return IMPL.get_cron_trigger_by_id(id, fields=fields)
def load_cron_trigger(identifier):
def load_cron_trigger(identifier, fields=()):
"""Unlike get_cron_trigger this method is allowed to return None."""
return IMPL.load_cron_trigger(identifier)
return IMPL.load_cron_trigger(identifier, fields=fields)
def get_cron_triggers(**kwargs):
@ -593,13 +593,13 @@ def delete_cron_triggers(**kwargs):
# Environments.
def get_environment(name):
return IMPL.get_environment(name)
def get_environment(name, fields=()):
return IMPL.get_environment(name, fields=fields)
def load_environment(name):
def load_environment(name, fields=()):
"""Unlike get_environment this method is allowed to return None."""
return IMPL.load_environment(name)
return IMPL.load_environment(name, fields=fields)
def get_environments(limit=None, marker=None, sort_keys=None,
@ -667,12 +667,12 @@ def delete_resource_members(**kwargs):
# Event triggers.
def get_event_trigger(id, insecure=False):
return IMPL.get_event_trigger(id, insecure)
def get_event_trigger(id, insecure=False, fields=()):
return IMPL.get_event_trigger(id, insecure, fields=fields)
def load_event_trigger(id, insecure=False):
return IMPL.load_event_trigger(id, insecure)
def load_event_trigger(id, insecure=False, fields=()):
return IMPL.load_event_trigger(id, insecure, fields=fields)
def get_event_triggers(insecure=False, limit=None, marker=None, sort_keys=None,

View File

@ -307,6 +307,11 @@ def _get_db_object_by_name(model, name, columns=()):
def _get_db_object_by_id(model, id, insecure=False, columns=()):
columns = (
tuple([getattr(model, f) for f in columns if hasattr(model, f)])
if columns and isinstance(columns, list) else columns
)
query = (
b.model_query(model, columns=columns)
if insecure
@ -319,6 +324,10 @@ def _get_db_object_by_id(model, id, insecure=False, columns=()):
def _get_db_object_by_name_and_namespace_or_id(model, identifier,
namespace=None, insecure=False,
columns=()):
columns = (
tuple([getattr(model, f) for f in columns if hasattr(model, f)])
if columns and isinstance(columns, list) else columns
)
query = (
b.model_query(model, columns=columns)
if insecure
@ -1599,13 +1608,14 @@ def _get_completed_root_executions_query(columns):
@b.session_aware()
def get_cron_trigger(identifier, session=None):
def get_cron_trigger(identifier, session=None, fields=()):
ctx = context.ctx()
cron_trigger = _get_db_object_by_name_and_namespace_or_id(
models.CronTrigger,
identifier,
insecure=ctx.is_admin
insecure=ctx.is_admin,
columns=fields,
)
if not cron_trigger:
@ -1617,10 +1627,11 @@ def get_cron_trigger(identifier, session=None):
@b.session_aware()
def get_cron_trigger_by_id(id, session=None):
def get_cron_trigger_by_id(id, session=None, fields=()):
ctx = context.ctx()
cron_trigger = _get_db_object_by_id(models.CronTrigger, id,
insecure=ctx.is_admin)
insecure=ctx.is_admin,
columns=fields)
if not cron_trigger:
raise exc.DBEntityNotFoundError(
"Cron trigger not found [id=%s]" % id
@ -1630,10 +1641,11 @@ def get_cron_trigger_by_id(id, session=None):
@b.session_aware()
def load_cron_trigger(identifier, session=None):
def load_cron_trigger(identifier, session=None, fields=()):
return _get_db_object_by_name_and_namespace_or_id(
models.CronTrigger,
identifier
identifier,
columns=fields,
)
@ -1744,8 +1756,8 @@ def delete_cron_triggers(session=None, **kwargs):
# Environments.
@b.session_aware()
def get_environment(name, session=None):
env = _get_db_object_by_name(models.Environment, name)
def get_environment(name, session=None, fields=()):
env = _get_db_object_by_name(models.Environment, name, columns=fields)
if not env:
raise exc.DBEntityNotFoundError(
@ -1756,8 +1768,8 @@ def get_environment(name, session=None):
@b.session_aware()
def load_environment(name, session=None):
return _get_db_object_by_name(models.Environment, name)
def load_environment(name, session=None, fields=()):
return _get_db_object_by_name(models.Environment, name, columns=fields)
@b.session_aware()
@ -1982,8 +1994,9 @@ def _get_accepted_resources(res_type):
# Event triggers.
@b.session_aware()
def get_event_trigger(id, insecure=False, session=None):
event_trigger = _get_db_object_by_id(models.EventTrigger, id, insecure)
def get_event_trigger(id, insecure=False, session=None, fields=()):
event_trigger = _get_db_object_by_id(models.EventTrigger, id, insecure,
columns=fields)
if not event_trigger:
raise exc.DBEntityNotFoundError(
@ -1994,8 +2007,9 @@ def get_event_trigger(id, insecure=False, session=None):
@b.session_aware()
def load_event_trigger(id, insecure=False, session=None):
return _get_db_object_by_id(models.EventTrigger, id, insecure)
def load_event_trigger(id, insecure=False, session=None, fields=()):
return _get_db_object_by_id(models.EventTrigger, id, insecure,
columns=fields)
@b.session_aware()

View File

@ -226,9 +226,22 @@ class TestActionExecutionsController(base.APITest):
@mock.patch.object(db_api, 'get_action_execution', MOCK_ACTION)
def test_get(self):
resp = self.app.get('/v2/action_executions/123')
action_exec = copy.deepcopy(ACTION_EX)
del action_exec['task_name']
self.assertEqual(200, resp.status_int)
self.assertDictEqual(action_exec, resp.json)
@mock.patch('mistral.db.v2.api.get_action_execution')
def test_get_with_fields_filter(self, mocked_get):
mocked_get.return_value = (ACTION_EX['id'], ACTION_EX['name'],)
resp = self.app.get('/v2/action_executions/123?fields=name')
expected = {
'id': ACTION_EX['id'],
'name': ACTION_EX['name'],
}
self.assertEqual(200, resp.status_int)
self.assertDictEqual(ACTION_EX, resp.json)
self.assertDictEqual(expected, resp.json)
@mock.patch.object(db_api, 'get_action_execution')
def test_get_operational_error(self, mocked_get):
@ -239,9 +252,10 @@ class TestActionExecutionsController(base.APITest):
]
resp = self.app.get('/v2/action_executions/123')
action_exec = copy.deepcopy(ACTION_EX)
del action_exec['task_name']
self.assertEqual(200, resp.status_int)
self.assertDictEqual(ACTION_EX, resp.json)
self.assertDictEqual(action_exec, resp.json)
def test_basic_get(self):
resp = self.app.get('/v2/action_executions/')
@ -259,7 +273,7 @@ class TestActionExecutionsController(base.APITest):
resp = self.app.get('/v2/action_executions/123')
self.assertEqual(200, resp.status_int)
self.assertTrue('project_id' in resp.json)
self.assertIn('project_id', resp.json)
@mock.patch.object(oslo_client.OsloRPCClient, 'sync_call',
mock.MagicMock(side_effect=oslo_exc.MessagingTimeout))
@ -561,11 +575,12 @@ class TestActionExecutionsController(base.APITest):
@mock.patch.object(db_api, 'get_action_executions', MOCK_ACTIONS)
def test_get_all(self):
resp = self.app.get('/v2/action_executions')
action_exec = copy.deepcopy(ACTION_EX)
del action_exec['task_name']
self.assertEqual(200, resp.status_int)
self.assertEqual(1, len(resp.json['action_executions']))
self.assertDictEqual(ACTION_EX, resp.json['action_executions'][0])
self.assertDictEqual(action_exec, resp.json['action_executions'][0])
@mock.patch.object(db_api, 'get_action_executions')
def test_get_all_operational_error(self, mocked_get_all):
@ -576,11 +591,13 @@ class TestActionExecutionsController(base.APITest):
]
resp = self.app.get('/v2/action_executions')
action_exec = copy.deepcopy(ACTION_EX)
del action_exec['task_name']
self.assertEqual(200, resp.status_int)
self.assertEqual(1, len(resp.json['action_executions']))
self.assertDictEqual(ACTION_EX, resp.json['action_executions'][0])
self.assertDictEqual(action_exec, resp.json['action_executions'][0])
@mock.patch.object(rest_utils, 'get_all',
return_value=resources.ActionExecutions())

View File

@ -79,6 +79,18 @@ class TestCronTriggerController(base.APITest):
self.assertEqual(200, resp.status_int)
self.assertDictEqual(TRIGGER, resp.json)
@mock.patch('mistral.db.v2.api.get_cron_trigger')
def test_get_with_fields_filter(self, mocked_get):
mocked_get.return_value = (TRIGGER['id'], TRIGGER['name'],)
resp = self.app.get('/v2/cron_triggers/my_cron_trigger?fields=name')
expected = {
'id': TRIGGER['id'],
'name': TRIGGER['name'],
}
self.assertEqual(200, resp.status_int)
self.assertDictEqual(expected, resp.json)
@mock.patch.object(db_api, 'get_cron_trigger')
def test_get_operational_error(self, mocked_get):
mocked_get.side_effect = [

View File

@ -188,6 +188,20 @@ class TestEnvironmentController(base.APITest):
self.assertEqual(200, resp.status_int)
self._assert_dict_equal(ENVIRONMENT, resp.json)
@mock.patch('mistral.db.v2.api.get_environment')
def test_get_with_fields_filter(self, mocked_get):
mocked_get.return_value = (
ENVIRONMENT['id'], ENVIRONMENT['name'],
)
resp = self.app.get('/v2/environments/123?fields=name')
expected = {
'id': ENVIRONMENT['id'],
'name': ENVIRONMENT['name'],
}
self.assertEqual(200, resp.status_int)
self.assertDictEqual(expected, resp.json)
@mock.patch.object(db_api, 'get_environment')
def test_get_operational_error(self, mocked_get):
mocked_get.side_effect = [

View File

@ -77,6 +77,21 @@ class TestEventTriggerController(base.APITest):
self.assertEqual(200, resp.status_int)
self.assertDictEqual(TRIGGER, resp.json)
@mock.patch('mistral.db.v2.api.get_event_trigger')
def test_get_with_fields_filter(self, mocked_get):
mocked_get.return_value = (TRIGGER['id'], TRIGGER['name'],)
resp = self.app.get(
'/v2/event_triggers/09cc56a9-d15e-4494-a6e2-c4ec8bdaacae'
'?fields=name'
)
expected = {
'id': TRIGGER['id'],
'name': TRIGGER['name'],
}
self.assertEqual(200, resp.status_int)
self.assertDictEqual(expected, resp.json)
@mock.patch.object(db_api, 'get_event_trigger')
def test_get_operational_error(self, mocked_get):
mocked_get.side_effect = [

View File

@ -169,6 +169,20 @@ class TestExecutionsController(base.APITest):
self.assertDictEqual(expected, resp.json)
@mock.patch('mistral.db.v2.api.get_workflow_execution')
def test_get_with_fields_filter(self, mocked_get):
mocked_get.return_value = (
WF_EX_JSON_WITH_DESC['id'], WF_EX_JSON_WITH_DESC['description'],
)
resp = self.app.get('/v2/executions/123?fields=description')
expected = {
'id': WF_EX_JSON_WITH_DESC['id'],
'description': WF_EX_JSON_WITH_DESC['description'],
}
self.assertEqual(200, resp.status_int)
self.assertDictEqual(expected, resp.json)
@mock.patch.object(db_api, 'get_workflow_execution')
def test_get_operational_error(self, mocked_get):
mocked_get.side_effect = [
@ -564,7 +578,8 @@ class TestExecutionsController(base.APITest):
self.assertEqual(201, resp.status_int)
self.assertDictEqual(expected_json, resp.json)
load_wf_ex_func.assert_called_once_with(expected_json['id'])
load_wf_ex_func.assert_called_once_with(expected_json['id'],
fields=())
kwargs = json.loads(expected_json['params'])
kwargs['description'] = expected_json['description']
@ -600,7 +615,8 @@ class TestExecutionsController(base.APITest):
self.assertEqual(201, resp.status_int)
self.assertDictEqual(expected_json, resp.json)
load_wf_ex_func.assert_called_once_with(expected_json['id'])
load_wf_ex_func.assert_called_once_with(expected_json['id'],
fields=())
# Note that "start_workflow" method on engine API should not be called
# in this case because we passed execution ID to the endpoint and the

View File

@ -171,6 +171,18 @@ class TestTasksController(base.APITest):
self.assertEqual(200, resp.status_int)
self.assertDictEqual(TASK, resp.json)
@mock.patch('mistral.db.v2.api.get_task_execution')
def test_get_with_fields_filter(self, mocked_get):
mocked_get.return_value = TASK_EX
resp = self.app.get('/v2/tasks/123?fields=name')
expected = {
'id': TASK['id'],
'name': TASK['name'],
}
self.assertEqual(200, resp.status_int)
self.assertDictEqual(expected, resp.json)
@mock.patch.object(db_api, 'get_task_execution')
def test_get_operational_error(self, mocked_get):
mocked_get.side_effect = [

View File

@ -169,6 +169,18 @@ class TestWorkbooksController(base.APITest):
self.assertEqual(200, resp.status_int)
self.assertDictEqual(WORKBOOK, resp.json)
@mock.patch('mistral.db.v2.api.get_workbook')
def test_get_with_fields_filter(self, mocked_get):
mocked_get.return_value = (WORKBOOK['id'], WORKBOOK['name'],)
resp = self.app.get('/v2/workbooks/123?fields=name')
expected = {
'id': WORKBOOK['id'],
'name': WORKBOOK['name'],
}
self.assertEqual(200, resp.status_int)
self.assertDictEqual(expected, resp.json)
@mock.patch.object(db_api, "get_workbook", MOCK_WB_WITH_NAMESPACE)
def test_get_with_namespace(self):
resp = self.app.get('/v2/workbooks/123?namespace=xyz')

View File

@ -264,6 +264,18 @@ class TestWorkflowsController(base.APITest):
self.assertEqual(200, resp.status_int)
self.assertDictEqual(WF, resp_json)
@mock.patch('mistral.db.v2.api.get_workflow_definition')
def test_get_with_fields_filter(self, mocked_get):
mocked_get.return_value = (WF['id'], WF['name'],)
resp = self.app.get('/v2/workflows/123?fields=name')
expected = {
'id': WF['id'],
'name': WF['name'],
}
self.assertEqual(200, resp.status_int)
self.assertDictEqual(expected, resp.json)
@mock.patch.object(db_api, 'get_workflow_definition')
def test_get_operational_error(self, mocked_get):
mocked_get.side_effect = [
@ -886,4 +898,4 @@ class TestWorkflowsController(base.APITest):
resp = self.app.get(
'/v2/workflows/123e4567-e89b-12d3-a456-426655440000')
self.assertEqual(200, resp.status_int)
self.assertTrue('project_id' in resp.json)
self.assertIn('project_id', resp.json)

View File

@ -121,6 +121,14 @@ def validate_fields(fields, object_fields):
)
def fields_list_to_cls_fields_tuple(model, f):
if not f:
return ()
return tuple(
[getattr(model, str(field)) for field in f]
)
def filters_to_dict(**kwargs):
"""Return only non-null values
@ -157,7 +165,7 @@ def get_all(list_cls, cls, get_all_function, get_function,
Default: ['asc'].
:param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in
fields if it's provided, since it will be used when
fields if it's not provided, since it will be used when
constructing 'next' link.
:param filters: Optional. A specified dictionary of filters to match.
:param all_projects: Optional. Get resources of all projects.