orchestrator-core 3.2.3__py3-none-any.whl → 4.0.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__ = "3.2.3"
16
+ __version__ = "4.0.0rc1"
17
17
 
18
18
  from orchestrator.app import OrchestratorCore
19
19
  from orchestrator.settings import app_settings
@@ -28,7 +28,6 @@ from orchestrator.domain.customer_description import (
28
28
  from orchestrator.schemas import SubscriptionDescriptionBaseSchema, SubscriptionDescriptionSchema
29
29
  from orchestrator.schemas.subscription_descriptions import UpdateSubscriptionDescriptionSchema
30
30
  from orchestrator.utils.errors import StaleDataError
31
- from orchestrator.utils.redis import delete_from_redis
32
31
 
33
32
  router = APIRouter()
34
33
 
@@ -55,7 +54,6 @@ def delete_subscription_customer_descriptions(_id: UUID) -> None:
55
54
  description = db.session.get(SubscriptionCustomerDescriptionTable, _id)
56
55
  if description:
57
56
  delete(SubscriptionCustomerDescriptionTable, _id)
58
- delete_from_redis(description.subscription_id)
59
57
 
60
58
 
61
59
  @router.get("/{_id}", response_model=SubscriptionDescriptionSchema)
orchestrator/db/models.py CHANGED
@@ -400,7 +400,7 @@ class WorkflowTable(BaseModel):
400
400
  workflow_id = mapped_column(UUIDType, server_default=text("uuid_generate_v4()"), primary_key=True)
401
401
  name = mapped_column(String(), nullable=False, unique=True)
402
402
  target = mapped_column(String(), nullable=False)
403
- description = mapped_column(Text(), nullable=True)
403
+ description = mapped_column(Text(), nullable=False)
404
404
  created_at = mapped_column(UtcTimestamp, nullable=False, server_default=text("current_timestamp()"))
405
405
  deleted_at = mapped_column(UtcTimestamp, deferred=True)
406
406
 
@@ -1294,27 +1294,13 @@ class SubscriptionModel(DomainModel):
1294
1294
  # Some common functions shared by from_other_product and from_subscription
1295
1295
  @classmethod
1296
1296
  def _get_subscription(cls: type[S], subscription_id: UUID | UUIDstr) -> SubscriptionTable | None:
1297
- from orchestrator.settings import app_settings
1298
1297
 
1299
1298
  if not isinstance(subscription_id, UUID | UUIDstr):
1300
1299
  raise TypeError(f"subscription_id is of type {type(subscription_id)} instead of UUID | UUIDstr")
1301
1300
 
1302
- if app_settings.ENABLE_SUBSCRIPTION_MODEL_OPTIMIZATIONS:
1303
- # TODO #900 remove toggle and make this path the default
1304
- loaders = [
1305
- joinedload(SubscriptionTable.product).selectinload(ProductTable.fixed_inputs),
1306
- ]
1307
-
1308
- else:
1309
- loaders = [
1310
- selectinload(SubscriptionTable.instances)
1311
- .joinedload(SubscriptionInstanceTable.product_block)
1312
- .selectinload(ProductBlockTable.resource_types),
1313
- selectinload(SubscriptionTable.instances).selectinload(
1314
- SubscriptionInstanceTable.in_use_by_block_relations
1315
- ),
1316
- selectinload(SubscriptionTable.instances).selectinload(SubscriptionInstanceTable.values),
1317
- ]
1301
+ loaders = [
1302
+ joinedload(SubscriptionTable.product).selectinload(ProductTable.fixed_inputs),
1303
+ ]
1318
1304
 
1319
1305
  return db.session.get(SubscriptionTable, subscription_id, options=loaders)
1320
1306
 
@@ -1394,7 +1380,6 @@ class SubscriptionModel(DomainModel):
1394
1380
  def from_subscription(cls: type[S], subscription_id: UUID | UUIDstr) -> S:
1395
1381
  """Use a subscription_id to return required fields of an existing subscription."""
1396
1382
  from orchestrator.domain.context_cache import get_from_cache, store_in_cache
1397
- from orchestrator.settings import app_settings
1398
1383
 
1399
1384
  if cached_model := get_from_cache(subscription_id):
1400
1385
  return cast(S, cached_model)
@@ -1421,12 +1406,7 @@ class SubscriptionModel(DomainModel):
1421
1406
 
1422
1407
  fixed_inputs = {fi.name: fi.value for fi in subscription.product.fixed_inputs}
1423
1408
 
1424
- instances: dict[str, Any]
1425
- if app_settings.ENABLE_SUBSCRIPTION_MODEL_OPTIMIZATIONS:
1426
- # TODO #900 remove toggle and make this path the default
1427
- instances = cls._load_root_instances(subscription_id)
1428
- else:
1429
- instances = cls._load_instances(subscription.instances, status, match_domain_attr=False)
1409
+ instances = cls._load_root_instances(subscription_id)
1430
1410
 
1431
1411
  try:
1432
1412
  model = cls(
@@ -21,7 +21,6 @@ from sqlalchemy import select
21
21
  from orchestrator.api.models import delete
22
22
  from orchestrator.db import SubscriptionCustomerDescriptionTable, db
23
23
  from orchestrator.utils.errors import StaleDataError
24
- from orchestrator.utils.redis import delete_subscription_from_redis
25
24
  from orchestrator.utils.validate_data_version import validate_data_version
26
25
  from orchestrator.websocket import invalidate_subscription_cache
27
26
 
@@ -38,7 +37,6 @@ def get_customer_description_by_customer_subscription(
38
37
  return db.session.scalars(stmt).one_or_none()
39
38
 
40
39
 
41
- @delete_subscription_from_redis()
42
40
  async def create_subscription_customer_description(
43
41
  customer_id: str, subscription_id: UUID, description: str
44
42
  ) -> SubscriptionCustomerDescriptionTable:
@@ -53,7 +51,6 @@ async def create_subscription_customer_description(
53
51
  return customer_description
54
52
 
55
53
 
56
- @delete_subscription_from_redis()
57
54
  async def update_subscription_customer_description(
58
55
  customer_description: SubscriptionCustomerDescriptionTable,
59
56
  description: str,
@@ -70,7 +67,6 @@ async def update_subscription_customer_description(
70
67
  return customer_description
71
68
 
72
69
 
73
- @delete_subscription_from_redis()
74
70
  async def delete_subscription_customer_description_by_customer_subscription(
75
71
  customer_id: str, subscription_id: UUID
76
72
  ) -> SubscriptionCustomerDescriptionTable | None:
@@ -60,7 +60,7 @@ async def resolve_remove_customer_description(
60
60
  )
61
61
  if not description:
62
62
  return NotFoundError(message="Customer description not found")
63
- return CustomerDescription.from_pydantic(description)
63
+ return CustomerDescription.from_pydantic(description) # type: ignore
64
64
 
65
65
 
66
66
  @strawberry.type(description="Customer subscription description mutations")
@@ -0,0 +1,33 @@
1
+ """Make workflow description mandatory.
2
+
3
+ Revision ID: 68d14db1b8da
4
+ Revises: bac6be6f2b4f
5
+ Create Date: 2025-02-20 16:39:34.889953
6
+
7
+ """
8
+
9
+ import sqlalchemy as sa
10
+ from alembic import op
11
+ from structlog import get_logger
12
+
13
+ logger = get_logger(__name__)
14
+
15
+ # revision identifiers, used by Alembic.
16
+ revision = "68d14db1b8da"
17
+ down_revision = "bac6be6f2b4f"
18
+ branch_labels = None
19
+ depends_on = None
20
+
21
+
22
+ def upgrade() -> None:
23
+ try:
24
+ op.alter_column("workflows", "description", existing_type=sa.TEXT(), nullable=False)
25
+ except sa.exc.IntegrityError:
26
+ logger.error(
27
+ "Unable to execute migrations due to missing descriptions in workflow table, please create a migration to backfill this column."
28
+ )
29
+ raise
30
+
31
+
32
+ def downgrade() -> None:
33
+ op.alter_column("workflows", "description", existing_type=sa.TEXT(), nullable=True)
@@ -441,6 +441,7 @@ def create_process(
441
441
  }
442
442
 
443
443
  try:
444
+
444
445
  state = post_form(workflow.initial_input_form, initial_state, user_inputs)
445
446
  except FormValidationError:
446
447
  logger.exception("Validation errors", user_inputs=user_inputs)
@@ -605,7 +605,6 @@ def build_domain_model(subscription_model: SubscriptionModel) -> dict:
605
605
 
606
606
  def build_extended_domain_model(subscription_model: SubscriptionModel) -> dict:
607
607
  """Create a subscription dict from the SubscriptionModel with additional keys."""
608
- from orchestrator.settings import app_settings
609
608
 
610
609
  stmt = select(SubscriptionCustomerDescriptionTable).where(
611
610
  SubscriptionCustomerDescriptionTable.subscription_id == subscription_model.subscription_id
@@ -615,27 +614,12 @@ def build_extended_domain_model(subscription_model: SubscriptionModel) -> dict:
615
614
  with cache_subscription_models():
616
615
  subscription = subscription_model.model_dump()
617
616
 
618
- def inject_in_use_by_ids(path_to_block: str) -> None:
619
- if not (in_use_by_subs := getattr_in(subscription_model, f"{path_to_block}.in_use_by")):
620
- return
621
-
622
- in_use_by_ids = [obj.in_use_by_id for obj in in_use_by_subs.col]
623
- in_use_by_relations = [convert_to_in_use_by_relation(instance) for instance in in_use_by_subs]
624
- update_in(subscription, f"{path_to_block}.in_use_by_ids", in_use_by_ids)
625
- update_in(subscription, f"{path_to_block}.in_use_by_relations", in_use_by_relations)
626
-
627
- if app_settings.ENABLE_SUBSCRIPTION_MODEL_OPTIMIZATIONS:
628
- # TODO #900 remove toggle and make this path the default
629
- # query all subscription instances and inject the in_use_by_ids/in_use_by_relations into the subscription dict.
630
- instance_to_in_use_by = {
631
- instance.subscription_instance_id: instance.in_use_by
632
- for instance in eagerload_all_subscription_instances_only_inuseby(subscription_model.subscription_id)
633
- }
634
- inject_in_use_by_ids_v2(subscription, instance_to_in_use_by)
635
- else:
636
- # find all product blocks, check if they have in_use_by and inject the in_use_by_ids into the subscription dict.
637
- for path in product_block_paths(subscription):
638
- inject_in_use_by_ids(path)
617
+ # query all subscription instances and inject the in_use_by_ids/in_use_by_relations into the subscription dict.
618
+ instance_to_in_use_by = {
619
+ instance.subscription_instance_id: instance.in_use_by
620
+ for instance in eagerload_all_subscription_instances_only_inuseby(subscription_model.subscription_id)
621
+ }
622
+ inject_in_use_by_ids_v2(subscription, instance_to_in_use_by)
639
623
 
640
624
  subscription["customer_descriptions"] = customer_descriptions
641
625
 
orchestrator/settings.py CHANGED
@@ -55,7 +55,6 @@ class AppSettings(BaseSettings):
55
55
  MAIL_PORT: int = 25
56
56
  MAIL_STARTTLS: bool = False
57
57
  CACHE_URI: RedisDsn = "redis://localhost:6379/0" # type: ignore
58
- CACHE_DOMAIN_MODELS: bool = False
59
58
  CACHE_HMAC_SECRET: str | None = None # HMAC signing key, used when pickling results in the cache
60
59
  REDIS_RETRY_COUNT: NonNegativeInt = Field(
61
60
  2, description="Number of retries for redis connection errors/timeouts, 0 to disable"
@@ -87,9 +86,6 @@ class AppSettings(BaseSettings):
87
86
  ENABLE_GRAPHQL_STATS_EXTENSION: bool = False
88
87
  VALIDATE_OUT_OF_SYNC_SUBSCRIPTIONS: bool = False
89
88
  FILTER_BY_MODE: Literal["partial", "exact"] = "exact"
90
- ENABLE_SUBSCRIPTION_MODEL_OPTIMIZATIONS: bool = (
91
- True # True=ignore cache + optimized DB queries; False=use cache + unoptimized DB queries. Remove in #900
92
- )
93
89
 
94
90
 
95
91
  app_settings = AppSettings()
@@ -2,15 +2,11 @@ from uuid import UUID
2
2
 
3
3
  from orchestrator.domain.base import SubscriptionModel
4
4
  from orchestrator.services.subscriptions import _generate_etag, build_domain_model, build_extended_domain_model
5
- from orchestrator.utils.redis import from_redis
6
5
 
7
6
 
8
7
  async def get_subscription_dict(subscription_id: UUID, inject_inuseby: bool = True) -> tuple[dict, str]:
9
8
  """Helper function to get subscription dict by uuid from db or cache."""
10
9
 
11
- if cached_model := from_redis(subscription_id):
12
- return cached_model # type: ignore
13
-
14
10
  subscription_model = SubscriptionModel.from_subscription(subscription_id)
15
11
 
16
12
  if not inject_inuseby:
@@ -10,11 +10,9 @@
10
10
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
11
  # See the License for the specific language governing permissions and
12
12
  # limitations under the License.
13
- import functools
14
13
  from collections.abc import AsyncGenerator
15
14
  from contextlib import asynccontextmanager
16
- from os import getenv
17
- from typing import Any, Callable
15
+ from typing import Any
18
16
  from uuid import UUID
19
17
 
20
18
  from anyio import CancelScope, get_cancelled_exc_class
@@ -22,9 +20,7 @@ from redis.asyncio import Redis as AIORedis
22
20
  from redis.asyncio.client import Pipeline, PubSub
23
21
  from structlog import get_logger
24
22
 
25
- from orchestrator.services.subscriptions import _generate_etag
26
23
  from orchestrator.settings import app_settings
27
- from orchestrator.utils.json import PY_JSON_TYPES, json_dumps, json_loads
28
24
  from orchestrator.utils.redis_client import (
29
25
  create_redis_asyncio_client,
30
26
  create_redis_client,
@@ -37,52 +33,6 @@ cache = create_redis_client(app_settings.CACHE_URI)
37
33
  ONE_WEEK = 3600 * 24 * 7
38
34
 
39
35
 
40
- def caching_models_enabled() -> bool:
41
- return getenv("AIOCACHE_DISABLE", "0") == "0" and app_settings.CACHE_DOMAIN_MODELS
42
-
43
-
44
- def to_redis(subscription: dict[str, Any]) -> str | None:
45
- if caching_models_enabled():
46
- logger.info("Setting cache for subscription", subscription=subscription["subscription_id"])
47
- etag = _generate_etag(subscription)
48
- cache.set(f"orchestrator:domain:{subscription['subscription_id']}", json_dumps(subscription), ex=ONE_WEEK)
49
- cache.set(f"orchestrator:domain:etag:{subscription['subscription_id']}", etag, ex=ONE_WEEK)
50
- return etag
51
-
52
- logger.warning("Caching disabled, not caching subscription", subscription=subscription["subscription_id"])
53
- return None
54
-
55
-
56
- def from_redis(subscription_id: UUID) -> tuple[PY_JSON_TYPES, str] | None:
57
- log = logger.bind(subscription_id=subscription_id)
58
-
59
- if app_settings.ENABLE_SUBSCRIPTION_MODEL_OPTIMIZATIONS:
60
- # TODO #900 remove toggle and remove usage of this function in get_subscription_dict
61
- log.info("Using SubscriptionModel optimization, not loading subscription from redis cache")
62
- return None
63
-
64
- if caching_models_enabled():
65
- log.debug("Try to retrieve subscription from cache")
66
- obj = cache.get(f"orchestrator:domain:{subscription_id}")
67
- etag = cache.get(f"orchestrator:domain:etag:{subscription_id}")
68
- if obj and etag:
69
- log.info("Retrieved subscription from cache")
70
- return json_loads(obj), etag.decode("utf-8")
71
- log.info("Subscription not found in cache")
72
- return None
73
- log.warning("Caching disabled, not loading subscription")
74
- return None
75
-
76
-
77
- def delete_from_redis(subscription_id: UUID) -> None:
78
- if caching_models_enabled():
79
- logger.info("Deleting subscription object from cache", subscription_id=subscription_id)
80
- cache.delete(f"orchestrator:domain:{subscription_id}")
81
- cache.delete(f"orchestrator:domain:etag:{subscription_id}")
82
- else:
83
- logger.warning("Caching disabled, not deleting subscription", subscription=subscription_id)
84
-
85
-
86
36
  def default_get_subscription_id(data: Any) -> UUID:
87
37
  if hasattr(data, "subscription_id"):
88
38
  return data.subscription_id
@@ -91,22 +41,6 @@ def default_get_subscription_id(data: Any) -> UUID:
91
41
  return data
92
42
 
93
43
 
94
- def delete_subscription_from_redis(
95
- extract_fn: Callable[..., UUID] = default_get_subscription_id,
96
- ) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
97
- def _delete_subscription_from_redis(func: Callable[..., Any]) -> Callable[..., Any]:
98
- @functools.wraps(func)
99
- async def wrapper(*args: tuple, **kwargs: dict[str, Any]) -> Any:
100
- data = await func(*args, **kwargs)
101
- key = extract_fn(data)
102
- delete_from_redis(key)
103
- return data
104
-
105
- return wrapper
106
-
107
- return _delete_subscription_from_redis
108
-
109
-
110
44
  async def delete_keys_matching_pattern(_cache: AIORedis, pattern: str, chunksize: int = 5000) -> int:
111
45
  """Delete all keys matching the given pattern.
112
46
 
@@ -13,11 +13,10 @@
13
13
  from orchestrator.db import db
14
14
  from orchestrator.forms import SubmitFormPage
15
15
  from orchestrator.services import subscriptions
16
- from orchestrator.settings import app_settings
17
16
  from orchestrator.targets import Target
18
17
  from orchestrator.utils.json import to_serializable
19
- from orchestrator.workflow import StepList, conditional, done, init, step, workflow
20
- from orchestrator.workflows.steps import cache_domain_models, store_process_subscription
18
+ from orchestrator.workflow import StepList, done, init, step, workflow
19
+ from orchestrator.workflows.steps import store_process_subscription
21
20
  from orchestrator.workflows.utils import wrap_modify_initial_input_form
22
21
  from pydantic_forms.types import FormGenerator, State, UUIDstr
23
22
  from pydantic_forms.validators import LongText
@@ -54,11 +53,4 @@ def store_subscription_note(subscription_id: UUIDstr, note: str) -> State:
54
53
 
55
54
  @workflow("Modify Note", initial_input_form=wrap_modify_initial_input_form(initial_input_form), target=Target.MODIFY)
56
55
  def modify_note() -> StepList:
57
- push_subscriptions = conditional(lambda _: app_settings.CACHE_DOMAIN_MODELS)
58
- return (
59
- init
60
- >> store_process_subscription(Target.MODIFY)
61
- >> store_subscription_note
62
- >> push_subscriptions(cache_domain_models)
63
- >> done
64
- )
56
+ return init >> store_process_subscription(Target.MODIFY) >> store_subscription_note >> done
@@ -11,22 +11,18 @@
11
11
  # See the License for the specific language governing permissions and
12
12
  # limitations under the License.
13
13
  from copy import deepcopy
14
- from typing import Any
15
- from uuid import UUID
16
14
 
17
15
  import structlog
18
16
  from pydantic import ValidationError
19
17
 
20
18
  from orchestrator.db import db
21
19
  from orchestrator.db.models import ProcessSubscriptionTable
22
- from orchestrator.domain.base import ProductBlockModel, SubscriptionModel
20
+ from orchestrator.domain.base import SubscriptionModel
23
21
  from orchestrator.services.settings import reset_search_index
24
- from orchestrator.services.subscriptions import build_extended_domain_model, get_subscription
22
+ from orchestrator.services.subscriptions import get_subscription
25
23
  from orchestrator.targets import Target
26
24
  from orchestrator.types import SubscriptionLifecycle
27
25
  from orchestrator.utils.json import to_serializable
28
- from orchestrator.utils.redis import delete_from_redis, to_redis
29
- from orchestrator.websocket import sync_invalidate_subscription_cache
30
26
  from orchestrator.workflow import Step, step
31
27
  from pydantic_forms.types import State, UUIDstr
32
28
 
@@ -138,86 +134,6 @@ def set_status(status: SubscriptionLifecycle) -> Step:
138
134
  return _set_status
139
135
 
140
136
 
141
- @step("Remove domain model from cache")
142
- def remove_domain_model_from_cache(
143
- workflow_name: str, subscription: SubscriptionModel | None = None, subscription_id: UUID | None = None
144
- ) -> State:
145
- """Remove the domain model from the cache if it exists.
146
-
147
- Args:
148
- workflow_name: The workflow name
149
- subscription: Subscription Model
150
- subscription_id: The subscription id
151
-
152
- Returns:
153
- State
154
-
155
- """
156
-
157
- if not (subscription or subscription_id):
158
- logger.warning("No subscription found in this workflow", workflow_name=workflow_name)
159
- return {"deleted_subscription_id": None}
160
- if subscription:
161
- delete_from_redis(subscription.subscription_id)
162
- elif subscription_id:
163
- delete_from_redis(subscription_id)
164
-
165
- return {"deleted_subscription_id": subscription_id or subscription.subscription_id} # type: ignore[union-attr]
166
-
167
-
168
- @step("Cache Subscription and related subscriptions")
169
- def cache_domain_models(workflow_name: str, subscription: SubscriptionModel | None = None) -> State: # noqa: C901
170
- """Attempt to cache all Subscriptions once they have been touched once.
171
-
172
- Args:
173
- workflow_name: The Workflow Name
174
- subscription: The Subscription if it exists.
175
-
176
- Returns:
177
- Returns State.
178
-
179
- """
180
- cached_subscription_ids: set[UUID] = set()
181
- if not subscription:
182
- logger.warning("No subscription found in this workflow", workflow_name=workflow_name)
183
- return {"cached_subscription_ids": cached_subscription_ids}
184
-
185
- def _cache_other_subscriptions(product_block: ProductBlockModel) -> None:
186
- for field in product_block.model_fields:
187
- # subscription_instance is a ProductBlockModel or an arbitrary type
188
- subscription_instance: ProductBlockModel | Any = getattr(product_block, field)
189
- # If subscription_instance is a list, we need to step into it and loop.
190
- if isinstance(subscription_instance, list):
191
- for item in subscription_instance:
192
- if isinstance(item, ProductBlockModel):
193
- _cache_other_subscriptions(item)
194
-
195
- # If subscription_instance is a ProductBlockModel check the owner_subscription_id to decide the cache
196
- elif isinstance(subscription_instance, ProductBlockModel):
197
- _cache_other_subscriptions(subscription_instance)
198
- if not subscription_instance.owner_subscription_id == subscription.subscription_id:
199
- cached_subscription_ids.add(subscription_instance.owner_subscription_id)
200
-
201
- for field in subscription.model_fields:
202
- # There always is a single Root Product Block, it cannot be a list, so no need to check.
203
- instance: ProductBlockModel | Any = getattr(subscription, field)
204
- if isinstance(instance, ProductBlockModel):
205
- _cache_other_subscriptions(instance)
206
-
207
- # Cache all the sub subscriptions
208
- for subscription_id in cached_subscription_ids:
209
- subscription_model = SubscriptionModel.from_subscription(subscription_id)
210
- to_redis(build_extended_domain_model(subscription_model))
211
- sync_invalidate_subscription_cache(subscription.subscription_id, invalidate_all=False)
212
-
213
- # Cache the main subscription
214
- to_redis(build_extended_domain_model(subscription))
215
- cached_subscription_ids.add(subscription.subscription_id)
216
- sync_invalidate_subscription_cache(subscription.subscription_id)
217
-
218
- return {"cached_subscription_ids": cached_subscription_ids}
219
-
220
-
221
137
  @step("Refresh subscription search index")
222
138
  def refresh_subscription_search_index() -> State:
223
139
  try:
@@ -24,16 +24,13 @@ from oauth2_lib.fastapi import OIDCUserModel
24
24
  from orchestrator.db import ProductTable, SubscriptionTable, db
25
25
  from orchestrator.forms.validators import ProductId
26
26
  from orchestrator.services import subscriptions
27
- from orchestrator.settings import app_settings
28
27
  from orchestrator.targets import Target
29
28
  from orchestrator.types import SubscriptionLifecycle
30
29
  from orchestrator.utils.errors import StaleDataError
31
- from orchestrator.utils.redis import caching_models_enabled
32
30
  from orchestrator.utils.state import form_inject_args
33
31
  from orchestrator.utils.validate_data_version import validate_data_version
34
- from orchestrator.workflow import Step, StepList, Workflow, begin, conditional, done, init, make_workflow, step
32
+ from orchestrator.workflow import Step, StepList, Workflow, begin, done, init, make_workflow, step
35
33
  from orchestrator.workflows.steps import (
36
- cache_domain_models,
37
34
  refresh_subscription_search_index,
38
35
  resync,
39
36
  set_status,
@@ -198,8 +195,6 @@ modify_initial_input_form_generator = None
198
195
 
199
196
  validate_initial_input_form_generator = wrap_modify_initial_input_form(modify_initial_input_form_generator)
200
197
 
201
- push_domain_models = conditional(lambda _: caching_models_enabled())
202
-
203
198
 
204
199
  def create_workflow(
205
200
  description: str,
@@ -228,7 +223,6 @@ def create_workflow(
228
223
  >> (additional_steps or StepList())
229
224
  >> set_status(status)
230
225
  >> resync
231
- >> push_domain_models(cache_domain_models)
232
226
  >> refresh_subscription_search_index
233
227
  >> done
234
228
  )
@@ -270,11 +264,9 @@ def modify_workflow(
270
264
  init
271
265
  >> store_process_subscription(Target.MODIFY)
272
266
  >> unsync
273
- >> push_domain_models(cache_domain_models)
274
267
  >> f()
275
268
  >> (additional_steps or StepList())
276
269
  >> resync
277
- >> push_domain_models(cache_domain_models)
278
270
  >> refresh_subscription_search_index
279
271
  >> done
280
272
  )
@@ -316,12 +308,10 @@ def terminate_workflow(
316
308
  init
317
309
  >> store_process_subscription(Target.TERMINATE)
318
310
  >> unsync
319
- >> push_domain_models(cache_domain_models)
320
311
  >> f()
321
312
  >> (additional_steps or StepList())
322
313
  >> set_status(SubscriptionLifecycle.TERMINATED)
323
314
  >> resync
324
- >> push_domain_models(cache_domain_models)
325
315
  >> refresh_subscription_search_index
326
316
  >> done
327
317
  )
@@ -352,17 +342,7 @@ def validate_workflow(description: str) -> Callable[[Callable[[], StepList]], Wo
352
342
  """
353
343
 
354
344
  def _validate_workflow(f: Callable[[], StepList]) -> Workflow:
355
- push_subscriptions = conditional(lambda _: app_settings.CACHE_DOMAIN_MODELS)
356
- steplist = (
357
- init
358
- >> store_process_subscription(Target.SYSTEM)
359
- >> unsync_unchecked
360
- >> push_subscriptions(cache_domain_models)
361
- >> f()
362
- >> resync
363
- >> push_subscriptions(cache_domain_models)
364
- >> done
365
- )
345
+ steplist = init >> store_process_subscription(Target.SYSTEM) >> unsync_unchecked >> f() >> resync >> done
366
346
 
367
347
  return make_workflow(f, description, validate_initial_input_form_generator, Target.SYSTEM, steplist)
368
348
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: orchestrator-core
3
- Version: 3.2.3
3
+ Version: 4.0.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
@@ -35,24 +35,24 @@ Requires-Dist: deprecated
35
35
  Requires-Dist: deepmerge==2.0
36
36
  Requires-Dist: fastapi~=0.115.2
37
37
  Requires-Dist: fastapi-etag==0.4.0
38
- Requires-Dist: more-itertools~=10.6.0
38
+ Requires-Dist: more-itertools~=10.7.0
39
39
  Requires-Dist: itsdangerous
40
40
  Requires-Dist: Jinja2==3.1.6
41
- Requires-Dist: orjson==3.10.16
41
+ Requires-Dist: orjson==3.10.18
42
42
  Requires-Dist: psycopg2-binary==2.9.10
43
43
  Requires-Dist: pydantic[email]~=2.8.2
44
- Requires-Dist: pydantic-settings~=2.8.0
44
+ Requires-Dist: pydantic-settings~=2.9.1
45
45
  Requires-Dist: python-dateutil==2.8.2
46
46
  Requires-Dist: python-rapidjson>=1.18,<1.21
47
47
  Requires-Dist: pytz==2025.2
48
48
  Requires-Dist: redis==5.1.1
49
49
  Requires-Dist: schedule==1.1.0
50
50
  Requires-Dist: semver==3.0.4
51
- Requires-Dist: sentry-sdk[fastapi]~=2.25.1
51
+ Requires-Dist: sentry-sdk[fastapi]~=2.27.0
52
52
  Requires-Dist: SQLAlchemy==2.0.40
53
53
  Requires-Dist: SQLAlchemy-Utils==0.41.2
54
54
  Requires-Dist: structlog
55
- Requires-Dist: typer==0.15.2
55
+ Requires-Dist: typer==0.15.3
56
56
  Requires-Dist: uvicorn[standard]~=0.34.0
57
57
  Requires-Dist: nwa-stdlib~=1.9.0
58
58
  Requires-Dist: oauth2-lib~=2.4.0
@@ -1,10 +1,10 @@
1
- orchestrator/__init__.py,sha256=SAG1EbiZw8m_0wbefpfPXZ_rYgoIoLssZr_HdgxQShI,1063
1
+ orchestrator/__init__.py,sha256=VeHQewmn2Fz_VeMN_e71lAH_9sS-t7zhdO8h30dYbKo,1066
2
2
  orchestrator/app.py,sha256=VN54_Zsx5x_3ym8aFadATg67a4J5lv8H-pxnPlR3RkM,11668
3
3
  orchestrator/exception_handlers.py,sha256=UsW3dw8q0QQlNLcV359bIotah8DYjMsj2Ts1LfX4ClY,1268
4
4
  orchestrator/log_config.py,sha256=1tPRX5q65e57a6a_zEii_PFK8SzWT0mnA5w2sKg4hh8,1853
5
5
  orchestrator/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  orchestrator/security.py,sha256=iXFxGxab54aav7oHEKLAVkTgrQMJGHy6IYLojEnD7gI,2422
7
- orchestrator/settings.py,sha256=MTG7gnmbQYwUjAnhvX4Y3MEqmX8vIpAEC0lhDafa1zw,4039
7
+ orchestrator/settings.py,sha256=21BT1SviPllSL4-M1Qc-8zdgF_Sh7p79WC8sR_AmTmE,3826
8
8
  orchestrator/targets.py,sha256=ykjTGK7CozFaltNaxQcK90P4Cc1Qvf-W8dFztxtZhRQ,776
9
9
  orchestrator/types.py,sha256=qzs7xx5AYRmKbpYRyJJP3wuDb0W0bcAzefCN0RWLAco,15459
10
10
  orchestrator/version.py,sha256=b58e08lxs47wUNXv0jXFO_ykpksmytuzEXD4La4W-NQ,1366
@@ -22,7 +22,7 @@ orchestrator/api/api_v1/endpoints/product_blocks.py,sha256=kZ6ywIOsS_S2qGq7RvZ4K
22
22
  orchestrator/api/api_v1/endpoints/products.py,sha256=BfFtwu9dZXEQbtKxYj9icc73GKGvAGMR5ytyf41nQlQ,3081
23
23
  orchestrator/api/api_v1/endpoints/resource_types.py,sha256=gGyuaDyOD0TAVoeFGaGmjDGnQ8eQQArOxKrrk4MaDzA,2145
24
24
  orchestrator/api/api_v1/endpoints/settings.py,sha256=QPBwrRGqBlYHNRnADKQ5hpj74X2DB5lROu8KwVnyN_0,6226
25
- orchestrator/api/api_v1/endpoints/subscription_customer_descriptions.py,sha256=Elu4DVJoNtUFq_b3pG1Ws8StrUIo_jTViff2TJqe6ZU,3398
25
+ orchestrator/api/api_v1/endpoints/subscription_customer_descriptions.py,sha256=1_6LtgQleoq3M6z_W-Qz__Bj3OFUweoPrUqHMwSH6AM,3288
26
26
  orchestrator/api/api_v1/endpoints/subscriptions.py,sha256=EwkWBztI9xSMPkol49SM5csECthyu7HC38AhuW7pWUE,8724
27
27
  orchestrator/api/api_v1/endpoints/translations.py,sha256=dIWh_fCnZZUxJoGiNeJ49DK_xpf75IpR_0EIMSvzIvY,963
28
28
  orchestrator/api/api_v1/endpoints/user.py,sha256=RyI32EXVu6I-IxWjz0XB5zQWzzLL60zKXLgLqLH02xU,1827
@@ -106,7 +106,7 @@ orchestrator/db/database.py,sha256=MU_w_e95ho2dVb2JDnt_KFYholx___XDkiQXbc8wCkI,1
106
106
  orchestrator/db/helpers.py,sha256=L8kEdnSSNGnUpZhdeGx2arCodakWN8vSpKdfjoLuHdY,831
107
107
  orchestrator/db/listeners.py,sha256=UBPYcH0FE3a7aZQu_D0O_JMXpXIRYXC0gjSAvlv5GZo,1142
108
108
  orchestrator/db/loaders.py,sha256=ez6JzQ3IKVkC_oLAkVlIIiI8Do7hXbdcPKCvUSLxRog,7962
109
- orchestrator/db/models.py,sha256=V7m-XOVzo_Kj_B-M46Ybr1zlbfXjbN64sQ3C-rz7PQE,27217
109
+ orchestrator/db/models.py,sha256=0WJOgKGa6h_0leIWtNxim0Kni5pWTbprJrzyiGRchio,27218
110
110
  orchestrator/db/filters/__init__.py,sha256=RUj6P0XxEBhYj0SN5wH5-Vf_Wt_ilZR_n9DSar5m9oM,371
111
111
  orchestrator/db/filters/filters.py,sha256=55RtpQwM2rhrk4A6CCSeSXoo-BT9GnQoNTryA8CtLEg,5020
112
112
  orchestrator/db/filters/process.py,sha256=xvGhyfo_MZ1xhLvFC6yULjcT4mJk0fKc1glJIYgsWLE,4018
@@ -142,9 +142,9 @@ orchestrator/distlock/managers/__init__.py,sha256=ImIkNsrXcyE7-NgRWqEhUXUuUzda9K
142
142
  orchestrator/distlock/managers/memory_distlock_manager.py,sha256=HWQafcVKBF-Cka_wukZZ1GM69AWPVOpJPje3quIebQ8,3114
143
143
  orchestrator/distlock/managers/redis_distlock_manager.py,sha256=DXtMhC8qtxiFO6xU9qYXHZQnCLjlmGBpeyfLA0vbRP0,3369
144
144
  orchestrator/domain/__init__.py,sha256=20DhXQPKY0g3rTgCkRlNDY58sLviToOVF8NPoex9WJc,936
145
- orchestrator/domain/base.py,sha256=26UbHKXqI99w8SyS-Kjj4DF8IbgPNOh8iDPP6e3GEsA,70996
145
+ orchestrator/domain/base.py,sha256=jjMxJvt0GyaTff_z490lmkqDCtitkh0oA4GygB60n4s,69939
146
146
  orchestrator/domain/context_cache.py,sha256=vT1a01MBSBIaokoShK9KwjItd7abNmz7cXaF67VRZK8,2508
147
- orchestrator/domain/customer_description.py,sha256=v7o6TTN4oc6bWHZU-jCT-fUYvkeYahbpXOwlKXofuI8,3360
147
+ orchestrator/domain/customer_description.py,sha256=RU_pcgCIZjjFfDsY45lfV0z6ATlS1NXtB0E7eH3UcYQ,3190
148
148
  orchestrator/domain/helpers.py,sha256=D9O2duhCAZGmm39u-9ggvU-X2JsCbIS607kF77-r8QM,2549
149
149
  orchestrator/domain/lifecycle.py,sha256=kGR0AFVOSUBlzdhgRr11CUnF26wbBYIjz8uKb_qPCg0,2922
150
150
  orchestrator/domain/subscription_instance_transform.py,sha256=j_d49dFcSss0dl-BHS-ev2faLbE0y8hLvCfrzui6C74,4360
@@ -165,7 +165,7 @@ orchestrator/graphql/extensions/model_cache.py,sha256=1uhMRjBs9eK7zJ1Y6P6BopX068
165
165
  orchestrator/graphql/extensions/stats.py,sha256=pGhEBQg45XvqZhRobcrCSGwt5AGmR3gflsm1dYiIg5g,2018
166
166
  orchestrator/graphql/loaders/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
167
167
  orchestrator/graphql/loaders/subscriptions.py,sha256=31zE2WC7z-tPIUmVpU1QWOJvNbLvF7sYgY7JAQ6OPJg,1856
168
- orchestrator/graphql/mutations/customer_description.py,sha256=37yX92fE1Sc51O9i-gP8JfD3HdsvpR3TtbgYqKtGC-w,3343
168
+ orchestrator/graphql/mutations/customer_description.py,sha256=zm_X1yvWl4qC97_rYUYSF-1q1gFrQX6fDrzQKhguDYs,3359
169
169
  orchestrator/graphql/mutations/start_process.py,sha256=8vLVvmBwL1ujbZJoI_8YE3VAgI-J2RTzgrTZJC8THZ4,1576
170
170
  orchestrator/graphql/resolvers/__init__.py,sha256=EEw9NO4LAryfrpkLlgsNQ9rytKd0usBDx95OURRV6sg,1031
171
171
  orchestrator/graphql/resolvers/customer.py,sha256=tq06MtMoaqFwqn3YQvSv0VmROW7QJZRJp1ykO4tUhck,934
@@ -231,6 +231,7 @@ orchestrator/migrations/versions/schema/2024-09-27_460ec6748e37_add_uuid_search_
231
231
  orchestrator/migrations/versions/schema/2025-01-08_4c5859620539_add_version_column_to_subscription.py,sha256=xAhe74U0ZiVRo9Z8Uq7491RBbATMMUnYpTBjbG-BYL0,1690
232
232
  orchestrator/migrations/versions/schema/2025-01-19_4fjdn13f83ga_add_validate_product_type_task.py,sha256=O0GfCISIDnyohGf3Ot_2HKedGRbMqLVox6t7Wd3PMvo,894
233
233
  orchestrator/migrations/versions/schema/2025-02-12_bac6be6f2b4f_added_input_state_table.py,sha256=RZpLkWP1yekeZ68feO5v4LZYluKvnnIKRNDCE4tI9HM,1753
234
+ orchestrator/migrations/versions/schema/2025-02-20_68d14db1b8da_make_workflow_description_mandatory.py,sha256=qabQWLjPWU-1-uLTFwcEyxyMQK89UPqUdl3Q-HSGKTI,847
234
235
  orchestrator/migrations/versions/schema/2025-03-06_42b3d076a85b_subscription_instance_as_json_function.py,sha256=jtwDFOh-NlE31aH5dFmbynb23TZN6Mkzevxx-KLP7KE,776
235
236
  orchestrator/migrations/versions/schema/2025-03-06_42b3d076a85b_subscription_instance_as_json_function.sql,sha256=hPldk0DAesUbHv3Qd_N7U-cAk-t1wIgxt4FOA120gQ8,1776
236
237
  orchestrator/migrations/versions/schema/2025-04-09_fc5c993a4b4a_add_cascade_constraint_on_processes_.py,sha256=6kHRNSZxUze2jy7b8uRvkt5mzsax10Z-Z3lsACtPLRM,1067
@@ -257,12 +258,12 @@ orchestrator/services/celery.py,sha256=W37UNc4hbUS2SKjVmawsnF5wukmEfIdipsTESv_EO
257
258
  orchestrator/services/fixed_inputs.py,sha256=kyz7s2HLzyDulvcq-ZqefTw1om86COvyvTjz0_5CmgI,876
258
259
  orchestrator/services/input_state.py,sha256=HF7wl9fWdaAW8pdCCqbuYoKyNj8dY0g8Ff8vXis8z5A,2211
259
260
  orchestrator/services/process_broadcast_thread.py,sha256=D44YbjF8mRqGuznkRUV4SoRn1J0lfy_x1H508GnSVlU,4649
260
- orchestrator/services/processes.py,sha256=JXVa9E3NtcuFlUggK3LhjHilSAlF-4kTyCCHROakAJA,30554
261
+ orchestrator/services/processes.py,sha256=cIesTJNl-kC2oLvzWOJE2uzmwcqZMWHLlgU-9qcVvac,30555
261
262
  orchestrator/services/products.py,sha256=BP4KyE8zO-8z7Trrs5T6zKBOw53S9BfBJnHWI3p6u5Y,1943
262
263
  orchestrator/services/resource_types.py,sha256=_QBy_JOW_X3aSTqH0CuLrq4zBJL0p7Q-UDJUcuK2_qc,884
263
264
  orchestrator/services/settings.py,sha256=u-834F4KWloXS8zi7R9mp-D3cjl-rbVjKJRU35IqhXo,2723
264
265
  orchestrator/services/subscription_relations.py,sha256=9C126TUfFvyBe7y4x007kH_dvxJ9pZ1zSnaWeH6HC5k,12261
265
- orchestrator/services/subscriptions.py,sha256=u1qkCk7FuniJseQjnuDWiIlqy9ok4SY8nWUyLeJ11No,28068
266
+ orchestrator/services/subscriptions.py,sha256=mXU88SzugwQGnLG0-aaa9YZXTr5rt4LAEOChCb7BF20,27116
266
267
  orchestrator/services/tasks.py,sha256=NjPkuauQoh9UJDcjA7OcKFgPk0i6NoKdDO7HlpGbBJ8,6575
267
268
  orchestrator/services/translations.py,sha256=GyP8soUFGej8AS8uulBsk10CCK6Kwfjv9AHMFm3ElQY,1713
268
269
  orchestrator/services/workflows.py,sha256=oH7klit4kv2NGo-BACWA0ZtajVMSJAxG5m-kM6TXIMI,3742
@@ -275,11 +276,11 @@ orchestrator/utils/enrich_process.py,sha256=o_QSy5Q4wn1SMHhzVOw6bp7uhDXr7GhAIWRD
275
276
  orchestrator/utils/errors.py,sha256=6FxvXrITmRjP5bYnJJ3CxjAwA5meNjRAVYouz4TWKkU,4653
276
277
  orchestrator/utils/fixed_inputs.py,sha256=pnL6I_19VMp_Bny8SYjSzVFNvTFDyeCxFFOWGhTnDiQ,2665
277
278
  orchestrator/utils/functional.py,sha256=X1MDNwHmkU3-8mFb21m31HGlcfc5TygliXR0sXN3-rU,8304
278
- orchestrator/utils/get_subscription_dict.py,sha256=EoSlFUXer_Kc5G0PjDPeujugZ76kKoPrjRr4Mg4HrzY,839
279
+ orchestrator/utils/get_subscription_dict.py,sha256=hctkFvD3U6LpygNwz2uesRMdnXSY-PaohBqPATsi9r4,694
279
280
  orchestrator/utils/get_updated_properties.py,sha256=egVZ0R5LNJ4e51Z8SXlU8cmb4tXxG-xb1d7OKwh-7xI,1322
280
281
  orchestrator/utils/helpers.py,sha256=NjUF3IvWdnLulliP8-JQvGGGpHrh0vs0Vm092ynw-ss,3212
281
282
  orchestrator/utils/json.py,sha256=7386sdqkrKYyy4sbn5NscwctH_v1hLyw5172P__rU3g,8341
282
- orchestrator/utils/redis.py,sha256=E5uPFCOlS1n_jv0xoaS1fuLnVv-shQn0K1hzpVwUWZY,7303
283
+ orchestrator/utils/redis.py,sha256=BjUNmrKx8YVyJIucl2mhXubK6WV-49OnAU6rPMOZpM0,4469
283
284
  orchestrator/utils/redis_client.py,sha256=9rhsvedjK_CyClAjUicQyge0mVIViATqKFGZyjBY3XA,1384
284
285
  orchestrator/utils/search_query.py,sha256=ji5LHtrzohGz6b1IG41cnPdpWXzLEzz4SGWgHly_yfU,16205
285
286
  orchestrator/utils/state.py,sha256=RYKVlvKDBfsBdDk9wHjZKBTlQJbV4SqtCotAlTA2-bI,14021
@@ -290,17 +291,17 @@ orchestrator/websocket/websocket_manager.py,sha256=hwlG9FDXcNU42jDNNsPMQLIyrvEpG
290
291
  orchestrator/websocket/managers/broadcast_websocket_manager.py,sha256=fwoSgTjkHJ2GmsLTU9dqQpAA9i8b1McPu7gLNzxtfG4,5401
291
292
  orchestrator/websocket/managers/memory_websocket_manager.py,sha256=lF5EEx1iFMCGEkTbItTDr88NENMSaSeG1QrJ7teoPkY,3324
292
293
  orchestrator/workflows/__init__.py,sha256=NzIGGI-8SNAwCk2YqH6sHhEWbgAY457ntDwjO15N8v4,4131
293
- orchestrator/workflows/modify_note.py,sha256=X70qQtC0ITp5IE5OR8j5Op57k3ol1S9txnhDbZTICao,2416
294
+ orchestrator/workflows/modify_note.py,sha256=l6QtijRPv8gnHxzwTz_4nWIPcZ0FcKQh_yFbtjYEDMg,2163
294
295
  orchestrator/workflows/removed_workflow.py,sha256=V0Da5TEdfLdZZKD38ig-MTp3_IuE7VGqzHHzvPYQmLI,909
295
- orchestrator/workflows/steps.py,sha256=yAb6XZaRhR2mKV4_yo-4hSYMTknbMD27-pNGMfjVzi0,9802
296
- orchestrator/workflows/utils.py,sha256=fLayx_oggM8PdYnyukHSJE3R9u-X2zYY1WRMCELUY74,14545
296
+ orchestrator/workflows/steps.py,sha256=ulpheoHaCOE1qh71Bja4KW4pItQh1Z6q4QU4tn5GtNk,6067
297
+ orchestrator/workflows/utils.py,sha256=ePXTb_0eO25qvKzC6ZO3FdVQU0sWhglJo7hX6nsO4_w,13776
297
298
  orchestrator/workflows/tasks/__init__.py,sha256=GyHNfEFCGKQwRiN6rQmvSRH2iYX7npjMZn97n8XzmLU,571
298
299
  orchestrator/workflows/tasks/cleanup_tasks_log.py,sha256=BfWYbPXhnLAHUJ0mlODDnjZnQQAvKCZJDVTwbwOWI04,1624
299
300
  orchestrator/workflows/tasks/resume_workflows.py,sha256=MzJqlSXUvKStkT7NGzxZyRlfAer_ezYm-kjUqaZi0yc,2359
300
301
  orchestrator/workflows/tasks/validate_product_type.py,sha256=UVX_6Kh8ueQs8ujLawnKVDdNc8UhWp_u69aNA8okR_w,3184
301
302
  orchestrator/workflows/tasks/validate_products.py,sha256=i6jQME9N8sZZWo4pkNOS1Zgwh3eB2w66DnJi9k93yNk,8521
302
303
  orchestrator/workflows/translations/en-GB.json,sha256=ST53HxkphFLTMjFHonykDBOZ7-P_KxksktZU3GbxLt0,846
303
- orchestrator_core-3.2.3.dist-info/licenses/LICENSE,sha256=b-aA5OZQuuBATmLKo_mln8CQrDPPhg3ghLzjPjLn4Tg,11409
304
- orchestrator_core-3.2.3.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
305
- orchestrator_core-3.2.3.dist-info/METADATA,sha256=AzahqbZrlKJSQnVOq21UaeGImMLI-62j2UI52TGS0eE,5029
306
- orchestrator_core-3.2.3.dist-info/RECORD,,
304
+ orchestrator_core-4.0.0rc1.dist-info/licenses/LICENSE,sha256=b-aA5OZQuuBATmLKo_mln8CQrDPPhg3ghLzjPjLn4Tg,11409
305
+ orchestrator_core-4.0.0rc1.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
306
+ orchestrator_core-4.0.0rc1.dist-info/METADATA,sha256=PnOlFXhp5Z5svvnngGuSFa3pfteaT69k7PoqWbfwHjA,5032
307
+ orchestrator_core-4.0.0rc1.dist-info/RECORD,,