zenml-nightly 0.58.1.dev20240607__py3-none-any.whl → 0.58.1.dev20240609__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.
zenml/VERSION CHANGED
@@ -1 +1 @@
1
- 0.58.1.dev20240607
1
+ 0.58.1.dev20240609
zenml/artifacts/utils.py CHANGED
@@ -745,6 +745,8 @@ def _load_file_from_artifact_store(
745
745
  uri: str,
746
746
  artifact_store: "BaseArtifactStore",
747
747
  mode: str = "rb",
748
+ offset: int = 0,
749
+ length: Optional[int] = None,
748
750
  ) -> Any:
749
751
  """Load the given uri from the given artifact store.
750
752
 
@@ -752,6 +754,8 @@ def _load_file_from_artifact_store(
752
754
  uri: The uri of the file to load.
753
755
  artifact_store: The artifact store from which to load the file.
754
756
  mode: The mode in which to open the file.
757
+ offset: The offset from which to start reading.
758
+ length: The amount of bytes that should be read.
755
759
 
756
760
  Returns:
757
761
  The loaded file.
@@ -763,7 +767,19 @@ def _load_file_from_artifact_store(
763
767
  """
764
768
  try:
765
769
  with artifact_store.open(uri, mode) as text_file:
766
- return text_file.read()
770
+ if offset < 0:
771
+ # If the offset is negative, we seek backwards from the end of
772
+ # the file
773
+ try:
774
+ text_file.seek(offset, os.SEEK_END)
775
+ except OSError:
776
+ # The negative offset was too large for the file, we seek
777
+ # to the start of the file
778
+ text_file.seek(0, os.SEEK_SET)
779
+ elif offset > 0:
780
+ text_file.seek(offset, os.SEEK_SET)
781
+
782
+ return text_file.read(length)
767
783
  except FileNotFoundError:
768
784
  raise DoesNotExistException(
769
785
  f"File '{uri}' does not exist in artifact store "
zenml/zen_server/auth.py CHANGED
@@ -238,8 +238,8 @@ def authenticate_credentials(
238
238
  decoded_token = JWTToken.decode_token(
239
239
  token=access_token,
240
240
  )
241
- except AuthorizationException:
242
- error = "Authentication error: error decoding access token. You may need to rerun zenml connect."
241
+ except AuthorizationException as e:
242
+ error = f"Authentication error: error decoding access token: {e}."
243
243
  logger.exception(error)
244
244
  raise AuthorizationException(error)
245
245
 
zenml/zen_server/jwt.py CHANGED
@@ -89,7 +89,7 @@ class JWTToken(BaseModel):
89
89
  except jwt.PyJWTError as e:
90
90
  raise AuthorizationException(f"Invalid JWT token: {e}") from e
91
91
 
92
- subject: str = claims.get("sub", "")
92
+ subject: str = claims.pop("sub", "")
93
93
  if not subject:
94
94
  raise AuthorizationException(
95
95
  "Invalid JWT token: the subject claim is missing"
@@ -105,7 +105,7 @@ class JWTToken(BaseModel):
105
105
  device_id: Optional[UUID] = None
106
106
  if "device_id" in claims:
107
107
  try:
108
- device_id = UUID(claims["device_id"])
108
+ device_id = UUID(claims.pop("device_id"))
109
109
  except ValueError:
110
110
  raise AuthorizationException(
111
111
  "Invalid JWT token: the device_id claim is not a valid "
@@ -115,7 +115,7 @@ class JWTToken(BaseModel):
115
115
  api_key_id: Optional[UUID] = None
116
116
  if "api_key_id" in claims:
117
117
  try:
118
- api_key_id = UUID(claims["api_key_id"])
118
+ api_key_id = UUID(claims.pop("api_key_id"))
119
119
  except ValueError:
120
120
  raise AuthorizationException(
121
121
  "Invalid JWT token: the api_key_id claim is not a valid "
@@ -125,7 +125,7 @@ class JWTToken(BaseModel):
125
125
  pipeline_id: Optional[UUID] = None
126
126
  if "pipeline_id" in claims:
127
127
  try:
128
- pipeline_id = UUID(claims["pipeline_id"])
128
+ pipeline_id = UUID(claims.pop("pipeline_id"))
129
129
  except ValueError:
130
130
  raise AuthorizationException(
131
131
  "Invalid JWT token: the pipeline_id claim is not a valid "
@@ -135,7 +135,7 @@ class JWTToken(BaseModel):
135
135
  schedule_id: Optional[UUID] = None
136
136
  if "schedule_id" in claims:
137
137
  try:
138
- schedule_id = UUID(claims["schedule_id"])
138
+ schedule_id = UUID(claims.pop("schedule_id"))
139
139
  except ValueError:
140
140
  raise AuthorizationException(
141
141
  "Invalid JWT token: the schedule_id claim is not a valid "
@@ -165,14 +165,17 @@ class JWTToken(BaseModel):
165
165
  """
166
166
  config = server_config()
167
167
 
168
- claims: Dict[str, Any] = dict(
169
- sub=str(self.user_id),
170
- )
168
+ claims: Dict[str, Any] = self.claims.copy()
169
+
170
+ claims["sub"] = str(self.user_id)
171
171
  claims["iss"] = config.get_jwt_token_issuer()
172
172
  claims["aud"] = config.get_jwt_token_audience()
173
173
 
174
174
  if expires:
175
175
  claims["exp"] = expires
176
+ else:
177
+ claims.pop("exp", None)
178
+
176
179
  if self.device_id:
177
180
  claims["device_id"] = str(self.device_id)
178
181
  if self.api_key_id:
@@ -182,9 +185,6 @@ class JWTToken(BaseModel):
182
185
  if self.schedule_id:
183
186
  claims["schedule_id"] = str(self.schedule_id)
184
187
 
185
- # Apply custom claims
186
- claims.update(self.claims)
187
-
188
188
  return jwt.encode(
189
189
  claims,
190
190
  config.jwt_secret_key,
@@ -44,6 +44,7 @@ from zenml.enums import (
44
44
  OAuthDeviceStatus,
45
45
  OAuthGrantTypes,
46
46
  )
47
+ from zenml.exceptions import AuthorizationException
47
48
  from zenml.logger import get_logger
48
49
  from zenml.models import (
49
50
  APIKeyInternalResponse,
@@ -510,6 +511,8 @@ def api_token(
510
511
 
511
512
  Raises:
512
513
  HTTPException: If the user is not authenticated.
514
+ AuthorizationException: If trying to scope the API token to a different
515
+ pipeline/schedule than the token used to authorize this request.
513
516
  """
514
517
  token = auth_context.access_token
515
518
  if not token or not auth_context.encoded_access_token:
@@ -523,6 +526,20 @@ def api_token(
523
526
  resource_type=ResourceType.PIPELINE_RUN, action=Action.CREATE
524
527
  )
525
528
 
529
+ if pipeline_id and token.pipeline_id and pipeline_id != token.pipeline_id:
530
+ raise AuthorizationException(
531
+ f"Unable to scope API token to pipeline {pipeline_id}. The "
532
+ f"token used to authorize this request is already scoped to "
533
+ f"pipeline {token.pipeline_id}."
534
+ )
535
+
536
+ if schedule_id and token.schedule_id and schedule_id != token.schedule_id:
537
+ raise AuthorizationException(
538
+ f"Unable to scope API token to schedule {schedule_id}. The "
539
+ f"token used to authorize this request is already scoped to "
540
+ f"schedule {token.schedule_id}."
541
+ )
542
+
526
543
  if not token.device_id and not token.api_key_id:
527
544
  # If not authenticated with a device or a service account, the current
528
545
  # API token is returned as is, without any modifications. Issuing
@@ -13,7 +13,7 @@
13
13
  # permissions and limitations under the License.
14
14
  """Endpoint definitions for steps (and artifacts) of pipeline runs."""
15
15
 
16
- from typing import Any, Dict
16
+ from typing import Any, Dict, Optional
17
17
  from uuid import UUID
18
18
 
19
19
  from fastapi import APIRouter, Depends, HTTPException, Security
@@ -239,12 +239,16 @@ def get_step_status(
239
239
  @handle_exceptions
240
240
  def get_step_logs(
241
241
  step_id: UUID,
242
+ offset: int = 0,
243
+ length: Optional[int] = 1024 * 1024 * 16, # Default to 16MiB of data
242
244
  _: AuthContext = Security(authorize),
243
245
  ) -> str:
244
246
  """Get the logs of a specific step.
245
247
 
246
248
  Args:
247
249
  step_id: ID of the step for which to get the logs.
250
+ offset: The offset from which to start reading.
251
+ length: The amount of bytes that should be read.
248
252
 
249
253
  Returns:
250
254
  The logs of the step.
@@ -265,6 +269,10 @@ def get_step_logs(
265
269
  artifact_store = _load_artifact_store(logs.artifact_store_id, store)
266
270
  return str(
267
271
  _load_file_from_artifact_store(
268
- logs.uri, artifact_store=artifact_store, mode="r"
269
- )
272
+ logs.uri,
273
+ artifact_store=artifact_store,
274
+ mode="rb",
275
+ offset=offset,
276
+ length=length,
277
+ ).decode()
270
278
  )
@@ -3730,7 +3730,13 @@ class RestZenStore(BaseZenStore):
3730
3730
  return self._session
3731
3731
 
3732
3732
  def clear_session(self) -> None:
3733
- """Clear the authentication session and any cached API tokens."""
3733
+ """Clear the authentication session and any cached API tokens.
3734
+
3735
+ Raises:
3736
+ AuthorizationException: If the API token can't be reset because
3737
+ the store configuration does not contain username and password
3738
+ or an API key to fetch a new token.
3739
+ """
3734
3740
  self._session = None
3735
3741
  self._api_token = None
3736
3742
  # Clear the configured API token only if it's possible to fetch a new
@@ -3742,6 +3748,16 @@ class RestZenStore(BaseZenStore):
3742
3748
  or self.config.api_key is not None
3743
3749
  ):
3744
3750
  self.config.api_token = None
3751
+ elif self.config.api_token:
3752
+ raise AuthorizationException(
3753
+ "Unable to refresh invalid API token. This is probably "
3754
+ "because you're connected to your ZenML server with device "
3755
+ "authentication. Rerunning `zenml connect --url "
3756
+ f"{self.config.url}` should solve this issue. "
3757
+ "If you're seeing this error from an automated workload, "
3758
+ "you should probably use a service account to start that "
3759
+ "workload to prevent this error."
3760
+ )
3745
3761
 
3746
3762
  @staticmethod
3747
3763
  def _handle_response(response: requests.Response) -> Json:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: zenml-nightly
3
- Version: 0.58.1.dev20240607
3
+ Version: 0.58.1.dev20240609
4
4
  Summary: ZenML: Write production-ready ML code.
5
5
  Home-page: https://zenml.io
6
6
  License: Apache-2.0
@@ -6,7 +6,7 @@ RELEASE_NOTES.md,sha256=nXkCYUiBFMOQrSB9Vmsl5zouXPEBE90pdxDp75jxxwU,329317
6
6
  ROADMAP.md,sha256=hiLSmr16BH8Dfx7SaQM4JcXCGCVl6mFZPFAwJeDTrJU,407
7
7
  SECURITY.md,sha256=9DepA8y03yvCZLHEfcXLTDH4lUyKHquAdukBsccNN7c,682
8
8
  zenml/README.md,sha256=827dekbOWAs1BpW7VF1a4d7EbwPbjwccX-2zdXBENZo,1777
9
- zenml/VERSION,sha256=B76BYXykJsh0PGXOE7JJl6BC7xbpqxYAZTYkWSlIdHA,19
9
+ zenml/VERSION,sha256=COl3SgNWbyFl1SMIf5AT0gOmTLSZ4VoLOlmyyr0G8Ao,19
10
10
  zenml/__init__.py,sha256=qT3QHiuTgUwk05sOiML0Gcjre3pugvDAw84_br9Psvo,2394
11
11
  zenml/_hub/__init__.py,sha256=6qDzpQAAZa__Aiiz0mC1qM-9dw9_jk_v_aXeJknxbDE,644
12
12
  zenml/_hub/client.py,sha256=LAxLuDjY9ya1PPRzxS0_pvu__FSpAqEjbtHTqVwgMWc,9071
@@ -36,7 +36,7 @@ zenml/artifacts/artifact_config.py,sha256=pG4qqnIRF5V9we_mIPIbwaynUpWAqOCunH8Ex5
36
36
  zenml/artifacts/external_artifact.py,sha256=IG5wKuPzy8nxDZyMb34C1TD81GJW0Ot_gtxQsSod8rY,6117
37
37
  zenml/artifacts/external_artifact_config.py,sha256=RSsu9ZBLgnI6YvB1Dxw9H_u66ZXcac28o2QuN6EABWI,3861
38
38
  zenml/artifacts/unmaterialized_artifact.py,sha256=tQX1ngze4vik4VTpSGN00ozyCv0ebHouULjTYtqOsf8,1514
39
- zenml/artifacts/utils.py,sha256=mabZRLgzyhFDJojqboSPWjt1ZwcijEaMpazsuwBpcW4,30470
39
+ zenml/artifacts/utils.py,sha256=6H6e0a5e7LOEq4Po4GBj6GrXoCSETpN2DPZ7zlF14gE,31144
40
40
  zenml/cli/__init__.py,sha256=ZRjE6Ug536SseT7puiCyMmcvw4mr0P_ZBoa-RmwIiEQ,77081
41
41
  zenml/cli/annotator.py,sha256=tEdducGdFn57DFLJVZQ-MyXH1auTGFueRmDc78N-vPQ,6970
42
42
  zenml/cli/artifact.py,sha256=UY_inYCNCKPzHyFXfSRnpR2xHaQYzqL1hvKGxaRHKMU,9012
@@ -734,7 +734,7 @@ zenml/utils/uuid_utils.py,sha256=aOGQ2SdREexcVQICPU2jUAgjvAJxTmh4ESdM52PEhck,204
734
734
  zenml/utils/visualization_utils.py,sha256=SawUfw1OTkU2GV5rH7tw7hd3eqpFooiB4Fvr_7MoTus,4555
735
735
  zenml/utils/yaml_utils.py,sha256=DIZOD7iExpvcjDlInCp3hhKnx2o4vmVBsNZEQYQcN44,5750
736
736
  zenml/zen_server/__init__.py,sha256=WyltI9TzFW2mEHZVOs6alLWMCQrrZaFALtrQXs83STA,1355
737
- zenml/zen_server/auth.py,sha256=NxeRO43uXmFH0-s6B8aWsVeN6DUPvL-SqCKdAK2-0cg,26264
737
+ zenml/zen_server/auth.py,sha256=KhPFDKpIMyQFl-yDacOljNZTrPc87S-RsqwWTq1urcM,26238
738
738
  zenml/zen_server/cloud_utils.py,sha256=w7pdc-9YJyqL7tvrGhTDINf2fum8HdtpRBZAUY9fiNg,6327
739
739
  zenml/zen_server/dashboard/assets/404-D5p6PIdn.js,sha256=8MB4WqhTpuaeEAwYhEjnzJKipxtK_0_Mor37gBsyIgE,1033
740
740
  zenml/zen_server/dashboard/assets/@radix-C9DBgJhe.js,sha256=MrmwKYEEzL1fZ5cWQ5TCAPTAjKZV5I-lVUEx_hbDFWc,262349
@@ -1053,7 +1053,7 @@ zenml/zen_server/feature_gate/__init__.py,sha256=yabe4fBY6NSusn-QlKQDLOvXVLERNpc
1053
1053
  zenml/zen_server/feature_gate/endpoint_utils.py,sha256=upQzEOlrD5c6cg0TfxYVM4BKDHle3LIV_c7NZHoaKvg,2220
1054
1054
  zenml/zen_server/feature_gate/feature_gate_interface.py,sha256=AdYAsHiNbGaloxSHpm7DLSsa8CDEGu8ikyxp9U3a0wU,1639
1055
1055
  zenml/zen_server/feature_gate/zenml_cloud_feature_gate.py,sha256=_qpA4qGgtCAwZX5stcj4auz5aWDVi-ouPiUqS20LFPo,4250
1056
- zenml/zen_server/jwt.py,sha256=AcazqLIFGD_eSoR-toQcUJAdEIlpoSLbHI2zbGsWtes,6139
1056
+ zenml/zen_server/jwt.py,sha256=cMJS24EDXqhZ_-hOFDORsZMgNF6JcfSGNYFOuh99u_w,6151
1057
1057
  zenml/zen_server/pipeline_deployment/__init__.py,sha256=79knXLKfegsvVSVSWecpqrepq6iAavTUA4hKuiDk-WE,613
1058
1058
  zenml/zen_server/pipeline_deployment/runner_entrypoint_configuration.py,sha256=PpraJ77nCOUesVC6IBxNTeVMdS3eWG2vt-4GrT8hORQ,1682
1059
1059
  zenml/zen_server/pipeline_deployment/utils.py,sha256=T4dqRAl3Khb3UdbfPEpDf4Mek_mnE9FRbTJ_vmoj6Nw,13039
@@ -1068,7 +1068,7 @@ zenml/zen_server/rbac/zenml_cloud_rbac.py,sha256=_rWFCmZRk5Z5TzmWmCJAYzSoXSz1C0d
1068
1068
  zenml/zen_server/routers/__init__.py,sha256=ViyAhWL-ogHxE9wBvB_iMcur5H1NRVrzXkpogVY7FBA,641
1069
1069
  zenml/zen_server/routers/artifact_endpoint.py,sha256=XhbOat2pddDluiT0a_QH2RfNcqlwtf3yf2Fh92a6ZDw,5175
1070
1070
  zenml/zen_server/routers/artifact_version_endpoints.py,sha256=o9RVrnzuiY-AV6Nj10YwXQ_jy-OqCVMk1hEKfmihPEQ,7751
1071
- zenml/zen_server/routers/auth_endpoints.py,sha256=U-Fsvi2oZAaYTj5LYMAokWDofam7YC9cZPzo4iAVWLY,18440
1071
+ zenml/zen_server/routers/auth_endpoints.py,sha256=xl8g5AVz_hiHVpofaLxyWHc2SDshH6028kYtHY6IRg0,19289
1072
1072
  zenml/zen_server/routers/code_repositories_endpoints.py,sha256=WFCRPsv3Qrm8QtCr5zxfSdgCS5WI1DPNCF4Y6vLXGow,4747
1073
1073
  zenml/zen_server/routers/devices_endpoints.py,sha256=2YoVb_a3fEjsYs8wMQYkzz9qM0n3ylHB6yTVNmhIVRU,10598
1074
1074
  zenml/zen_server/routers/event_source_endpoints.py,sha256=dXupWrySV3LtxsqMVoYpUJ-OK7q6o6ehfuYW_RU1JlA,10379
@@ -1089,7 +1089,7 @@ zenml/zen_server/routers/service_connectors_endpoints.py,sha256=Imf1WAKUiATPiQsi
1089
1089
  zenml/zen_server/routers/service_endpoints.py,sha256=PyAxQLewVnpotfQI_OmyUTl7ohTCrWrteA9nfvxDRyU,5055
1090
1090
  zenml/zen_server/routers/stack_components_endpoints.py,sha256=mFVeAZY2QmlEdeowvaFO0wy6mG2zQlybOBvrFg4zWdU,6110
1091
1091
  zenml/zen_server/routers/stacks_endpoints.py,sha256=imL7s26xFOf4EY4zwSz8y8-Ggl6-Xfu-tJY75TNodeo,4540
1092
- zenml/zen_server/routers/steps_endpoints.py,sha256=kd3CFI2NXyaECSl9tlvdGCjt223tRiyvp2g96BjK_JA,7511
1092
+ zenml/zen_server/routers/steps_endpoints.py,sha256=oWI6B1erltAbG_PmVFjZeH2JElH7J46ecnp77lKO52s,7818
1093
1093
  zenml/zen_server/routers/tags_endpoints.py,sha256=oK-A-EqAsrIXXgl7A870I2PT8_fct1dZVQDQ-g8GHys,4941
1094
1094
  zenml/zen_server/routers/triggers_endpoints.py,sha256=FpnDKcWirLqV757radYCcEeNOUca3tjsmQnnLJMJJRs,14445
1095
1095
  zenml/zen_server/routers/users_endpoints.py,sha256=f-ZuuqtOwiyhtucJ3RAJ4IzeQVYcPTaQBca_wAtlxcU,25199
@@ -1245,7 +1245,7 @@ zenml/zen_stores/migrations/versions/ec0d785ca296_create_run_metadata_table.py,s
1245
1245
  zenml/zen_stores/migrations/versions/f3b3964e3a0f_add_oauth_devices.py,sha256=2CR4R-7Vx6j_AXxo-e5Guy6OX-ZnS47HSKSGfqlO-y0,3065
1246
1246
  zenml/zen_stores/migrations/versions/f49904a80aa7_increase_length_of_artifact_table_sources.py,sha256=kLgfDUnQdAb5_SyFx3VKXDLC0YbuBKf9iXRDNeBin7Q,1618
1247
1247
  zenml/zen_stores/migrations/versions/fbd7f18ced1e_increase_step_run_field_lengths.py,sha256=mqNSUNTkrzkGmOscmwmR98lqC7hFH4PXQPxTPFvBxps,1964
1248
- zenml/zen_stores/rest_zen_store.py,sha256=zRTt6t9VWAu2B1NhP2RCENoIKsxIUt8ft8U0cuVdkxU,137354
1248
+ zenml/zen_stores/rest_zen_store.py,sha256=72twP2owz52SY6ecqiDLGX2t_vwAx2J1LLTBdtN0cs4,138159
1249
1249
  zenml/zen_stores/schemas/__init__.py,sha256=y9bkvHil9HkHU5s2eFRhnMlQM3n2Idu83kva-hjrOtM,4080
1250
1250
  zenml/zen_stores/schemas/api_key_schemas.py,sha256=lyFE3gtCQx00r4jaUaEUYcqcGWvjqpPdatb-WrOExPk,7064
1251
1251
  zenml/zen_stores/schemas/artifact_schemas.py,sha256=8YDgZwmWSnFrENyyzvNU4XYKW9Rg8_WdJHfU7AxGiYc,13170
@@ -1287,8 +1287,8 @@ zenml/zen_stores/secrets_stores/service_connector_secrets_store.py,sha256=0CgWJo
1287
1287
  zenml/zen_stores/secrets_stores/sql_secrets_store.py,sha256=Cips_SPX7CtfYQyOZNiHAhmVtVgb17HCyHQia5MlTX4,8653
1288
1288
  zenml/zen_stores/sql_zen_store.py,sha256=f5spF_vWJg8kO6ud9uzELqh7LRxSAWyB1HLono1_gT0,353540
1289
1289
  zenml/zen_stores/zen_store_interface.py,sha256=vi0tr6vDES97h_rFZZYeaz2Y0nM4xm66CcUXSOEITN4,86372
1290
- zenml_nightly-0.58.1.dev20240607.dist-info/LICENSE,sha256=wbnfEnXnafPbqwANHkV6LUsPKOtdpsd-SNw37rogLtc,11359
1291
- zenml_nightly-0.58.1.dev20240607.dist-info/METADATA,sha256=aM_bXFMT5x-uqu3-fUs-MowURaMqFu3o8CxG2Jxjm94,19094
1292
- zenml_nightly-0.58.1.dev20240607.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1293
- zenml_nightly-0.58.1.dev20240607.dist-info/entry_points.txt,sha256=QK3ETQE0YswAM2mWypNMOv8TLtr7EjnqAFq1br_jEFE,43
1294
- zenml_nightly-0.58.1.dev20240607.dist-info/RECORD,,
1290
+ zenml_nightly-0.58.1.dev20240609.dist-info/LICENSE,sha256=wbnfEnXnafPbqwANHkV6LUsPKOtdpsd-SNw37rogLtc,11359
1291
+ zenml_nightly-0.58.1.dev20240609.dist-info/METADATA,sha256=cZ-_RALqyhbiBd4HYXdTm_iBJKPaBcLDkyzhPq2l9Vw,19094
1292
+ zenml_nightly-0.58.1.dev20240609.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1293
+ zenml_nightly-0.58.1.dev20240609.dist-info/entry_points.txt,sha256=QK3ETQE0YswAM2mWypNMOv8TLtr7EjnqAFq1br_jEFE,43
1294
+ zenml_nightly-0.58.1.dev20240609.dist-info/RECORD,,