doover-cli 0.4.2__tar.gz → 0.5.0__tar.gz

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.
Files changed (88) hide show
  1. {doover_cli-0.4.2 → doover_cli-0.5.0}/PKG-INFO +1 -1
  2. {doover_cli-0.4.2 → doover_cli-0.5.0}/pyproject.toml +1 -1
  3. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/apps/apps.py +34 -28
  4. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/config_schema.py +20 -5
  5. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/ui_schema.py +20 -5
  6. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/utils/apps.py +27 -3
  7. {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/test_utils_apps.py +8 -1
  8. {doover_cli-0.4.2 → doover_cli-0.5.0}/uv.lock +4 -4
  9. {doover_cli-0.4.2 → doover_cli-0.5.0}/.github/CONTRIBUTING.md +0 -0
  10. {doover_cli-0.4.2 → doover_cli-0.5.0}/.github/ISSUE_TEMPLATE/bug-report.yaml +0 -0
  11. {doover_cli-0.4.2 → doover_cli-0.5.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  12. {doover_cli-0.4.2 → doover_cli-0.5.0}/.github/ISSUE_TEMPLATE/feature-request.yml +0 -0
  13. {doover_cli-0.4.2 → doover_cli-0.5.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  14. {doover_cli-0.4.2 → doover_cli-0.5.0}/.github/workflows/lint-test.yml +0 -0
  15. {doover_cli-0.4.2 → doover_cli-0.5.0}/.github/workflows/release-brew.yml +0 -0
  16. {doover_cli-0.4.2 → doover_cli-0.5.0}/.github/workflows/release-pypi.yml +0 -0
  17. {doover_cli-0.4.2 → doover_cli-0.5.0}/.github/workflows/release.yml +0 -0
  18. {doover_cli-0.4.2 → doover_cli-0.5.0}/.gitignore +0 -0
  19. {doover_cli-0.4.2 → doover_cli-0.5.0}/.pre-commit-config.yaml +0 -0
  20. {doover_cli-0.4.2 → doover_cli-0.5.0}/AGENT.md +0 -0
  21. {doover_cli-0.4.2 → doover_cli-0.5.0}/LICENSE +0 -0
  22. {doover_cli-0.4.2 → doover_cli-0.5.0}/README.md +0 -0
  23. {doover_cli-0.4.2 → doover_cli-0.5.0}/debian/changelog +0 -0
  24. {doover_cli-0.4.2 → doover_cli-0.5.0}/debian/control +0 -0
  25. {doover_cli-0.4.2 → doover_cli-0.5.0}/debian/rules +0 -0
  26. {doover_cli-0.4.2 → doover_cli-0.5.0}/docs/user_stories.md +0 -0
  27. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/__init__.py +0 -0
  28. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/agent.py +0 -0
  29. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/api/__init__.py +0 -0
  30. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/api/auth.py +0 -0
  31. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/api/errors.py +0 -0
  32. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/api/session.py +0 -0
  33. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/apps/app_install.py +0 -0
  34. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/apps/device.py +0 -0
  35. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/apps/device_type.py +0 -0
  36. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/apps/tunnel.py +0 -0
  37. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/channel.py +0 -0
  38. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/colours.py +0 -0
  39. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/dda_logs.py +0 -0
  40. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/doover_config.py +0 -0
  41. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/grpc.py +0 -0
  42. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/login.py +0 -0
  43. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/renderer/__init__.py +0 -0
  44. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/renderer/_base.py +0 -0
  45. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/renderer/_basic.py +0 -0
  46. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/renderer/_default.py +0 -0
  47. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/renderer/_json.py +0 -0
  48. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/report.py +0 -0
  49. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/simulator.py +0 -0
  50. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/user.py +0 -0
  51. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/utils/__init__.py +0 -0
  52. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/utils/api.py +0 -0
  53. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/utils/context.py +0 -0
  54. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/utils/crud/__init__.py +0 -0
  55. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/utils/crud/commands.py +0 -0
  56. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/utils/crud/lookup.py +0 -0
  57. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/utils/crud/prompting.py +0 -0
  58. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/utils/crud/schema.py +0 -0
  59. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/utils/crud/values.py +0 -0
  60. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/utils/errors.py +0 -0
  61. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/utils/formatters.py +0 -0
  62. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/utils/misc.py +0 -0
  63. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/utils/parsers.py +0 -0
  64. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/utils/prompt.py +0 -0
  65. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/utils/sentry.py +0 -0
  66. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/utils/shell_commands.py +0 -0
  67. {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/utils/state.py +0 -0
  68. {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/__init__.py +0 -0
  69. {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/conftest.py +0 -0
  70. {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/test_agent.py +0 -0
  71. {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/test_app_install.py +0 -0
  72. {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/test_apps.py +0 -0
  73. {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/test_auth_integration.py +0 -0
  74. {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/test_auth_unit.py +0 -0
  75. {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/test_basic.py +0 -0
  76. {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/test_crud_commands.py +0 -0
  77. {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/test_crud_lookup.py +0 -0
  78. {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/test_crud_prompting.py +0 -0
  79. {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/test_crud_schema.py +0 -0
  80. {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/test_crud_values.py +0 -0
  81. {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/test_default_renderer.py +0 -0
  82. {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/test_device.py +0 -0
  83. {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/test_device_cli_integration.py +0 -0
  84. {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/test_device_type.py +0 -0
  85. {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/test_device_type_cli_integration.py +0 -0
  86. {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/test_prompt.py +0 -0
  87. {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/test_sentry.py +0 -0
  88. {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/test_user.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: doover-cli
3
- Version: 0.4.2
3
+ Version: 0.5.0
4
4
  Summary: CLI for Doover
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.11
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "doover-cli"
3
- version = "0.4.2"
3
+ version = "0.5.0"
4
4
  description = "CLI for Doover"
5
5
  requires-python = ">=3.11"
6
6
  dependencies = [
@@ -31,6 +31,7 @@ from ..utils.apps import (
31
31
  call_with_uv,
32
32
  get_docker_path,
33
33
  get_app_config,
34
+ preserve_file,
34
35
  )
35
36
  from ..utils.crud import (
36
37
  build_update_command,
@@ -961,35 +962,40 @@ def publish(
961
962
  # Resolve the app name early so subsequent get_app_config calls don't re-prompt.
962
963
  app_config = get_app_config(root_fp, app_name=app_name)
963
964
 
964
- if export_config:
965
- try:
966
- ctx.invoke(export_config_command, ctx, app_fp=root_fp, validate_=True)
967
- except jsonschema.exceptions.SchemaError as exc:
968
- summary, remainder = str(exc).split("\n", 1)
969
- rich.print(
970
- f"[red]Failed to export application configuration: {summary}[/red]\n{remainder}\n"
971
- )
972
- typer.confirm("Do you want to continue?", abort=True)
973
- else:
974
- rich.print("[green]Exported application configuration.[/green]")
975
- app_config = get_app_config(root_fp)
976
-
977
- if export_ui and _should_export_ui(app_config):
978
- try:
979
- ctx.invoke(export_ui_command, ctx, app_fp=root_fp, validate_=True)
980
- except Exception as exc:
981
- rich.print(f"[red]Failed to export UI schema: {exc}[/red]\n")
982
- typer.confirm("Do you want to continue?", abort=True)
983
- else:
984
- rich.print("[green]Exported UI schema.[/green]")
965
+ # Generate the schema from the Python into doover_config.json so the upload
966
+ # payload carries the current source, then restore the file -- publishing
967
+ # never leaves generated config_schema/ui_schema in the working tree. The
968
+ # Python is the source of truth; the JSON is regenerated on demand.
969
+ with preserve_file(root_fp / "doover_config.json"):
970
+ if export_config:
971
+ try:
972
+ ctx.invoke(export_config_command, ctx, app_fp=root_fp, validate_=True)
973
+ except jsonschema.exceptions.SchemaError as exc:
974
+ summary, remainder = str(exc).split("\n", 1)
975
+ rich.print(
976
+ f"[red]Failed to export application configuration: {summary}[/red]\n{remainder}\n"
977
+ )
978
+ typer.confirm("Do you want to continue?", abort=True)
979
+ else:
980
+ rich.print("[green]Exported application configuration.[/green]")
981
+ app_config = get_app_config(root_fp)
985
982
 
986
- app_config = get_app_config(root_fp)
987
- resolved_staging = _resolve_staging(staging)
988
- payload = _build_application_payload(
989
- app_config,
990
- staging=resolved_staging,
991
- include_deployment_data=True,
992
- )
983
+ if export_ui and _should_export_ui(app_config):
984
+ try:
985
+ ctx.invoke(export_ui_command, ctx, app_fp=root_fp, validate_=True)
986
+ except Exception as exc:
987
+ rich.print(f"[red]Failed to export UI schema: {exc}[/red]\n")
988
+ typer.confirm("Do you want to continue?", abort=True)
989
+ else:
990
+ rich.print("[green]Exported UI schema.[/green]")
991
+
992
+ app_config = get_app_config(root_fp)
993
+ resolved_staging = _resolve_staging(staging)
994
+ payload = _build_application_payload(
995
+ app_config,
996
+ staging=resolved_staging,
997
+ include_deployment_data=True,
998
+ )
993
999
 
994
1000
  client, renderer = get_state()
995
1001
 
@@ -7,7 +7,12 @@ import rich
7
7
  import typer
8
8
  import jsf
9
9
 
10
- from .utils.apps import get_app_directory, call_with_uv, get_app_config
10
+ from .utils.apps import (
11
+ get_app_directory,
12
+ call_with_uv,
13
+ get_app_config,
14
+ preserve_file,
15
+ )
11
16
 
12
17
  app = typer.Typer(no_args_is_help=True)
13
18
 
@@ -63,18 +68,28 @@ def validate(
63
68
  export_: Annotated[
64
69
  bool,
65
70
  typer.Option(
66
- "--export",
67
- help="Export the configuration before validating.",
71
+ "--export/--no-export",
72
+ help="Regenerate the schema from the Python before validating. "
73
+ "The export is transient -- doover_config.json is restored "
74
+ "afterwards, so validation never writes to the working tree.",
68
75
  ),
69
76
  ] = True,
70
77
  ):
71
78
  """Validate application config is a valid JSON schema."""
72
79
  root_fp = get_app_directory(app_fp)
80
+ config_file = root_fp / "doover_config.json"
73
81
 
74
82
  if export_ is True:
75
- ctx.invoke(export, ctx, app_fp=root_fp, validate_=False)
83
+ # Validate the schema the Python *currently* generates, without leaving
84
+ # doover_config.json modified (e.g. when run as a pre-commit hook).
85
+ with preserve_file(config_file):
86
+ ctx.invoke(export, ctx, app_fp=root_fp, validate_=False)
87
+ _validate_config_file(config_file)
88
+ else:
89
+ _validate_config_file(config_file)
76
90
 
77
- config_file = root_fp / "doover_config.json"
91
+
92
+ def _validate_config_file(config_file: Path):
78
93
  if not config_file.exists():
79
94
  raise FileNotFoundError(
80
95
  "doover_config.json not found. Please ensure there is a doover_config.json file in the application directory."
@@ -6,7 +6,12 @@ from typing import Annotated
6
6
  import rich
7
7
  import typer
8
8
 
9
- from .utils.apps import get_app_directory, call_with_uv, get_app_config
9
+ from .utils.apps import (
10
+ get_app_directory,
11
+ call_with_uv,
12
+ get_app_config,
13
+ preserve_file,
14
+ )
10
15
 
11
16
  app = typer.Typer(no_args_is_help=True)
12
17
 
@@ -55,18 +60,28 @@ def validate(
55
60
  export_: Annotated[
56
61
  bool,
57
62
  typer.Option(
58
- "--export",
59
- help="Export the UI schema before validating.",
63
+ "--export/--no-export",
64
+ help="Regenerate the schema from the Python before validating. "
65
+ "The export is transient -- doover_config.json is restored "
66
+ "afterwards, so validation never writes to the working tree.",
60
67
  ),
61
68
  ] = True,
62
69
  ):
63
70
  """Validate application UI schema is valid JSON."""
64
71
  root_fp = get_app_directory(app_fp)
72
+ config_file = root_fp / "doover_config.json"
65
73
 
66
74
  if export_ is True:
67
- ctx.invoke(export, ctx, app_fp=root_fp, validate_=False)
75
+ # Validate the schema the Python *currently* generates, without leaving
76
+ # doover_config.json modified (e.g. when run as a pre-commit hook).
77
+ with preserve_file(config_file):
78
+ ctx.invoke(export, ctx, app_fp=root_fp, validate_=False)
79
+ _validate_ui_file(config_file)
80
+ else:
81
+ _validate_ui_file(config_file)
68
82
 
69
- config_file = root_fp / "doover_config.json"
83
+
84
+ def _validate_ui_file(config_file: Path):
70
85
  if not config_file.exists():
71
86
  raise FileNotFoundError(
72
87
  "doover_config.json not found. Please ensure there is a doover_config.json file in the application directory."
@@ -2,6 +2,7 @@ import os
2
2
  import json
3
3
  import base64
4
4
  import shutil
5
+ import contextlib
5
6
  from pathlib import Path
6
7
  from typing import Any
7
8
 
@@ -19,6 +20,27 @@ def get_id_or_key(data: dict[str, Any], key: str) -> Any:
19
20
  return data.get(key)
20
21
 
21
22
 
23
+ @contextlib.contextmanager
24
+ def preserve_file(path: Path):
25
+ """Snapshot a file (or its absence) and restore it on exit.
26
+
27
+ Lets a command generate schema into ``doover_config.json`` transiently --
28
+ to validate it or build an upload payload from it -- without leaving the
29
+ working tree modified. This keeps generated ``config_schema``/``ui_schema``
30
+ out of git: the Python is the source of truth, the JSON is regenerated on
31
+ demand. Restores on exceptions too, so a failed run never dirties the tree.
32
+ """
33
+ existed = path.exists()
34
+ original = path.read_text() if existed else None
35
+ try:
36
+ yield
37
+ finally:
38
+ if existed:
39
+ path.write_text(original)
40
+ elif path.exists():
41
+ path.unlink()
42
+
43
+
22
44
  class LocalApplication(ControlApplication):
23
45
  _model_name = None
24
46
 
@@ -366,14 +388,16 @@ def get_app_config(root_fp: Path, app_name: str | None = None) -> Any:
366
388
 
367
389
  result = []
368
390
  for k, v in data.items():
369
- if isinstance(v, dict) and "config_schema" in v:
370
- # config_schema bit of a prerequisite for an app config entry.
391
+ if isinstance(v, dict) and "type" in v:
392
+ # `type` (e.g. DEV/PRO) marks an app entry. Don't key off
393
+ # config_schema -- it's generated from the Python and may be
394
+ # absent from the committed file.
371
395
  result.append(LocalApplication.from_config(v, root_fp))
372
396
 
373
397
  if len(result) == 0:
374
398
  print(
375
399
  f"No application configuration found in the `doover_config.json` file at {root_fp}. "
376
- f"Make sure the `type` is set to `application` in the configuration."
400
+ f"Make sure each application entry has a `type` set in the configuration."
377
401
  )
378
402
  raise typer.Exit(1)
379
403
 
@@ -63,10 +63,12 @@ def test_get_app_config_caches_explicit_app_name(monkeypatch, tmp_path):
63
63
  {
64
64
  "vega_level_sensor": {
65
65
  "name": "vega_level_sensor",
66
+ "type": "DEV",
66
67
  "config_schema": {"type": "object"},
67
68
  },
68
69
  "farm_water_dashboard": {
69
70
  "name": "farm_water_dashboard",
71
+ "type": "DEV",
70
72
  "config_schema": {"type": "object"},
71
73
  },
72
74
  }
@@ -85,17 +87,21 @@ def test_get_app_config_caches_explicit_app_name(monkeypatch, tmp_path):
85
87
  assert cached_config.name == "farm_water_dashboard"
86
88
 
87
89
 
88
- def test_get_app_config_rejects_unknown_explicit_app_name(monkeypatch, tmp_path, capsys):
90
+ def test_get_app_config_rejects_unknown_explicit_app_name(
91
+ monkeypatch, tmp_path, capsys
92
+ ):
89
93
  config_path = tmp_path / "doover_config.json"
90
94
  config_path.write_text(
91
95
  json.dumps(
92
96
  {
93
97
  "vega_level_sensor": {
94
98
  "name": "vega_level_sensor",
99
+ "type": "DEV",
95
100
  "config_schema": {"type": "object"},
96
101
  },
97
102
  "farm_water_dashboard": {
98
103
  "name": "farm_water_dashboard",
104
+ "type": "DEV",
99
105
  "config_schema": {"type": "object"},
100
106
  },
101
107
  }
@@ -124,6 +130,7 @@ def test_get_app_config_rejects_unknown_explicit_app_name_with_single_app(
124
130
  {
125
131
  "tracker-app": {
126
132
  "name": "tracker-app",
133
+ "type": "DEV",
127
134
  "config_schema": {"type": "object"},
128
135
  },
129
136
  }
@@ -544,7 +544,7 @@ wheels = [
544
544
 
545
545
  [[package]]
546
546
  name = "doover-cli"
547
- version = "0.4.2"
547
+ version = "0.5.0"
548
548
  source = { editable = "." }
549
549
  dependencies = [
550
550
  { name = "click" },
@@ -1325,15 +1325,15 @@ wheels = [
1325
1325
 
1326
1326
  [[package]]
1327
1327
  name = "pydoover"
1328
- version = "1.7.0"
1328
+ version = "1.7.1"
1329
1329
  source = { registry = "https://pypi.org/simple" }
1330
1330
  dependencies = [
1331
1331
  { name = "aiohttp" },
1332
1332
  { name = "httpx" },
1333
1333
  ]
1334
- sdist = { url = "https://files.pythonhosted.org/packages/a6/8e/b44585e9d89573ed2848acbc32a49735bc1117842f2276b269ed7c65c227/pydoover-1.7.0.tar.gz", hash = "sha256:fe23f440dd3d2054e40c7cdf8d027664726e9f91ef42993a6139b9ba757b4eb3", size = 537261, upload-time = "2026-06-27T08:39:42.61Z" }
1334
+ sdist = { url = "https://files.pythonhosted.org/packages/bf/b6/446d8f51b55d402a9e92519a4f0ecad0571c3bc0c0ce5d8da98c456dd8a6/pydoover-1.7.1.tar.gz", hash = "sha256:2d98f99650078d105c49cd9ed8e6ba99ea1d6e04de39524beebf27871b102cb5", size = 537405, upload-time = "2026-06-27T10:44:37.13Z" }
1335
1335
  wheels = [
1336
- { url = "https://files.pythonhosted.org/packages/80/17/c8650ef2c47e6874cd88b915cba27fcc5a8533c05ffeb015da4826c9cd22/pydoover-1.7.0-py3-none-any.whl", hash = "sha256:4bd4e46a8b2171fddc561aa1d0e9ae6a34e7380fa85e69787eb0d44411c174f1", size = 284886, upload-time = "2026-06-27T08:39:41.383Z" },
1336
+ { url = "https://files.pythonhosted.org/packages/76/7f/c688ea03adf654238a86035bbd94f225de4d923c6ace7fcd1ed7a47991ee/pydoover-1.7.1-py3-none-any.whl", hash = "sha256:bda708ab027987769c04385e1127e5d7706110308b650362e85cbb1f9478d872", size = 284995, upload-time = "2026-06-27T10:44:35.919Z" },
1337
1337
  ]
1338
1338
 
1339
1339
  [[package]]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes