orchestrator-core 3.2.3__py3-none-any.whl → 4.0.0rc2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. orchestrator/__init__.py +1 -1
  2. orchestrator/api/api_v1/endpoints/subscription_customer_descriptions.py +0 -2
  3. orchestrator/cli/generator/templates/new_product_migration.j2 +5 -1
  4. orchestrator/cli/migrate_tasks.py +5 -5
  5. orchestrator/cli/migrate_workflows.py +1 -2
  6. orchestrator/db/models.py +3 -1
  7. orchestrator/domain/base.py +4 -24
  8. orchestrator/domain/customer_description.py +0 -4
  9. orchestrator/graphql/mutations/customer_description.py +1 -1
  10. orchestrator/migrations/helpers.py +14 -6
  11. orchestrator/migrations/versions/schema/2025-02-20_68d14db1b8da_make_workflow_description_mandatory.py +33 -0
  12. orchestrator/migrations/versions/schema/2025-05-08_161918133bec_add_is_task_to_workflow.py +28 -0
  13. orchestrator/schedules/validate_subscriptions.py +4 -4
  14. orchestrator/schemas/workflow.py +1 -0
  15. orchestrator/services/celery.py +11 -3
  16. orchestrator/services/processes.py +2 -2
  17. orchestrator/services/subscriptions.py +10 -23
  18. orchestrator/services/workflows.py +8 -4
  19. orchestrator/settings.py +0 -4
  20. orchestrator/targets.py +1 -0
  21. orchestrator/utils/get_subscription_dict.py +0 -4
  22. orchestrator/utils/redis.py +1 -67
  23. orchestrator/workflows/modify_note.py +3 -11
  24. orchestrator/workflows/steps.py +2 -86
  25. orchestrator/workflows/tasks/validate_product_type.py +2 -2
  26. orchestrator/workflows/tasks/validate_products.py +3 -6
  27. orchestrator/workflows/utils.py +3 -23
  28. {orchestrator_core-3.2.3.dist-info → orchestrator_core-4.0.0rc2.dist-info}/METADATA +6 -6
  29. {orchestrator_core-3.2.3.dist-info → orchestrator_core-4.0.0rc2.dist-info}/RECORD +31 -29
  30. {orchestrator_core-3.2.3.dist-info → orchestrator_core-4.0.0rc2.dist-info}/WHEEL +0 -0
  31. {orchestrator_core-3.2.3.dist-info → orchestrator_core-4.0.0rc2.dist-info}/licenses/LICENSE +0 -0
orchestrator/__init__.py CHANGED
@@ -13,7 +13,7 @@
13
13
 
14
14
  """This is the orchestrator workflow engine."""
15
15
 
16
- __version__ = "3.2.3"
16
+ __version__ = "4.0.0rc2"
17
17
 
18
18
  from orchestrator.app import OrchestratorCore
19
19
  from orchestrator.settings import app_settings
@@ -28,7 +28,6 @@ from orchestrator.domain.customer_description import (
28
28
  from orchestrator.schemas import SubscriptionDescriptionBaseSchema, SubscriptionDescriptionSchema
29
29
  from orchestrator.schemas.subscription_descriptions import UpdateSubscriptionDescriptionSchema
30
30
  from orchestrator.utils.errors import StaleDataError
31
- from orchestrator.utils.redis import delete_from_redis
32
31
 
33
32
  router = APIRouter()
34
33
 
@@ -55,7 +54,6 @@ def delete_subscription_customer_descriptions(_id: UUID) -> None:
55
54
  description = db.session.get(SubscriptionCustomerDescriptionTable, _id)
56
55
  if description:
57
56
  delete(SubscriptionCustomerDescriptionTable, _id)
58
- delete_from_redis(description.subscription_id)
59
57
 
60
58
 
61
59
  @router.get("/{_id}", response_model=SubscriptionDescriptionSchema)
@@ -63,24 +63,28 @@ new_workflows = [
63
63
  {
64
64
  "name": "create_{{ product.variable }}",
65
65
  "target": Target.CREATE,
66
+ "is_task": False,
66
67
  "description": "Create {{ product.name }}",
67
68
  "product_type": "{{ product.type }}",
68
69
  },
69
70
  {
70
71
  "name": "modify_{{ product.variable }}",
71
72
  "target": Target.MODIFY,
73
+ "is_task": False,
72
74
  "description": "Modify {{ product.name }}",
73
75
  "product_type": "{{ product.type }}",
74
76
  },
75
77
  {
76
78
  "name": "terminate_{{ product.variable }}",
77
79
  "target": Target.TERMINATE,
80
+ "is_task": False,
78
81
  "description": "Terminate {{ product.name }}",
79
82
  "product_type": "{{ product.type }}",
80
83
  },
81
84
  {
82
85
  "name": "validate_{{ product.variable }}",
83
- "target": Target.SYSTEM,
86
+ "target": Target.VALIDATE,
87
+ "is_task": True,
84
88
  "description": "Validate {{ product.name }}",
85
89
  "product_type": "{{ product.type }}",
86
90
  },
@@ -150,22 +150,22 @@ def create_tasks_migration_wizard() -> tuple[list[dict], list[dict]]:
150
150
  - list of task items to add in the migration
151
151
  - list of task items to delete in the migration
152
152
  """
153
- database_tasks = {
154
- task.name: task for task in list(db.session.scalars(select(WorkflowTable))) if task.target == Target.SYSTEM
155
- }
153
+ database_tasks = {task.name: task for task in list(db.session.scalars(select(WorkflowTable))) if task.is_task}
156
154
  registered_wf_instances = {
157
155
  task: cast(Workflow, get_workflow(task)) for task in orchestrator.workflows.ALL_WORKFLOWS.keys()
158
156
  }
159
157
 
158
+ is_task = [Target.SYSTEM, Target.VALIDATE]
159
+
160
160
  registered_tasks = dict(
161
161
  filter(
162
- lambda task: task[1].target == Target.SYSTEM and task[0] in database_tasks.keys(),
162
+ lambda task: task[1].target in is_task and task[0] in database_tasks.keys(),
163
163
  registered_wf_instances.items(),
164
164
  )
165
165
  )
166
166
  available_tasks = dict(
167
167
  filter(
168
- lambda task: task[1].target == Target.SYSTEM and task[0] not in database_tasks.keys(),
168
+ lambda task: task[1].target in is_task and task[0] not in database_tasks.keys(),
169
169
  registered_wf_instances.items(),
170
170
  )
171
171
  )
@@ -11,7 +11,6 @@ import orchestrator.workflows
11
11
  from orchestrator.cli.helpers.input_helpers import _enumerate_menu_keys, _prompt_user_menu, get_user_input
12
12
  from orchestrator.cli.helpers.print_helpers import COLOR, noqa_print, print_fmt, str_fmt
13
13
  from orchestrator.db import ProductTable, WorkflowTable, db
14
- from orchestrator.targets import Target
15
14
  from orchestrator.workflows import LazyWorkflowInstance, get_workflow
16
15
 
17
16
  # Workflows are registered via migrations with product type. For every product with the given product_type, there will be an entry in products_workflows.
@@ -183,7 +182,7 @@ def create_workflows_migration_wizard() -> tuple[list[dict], list[dict]]:
183
182
  """
184
183
  database_workflows = list(db.session.scalars(select(WorkflowTable)))
185
184
  registered_workflows = orchestrator.workflows.ALL_WORKFLOWS
186
- system_workflow_names = {wf.name for wf in database_workflows if wf.target == Target.SYSTEM}
185
+ system_workflow_names = {wf.name for wf in database_workflows if wf.is_task}
187
186
  registered_non_system_workflows = {k: v for k, v in registered_workflows.items() if k not in system_workflow_names}
188
187
 
189
188
  unregistered_workflows = [wf for wf in database_workflows if wf.name not in registered_workflows.keys()]
orchestrator/db/models.py CHANGED
@@ -400,7 +400,7 @@ class WorkflowTable(BaseModel):
400
400
  workflow_id = mapped_column(UUIDType, server_default=text("uuid_generate_v4()"), primary_key=True)
401
401
  name = mapped_column(String(), nullable=False, unique=True)
402
402
  target = mapped_column(String(), nullable=False)
403
- description = mapped_column(Text(), nullable=True)
403
+ description = mapped_column(Text(), nullable=False)
404
404
  created_at = mapped_column(UtcTimestamp, nullable=False, server_default=text("current_timestamp()"))
405
405
  deleted_at = mapped_column(UtcTimestamp, deferred=True)
406
406
 
@@ -412,6 +412,8 @@ class WorkflowTable(BaseModel):
412
412
  )
413
413
  processes = relationship("ProcessTable", cascade="all, delete-orphan", back_populates="workflow")
414
414
 
415
+ is_task = mapped_column(Boolean, nullable=False, server_default=text("false"))
416
+
415
417
  @staticmethod
416
418
  def select() -> Select:
417
419
  return (
@@ -1294,27 +1294,13 @@ class SubscriptionModel(DomainModel):
1294
1294
  # Some common functions shared by from_other_product and from_subscription
1295
1295
  @classmethod
1296
1296
  def _get_subscription(cls: type[S], subscription_id: UUID | UUIDstr) -> SubscriptionTable | None:
1297
- from orchestrator.settings import app_settings
1298
1297
 
1299
1298
  if not isinstance(subscription_id, UUID | UUIDstr):
1300
1299
  raise TypeError(f"subscription_id is of type {type(subscription_id)} instead of UUID | UUIDstr")
1301
1300
 
1302
- if app_settings.ENABLE_SUBSCRIPTION_MODEL_OPTIMIZATIONS:
1303
- # TODO #900 remove toggle and make this path the default
1304
- loaders = [
1305
- joinedload(SubscriptionTable.product).selectinload(ProductTable.fixed_inputs),
1306
- ]
1307
-
1308
- else:
1309
- loaders = [
1310
- selectinload(SubscriptionTable.instances)
1311
- .joinedload(SubscriptionInstanceTable.product_block)
1312
- .selectinload(ProductBlockTable.resource_types),
1313
- selectinload(SubscriptionTable.instances).selectinload(
1314
- SubscriptionInstanceTable.in_use_by_block_relations
1315
- ),
1316
- selectinload(SubscriptionTable.instances).selectinload(SubscriptionInstanceTable.values),
1317
- ]
1301
+ loaders = [
1302
+ joinedload(SubscriptionTable.product).selectinload(ProductTable.fixed_inputs),
1303
+ ]
1318
1304
 
1319
1305
  return db.session.get(SubscriptionTable, subscription_id, options=loaders)
1320
1306
 
@@ -1394,7 +1380,6 @@ class SubscriptionModel(DomainModel):
1394
1380
  def from_subscription(cls: type[S], subscription_id: UUID | UUIDstr) -> S:
1395
1381
  """Use a subscription_id to return required fields of an existing subscription."""
1396
1382
  from orchestrator.domain.context_cache import get_from_cache, store_in_cache
1397
- from orchestrator.settings import app_settings
1398
1383
 
1399
1384
  if cached_model := get_from_cache(subscription_id):
1400
1385
  return cast(S, cached_model)
@@ -1421,12 +1406,7 @@ class SubscriptionModel(DomainModel):
1421
1406
 
1422
1407
  fixed_inputs = {fi.name: fi.value for fi in subscription.product.fixed_inputs}
1423
1408
 
1424
- instances: dict[str, Any]
1425
- if app_settings.ENABLE_SUBSCRIPTION_MODEL_OPTIMIZATIONS:
1426
- # TODO #900 remove toggle and make this path the default
1427
- instances = cls._load_root_instances(subscription_id)
1428
- else:
1429
- instances = cls._load_instances(subscription.instances, status, match_domain_attr=False)
1409
+ instances = cls._load_root_instances(subscription_id)
1430
1410
 
1431
1411
  try:
1432
1412
  model = cls(
@@ -21,7 +21,6 @@ from sqlalchemy import select
21
21
  from orchestrator.api.models import delete
22
22
  from orchestrator.db import SubscriptionCustomerDescriptionTable, db
23
23
  from orchestrator.utils.errors import StaleDataError
24
- from orchestrator.utils.redis import delete_subscription_from_redis
25
24
  from orchestrator.utils.validate_data_version import validate_data_version
26
25
  from orchestrator.websocket import invalidate_subscription_cache
27
26
 
@@ -38,7 +37,6 @@ def get_customer_description_by_customer_subscription(
38
37
  return db.session.scalars(stmt).one_or_none()
39
38
 
40
39
 
41
- @delete_subscription_from_redis()
42
40
  async def create_subscription_customer_description(
43
41
  customer_id: str, subscription_id: UUID, description: str
44
42
  ) -> SubscriptionCustomerDescriptionTable:
@@ -53,7 +51,6 @@ async def create_subscription_customer_description(
53
51
  return customer_description
54
52
 
55
53
 
56
- @delete_subscription_from_redis()
57
54
  async def update_subscription_customer_description(
58
55
  customer_description: SubscriptionCustomerDescriptionTable,
59
56
  description: str,
@@ -70,7 +67,6 @@ async def update_subscription_customer_description(
70
67
  return customer_description
71
68
 
72
69
 
73
- @delete_subscription_from_redis()
74
70
  async def delete_subscription_customer_description_by_customer_subscription(
75
71
  customer_id: str, subscription_id: UUID
76
72
  ) -> SubscriptionCustomerDescriptionTable | None:
@@ -60,7 +60,7 @@ async def resolve_remove_customer_description(
60
60
  )
61
61
  if not description:
62
62
  return NotFoundError(message="Customer description not found")
63
- return CustomerDescription.from_pydantic(description)
63
+ return CustomerDescription.from_pydantic(description) # type: ignore
64
64
 
65
65
 
66
66
  @strawberry.type(description="Customer subscription description mutations")
@@ -135,18 +135,21 @@ def create_workflow(conn: sa.engine.Connection, workflow: dict) -> None:
135
135
  >>> workflow = {
136
136
  "name": "workflow_name",
137
137
  "target": "SYSTEM",
138
+ "is_task": False,
138
139
  "description": "workflow description",
139
140
  "product_type": "product_type",
140
141
  }
141
142
  >>> create_workflow(conn, workflow)
142
143
  """
144
+ if not workflow.get("is_task", False):
145
+ workflow["is_task"] = False
143
146
 
144
147
  conn.execute(
145
148
  sa.text(
146
149
  """
147
150
  WITH new_workflow AS (
148
- INSERT INTO workflows(name, target, description)
149
- VALUES (:name, :target, :description)
151
+ INSERT INTO workflows(name, target, is_task, description)
152
+ VALUES (:name, :target, :is_task, :description)
150
153
  ON CONFLICT DO NOTHING
151
154
  RETURNING workflow_id)
152
155
  INSERT
@@ -184,8 +187,8 @@ def create_task(conn: sa.engine.Connection, task: dict) -> None:
184
187
  conn.execute(
185
188
  sa.text(
186
189
  """
187
- INSERT INTO workflows(name, target, description)
188
- VALUES (:name, 'SYSTEM', :description)
190
+ INSERT INTO workflows(name, target, is_task, description)
191
+ VALUES (:name, 'SYSTEM', TRUE, :description)
189
192
  ON CONFLICT DO NOTHING
190
193
  RETURNING workflow_id
191
194
  """
@@ -206,6 +209,7 @@ def create_workflows(conn: sa.engine.Connection, new: dict) -> None:
206
209
  "workflow_name": {
207
210
  "workflow_id": "f2702074-3203-454c-b298-6dfa7675423d",
208
211
  "target": "CREATE",
212
+ "is_task": False,
209
213
  "description": "Workflow description",
210
214
  "tag": "ProductBlockName1",
211
215
  "search_phrase": "Search Phrase%",
@@ -214,12 +218,16 @@ def create_workflows(conn: sa.engine.Connection, new: dict) -> None:
214
218
  """
215
219
  for name, workflow in new.items():
216
220
  workflow["name"] = name
221
+
222
+ if not workflow.get("is_task", False):
223
+ workflow["is_task"] = False
224
+
217
225
  conn.execute(
218
226
  sa.text(
219
227
  """
220
228
  WITH new_workflow AS (
221
- INSERT INTO workflows(workflow_id, name, target, description)
222
- VALUES (:workflow_id, :name, :target, :description)
229
+ INSERT INTO workflows(workflow_id, name, target, is_task, description)
230
+ VALUES (:workflow_id, :name, :target, :is_task, :description)
223
231
  RETURNING workflow_id)
224
232
  INSERT
225
233
  INTO products_workflows (product_id, workflow_id)
@@ -0,0 +1,33 @@
1
+ """Make workflow description mandatory.
2
+
3
+ Revision ID: 68d14db1b8da
4
+ Revises: bac6be6f2b4f
5
+ Create Date: 2025-02-20 16:39:34.889953
6
+
7
+ """
8
+
9
+ import sqlalchemy as sa
10
+ from alembic import op
11
+ from structlog import get_logger
12
+
13
+ logger = get_logger(__name__)
14
+
15
+ # revision identifiers, used by Alembic.
16
+ revision = "68d14db1b8da"
17
+ down_revision = "fc5c993a4b4a"
18
+ branch_labels = None
19
+ depends_on = None
20
+
21
+
22
+ def upgrade() -> None:
23
+ try:
24
+ op.alter_column("workflows", "description", existing_type=sa.TEXT(), nullable=False)
25
+ except sa.exc.IntegrityError:
26
+ logger.error(
27
+ "Unable to execute migrations due to missing descriptions in workflow table, please create a migration to backfill this column."
28
+ )
29
+ raise
30
+
31
+
32
+ def downgrade() -> None:
33
+ op.alter_column("workflows", "description", existing_type=sa.TEXT(), nullable=True)
@@ -0,0 +1,28 @@
1
+ """Add is_task to workflow.
2
+
3
+ Revision ID: 161918133bec
4
+ Revises: 68d14db1b8da
5
+ Create Date: 2025-05-08 11:25:51.966410
6
+
7
+ """
8
+
9
+ import sqlalchemy as sa
10
+ from alembic import op
11
+
12
+ # revision identifiers, used by Alembic.
13
+ revision = "161918133bec"
14
+ down_revision = "68d14db1b8da"
15
+ branch_labels = None
16
+ depends_on = None
17
+
18
+
19
+ def upgrade() -> None:
20
+ # ### commands auto generated by Alembic - please adjust! ###
21
+ op.add_column("workflows", sa.Column("is_task", sa.Boolean(), server_default=sa.text("false"), nullable=False))
22
+ # ### end Alembic commands ###
23
+
24
+
25
+ def downgrade() -> None:
26
+ # ### commands auto generated by Alembic - please adjust! ###
27
+ op.drop_column("workflows", "is_task")
28
+ # ### end Alembic commands ###
@@ -22,7 +22,7 @@ from orchestrator.services.subscriptions import (
22
22
  get_subscriptions_on_product_table_in_sync,
23
23
  )
24
24
  from orchestrator.services.workflows import (
25
- get_system_product_workflows_for_subscription,
25
+ get_validation_product_workflows_for_subscription,
26
26
  start_validation_workflow_for_workflows,
27
27
  )
28
28
  from orchestrator.settings import app_settings
@@ -42,9 +42,9 @@ def validate_subscriptions() -> None:
42
42
  subscriptions = get_subscriptions_on_product_table_in_sync()
43
43
 
44
44
  for subscription in subscriptions:
45
- system_product_workflows = get_system_product_workflows_for_subscription(subscription)
45
+ validation_product_workflows = get_validation_product_workflows_for_subscription(subscription)
46
46
 
47
- if not system_product_workflows:
47
+ if not validation_product_workflows:
48
48
  logger.warning(
49
49
  "SubscriptionTable has no validation workflow",
50
50
  subscription=subscription,
@@ -52,4 +52,4 @@ def validate_subscriptions() -> None:
52
52
  )
53
53
  break
54
54
 
55
- start_validation_workflow_for_workflows(subscription=subscription, workflows=system_product_workflows)
55
+ start_validation_workflow_for_workflows(subscription=subscription, workflows=validation_product_workflows)
@@ -24,6 +24,7 @@ from orchestrator.targets import Target
24
24
  class WorkflowBaseSchema(OrchestratorBaseModel):
25
25
  name: str
26
26
  target: Target
27
+ is_task: bool = False
27
28
  description: str | None = None
28
29
  created_at: datetime | None = None
29
30
 
@@ -24,7 +24,7 @@ from orchestrator.api.error_handling import raise_status
24
24
  from orchestrator.db import ProcessTable, db
25
25
  from orchestrator.services.input_state import store_input_state
26
26
  from orchestrator.services.processes import create_process, delete_process
27
- from orchestrator.targets import Target
27
+ from orchestrator.services.workflows import get_workflow_by_name
28
28
  from orchestrator.workflows import get_workflow
29
29
  from pydantic_forms.types import State
30
30
 
@@ -51,7 +51,11 @@ def _celery_start_process(
51
51
  if not workflow:
52
52
  raise_status(HTTPStatus.NOT_FOUND, "Workflow does not exist")
53
53
 
54
- task_name = NEW_TASK if workflow.target == Target.SYSTEM else NEW_WORKFLOW
54
+ wf_table = get_workflow_by_name(workflow.name)
55
+ if not wf_table:
56
+ raise_status(HTTPStatus.NOT_FOUND, "Workflow in Database does not exist")
57
+
58
+ task_name = NEW_TASK if wf_table.is_task else NEW_WORKFLOW
55
59
  trigger_task = get_celery_task(task_name)
56
60
  pstat = create_process(workflow_key, user_inputs, user)
57
61
  try:
@@ -80,7 +84,11 @@ def _celery_resume_process(
80
84
  last_process_status = process.last_status
81
85
  workflow = pstat.workflow
82
86
 
83
- task_name = RESUME_TASK if workflow.target == Target.SYSTEM else RESUME_WORKFLOW
87
+ wf_table = get_workflow_by_name(workflow.name)
88
+ if not workflow or not wf_table:
89
+ raise_status(HTTPStatus.NOT_FOUND, "Workflow does not exist")
90
+
91
+ task_name = RESUME_TASK if wf_table.is_task else RESUME_WORKFLOW
84
92
  trigger_task = get_celery_task(task_name)
85
93
 
86
94
  user_inputs = user_inputs or [{}]
@@ -34,7 +34,6 @@ from orchestrator.services.input_state import store_input_state
34
34
  from orchestrator.services.settings import get_engine_settings_for_update
35
35
  from orchestrator.services.workflows import get_workflow_by_name
36
36
  from orchestrator.settings import ExecutorType, app_settings
37
- from orchestrator.targets import Target
38
37
  from orchestrator.types import BroadcastFunc
39
38
  from orchestrator.utils.datetime import nowtz
40
39
  from orchestrator.utils.errors import error_state_to_dict
@@ -108,7 +107,7 @@ def _db_create_process(stat: ProcessStat) -> None:
108
107
  workflow_id=wf_table.workflow_id,
109
108
  last_status=ProcessStatus.CREATED,
110
109
  created_by=stat.current_user,
111
- is_task=stat.workflow.target == Target.SYSTEM,
110
+ is_task=wf_table.is_task,
112
111
  )
113
112
  db.session.add(p)
114
113
  db.session.commit()
@@ -441,6 +440,7 @@ def create_process(
441
440
  }
442
441
 
443
442
  try:
443
+
444
444
  state = post_form(workflow.initial_input_form, initial_state, user_inputs)
445
445
  except FormValidationError:
446
446
  logger.exception("Validation errors", user_inputs=user_inputs)
@@ -505,6 +505,7 @@ TARGET_DEFAULT_USABLE_MAP: dict[Target, list[str]] = {
505
505
  Target.MODIFY: ["active"],
506
506
  Target.TERMINATE: ["active", "provisioning"],
507
507
  Target.SYSTEM: ["active"],
508
+ Target.VALIDATE: ["active"],
508
509
  }
509
510
 
510
511
  WF_USABLE_MAP: dict[str, list[str]] = {}
@@ -530,6 +531,7 @@ def subscription_workflows(subscription: SubscriptionTable) -> dict[str, Any]:
530
531
  ... "modify": [],
531
532
  ... "terminate": [],
532
533
  ... "system": [],
534
+ ... "validate": [],
533
535
  ... }
534
536
 
535
537
  """
@@ -549,9 +551,10 @@ def subscription_workflows(subscription: SubscriptionTable) -> dict[str, Any]:
549
551
  "modify": [],
550
552
  "terminate": [],
551
553
  "system": [],
554
+ "validate": [],
552
555
  }
553
556
  for workflow in subscription.product.workflows:
554
- if workflow.name in WF_USABLE_WHILE_OUT_OF_SYNC or workflow.target == Target.SYSTEM:
557
+ if workflow.name in WF_USABLE_WHILE_OUT_OF_SYNC or workflow.is_task:
555
558
  # validations and modify note are also possible with: not in sync or locked relations
556
559
  workflow_json = {"name": workflow.name, "description": workflow.description}
557
560
  else:
@@ -605,7 +608,6 @@ def build_domain_model(subscription_model: SubscriptionModel) -> dict:
605
608
 
606
609
  def build_extended_domain_model(subscription_model: SubscriptionModel) -> dict:
607
610
  """Create a subscription dict from the SubscriptionModel with additional keys."""
608
- from orchestrator.settings import app_settings
609
611
 
610
612
  stmt = select(SubscriptionCustomerDescriptionTable).where(
611
613
  SubscriptionCustomerDescriptionTable.subscription_id == subscription_model.subscription_id
@@ -615,27 +617,12 @@ def build_extended_domain_model(subscription_model: SubscriptionModel) -> dict:
615
617
  with cache_subscription_models():
616
618
  subscription = subscription_model.model_dump()
617
619
 
618
- def inject_in_use_by_ids(path_to_block: str) -> None:
619
- if not (in_use_by_subs := getattr_in(subscription_model, f"{path_to_block}.in_use_by")):
620
- return
621
-
622
- in_use_by_ids = [obj.in_use_by_id for obj in in_use_by_subs.col]
623
- in_use_by_relations = [convert_to_in_use_by_relation(instance) for instance in in_use_by_subs]
624
- update_in(subscription, f"{path_to_block}.in_use_by_ids", in_use_by_ids)
625
- update_in(subscription, f"{path_to_block}.in_use_by_relations", in_use_by_relations)
626
-
627
- if app_settings.ENABLE_SUBSCRIPTION_MODEL_OPTIMIZATIONS:
628
- # TODO #900 remove toggle and make this path the default
629
- # query all subscription instances and inject the in_use_by_ids/in_use_by_relations into the subscription dict.
630
- instance_to_in_use_by = {
631
- instance.subscription_instance_id: instance.in_use_by
632
- for instance in eagerload_all_subscription_instances_only_inuseby(subscription_model.subscription_id)
633
- }
634
- inject_in_use_by_ids_v2(subscription, instance_to_in_use_by)
635
- else:
636
- # find all product blocks, check if they have in_use_by and inject the in_use_by_ids into the subscription dict.
637
- for path in product_block_paths(subscription):
638
- inject_in_use_by_ids(path)
620
+ # query all subscription instances and inject the in_use_by_ids/in_use_by_relations into the subscription dict.
621
+ instance_to_in_use_by = {
622
+ instance.subscription_instance_id: instance.in_use_by
623
+ for instance in eagerload_all_subscription_instances_only_inuseby(subscription_model.subscription_id)
624
+ }
625
+ inject_in_use_by_ids_v2(subscription, instance_to_in_use_by)
639
626
 
640
627
  subscription["customer_descriptions"] = customer_descriptions
641
628
 
@@ -39,6 +39,7 @@ def _to_workflow_schema(workflow: WorkflowTable, include_steps: bool = False) ->
39
39
  workflow_id=workflow.workflow_id,
40
40
  name=workflow.name,
41
41
  target=workflow.target,
42
+ is_task=workflow.is_task,
42
43
  description=workflow.description,
43
44
  created_at=workflow.created_at,
44
45
  **extra_kwargs,
@@ -63,10 +64,10 @@ def get_workflow_by_name(workflow_name: str) -> WorkflowTable | None:
63
64
  return db.session.scalar(select(WorkflowTable).where(WorkflowTable.name == workflow_name))
64
65
 
65
66
 
66
- def get_system_product_workflows_for_subscription(
67
+ def get_validation_product_workflows_for_subscription(
67
68
  subscription: SubscriptionTable,
68
69
  ) -> list:
69
- return [workflow.name for workflow in subscription.product.workflows if workflow.target == Target.SYSTEM]
70
+ return [workflow.name for workflow in subscription.product.workflows if workflow.target == Target.VALIDATE]
70
71
 
71
72
 
72
73
  def start_validation_workflow_for_workflows(
@@ -78,9 +79,12 @@ def start_validation_workflow_for_workflows(
78
79
  result = []
79
80
 
80
81
  for workflow_name in workflows:
81
- default = TARGET_DEFAULT_USABLE_MAP[Target.SYSTEM]
82
- usable_when = WF_USABLE_MAP.get(workflow_name, default)
82
+ target_system = TARGET_DEFAULT_USABLE_MAP[Target.SYSTEM]
83
+ system_usable_when = WF_USABLE_MAP.get(workflow_name, target_system)
84
+ target_validate = TARGET_DEFAULT_USABLE_MAP[Target.VALIDATE]
85
+ validate_usable_when = WF_USABLE_MAP.get(workflow_name, target_validate)
83
86
 
87
+ usable_when = system_usable_when + validate_usable_when
84
88
  if subscription.status in usable_when and (
85
89
  product_type_filter is None or subscription.product.product_type == product_type_filter
86
90
  ):
orchestrator/settings.py CHANGED
@@ -55,7 +55,6 @@ class AppSettings(BaseSettings):
55
55
  MAIL_PORT: int = 25
56
56
  MAIL_STARTTLS: bool = False
57
57
  CACHE_URI: RedisDsn = "redis://localhost:6379/0" # type: ignore
58
- CACHE_DOMAIN_MODELS: bool = False
59
58
  CACHE_HMAC_SECRET: str | None = None # HMAC signing key, used when pickling results in the cache
60
59
  REDIS_RETRY_COUNT: NonNegativeInt = Field(
61
60
  2, description="Number of retries for redis connection errors/timeouts, 0 to disable"
@@ -87,9 +86,6 @@ class AppSettings(BaseSettings):
87
86
  ENABLE_GRAPHQL_STATS_EXTENSION: bool = False
88
87
  VALIDATE_OUT_OF_SYNC_SUBSCRIPTIONS: bool = False
89
88
  FILTER_BY_MODE: Literal["partial", "exact"] = "exact"
90
- ENABLE_SUBSCRIPTION_MODEL_OPTIMIZATIONS: bool = (
91
- True # True=ignore cache + optimized DB queries; False=use cache + unoptimized DB queries. Remove in #900
92
- )
93
89
 
94
90
 
95
91
  app_settings = AppSettings()
orchestrator/targets.py CHANGED
@@ -22,3 +22,4 @@ class Target(strEnum):
22
22
  MODIFY = "MODIFY"
23
23
  TERMINATE = "TERMINATE"
24
24
  SYSTEM = "SYSTEM"
25
+ VALIDATE = "VALIDATE"
@@ -2,15 +2,11 @@ from uuid import UUID
2
2
 
3
3
  from orchestrator.domain.base import SubscriptionModel
4
4
  from orchestrator.services.subscriptions import _generate_etag, build_domain_model, build_extended_domain_model
5
- from orchestrator.utils.redis import from_redis
6
5
 
7
6
 
8
7
  async def get_subscription_dict(subscription_id: UUID, inject_inuseby: bool = True) -> tuple[dict, str]:
9
8
  """Helper function to get subscription dict by uuid from db or cache."""
10
9
 
11
- if cached_model := from_redis(subscription_id):
12
- return cached_model # type: ignore
13
-
14
10
  subscription_model = SubscriptionModel.from_subscription(subscription_id)
15
11
 
16
12
  if not inject_inuseby:
@@ -10,11 +10,9 @@
10
10
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
11
  # See the License for the specific language governing permissions and
12
12
  # limitations under the License.
13
- import functools
14
13
  from collections.abc import AsyncGenerator
15
14
  from contextlib import asynccontextmanager
16
- from os import getenv
17
- from typing import Any, Callable
15
+ from typing import Any
18
16
  from uuid import UUID
19
17
 
20
18
  from anyio import CancelScope, get_cancelled_exc_class
@@ -22,9 +20,7 @@ from redis.asyncio import Redis as AIORedis
22
20
  from redis.asyncio.client import Pipeline, PubSub
23
21
  from structlog import get_logger
24
22
 
25
- from orchestrator.services.subscriptions import _generate_etag
26
23
  from orchestrator.settings import app_settings
27
- from orchestrator.utils.json import PY_JSON_TYPES, json_dumps, json_loads
28
24
  from orchestrator.utils.redis_client import (
29
25
  create_redis_asyncio_client,
30
26
  create_redis_client,
@@ -37,52 +33,6 @@ cache = create_redis_client(app_settings.CACHE_URI)
37
33
  ONE_WEEK = 3600 * 24 * 7
38
34
 
39
35
 
40
- def caching_models_enabled() -> bool:
41
- return getenv("AIOCACHE_DISABLE", "0") == "0" and app_settings.CACHE_DOMAIN_MODELS
42
-
43
-
44
- def to_redis(subscription: dict[str, Any]) -> str | None:
45
- if caching_models_enabled():
46
- logger.info("Setting cache for subscription", subscription=subscription["subscription_id"])
47
- etag = _generate_etag(subscription)
48
- cache.set(f"orchestrator:domain:{subscription['subscription_id']}", json_dumps(subscription), ex=ONE_WEEK)
49
- cache.set(f"orchestrator:domain:etag:{subscription['subscription_id']}", etag, ex=ONE_WEEK)
50
- return etag
51
-
52
- logger.warning("Caching disabled, not caching subscription", subscription=subscription["subscription_id"])
53
- return None
54
-
55
-
56
- def from_redis(subscription_id: UUID) -> tuple[PY_JSON_TYPES, str] | None:
57
- log = logger.bind(subscription_id=subscription_id)
58
-
59
- if app_settings.ENABLE_SUBSCRIPTION_MODEL_OPTIMIZATIONS:
60
- # TODO #900 remove toggle and remove usage of this function in get_subscription_dict
61
- log.info("Using SubscriptionModel optimization, not loading subscription from redis cache")
62
- return None
63
-
64
- if caching_models_enabled():
65
- log.debug("Try to retrieve subscription from cache")
66
- obj = cache.get(f"orchestrator:domain:{subscription_id}")
67
- etag = cache.get(f"orchestrator:domain:etag:{subscription_id}")
68
- if obj and etag:
69
- log.info("Retrieved subscription from cache")
70
- return json_loads(obj), etag.decode("utf-8")
71
- log.info("Subscription not found in cache")
72
- return None
73
- log.warning("Caching disabled, not loading subscription")
74
- return None
75
-
76
-
77
- def delete_from_redis(subscription_id: UUID) -> None:
78
- if caching_models_enabled():
79
- logger.info("Deleting subscription object from cache", subscription_id=subscription_id)
80
- cache.delete(f"orchestrator:domain:{subscription_id}")
81
- cache.delete(f"orchestrator:domain:etag:{subscription_id}")
82
- else:
83
- logger.warning("Caching disabled, not deleting subscription", subscription=subscription_id)
84
-
85
-
86
36
  def default_get_subscription_id(data: Any) -> UUID:
87
37
  if hasattr(data, "subscription_id"):
88
38
  return data.subscription_id
@@ -91,22 +41,6 @@ def default_get_subscription_id(data: Any) -> UUID:
91
41
  return data
92
42
 
93
43
 
94
- def delete_subscription_from_redis(
95
- extract_fn: Callable[..., UUID] = default_get_subscription_id,
96
- ) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
97
- def _delete_subscription_from_redis(func: Callable[..., Any]) -> Callable[..., Any]:
98
- @functools.wraps(func)
99
- async def wrapper(*args: tuple, **kwargs: dict[str, Any]) -> Any:
100
- data = await func(*args, **kwargs)
101
- key = extract_fn(data)
102
- delete_from_redis(key)
103
- return data
104
-
105
- return wrapper
106
-
107
- return _delete_subscription_from_redis
108
-
109
-
110
44
  async def delete_keys_matching_pattern(_cache: AIORedis, pattern: str, chunksize: int = 5000) -> int:
111
45
  """Delete all keys matching the given pattern.
112
46
 
@@ -13,11 +13,10 @@
13
13
  from orchestrator.db import db
14
14
  from orchestrator.forms import SubmitFormPage
15
15
  from orchestrator.services import subscriptions
16
- from orchestrator.settings import app_settings
17
16
  from orchestrator.targets import Target
18
17
  from orchestrator.utils.json import to_serializable
19
- from orchestrator.workflow import StepList, conditional, done, init, step, workflow
20
- from orchestrator.workflows.steps import cache_domain_models, store_process_subscription
18
+ from orchestrator.workflow import StepList, done, init, step, workflow
19
+ from orchestrator.workflows.steps import store_process_subscription
21
20
  from orchestrator.workflows.utils import wrap_modify_initial_input_form
22
21
  from pydantic_forms.types import FormGenerator, State, UUIDstr
23
22
  from pydantic_forms.validators import LongText
@@ -54,11 +53,4 @@ def store_subscription_note(subscription_id: UUIDstr, note: str) -> State:
54
53
 
55
54
  @workflow("Modify Note", initial_input_form=wrap_modify_initial_input_form(initial_input_form), target=Target.MODIFY)
56
55
  def modify_note() -> StepList:
57
- push_subscriptions = conditional(lambda _: app_settings.CACHE_DOMAIN_MODELS)
58
- return (
59
- init
60
- >> store_process_subscription(Target.MODIFY)
61
- >> store_subscription_note
62
- >> push_subscriptions(cache_domain_models)
63
- >> done
64
- )
56
+ return init >> store_process_subscription(Target.MODIFY) >> store_subscription_note >> done
@@ -11,22 +11,18 @@
11
11
  # See the License for the specific language governing permissions and
12
12
  # limitations under the License.
13
13
  from copy import deepcopy
14
- from typing import Any
15
- from uuid import UUID
16
14
 
17
15
  import structlog
18
16
  from pydantic import ValidationError
19
17
 
20
18
  from orchestrator.db import db
21
19
  from orchestrator.db.models import ProcessSubscriptionTable
22
- from orchestrator.domain.base import ProductBlockModel, SubscriptionModel
20
+ from orchestrator.domain.base import SubscriptionModel
23
21
  from orchestrator.services.settings import reset_search_index
24
- from orchestrator.services.subscriptions import build_extended_domain_model, get_subscription
22
+ from orchestrator.services.subscriptions import get_subscription
25
23
  from orchestrator.targets import Target
26
24
  from orchestrator.types import SubscriptionLifecycle
27
25
  from orchestrator.utils.json import to_serializable
28
- from orchestrator.utils.redis import delete_from_redis, to_redis
29
- from orchestrator.websocket import sync_invalidate_subscription_cache
30
26
  from orchestrator.workflow import Step, step
31
27
  from pydantic_forms.types import State, UUIDstr
32
28
 
@@ -138,86 +134,6 @@ def set_status(status: SubscriptionLifecycle) -> Step:
138
134
  return _set_status
139
135
 
140
136
 
141
- @step("Remove domain model from cache")
142
- def remove_domain_model_from_cache(
143
- workflow_name: str, subscription: SubscriptionModel | None = None, subscription_id: UUID | None = None
144
- ) -> State:
145
- """Remove the domain model from the cache if it exists.
146
-
147
- Args:
148
- workflow_name: The workflow name
149
- subscription: Subscription Model
150
- subscription_id: The subscription id
151
-
152
- Returns:
153
- State
154
-
155
- """
156
-
157
- if not (subscription or subscription_id):
158
- logger.warning("No subscription found in this workflow", workflow_name=workflow_name)
159
- return {"deleted_subscription_id": None}
160
- if subscription:
161
- delete_from_redis(subscription.subscription_id)
162
- elif subscription_id:
163
- delete_from_redis(subscription_id)
164
-
165
- return {"deleted_subscription_id": subscription_id or subscription.subscription_id} # type: ignore[union-attr]
166
-
167
-
168
- @step("Cache Subscription and related subscriptions")
169
- def cache_domain_models(workflow_name: str, subscription: SubscriptionModel | None = None) -> State: # noqa: C901
170
- """Attempt to cache all Subscriptions once they have been touched once.
171
-
172
- Args:
173
- workflow_name: The Workflow Name
174
- subscription: The Subscription if it exists.
175
-
176
- Returns:
177
- Returns State.
178
-
179
- """
180
- cached_subscription_ids: set[UUID] = set()
181
- if not subscription:
182
- logger.warning("No subscription found in this workflow", workflow_name=workflow_name)
183
- return {"cached_subscription_ids": cached_subscription_ids}
184
-
185
- def _cache_other_subscriptions(product_block: ProductBlockModel) -> None:
186
- for field in product_block.model_fields:
187
- # subscription_instance is a ProductBlockModel or an arbitrary type
188
- subscription_instance: ProductBlockModel | Any = getattr(product_block, field)
189
- # If subscription_instance is a list, we need to step into it and loop.
190
- if isinstance(subscription_instance, list):
191
- for item in subscription_instance:
192
- if isinstance(item, ProductBlockModel):
193
- _cache_other_subscriptions(item)
194
-
195
- # If subscription_instance is a ProductBlockModel check the owner_subscription_id to decide the cache
196
- elif isinstance(subscription_instance, ProductBlockModel):
197
- _cache_other_subscriptions(subscription_instance)
198
- if not subscription_instance.owner_subscription_id == subscription.subscription_id:
199
- cached_subscription_ids.add(subscription_instance.owner_subscription_id)
200
-
201
- for field in subscription.model_fields:
202
- # There always is a single Root Product Block, it cannot be a list, so no need to check.
203
- instance: ProductBlockModel | Any = getattr(subscription, field)
204
- if isinstance(instance, ProductBlockModel):
205
- _cache_other_subscriptions(instance)
206
-
207
- # Cache all the sub subscriptions
208
- for subscription_id in cached_subscription_ids:
209
- subscription_model = SubscriptionModel.from_subscription(subscription_id)
210
- to_redis(build_extended_domain_model(subscription_model))
211
- sync_invalidate_subscription_cache(subscription.subscription_id, invalidate_all=False)
212
-
213
- # Cache the main subscription
214
- to_redis(build_extended_domain_model(subscription))
215
- cached_subscription_ids.add(subscription.subscription_id)
216
- sync_invalidate_subscription_cache(subscription.subscription_id)
217
-
218
- return {"cached_subscription_ids": cached_subscription_ids}
219
-
220
-
221
137
  @step("Refresh subscription search index")
222
138
  def refresh_subscription_search_index() -> State:
223
139
  try:
@@ -22,7 +22,7 @@ from orchestrator.services.subscriptions import (
22
22
  get_subscriptions_on_product_table_in_sync,
23
23
  )
24
24
  from orchestrator.services.workflows import (
25
- get_system_product_workflows_for_subscription,
25
+ get_validation_product_workflows_for_subscription,
26
26
  start_validation_workflow_for_workflows,
27
27
  )
28
28
  from orchestrator.targets import Target
@@ -62,7 +62,7 @@ def validate_product_type(product_type: str) -> State:
62
62
  subscriptions = get_subscriptions_on_product_table_in_sync()
63
63
 
64
64
  for subscription in subscriptions:
65
- system_product_workflows = get_system_product_workflows_for_subscription(
65
+ system_product_workflows = get_validation_product_workflows_for_subscription(
66
66
  subscription=subscription,
67
67
  )
68
68
 
@@ -105,13 +105,10 @@ def check_that_products_have_create_modify_and_terminate_workflows() -> State:
105
105
  product_data = get_products(filters=[ProductTable.status == "active"])
106
106
 
107
107
  workflows_not_complete: list = []
108
+ targets = ["CREATE", "TERMINATE", "MODIFY", "VALIDATE"]
108
109
  for product in product_data:
109
- workflows = {
110
- c.target
111
- for c in product.workflows
112
- if c.target in ["CREATE", "TERMINATE", "MODIFY", "SYSTEM"] and c.name != "modify_note"
113
- }
114
- if len(workflows) < 4:
110
+ workflows = {c.target for c in product.workflows if c.target in targets and c.name != "modify_note"}
111
+ if len(workflows) < len(targets):
115
112
  workflows_not_complete.append(product.name)
116
113
 
117
114
  # Do not raise an error but only report it in the `State` to allow exceptions.
@@ -24,16 +24,13 @@ from oauth2_lib.fastapi import OIDCUserModel
24
24
  from orchestrator.db import ProductTable, SubscriptionTable, db
25
25
  from orchestrator.forms.validators import ProductId
26
26
  from orchestrator.services import subscriptions
27
- from orchestrator.settings import app_settings
28
27
  from orchestrator.targets import Target
29
28
  from orchestrator.types import SubscriptionLifecycle
30
29
  from orchestrator.utils.errors import StaleDataError
31
- from orchestrator.utils.redis import caching_models_enabled
32
30
  from orchestrator.utils.state import form_inject_args
33
31
  from orchestrator.utils.validate_data_version import validate_data_version
34
- from orchestrator.workflow import Step, StepList, Workflow, begin, conditional, done, init, make_workflow, step
32
+ from orchestrator.workflow import Step, StepList, Workflow, begin, done, init, make_workflow, step
35
33
  from orchestrator.workflows.steps import (
36
- cache_domain_models,
37
34
  refresh_subscription_search_index,
38
35
  resync,
39
36
  set_status,
@@ -198,8 +195,6 @@ modify_initial_input_form_generator = None
198
195
 
199
196
  validate_initial_input_form_generator = wrap_modify_initial_input_form(modify_initial_input_form_generator)
200
197
 
201
- push_domain_models = conditional(lambda _: caching_models_enabled())
202
-
203
198
 
204
199
  def create_workflow(
205
200
  description: str,
@@ -228,7 +223,6 @@ def create_workflow(
228
223
  >> (additional_steps or StepList())
229
224
  >> set_status(status)
230
225
  >> resync
231
- >> push_domain_models(cache_domain_models)
232
226
  >> refresh_subscription_search_index
233
227
  >> done
234
228
  )
@@ -270,11 +264,9 @@ def modify_workflow(
270
264
  init
271
265
  >> store_process_subscription(Target.MODIFY)
272
266
  >> unsync
273
- >> push_domain_models(cache_domain_models)
274
267
  >> f()
275
268
  >> (additional_steps or StepList())
276
269
  >> resync
277
- >> push_domain_models(cache_domain_models)
278
270
  >> refresh_subscription_search_index
279
271
  >> done
280
272
  )
@@ -316,12 +308,10 @@ def terminate_workflow(
316
308
  init
317
309
  >> store_process_subscription(Target.TERMINATE)
318
310
  >> unsync
319
- >> push_domain_models(cache_domain_models)
320
311
  >> f()
321
312
  >> (additional_steps or StepList())
322
313
  >> set_status(SubscriptionLifecycle.TERMINATED)
323
314
  >> resync
324
- >> push_domain_models(cache_domain_models)
325
315
  >> refresh_subscription_search_index
326
316
  >> done
327
317
  )
@@ -352,19 +342,9 @@ def validate_workflow(description: str) -> Callable[[Callable[[], StepList]], Wo
352
342
  """
353
343
 
354
344
  def _validate_workflow(f: Callable[[], StepList]) -> Workflow:
355
- push_subscriptions = conditional(lambda _: app_settings.CACHE_DOMAIN_MODELS)
356
- steplist = (
357
- init
358
- >> store_process_subscription(Target.SYSTEM)
359
- >> unsync_unchecked
360
- >> push_subscriptions(cache_domain_models)
361
- >> f()
362
- >> resync
363
- >> push_subscriptions(cache_domain_models)
364
- >> done
365
- )
345
+ steplist = init >> store_process_subscription(Target.SYSTEM) >> unsync_unchecked >> f() >> resync >> done
366
346
 
367
- return make_workflow(f, description, validate_initial_input_form_generator, Target.SYSTEM, steplist)
347
+ return make_workflow(f, description, validate_initial_input_form_generator, Target.VALIDATE, steplist)
368
348
 
369
349
  return _validate_workflow
370
350
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: orchestrator-core
3
- Version: 3.2.3
3
+ Version: 4.0.0rc2
4
4
  Summary: This is the orchestrator workflow engine.
5
5
  Requires-Python: >=3.11,<3.14
6
6
  Classifier: Intended Audience :: Information Technology
@@ -35,24 +35,24 @@ Requires-Dist: deprecated
35
35
  Requires-Dist: deepmerge==2.0
36
36
  Requires-Dist: fastapi~=0.115.2
37
37
  Requires-Dist: fastapi-etag==0.4.0
38
- Requires-Dist: more-itertools~=10.6.0
38
+ Requires-Dist: more-itertools~=10.7.0
39
39
  Requires-Dist: itsdangerous
40
40
  Requires-Dist: Jinja2==3.1.6
41
- Requires-Dist: orjson==3.10.16
41
+ Requires-Dist: orjson==3.10.18
42
42
  Requires-Dist: psycopg2-binary==2.9.10
43
43
  Requires-Dist: pydantic[email]~=2.8.2
44
- Requires-Dist: pydantic-settings~=2.8.0
44
+ Requires-Dist: pydantic-settings~=2.9.1
45
45
  Requires-Dist: python-dateutil==2.8.2
46
46
  Requires-Dist: python-rapidjson>=1.18,<1.21
47
47
  Requires-Dist: pytz==2025.2
48
48
  Requires-Dist: redis==5.1.1
49
49
  Requires-Dist: schedule==1.1.0
50
50
  Requires-Dist: semver==3.0.4
51
- Requires-Dist: sentry-sdk[fastapi]~=2.25.1
51
+ Requires-Dist: sentry-sdk[fastapi]~=2.27.0
52
52
  Requires-Dist: SQLAlchemy==2.0.40
53
53
  Requires-Dist: SQLAlchemy-Utils==0.41.2
54
54
  Requires-Dist: structlog
55
- Requires-Dist: typer==0.15.2
55
+ Requires-Dist: typer==0.15.3
56
56
  Requires-Dist: uvicorn[standard]~=0.34.0
57
57
  Requires-Dist: nwa-stdlib~=1.9.0
58
58
  Requires-Dist: oauth2-lib~=2.4.0
@@ -1,11 +1,11 @@
1
- orchestrator/__init__.py,sha256=SAG1EbiZw8m_0wbefpfPXZ_rYgoIoLssZr_HdgxQShI,1063
1
+ orchestrator/__init__.py,sha256=_4qDYGxoCurHceKtJMPSdPiXXCzkD0XpCJiQy6gBP5g,1066
2
2
  orchestrator/app.py,sha256=VN54_Zsx5x_3ym8aFadATg67a4J5lv8H-pxnPlR3RkM,11668
3
3
  orchestrator/exception_handlers.py,sha256=UsW3dw8q0QQlNLcV359bIotah8DYjMsj2Ts1LfX4ClY,1268
4
4
  orchestrator/log_config.py,sha256=1tPRX5q65e57a6a_zEii_PFK8SzWT0mnA5w2sKg4hh8,1853
5
5
  orchestrator/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  orchestrator/security.py,sha256=iXFxGxab54aav7oHEKLAVkTgrQMJGHy6IYLojEnD7gI,2422
7
- orchestrator/settings.py,sha256=MTG7gnmbQYwUjAnhvX4Y3MEqmX8vIpAEC0lhDafa1zw,4039
8
- orchestrator/targets.py,sha256=ykjTGK7CozFaltNaxQcK90P4Cc1Qvf-W8dFztxtZhRQ,776
7
+ orchestrator/settings.py,sha256=21BT1SviPllSL4-M1Qc-8zdgF_Sh7p79WC8sR_AmTmE,3826
8
+ orchestrator/targets.py,sha256=WizBgnp8hWX9YLFUIju7ewSubiwQqinCvyiYNcXHbHI,802
9
9
  orchestrator/types.py,sha256=qzs7xx5AYRmKbpYRyJJP3wuDb0W0bcAzefCN0RWLAco,15459
10
10
  orchestrator/version.py,sha256=b58e08lxs47wUNXv0jXFO_ykpksmytuzEXD4La4W-NQ,1366
11
11
  orchestrator/workflow.py,sha256=T_3Z1PrlF70C16ehAthKRF1hd3jpwV-MREDVxSLfxOw,44063
@@ -22,7 +22,7 @@ orchestrator/api/api_v1/endpoints/product_blocks.py,sha256=kZ6ywIOsS_S2qGq7RvZ4K
22
22
  orchestrator/api/api_v1/endpoints/products.py,sha256=BfFtwu9dZXEQbtKxYj9icc73GKGvAGMR5ytyf41nQlQ,3081
23
23
  orchestrator/api/api_v1/endpoints/resource_types.py,sha256=gGyuaDyOD0TAVoeFGaGmjDGnQ8eQQArOxKrrk4MaDzA,2145
24
24
  orchestrator/api/api_v1/endpoints/settings.py,sha256=QPBwrRGqBlYHNRnADKQ5hpj74X2DB5lROu8KwVnyN_0,6226
25
- orchestrator/api/api_v1/endpoints/subscription_customer_descriptions.py,sha256=Elu4DVJoNtUFq_b3pG1Ws8StrUIo_jTViff2TJqe6ZU,3398
25
+ orchestrator/api/api_v1/endpoints/subscription_customer_descriptions.py,sha256=1_6LtgQleoq3M6z_W-Qz__Bj3OFUweoPrUqHMwSH6AM,3288
26
26
  orchestrator/api/api_v1/endpoints/subscriptions.py,sha256=EwkWBztI9xSMPkol49SM5csECthyu7HC38AhuW7pWUE,8724
27
27
  orchestrator/api/api_v1/endpoints/translations.py,sha256=dIWh_fCnZZUxJoGiNeJ49DK_xpf75IpR_0EIMSvzIvY,963
28
28
  orchestrator/api/api_v1/endpoints/user.py,sha256=RyI32EXVu6I-IxWjz0XB5zQWzzLL60zKXLgLqLH02xU,1827
@@ -33,8 +33,8 @@ orchestrator/cli/database.py,sha256=qig-WqUtiDITz4KePxzcqZtC0FPTWRs_3l19qQpqIGA,
33
33
  orchestrator/cli/generate.py,sha256=SBaYfRijXPF9r3VxarPKTiDzDcB6GBMMQvecQIb_ZLQ,7377
34
34
  orchestrator/cli/main.py,sha256=GC_kxa9OZ-Y0ig_klfWc6ysOQuPVTUmTmDRj3m8cJHA,983
35
35
  orchestrator/cli/migrate_domain_models.py,sha256=OhjNuIheytgShpMYCZ18leNUzk17ExhtkCqx7Ww03R8,20371
36
- orchestrator/cli/migrate_tasks.py,sha256=Fmcwuv8rI1Z_qEiL_kmOWCUq7rNIgnlntUc37phadN8,7249
37
- orchestrator/cli/migrate_workflows.py,sha256=-_nsKUcVa14-Ug3aSppU9yk-oWlK411SX33WqzD1p4M,8979
36
+ orchestrator/cli/migrate_tasks.py,sha256=bju8XColjSZD0v3rS4kl-24dLr8En_H4-6enBmqd494,7255
37
+ orchestrator/cli/migrate_workflows.py,sha256=nxUpx0vgEIc_8aJrjAyrw3E9Dt8JmaamTts8oiQ4vHY,8923
38
38
  orchestrator/cli/migration_helpers.py,sha256=C5tpkP5WEBr7G9S-1k1hgSI8ili6xd9Z5ygc9notaK0,4110
39
39
  orchestrator/cli/scheduler.py,sha256=iCKBWYUwQIYTDqKQ9rMVvs2sNiAzE-J2SkV170TPP2g,1896
40
40
  orchestrator/cli/domain_gen_helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -83,7 +83,7 @@ orchestrator/cli/generator/templates/lazy_workflow_instance.j2,sha256=BYB6LwE19O
83
83
  orchestrator/cli/generator/templates/list_definitions.j2,sha256=XS-F5XXy4HOdsZ-xahprwxfhU0UaiF4VeopkIxfoado,277
84
84
  orchestrator/cli/generator/templates/macros.j2,sha256=fEODRym4gLfL40w9OelXFMDtAjtzmN5nccWDk86oTNI,904
85
85
  orchestrator/cli/generator/templates/modify_product.j2,sha256=M1WDMmPwfXpUcuZUa-H5GnlKAr1t_xCQliLa66FI-YU,5057
86
- orchestrator/cli/generator/templates/new_product_migration.j2,sha256=d67I9haNkKdzWzNT0f1S6-UhG86fOWEohF0Al7NmXmA,3023
86
+ orchestrator/cli/generator/templates/new_product_migration.j2,sha256=XTeOmv8nVNfGXx6pfSGVwRk282Xx3lnw-7bkpHkd-Tk,3128
87
87
  orchestrator/cli/generator/templates/product.j2,sha256=gcN1poWRuGWbsLrQv0Z8SXO6MGuXBHBr33UvQtqsF8E,1241
88
88
  orchestrator/cli/generator/templates/product_block.j2,sha256=X5ynyBG39UiYekPHms7IkSHmxq2j-2OrhAYsFaRqktU,2454
89
89
  orchestrator/cli/generator/templates/shared_forms.j2,sha256=tXbauuMMExwHLYLJVkmAJ74SSFriXlwpOyd04jwWE0k,545
@@ -106,7 +106,7 @@ orchestrator/db/database.py,sha256=MU_w_e95ho2dVb2JDnt_KFYholx___XDkiQXbc8wCkI,1
106
106
  orchestrator/db/helpers.py,sha256=L8kEdnSSNGnUpZhdeGx2arCodakWN8vSpKdfjoLuHdY,831
107
107
  orchestrator/db/listeners.py,sha256=UBPYcH0FE3a7aZQu_D0O_JMXpXIRYXC0gjSAvlv5GZo,1142
108
108
  orchestrator/db/loaders.py,sha256=ez6JzQ3IKVkC_oLAkVlIIiI8Do7hXbdcPKCvUSLxRog,7962
109
- orchestrator/db/models.py,sha256=V7m-XOVzo_Kj_B-M46Ybr1zlbfXjbN64sQ3C-rz7PQE,27217
109
+ orchestrator/db/models.py,sha256=92lkqI4u3Si2tEMSTnMb5Uv08CSjqkgotgyEYqLvFeI,27302
110
110
  orchestrator/db/filters/__init__.py,sha256=RUj6P0XxEBhYj0SN5wH5-Vf_Wt_ilZR_n9DSar5m9oM,371
111
111
  orchestrator/db/filters/filters.py,sha256=55RtpQwM2rhrk4A6CCSeSXoo-BT9GnQoNTryA8CtLEg,5020
112
112
  orchestrator/db/filters/process.py,sha256=xvGhyfo_MZ1xhLvFC6yULjcT4mJk0fKc1glJIYgsWLE,4018
@@ -142,9 +142,9 @@ orchestrator/distlock/managers/__init__.py,sha256=ImIkNsrXcyE7-NgRWqEhUXUuUzda9K
142
142
  orchestrator/distlock/managers/memory_distlock_manager.py,sha256=HWQafcVKBF-Cka_wukZZ1GM69AWPVOpJPje3quIebQ8,3114
143
143
  orchestrator/distlock/managers/redis_distlock_manager.py,sha256=DXtMhC8qtxiFO6xU9qYXHZQnCLjlmGBpeyfLA0vbRP0,3369
144
144
  orchestrator/domain/__init__.py,sha256=20DhXQPKY0g3rTgCkRlNDY58sLviToOVF8NPoex9WJc,936
145
- orchestrator/domain/base.py,sha256=26UbHKXqI99w8SyS-Kjj4DF8IbgPNOh8iDPP6e3GEsA,70996
145
+ orchestrator/domain/base.py,sha256=jjMxJvt0GyaTff_z490lmkqDCtitkh0oA4GygB60n4s,69939
146
146
  orchestrator/domain/context_cache.py,sha256=vT1a01MBSBIaokoShK9KwjItd7abNmz7cXaF67VRZK8,2508
147
- orchestrator/domain/customer_description.py,sha256=v7o6TTN4oc6bWHZU-jCT-fUYvkeYahbpXOwlKXofuI8,3360
147
+ orchestrator/domain/customer_description.py,sha256=RU_pcgCIZjjFfDsY45lfV0z6ATlS1NXtB0E7eH3UcYQ,3190
148
148
  orchestrator/domain/helpers.py,sha256=D9O2duhCAZGmm39u-9ggvU-X2JsCbIS607kF77-r8QM,2549
149
149
  orchestrator/domain/lifecycle.py,sha256=kGR0AFVOSUBlzdhgRr11CUnF26wbBYIjz8uKb_qPCg0,2922
150
150
  orchestrator/domain/subscription_instance_transform.py,sha256=j_d49dFcSss0dl-BHS-ev2faLbE0y8hLvCfrzui6C74,4360
@@ -165,7 +165,7 @@ orchestrator/graphql/extensions/model_cache.py,sha256=1uhMRjBs9eK7zJ1Y6P6BopX068
165
165
  orchestrator/graphql/extensions/stats.py,sha256=pGhEBQg45XvqZhRobcrCSGwt5AGmR3gflsm1dYiIg5g,2018
166
166
  orchestrator/graphql/loaders/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
167
167
  orchestrator/graphql/loaders/subscriptions.py,sha256=31zE2WC7z-tPIUmVpU1QWOJvNbLvF7sYgY7JAQ6OPJg,1856
168
- orchestrator/graphql/mutations/customer_description.py,sha256=37yX92fE1Sc51O9i-gP8JfD3HdsvpR3TtbgYqKtGC-w,3343
168
+ orchestrator/graphql/mutations/customer_description.py,sha256=zm_X1yvWl4qC97_rYUYSF-1q1gFrQX6fDrzQKhguDYs,3359
169
169
  orchestrator/graphql/mutations/start_process.py,sha256=8vLVvmBwL1ujbZJoI_8YE3VAgI-J2RTzgrTZJC8THZ4,1576
170
170
  orchestrator/graphql/resolvers/__init__.py,sha256=EEw9NO4LAryfrpkLlgsNQ9rytKd0usBDx95OURRV6sg,1031
171
171
  orchestrator/graphql/resolvers/customer.py,sha256=tq06MtMoaqFwqn3YQvSv0VmROW7QJZRJp1ykO4tUhck,934
@@ -205,7 +205,7 @@ orchestrator/graphql/utils/to_graphql_result_page.py,sha256=8ObkJP8reVf-TQOQVPKv
205
205
  orchestrator/migrations/README,sha256=heMzebYwlGhnE8_4CWJ4LS74WoEZjBy-S-mIJRxAEKI,39
206
206
  orchestrator/migrations/alembic.ini,sha256=kMoADqhGeubU8xanILNaqm4oixLy9m4ngYtdGpZcc7I,873
207
207
  orchestrator/migrations/env.py,sha256=M_cPoAL2axuuup5fvMy8I_WTPHEw0RbPEHkhZ3QEGoE,3740
208
- orchestrator/migrations/helpers.py,sha256=7r286N3MFHJhSc-59okj_UO2VoU8L_4QBWfvABMzZAw,43779
208
+ orchestrator/migrations/helpers.py,sha256=xwi1Z-0MMZJ1Bk-HXkb6f_RU7BmO3ajK5a4qqO94elc,44068
209
209
  orchestrator/migrations/script.py.mako,sha256=607Zrgp-Z-m9WGLt4wewN1QDOmHeifxcePUdADkSZyM,510
210
210
  orchestrator/migrations/templates/alembic.ini.j2,sha256=jA-QykVparwWSNt5XDP0Zk7epLOhK7D87Af-i2shJV4,905
211
211
  orchestrator/migrations/templates/env.py.j2,sha256=RfLAQItZ56Jlzwi6LJfBo92m1-th_bdfkFKD1mwTZIE,2821
@@ -231,15 +231,17 @@ orchestrator/migrations/versions/schema/2024-09-27_460ec6748e37_add_uuid_search_
231
231
  orchestrator/migrations/versions/schema/2025-01-08_4c5859620539_add_version_column_to_subscription.py,sha256=xAhe74U0ZiVRo9Z8Uq7491RBbATMMUnYpTBjbG-BYL0,1690
232
232
  orchestrator/migrations/versions/schema/2025-01-19_4fjdn13f83ga_add_validate_product_type_task.py,sha256=O0GfCISIDnyohGf3Ot_2HKedGRbMqLVox6t7Wd3PMvo,894
233
233
  orchestrator/migrations/versions/schema/2025-02-12_bac6be6f2b4f_added_input_state_table.py,sha256=RZpLkWP1yekeZ68feO5v4LZYluKvnnIKRNDCE4tI9HM,1753
234
+ orchestrator/migrations/versions/schema/2025-02-20_68d14db1b8da_make_workflow_description_mandatory.py,sha256=uh5J2bR_UERqlNrDjDBSfiAdEfI9YQzgR9-t9ordFOo,847
234
235
  orchestrator/migrations/versions/schema/2025-03-06_42b3d076a85b_subscription_instance_as_json_function.py,sha256=jtwDFOh-NlE31aH5dFmbynb23TZN6Mkzevxx-KLP7KE,776
235
236
  orchestrator/migrations/versions/schema/2025-03-06_42b3d076a85b_subscription_instance_as_json_function.sql,sha256=hPldk0DAesUbHv3Qd_N7U-cAk-t1wIgxt4FOA120gQ8,1776
236
237
  orchestrator/migrations/versions/schema/2025-04-09_fc5c993a4b4a_add_cascade_constraint_on_processes_.py,sha256=6kHRNSZxUze2jy7b8uRvkt5mzsax10Z-Z3lsACtPLRM,1067
238
+ orchestrator/migrations/versions/schema/2025-05-08_161918133bec_add_is_task_to_workflow.py,sha256=VLFDHFYRWn5ktUba0KuSPWyvjYJdfN1WypWmOPqIW18,721
237
239
  orchestrator/schedules/__init__.py,sha256=JnnaglfK1qYUBKI6Dd9taV-tCZIPlAdAkHtnkJDMXxY,1066
238
240
  orchestrator/schedules/resume_workflows.py,sha256=kSotzTAXjX7p9fpSYiGOpuxuTQfv54eRFAe0YSG0DHc,832
239
241
  orchestrator/schedules/scheduling.py,sha256=ehtwgpbvMOk1jhn-hHgVzg_9wLJkI6l3mRY3DcO9ZVY,1526
240
242
  orchestrator/schedules/task_vacuum.py,sha256=eovnuKimU8SFRw1IF62MsAVFSdgeeV1u57kapUbz8As,821
241
243
  orchestrator/schedules/validate_products.py,sha256=YMr7ASSqdXM6pd6oZu0kr8mfmH8If16MzprrsHdN_ZU,1234
242
- orchestrator/schedules/validate_subscriptions.py,sha256=YYcU2iGf8Ga_s577kgpKdhQV4p7wCEHGYvUf8FCvBvQ,2022
244
+ orchestrator/schedules/validate_subscriptions.py,sha256=9SYvsn4BJ5yo_1nu555hWjl5XffTx7QMaRhH5oOjM9E,2042
243
245
  orchestrator/schemas/__init__.py,sha256=YDyZ0YBvzB4ML9oDBCBPGnBvf680zFFgUzg7X0tYBRY,2326
244
246
  orchestrator/schemas/base.py,sha256=Vc444LetsINLRhG2SxW9Bq01hOzChPOhQWCImQTr-As,930
245
247
  orchestrator/schemas/engine_settings.py,sha256=LF8al7tJssiilb5A4emPtUYo0tVDSaT1Lvo_DN_ttrY,1296
@@ -251,21 +253,21 @@ orchestrator/schemas/product_block.py,sha256=kCqvm6qadHpegMr9aWI_fYX-T7mS-5S-ldP
251
253
  orchestrator/schemas/resource_type.py,sha256=VDju4XywcDDLxdpbWU62RTvR9QF8x_GRrpTlN_NE8uI,1064
252
254
  orchestrator/schemas/subscription.py,sha256=-jXyHZIed9Xlia18ksSDyenblNN6Q2yM2FlGELyJ458,3423
253
255
  orchestrator/schemas/subscription_descriptions.py,sha256=Ft_jw1U0bf9Z0U8O4OWfLlcl0mXCVT_qYVagBP3GbIQ,1262
254
- orchestrator/schemas/workflow.py,sha256=w-CaRPp9AAddhnd8o_0jPaey1Vnnh-s-A5s5kWlR2pI,1977
256
+ orchestrator/schemas/workflow.py,sha256=gBpLXttgU3cSkVyEqr5ji4yBskl-n7QNmEerUr9UXt0,2003
255
257
  orchestrator/services/__init__.py,sha256=GyHNfEFCGKQwRiN6rQmvSRH2iYX7npjMZn97n8XzmLU,571
256
- orchestrator/services/celery.py,sha256=W37UNc4hbUS2SKjVmawsnF5wukmEfIdipsTESv_EOTw,4768
258
+ orchestrator/services/celery.py,sha256=ezw7IxkIZbmaRJt-9_Iwvn86TJV3Y3DTHI7Qjh0-r9s,5075
257
259
  orchestrator/services/fixed_inputs.py,sha256=kyz7s2HLzyDulvcq-ZqefTw1om86COvyvTjz0_5CmgI,876
258
260
  orchestrator/services/input_state.py,sha256=HF7wl9fWdaAW8pdCCqbuYoKyNj8dY0g8Ff8vXis8z5A,2211
259
261
  orchestrator/services/process_broadcast_thread.py,sha256=D44YbjF8mRqGuznkRUV4SoRn1J0lfy_x1H508GnSVlU,4649
260
- orchestrator/services/processes.py,sha256=JXVa9E3NtcuFlUggK3LhjHilSAlF-4kTyCCHROakAJA,30554
262
+ orchestrator/services/processes.py,sha256=rTH6zLNsun3qDCPguz2LYS87MQR_LJREIPrgkGS6kwk,30494
261
263
  orchestrator/services/products.py,sha256=BP4KyE8zO-8z7Trrs5T6zKBOw53S9BfBJnHWI3p6u5Y,1943
262
264
  orchestrator/services/resource_types.py,sha256=_QBy_JOW_X3aSTqH0CuLrq4zBJL0p7Q-UDJUcuK2_qc,884
263
265
  orchestrator/services/settings.py,sha256=u-834F4KWloXS8zi7R9mp-D3cjl-rbVjKJRU35IqhXo,2723
264
266
  orchestrator/services/subscription_relations.py,sha256=9C126TUfFvyBe7y4x007kH_dvxJ9pZ1zSnaWeH6HC5k,12261
265
- orchestrator/services/subscriptions.py,sha256=u1qkCk7FuniJseQjnuDWiIlqy9ok4SY8nWUyLeJ11No,28068
267
+ orchestrator/services/subscriptions.py,sha256=nr2HI89nC0lYjzTh2j-lEQ5cPQK43LNZv3gvP6jbepw,27189
266
268
  orchestrator/services/tasks.py,sha256=NjPkuauQoh9UJDcjA7OcKFgPk0i6NoKdDO7HlpGbBJ8,6575
267
269
  orchestrator/services/translations.py,sha256=GyP8soUFGej8AS8uulBsk10CCK6Kwfjv9AHMFm3ElQY,1713
268
- orchestrator/services/workflows.py,sha256=oH7klit4kv2NGo-BACWA0ZtajVMSJAxG5m-kM6TXIMI,3742
270
+ orchestrator/services/workflows.py,sha256=iEkt2OBuTwkDru4V6ZSKatnw0b96ZdPV-VQqeZ9EOgU,4015
269
271
  orchestrator/utils/__init__.py,sha256=GyHNfEFCGKQwRiN6rQmvSRH2iYX7npjMZn97n8XzmLU,571
270
272
  orchestrator/utils/crypt.py,sha256=18eNamYWMllPkxyRtWIde3FDr3rSF74R5SAL6WsCj9Y,5584
271
273
  orchestrator/utils/datetime.py,sha256=a1WQ_yvu7MA0TiaRpC5avwbOSFdrj4eMrV4a7I2sD5Q,1477
@@ -275,11 +277,11 @@ orchestrator/utils/enrich_process.py,sha256=o_QSy5Q4wn1SMHhzVOw6bp7uhDXr7GhAIWRD
275
277
  orchestrator/utils/errors.py,sha256=6FxvXrITmRjP5bYnJJ3CxjAwA5meNjRAVYouz4TWKkU,4653
276
278
  orchestrator/utils/fixed_inputs.py,sha256=pnL6I_19VMp_Bny8SYjSzVFNvTFDyeCxFFOWGhTnDiQ,2665
277
279
  orchestrator/utils/functional.py,sha256=X1MDNwHmkU3-8mFb21m31HGlcfc5TygliXR0sXN3-rU,8304
278
- orchestrator/utils/get_subscription_dict.py,sha256=EoSlFUXer_Kc5G0PjDPeujugZ76kKoPrjRr4Mg4HrzY,839
280
+ orchestrator/utils/get_subscription_dict.py,sha256=hctkFvD3U6LpygNwz2uesRMdnXSY-PaohBqPATsi9r4,694
279
281
  orchestrator/utils/get_updated_properties.py,sha256=egVZ0R5LNJ4e51Z8SXlU8cmb4tXxG-xb1d7OKwh-7xI,1322
280
282
  orchestrator/utils/helpers.py,sha256=NjUF3IvWdnLulliP8-JQvGGGpHrh0vs0Vm092ynw-ss,3212
281
283
  orchestrator/utils/json.py,sha256=7386sdqkrKYyy4sbn5NscwctH_v1hLyw5172P__rU3g,8341
282
- orchestrator/utils/redis.py,sha256=E5uPFCOlS1n_jv0xoaS1fuLnVv-shQn0K1hzpVwUWZY,7303
284
+ orchestrator/utils/redis.py,sha256=BjUNmrKx8YVyJIucl2mhXubK6WV-49OnAU6rPMOZpM0,4469
283
285
  orchestrator/utils/redis_client.py,sha256=9rhsvedjK_CyClAjUicQyge0mVIViATqKFGZyjBY3XA,1384
284
286
  orchestrator/utils/search_query.py,sha256=ji5LHtrzohGz6b1IG41cnPdpWXzLEzz4SGWgHly_yfU,16205
285
287
  orchestrator/utils/state.py,sha256=RYKVlvKDBfsBdDk9wHjZKBTlQJbV4SqtCotAlTA2-bI,14021
@@ -290,17 +292,17 @@ orchestrator/websocket/websocket_manager.py,sha256=hwlG9FDXcNU42jDNNsPMQLIyrvEpG
290
292
  orchestrator/websocket/managers/broadcast_websocket_manager.py,sha256=fwoSgTjkHJ2GmsLTU9dqQpAA9i8b1McPu7gLNzxtfG4,5401
291
293
  orchestrator/websocket/managers/memory_websocket_manager.py,sha256=lF5EEx1iFMCGEkTbItTDr88NENMSaSeG1QrJ7teoPkY,3324
292
294
  orchestrator/workflows/__init__.py,sha256=NzIGGI-8SNAwCk2YqH6sHhEWbgAY457ntDwjO15N8v4,4131
293
- orchestrator/workflows/modify_note.py,sha256=X70qQtC0ITp5IE5OR8j5Op57k3ol1S9txnhDbZTICao,2416
295
+ orchestrator/workflows/modify_note.py,sha256=l6QtijRPv8gnHxzwTz_4nWIPcZ0FcKQh_yFbtjYEDMg,2163
294
296
  orchestrator/workflows/removed_workflow.py,sha256=V0Da5TEdfLdZZKD38ig-MTp3_IuE7VGqzHHzvPYQmLI,909
295
- orchestrator/workflows/steps.py,sha256=yAb6XZaRhR2mKV4_yo-4hSYMTknbMD27-pNGMfjVzi0,9802
296
- orchestrator/workflows/utils.py,sha256=fLayx_oggM8PdYnyukHSJE3R9u-X2zYY1WRMCELUY74,14545
297
+ orchestrator/workflows/steps.py,sha256=ulpheoHaCOE1qh71Bja4KW4pItQh1Z6q4QU4tn5GtNk,6067
298
+ orchestrator/workflows/utils.py,sha256=yBwfITlseimyLEzbwI0ehOAlr-xI1N3WGVudFz4boXk,13778
297
299
  orchestrator/workflows/tasks/__init__.py,sha256=GyHNfEFCGKQwRiN6rQmvSRH2iYX7npjMZn97n8XzmLU,571
298
300
  orchestrator/workflows/tasks/cleanup_tasks_log.py,sha256=BfWYbPXhnLAHUJ0mlODDnjZnQQAvKCZJDVTwbwOWI04,1624
299
301
  orchestrator/workflows/tasks/resume_workflows.py,sha256=MzJqlSXUvKStkT7NGzxZyRlfAer_ezYm-kjUqaZi0yc,2359
300
- orchestrator/workflows/tasks/validate_product_type.py,sha256=UVX_6Kh8ueQs8ujLawnKVDdNc8UhWp_u69aNA8okR_w,3184
301
- orchestrator/workflows/tasks/validate_products.py,sha256=i6jQME9N8sZZWo4pkNOS1Zgwh3eB2w66DnJi9k93yNk,8521
302
+ orchestrator/workflows/tasks/validate_product_type.py,sha256=paG-NAY1bdde3Adt8zItkcBKf5Pxw6f5ngGW6an6dYU,3192
303
+ orchestrator/workflows/tasks/validate_products.py,sha256=GZJBoFF-WMphS7ghMs2-gqvV2iL1F0POhk0uSNt93n0,8510
302
304
  orchestrator/workflows/translations/en-GB.json,sha256=ST53HxkphFLTMjFHonykDBOZ7-P_KxksktZU3GbxLt0,846
303
- orchestrator_core-3.2.3.dist-info/licenses/LICENSE,sha256=b-aA5OZQuuBATmLKo_mln8CQrDPPhg3ghLzjPjLn4Tg,11409
304
- orchestrator_core-3.2.3.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
305
- orchestrator_core-3.2.3.dist-info/METADATA,sha256=AzahqbZrlKJSQnVOq21UaeGImMLI-62j2UI52TGS0eE,5029
306
- orchestrator_core-3.2.3.dist-info/RECORD,,
305
+ orchestrator_core-4.0.0rc2.dist-info/licenses/LICENSE,sha256=b-aA5OZQuuBATmLKo_mln8CQrDPPhg3ghLzjPjLn4Tg,11409
306
+ orchestrator_core-4.0.0rc2.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
307
+ orchestrator_core-4.0.0rc2.dist-info/METADATA,sha256=eEc0N4MXX03Q--Jx61bXgZqQ3zwjzjdwLUhVl_5eGFM,5032
308
+ orchestrator_core-4.0.0rc2.dist-info/RECORD,,