orchestrator-core 3.1.2rc3__py3-none-any.whl → 3.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 +2 -2
- orchestrator/api/api_v1/api.py +1 -1
- orchestrator/api/api_v1/endpoints/processes.py +6 -9
- orchestrator/api/api_v1/endpoints/settings.py +1 -1
- orchestrator/api/api_v1/endpoints/subscriptions.py +1 -1
- orchestrator/app.py +1 -1
- orchestrator/cli/database.py +1 -1
- orchestrator/cli/generator/generator/migration.py +2 -5
- orchestrator/cli/migrate_tasks.py +13 -0
- orchestrator/config/assignee.py +1 -1
- orchestrator/db/__init__.py +2 -0
- orchestrator/db/loaders.py +51 -3
- orchestrator/db/models.py +14 -1
- orchestrator/db/queries/__init__.py +0 -0
- orchestrator/db/queries/subscription.py +85 -0
- orchestrator/db/queries/subscription_instance.py +28 -0
- orchestrator/devtools/populator.py +1 -1
- orchestrator/domain/__init__.py +2 -3
- orchestrator/domain/base.py +236 -49
- orchestrator/domain/context_cache.py +62 -0
- orchestrator/domain/helpers.py +41 -1
- orchestrator/domain/lifecycle.py +1 -1
- orchestrator/domain/subscription_instance_transform.py +114 -0
- orchestrator/graphql/resolvers/process.py +3 -3
- orchestrator/graphql/resolvers/product.py +2 -2
- orchestrator/graphql/resolvers/product_block.py +2 -2
- orchestrator/graphql/resolvers/resource_type.py +2 -2
- orchestrator/graphql/resolvers/workflow.py +2 -2
- orchestrator/graphql/schema.py +1 -1
- orchestrator/graphql/types.py +1 -1
- orchestrator/graphql/utils/get_query_loaders.py +6 -48
- orchestrator/graphql/utils/get_subscription_product_blocks.py +21 -1
- orchestrator/migrations/env.py +1 -1
- orchestrator/migrations/helpers.py +6 -6
- orchestrator/migrations/versions/schema/2025-03-06_42b3d076a85b_subscription_instance_as_json_function.py +33 -0
- orchestrator/migrations/versions/schema/2025-03-06_42b3d076a85b_subscription_instance_as_json_function.sql +40 -0
- orchestrator/schemas/engine_settings.py +1 -1
- orchestrator/schemas/subscription.py +1 -1
- orchestrator/security.py +1 -1
- orchestrator/services/celery.py +1 -1
- orchestrator/services/processes.py +28 -9
- orchestrator/services/products.py +1 -1
- orchestrator/services/subscriptions.py +37 -7
- orchestrator/services/tasks.py +1 -1
- orchestrator/settings.py +5 -23
- orchestrator/targets.py +1 -1
- orchestrator/types.py +1 -1
- orchestrator/utils/errors.py +1 -1
- orchestrator/utils/functional.py +9 -0
- orchestrator/utils/redis.py +6 -0
- orchestrator/utils/state.py +1 -1
- orchestrator/websocket/websocket_manager.py +1 -1
- orchestrator/workflow.py +19 -4
- orchestrator/workflows/modify_note.py +1 -1
- orchestrator/workflows/steps.py +1 -1
- orchestrator/workflows/tasks/cleanup_tasks_log.py +1 -1
- orchestrator/workflows/tasks/resume_workflows.py +1 -1
- orchestrator/workflows/tasks/validate_product_type.py +1 -1
- orchestrator/workflows/tasks/validate_products.py +1 -1
- orchestrator/workflows/utils.py +40 -5
- {orchestrator_core-3.1.2rc3.dist-info → orchestrator_core-3.2.0rc1.dist-info}/METADATA +7 -7
- {orchestrator_core-3.1.2rc3.dist-info → orchestrator_core-3.2.0rc1.dist-info}/RECORD +65 -58
- {orchestrator_core-3.1.2rc3.dist-info → orchestrator_core-3.2.0rc1.dist-info}/WHEEL +1 -1
- /orchestrator/migrations/versions/schema/{2025-10-19_4fjdn13f83ga_add_validate_product_type_task.py → 2025-01-19_4fjdn13f83ga_add_validate_product_type_task.py} +0 -0
- {orchestrator_core-3.1.2rc3.dist-info → orchestrator_core-3.2.0rc1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2019-2020 SURF.
|
|
1
|
+
# Copyright 2019-2020 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
|
|
@@ -22,6 +22,7 @@ from uuid import UUID
|
|
|
22
22
|
|
|
23
23
|
import more_itertools
|
|
24
24
|
import structlog
|
|
25
|
+
from more_itertools import first
|
|
25
26
|
from sqlalchemy import Text, cast, not_, select
|
|
26
27
|
from sqlalchemy.exc import SQLAlchemyError
|
|
27
28
|
from sqlalchemy.orm import Query, aliased, joinedload
|
|
@@ -41,7 +42,11 @@ from orchestrator.db.models import (
|
|
|
41
42
|
SubscriptionInstanceRelationTable,
|
|
42
43
|
SubscriptionMetadataTable,
|
|
43
44
|
)
|
|
45
|
+
from orchestrator.db.queries.subscription import (
|
|
46
|
+
eagerload_all_subscription_instances_only_inuseby,
|
|
47
|
+
)
|
|
44
48
|
from orchestrator.domain.base import SubscriptionModel
|
|
49
|
+
from orchestrator.domain.context_cache import cache_subscription_models
|
|
45
50
|
from orchestrator.targets import Target
|
|
46
51
|
from orchestrator.types import SubscriptionLifecycle
|
|
47
52
|
from orchestrator.utils.datetime import nowtz
|
|
@@ -594,13 +599,15 @@ def convert_to_in_use_by_relation(obj: Any) -> dict[str, str]:
|
|
|
594
599
|
|
|
595
600
|
def build_extended_domain_model(subscription_model: SubscriptionModel) -> dict:
|
|
596
601
|
"""Create a subscription dict from the SubscriptionModel with additional keys."""
|
|
597
|
-
|
|
602
|
+
from orchestrator.settings import app_settings
|
|
603
|
+
|
|
604
|
+
stmt = select(SubscriptionCustomerDescriptionTable).where(
|
|
598
605
|
SubscriptionCustomerDescriptionTable.subscription_id == subscription_model.subscription_id
|
|
599
606
|
)
|
|
600
607
|
customer_descriptions = list(db.session.scalars(stmt))
|
|
601
608
|
|
|
602
|
-
|
|
603
|
-
|
|
609
|
+
with cache_subscription_models():
|
|
610
|
+
subscription = subscription_model.model_dump()
|
|
604
611
|
|
|
605
612
|
def inject_in_use_by_ids(path_to_block: str) -> None:
|
|
606
613
|
if not (in_use_by_subs := getattr_in(subscription_model, f"{path_to_block}.in_use_by")):
|
|
@@ -611,15 +618,38 @@ def build_extended_domain_model(subscription_model: SubscriptionModel) -> dict:
|
|
|
611
618
|
update_in(subscription, f"{path_to_block}.in_use_by_ids", in_use_by_ids)
|
|
612
619
|
update_in(subscription, f"{path_to_block}.in_use_by_relations", in_use_by_relations)
|
|
613
620
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
621
|
+
if app_settings.ENABLE_SUBSCRIPTION_MODEL_OPTIMIZATIONS:
|
|
622
|
+
# TODO #900 remove toggle and make this path the default
|
|
623
|
+
# query all subscription instances and inject the in_use_by_ids/in_use_by_relations into the subscription dict.
|
|
624
|
+
instance_to_in_use_by = {
|
|
625
|
+
instance.subscription_instance_id: instance.in_use_by
|
|
626
|
+
for instance in eagerload_all_subscription_instances_only_inuseby(subscription_model.subscription_id)
|
|
627
|
+
}
|
|
628
|
+
inject_in_use_by_ids_v2(subscription, instance_to_in_use_by)
|
|
629
|
+
else:
|
|
630
|
+
# find all product blocks, check if they have in_use_by and inject the in_use_by_ids into the subscription dict.
|
|
631
|
+
for path in product_block_paths(subscription):
|
|
632
|
+
inject_in_use_by_ids(path)
|
|
617
633
|
|
|
618
634
|
subscription["customer_descriptions"] = customer_descriptions
|
|
619
635
|
|
|
620
636
|
return subscription
|
|
621
637
|
|
|
622
638
|
|
|
639
|
+
def inject_in_use_by_ids_v2(dikt: dict, instance_to_in_use_by: dict[UUID, Sequence[SubscriptionInstanceTable]]) -> None:
|
|
640
|
+
for value in dikt.values():
|
|
641
|
+
if isinstance(value, dict):
|
|
642
|
+
inject_in_use_by_ids_v2(value, instance_to_in_use_by)
|
|
643
|
+
elif isinstance(value, list) and isinstance(first(value, None), dict):
|
|
644
|
+
for item in value:
|
|
645
|
+
inject_in_use_by_ids_v2(item, instance_to_in_use_by)
|
|
646
|
+
|
|
647
|
+
if subscription_instance_id := dikt.get("subscription_instance_id"):
|
|
648
|
+
in_use_by_subs = instance_to_in_use_by[subscription_instance_id]
|
|
649
|
+
dikt["in_use_by_ids"] = [i.subscription_instance_id for i in in_use_by_subs]
|
|
650
|
+
dikt["in_use_by_relations"] = [convert_to_in_use_by_relation(instance) for instance in in_use_by_subs]
|
|
651
|
+
|
|
652
|
+
|
|
623
653
|
def format_special_types(subscription: dict) -> dict:
|
|
624
654
|
"""Modifies the subscription dict in-place, formatting special types to string.
|
|
625
655
|
|
orchestrator/services/tasks.py
CHANGED
orchestrator/settings.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2019-2020 SURF.
|
|
1
|
+
# Copyright 2019-2020 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
|
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
|
|
14
14
|
import secrets
|
|
15
15
|
import string
|
|
16
|
-
import warnings
|
|
17
16
|
from pathlib import Path
|
|
18
17
|
from typing import Literal
|
|
19
18
|
|
|
@@ -24,10 +23,6 @@ from oauth2_lib.settings import oauth2lib_settings
|
|
|
24
23
|
from pydantic_forms.types import strEnum
|
|
25
24
|
|
|
26
25
|
|
|
27
|
-
class OrchestratorDeprecationWarning(DeprecationWarning):
|
|
28
|
-
pass
|
|
29
|
-
|
|
30
|
-
|
|
31
26
|
class ExecutorType(strEnum):
|
|
32
27
|
WORKER = "celery"
|
|
33
28
|
THREADPOOL = "threadpool"
|
|
@@ -54,7 +49,7 @@ class AppSettings(BaseSettings):
|
|
|
54
49
|
EXECUTOR: str = ExecutorType.THREADPOOL
|
|
55
50
|
WORKFLOWS_SWAGGER_HOST: str = "localhost"
|
|
56
51
|
WORKFLOWS_GUI_URI: str = "http://localhost:3000"
|
|
57
|
-
DATABASE_URI: PostgresDsn = "postgresql
|
|
52
|
+
DATABASE_URI: PostgresDsn = "postgresql://nwa:nwa@localhost/orchestrator-core" # type: ignore
|
|
58
53
|
MAX_WORKERS: int = 5
|
|
59
54
|
MAIL_SERVER: str = "localhost"
|
|
60
55
|
MAIL_PORT: int = 25
|
|
@@ -92,22 +87,9 @@ class AppSettings(BaseSettings):
|
|
|
92
87
|
ENABLE_GRAPHQL_STATS_EXTENSION: bool = False
|
|
93
88
|
VALIDATE_OUT_OF_SYNC_SUBSCRIPTIONS: bool = False
|
|
94
89
|
FILTER_BY_MODE: Literal["partial", "exact"] = "exact"
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
self.DATABASE_URI = PostgresDsn(convert_database_uri(str(self.DATABASE_URI)))
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
def convert_database_uri(db_uri: str) -> str:
|
|
102
|
-
if db_uri.startswith(("postgresql://", "postgresql+psycopg2://")):
|
|
103
|
-
db_uri = "postgresql+psycopg" + db_uri[db_uri.find("://") :]
|
|
104
|
-
warnings.filterwarnings("always", category=OrchestratorDeprecationWarning)
|
|
105
|
-
warnings.warn(
|
|
106
|
-
"DATABASE_URI converted to postgresql+psycopg:// format, please update your enviroment variable",
|
|
107
|
-
OrchestratorDeprecationWarning,
|
|
108
|
-
stacklevel=2,
|
|
109
|
-
)
|
|
110
|
-
return db_uri
|
|
90
|
+
ENABLE_SUBSCRIPTION_MODEL_OPTIMIZATIONS: bool = (
|
|
91
|
+
True # True=ignore cache + optimized DB queries; False=use cache + unoptimized DB queries. Remove in #900
|
|
92
|
+
)
|
|
111
93
|
|
|
112
94
|
|
|
113
95
|
app_settings = AppSettings()
|
orchestrator/targets.py
CHANGED
orchestrator/types.py
CHANGED
orchestrator/utils/errors.py
CHANGED
orchestrator/utils/functional.py
CHANGED
|
@@ -241,3 +241,12 @@ def to_ranges(i: Iterable[int]) -> Iterable[range]:
|
|
|
241
241
|
for _, g in itertools.groupby(enumerate(i), lambda t: t[1] - t[0]):
|
|
242
242
|
group = list(g)
|
|
243
243
|
yield range(group[0][1], group[-1][1] + 1)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
K = TypeVar("K")
|
|
247
|
+
V = TypeVar("V")
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def group_by_key(items: Iterable[tuple[K, V]]) -> dict[K, list[V]]:
|
|
251
|
+
groups = itertools.groupby(items, key=lambda item: item[0])
|
|
252
|
+
return {key: [item[1] for item in group] for key, group in groups}
|
orchestrator/utils/redis.py
CHANGED
|
@@ -55,6 +55,12 @@ def to_redis(subscription: dict[str, Any]) -> str | None:
|
|
|
55
55
|
|
|
56
56
|
def from_redis(subscription_id: UUID) -> tuple[PY_JSON_TYPES, str] | None:
|
|
57
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.warning("Using SubscriptionModel optimization, not loading subscription from cache")
|
|
62
|
+
return None
|
|
63
|
+
|
|
58
64
|
if caching_models_enabled():
|
|
59
65
|
log.debug("Try to retrieve subscription from cache")
|
|
60
66
|
obj = cache.get(f"orchestrator:domain:{subscription_id}")
|
orchestrator/utils/state.py
CHANGED
orchestrator/workflow.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2019-
|
|
1
|
+
# Copyright 2019-2025 SURF, GÉANT, ESnet.
|
|
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
|
|
@@ -39,6 +39,7 @@ from structlog.contextvars import bound_contextvars
|
|
|
39
39
|
from structlog.stdlib import BoundLogger
|
|
40
40
|
|
|
41
41
|
from nwastdlib import const, identity
|
|
42
|
+
from oauth2_lib.fastapi import OIDCUserModel
|
|
42
43
|
from orchestrator.config.assignee import Assignee
|
|
43
44
|
from orchestrator.db import db, transactional
|
|
44
45
|
from orchestrator.services.settings import get_engine_settings
|
|
@@ -89,6 +90,7 @@ class Workflow(Protocol):
|
|
|
89
90
|
__qualname__: str
|
|
90
91
|
name: str
|
|
91
92
|
description: str
|
|
93
|
+
authorize_callback: Callable[[OIDCUserModel | None], bool]
|
|
92
94
|
initial_input_form: InputFormGenerator | None = None
|
|
93
95
|
target: Target
|
|
94
96
|
steps: StepList
|
|
@@ -178,12 +180,18 @@ def _handle_simple_input_form_generator(f: StateInputStepFunc) -> StateInputForm
|
|
|
178
180
|
return form_generator
|
|
179
181
|
|
|
180
182
|
|
|
183
|
+
def allow(_: OIDCUserModel | None) -> bool:
|
|
184
|
+
"""Default function to return True in absence of user-defined authorize function."""
|
|
185
|
+
return True
|
|
186
|
+
|
|
187
|
+
|
|
181
188
|
def make_workflow(
|
|
182
189
|
f: Callable,
|
|
183
190
|
description: str,
|
|
184
191
|
initial_input_form: InputStepFunc | None,
|
|
185
192
|
target: Target,
|
|
186
193
|
steps: StepList,
|
|
194
|
+
authorize_callback: Callable[[OIDCUserModel | None], bool] | None = None,
|
|
187
195
|
) -> Workflow:
|
|
188
196
|
@functools.wraps(f)
|
|
189
197
|
def wrapping_function() -> NoReturn:
|
|
@@ -193,6 +201,7 @@ def make_workflow(
|
|
|
193
201
|
|
|
194
202
|
wrapping_function.name = f.__name__ # default, will be changed by LazyWorkflowInstance
|
|
195
203
|
wrapping_function.description = description
|
|
204
|
+
wrapping_function.authorize_callback = allow if authorize_callback is None else authorize_callback
|
|
196
205
|
|
|
197
206
|
if initial_input_form is None:
|
|
198
207
|
# We always need a form to prevent starting a workflow when no input is needed.
|
|
@@ -459,7 +468,10 @@ def focussteps(key: str) -> Callable[[Step | StepList], StepList]:
|
|
|
459
468
|
|
|
460
469
|
|
|
461
470
|
def workflow(
|
|
462
|
-
description: str,
|
|
471
|
+
description: str,
|
|
472
|
+
initial_input_form: InputStepFunc | None = None,
|
|
473
|
+
target: Target = Target.SYSTEM,
|
|
474
|
+
authorize_callback: Callable[[OIDCUserModel | None], bool] | None = None,
|
|
463
475
|
) -> Callable[[Callable[[], StepList]], Workflow]:
|
|
464
476
|
"""Transform an initial_input_form and a step list into a workflow.
|
|
465
477
|
|
|
@@ -479,7 +491,9 @@ def workflow(
|
|
|
479
491
|
initial_input_form_in_form_inject_args = form_inject_args(initial_input_form)
|
|
480
492
|
|
|
481
493
|
def _workflow(f: Callable[[], StepList]) -> Workflow:
|
|
482
|
-
return make_workflow(
|
|
494
|
+
return make_workflow(
|
|
495
|
+
f, description, initial_input_form_in_form_inject_args, target, f(), authorize_callback=authorize_callback
|
|
496
|
+
)
|
|
483
497
|
|
|
484
498
|
return _workflow
|
|
485
499
|
|
|
@@ -491,13 +505,14 @@ class ProcessStat:
|
|
|
491
505
|
state: Process
|
|
492
506
|
log: StepList
|
|
493
507
|
current_user: str
|
|
508
|
+
user_model: OIDCUserModel | None = None
|
|
494
509
|
|
|
495
510
|
def update(self, **vs: Any) -> ProcessStat:
|
|
496
511
|
"""Update ProcessStat.
|
|
497
512
|
|
|
498
513
|
>>> pstat = ProcessStat('', None, {}, [], "")
|
|
499
514
|
>>> pstat.update(state={"a": "b"})
|
|
500
|
-
ProcessStat(process_id='', workflow=None, state={'a': 'b'}, log=[], current_user='')
|
|
515
|
+
ProcessStat(process_id='', workflow=None, state={'a': 'b'}, log=[], current_user='', user_model=None)
|
|
501
516
|
"""
|
|
502
517
|
return ProcessStat(**{**asdict(self), **vs})
|
|
503
518
|
|
orchestrator/workflows/steps.py
CHANGED
orchestrator/workflows/utils.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2019-
|
|
1
|
+
# Copyright 2019-2025 SURF, GÉANT, ESnet.
|
|
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
|
|
@@ -20,6 +20,7 @@ from more_itertools import first_true
|
|
|
20
20
|
from pydantic import field_validator, model_validator
|
|
21
21
|
from sqlalchemy import select
|
|
22
22
|
|
|
23
|
+
from oauth2_lib.fastapi import OIDCUserModel
|
|
23
24
|
from orchestrator.db import ProductTable, SubscriptionTable, db
|
|
24
25
|
from orchestrator.forms.validators import ProductId
|
|
25
26
|
from orchestrator.services import subscriptions
|
|
@@ -30,7 +31,7 @@ from orchestrator.utils.errors import StaleDataError
|
|
|
30
31
|
from orchestrator.utils.redis import caching_models_enabled
|
|
31
32
|
from orchestrator.utils.state import form_inject_args
|
|
32
33
|
from orchestrator.utils.validate_data_version import validate_data_version
|
|
33
|
-
from orchestrator.workflow import StepList, Workflow, conditional, done, init, make_workflow, step
|
|
34
|
+
from orchestrator.workflow import Step, StepList, Workflow, begin, conditional, done, init, make_workflow, step
|
|
34
35
|
from orchestrator.workflows.steps import (
|
|
35
36
|
cache_domain_models,
|
|
36
37
|
refresh_subscription_search_index,
|
|
@@ -205,6 +206,7 @@ def create_workflow(
|
|
|
205
206
|
initial_input_form: InputStepFunc | None = None,
|
|
206
207
|
status: SubscriptionLifecycle = SubscriptionLifecycle.ACTIVE,
|
|
207
208
|
additional_steps: StepList | None = None,
|
|
209
|
+
authorize_callback: Callable[[OIDCUserModel | None], bool] | None = None,
|
|
208
210
|
) -> Callable[[Callable[[], StepList]], Workflow]:
|
|
209
211
|
"""Transform an initial_input_form and a step list into a workflow with a target=Target.CREATE.
|
|
210
212
|
|
|
@@ -231,7 +233,14 @@ def create_workflow(
|
|
|
231
233
|
>> done
|
|
232
234
|
)
|
|
233
235
|
|
|
234
|
-
return make_workflow(
|
|
236
|
+
return make_workflow(
|
|
237
|
+
f,
|
|
238
|
+
description,
|
|
239
|
+
create_initial_input_form_generator,
|
|
240
|
+
Target.CREATE,
|
|
241
|
+
steplist,
|
|
242
|
+
authorize_callback=authorize_callback,
|
|
243
|
+
)
|
|
235
244
|
|
|
236
245
|
return _create_workflow
|
|
237
246
|
|
|
@@ -240,6 +249,7 @@ def modify_workflow(
|
|
|
240
249
|
description: str,
|
|
241
250
|
initial_input_form: InputStepFunc | None = None,
|
|
242
251
|
additional_steps: StepList | None = None,
|
|
252
|
+
authorize_callback: Callable[[OIDCUserModel | None], bool] | None = None,
|
|
243
253
|
) -> Callable[[Callable[[], StepList]], Workflow]:
|
|
244
254
|
"""Transform an initial_input_form and a step list into a workflow.
|
|
245
255
|
|
|
@@ -269,7 +279,14 @@ def modify_workflow(
|
|
|
269
279
|
>> done
|
|
270
280
|
)
|
|
271
281
|
|
|
272
|
-
return make_workflow(
|
|
282
|
+
return make_workflow(
|
|
283
|
+
f,
|
|
284
|
+
description,
|
|
285
|
+
wrapped_modify_initial_input_form_generator,
|
|
286
|
+
Target.MODIFY,
|
|
287
|
+
steplist,
|
|
288
|
+
authorize_callback=authorize_callback,
|
|
289
|
+
)
|
|
273
290
|
|
|
274
291
|
return _modify_workflow
|
|
275
292
|
|
|
@@ -278,6 +295,7 @@ def terminate_workflow(
|
|
|
278
295
|
description: str,
|
|
279
296
|
initial_input_form: InputStepFunc | None = None,
|
|
280
297
|
additional_steps: StepList | None = None,
|
|
298
|
+
authorize_callback: Callable[[OIDCUserModel | None], bool] | None = None,
|
|
281
299
|
) -> Callable[[Callable[[], StepList]], Workflow]:
|
|
282
300
|
"""Transform an initial_input_form and a step list into a workflow.
|
|
283
301
|
|
|
@@ -308,7 +326,14 @@ def terminate_workflow(
|
|
|
308
326
|
>> done
|
|
309
327
|
)
|
|
310
328
|
|
|
311
|
-
return make_workflow(
|
|
329
|
+
return make_workflow(
|
|
330
|
+
f,
|
|
331
|
+
description,
|
|
332
|
+
wrapped_terminate_initial_input_form_generator,
|
|
333
|
+
Target.TERMINATE,
|
|
334
|
+
steplist,
|
|
335
|
+
authorize_callback=authorize_callback,
|
|
336
|
+
)
|
|
312
337
|
|
|
313
338
|
return _terminate_workflow
|
|
314
339
|
|
|
@@ -344,6 +369,16 @@ def validate_workflow(description: str) -> Callable[[Callable[[], StepList]], Wo
|
|
|
344
369
|
return _validate_workflow
|
|
345
370
|
|
|
346
371
|
|
|
372
|
+
def ensure_provisioning_status(modify_steps: Step | StepList) -> StepList:
|
|
373
|
+
"""Decorator to ensure subscription modifications are executed only during Provisioning status."""
|
|
374
|
+
return (
|
|
375
|
+
begin
|
|
376
|
+
>> set_status(SubscriptionLifecycle.PROVISIONING)
|
|
377
|
+
>> modify_steps
|
|
378
|
+
>> set_status(SubscriptionLifecycle.ACTIVE)
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
|
|
347
382
|
@step("Equalize workflow step count")
|
|
348
383
|
def obsolete_step() -> None:
|
|
349
384
|
"""Equalize workflow step counts.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: orchestrator-core
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.2.0rc1
|
|
4
4
|
Summary: This is the orchestrator workflow engine.
|
|
5
5
|
Requires-Python: >=3.11,<3.14
|
|
6
6
|
Classifier: Intended Audience :: Information Technology
|
|
@@ -28,7 +28,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
28
28
|
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
|
|
29
29
|
Classifier: Topic :: Internet :: WWW/HTTP
|
|
30
30
|
License-File: LICENSE
|
|
31
|
-
Requires-Dist: alembic==1.15.
|
|
31
|
+
Requires-Dist: alembic==1.15.2
|
|
32
32
|
Requires-Dist: anyio>=3.7.0
|
|
33
33
|
Requires-Dist: click==8.*
|
|
34
34
|
Requires-Dist: deprecated
|
|
@@ -38,17 +38,17 @@ Requires-Dist: fastapi-etag==0.4.0
|
|
|
38
38
|
Requires-Dist: more-itertools~=10.6.0
|
|
39
39
|
Requires-Dist: itsdangerous
|
|
40
40
|
Requires-Dist: Jinja2==3.1.6
|
|
41
|
-
Requires-Dist: orjson==3.10.
|
|
42
|
-
Requires-Dist:
|
|
41
|
+
Requires-Dist: orjson==3.10.16
|
|
42
|
+
Requires-Dist: psycopg2-binary==2.9.10
|
|
43
43
|
Requires-Dist: pydantic[email]~=2.8.2
|
|
44
44
|
Requires-Dist: pydantic-settings~=2.8.0
|
|
45
45
|
Requires-Dist: python-dateutil==2.8.2
|
|
46
46
|
Requires-Dist: python-rapidjson>=1.18,<1.21
|
|
47
|
-
Requires-Dist: pytz==2025.
|
|
47
|
+
Requires-Dist: pytz==2025.2
|
|
48
48
|
Requires-Dist: redis==5.1.1
|
|
49
49
|
Requires-Dist: schedule==1.1.0
|
|
50
|
-
Requires-Dist: sentry-sdk[fastapi]~=2.
|
|
51
|
-
Requires-Dist: SQLAlchemy==2.0.
|
|
50
|
+
Requires-Dist: sentry-sdk[fastapi]~=2.25.1
|
|
51
|
+
Requires-Dist: SQLAlchemy==2.0.40
|
|
52
52
|
Requires-Dist: SQLAlchemy-Utils==0.41.2
|
|
53
53
|
Requires-Dist: structlog
|
|
54
54
|
Requires-Dist: typer==0.15.2
|