orchestrator-core 4.1.0rc2__py3-none-any.whl → 4.2.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.
- orchestrator/__init__.py +1 -1
- orchestrator/api/api_v1/endpoints/processes.py +6 -2
- orchestrator/api/api_v1/endpoints/subscriptions.py +25 -2
- orchestrator/cli/database.py +8 -1
- orchestrator/cli/domain_gen_helpers/helpers.py +44 -2
- orchestrator/cli/domain_gen_helpers/product_block_helpers.py +35 -15
- orchestrator/cli/domain_gen_helpers/resource_type_helpers.py +5 -5
- orchestrator/cli/domain_gen_helpers/types.py +7 -1
- orchestrator/cli/generator/templates/create_product.j2 +1 -2
- orchestrator/cli/migrate_domain_models.py +16 -5
- orchestrator/db/models.py +6 -3
- orchestrator/graphql/schemas/process.py +21 -2
- orchestrator/graphql/schemas/product.py +8 -9
- orchestrator/graphql/schemas/workflow.py +9 -0
- orchestrator/graphql/types.py +7 -1
- orchestrator/migrations/versions/schema/2025-07-01_93fc5834c7e5_changed_timestamping_fields_in_process_steps.py +65 -0
- orchestrator/migrations/versions/schema/2025-07-04_4b58e336d1bf_deprecating_workflow_target_in_.py +30 -0
- orchestrator/schemas/process.py +5 -1
- orchestrator/services/processes.py +11 -2
- orchestrator/utils/enrich_process.py +4 -2
- orchestrator/utils/errors.py +2 -1
- orchestrator/workflow.py +7 -2
- orchestrator/workflows/modify_note.py +1 -1
- orchestrator/workflows/steps.py +14 -8
- orchestrator/workflows/utils.py +3 -3
- orchestrator_core-4.2.0rc2.dist-info/METADATA +167 -0
- {orchestrator_core-4.1.0rc2.dist-info → orchestrator_core-4.2.0rc2.dist-info}/RECORD +29 -27
- orchestrator_core-4.1.0rc2.dist-info/METADATA +0 -118
- {orchestrator_core-4.1.0rc2.dist-info → orchestrator_core-4.2.0rc2.dist-info}/WHEEL +0 -0
- {orchestrator_core-4.1.0rc2.dist-info → orchestrator_core-4.2.0rc2.dist-info}/licenses/LICENSE +0 -0
|
@@ -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 ###
|
orchestrator/migrations/versions/schema/2025-07-04_4b58e336d1bf_deprecating_workflow_target_in_.py
ADDED
|
@@ -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
|
+
)
|
orchestrator/schemas/process.py
CHANGED
|
@@ -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 =
|
|
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
|
-
"
|
|
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.
|
|
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.
|
|
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.
|
|
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/utils/errors.py
CHANGED
|
@@ -128,12 +128,13 @@ def _(err: Exception) -> ErrorDict:
|
|
|
128
128
|
# We can't dispatch on ApiException, see is_api_exception docstring
|
|
129
129
|
if is_api_exception(err):
|
|
130
130
|
err = cast(ApiException, err)
|
|
131
|
+
headers = err.headers or {}
|
|
131
132
|
return {
|
|
132
133
|
"class": type(err).__name__,
|
|
133
134
|
"error": err.reason,
|
|
134
135
|
"status_code": err.status,
|
|
135
136
|
"body": err.body,
|
|
136
|
-
"headers": "\n".join(f"{k}: {v}" for k, v in
|
|
137
|
+
"headers": "\n".join(f"{k}: {v}" for k, v in headers.items()),
|
|
137
138
|
"traceback": show_ex(err),
|
|
138
139
|
}
|
|
139
140
|
|
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(
|
|
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(
|
|
56
|
+
return init >> store_process_subscription() >> store_subscription_note >> done
|
orchestrator/workflows/steps.py
CHANGED
|
@@ -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
|
|
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
|
|
127
|
+
store_process_subscription_relationship(process_id, subscription_id)
|
|
122
128
|
|
|
123
129
|
return _store_process_subscription
|
|
124
130
|
|
orchestrator/workflows/utils.py
CHANGED
|
@@ -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(
|
|
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(
|
|
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(
|
|
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.0rc2
|
|
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
|
+
[](https://pepy.tech/project/orchestrator-core)
|
|
74
|
+
[](https://codecov.io/gh/workfloworchestrator/orchestrator-core)
|
|
75
|
+
[](https://pypi.org/project/orchestrator-core)
|
|
76
|
+
[](https://pypi.org/project/orchestrator-core)
|
|
77
|
+

|
|
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
|
+
|