orchestrator-core 3.2.1__py3-none-any.whl → 3.2.2__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.1"
16
+ __version__ = "3.2.2"
17
17
 
18
18
  from orchestrator.app import OrchestratorCore
19
19
  from orchestrator.settings import app_settings
@@ -38,6 +38,13 @@ def cache_subscription_models() -> Iterator:
38
38
  with cache_subscription_models():
39
39
  subscription_dict = subscription.model_dump()
40
40
  """
41
+ if __subscription_model_cache.get() is not None:
42
+ # If it's already active in the current context, we do nothing.
43
+ # This makes the contextmanager reentrant.
44
+ # The outermost contextmanager will eventually reset the context.
45
+ yield
46
+ return
47
+
41
48
  before = __subscription_model_cache.set({})
42
49
  try:
43
50
  yield
@@ -0,0 +1,29 @@
1
+ # Copyright 2022-2025 SURF.
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ from collections.abc import Iterator
14
+
15
+ from strawberry.extensions import SchemaExtension
16
+
17
+ from orchestrator.domain.context_cache import cache_subscription_models
18
+
19
+
20
+ class ModelCacheExtension(SchemaExtension):
21
+ """Wraps the GraphQL operation in a cache_subscription_models context.
22
+
23
+ For more background, please refer to the documentation of the contextmanager.
24
+ """
25
+
26
+ def on_operation(self, *args, **kwargs) -> Iterator[None]: # type: ignore
27
+
28
+ with cache_subscription_models():
29
+ yield
@@ -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
@@ -32,6 +32,7 @@ from oauth2_lib.fastapi import AuthManager
32
32
  from oauth2_lib.strawberry import authenticated_field
33
33
  from orchestrator.domain.base import SubscriptionModel
34
34
  from orchestrator.graphql.autoregistration import create_subscription_strawberry_type, register_domain_models
35
+ from orchestrator.graphql.extensions.model_cache import ModelCacheExtension
35
36
  from orchestrator.graphql.extensions.stats import StatsExtension
36
37
  from orchestrator.graphql.mutations.customer_description import CustomerSubscriptionDescriptionMutation
37
38
  from orchestrator.graphql.mutations.start_process import ProcessMutation
@@ -160,6 +161,7 @@ def default_context_getter(
160
161
 
161
162
 
162
163
  def get_extensions(mutation: Any, query: Any) -> Iterable[type[SchemaExtension]]:
164
+ yield ModelCacheExtension
163
165
  yield ErrorHandlerExtension
164
166
  if app_settings.ENABLE_GRAPHQL_DEPRECATION_CHECKER:
165
167
  yield make_deprecation_checker_extension(query=query, mutation=mutation)
@@ -101,9 +101,9 @@ class SubscriptionInterface:
101
101
 
102
102
  @strawberry.field(description="Return all products block instances of a subscription") # type: ignore
103
103
  async def product_block_instances(
104
- self, tags: list[str] | None = None, resource_types: list[str] | None = None
104
+ self, info: OrchestratorInfo, tags: list[str] | None = None, resource_types: list[str] | None = None
105
105
  ) -> list[ProductBlockInstance]:
106
- return await get_subscription_product_blocks(self.subscription_id, tags, resource_types)
106
+ return await get_subscription_product_blocks(info, self.subscription_id, tags, resource_types)
107
107
 
108
108
  @strawberry.field(description="Return fixed inputs") # type: ignore
109
109
  async def fixed_inputs(self) -> strawberry.scalars.JSON:
@@ -21,6 +21,8 @@ from pydantic.alias_generators import to_camel as to_lower_camel
21
21
  from strawberry.scalars import JSON
22
22
 
23
23
  from orchestrator.graphql.schemas.product_block import owner_subscription_resolver
24
+ from orchestrator.graphql.types import OrchestratorInfo
25
+ from orchestrator.graphql.utils.get_selected_paths import get_selected_paths
24
26
  from orchestrator.utils.get_subscription_dict import get_subscription_dict
25
27
 
26
28
  if TYPE_CHECKING:
@@ -81,9 +83,13 @@ pb_instance_property_keys = (
81
83
 
82
84
 
83
85
  async def get_subscription_product_blocks(
84
- subscription_id: UUID, tags: list[str] | None = None, product_block_instance_values: list[str] | None = None
86
+ info: OrchestratorInfo,
87
+ subscription_id: UUID,
88
+ tags: list[str] | None = None,
89
+ product_block_instance_values: list[str] | None = None,
85
90
  ) -> list[ProductBlockInstance]:
86
- subscription, _ = await get_subscription_dict(subscription_id)
91
+ inject_inuseby = "in_use_by_relations" in get_selected_paths(info)
92
+ subscription, _ = await get_subscription_dict(subscription_id, inject_inuseby=inject_inuseby)
87
93
 
88
94
  def to_product_block(product_block: dict[str, Any]) -> ProductBlockInstance:
89
95
  def is_resource_type(candidate: Any) -> bool:
@@ -52,5 +52,5 @@ def upgrade() -> None:
52
52
  def downgrade() -> None:
53
53
  # ### commands auto generated by Alembic - please adjust! ###
54
54
  op.drop_index(op.f("ix_input_state_input_state_id"), table_name="input_states")
55
- op.drop_table("input_statse")
55
+ op.drop_table("input_states")
56
56
  # ### end Alembic commands ###
@@ -597,6 +597,12 @@ def convert_to_in_use_by_relation(obj: Any) -> dict[str, str]:
597
597
  return {"subscription_instance_id": str(obj.subscription_instance_id), "subscription_id": str(obj.subscription_id)}
598
598
 
599
599
 
600
+ def build_domain_model(subscription_model: SubscriptionModel) -> dict:
601
+ """Create a subscription dict from the SubscriptionModel."""
602
+ with cache_subscription_models():
603
+ return subscription_model.model_dump()
604
+
605
+
600
606
  def build_extended_domain_model(subscription_model: SubscriptionModel) -> dict:
601
607
  """Create a subscription dict from the SubscriptionModel with additional keys."""
602
608
  from orchestrator.settings import app_settings
@@ -1,17 +1,21 @@
1
1
  from uuid import UUID
2
2
 
3
3
  from orchestrator.domain.base import SubscriptionModel
4
- from orchestrator.services.subscriptions import _generate_etag, build_extended_domain_model
4
+ from orchestrator.services.subscriptions import _generate_etag, build_domain_model, build_extended_domain_model
5
5
  from orchestrator.utils.redis import from_redis
6
6
 
7
7
 
8
- async def get_subscription_dict(subscription_id: UUID) -> tuple[dict, str]:
8
+ async def get_subscription_dict(subscription_id: UUID, inject_inuseby: bool = True) -> tuple[dict, str]:
9
9
  """Helper function to get subscription dict by uuid from db or cache."""
10
10
 
11
11
  if cached_model := from_redis(subscription_id):
12
12
  return cached_model # type: ignore
13
13
 
14
14
  subscription_model = SubscriptionModel.from_subscription(subscription_id)
15
- subscription = build_extended_domain_model(subscription_model)
15
+
16
+ if not inject_inuseby:
17
+ subscription = build_domain_model(subscription_model)
18
+ else:
19
+ subscription = build_extended_domain_model(subscription_model)
16
20
  etag = _generate_etag(subscription)
17
21
  return subscription, etag
@@ -58,7 +58,7 @@ def from_redis(subscription_id: UUID) -> tuple[PY_JSON_TYPES, str] | None:
58
58
 
59
59
  if app_settings.ENABLE_SUBSCRIPTION_MODEL_OPTIMIZATIONS:
60
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")
61
+ log.info("Using SubscriptionModel optimization, not loading subscription from redis cache")
62
62
  return None
63
63
 
64
64
  if caching_models_enabled():
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: orchestrator-core
3
- Version: 3.2.1
3
+ Version: 3.2.2
4
4
  Summary: This is the orchestrator workflow engine.
5
5
  Requires-Python: >=3.11,<3.14
6
6
  Classifier: Intended Audience :: Information Technology
@@ -1,4 +1,4 @@
1
- orchestrator/__init__.py,sha256=6M2nhPcbqy6evQZotd6vfnqqrc1M9YfUxkRSSjW12kw,1063
1
+ orchestrator/__init__.py,sha256=mMFQmoeaP3qy5_hoq-R3v0-C8-hJNlXSTQb79ERXOKk,1063
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
@@ -143,7 +143,7 @@ orchestrator/distlock/managers/memory_distlock_manager.py,sha256=HWQafcVKBF-Cka_
143
143
  orchestrator/distlock/managers/redis_distlock_manager.py,sha256=DXtMhC8qtxiFO6xU9qYXHZQnCLjlmGBpeyfLA0vbRP0,3369
144
144
  orchestrator/domain/__init__.py,sha256=20DhXQPKY0g3rTgCkRlNDY58sLviToOVF8NPoex9WJc,936
145
145
  orchestrator/domain/base.py,sha256=26UbHKXqI99w8SyS-Kjj4DF8IbgPNOh8iDPP6e3GEsA,70996
146
- orchestrator/domain/context_cache.py,sha256=eIOP0xESBdNSPw3DMWxxSOzpUzVVgnMMyMhGkl99-Xc,2228
146
+ orchestrator/domain/context_cache.py,sha256=vT1a01MBSBIaokoShK9KwjItd7abNmz7cXaF67VRZK8,2508
147
147
  orchestrator/domain/customer_description.py,sha256=v7o6TTN4oc6bWHZU-jCT-fUYvkeYahbpXOwlKXofuI8,3360
148
148
  orchestrator/domain/helpers.py,sha256=D9O2duhCAZGmm39u-9ggvU-X2JsCbIS607kF77-r8QM,2549
149
149
  orchestrator/domain/lifecycle.py,sha256=kGR0AFVOSUBlzdhgRr11CUnF26wbBYIjz8uKb_qPCg0,2922
@@ -158,9 +158,10 @@ orchestrator/forms/validators/product_id.py,sha256=u5mURLT0pOhbFLdwvYcy2_2fXMt35
158
158
  orchestrator/graphql/__init__.py,sha256=avq8Yg3Jr_9pJqh7ClyIAOX7YSg1eM_AWmt5C3FRYUY,1440
159
159
  orchestrator/graphql/autoregistration.py,sha256=pF2jbMKG26MvYoMSa6ZpqpHjVks7_NvSRFymHTgmfjs,6342
160
160
  orchestrator/graphql/pagination.py,sha256=iqVDn3GPZpiQhEydfwkBJLURY-X8wwUphS8Lkeg0BOc,2413
161
- orchestrator/graphql/schema.py,sha256=uAD7cPqjxhHGDTilTLqmovtQsm7nnv-9adLk7YHPt4I,9098
161
+ orchestrator/graphql/schema.py,sha256=gwZ3nAgKL0zlpc-aK58hSUAGPVD11Tb3aRSSK9hC39I,9204
162
162
  orchestrator/graphql/types.py,sha256=tF3B2n_4AmNIpNuA4Xg-kB-q9Xy7HU8opfDDPSX26nw,5045
163
163
  orchestrator/graphql/extensions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
164
+ orchestrator/graphql/extensions/model_cache.py,sha256=1uhMRjBs9eK7zJ1Y6P6BopX06822w2Yh9jliwYvG6yQ,1085
164
165
  orchestrator/graphql/extensions/stats.py,sha256=pGhEBQg45XvqZhRobcrCSGwt5AGmR3gflsm1dYiIg5g,2018
165
166
  orchestrator/graphql/loaders/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
166
167
  orchestrator/graphql/loaders/subscriptions.py,sha256=31zE2WC7z-tPIUmVpU1QWOJvNbLvF7sYgY7JAQ6OPJg,1856
@@ -189,7 +190,7 @@ orchestrator/graphql/schemas/product_block.py,sha256=Qk9cbA6vm7ZPrhdgPHatKRuy6Ty
189
190
  orchestrator/graphql/schemas/resource_type.py,sha256=s5d_FwQXL2-Sc-IDUxTJun5qFQ4zOP4-XcHF9ql-t1g,898
190
191
  orchestrator/graphql/schemas/settings.py,sha256=drhm5VcLmUbiYAk6WUSJcyJqjNM96E6GvpxVdPAobnA,999
191
192
  orchestrator/graphql/schemas/strawberry_pydantic_patch.py,sha256=CjNUhTKdYmLiaem-WY_mzw4HASIeaZitxGF8pPocqVw,1602
192
- orchestrator/graphql/schemas/subscription.py,sha256=_ra7MG9P2w7_WMiMx-zTOaAMinGlTKN4gwE9vej-5V8,9573
193
+ orchestrator/graphql/schemas/subscription.py,sha256=RnnxPgha_7D4Ii87cp3eyBV93_RZIryzWyVHZwyn3eA,9603
193
194
  orchestrator/graphql/schemas/version.py,sha256=HSzVg_y4Sjd5_H5rRUtu3FJKOG_8ifhvBNt_qjOtC-E,92
194
195
  orchestrator/graphql/schemas/workflow.py,sha256=0UWU0HGTiAC_5Wzh16clBd74JoYHrr38YIGV86q-si0,1276
195
196
  orchestrator/graphql/utils/__init__.py,sha256=1JvenzEVW1CBa1sGVI9I8IWnnoXIkb1hneDqph9EEZY,524
@@ -197,7 +198,7 @@ orchestrator/graphql/utils/create_resolver_error_handler.py,sha256=PpQMVwGrE9t0n
197
198
  orchestrator/graphql/utils/get_query_loaders.py,sha256=abS_HJ7K9een78gMiGq3IhwGwxQXHvZygExe0h_t9ns,815
198
199
  orchestrator/graphql/utils/get_selected_fields.py,sha256=0hBcQkU-7TNVO_KG-MmLItKm0O3gmbqoxXNkLHO-wHo,1002
199
200
  orchestrator/graphql/utils/get_selected_paths.py,sha256=H0btESeOr3_VB7zy5Cx25OS0uzBcg2Y1I-arAmSOnsQ,1382
200
- orchestrator/graphql/utils/get_subscription_product_blocks.py,sha256=U_WJfi1GWTIK373q8qLVPfYD1zhso-TjyMk3awizx3A,4818
201
+ orchestrator/graphql/utils/get_subscription_product_blocks.py,sha256=NReel2uZuiretN62xEXUIFVXgJZUIKSQ3dUsSWCCzT4,5090
201
202
  orchestrator/graphql/utils/is_query_detailed.py,sha256=ESQiM8OyhGF5vEE__cLV61oEIfnFvznoNCxi02rMTsE,2156
202
203
  orchestrator/graphql/utils/override_class.py,sha256=blwPXVHxLyXQga3KjiDzWozmMhHEWNrhLL_GDmoj6y0,1373
203
204
  orchestrator/graphql/utils/to_graphql_result_page.py,sha256=8ObkJP8reVf-TQOQVPKv1mNdfmSEMS1sG7s_-T7-pUU,902
@@ -229,7 +230,7 @@ orchestrator/migrations/versions/schema/2024-09-27_460ec6748e37_add_uuid_search_
229
230
  orchestrator/migrations/versions/schema/2024-09-27_460ec6748e37_add_uuid_search_workaround.sql,sha256=mhPnqjG5H3W8_BD7w5tYzXUQSxFOM7Rahn_MudEPTIE,5383
230
231
  orchestrator/migrations/versions/schema/2025-01-08_4c5859620539_add_version_column_to_subscription.py,sha256=xAhe74U0ZiVRo9Z8Uq7491RBbATMMUnYpTBjbG-BYL0,1690
231
232
  orchestrator/migrations/versions/schema/2025-01-19_4fjdn13f83ga_add_validate_product_type_task.py,sha256=O0GfCISIDnyohGf3Ot_2HKedGRbMqLVox6t7Wd3PMvo,894
232
- orchestrator/migrations/versions/schema/2025-02-12_bac6be6f2b4f_added_input_state_table.py,sha256=0vBDGltmOs_gcTTYlWBNOgItzqCXm8qqT02jZfTpL5c,1753
233
+ orchestrator/migrations/versions/schema/2025-02-12_bac6be6f2b4f_added_input_state_table.py,sha256=RZpLkWP1yekeZ68feO5v4LZYluKvnnIKRNDCE4tI9HM,1753
233
234
  orchestrator/migrations/versions/schema/2025-03-06_42b3d076a85b_subscription_instance_as_json_function.py,sha256=jtwDFOh-NlE31aH5dFmbynb23TZN6Mkzevxx-KLP7KE,776
234
235
  orchestrator/migrations/versions/schema/2025-03-06_42b3d076a85b_subscription_instance_as_json_function.sql,sha256=hPldk0DAesUbHv3Qd_N7U-cAk-t1wIgxt4FOA120gQ8,1776
235
236
  orchestrator/migrations/versions/schema/2025-04-09_fc5c993a4b4a_add_cascade_constraint_on_processes_.py,sha256=6kHRNSZxUze2jy7b8uRvkt5mzsax10Z-Z3lsACtPLRM,1067
@@ -261,7 +262,7 @@ orchestrator/services/products.py,sha256=BP4KyE8zO-8z7Trrs5T6zKBOw53S9BfBJnHWI3p
261
262
  orchestrator/services/resource_types.py,sha256=_QBy_JOW_X3aSTqH0CuLrq4zBJL0p7Q-UDJUcuK2_qc,884
262
263
  orchestrator/services/settings.py,sha256=u-834F4KWloXS8zi7R9mp-D3cjl-rbVjKJRU35IqhXo,2723
263
264
  orchestrator/services/subscription_relations.py,sha256=9C126TUfFvyBe7y4x007kH_dvxJ9pZ1zSnaWeH6HC5k,12261
264
- orchestrator/services/subscriptions.py,sha256=Mb1GvtpU5QQkyxB4leh5xMkbuBdKWr-CVNNbHyUj5qE,27845
265
+ orchestrator/services/subscriptions.py,sha256=u1qkCk7FuniJseQjnuDWiIlqy9ok4SY8nWUyLeJ11No,28068
265
266
  orchestrator/services/tasks.py,sha256=NjPkuauQoh9UJDcjA7OcKFgPk0i6NoKdDO7HlpGbBJ8,6575
266
267
  orchestrator/services/translations.py,sha256=GyP8soUFGej8AS8uulBsk10CCK6Kwfjv9AHMFm3ElQY,1713
267
268
  orchestrator/services/workflows.py,sha256=oH7klit4kv2NGo-BACWA0ZtajVMSJAxG5m-kM6TXIMI,3742
@@ -274,11 +275,11 @@ orchestrator/utils/enrich_process.py,sha256=o_QSy5Q4wn1SMHhzVOw6bp7uhDXr7GhAIWRD
274
275
  orchestrator/utils/errors.py,sha256=6FxvXrITmRjP5bYnJJ3CxjAwA5meNjRAVYouz4TWKkU,4653
275
276
  orchestrator/utils/fixed_inputs.py,sha256=pnL6I_19VMp_Bny8SYjSzVFNvTFDyeCxFFOWGhTnDiQ,2665
276
277
  orchestrator/utils/functional.py,sha256=X1MDNwHmkU3-8mFb21m31HGlcfc5TygliXR0sXN3-rU,8304
277
- orchestrator/utils/get_subscription_dict.py,sha256=fkgDM54hn5YGUP9_2MOcJApJK1Z6c_Rl6sJERsrOy6M,686
278
+ orchestrator/utils/get_subscription_dict.py,sha256=EoSlFUXer_Kc5G0PjDPeujugZ76kKoPrjRr4Mg4HrzY,839
278
279
  orchestrator/utils/get_updated_properties.py,sha256=egVZ0R5LNJ4e51Z8SXlU8cmb4tXxG-xb1d7OKwh-7xI,1322
279
280
  orchestrator/utils/helpers.py,sha256=NjUF3IvWdnLulliP8-JQvGGGpHrh0vs0Vm092ynw-ss,3212
280
281
  orchestrator/utils/json.py,sha256=7386sdqkrKYyy4sbn5NscwctH_v1hLyw5172P__rU3g,8341
281
- orchestrator/utils/redis.py,sha256=AGWVPt83fjRz0If0PLMlEshUYiDaJXKJdiocIr5__1s,7300
282
+ orchestrator/utils/redis.py,sha256=E5uPFCOlS1n_jv0xoaS1fuLnVv-shQn0K1hzpVwUWZY,7303
282
283
  orchestrator/utils/redis_client.py,sha256=9rhsvedjK_CyClAjUicQyge0mVIViATqKFGZyjBY3XA,1384
283
284
  orchestrator/utils/search_query.py,sha256=ji5LHtrzohGz6b1IG41cnPdpWXzLEzz4SGWgHly_yfU,16205
284
285
  orchestrator/utils/state.py,sha256=RYKVlvKDBfsBdDk9wHjZKBTlQJbV4SqtCotAlTA2-bI,14021
@@ -299,7 +300,7 @@ orchestrator/workflows/tasks/resume_workflows.py,sha256=MzJqlSXUvKStkT7NGzxZyRlf
299
300
  orchestrator/workflows/tasks/validate_product_type.py,sha256=UVX_6Kh8ueQs8ujLawnKVDdNc8UhWp_u69aNA8okR_w,3184
300
301
  orchestrator/workflows/tasks/validate_products.py,sha256=i6jQME9N8sZZWo4pkNOS1Zgwh3eB2w66DnJi9k93yNk,8521
301
302
  orchestrator/workflows/translations/en-GB.json,sha256=ST53HxkphFLTMjFHonykDBOZ7-P_KxksktZU3GbxLt0,846
302
- orchestrator_core-3.2.1.dist-info/licenses/LICENSE,sha256=b-aA5OZQuuBATmLKo_mln8CQrDPPhg3ghLzjPjLn4Tg,11409
303
- orchestrator_core-3.2.1.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
304
- orchestrator_core-3.2.1.dist-info/METADATA,sha256=SA6f9kL3n50wDwmn93yGZmHg6KGdkVpPKLKSh0j1mJ8,5029
305
- orchestrator_core-3.2.1.dist-info/RECORD,,
303
+ orchestrator_core-3.2.2.dist-info/licenses/LICENSE,sha256=b-aA5OZQuuBATmLKo_mln8CQrDPPhg3ghLzjPjLn4Tg,11409
304
+ orchestrator_core-3.2.2.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
305
+ orchestrator_core-3.2.2.dist-info/METADATA,sha256=OAszxQW45Nht7C2FhlviZ1H85z2LGfjFePbrkeKWmeY,5029
306
+ orchestrator_core-3.2.2.dist-info/RECORD,,