orchestrator-core 4.1.0rc2__py3-none-any.whl → 4.2.0rc1__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.
orchestrator/__init__.py CHANGED
@@ -13,7 +13,7 @@
13
13
 
14
14
  """This is the orchestrator workflow engine."""
15
15
 
16
- __version__ = "4.1.0rc2"
16
+ __version__ = "4.2.0rc1"
17
17
 
18
18
  from orchestrator.app import OrchestratorCore
19
19
  from orchestrator.settings import app_settings
@@ -158,6 +158,9 @@ def delete(process_id: UUID) -> None:
158
158
  if not process:
159
159
  raise_status(HTTPStatus.NOT_FOUND)
160
160
 
161
+ if not process.is_task:
162
+ raise_status(HTTPStatus.BAD_REQUEST)
163
+
161
164
  db.session.delete(db.session.get(ProcessTable, process_id))
162
165
  db.session.commit()
163
166
 
@@ -270,7 +273,7 @@ def update_progress_on_awaiting_process_endpoint(
270
273
  @router.put(
271
274
  "/resume-all", response_model=ProcessResumeAllSchema, dependencies=[Depends(check_global_lock, use_cache=False)]
272
275
  )
273
- async def resume_all_processess_endpoint(request: Request, user: str = Depends(user_name)) -> dict[str, int]:
276
+ async def resume_all_processes_endpoint(request: Request, user: str = Depends(user_name)) -> dict[str, int]:
274
277
  """Retry all task processes in status Failed, Waiting, API Unavailable or Inconsistent Data.
275
278
 
276
279
  The retry is started in the background, returning status 200 and number of processes in message.
@@ -47,10 +47,12 @@ from orchestrator.services.subscriptions import (
47
47
  subscription_workflows,
48
48
  )
49
49
  from orchestrator.settings import app_settings
50
+ from orchestrator.targets import Target
50
51
  from orchestrator.types import SubscriptionLifecycle
51
52
  from orchestrator.utils.deprecation_logger import deprecated_endpoint
52
53
  from orchestrator.utils.get_subscription_dict import get_subscription_dict
53
54
  from orchestrator.websocket import sync_invalidate_subscription_cache
55
+ from orchestrator.workflows import get_workflow
54
56
 
55
57
  router = APIRouter()
56
58
 
@@ -100,6 +102,25 @@ def _filter_statuses(filter_statuses: str | None = None) -> list[str]:
100
102
  return statuses
101
103
 
102
104
 
105
+ def _authorized_subscription_workflows(
106
+ subscription: SubscriptionTable, current_user: OIDCUserModel | None
107
+ ) -> dict[str, list[dict[str, list[Any] | str]]]:
108
+ subscription_workflows_dict = subscription_workflows(subscription)
109
+
110
+ for workflow_target in Target.values():
111
+ for workflow_dict in subscription_workflows_dict[workflow_target.lower()]:
112
+ workflow = get_workflow(workflow_dict["name"])
113
+ if not workflow:
114
+ continue
115
+ if (
116
+ not workflow.authorize_callback(current_user) # The current user isn't allowed to run this workflow
117
+ and "reason" not in workflow_dict # and there isn't already a reason why this workflow cannot run
118
+ ):
119
+ workflow_dict["reason"] = "subscription.insufficient_workflow_permissions"
120
+
121
+ return subscription_workflows_dict
122
+
123
+
103
124
  @router.get(
104
125
  "/domain-model/{subscription_id}",
105
126
  response_model=SubscriptionDomainModelSchema | None,
@@ -169,7 +190,9 @@ def subscriptions_search(
169
190
  description="This endpoint is deprecated and will be removed in a future release. Please use the GraphQL query",
170
191
  dependencies=[Depends(deprecated_endpoint)],
171
192
  )
172
- def subscription_workflows_by_id(subscription_id: UUID) -> dict[str, list[dict[str, list[Any] | str]]]:
193
+ def subscription_workflows_by_id(
194
+ subscription_id: UUID, current_user: OIDCUserModel | None = Depends(authenticate)
195
+ ) -> dict[str, list[dict[str, list[Any] | str]]]:
173
196
  subscription = db.session.get(
174
197
  SubscriptionTable,
175
198
  subscription_id,
@@ -181,7 +204,7 @@ def subscription_workflows_by_id(subscription_id: UUID) -> dict[str, list[dict[s
181
204
  if not subscription:
182
205
  raise_status(HTTPStatus.NOT_FOUND)
183
206
 
184
- return subscription_workflows(subscription)
207
+ return _authorized_subscription_workflows(subscription, current_user)
185
208
 
186
209
 
187
210
  @router.put("/{subscription_id}/set_in_sync", response_model=None, status_code=HTTPStatus.OK)
@@ -11,7 +11,6 @@ from pydantic_forms.types import FormGenerator, State, UUIDstr
11
11
 
12
12
  from orchestrator.forms import FormPage
13
13
  from orchestrator.forms.validators import Divider, Label, CustomerId, MigrationSummary
14
- from orchestrator.targets import Target
15
14
  from orchestrator.types import SubscriptionLifecycle
16
15
  from orchestrator.workflow import StepList, begin, step
17
16
  from orchestrator.workflows.steps import store_process_subscription
@@ -119,6 +118,6 @@ def create_{{ product.variable }}() -> StepList:
119
118
  return (
120
119
  begin
121
120
  >> construct_{{ product.variable }}_model
122
- >> store_process_subscription(Target.CREATE)
121
+ >> store_process_subscription()
123
122
  # TODO add provision step(s)
124
123
  )
orchestrator/db/models.py CHANGED
@@ -117,7 +117,7 @@ class ProcessTable(BaseModel):
117
117
  is_task = mapped_column(Boolean, nullable=False, server_default=text("false"), index=True)
118
118
 
119
119
  steps = relationship(
120
- "ProcessStepTable", cascade="delete", passive_deletes=True, order_by="asc(ProcessStepTable.executed_at)"
120
+ "ProcessStepTable", cascade="delete", passive_deletes=True, order_by="asc(ProcessStepTable.completed_at)"
121
121
  )
122
122
  input_states = relationship("InputStateTable", cascade="delete", order_by="desc(InputStateTable.input_time)")
123
123
  process_subscriptions = relationship("ProcessSubscriptionTable", back_populates="process", passive_deletes=True)
@@ -141,7 +141,8 @@ class ProcessStepTable(BaseModel):
141
141
  status = mapped_column(String(50), nullable=False)
142
142
  state = mapped_column(pg.JSONB(), nullable=False)
143
143
  created_by = mapped_column(String(255), nullable=True)
144
- executed_at = mapped_column(UtcTimestamp, server_default=text("statement_timestamp()"), nullable=False)
144
+ completed_at = mapped_column(UtcTimestamp, server_default=text("statement_timestamp()"), nullable=False)
145
+ started_at = mapped_column(UtcTimestamp, server_default=text("statement_timestamp()"), nullable=False)
145
146
  commit_hash = mapped_column(String(40), nullable=True, default=GIT_COMMIT_HASH)
146
147
 
147
148
 
@@ -154,7 +155,9 @@ class ProcessSubscriptionTable(BaseModel):
154
155
  )
155
156
  subscription_id = mapped_column(UUIDType, ForeignKey("subscriptions.subscription_id"), nullable=False, index=True)
156
157
  created_at = mapped_column(UtcTimestamp, server_default=text("current_timestamp()"), nullable=False)
157
- workflow_target = mapped_column(String(255), nullable=False, server_default=Target.CREATE)
158
+
159
+ # FIXME: workflow_target is already stored in the workflow table, this column should get removed in a later release.
160
+ workflow_target = mapped_column(String(255), nullable=True)
158
161
 
159
162
  process = relationship("ProcessTable", back_populates="process_subscriptions")
160
163
  subscription = relationship("SubscriptionTable", back_populates="processes")
@@ -6,14 +6,17 @@ from strawberry.federation.schema_directives import Key
6
6
  from strawberry.scalars import JSON
7
7
 
8
8
  from oauth2_lib.strawberry import authenticated_field
9
+ from orchestrator.api.api_v1.endpoints.processes import get_auth_callbacks, get_current_steps
9
10
  from orchestrator.db import ProcessTable, ProductTable, db
10
11
  from orchestrator.graphql.pagination import EMPTY_PAGE, Connection
11
12
  from orchestrator.graphql.schemas.customer import CustomerType
12
13
  from orchestrator.graphql.schemas.helpers import get_original_model
13
14
  from orchestrator.graphql.schemas.product import ProductType
14
- from orchestrator.graphql.types import GraphqlFilter, GraphqlSort, OrchestratorInfo
15
+ from orchestrator.graphql.types import FormUserPermissionsType, GraphqlFilter, GraphqlSort, OrchestratorInfo
15
16
  from orchestrator.schemas.process import ProcessSchema, ProcessStepSchema
17
+ from orchestrator.services.processes import load_process
16
18
  from orchestrator.settings import app_settings
19
+ from orchestrator.workflows import get_workflow
17
20
 
18
21
  if TYPE_CHECKING:
19
22
  from orchestrator.graphql.schemas.subscription import SubscriptionInterface
@@ -29,7 +32,11 @@ class ProcessStepType:
29
32
  name: strawberry.auto
30
33
  status: strawberry.auto
31
34
  created_by: strawberry.auto
32
- executed: strawberry.auto
35
+ executed: strawberry.auto = strawberry.field(
36
+ deprecation_reason="Deprecated, use 'started' and 'completed' for step start and completion times"
37
+ )
38
+ started: strawberry.auto
39
+ completed: strawberry.auto
33
40
  commit_hash: strawberry.auto
34
41
  state: JSON | None
35
42
  state_delta: JSON | None
@@ -74,6 +81,18 @@ class ProcessType:
74
81
  shortcode=app_settings.DEFAULT_CUSTOMER_SHORTCODE,
75
82
  )
76
83
 
84
+ @strawberry.field(description="Returns user permissions for operations on this process") # type: ignore
85
+ def user_permissions(self, info: OrchestratorInfo) -> FormUserPermissionsType:
86
+ oidc_user = info.context.get_current_user
87
+ workflow = get_workflow(self.workflow_name)
88
+ process = load_process(db.session.get(ProcessTable, self.process_id)) # type: ignore[arg-type]
89
+ auth_resume, auth_retry = get_auth_callbacks(get_current_steps(process), workflow) # type: ignore[arg-type]
90
+
91
+ return FormUserPermissionsType(
92
+ retryAllowed=auth_retry and auth_retry(oidc_user), # type: ignore[arg-type]
93
+ resumeAllowed=auth_resume and auth_resume(oidc_user), # type: ignore[arg-type]
94
+ )
95
+
77
96
  @authenticated_field(description="Returns list of subscriptions of the process") # type: ignore
78
97
  async def subscriptions(
79
98
  self,
@@ -52,21 +52,20 @@ class ProductType:
52
52
  return await resolve_subscriptions(info, filter_by_with_related_subscriptions, sort_by, first, after)
53
53
 
54
54
  @strawberry.field(description="Returns list of all nested productblock names") # type: ignore
55
- async def all_pb_names(self) -> list[str]:
56
-
55
+ async def all_product_block_names(self) -> list[str]:
57
56
  model = get_original_model(self, ProductTable)
58
57
 
59
- def get_all_pb_names(product_blocks: list[ProductBlockTable]) -> Iterable[str]:
58
+ def get_names(product_blocks: list[ProductBlockTable], visited: set) -> Iterable[str]:
60
59
  for product_block in product_blocks:
60
+ if product_block.product_block_id in visited:
61
+ continue
62
+ visited.add(product_block.product_block_id)
61
63
  yield product_block.name
62
-
63
64
  if product_block.depends_on:
64
- yield from get_all_pb_names(product_block.depends_on)
65
-
66
- names: list[str] = list(get_all_pb_names(model.product_blocks))
67
- names.sort()
65
+ yield from get_names(product_block.depends_on, visited)
68
66
 
69
- return names
67
+ names = set(get_names(model.product_blocks, set()))
68
+ return sorted(names)
70
69
 
71
70
  @strawberry.field(description="Return product blocks") # type: ignore
72
71
  async def product_blocks(self) -> list[Annotated["ProductBlock", strawberry.lazy(".product_block")]]:
@@ -5,6 +5,7 @@ import strawberry
5
5
  from orchestrator.config.assignee import Assignee
6
6
  from orchestrator.db import WorkflowTable
7
7
  from orchestrator.graphql.schemas.helpers import get_original_model
8
+ from orchestrator.graphql.types import OrchestratorInfo
8
9
  from orchestrator.schemas import StepSchema, WorkflowSchema
9
10
  from orchestrator.workflows import get_workflow
10
11
 
@@ -30,3 +31,11 @@ class Workflow:
30
31
  @strawberry.field(description="Return all steps for this workflow") # type: ignore
31
32
  def steps(self) -> list[Step]:
32
33
  return [Step(name=step.name, assignee=step.assignee) for step in get_workflow(self.name).steps] # type: ignore
34
+
35
+ @strawberry.field(description="Return whether the currently logged-in used is allowed to start this workflow") # type: ignore
36
+ def is_allowed(self, info: OrchestratorInfo) -> bool:
37
+ oidc_user = info.context.get_current_user
38
+ workflow_table = get_original_model(self, WorkflowTable)
39
+ workflow = get_workflow(workflow_table.name)
40
+
41
+ return workflow.authorize_callback(oidc_user) # type: ignore
@@ -1,4 +1,4 @@
1
- # Copyright 2022-2023 SURF, GÉANT.
1
+ # Copyright 2022-2025 SURF, GÉANT.
2
2
  # Licensed under the Apache License, Version 2.0 (the "License");
3
3
  # you may not use this file except in compliance with the License.
4
4
  # You may obtain a copy of the License at
@@ -132,6 +132,12 @@ SCALAR_OVERRIDES: ScalarOverrideType = {
132
132
  }
133
133
 
134
134
 
135
+ @strawberry.type(description="User permissions on a specific process")
136
+ class FormUserPermissionsType:
137
+ retryAllowed: bool
138
+ resumeAllowed: bool
139
+
140
+
135
141
  @strawberry.type(description="Generic class to capture errors")
136
142
  class MutationError:
137
143
  message: str = strawberry.field(description="Error message")
@@ -0,0 +1,65 @@
1
+ """Changed timestamping fields in process_steps.
2
+
3
+ Revision ID: 93fc5834c7e5
4
+ Revises: 4b58e336d1bf
5
+ Create Date: 2025-07-01 14:20:44.755694
6
+
7
+ """
8
+
9
+ import sqlalchemy as sa
10
+ from alembic import op
11
+
12
+ from orchestrator import db
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision = "93fc5834c7e5"
16
+ down_revision = "4b58e336d1bf"
17
+ branch_labels = None
18
+ depends_on = None
19
+
20
+
21
+ def upgrade() -> None:
22
+ # ### commands auto generated by Alembic - please adjust! ###
23
+ op.add_column(
24
+ "process_steps",
25
+ sa.Column(
26
+ "started_at",
27
+ db.UtcTimestamp(timezone=True),
28
+ server_default=sa.text("statement_timestamp()"),
29
+ nullable=False,
30
+ ),
31
+ )
32
+ op.alter_column("process_steps", "executed_at", new_column_name="completed_at")
33
+ # conn = op.get_bind()
34
+ # sa.select
35
+ # ### end Alembic commands ###
36
+ # Backfill started_at field correctly using proper aliasing
37
+ op.execute(
38
+ """
39
+ WITH backfill_started_at AS (
40
+ SELECT
41
+ ps1.stepid,
42
+ COALESCE(prev.completed_at, p.started_at) AS new_started_at
43
+ FROM process_steps ps1
44
+ JOIN processes p ON ps1.pid = p.pid
45
+ LEFT JOIN LATERAL (
46
+ SELECT ps2.completed_at
47
+ FROM process_steps ps2
48
+ WHERE ps2.pid = ps1.pid AND ps2.completed_at < ps1.completed_at
49
+ ORDER BY ps2.completed_at DESC
50
+ LIMIT 1
51
+ ) prev ON true
52
+ )
53
+ UPDATE process_steps
54
+ SET started_at = b.new_started_at
55
+ FROM backfill_started_at b
56
+ WHERE process_steps.stepid = b.stepid;
57
+ """
58
+ )
59
+
60
+
61
+ def downgrade() -> None:
62
+ # ### commands auto generated by Alembic - please adjust! ###
63
+ op.drop_column("process_steps", "started_at")
64
+ op.alter_column("process_steps", "completed_at", new_column_name="executed_at")
65
+ # ### end Alembic commands ###
@@ -0,0 +1,30 @@
1
+ """Deprecating workflow target in ProcessSubscriptionTable.
2
+
3
+ Revision ID: 4b58e336d1bf
4
+ Revises: 161918133bec
5
+ Create Date: 2025-07-04 15:27:23.814954
6
+
7
+ """
8
+
9
+ import sqlalchemy as sa
10
+ from alembic import op
11
+
12
+ # revision identifiers, used by Alembic.
13
+ revision = "4b58e336d1bf"
14
+ down_revision = "161918133bec"
15
+ branch_labels = None
16
+ depends_on = None
17
+
18
+
19
+ def upgrade() -> None:
20
+ op.alter_column("processes_subscriptions", "workflow_target", existing_type=sa.VARCHAR(length=255), nullable=True)
21
+
22
+
23
+ def downgrade() -> None:
24
+ op.alter_column(
25
+ "processes_subscriptions",
26
+ "workflow_target",
27
+ existing_type=sa.VARCHAR(length=255),
28
+ nullable=False,
29
+ existing_server_default=sa.text("'CREATE'::character varying"),
30
+ )
@@ -49,7 +49,11 @@ class ProcessStepSchema(OrchestratorBaseModel):
49
49
  name: str
50
50
  status: str
51
51
  created_by: str | None = None
52
- executed: datetime | None = None
52
+ executed: datetime | None = Field(
53
+ None, deprecated="Deprecated, use 'started' and 'completed' for step start and completion times"
54
+ )
55
+ started: datetime | None = None
56
+ completed: datetime | None = None
53
57
  commit_hash: str | None = None
54
58
  state: dict[str, Any] | None = None
55
59
  state_delta: dict[str, Any] | None = None
@@ -12,6 +12,7 @@
12
12
  # limitations under the License.
13
13
  from collections.abc import Callable, Sequence
14
14
  from concurrent.futures.thread import ThreadPoolExecutor
15
+ from datetime import datetime
15
16
  from functools import partial
16
17
  from http import HTTPStatus
17
18
  from typing import Any
@@ -19,6 +20,7 @@ from uuid import UUID, uuid4
19
20
 
20
21
  import structlog
21
22
  from deepmerge.merger import Merger
23
+ from pytz import utc
22
24
  from sqlalchemy import delete, select
23
25
  from sqlalchemy.exc import SQLAlchemyError
24
26
  from sqlalchemy.orm import joinedload
@@ -206,6 +208,10 @@ def _get_current_step_to_update(
206
208
  finally:
207
209
  step_state.pop("__remove_keys", None)
208
210
 
211
+ # We don't have __last_step_started in __remove_keys because the way __remove_keys is populated appears like it would overwrite
212
+ # what's put there in the step decorator in certain cases (step groups and callback steps)
213
+ step_start_time = step_state.pop("__last_step_started_at", None)
214
+
209
215
  if process_state.isfailed() or process_state.iswaiting():
210
216
  if (
211
217
  last_db_step is not None
@@ -216,7 +222,7 @@ def _get_current_step_to_update(
216
222
  ):
217
223
  state_ex_info = {
218
224
  "retries": last_db_step.state.get("retries", 0) + 1,
219
- "executed_at": last_db_step.state.get("executed_at", []) + [str(last_db_step.executed_at)],
225
+ "completed_at": last_db_step.state.get("completed_at", []) + [str(last_db_step.completed_at)],
220
226
  }
221
227
 
222
228
  # write new state info and execution date
@@ -236,10 +242,13 @@ def _get_current_step_to_update(
236
242
  state=step_state,
237
243
  created_by=stat.current_user,
238
244
  )
245
+ # Since the Start step does not have a __last_step_started_at in it's state, we effectively assume it is instantaneous.
246
+ now = nowtz()
247
+ current_step.started_at = datetime.fromtimestamp(step_start_time or now.timestamp(), tz=utc)
239
248
 
240
249
  # Always explicitly set this instead of leaving it to the database to prevent failing tests
241
250
  # Test will fail if multiple steps have the same timestamp
242
- current_step.executed_at = nowtz()
251
+ current_step.completed_at = now
243
252
  return current_step
244
253
 
245
254
 
@@ -57,7 +57,9 @@ def enrich_step_details(step: ProcessStepTable, previous_step: ProcessStepTable
57
57
 
58
58
  return {
59
59
  "name": step.name,
60
- "executed": step.executed_at.timestamp(),
60
+ "executed": step.completed_at.timestamp(),
61
+ "started": step.started_at.timestamp(),
62
+ "completed": step.completed_at.timestamp(),
61
63
  "status": step.status,
62
64
  "state": step.state,
63
65
  "created_by": step.created_by,
@@ -103,7 +105,7 @@ def enrich_process(process: ProcessTable, p_stat: ProcessStat | None = None) ->
103
105
  "is_task": process.is_task,
104
106
  "workflow_id": process.workflow_id,
105
107
  "workflow_name": process.workflow.name,
106
- "workflow_target": process.process_subscriptions[0].workflow_target if process.process_subscriptions else None,
108
+ "workflow_target": process.workflow.target,
107
109
  "failed_reason": process.failed_reason,
108
110
  "created_by": process.created_by,
109
111
  "started_at": process.started_at,
orchestrator/workflow.py CHANGED
@@ -46,6 +46,7 @@ from orchestrator.services.settings import get_engine_settings
46
46
  from orchestrator.targets import Target
47
47
  from orchestrator.types import ErrorDict, StepFunc
48
48
  from orchestrator.utils.auth import Authorizer
49
+ from orchestrator.utils.datetime import nowtz
49
50
  from orchestrator.utils.docs import make_workflow_doc
50
51
  from orchestrator.utils.errors import error_state_to_dict
51
52
  from orchestrator.utils.state import form_inject_args, inject_args
@@ -381,11 +382,13 @@ def step_group(name: str, steps: StepList, extract_form: bool = True) -> Step:
381
382
  p = p.map(lambda s: s | {"__replace_last_state": True})
382
383
  return step_log_fn(step_, p)
383
384
 
385
+ step_group_start_time = nowtz().timestamp()
384
386
  process: Process = Success(initial_state)
385
387
  process = _exec_steps(step_list, process, dblogstep)
386
-
387
388
  # Add instruction to replace state of last sub step before returning process _exec_steps higher in the call tree
388
- return process.map(lambda s: s | {"__replace_last_state": True})
389
+ return process.map(
390
+ lambda s: s | {"__replace_last_state": True, "__last_step_started_at": step_group_start_time}
391
+ )
389
392
 
390
393
  # Make sure we return a form is a sub step has a form
391
394
  form = next((sub_step.form for sub_step in steps if sub_step.form), None) if extract_form else None
@@ -1454,6 +1457,8 @@ def _exec_steps(steps: StepList, starting_process: Process, dblogstep: StepLogFu
1454
1457
  "Not executing Step as the workflow engine is Paused. Process will remain in state 'running'"
1455
1458
  )
1456
1459
  return process
1460
+
1461
+ process = process.map(lambda s: s | {"__last_step_started_at": nowtz().timestamp()})
1457
1462
  step_result_process = process.execute_step(step)
1458
1463
  except Exception as e:
1459
1464
  consolelogger.error("An exception occurred while executing the workflow step.")
@@ -53,4 +53,4 @@ def store_subscription_note(subscription_id: UUIDstr, note: str) -> State:
53
53
 
54
54
  @workflow("Modify Note", initial_input_form=wrap_modify_initial_input_form(initial_input_form), target=Target.MODIFY)
55
55
  def modify_note() -> StepList:
56
- return init >> store_process_subscription(Target.MODIFY) >> store_subscription_note >> done
56
+ return init >> store_process_subscription() >> store_subscription_note >> done
@@ -23,6 +23,7 @@ from orchestrator.services.subscriptions import get_subscription
23
23
  from orchestrator.targets import Target
24
24
  from orchestrator.types import SubscriptionLifecycle
25
25
  from orchestrator.utils.json import to_serializable
26
+ from orchestrator.websocket import sync_invalidate_subscription_cache
26
27
  from orchestrator.workflow import Step, step
27
28
  from pydantic_forms.types import State, UUIDstr
28
29
 
@@ -33,6 +34,7 @@ logger = structlog.get_logger(__name__)
33
34
  def resync(subscription: SubscriptionModel) -> State:
34
35
  """Transition a subscription to in sync."""
35
36
  subscription.insync = True
37
+ sync_invalidate_subscription_cache(subscription.subscription_id)
36
38
  return {"subscription": subscription}
37
39
 
38
40
 
@@ -93,6 +95,7 @@ def unsync(subscription_id: UUIDstr, __old_subscriptions__: dict | None = None)
93
95
  if not subscription.insync:
94
96
  raise ValueError("Subscription is already out of sync, cannot continue!")
95
97
  subscription.insync = False
98
+ sync_invalidate_subscription_cache(subscription.subscription_id)
96
99
 
97
100
  return {"subscription": subscription, "__old_subscriptions__": subscription_backup}
98
101
 
@@ -105,20 +108,23 @@ def unsync_unchecked(subscription_id: UUIDstr) -> State:
105
108
  return {"subscription": subscription}
106
109
 
107
110
 
108
- def store_process_subscription_relationship(
109
- process_id: UUIDstr, subscription_id: UUIDstr, workflow_target: str
110
- ) -> ProcessSubscriptionTable:
111
- process_subscription = ProcessSubscriptionTable(
112
- process_id=process_id, subscription_id=subscription_id, workflow_target=workflow_target
113
- )
111
+ def store_process_subscription_relationship(process_id: UUIDstr, subscription_id: UUIDstr) -> ProcessSubscriptionTable:
112
+ process_subscription = ProcessSubscriptionTable(process_id=process_id, subscription_id=subscription_id)
114
113
  db.session.add(process_subscription)
115
114
  return process_subscription
116
115
 
117
116
 
118
- def store_process_subscription(workflow_target: Target) -> Step:
117
+ def store_process_subscription(workflow_target: Target | None = None) -> Step:
118
+ if workflow_target:
119
+ deprecation_warning = (
120
+ "Providing a workflow target to function store_process_subscription() is deprecated. "
121
+ "This information is already stored in the workflow table."
122
+ )
123
+ logger.warning(deprecation_warning)
124
+
119
125
  @step("Create Process Subscription relation")
120
126
  def _store_process_subscription(process_id: UUIDstr, subscription_id: UUIDstr) -> None:
121
- store_process_subscription_relationship(process_id, subscription_id, workflow_target)
127
+ store_process_subscription_relationship(process_id, subscription_id)
122
128
 
123
129
  return _store_process_subscription
124
130
 
@@ -265,7 +265,7 @@ def modify_workflow(
265
265
  def _modify_workflow(f: Callable[[], StepList]) -> Workflow:
266
266
  steplist = (
267
267
  init
268
- >> store_process_subscription(Target.MODIFY)
268
+ >> store_process_subscription()
269
269
  >> unsync
270
270
  >> f()
271
271
  >> (additional_steps or StepList())
@@ -311,7 +311,7 @@ def terminate_workflow(
311
311
  def _terminate_workflow(f: Callable[[], StepList]) -> Workflow:
312
312
  steplist = (
313
313
  init
314
- >> store_process_subscription(Target.TERMINATE)
314
+ >> store_process_subscription()
315
315
  >> unsync
316
316
  >> f()
317
317
  >> (additional_steps or StepList())
@@ -348,7 +348,7 @@ def validate_workflow(description: str) -> Callable[[Callable[[], StepList]], Wo
348
348
  """
349
349
 
350
350
  def _validate_workflow(f: Callable[[], StepList]) -> Workflow:
351
- steplist = init >> store_process_subscription(Target.SYSTEM) >> unsync_unchecked >> f() >> resync >> done
351
+ steplist = init >> store_process_subscription() >> unsync_unchecked >> f() >> resync >> done
352
352
 
353
353
  return make_workflow(f, description, validate_initial_input_form_generator, Target.VALIDATE, steplist)
354
354
 
@@ -0,0 +1,167 @@
1
+ Metadata-Version: 2.4
2
+ Name: orchestrator-core
3
+ Version: 4.2.0rc1
4
+ Summary: This is the orchestrator workflow engine.
5
+ Author-email: SURF <automation-beheer@surf.nl>
6
+ Requires-Python: >=3.11,<3.14
7
+ Description-Content-Type: text/markdown
8
+ License-Expression: Apache-2.0
9
+ Classifier: Development Status :: 5 - Production/Stable
10
+ Classifier: Environment :: Web Environment
11
+ Classifier: Framework :: AsyncIO
12
+ Classifier: Framework :: FastAPI
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Information Technology
15
+ Classifier: Intended Audience :: System Administrators
16
+ Classifier: Intended Audience :: Telecommunications Industry
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python
24
+ Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
25
+ Classifier: Topic :: Internet :: WWW/HTTP
26
+ Classifier: Topic :: Internet
27
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
28
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
29
+ Classifier: Topic :: Software Development :: Libraries
30
+ Classifier: Topic :: Software Development
31
+ Classifier: Typing :: Typed
32
+ License-File: LICENSE
33
+ Requires-Dist: alembic==1.16.1
34
+ Requires-Dist: anyio>=3.7.0
35
+ Requires-Dist: click==8.*
36
+ Requires-Dist: deepmerge==2.0
37
+ Requires-Dist: deprecated>=1.2.18
38
+ Requires-Dist: fastapi~=0.115.2
39
+ Requires-Dist: fastapi-etag==0.4.0
40
+ Requires-Dist: itsdangerous>=2.2.0
41
+ Requires-Dist: jinja2==3.1.6
42
+ Requires-Dist: more-itertools~=10.7.0
43
+ Requires-Dist: nwa-stdlib~=1.9.0
44
+ Requires-Dist: oauth2-lib~=2.4.0
45
+ Requires-Dist: orjson==3.10.18
46
+ Requires-Dist: prometheus-client==0.22.1
47
+ Requires-Dist: psycopg2-binary==2.9.10
48
+ Requires-Dist: pydantic-forms>=1.4.0,<=2.1.0
49
+ Requires-Dist: pydantic-settings~=2.9.1
50
+ Requires-Dist: pydantic[email]~=2.8.2
51
+ Requires-Dist: python-dateutil==2.8.2
52
+ Requires-Dist: python-rapidjson>=1.18,<1.21
53
+ Requires-Dist: pytz==2025.2
54
+ Requires-Dist: redis==5.1.1
55
+ Requires-Dist: schedule==1.1.0
56
+ Requires-Dist: semver==3.0.4
57
+ Requires-Dist: sentry-sdk[fastapi]~=2.29.1
58
+ Requires-Dist: sqlalchemy==2.0.41
59
+ Requires-Dist: sqlalchemy-utils==0.41.2
60
+ Requires-Dist: strawberry-graphql>=0.246.2
61
+ Requires-Dist: structlog>=25.4.0
62
+ Requires-Dist: tabulate==0.9.0
63
+ Requires-Dist: typer==0.15.4
64
+ Requires-Dist: uvicorn[standard]~=0.34.0
65
+ Requires-Dist: celery~=5.5.1 ; extra == "celery"
66
+ Project-URL: Documentation, https://workfloworchestrator.org/orchestrator-core
67
+ Project-URL: Homepage, https://workfloworchestrator.org/orchestrator-core
68
+ Project-URL: Source, https://github.com/workfloworchestrator/orchestrator-core
69
+ Provides-Extra: celery
70
+
71
+ # Orchestrator-Core
72
+
73
+ [![Downloads](https://pepy.tech/badge/orchestrator-core/month)](https://pepy.tech/project/orchestrator-core)
74
+ [![codecov](https://codecov.io/gh/workfloworchestrator/orchestrator-core/branch/main/graph/badge.svg?token=5ANQFI2DHS)](https://codecov.io/gh/workfloworchestrator/orchestrator-core)
75
+ [![pypi_version](https://img.shields.io/pypi/v/orchestrator-core?color=%2334D058&label=pypi%20package)](https://pypi.org/project/orchestrator-core)
76
+ [![Supported python versions](https://img.shields.io/pypi/pyversions/orchestrator-core.svg?color=%2334D058)](https://pypi.org/project/orchestrator-core)
77
+ ![Discord](https://img.shields.io/discord/1295834294270558280?style=flat&logo=discord&label=discord&link=https%3A%2F%2Fdiscord.gg%2FKNgF6gE8)
78
+
79
+ <p style="text-align: center"><em>Production ready Orchestration Framework to manage product lifecycle and workflows. Easy to use, built on top of FastAPI and Pydantic</em></p>
80
+
81
+ ## Documentation
82
+
83
+ The documentation can be found at [workfloworchestrator.org](https://workfloworchestrator.org/orchestrator-core/).
84
+
85
+ ## Installation (quick start)
86
+
87
+ Simplified steps to install and use the orchestrator-core.
88
+ For more details, read the [Getting started](https://workfloworchestrator.org/orchestrator-core/getting-started/base/) documentation.
89
+
90
+ ### Step 1 - Install the package
91
+
92
+ Create a virtualenv and install the orchestrator-core.
93
+
94
+ ```shell
95
+ python -m venv .venv
96
+ source .venv/bin/activate
97
+ pip install orchestrator-core
98
+ ```
99
+
100
+ ### Step 2 - Setup the database
101
+
102
+ Create a postgres database:
103
+
104
+ ```shell
105
+ createuser -sP nwa
106
+ createdb orchestrator-core -O nwa # set password to 'nwa'
107
+ ```
108
+
109
+ Configure the database URI in your local environment:
110
+
111
+ ```
112
+ export DATABASE_URI=postgresql://nwa:nwa@localhost:5432/orchestrator-core
113
+ ```
114
+
115
+ ### Step 3 - Create main.py
116
+
117
+ Create a `main.py` file.
118
+
119
+ ```python
120
+ from orchestrator import OrchestratorCore
121
+ from orchestrator.cli.main import app as core_cli
122
+ from orchestrator.settings import AppSettings
123
+
124
+ app = OrchestratorCore(base_settings=AppSettings())
125
+
126
+ if __name__ == "__main__":
127
+ core_cli()
128
+ ```
129
+
130
+ ### Step 4 - Run the database migrations
131
+
132
+ Initialize the migration environment and database tables.
133
+
134
+ ```shell
135
+ python main.py db init
136
+ python main.py db upgrade heads
137
+ ```
138
+
139
+ ### Step 5 - Run the app
140
+
141
+ ```shell
142
+ export OAUTH2_ACTIVE=False
143
+ uvicorn --reload --host 127.0.0.1 --port 8080 main:app
144
+ ```
145
+
146
+ Visit the [ReDoc](http://127.0.0.1:8080/api/redoc) or [OpenAPI](http://127.0.0.1:8080/api/docs) page to view and interact with the API.
147
+
148
+ ## Contributing
149
+
150
+ We use [uv](https://docs.astral.sh/uv/getting-started/installation/) to manage dependencies.
151
+
152
+ To get started, follow these steps:
153
+
154
+ ```shell
155
+ # in your postgres database
156
+ createdb orchestrator-core-test -O nwa # set password to 'nwa'
157
+
158
+ # on your local machine
159
+ git clone https://github.com/workfloworchestrator/orchestrator-core
160
+ cd orchestrator-core
161
+ export DATABASE_URI=postgresql://nwa:nwa@localhost:5432/orchestrator-core-test
162
+ uv sync --all-extras --all-groups
163
+ uv run pytest
164
+ ```
165
+
166
+ For more details please read the [development docs](https://workfloworchestrator.org/orchestrator-core/contributing/development/).
167
+
@@ -1,4 +1,4 @@
1
- orchestrator/__init__.py,sha256=rvhBWDPCO-2bbuNrSMvfh1UqquJITXBYE2zo4tsKmWo,1066
1
+ orchestrator/__init__.py,sha256=KuNtfAwhqS1RMy4hovcLLlT1kTH_aNMFLNu1SnkB0Qo,1066
2
2
  orchestrator/app.py,sha256=7UrXKjBKNSEaSSXAd5ww_RdMFhFqE4yvfj8faS2MzAA,12089
3
3
  orchestrator/exception_handlers.py,sha256=UsW3dw8q0QQlNLcV359bIotah8DYjMsj2Ts1LfX4ClY,1268
4
4
  orchestrator/log_config.py,sha256=1tPRX5q65e57a6a_zEii_PFK8SzWT0mnA5w2sKg4hh8,1853
@@ -8,7 +8,7 @@ orchestrator/settings.py,sha256=TFIv09JIKY-lXqd04lH_XEcijEEyheaz3zTcgeG8DEI,4339
8
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
- orchestrator/workflow.py,sha256=N4KGmH_Nf0U6nTxs62-qby7WVIoppHF6uKrZKCdxwfQ,45285
11
+ orchestrator/workflow.py,sha256=PVHe6vnnkswzqw2UoY-j6NMSEhL6rLHXRnO7yLOyDC8,45551
12
12
  orchestrator/api/__init__.py,sha256=GyHNfEFCGKQwRiN6rQmvSRH2iYX7npjMZn97n8XzmLU,571
13
13
  orchestrator/api/error_handling.py,sha256=YrPCxSa-DSa9KwqIMlXI-KGBGnbGIW5ukOPiikUH9E4,1502
14
14
  orchestrator/api/helpers.py,sha256=s0QRHYw8AvEmlkmRhuEzz9xixaZKUF3YuPzUVHkcoXk,6933
@@ -17,13 +17,13 @@ orchestrator/api/api_v1/__init__.py,sha256=GyHNfEFCGKQwRiN6rQmvSRH2iYX7npjMZn97n
17
17
  orchestrator/api/api_v1/api.py,sha256=m4iDktsSpzxUDaudkdgXeZ83a6B4wfc3pczQsa-Pb-8,2866
18
18
  orchestrator/api/api_v1/endpoints/__init__.py,sha256=GyHNfEFCGKQwRiN6rQmvSRH2iYX7npjMZn97n8XzmLU,571
19
19
  orchestrator/api/api_v1/endpoints/health.py,sha256=iaxs1XX1_250_gKNsspuULCV2GEMBjbtjsmfQTOvMAI,1284
20
- orchestrator/api/api_v1/endpoints/processes.py,sha256=Ah_Dc9ONXDpaFzLTqSNdhkGPHC7vUBkWmx1fWsEwbzo,16076
20
+ orchestrator/api/api_v1/endpoints/processes.py,sha256=3ggFkLLTqX7zwJmEmr77RbN2n-iIuNNCIpsPMeMIYFA,16149
21
21
  orchestrator/api/api_v1/endpoints/product_blocks.py,sha256=kZ6ywIOsS_S2qGq7RvZ4KzjvaS1LmwbGWR37AKRvWOw,2146
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=5s-k169podZjgGHUbVDmSQwpY_3Cs_Bbf2PPtZIkBcw,6184
25
25
  orchestrator/api/api_v1/endpoints/subscription_customer_descriptions.py,sha256=1_6LtgQleoq3M6z_W-Qz__Bj3OFUweoPrUqHMwSH6AM,3288
26
- orchestrator/api/api_v1/endpoints/subscriptions.py,sha256=V-ebvjtFEKlALx8SKX42SiNPM-GAgSMWbuaimyktUQQ,8758
26
+ orchestrator/api/api_v1/endpoints/subscriptions.py,sha256=zn_LeVfmp2uw7CszK4BvQ5n37hZccy3K2htkoDgF1sI,9809
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
29
29
  orchestrator/api/api_v1/endpoints/workflows.py,sha256=_0vhGiQeu3-z16Zi0WmuDWBs8gmed6BzRNwYH_sF6AY,1977
@@ -77,7 +77,7 @@ orchestrator/cli/generator/templates/additional_modify_steps.j2,sha256=jwle7hIvd
77
77
  orchestrator/cli/generator/templates/additional_terminate_steps.j2,sha256=jwle7hIvd-EYbnecVFmG0n-6Jpr4DearM0era8AvGYM,25
78
78
  orchestrator/cli/generator/templates/constrained_int_definitions.j2,sha256=59FKIWOeXrO0nblLmbhaFXKb4UQesQ8zFUZ1jDCp9Vk,282
79
79
  orchestrator/cli/generator/templates/create_data_head.j2,sha256=ArP9TUsbobXF4CIpckcaIYos-dgGmPeL-n7SQATvSiE,361
80
- orchestrator/cli/generator/templates/create_product.j2,sha256=ZPzY1clJ7in_UlBJf0TS94-KYuwN71kGjJn_6eM1Bo4,4838
80
+ orchestrator/cli/generator/templates/create_product.j2,sha256=P5XUD-s8ouAfoT14xv7f5VrgpWytvClrWVnBV6Bm1YI,4785
81
81
  orchestrator/cli/generator/templates/enums.j2,sha256=pmx7_DeoE4X9u83_In18C97XqZP_YwETiw54_E33p1A,298
82
82
  orchestrator/cli/generator/templates/lazy_workflow_instance.j2,sha256=BYB6LwE19OarY7_7Ovej5rqppZWtjiDkG7TDK5dYU7Y,523
83
83
  orchestrator/cli/generator/templates/list_definitions.j2,sha256=XS-F5XXy4HOdsZ-xahprwxfhU0UaiF4VeopkIxfoado,277
@@ -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=92lkqI4u3Si2tEMSTnMb5Uv08CSjqkgotgyEYqLvFeI,27302
109
+ orchestrator/db/models.py,sha256=9XOppPkXlbILM3M87wgaItsE8BKLNdnyyfeiSfYuYQ8,27502
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
@@ -159,7 +159,7 @@ orchestrator/graphql/__init__.py,sha256=avq8Yg3Jr_9pJqh7ClyIAOX7YSg1eM_AWmt5C3FR
159
159
  orchestrator/graphql/autoregistration.py,sha256=pF2jbMKG26MvYoMSa6ZpqpHjVks7_NvSRFymHTgmfjs,6342
160
160
  orchestrator/graphql/pagination.py,sha256=iqVDn3GPZpiQhEydfwkBJLURY-X8wwUphS8Lkeg0BOc,2413
161
161
  orchestrator/graphql/schema.py,sha256=gwZ3nAgKL0zlpc-aK58hSUAGPVD11Tb3aRSSK9hC39I,9204
162
- orchestrator/graphql/types.py,sha256=tF3B2n_4AmNIpNuA4Xg-kB-q9Xy7HU8opfDDPSX26nw,5045
162
+ orchestrator/graphql/types.py,sha256=CpWrDqVTQwxYkJQqidPzHEAURGirJ-6i-dlBcBwBaTI,5196
163
163
  orchestrator/graphql/extensions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
164
164
  orchestrator/graphql/extensions/model_cache.py,sha256=1uhMRjBs9eK7zJ1Y6P6BopX06822w2Yh9jliwYvG6yQ,1085
165
165
  orchestrator/graphql/extensions/stats.py,sha256=pGhEBQg45XvqZhRobcrCSGwt5AGmR3gflsm1dYiIg5g,2018
@@ -184,15 +184,15 @@ orchestrator/graphql/schemas/customer_description.py,sha256=fize71IMpkvk_rTzcqCY
184
184
  orchestrator/graphql/schemas/errors.py,sha256=VRl-Zd1FHMnscyozhfxzqeEUZ0ERAWum_Y8YwjGxwmA,203
185
185
  orchestrator/graphql/schemas/fixed_input.py,sha256=1yqYHADQRgHz8OIP7ObYsPFS-gmzfkCvEO0a-KKf7zI,513
186
186
  orchestrator/graphql/schemas/helpers.py,sha256=Kpj4kIbmoKKN35bdgUSwQvGUIbeg7VJAVMEq65YS_ik,346
187
- orchestrator/graphql/schemas/process.py,sha256=DGsOJwn2b6sz1q2CZ1aUPiazigqFsMxCkyN0Jv5LKwM,3775
188
- orchestrator/graphql/schemas/product.py,sha256=Bj9hBYLNuH3O9r3ybU0i0PFEVlMU8F_nOFyasFhaNaw,4199
187
+ orchestrator/graphql/schemas/process.py,sha256=nvD6Rvr0hnrMINdXF_rQuLF8szKJ7E-SywCFMuZsnlg,4940
188
+ orchestrator/graphql/schemas/product.py,sha256=vUCqcjrKBJj-VKSrMYPKzjmmxLMXL7alKTJ8UdUkhTg,4342
189
189
  orchestrator/graphql/schemas/product_block.py,sha256=Qk9cbA6vm7ZPrhdgPHatKRuy6TytBmxSr97McEOxAu8,2860
190
190
  orchestrator/graphql/schemas/resource_type.py,sha256=s5d_FwQXL2-Sc-IDUxTJun5qFQ4zOP4-XcHF9ql-t1g,898
191
191
  orchestrator/graphql/schemas/settings.py,sha256=drhm5VcLmUbiYAk6WUSJcyJqjNM96E6GvpxVdPAobnA,999
192
192
  orchestrator/graphql/schemas/strawberry_pydantic_patch.py,sha256=CjNUhTKdYmLiaem-WY_mzw4HASIeaZitxGF8pPocqVw,1602
193
193
  orchestrator/graphql/schemas/subscription.py,sha256=RnnxPgha_7D4Ii87cp3eyBV93_RZIryzWyVHZwyn3eA,9603
194
194
  orchestrator/graphql/schemas/version.py,sha256=HSzVg_y4Sjd5_H5rRUtu3FJKOG_8ifhvBNt_qjOtC-E,92
195
- orchestrator/graphql/schemas/workflow.py,sha256=0UWU0HGTiAC_5Wzh16clBd74JoYHrr38YIGV86q-si0,1276
195
+ orchestrator/graphql/schemas/workflow.py,sha256=WLbegRNxOfvXg4kPYrO5KPBwtHmUofAr2pvZT2JsW1c,1761
196
196
  orchestrator/graphql/utils/__init__.py,sha256=1JvenzEVW1CBa1sGVI9I8IWnnoXIkb1hneDqph9EEZY,524
197
197
  orchestrator/graphql/utils/create_resolver_error_handler.py,sha256=PpQMVwGrE9t0nZ12TwoxPxksXxEwQM7lSNPeh7qW3vk,1233
198
198
  orchestrator/graphql/utils/get_query_loaders.py,sha256=abS_HJ7K9een78gMiGq3IhwGwxQXHvZygExe0h_t9ns,815
@@ -241,6 +241,8 @@ orchestrator/migrations/versions/schema/2025-03-06_42b3d076a85b_subscription_ins
241
241
  orchestrator/migrations/versions/schema/2025-03-06_42b3d076a85b_subscription_instance_as_json_function.sql,sha256=hPldk0DAesUbHv3Qd_N7U-cAk-t1wIgxt4FOA120gQ8,1776
242
242
  orchestrator/migrations/versions/schema/2025-04-09_fc5c993a4b4a_add_cascade_constraint_on_processes_.py,sha256=6kHRNSZxUze2jy7b8uRvkt5mzsax10Z-Z3lsACtPLRM,1067
243
243
  orchestrator/migrations/versions/schema/2025-05-08_161918133bec_add_is_task_to_workflow.py,sha256=VLFDHFYRWn5ktUba0KuSPWyvjYJdfN1WypWmOPqIW18,721
244
+ orchestrator/migrations/versions/schema/2025-07-01_93fc5834c7e5_changed_timestamping_fields_in_process_steps.py,sha256=Oezd8b2qaI1Kyq-sZFVFmdzd4d9NjXrf6HtJGk11fy0,1914
245
+ orchestrator/migrations/versions/schema/2025-07-04_4b58e336d1bf_deprecating_workflow_target_in_.py,sha256=xnD6w-97R4ClS7rbmXQEXc36K3fdcXKhCy7ZZNy_FX4,742
244
246
  orchestrator/schedules/__init__.py,sha256=JnnaglfK1qYUBKI6Dd9taV-tCZIPlAdAkHtnkJDMXxY,1066
245
247
  orchestrator/schedules/resume_workflows.py,sha256=kSotzTAXjX7p9fpSYiGOpuxuTQfv54eRFAe0YSG0DHc,832
246
248
  orchestrator/schedules/scheduling.py,sha256=ehtwgpbvMOk1jhn-hHgVzg_9wLJkI6l3mRY3DcO9ZVY,1526
@@ -252,7 +254,7 @@ orchestrator/schemas/base.py,sha256=Vc444LetsINLRhG2SxW9Bq01hOzChPOhQWCImQTr-As,
252
254
  orchestrator/schemas/engine_settings.py,sha256=LF8al7tJssiilb5A4emPtUYo0tVDSaT1Lvo_DN_ttrY,1296
253
255
  orchestrator/schemas/fixed_input.py,sha256=Rth3hT5K7zYuQr1bUY_NJRzb03xEZuT1p6EvYXVNE54,1214
254
256
  orchestrator/schemas/problem_detail.py,sha256=DxiUhWv6EVXLZgdKFv0EYVnCgtkDj7xteDCR0q2f5yw,802
255
- orchestrator/schemas/process.py,sha256=NgS1eBRtO2GUCRNsvbvYyjNkR2aBdH-kwcsR_y8DfNU,2354
257
+ orchestrator/schemas/process.py,sha256=UACBNt-4g4v9Y528u-gZ-Wk7YxwJHhnI4cEu5CtQm2w,2541
256
258
  orchestrator/schemas/product.py,sha256=MhMCh058ZuS2RJq-wSmxIPUNlhQexxXIx3DSz2OmOh4,1570
257
259
  orchestrator/schemas/product_block.py,sha256=kCqvm6qadHpegMr9aWI_fYX-T7mS-5S-ldPxnGQZg7M,1519
258
260
  orchestrator/schemas/resource_type.py,sha256=VDju4XywcDDLxdpbWU62RTvR9QF8x_GRrpTlN_NE8uI,1064
@@ -264,7 +266,7 @@ orchestrator/services/celery.py,sha256=PsIgRBJsmA3vKwAUaqPq9ynLwDsXHY2ggDWc-nQAw
264
266
  orchestrator/services/fixed_inputs.py,sha256=kyz7s2HLzyDulvcq-ZqefTw1om86COvyvTjz0_5CmgI,876
265
267
  orchestrator/services/input_state.py,sha256=HF7wl9fWdaAW8pdCCqbuYoKyNj8dY0g8Ff8vXis8z5A,2211
266
268
  orchestrator/services/process_broadcast_thread.py,sha256=D44YbjF8mRqGuznkRUV4SoRn1J0lfy_x1H508GnSVlU,4649
267
- orchestrator/services/processes.py,sha256=gXWdsJK8m_KwM4g-8LBQMb1tEcjUJWWIKmqobTE7OmU,29903
269
+ orchestrator/services/processes.py,sha256=W-MCuGxRXLNIx3zn_jQQWXXFIBUrjJgXyBMsx2E0FbQ,30490
268
270
  orchestrator/services/products.py,sha256=BP4KyE8zO-8z7Trrs5T6zKBOw53S9BfBJnHWI3p6u5Y,1943
269
271
  orchestrator/services/resource_types.py,sha256=_QBy_JOW_X3aSTqH0CuLrq4zBJL0p7Q-UDJUcuK2_qc,884
270
272
  orchestrator/services/settings.py,sha256=HEWfFulgoEDwgfxGEO__QTr5fDiwNBEj1UhAeTAdbLQ,3159
@@ -280,7 +282,7 @@ orchestrator/utils/crypt.py,sha256=18eNamYWMllPkxyRtWIde3FDr3rSF74R5SAL6WsCj9Y,5
280
282
  orchestrator/utils/datetime.py,sha256=a1WQ_yvu7MA0TiaRpC5avwbOSFdrj4eMrV4a7I2sD5Q,1477
281
283
  orchestrator/utils/deprecation_logger.py,sha256=oqju7ecJcB_r7cMnldaOAA79QUZYS_h69IkDrFV9nAg,875
282
284
  orchestrator/utils/docs.py,sha256=GbyD61oKn1yVYaphUKHCBvrWEWJDTQfRc_VEbVb-zgU,6172
283
- orchestrator/utils/enrich_process.py,sha256=o_QSy5Q4wn1SMHhzVOw6bp7uhDXr7GhAIWRDDMWUVO4,4699
285
+ orchestrator/utils/enrich_process.py,sha256=sIb9jVM6MzttK1uJsRbUNMN8Aevo3erSAX0Agjbo5EA,4732
284
286
  orchestrator/utils/errors.py,sha256=6FxvXrITmRjP5bYnJJ3CxjAwA5meNjRAVYouz4TWKkU,4653
285
287
  orchestrator/utils/expose_settings.py,sha256=0NOjLBifQy4k2zUYJ31QjGQCaXEQ1zB4UtCle7XglAM,1640
286
288
  orchestrator/utils/fixed_inputs.py,sha256=pnL6I_19VMp_Bny8SYjSzVFNvTFDyeCxFFOWGhTnDiQ,2665
@@ -300,17 +302,17 @@ orchestrator/websocket/websocket_manager.py,sha256=hwlG9FDXcNU42jDNNsPMQLIyrvEpG
300
302
  orchestrator/websocket/managers/broadcast_websocket_manager.py,sha256=fwoSgTjkHJ2GmsLTU9dqQpAA9i8b1McPu7gLNzxtfG4,5401
301
303
  orchestrator/websocket/managers/memory_websocket_manager.py,sha256=lF5EEx1iFMCGEkTbItTDr88NENMSaSeG1QrJ7teoPkY,3324
302
304
  orchestrator/workflows/__init__.py,sha256=NzIGGI-8SNAwCk2YqH6sHhEWbgAY457ntDwjO15N8v4,4131
303
- orchestrator/workflows/modify_note.py,sha256=l6QtijRPv8gnHxzwTz_4nWIPcZ0FcKQh_yFbtjYEDMg,2163
305
+ orchestrator/workflows/modify_note.py,sha256=eXt5KQvrkOXf-3YEXCn2XbBLP9N-n1pUYRW2t8Odupo,2150
304
306
  orchestrator/workflows/removed_workflow.py,sha256=V0Da5TEdfLdZZKD38ig-MTp3_IuE7VGqzHHzvPYQmLI,909
305
- orchestrator/workflows/steps.py,sha256=ulpheoHaCOE1qh71Bja4KW4pItQh1Z6q4QU4tn5GtNk,6067
306
- orchestrator/workflows/utils.py,sha256=9j0hQOvGnjXUuVUMMzSIpdiyT5NzijhLh1sBXbd5QJA,14008
307
+ orchestrator/workflows/steps.py,sha256=CZxfzkG5ANJYwuYTkQ4da2RpQqIjXCtey_Uy1ezRAZ4,6479
308
+ orchestrator/workflows/utils.py,sha256=bhX9vm3oc9k6RSaESl34v4Nrh40G4Ys91INoTjZ0XVM,13966
307
309
  orchestrator/workflows/tasks/__init__.py,sha256=GyHNfEFCGKQwRiN6rQmvSRH2iYX7npjMZn97n8XzmLU,571
308
310
  orchestrator/workflows/tasks/cleanup_tasks_log.py,sha256=BfWYbPXhnLAHUJ0mlODDnjZnQQAvKCZJDVTwbwOWI04,1624
309
311
  orchestrator/workflows/tasks/resume_workflows.py,sha256=MzJqlSXUvKStkT7NGzxZyRlfAer_ezYm-kjUqaZi0yc,2359
310
312
  orchestrator/workflows/tasks/validate_product_type.py,sha256=paG-NAY1bdde3Adt8zItkcBKf5Pxw6f5ngGW6an6dYU,3192
311
313
  orchestrator/workflows/tasks/validate_products.py,sha256=GZJBoFF-WMphS7ghMs2-gqvV2iL1F0POhk0uSNt93n0,8510
312
314
  orchestrator/workflows/translations/en-GB.json,sha256=ST53HxkphFLTMjFHonykDBOZ7-P_KxksktZU3GbxLt0,846
313
- orchestrator_core-4.1.0rc2.dist-info/licenses/LICENSE,sha256=b-aA5OZQuuBATmLKo_mln8CQrDPPhg3ghLzjPjLn4Tg,11409
314
- orchestrator_core-4.1.0rc2.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
315
- orchestrator_core-4.1.0rc2.dist-info/METADATA,sha256=Ls33kV9YCHIfgDAD1pZ-7e4_EcsKBpWZ8iRSZCDQk-Y,5073
316
- orchestrator_core-4.1.0rc2.dist-info/RECORD,,
315
+ orchestrator_core-4.2.0rc1.dist-info/licenses/LICENSE,sha256=b-aA5OZQuuBATmLKo_mln8CQrDPPhg3ghLzjPjLn4Tg,11409
316
+ orchestrator_core-4.2.0rc1.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
317
+ orchestrator_core-4.2.0rc1.dist-info/METADATA,sha256=Kkb7hvUNxHfux_npyFWEVr7FVci_N84-9PRvo-ogOqk,5963
318
+ orchestrator_core-4.2.0rc1.dist-info/RECORD,,
@@ -1,118 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: orchestrator-core
3
- Version: 4.1.0rc2
4
- Summary: This is the orchestrator workflow engine.
5
- Requires-Python: >=3.11,<3.14
6
- Classifier: Intended Audience :: Information Technology
7
- Classifier: Intended Audience :: System Administrators
8
- Classifier: Operating System :: OS Independent
9
- Classifier: Programming Language :: Python :: 3
10
- Classifier: Programming Language :: Python
11
- Classifier: Topic :: Internet
12
- Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
13
- Classifier: Topic :: Software Development :: Libraries :: Python Modules
14
- Classifier: Topic :: Software Development :: Libraries
15
- Classifier: Topic :: Software Development
16
- Classifier: Typing :: Typed
17
- Classifier: Development Status :: 5 - Production/Stable
18
- Classifier: Environment :: Web Environment
19
- Classifier: Framework :: AsyncIO
20
- Classifier: Framework :: FastAPI
21
- Classifier: Intended Audience :: Developers
22
- Classifier: Intended Audience :: Telecommunications Industry
23
- Classifier: License :: OSI Approved :: Apache Software License
24
- Classifier: Programming Language :: Python :: 3 :: Only
25
- Classifier: Programming Language :: Python :: 3.13
26
- Classifier: Programming Language :: Python :: 3.12
27
- Classifier: Programming Language :: Python :: 3.11
28
- Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
29
- Classifier: Topic :: Internet :: WWW/HTTP
30
- License-File: LICENSE
31
- Requires-Dist: alembic==1.16.1
32
- Requires-Dist: anyio>=3.7.0
33
- Requires-Dist: click==8.*
34
- Requires-Dist: deprecated
35
- Requires-Dist: deepmerge==2.0
36
- Requires-Dist: fastapi~=0.115.2
37
- Requires-Dist: fastapi-etag==0.4.0
38
- Requires-Dist: more-itertools~=10.7.0
39
- Requires-Dist: itsdangerous
40
- Requires-Dist: Jinja2==3.1.6
41
- Requires-Dist: orjson==3.10.18
42
- Requires-Dist: prometheus-client==0.22.0
43
- Requires-Dist: psycopg2-binary==2.9.10
44
- Requires-Dist: pydantic[email]~=2.8.2
45
- Requires-Dist: pydantic-settings~=2.9.1
46
- Requires-Dist: python-dateutil==2.8.2
47
- Requires-Dist: python-rapidjson>=1.18,<1.21
48
- Requires-Dist: pytz==2025.2
49
- Requires-Dist: redis==5.1.1
50
- Requires-Dist: schedule==1.1.0
51
- Requires-Dist: semver==3.0.4
52
- Requires-Dist: sentry-sdk[fastapi]~=2.29.1
53
- Requires-Dist: SQLAlchemy==2.0.41
54
- Requires-Dist: SQLAlchemy-Utils==0.41.2
55
- Requires-Dist: structlog
56
- Requires-Dist: typer==0.15.4
57
- Requires-Dist: uvicorn[standard]~=0.34.0
58
- Requires-Dist: nwa-stdlib~=1.9.0
59
- Requires-Dist: oauth2-lib~=2.4.0
60
- Requires-Dist: tabulate==0.9.0
61
- Requires-Dist: strawberry-graphql>=0.246.2
62
- Requires-Dist: pydantic-forms>=1.4.0, <=2.1.0
63
- Requires-Dist: celery~=5.5.1 ; extra == "celery"
64
- Requires-Dist: toml ; extra == "dev"
65
- Requires-Dist: bumpversion ; extra == "dev"
66
- Requires-Dist: mypy_extensions ; extra == "dev"
67
- Requires-Dist: pre-commit ; extra == "dev"
68
- Requires-Dist: pydocstyle ; extra == "dev"
69
- Requires-Dist: python-dotenv ; extra == "dev"
70
- Requires-Dist: watchdog ; extra == "dev"
71
- Requires-Dist: mkdocs ; extra == "doc"
72
- Requires-Dist: mkdocs-material[imaging] ; extra == "doc"
73
- Requires-Dist: mkdocs-render-swagger-plugin ; extra == "doc"
74
- Requires-Dist: mkdocs-include-markdown-plugin ; extra == "doc"
75
- Requires-Dist: mkdocstrings[python] ; extra == "doc"
76
- Requires-Dist: mkdocs-open-in-new-tab ; extra == "doc"
77
- Requires-Dist: mkdocs-macros-plugin ; extra == "doc"
78
- Requires-Dist: mkdocs-embed-external-markdown ; extra == "doc"
79
- Requires-Dist: apache-license-check ; extra == "test"
80
- Requires-Dist: black ; extra == "test"
81
- Requires-Dist: blinker ; extra == "test"
82
- Requires-Dist: deepdiff ; extra == "test"
83
- Requires-Dist: dirty-equals ; extra == "test"
84
- Requires-Dist: jsonref ; extra == "test"
85
- Requires-Dist: mypy==1.9 ; extra == "test"
86
- Requires-Dist: pyinstrument ; extra == "test"
87
- Requires-Dist: pytest==8.3.5 ; extra == "test"
88
- Requires-Dist: pytest-asyncio==0.21.2 ; extra == "test"
89
- Requires-Dist: pytest-codspeed ; extra == "test"
90
- Requires-Dist: pytest-cov ; extra == "test"
91
- Requires-Dist: pytest-httpx ; extra == "test"
92
- Requires-Dist: pytest-xdist ; extra == "test"
93
- Requires-Dist: requests-mock ; extra == "test"
94
- Requires-Dist: ruff ; extra == "test"
95
- Requires-Dist: sqlalchemy[mypy] ; extra == "test"
96
- Requires-Dist: urllib3-mock ; extra == "test"
97
- Requires-Dist: types-Deprecated ; extra == "test"
98
- Requires-Dist: types-Jinja2 ; extra == "test"
99
- Requires-Dist: types-aiofiles ; extra == "test"
100
- Requires-Dist: types-certifi ; extra == "test"
101
- Requires-Dist: types-click ; extra == "test"
102
- Requires-Dist: types-itsdangerous ; extra == "test"
103
- Requires-Dist: types-orjson ; extra == "test"
104
- Requires-Dist: types-python-dateutil ; extra == "test"
105
- Requires-Dist: types-pytz ; extra == "test"
106
- Requires-Dist: types-redis ; extra == "test"
107
- Requires-Dist: types-requests ; extra == "test"
108
- Requires-Dist: types-setuptools ; extra == "test"
109
- Requires-Dist: types-tabulate ; extra == "test"
110
- Requires-Dist: types-toml ; extra == "test"
111
- Requires-Dist: types-ujson ; extra == "test"
112
- Requires-Dist: types-PyYAML ; extra == "test"
113
- Project-URL: Documentation, https://workfloworchestrator.org/orchestrator-core/
114
- Project-URL: Source, https://github.com/workfloworchestrator/orchestrator-core
115
- Provides-Extra: celery
116
- Provides-Extra: dev
117
- Provides-Extra: doc
118
- Provides-Extra: test