mlrun 1.7.1rc4__py3-none-any.whl → 1.7.1rc6__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.

Potentially problematic release.


This version of mlrun might be problematic. Click here for more details.

@@ -23,7 +23,7 @@ import mlrun.model_monitoring.applications.base as mm_base
23
23
  import mlrun.model_monitoring.applications.context as mm_context
24
24
  from mlrun.errors import MLRunIncompatibleVersionError
25
25
 
26
- SUPPORTED_EVIDENTLY_VERSION = semver.Version.parse("0.4.32")
26
+ SUPPORTED_EVIDENTLY_VERSION = semver.Version.parse("0.4.39")
27
27
 
28
28
 
29
29
  def _check_evidently_version(*, cur: semver.Version, ref: semver.Version) -> None:
@@ -82,9 +82,10 @@ class TDEngineSchema:
82
82
  super_table: str,
83
83
  columns: dict[str, _TDEngineColumn],
84
84
  tags: dict[str, str],
85
+ project: str,
85
86
  database: Optional[str] = None,
86
87
  ):
87
- self.super_table = super_table
88
+ self.super_table = f"{super_table}_{project.replace('-', '_')}"
88
89
  self.columns = columns
89
90
  self.tags = tags
90
91
  self.database = database or _MODEL_MONITORING_DATABASE
@@ -148,6 +149,9 @@ class TDEngineSchema:
148
149
  ) -> str:
149
150
  return f"DROP TABLE if EXISTS {self.database}.{subtable};"
150
151
 
152
+ def drop_supertable_query(self) -> str:
153
+ return f"DROP STABLE if EXISTS {self.database}.{self.super_table};"
154
+
151
155
  def _get_subtables_query(
152
156
  self,
153
157
  values: dict[str, Union[str, int, float, datetime.datetime]],
@@ -227,7 +231,7 @@ class TDEngineSchema:
227
231
 
228
232
  @dataclass
229
233
  class AppResultTable(TDEngineSchema):
230
- def __init__(self, database: Optional[str] = None):
234
+ def __init__(self, project: str, database: Optional[str] = None):
231
235
  super_table = mm_schemas.TDEngineSuperTables.APP_RESULTS
232
236
  columns = {
233
237
  mm_schemas.WriterEvent.END_INFER_TIME: _TDEngineColumn.TIMESTAMP,
@@ -236,18 +240,23 @@ class AppResultTable(TDEngineSchema):
236
240
  mm_schemas.ResultData.RESULT_STATUS: _TDEngineColumn.INT,
237
241
  }
238
242
  tags = {
239
- mm_schemas.EventFieldType.PROJECT: _TDEngineColumn.BINARY_64,
240
243
  mm_schemas.WriterEvent.ENDPOINT_ID: _TDEngineColumn.BINARY_64,
241
244
  mm_schemas.WriterEvent.APPLICATION_NAME: _TDEngineColumn.BINARY_64,
242
245
  mm_schemas.ResultData.RESULT_NAME: _TDEngineColumn.BINARY_64,
243
246
  mm_schemas.ResultData.RESULT_KIND: _TDEngineColumn.INT,
244
247
  }
245
- super().__init__(super_table, columns, tags, database)
248
+ super().__init__(
249
+ super_table=super_table,
250
+ columns=columns,
251
+ tags=tags,
252
+ database=database,
253
+ project=project,
254
+ )
246
255
 
247
256
 
248
257
  @dataclass
249
258
  class Metrics(TDEngineSchema):
250
- def __init__(self, database: Optional[str] = None):
259
+ def __init__(self, project: str, database: Optional[str] = None):
251
260
  super_table = mm_schemas.TDEngineSuperTables.METRICS
252
261
  columns = {
253
262
  mm_schemas.WriterEvent.END_INFER_TIME: _TDEngineColumn.TIMESTAMP,
@@ -255,17 +264,22 @@ class Metrics(TDEngineSchema):
255
264
  mm_schemas.MetricData.METRIC_VALUE: _TDEngineColumn.FLOAT,
256
265
  }
257
266
  tags = {
258
- mm_schemas.EventFieldType.PROJECT: _TDEngineColumn.BINARY_64,
259
267
  mm_schemas.WriterEvent.ENDPOINT_ID: _TDEngineColumn.BINARY_64,
260
268
  mm_schemas.WriterEvent.APPLICATION_NAME: _TDEngineColumn.BINARY_64,
261
269
  mm_schemas.MetricData.METRIC_NAME: _TDEngineColumn.BINARY_64,
262
270
  }
263
- super().__init__(super_table, columns, tags, database)
271
+ super().__init__(
272
+ super_table=super_table,
273
+ columns=columns,
274
+ tags=tags,
275
+ database=database,
276
+ project=project,
277
+ )
264
278
 
265
279
 
266
280
  @dataclass
267
281
  class Predictions(TDEngineSchema):
268
- def __init__(self, database: Optional[str] = None):
282
+ def __init__(self, project: str, database: Optional[str] = None):
269
283
  super_table = mm_schemas.TDEngineSuperTables.PREDICTIONS
270
284
  columns = {
271
285
  mm_schemas.EventFieldType.TIME: _TDEngineColumn.TIMESTAMP,
@@ -273,7 +287,12 @@ class Predictions(TDEngineSchema):
273
287
  mm_schemas.EventKeyMetrics.CUSTOM_METRICS: _TDEngineColumn.BINARY_10000,
274
288
  }
275
289
  tags = {
276
- mm_schemas.EventFieldType.PROJECT: _TDEngineColumn.BINARY_64,
277
290
  mm_schemas.WriterEvent.ENDPOINT_ID: _TDEngineColumn.BINARY_64,
278
291
  }
279
- super().__init__(super_table, columns, tags, database)
292
+ super().__init__(
293
+ super_table=super_table,
294
+ columns=columns,
295
+ tags=tags,
296
+ database=database,
297
+ project=project,
298
+ )
@@ -82,13 +82,13 @@ class TDEngineConnector(TSDBConnector):
82
82
  """Initialize the super tables for the TSDB."""
83
83
  self.tables = {
84
84
  mm_schemas.TDEngineSuperTables.APP_RESULTS: tdengine_schemas.AppResultTable(
85
- self.database
85
+ project=self.project, database=self.database
86
86
  ),
87
87
  mm_schemas.TDEngineSuperTables.METRICS: tdengine_schemas.Metrics(
88
- self.database
88
+ project=self.project, database=self.database
89
89
  ),
90
90
  mm_schemas.TDEngineSuperTables.PREDICTIONS: tdengine_schemas.Predictions(
91
- self.database
91
+ project=self.project, database=self.database
92
92
  ),
93
93
  }
94
94
 
@@ -112,11 +112,9 @@ class TDEngineConnector(TSDBConnector):
112
112
  """
113
113
 
114
114
  table_name = (
115
- f"{self.project}_"
116
115
  f"{event[mm_schemas.WriterEvent.ENDPOINT_ID]}_"
117
- f"{event[mm_schemas.WriterEvent.APPLICATION_NAME]}_"
116
+ f"{event[mm_schemas.WriterEvent.APPLICATION_NAME]}"
118
117
  )
119
- event[mm_schemas.EventFieldType.PROJECT] = self.project
120
118
 
121
119
  if kind == mm_schemas.WriterEventKind.RESULT:
122
120
  # Write a new result
@@ -187,7 +185,9 @@ class TDEngineConnector(TSDBConnector):
187
185
  name=name,
188
186
  after=after,
189
187
  url=self._tdengine_connection_string,
190
- supertable=mm_schemas.TDEngineSuperTables.PREDICTIONS,
188
+ supertable=self.tables[
189
+ mm_schemas.TDEngineSuperTables.PREDICTIONS
190
+ ].super_table,
191
191
  table_col=mm_schemas.EventFieldType.TABLE_COLUMN,
192
192
  time_col=mm_schemas.EventFieldType.TIME,
193
193
  database=self.database,
@@ -220,22 +220,23 @@ class TDEngineConnector(TSDBConnector):
220
220
  "Deleting all project resources using the TDEngine connector",
221
221
  project=self.project,
222
222
  )
223
+ drop_statements = []
223
224
  for table in self.tables:
224
- get_subtable_names_query = self.tables[table]._get_subtables_query(
225
- values={mm_schemas.EventFieldType.PROJECT: self.project}
226
- )
227
- subtables = self.connection.run(
228
- query=get_subtable_names_query,
225
+ drop_statements.append(self.tables[table].drop_supertable_query())
226
+
227
+ try:
228
+ self.connection.run(
229
+ statements=drop_statements,
229
230
  timeout=self._timeout,
230
231
  retries=self._retries,
231
- ).data
232
- drop_statements = []
233
- for subtable in subtables:
234
- drop_statements.append(
235
- self.tables[table]._drop_subtable_query(subtable=subtable[0])
236
- )
237
- self.connection.run(
238
- statements=drop_statements, timeout=self._timeout, retries=self._retries
232
+ )
233
+ except Exception as e:
234
+ logger.warning(
235
+ "Failed to drop TDEngine tables. You may need to drop them manually. "
236
+ "These can be found under the following supertables: app_results, "
237
+ "metrics, and predictions.",
238
+ project=self.project,
239
+ error=mlrun.errors.err_to_str(e),
239
240
  )
240
241
  logger.debug(
241
242
  "Deleted all project resources using the TDEngine connector",
@@ -288,13 +289,6 @@ class TDEngineConnector(TSDBConnector):
288
289
  :raise: MLRunInvalidArgumentError if query the provided table failed.
289
290
  """
290
291
 
291
- project_condition = f"project = '{self.project}'"
292
- filter_query = (
293
- f"({filter_query}) AND ({project_condition})"
294
- if filter_query
295
- else project_condition
296
- )
297
-
298
292
  full_query = tdengine_schemas.TDEngineSchema._get_records_query(
299
293
  table=table,
300
294
  start=start,
@@ -346,12 +340,12 @@ class TDEngineConnector(TSDBConnector):
346
340
  timestamp_column = mm_schemas.WriterEvent.END_INFER_TIME
347
341
  columns = [timestamp_column, mm_schemas.WriterEvent.APPLICATION_NAME]
348
342
  if type == "metrics":
349
- table = mm_schemas.TDEngineSuperTables.METRICS
343
+ table = self.tables[mm_schemas.TDEngineSuperTables.METRICS].super_table
350
344
  name = mm_schemas.MetricData.METRIC_NAME
351
345
  columns += [name, mm_schemas.MetricData.METRIC_VALUE]
352
346
  df_handler = self.df_to_metrics_values
353
347
  elif type == "results":
354
- table = mm_schemas.TDEngineSuperTables.APP_RESULTS
348
+ table = self.tables[mm_schemas.TDEngineSuperTables.APP_RESULTS].super_table
355
349
  name = mm_schemas.ResultData.RESULT_NAME
356
350
  columns += [
357
351
  name,
@@ -417,7 +411,7 @@ class TDEngineConnector(TSDBConnector):
417
411
  "both or neither of `aggregation_window` and `agg_funcs` must be provided"
418
412
  )
419
413
  df = self._get_records(
420
- table=mm_schemas.TDEngineSuperTables.PREDICTIONS,
414
+ table=self.tables[mm_schemas.TDEngineSuperTables.PREDICTIONS].super_table,
421
415
  start=start,
422
416
  end=end,
423
417
  columns=[mm_schemas.EventFieldType.LATENCY],
@@ -984,14 +984,16 @@ def github_webhook(request):
984
984
  return {"msg": "pushed"}
985
985
 
986
986
 
987
- def load_and_run(*args, **kwargs):
987
+ def load_and_run(context, *args, **kwargs):
988
988
  """
989
989
  This function serves as an alias to `load_and_run_workflow`,
990
990
  allowing to continue using `load_and_run` without modifying existing workflows or exported runs.
991
991
  This approach ensures backward compatibility,
992
992
  while directing all new calls to the updated `load_and_run_workflow` function.
993
993
  """
994
- load_and_run_workflow(kwargs.pop("load_only", None))
994
+ kwargs.pop("load_only", None)
995
+ kwargs.pop("save", None)
996
+ load_and_run_workflow(context, *args, **kwargs)
995
997
 
996
998
 
997
999
  def load_and_run_workflow(
mlrun/utils/helpers.py CHANGED
@@ -1226,14 +1226,24 @@ def datetime_to_iso(time_obj: Optional[datetime]) -> Optional[str]:
1226
1226
  return time_obj.isoformat()
1227
1227
 
1228
1228
 
1229
- def enrich_datetime_with_tz_info(timestamp_string):
1229
+ def enrich_datetime_with_tz_info(timestamp_string) -> Optional[datetime]:
1230
1230
  if not timestamp_string:
1231
1231
  return timestamp_string
1232
1232
 
1233
1233
  if timestamp_string and not mlrun.utils.helpers.has_timezone(timestamp_string):
1234
1234
  timestamp_string += datetime.now(timezone.utc).astimezone().strftime("%z")
1235
1235
 
1236
- return datetime.strptime(timestamp_string, "%Y-%m-%d %H:%M:%S.%f%z")
1236
+ for _format in [
1237
+ # e.g: 2021-08-25 12:00:00.000Z
1238
+ "%Y-%m-%d %H:%M:%S.%f%z",
1239
+ # e.g: 2024-11-11 07:44:56+0000
1240
+ "%Y-%m-%d %H:%M:%S%z",
1241
+ ]:
1242
+ try:
1243
+ return datetime.strptime(timestamp_string, _format)
1244
+ except ValueError as exc:
1245
+ last_exc = exc
1246
+ raise last_exc
1237
1247
 
1238
1248
 
1239
1249
  def has_timezone(timestamp):
@@ -1,4 +1,4 @@
1
1
  {
2
- "git_commit": "cea87e17a9fb69a20668cbe421819d4fc73f08a3",
3
- "version": "1.7.1-rc4"
2
+ "git_commit": "5f5a1c36e7f5879da41dcf68f3ed34c0f67f480d",
3
+ "version": "1.7.1-rc6"
4
4
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mlrun
3
- Version: 1.7.1rc4
3
+ Version: 1.7.1rc6
4
4
  Summary: Tracking and config of machine learning runs
5
5
  Home-page: https://github.com/mlrun/mlrun
6
6
  Author: Yaron Haviv
@@ -224,7 +224,7 @@ mlrun/model_monitoring/applications/__init__.py,sha256=QYvzgCutFdAkzqKPD3mvkX_3c
224
224
  mlrun/model_monitoring/applications/_application_steps.py,sha256=FWgEldIC0Jbg0KLMBIcSNv8uULD1QZ3i7xcC4kEWmrA,7231
225
225
  mlrun/model_monitoring/applications/base.py,sha256=uzc14lFlwTJnL0p2VBCzmp-CNoHd73cK_Iz0YHC1KAs,4380
226
226
  mlrun/model_monitoring/applications/context.py,sha256=vOZ_ZgUuy5UsNe22-puJSt7TB32HiZtqBdN1hegykuQ,12436
227
- mlrun/model_monitoring/applications/evidently_base.py,sha256=FSzmoDZP8EiSQ3tq5RmU7kJ6edh8bWaKQh0rBORjODY,5099
227
+ mlrun/model_monitoring/applications/evidently_base.py,sha256=Z9v7Pa5PEQqei3FvhUdREfKK82tUDSQix4ELeNQZyoA,5099
228
228
  mlrun/model_monitoring/applications/histogram_data_drift.py,sha256=wRCttgK1H4eRDiAJJ7Aid2hPuQPzUoBY3hSHlVkdE5w,13337
229
229
  mlrun/model_monitoring/applications/results.py,sha256=B0YuLig4rgBzBs3OAh01yLavhtNgj8Oz1RD8UfEkENU,3590
230
230
  mlrun/model_monitoring/db/__init__.py,sha256=6Ic-X3Fh9XLPYMytmevGNSs-Hii1rAjLLoFTSPwTguw,736
@@ -243,9 +243,9 @@ mlrun/model_monitoring/db/tsdb/__init__.py,sha256=Zqh_27I2YAEHk9nl0Z6lUxP7VEfrgr
243
243
  mlrun/model_monitoring/db/tsdb/base.py,sha256=X89X763sDrShfRXE1N-p8k97E8NBs7O1QJFiO-CffLM,18583
244
244
  mlrun/model_monitoring/db/tsdb/helpers.py,sha256=0oUXc4aUkYtP2SGP6jTb3uPPKImIUsVsrb9otX9a7O4,1189
245
245
  mlrun/model_monitoring/db/tsdb/tdengine/__init__.py,sha256=vgBdsKaXUURKqIf3M0y4sRatmSVA4CQiJs7J5dcVBkQ,620
246
- mlrun/model_monitoring/db/tsdb/tdengine/schemas.py,sha256=UOtb-0shOyKxfYnNzI5uNM5fdI9FbbSDGGRuzvgOKO8,10560
246
+ mlrun/model_monitoring/db/tsdb/tdengine/schemas.py,sha256=bzPK5PGRznvw8RX09rP7s5XPxbiCc0uAcPB9WdgJzzM,10922
247
247
  mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py,sha256=Hb0vcCBP-o0ET78mU4P32fnhUL65QZv-pMuv2lnCby4,1586
248
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py,sha256=ZpYqPLq8l9aRzgAZ-1uxY_T1eRfx2I2_k7mGfKR2vwI,19683
248
+ mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py,sha256=uNc6JjoGI8GFs1GR6f8_zxUpwkTsc4yydApNd3cfI6c,19566
249
249
  mlrun/model_monitoring/db/tsdb/v3io/__init__.py,sha256=aL3bfmQsUQ-sbvKGdNihFj8gLCK3mSys0qDcXtYOwgc,616
250
250
  mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py,sha256=mbmhN4f_F58ptVjhwoMF6ifZSdnZWhK7x8eNsWS39IA,6217
251
251
  mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py,sha256=1H-IBXPNJPRAaxDMGWpUU25QqfR87LpZbJ03vaJkICs,32858
@@ -272,7 +272,7 @@ mlrun/platforms/__init__.py,sha256=ggSGF7inITs6S-vj9u4S9X_5psgbA0G3GVqf7zu8qYc,2
272
272
  mlrun/platforms/iguazio.py,sha256=MNRzIzxcc_3wsePLjBXuKKKSaObVnnrC3ZyXgSRu8m0,13697
273
273
  mlrun/projects/__init__.py,sha256=0Krf0WIKfnZa71WthYOg0SoaTodGg3sV_hK3f_OlTPI,1220
274
274
  mlrun/projects/operations.py,sha256=gtqSU9OvYOV-b681uQtWgnW7YSnX6qfa1Mt1Xm4f1ZI,19752
275
- mlrun/projects/pipelines.py,sha256=IE8MpuXPnXi0_izOCEC1dtpEctcdWZUyCADnMvAZH0M,45331
275
+ mlrun/projects/pipelines.py,sha256=6_EPuKQ5pN1z-3UgyGeMyDZU1hrkkgv7Fgh5KGE074Q,45398
276
276
  mlrun/projects/project.py,sha256=UOu625oJUwJA9o--MboL19Zvqv_xDqO9oCx-0Rs_Khk,191436
277
277
  mlrun/runtimes/__init__.py,sha256=egLM94cDMUyQ1GVABdFGXUQcDhU70lP3k7qSnM_UnHY,9008
278
278
  mlrun/runtimes/base.py,sha256=JXWmTIcm3b0klGUOHDlyFNa3bUgsNzQIgWhUQpSZoE0,37692
@@ -323,7 +323,7 @@ mlrun/utils/azure_vault.py,sha256=IEFizrDGDbAaoWwDr1WoA88S_EZ0T--vjYtY-i0cvYQ,34
323
323
  mlrun/utils/clones.py,sha256=mJpx4nyFiY6jlBCvFABsNuyi_mr1mvfPWn81vlafpOU,7361
324
324
  mlrun/utils/condition_evaluator.py,sha256=-nGfRmZzivn01rHTroiGY4rqEv8T1irMyhzxEei-sKc,1897
325
325
  mlrun/utils/db.py,sha256=blQgkWMfFH9lcN4sgJQcPQgEETz2Dl_zwbVA0SslpFg,2186
326
- mlrun/utils/helpers.py,sha256=F2hrR3748PTbFCzvckakACSjzL2ZypqEekTMldizxr0,61146
326
+ mlrun/utils/helpers.py,sha256=bYgoOLM_Yire6idrqcz_XIa-bFKE72OomxrHWSgmGWQ,61425
327
327
  mlrun/utils/http.py,sha256=t6FrXQstZm9xVVjxqIGiLzrwZNCR4CSienSOuVgNIcI,8706
328
328
  mlrun/utils/logger.py,sha256=cag2J30-jynIHmHZ2J8RYmVMNhYBGgAoimc5sbk-A1U,10016
329
329
  mlrun/utils/regex.py,sha256=b0AUa2THS-ELzJj0grl5b8Stq609F2XomTZkD9SB1fQ,4900
@@ -341,11 +341,11 @@ mlrun/utils/notifications/notification/ipython.py,sha256=ZtVL30B_Ha0VGoo4LxO-voT
341
341
  mlrun/utils/notifications/notification/slack.py,sha256=wqpFGr5BTvFO5KuUSzFfxsgmyU1Ohq7fbrGeNe9TXOk,7006
342
342
  mlrun/utils/notifications/notification/webhook.py,sha256=cb9w1Mc8ENfJBdgan7iiVHK9eVls4-R3tUxmXM-P-8I,4746
343
343
  mlrun/utils/version/__init__.py,sha256=7kkrB7hEZ3cLXoWj1kPoDwo4MaswsI2JVOBpbKgPAgc,614
344
- mlrun/utils/version/version.json,sha256=K3OspIDXXuhY1cyInBRY_fVXaoyI-2e-8DfXWB2SK3o,88
344
+ mlrun/utils/version/version.json,sha256=PPtD50BBpAFXe21-3lwZoqkkZDJqsn7S-PVo6nvLwSE,88
345
345
  mlrun/utils/version/version.py,sha256=eEW0tqIAkU9Xifxv8Z9_qsYnNhn3YH7NRAfM-pPLt1g,1878
346
- mlrun-1.7.1rc4.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
347
- mlrun-1.7.1rc4.dist-info/METADATA,sha256=MlsjuiTJPlbez2yLh9-QMd9bw_J70L1vgvT68yBj0AM,24486
348
- mlrun-1.7.1rc4.dist-info/WHEEL,sha256=a7TGlA-5DaHMRrarXjVbQagU3Man_dCnGIWMJr5kRWo,91
349
- mlrun-1.7.1rc4.dist-info/entry_points.txt,sha256=1Owd16eAclD5pfRCoJpYC2ZJSyGNTtUr0nCELMioMmU,46
350
- mlrun-1.7.1rc4.dist-info/top_level.txt,sha256=NObLzw3maSF9wVrgSeYBv-fgnHkAJ1kEkh12DLdd5KM,6
351
- mlrun-1.7.1rc4.dist-info/RECORD,,
346
+ mlrun-1.7.1rc6.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
347
+ mlrun-1.7.1rc6.dist-info/METADATA,sha256=hYM5fl5KYM3U8M8b3C5igPRvRM80tR3ahywAaMyzVbA,24486
348
+ mlrun-1.7.1rc6.dist-info/WHEEL,sha256=R06PA3UVYHThwHvxuRWMqaGcr-PuniXahwjmQRFMEkY,91
349
+ mlrun-1.7.1rc6.dist-info/entry_points.txt,sha256=1Owd16eAclD5pfRCoJpYC2ZJSyGNTtUr0nCELMioMmU,46
350
+ mlrun-1.7.1rc6.dist-info/top_level.txt,sha256=NObLzw3maSF9wVrgSeYBv-fgnHkAJ1kEkh12DLdd5KM,6
351
+ mlrun-1.7.1rc6.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.4.0)
2
+ Generator: setuptools (75.5.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5