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()) return cls.from_tuples(d.items())
@classmethod @classmethod
def from_db_model(cls, db_model): def from_db_model(cls, db_model, fields=()):
return cls.from_tuples(db_model.iter_columns()) 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): def __str__(self):
"""WSME based implementation of __str__.""" """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 identifier: ID or name of the Action to get.
:param namespace: The namespace of the action. :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()) acl.enforce('actions:get', context.ctx())
@ -251,7 +254,7 @@ class ActionsController(rest.RestController, hooks.HookController):
Default: asc. Default: asc.
:param fields: Optional. A specified list of fields of the resource to :param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in 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. constructing 'next' link.
:param name: Optional. Keep only resources with a specific name. :param name: Optional. Keep only resources with a specific name.
:param scope: Optional. Keep only resources with a specific scope. :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. # Use retries to prevent possible failures.
@rest_utils.rest_retry_on_db_error @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(): 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) _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): Get rid of using dicts for constructing resources.
# TODO(nmakhotkin): Use db_model for this instead. # TODO(nmakhotkin): Use db_model for this instead.
res = resources.ActionExecution.from_db_model(action_ex)
task_name = (action_ex.task_execution.name # field_task_name_needed = 'task_name' in fields
if action_ex.task_execution else None) # if field_task_name_needed:
setattr(res, 'task_name', task_name) # 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 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. or less than that of sort_keys.
:param fields: Optional. A specified list of fields of the resource to :param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in 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. constructing 'next' link.
:param filters: Optional. A list of filters to apply to the result. :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): class ActionExecutionsController(rest.RestController):
@rest_utils.wrap_wsme_controller_exception @rest_utils.wrap_wsme_controller_exception
@wsme_pecan.wsexpose(resources.ActionExecution, wtypes.text) @wsme_pecan.wsexpose(resources.ActionExecution, wtypes.text,
def get(self, id): types.uniquelist)
def get(self, id, fields=None):
"""Return the specified action_execution. """Return the specified action_execution.
:param id: UUID of action execution to retrieve :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()) acl.enforce('action_executions:get', context.ctx())
LOG.debug("Fetch action_execution [id=%s]", id) 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 @rest_utils.wrap_wsme_controller_exception
@wsme_pecan.wsexpose(resources.ActionExecution, @wsme_pecan.wsexpose(resources.ActionExecution,
@ -248,7 +267,7 @@ class ActionExecutionsController(rest.RestController):
or less than that of sort_keys. or less than that of sort_keys.
:param fields: Optional. A specified list of fields of the resource to :param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in 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. constructing 'next' link.
:param name: Optional. Keep only resources with a specific name. :param name: Optional. Keep only resources with a specific name.
:param workflow_name: Optional. Keep only resources with a specific :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. or less than that of sort_keys.
:param fields: Optional. A specified list of fields of the resource to :param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in 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. constructing 'next' link.
:param name: Optional. Keep only resources with a specific name. :param name: Optional. Keep only resources with a specific name.
:param workflow_name: Optional. Keep only resources with a specific :param workflow_name: Optional. Keep only resources with a specific
@ -444,15 +463,19 @@ class TasksActionExecutionController(rest.RestController):
) )
@rest_utils.wrap_wsme_controller_exception @rest_utils.wrap_wsme_controller_exception
@wsme_pecan.wsexpose(resources.ActionExecution, wtypes.text, wtypes.text) @wsme_pecan.wsexpose(resources.ActionExecution, wtypes.text, wtypes.text,
def get(self, task_execution_id, action_ex_id): types.uniquelist)
def get(self, task_execution_id, action_ex_id, fields=()):
"""Return the specified action_execution. """Return the specified action_execution.
:param task_execution_id: Task execution UUID :param task_execution_id: Task execution UUID
:param action_ex_id: Action 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()) acl.enforce('action_executions:get', context.ctx())
LOG.debug("Fetch action_execution [id=%s]", action_ex_id) 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 @rest_utils.wrap_wsme_controller_exception
@wsme_pecan.wsexpose(resources.CodeSource, wtypes.text, wtypes.text) @wsme_pecan.wsexpose(resources.CodeSource, wtypes.text,
def get(self, identifier, namespace=''): wtypes.text, types.uniquelist)
def get(self, identifier, namespace='', fields=''):
"""Return a code source. """Return a code source.
:param identifier: Name or UUID of the code source to retrieve. :param identifier: Name or UUID of the code source to retrieve.
@ -202,13 +203,17 @@ class CodeSourcesController(rest.RestController, hooks.HookController):
identifier, identifier,
namespace namespace
) )
if fields and 'id' not in fields:
fields.insert(0, 'id')
db_model = rest_utils.rest_retry_on_db_error( db_model = rest_utils.rest_retry_on_db_error(
db_api.get_code_source)( db_api.get_code_source)(
identifier=identifier, 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) return resources.CodeSource.from_db_model(db_model)
@rest_utils.wrap_pecan_controller_exception @rest_utils.wrap_pecan_controller_exception

View File

@ -31,22 +31,30 @@ LOG = logging.getLogger(__name__)
class CronTriggersController(rest.RestController): class CronTriggersController(rest.RestController):
@rest_utils.wrap_wsme_controller_exception @rest_utils.wrap_wsme_controller_exception
@wsme_pecan.wsexpose(resources.CronTrigger, wtypes.text) @wsme_pecan.wsexpose(resources.CronTrigger,
def get(self, identifier): wtypes.text, types.uniquelist)
def get(self, identifier, fields=''):
"""Returns the named cron_trigger. """Returns the named cron_trigger.
:param identifier: Id or name of cron trigger to retrieve :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()) acl.enforce('cron_triggers:get', context.ctx())
LOG.debug('Fetch cron trigger [identifier=%s]', identifier) 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. # Use retries to prevent possible failures.
db_model = rest_utils.rest_retry_on_db_error( db_model = rest_utils.rest_retry_on_db_error(
db_api.get_cron_trigger db_api.get_cron_trigger
)(identifier) )(identifier, fields=fields)
if fields:
return resources.CronTrigger.from_db_model(db_model) 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 @rest_utils.wrap_wsme_controller_exception
@wsme_pecan.wsexpose( @wsme_pecan.wsexpose(
@ -124,7 +132,7 @@ class CronTriggersController(rest.RestController):
or less than that of sort_keys. or less than that of sort_keys.
:param fields: Optional. A specified list of fields of the resource to :param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in 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. constructing 'next' link.
:param name: Optional. Keep only resources with a specific name. :param name: Optional. Keep only resources with a specific name.
:param workflow_name: Optional. Keep only resources with a specific :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 @rest_utils.wrap_wsme_controller_exception
@wsme_pecan.wsexpose(resources.DynamicAction, wtypes.text, wtypes.text) @wsme_pecan.wsexpose(resources.DynamicAction, wtypes.text,
def get(self, identifier, namespace=''): wtypes.text, types.uniquelist)
def get(self, identifier, namespace='', fields=''):
"""Return the named action. """Return the named action.
:param identifier: Name or UUID of the action to retrieve. :param identifier: Name or UUID of the action to retrieve.
@ -222,14 +223,19 @@ class DynamicActionsController(rest.RestController, hooks.HookController):
identifier, identifier,
namespace namespace
) )
if fields and 'id' not in fields:
fields.insert(0, 'id')
db_model = rest_utils.rest_retry_on_db_error( db_model = rest_utils.rest_retry_on_db_error(
db_api.get_dynamic_action_definition db_api.get_dynamic_action_definition
)( )(
identifier=identifier, 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) return resources.DynamicAction.from_db_model(db_model)
@rest_utils.wrap_pecan_controller_exception @rest_utils.wrap_pecan_controller_exception

View File

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

View File

@ -34,18 +34,22 @@ CREATE_MANDATORY = set(['exchange', 'topic', 'event', 'workflow_id'])
class EventTriggersController(rest.RestController): class EventTriggersController(rest.RestController):
@rest_utils.wrap_wsme_controller_exception @rest_utils.wrap_wsme_controller_exception
@wsme_pecan.wsexpose(resources.EventTrigger, types.uuid) @wsme_pecan.wsexpose(resources.EventTrigger, types.uuid, types.uniquelist)
def get(self, id): def get(self, id, fields=''):
"""Returns the specified event_trigger.""" """Returns the specified event_trigger."""
acl.enforce('event_triggers:get', auth_ctx.ctx()) acl.enforce('event_triggers:get', auth_ctx.ctx())
LOG.debug('Fetch event trigger [id=%s]', id) 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. # Use retries to prevent possible failures.
r = rest_utils.create_db_retry_object() r = rest_utils.create_db_retry_object()
db_model = r.call(db_api.get_event_trigger, id) db_model = r.call(db_api.get_event_trigger, id, fields=fields)
if fields:
return resources.EventTrigger.from_db_model(db_model) 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 @rest_utils.wrap_wsme_controller_exception
@wsme_pecan.wsexpose(resources.EventTrigger, body=resources.EventTrigger, @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. # Use retries to prevent possible failures.
@rest_utils.rest_retry_on_db_error @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(): with db_api.transaction():
if must_exist: if must_exist:
wf_ex = db_api.get_workflow_execution(id) wf_ex = db_api.get_workflow_execution(id, fields=fields_tuple)
else: 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( return rest_utils.load_deferred_fields(
wf_ex, wf_ex,
@ -90,19 +98,25 @@ class ExecutionsController(rest.RestController):
executions = sub_execution.SubExecutionsController() executions = sub_execution.SubExecutionsController()
@rest_utils.wrap_wsme_controller_exception @rest_utils.wrap_wsme_controller_exception
@wsme_pecan.wsexpose(resources.Execution, wtypes.text) @wsme_pecan.wsexpose(resources.Execution, wtypes.text, types.uniquelist)
def get(self, id): def get(self, id, fields=None):
"""Return the specified Execution. """Return the specified Execution.
:param id: UUID of execution to retrieve. :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()) acl.enforce("executions:get", context.ctx())
LOG.debug("Fetch execution [id=%s]", id) 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 = ( resource.published_global = (
data_flow.get_workflow_execution_published_global(wf_ex) data_flow.get_workflow_execution_published_global(wf_ex)
@ -363,7 +377,7 @@ class ExecutionsController(rest.RestController):
or less than that of sort_keys. or less than that of sort_keys.
:param fields: Optional. A specified list of fields of the resource to :param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in 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. constructing 'next' link.
:param workflow_name: Optional. Keep only resources with a specific :param workflow_name: Optional. Keep only resources with a specific
workflow name. workflow name.

View File

@ -159,8 +159,8 @@ class Workflow(resource.Resource, ScopedResource):
return obj return obj
@classmethod @classmethod
def from_db_model(cls, db_model): def from_db_model(cls, db_model, fields=()):
obj = super(Workflow, cls).from_db_model(db_model) obj = super(Workflow, cls).from_db_model(db_model, fields=fields)
obj.set_attributes_from_spec(db_model.get('spec')) 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): def _get_task_resource_with_result(task_ex, fields=()):
task = resources.Task.from_db_model(task_ex) 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)) task.result = json.dumps(data_flow.get_task_execution_result(task_ex))
return task return task
# Use retries to prevent possible failures. # Use retries to prevent possible failures.
@rest_utils.rest_retry_on_db_error @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(): 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(task_ex, ['workflow_execution'])
rest_utils.load_deferred_fields( rest_utils.load_deferred_fields(
@ -74,7 +77,7 @@ def _get_task_execution(id):
['params'] ['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): def get_published_global(task_ex, wf_ex=None):
@ -141,7 +144,7 @@ class TaskExecutionsController(rest.RestController):
or less than that of sort_keys. or less than that of sort_keys.
:param fields: Optional. A specified list of fields of the resource to :param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in 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. constructing 'next' link.
:param workflow_name: Optional. Keep only resources with a specific :param workflow_name: Optional. Keep only resources with a specific
workflow name. workflow name.
@ -204,18 +207,26 @@ class TasksController(rest.RestController):
executions = sub_execution.SubExecutionsController() executions = sub_execution.SubExecutionsController()
@rest_utils.wrap_wsme_controller_exception @rest_utils.wrap_wsme_controller_exception
@wsme_pecan.wsexpose(resources.Task, wtypes.text) @wsme_pecan.wsexpose(resources.Task, wtypes.text, types.uniquelist)
def get(self, id): def get(self, id, fields=''):
"""Return the specified task. """Return the specified task.
:param id: UUID of task to retrieve :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()) acl.enforce('tasks:get', context.ctx())
LOG.debug("Fetch task [id=%s]", id) LOG.debug("Fetch task [id=%s]", id)
task, task_ex = _get_task_execution(id) task, task_ex = _get_task_execution(id, ())
task = _task_with_published_global(task, task_ex)
return _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 @rest_utils.wrap_wsme_controller_exception
@wsme_pecan.wsexpose(resources.Tasks, types.uuid, int, types.uniquelist, @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. or less than that of sort_keys.
:param fields: Optional. A specified list of fields of the resource to :param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in 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. constructing 'next' link.
:param name: Optional. Keep only resources with a specific name. :param name: Optional. Keep only resources with a specific name.
:param workflow_name: Optional. Keep only resources with a specific :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. or less than that of sort_keys.
:param fields: Optional. A specified list of fields of the resource to :param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in 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. constructing 'next' link.
:param name: Optional. Keep only resources with a specific name. :param name: Optional. Keep only resources with a specific name.
:param workflow_name: Optional. Keep only resources with a specific :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 @rest_utils.wrap_wsme_controller_exception
@wsme_pecan.wsexpose(resources.Workbook, wtypes.text, wtypes.text) @wsme_pecan.wsexpose(resources.Workbook, wtypes.text, wtypes.text,
def get(self, name, namespace=''): types.uniquelist)
def get(self, name, namespace='', fields=''):
"""Return the named workbook. """Return the named workbook.
:param name: Name of workbook to retrieve. :param name: Name of workbook to retrieve.
:param namespace: Optional. Namespace 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()) acl.enforce('workbooks:get', context.ctx())
LOG.debug("Fetch workbook [name=%s, namespace=%s]", name, namespace) 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. # Use retries to prevent possible failures.
r = rest_utils.create_db_retry_object() r = rest_utils.create_db_retry_object()
db_model = r.call( db_model = r.call(
db_api.get_workbook, db_api.get_workbook,
name, name,
namespace=namespace namespace=namespace,
fields=fields
) )
if fields:
return resources.Workbook.from_db_model(db_model) 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 @rest_utils.wrap_pecan_controller_exception
@pecan.expose(content_type="text/plain") @pecan.expose(content_type="text/plain")
@ -171,7 +180,7 @@ class WorkbooksController(rest.RestController, hooks.HookController):
Default: asc. Default: asc.
:param fields: Optional. A specified list of fields of the resource to :param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in 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. constructing 'next' link.
:param name: Optional. Keep only resources with a specific name. :param name: Optional. Keep only resources with a specific name.
:param definition: Optional. Keep only resources with a specific :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 @rest_utils.wrap_wsme_controller_exception
@wsme_pecan.wsexpose(resources.Workflow, wtypes.text, wtypes.text) @wsme_pecan.wsexpose(resources.Workflow, wtypes.text, wtypes.text,
def get(self, identifier, namespace=''): types.uniquelist)
def get(self, identifier, namespace='', fields=''):
"""Return the named workflow. """Return the named workflow.
:param identifier: Name or UUID of the workflow to retrieve. :param identifier: Name or UUID of the workflow to retrieve.
:param namespace: Optional. Namespace 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()) acl.enforce('workflows:get', context.ctx())
LOG.debug("Fetch workflow [identifier=%s]", identifier) LOG.debug("Fetch workflow [identifier=%s]", identifier)
if fields and 'id' not in fields:
fields.insert(0, 'id')
# Use retries to prevent possible failures. # Use retries to prevent possible failures.
r = rest_utils.create_db_retry_object() r = rest_utils.create_db_retry_object()
db_model = r.call( db_model = r.call(
db_api.get_workflow_definition, db_api.get_workflow_definition,
identifier, 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 @rest_utils.wrap_pecan_controller_exception
@pecan.expose(content_type="text/plain") @pecan.expose(content_type="text/plain")
@ -240,7 +250,7 @@ class WorkflowsController(rest.RestController, hooks.HookController):
Default: asc. Default: asc.
:param fields: Optional. A specified list of fields of the resource to :param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in 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. constructing 'next' link.
:param name: Optional. Keep only resources with a specific name. :param name: Optional. Keep only resources with a specific name.
:param namespace: Optional. Keep only resources with a specific :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): if col.name not in unloaded and hasattr(self, col.name):
yield col.name yield col.name
def iter_columns(self): def iter_columns(self, fields=()):
"""Returns an iterator for loaded columns. """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 :return: A generator function that generates
tuples (column name, column value). tuples (column name, column value).
""" """
for col_name in self.iter_column_names(): 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): def get_clone(self):
"""Clones current object, loads all fields and returns the result.""" """Clones current object, loads all fields and returns the result."""

View File

@ -524,17 +524,17 @@ def get_scheduled_jobs_count(**kwargs):
# Cron triggers. # Cron triggers.
def get_cron_trigger(identifier): def get_cron_trigger(identifier, fields=()):
return IMPL.get_cron_trigger(identifier) return IMPL.get_cron_trigger(identifier, fields=fields)
def get_cron_trigger_by_id(id): def get_cron_trigger_by_id(id, fields=()):
return IMPL.get_cron_trigger_by_id(id) 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.""" """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): def get_cron_triggers(**kwargs):
@ -593,13 +593,13 @@ def delete_cron_triggers(**kwargs):
# Environments. # Environments.
def get_environment(name): def get_environment(name, fields=()):
return IMPL.get_environment(name) 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.""" """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, def get_environments(limit=None, marker=None, sort_keys=None,
@ -667,12 +667,12 @@ def delete_resource_members(**kwargs):
# Event triggers. # Event triggers.
def get_event_trigger(id, insecure=False): def get_event_trigger(id, insecure=False, fields=()):
return IMPL.get_event_trigger(id, insecure) return IMPL.get_event_trigger(id, insecure, fields=fields)
def load_event_trigger(id, insecure=False): def load_event_trigger(id, insecure=False, fields=()):
return IMPL.load_event_trigger(id, insecure) return IMPL.load_event_trigger(id, insecure, fields=fields)
def get_event_triggers(insecure=False, limit=None, marker=None, sort_keys=None, 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=()): 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 = ( query = (
b.model_query(model, columns=columns) b.model_query(model, columns=columns)
if insecure 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, def _get_db_object_by_name_and_namespace_or_id(model, identifier,
namespace=None, insecure=False, namespace=None, insecure=False,
columns=()): columns=()):
columns = (
tuple([getattr(model, f) for f in columns if hasattr(model, f)])
if columns and isinstance(columns, list) else columns
)
query = ( query = (
b.model_query(model, columns=columns) b.model_query(model, columns=columns)
if insecure if insecure
@ -1599,13 +1608,14 @@ def _get_completed_root_executions_query(columns):
@b.session_aware() @b.session_aware()
def get_cron_trigger(identifier, session=None): def get_cron_trigger(identifier, session=None, fields=()):
ctx = context.ctx() ctx = context.ctx()
cron_trigger = _get_db_object_by_name_and_namespace_or_id( cron_trigger = _get_db_object_by_name_and_namespace_or_id(
models.CronTrigger, models.CronTrigger,
identifier, identifier,
insecure=ctx.is_admin insecure=ctx.is_admin,
columns=fields,
) )
if not cron_trigger: if not cron_trigger:
@ -1617,10 +1627,11 @@ def get_cron_trigger(identifier, session=None):
@b.session_aware() @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() ctx = context.ctx()
cron_trigger = _get_db_object_by_id(models.CronTrigger, id, cron_trigger = _get_db_object_by_id(models.CronTrigger, id,
insecure=ctx.is_admin) insecure=ctx.is_admin,
columns=fields)
if not cron_trigger: if not cron_trigger:
raise exc.DBEntityNotFoundError( raise exc.DBEntityNotFoundError(
"Cron trigger not found [id=%s]" % id "Cron trigger not found [id=%s]" % id
@ -1630,10 +1641,11 @@ def get_cron_trigger_by_id(id, session=None):
@b.session_aware() @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( return _get_db_object_by_name_and_namespace_or_id(
models.CronTrigger, models.CronTrigger,
identifier identifier,
columns=fields,
) )
@ -1744,8 +1756,8 @@ def delete_cron_triggers(session=None, **kwargs):
# Environments. # Environments.
@b.session_aware() @b.session_aware()
def get_environment(name, session=None): def get_environment(name, session=None, fields=()):
env = _get_db_object_by_name(models.Environment, name) env = _get_db_object_by_name(models.Environment, name, columns=fields)
if not env: if not env:
raise exc.DBEntityNotFoundError( raise exc.DBEntityNotFoundError(
@ -1756,8 +1768,8 @@ def get_environment(name, session=None):
@b.session_aware() @b.session_aware()
def load_environment(name, session=None): def load_environment(name, session=None, fields=()):
return _get_db_object_by_name(models.Environment, name) return _get_db_object_by_name(models.Environment, name, columns=fields)
@b.session_aware() @b.session_aware()
@ -1982,8 +1994,9 @@ def _get_accepted_resources(res_type):
# Event triggers. # Event triggers.
@b.session_aware() @b.session_aware()
def get_event_trigger(id, insecure=False, session=None): def get_event_trigger(id, insecure=False, session=None, fields=()):
event_trigger = _get_db_object_by_id(models.EventTrigger, id, insecure) event_trigger = _get_db_object_by_id(models.EventTrigger, id, insecure,
columns=fields)
if not event_trigger: if not event_trigger:
raise exc.DBEntityNotFoundError( raise exc.DBEntityNotFoundError(
@ -1994,8 +2007,9 @@ def get_event_trigger(id, insecure=False, session=None):
@b.session_aware() @b.session_aware()
def load_event_trigger(id, insecure=False, session=None): def load_event_trigger(id, insecure=False, session=None, fields=()):
return _get_db_object_by_id(models.EventTrigger, id, insecure) return _get_db_object_by_id(models.EventTrigger, id, insecure,
columns=fields)
@b.session_aware() @b.session_aware()

View File

@ -226,9 +226,22 @@ class TestActionExecutionsController(base.APITest):
@mock.patch.object(db_api, 'get_action_execution', MOCK_ACTION) @mock.patch.object(db_api, 'get_action_execution', MOCK_ACTION)
def test_get(self): def test_get(self):
resp = self.app.get('/v2/action_executions/123') 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.assertEqual(200, resp.status_int)
self.assertDictEqual(ACTION_EX, resp.json) self.assertDictEqual(expected, resp.json)
@mock.patch.object(db_api, 'get_action_execution') @mock.patch.object(db_api, 'get_action_execution')
def test_get_operational_error(self, mocked_get): def test_get_operational_error(self, mocked_get):
@ -239,9 +252,10 @@ class TestActionExecutionsController(base.APITest):
] ]
resp = self.app.get('/v2/action_executions/123') 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.assertEqual(200, resp.status_int)
self.assertDictEqual(ACTION_EX, resp.json) self.assertDictEqual(action_exec, resp.json)
def test_basic_get(self): def test_basic_get(self):
resp = self.app.get('/v2/action_executions/') resp = self.app.get('/v2/action_executions/')
@ -259,7 +273,7 @@ class TestActionExecutionsController(base.APITest):
resp = self.app.get('/v2/action_executions/123') resp = self.app.get('/v2/action_executions/123')
self.assertEqual(200, resp.status_int) 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.patch.object(oslo_client.OsloRPCClient, 'sync_call',
mock.MagicMock(side_effect=oslo_exc.MessagingTimeout)) 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) @mock.patch.object(db_api, 'get_action_executions', MOCK_ACTIONS)
def test_get_all(self): def test_get_all(self):
resp = self.app.get('/v2/action_executions') 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(200, resp.status_int)
self.assertEqual(1, len(resp.json['action_executions'])) 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') @mock.patch.object(db_api, 'get_action_executions')
def test_get_all_operational_error(self, mocked_get_all): 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') 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(200, resp.status_int)
self.assertEqual(1, len(resp.json['action_executions'])) 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', @mock.patch.object(rest_utils, 'get_all',
return_value=resources.ActionExecutions()) return_value=resources.ActionExecutions())

View File

@ -79,6 +79,18 @@ class TestCronTriggerController(base.APITest):
self.assertEqual(200, resp.status_int) self.assertEqual(200, resp.status_int)
self.assertDictEqual(TRIGGER, resp.json) 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') @mock.patch.object(db_api, 'get_cron_trigger')
def test_get_operational_error(self, mocked_get): def test_get_operational_error(self, mocked_get):
mocked_get.side_effect = [ mocked_get.side_effect = [

View File

@ -188,6 +188,20 @@ class TestEnvironmentController(base.APITest):
self.assertEqual(200, resp.status_int) self.assertEqual(200, resp.status_int)
self._assert_dict_equal(ENVIRONMENT, resp.json) 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') @mock.patch.object(db_api, 'get_environment')
def test_get_operational_error(self, mocked_get): def test_get_operational_error(self, mocked_get):
mocked_get.side_effect = [ mocked_get.side_effect = [

View File

@ -77,6 +77,21 @@ class TestEventTriggerController(base.APITest):
self.assertEqual(200, resp.status_int) self.assertEqual(200, resp.status_int)
self.assertDictEqual(TRIGGER, resp.json) 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') @mock.patch.object(db_api, 'get_event_trigger')
def test_get_operational_error(self, mocked_get): def test_get_operational_error(self, mocked_get):
mocked_get.side_effect = [ mocked_get.side_effect = [

View File

@ -169,6 +169,20 @@ class TestExecutionsController(base.APITest):
self.assertDictEqual(expected, resp.json) 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') @mock.patch.object(db_api, 'get_workflow_execution')
def test_get_operational_error(self, mocked_get): def test_get_operational_error(self, mocked_get):
mocked_get.side_effect = [ mocked_get.side_effect = [
@ -564,7 +578,8 @@ class TestExecutionsController(base.APITest):
self.assertEqual(201, resp.status_int) self.assertEqual(201, resp.status_int)
self.assertDictEqual(expected_json, resp.json) 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 = json.loads(expected_json['params'])
kwargs['description'] = expected_json['description'] kwargs['description'] = expected_json['description']
@ -600,7 +615,8 @@ class TestExecutionsController(base.APITest):
self.assertEqual(201, resp.status_int) self.assertEqual(201, resp.status_int)
self.assertDictEqual(expected_json, resp.json) 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 # 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 # 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.assertEqual(200, resp.status_int)
self.assertDictEqual(TASK, resp.json) 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') @mock.patch.object(db_api, 'get_task_execution')
def test_get_operational_error(self, mocked_get): def test_get_operational_error(self, mocked_get):
mocked_get.side_effect = [ mocked_get.side_effect = [

View File

@ -169,6 +169,18 @@ class TestWorkbooksController(base.APITest):
self.assertEqual(200, resp.status_int) self.assertEqual(200, resp.status_int)
self.assertDictEqual(WORKBOOK, resp.json) 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) @mock.patch.object(db_api, "get_workbook", MOCK_WB_WITH_NAMESPACE)
def test_get_with_namespace(self): def test_get_with_namespace(self):
resp = self.app.get('/v2/workbooks/123?namespace=xyz') 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.assertEqual(200, resp.status_int)
self.assertDictEqual(WF, resp_json) 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') @mock.patch.object(db_api, 'get_workflow_definition')
def test_get_operational_error(self, mocked_get): def test_get_operational_error(self, mocked_get):
mocked_get.side_effect = [ mocked_get.side_effect = [
@ -886,4 +898,4 @@ class TestWorkflowsController(base.APITest):
resp = self.app.get( resp = self.app.get(
'/v2/workflows/123e4567-e89b-12d3-a456-426655440000') '/v2/workflows/123e4567-e89b-12d3-a456-426655440000')
self.assertEqual(200, resp.status_int) 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): def filters_to_dict(**kwargs):
"""Return only non-null values """Return only non-null values
@ -157,7 +165,7 @@ def get_all(list_cls, cls, get_all_function, get_function,
Default: ['asc']. Default: ['asc'].
:param fields: Optional. A specified list of fields of the resource to :param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in 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. constructing 'next' link.
:param filters: Optional. A specified dictionary of filters to match. :param filters: Optional. A specified dictionary of filters to match.
:param all_projects: Optional. Get resources of all projects. :param all_projects: Optional. Get resources of all projects.