Skip to content

Commit

Permalink
KE-2262 add create_workflow to client and scope object (#1154)
Browse files Browse the repository at this point in the history
* added create workflow stub

* created tests for workflow creation, added casettes

* KE-2262 remove time dependence in tests to check the casettes against.

* KE-2262 type checking improvements and removed unused imports.

Co-authored-by: Jochem Berends <jochem.berends@ke-works.com>
  • Loading branch information
jberends and Jochem Berends authored May 24, 2022
1 parent ff6ac77 commit a709890
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 26 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
Change Log
==========

v4.0.4 (UNRELEASED)
-------------------
* :star: Added the method to create workflow in a certain project. `Client.create_workflow(scope=...)` or `Scope.create_workflow()`.

v4.0.3 (24MAY22)
----------------
+ :+1: Ensuring that the order attribute is removed from sidebar items in sync with the deprecation of the order in backend.
* :+1: Ensuring that the order attribute is removed from sidebar items in sync with the deprecation of the order in backend.

v4.0.2 (10MAY22)
----------------
Expand Down
9 changes: 9 additions & 0 deletions pykechain/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3992,3 +3992,12 @@ def workflows(
request_params.update(**kwargs)

return Workflow.list(client=self, **request_params)

def create_workflow(self, scope: ObjectID, **kwargs) -> Workflow:
"""Create a new Defined Workflow object in a scope.
See `Workflow.create_workflow` for available parameters.
:return: a Workflow object
"""
return Workflow.create(client=self, scope=scope, **kwargs)
20 changes: 11 additions & 9 deletions pykechain/models/scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,7 @@
from pykechain.models.team import Team
from pykechain.models.workflow import Workflow
from pykechain.typing import ObjectID
from pykechain.utils import (
Empty,
clean_empty_values,
empty,
find,
is_uuid,
parse_datetime,
)
from pykechain.utils import Empty, clean_empty_values, empty, find, is_uuid, parse_datetime


class Scope(Base, TagsMixin):
Expand Down Expand Up @@ -826,7 +819,7 @@ def instantiate_form(self, model, *args, **kwargs) -> "Form":
return self._client.instantiate_form(*args, model=model, *args, **kwargs)
else:
raise IllegalArgumentError(
"Form Model '{}' not in Scope '{}'".format(model.name, self.name)
f"Form Model '{model.name}' not in Scope '{self.name}'"
)

#
Expand All @@ -852,6 +845,15 @@ def workflow(self, **kwargs) -> "Workflow":
"""
return self._client.workflow(scope=self, **kwargs)

def create_workflow(self, **kwargs) -> Workflow:
"""Create a new Defined Workflow object in this scope.
See `Workflow.create_workflow` for available parameters.
:return: a Workflow object
"""
return Workflow.create(client=self._client, scope=self, **kwargs)

def import_workflow(
self, workflow: Union[Workflow, ObjectID], **kwargs
) -> "Workflow":
Expand Down
62 changes: 56 additions & 6 deletions pykechain/models/workflow.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import List, Optional, Union
from typing import List, Optional, TYPE_CHECKING, Union

import requests

Expand All @@ -18,6 +18,10 @@
from pykechain.typing import ObjectID
from pykechain.utils import Empty, clean_empty_values, find_obj_in_list

if TYPE_CHECKING:
from pykechain.client import Client
from pykechain.models import Scope


class Transition(Base, CrudActionsMixin):
"""Transition Object."""
Expand Down Expand Up @@ -155,6 +159,52 @@ def delete(self):
"""Delete Workflow."""
return super().delete()

@classmethod
def create(
cls,
client: "Client",
name: str,
scope: Union["Scope", ObjectID],
category: WorkflowCategory = WorkflowCategory.DEFINED,
description: str = None,
options: dict = None,
active: bool = False,
**kwargs,
) -> "Workflow":
"""
Create a new Workflow object using the client.
:param client: Client object.
:param name: Name of the workflow
:param scope: Scope of the workflow
:param category: (optional) category of the workflow, defaults to WorkflowCategory.DEFINED
:param description: (optional) description of the workflow
:param options: (optional) JSON/dictionary with workflow options
:param active: (optional) boolean flag to set the workflow to active. Defaults to False
:param kwargs: (optional) additional kwargs.
:return: a Workflow object
:raises APIError: When the Workflow could not be created.
"""
from pykechain.models import Scope # avoiding circular imports here.

data = {
"name": check_text(name, "name"),
"scope": check_base(scope, Scope, "scope"),
"category": check_enum(category, WorkflowCategory, "category"),
"description": check_text(description, "description"),
"options": check_type(options, dict, "options"),
"active": check_type(active, bool, "active"),
}
kwargs.update(API_EXTRA_PARAMS[cls.url_list_name])
response = client._request(
"POST", client._build_url(cls.url_list_name), params=kwargs, json=data
)

if response.status_code != requests.codes.created: # pragma: no cover
raise APIError(f"Could not create {cls.__name__}", response=response)

return cls(json=response.json()["results"][0], client=client)

@property
def status_order(self) -> List[Status]:
"""Statuses in the right order."""
Expand Down Expand Up @@ -193,7 +243,7 @@ def transition(
Retrieve the Transition belonging to this workflow based on its name, ref or uuid.
:param value: transition name, ref or UUID to search for
:param attr: the attribute to match on. Eg. to_status=<Status Obj>
:param attr: the attribute to match on. E.g. to_status=<Status Obj>
:return: a single :class:`Transition`
:raises NotFoundError: if the `Transition` is not part of the `Workflow`
:raises MultipleFoundError
Expand Down Expand Up @@ -347,7 +397,7 @@ def update_transition(
"workflow",
response=response,
)
# an updated transition will be altered so we want to refresh the workflow.
# an updated transition will be altered, so we want to refresh the workflow.
self.refresh()
return Transition(json=response.json()["results"][0])

Expand All @@ -373,7 +423,7 @@ def delete_transition(self, transition: Union[Transition, ObjectID]) -> None:
"workflow",
response=response,
)
# a deleted transition will be unlinked so we want to refresh the workflow.
# a deleted transition will be unlinked, so we want to refresh the workflow.
self.refresh()

def create_transition(
Expand Down Expand Up @@ -412,7 +462,7 @@ def create_transition(
"Could not create the specific transition in the " "workflow",
response=response,
)
# a new transition will be linked to the workflow so we want to refresh the workflow.
# a new transition will be linked to the workflow, so we want to refresh the workflow.
self.refresh()
return Transition(json=response.json()["results"][0], client=self._client)

Expand Down Expand Up @@ -449,7 +499,7 @@ def create_status(
"link it to the workflow",
response=response,
)
# a new status will create a new global transition to that status
# a new status will create a new global transition to that status,
# so we want to update the current workflow.
self.refresh()
return Status(json=response.json()["results"][0], client=self._client)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"http_interactions": [{"request": {"body": {"encoding": "utf-8", "string": ""}, "headers": {"User-Agent": ["python-requests/2.27.1"], "Accept-Encoding": ["gzip, deflate"], "Accept": ["*/*"], "Connection": ["keep-alive"], "X-Requested-With": ["XMLHttpRequest"], "PyKechain-Version": ["4.0.3"], "Authorization": ["Token <AUTH_TOKEN>"]}, "method": "GET", "uri": "<API_URL>/api/v3/scopes.json?status=ACTIVE&name=Bike+Project&fields=id%2Cname%2Cref%2Ctext%2Ccreated_at%2Cupdated_at%2Cstart_date%2Cdue_date%2Cstatus%2Ccategory%2Cprogress%2Cmembers%2Cteam%2Ctags%2Cscope_options%2Cteam_id_name%2Cworkflow_root_id%2Ccatalog_root_id%2Capp_root_id%2Cproduct_model_id%2Cproduct_instance_id%2Ccatalog_model_id%2Ccatalog_instance_id&limit=2"}, "response": {"body": {"encoding": "utf-8", "base64_string": "H4sIAAAAAAAAA7VVwW7jNhD9FUPXhgZJkRTl07aLPeyhbdBke2gRCCNq5KiRREGivBsE/vcO5XUsI2h7SQ8GPI/zZt7MkKOXxPm5D8lO3CQ9fqM//dy2N8kw4qHx83S2R5zmNpD550vSVMkuKStdOQRgkGpkKis5y2unmc20ErxWoE2dUEzokLx/ap5wczv6v9CFJEarYwgC2fAKhiV98t1rM09YbYLfBJzC5vb5Cd0jNP0G+mozzNPjpgnL6SNu2qZrSNpNMgUIUXLy48f7z79/IsRBwL0fnwn7cvfpt+Lu46+3Cz4inVQFxIySi5wJzqS853bHzU7Zrc2UTvUf5DoP1dpVSsY1k+qe5zvBd1psU5UKoaJrgH3s0FIYmd/Y5PyALMC4x5A8kMBoF34Ije/J8yXxBxzHpsI7+pUwJrsa2gljg2gAE/ZU0MmV2j6G5yH20s1T8N1n5/tYiO/rZh9DVc00tPC84LG37tm1UcUK/9lXMcCI+7mlZMcjSTpF+wUOzX7JRakejnEY0BVNVZzmd7oDHXYljpc7IK2h9kw4fh/yNA84HprJj5fB079mKsCF5kBmGGcqDjto2tezU9TzWQSghz1eekHQKvLFr0WozuTF9Xhz0pWnV7LiBYq+/6eoC7pWFdlnUW81nUO+i6xX5L9VXfVKvZEVzYumL2Rt7gn+N3Hwodz27Xt27kqjeHvLrkXeraB/FPnoY8ungOMPQ9OlLNb6gRbQsla2znfvPvmHZSONoYgL5PyIqhmvbNp/e3rq9Kr4Nrc8k9rIVFuZGXpcX/34VLf+azF6H4pl73LBK2O4Ykq7kinrFLNKZ8ykteZWGlC2Pi0+aP1+RaxqVLSgOVO5kUzVObBcWcEyLGUFVmRKAhFhGFaktITUKlRMZpkgEucMKAsrpa2F0WnFpUmWIqrZhaKjBdOemLm1KhdSMZGrnCnDU5YjSCZUnqsSaJFyWDGbnlrVOzyRwfAsRVUynQugSkVKGbP4hQEjoJSQunRV5CptaYxQTgsmtSamxJzZVFqmhDQZirSCLFsxr9PmnAJjLhhaYYjCgZWOBAhj6BuhNNQyTY4Px78Bmp1kGDUHAAA=", "string": ""}, "headers": {"Server": ["nginx"], "Date": ["Tue, 24 May 2022 11:33:07 GMT"], "Content-Type": ["application/json"], "Transfer-Encoding": ["chunked"], "Connection": ["keep-alive"], "Vary": ["Accept-Encoding", "Accept, Accept-Language, Cookie"], "allow": ["GET, POST, HEAD, OPTIONS"], "x-frame-options": ["SAMEORIGIN"], "content-language": ["en", "En"], "strict-transport-security": ["max-age=15768000; includeSubDomains"], "x-content-type-options": ["nosniff"], "x-xss-protection": ["1; mode=block"], "referrer-policy": ["same-origin"], "Ke-Chain": ["3"], "Content-Encoding": ["gzip"]}, "status": {"code": 200, "message": "OK"}, "url": "<API_URL>/api/v3/scopes.json?status=ACTIVE&name=Bike+Project&fields=id%2Cname%2Cref%2Ctext%2Ccreated_at%2Cupdated_at%2Cstart_date%2Cdue_date%2Cstatus%2Ccategory%2Cprogress%2Cmembers%2Cteam%2Ctags%2Cscope_options%2Cteam_id_name%2Cworkflow_root_id%2Ccatalog_root_id%2Capp_root_id%2Cproduct_model_id%2Cproduct_instance_id%2Ccatalog_model_id%2Ccatalog_instance_id&limit=2"}, "recorded_at": "2022-05-24T11:33:07"}, {"request": {"body": {"encoding": "utf-8", "string": ""}, "headers": {"User-Agent": ["python-requests/2.27.1"], "Accept-Encoding": ["gzip, deflate"], "Accept": ["*/*"], "Connection": ["keep-alive"], "X-Requested-With": ["XMLHttpRequest"], "PyKechain-Version": ["4.0.3"], "Authorization": ["Token <AUTH_TOKEN>"]}, "method": "GET", "uri": "<API_URL>/api/v3/workflows?name=Simple+Workflow&category=CATALOG&limit=2&fields=id%2Cname%2Cref%2Ccreated_at%2Cupdated_at%2Cdescription%2Cderived_from%2Coptions%2Ccategory%2Cactive%2Cstatuses%2Cis_consistent%2Ctransitions%2Cscope_id"}, "response": {"body": {"encoding": "utf-8", "base64_string": "H4sIAAAAAAAAA+1X227jNhD9FUHPnoKkSFHMW3azDQIE8SIxUKBFYPCmWFhZVHWJGwT5945t+ZIm0fq1TQE9UObhcM451Az9HNvQV118Ridx5f/CQdWX5SSuG/9YhL7dvTe+7csOX/94jgsXn8W5sirJrIdMEQXcUgqZNDl4zROSpJQrnsQYUy89ou+KZV366LfQ/MjLsMIJ51vbFHVXhArnz6MBsRoQUbfQXbTQLQ58NAsuTKKrKqqb8ICptJPoFvPzq0hXLroIlY/aTnd969t4nWuOIdtNQFgdtrSN1513c40sY0YYA7J+ZlSdifSMpr+kTLCM/47QvnbjUClExtgaahH4EJonBH49n51fTy/xx302a71eU50tijbCZxYwcYRu1KSeMi80hYQaDZz5HFTuNJicWMNp5k1GDmrulm6JdgFc2O85P8pnNr2Yvp2Yu6KtSz0CKOwm07rsW2j/7HXj3wHZUIYGUejHU/wy+YhmcTBtRzZXmqhUaVCCSuA8R57aapBMMsKdt8YcHR10/fshwJZyUcFR0LfEr26+304vb7/d3Y3SH4UNIuimCatBBWiKh0U3ooUpez+uRbM5tjslkLtQghnQ1hG0nSlQVirQRkrmUp0axg5K3O7WbkXYh/rX8Hf4oe6oS5HLzGYKvCQ5cM0FaMI0JMRYK6zMpPMH6hfblVviQ5i3tC+mN99GCX8AGKjahbc/Tjrwvopf7idx1+iqLdYkjwqjzoXLWSbAauOAe0nBMGog0YJaPNqC0nX0vAnL+XYLXHu/Z/p1U6T2XO3u9bWiW1Skowpr4K+hWSKiC/t4n6TmHBkw757qTbW4uZpdnV9vDuGGpZE0JcqnwFOGdgiSgXGOAnXM0zTNM+rsiB3v0/5H86qehvYTLcNjUT1EXdhL/b8rOHN5Pf1ybApPOeckM+B4is4wkUDGBJriiU+xFFor9YgpP28JJxj0OsgJNn3GTnaCl4lIqCbOgkh8Ahy/J+xmOFLMKc85TSSjI15+1NROcHC/9DTzPkXrPcEwJ2VidYptiVIGnOAdHnuWwn6lUp/m3CWCjRj2Xis+waxh2QlW/aduCWOGoKahHm4Pz4jUtiseEdA1vcfANtR+vlZh/Q/s5f7lbzWMZgqrDQAA", "string": ""}, "headers": {"Server": ["nginx"], "Date": ["Tue, 24 May 2022 11:33:07 GMT"], "Content-Type": ["application/json"], "Transfer-Encoding": ["chunked"], "Connection": ["keep-alive"], "Vary": ["Accept-Encoding", "Accept, Accept-Language, Cookie"], "allow": ["GET, POST, HEAD, OPTIONS"], "x-frame-options": ["SAMEORIGIN"], "content-language": ["en", "En"], "strict-transport-security": ["max-age=15768000; includeSubDomains"], "x-content-type-options": ["nosniff"], "x-xss-protection": ["1; mode=block"], "referrer-policy": ["same-origin"], "Ke-Chain": ["3"], "Content-Encoding": ["gzip"]}, "status": {"code": 200, "message": "OK"}, "url": "<API_URL>/api/v3/workflows?name=Simple+Workflow&category=CATALOG&limit=2&fields=id%2Cname%2Cref%2Ccreated_at%2Cupdated_at%2Cdescription%2Cderived_from%2Coptions%2Ccategory%2Cactive%2Cstatuses%2Cis_consistent%2Ctransitions%2Cscope_id"}, "recorded_at": "2022-05-24T11:33:07"}, {"request": {"body": {"encoding": "utf-8", "string": "{\"name\": \"___TEST WORKFLOW__DELETE_ME\", \"scope\": \"bd5dceaa-a35e-47b0-9fc5-875410f4a56f\", \"category\": \"DEFINED\", \"description\": \"This is a test workflow created in a scope\", \"options\": {\"foo\": \"bar\"}, \"active\": true}"}, "headers": {"User-Agent": ["python-requests/2.27.1"], "Accept-Encoding": ["gzip, deflate"], "Accept": ["*/*"], "Connection": ["keep-alive"], "X-Requested-With": ["XMLHttpRequest"], "PyKechain-Version": ["4.0.3"], "Authorization": ["Token <AUTH_TOKEN>"], "Content-Length": ["215"], "Content-Type": ["application/json"]}, "method": "POST", "uri": "<API_URL>/api/v3/workflows?fields=id%2Cname%2Cref%2Ccreated_at%2Cupdated_at%2Cdescription%2Cderived_from%2Coptions%2Ccategory%2Cactive%2Cstatuses%2Cis_consistent%2Ctransitions%2Cscope_id"}, "response": {"body": {"encoding": "utf-8", "string": "{\"results\":[{\"id\":\"d40944e7-bf5a-402f-86cf-03b14386da0e\",\"name\":\"___TEST WORKFLOW__DELETE_ME\",\"description\":\"This is a test workflow created in a scope\",\"ref\":\"test-workflow__delete_me\",\"derived_from_id\":null,\"derived_from_id_name\":{},\"created_at\":\"2022-05-24T11:33:07.747865Z\",\"updated_at\":\"2022-05-24T11:33:07.747890Z\",\"category\":\"DEFINED\",\"scope\":\"bd5dceaa-a35e-47b0-9fc5-875410f4a56f\",\"statuses\":[],\"transitions\":[],\"permissions\":{\"create\":true,\"update\":true,\"destroy\":true,\"read\":true,\"activate\":true,\"approve_translations\":true,\"clone\":true,\"create_status\":true,\"create_transition\":true,\"deactivate\":true,\"delete_transition\":true,\"link_transitions\":true,\"set_name_translations\":true,\"set_description_translations\":true,\"set_status_order\":true,\"unlink_transitions\":true,\"update_transition\":true},\"options\":{\"foo\":\"bar\"},\"active\":true,\"name_translations\":{\"it\":\"\",\"nl\":\"\",\"de\":\"\",\"en\":\"\",\"fr\":\"\"},\"description_translations\":{\"it\":\"\",\"nl\":\"\",\"de\":\"\",\"en\":\"\",\"fr\":\"\"},\"display_name\":\"___TEST WORKFLOW__DELETE_ME\",\"display_description\":\"This is a test workflow created in a scope\",\"has_dirty_translations\":true}]}"}, "headers": {"Server": ["nginx"], "Date": ["Tue, 24 May 2022 11:33:07 GMT"], "Content-Type": ["application/json"], "Content-Length": ["1114"], "Connection": ["keep-alive"], "vary": ["Accept, Accept-Language, Cookie"], "allow": ["GET, POST, HEAD, OPTIONS"], "x-frame-options": ["SAMEORIGIN"], "content-language": ["en", "En"], "strict-transport-security": ["max-age=15768000; includeSubDomains"], "x-content-type-options": ["nosniff"], "x-xss-protection": ["1; mode=block"], "referrer-policy": ["same-origin"], "Ke-Chain": ["3"]}, "status": {"code": 201, "message": "Created"}, "url": "<API_URL>/api/v3/workflows?fields=id%2Cname%2Cref%2Ccreated_at%2Cupdated_at%2Cdescription%2Cderived_from%2Coptions%2Ccategory%2Cactive%2Cstatuses%2Cis_consistent%2Ctransitions%2Cscope_id"}, "recorded_at": "2022-05-24T11:33:07"}, {"request": {"body": {"encoding": "utf-8", "string": ""}, "headers": {"User-Agent": ["python-requests/2.27.1"], "Accept-Encoding": ["gzip, deflate"], "Accept": ["*/*"], "Connection": ["keep-alive"], "X-Requested-With": ["XMLHttpRequest"], "PyKechain-Version": ["4.0.3"], "Authorization": ["Token <AUTH_TOKEN>"], "Content-Length": ["0"]}, "method": "DELETE", "uri": "<API_URL>/api/v3/workflows/d40944e7-bf5a-402f-86cf-03b14386da0e"}, "response": {"body": {"encoding": "utf-8", "string": ""}, "headers": {"Server": ["nginx"], "Date": ["Tue, 24 May 2022 11:33:07 GMT"], "Content-Type": ["application/json"], "Content-Length": ["14"], "Connection": ["keep-alive"], "vary": ["Accept, Accept-Language, Cookie"], "allow": ["GET, PUT, PATCH, DELETE, HEAD, OPTIONS"], "x-frame-options": ["SAMEORIGIN"], "content-language": ["en", "En"], "strict-transport-security": ["max-age=15768000; includeSubDomains"], "x-content-type-options": ["nosniff"], "x-xss-protection": ["1; mode=block"], "referrer-policy": ["same-origin"], "Ke-Chain": ["3"]}, "status": {"code": 204, "message": "No Content"}, "url": "<API_URL>/api/v3/workflows/d40944e7-bf5a-402f-86cf-03b14386da0e"}, "recorded_at": "2022-05-24T11:33:07"}], "recorded_with": "betamax/0.8.1"}
Loading

0 comments on commit a709890

Please sign in to comment.