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.
- {doover_cli-0.4.2 → doover_cli-0.5.0}/PKG-INFO +1 -1
- {doover_cli-0.4.2 → doover_cli-0.5.0}/pyproject.toml +1 -1
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/apps/apps.py +34 -28
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/config_schema.py +20 -5
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/ui_schema.py +20 -5
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/utils/apps.py +27 -3
- {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/test_utils_apps.py +8 -1
- {doover_cli-0.4.2 → doover_cli-0.5.0}/uv.lock +4 -4
- {doover_cli-0.4.2 → doover_cli-0.5.0}/.github/CONTRIBUTING.md +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/.github/ISSUE_TEMPLATE/bug-report.yaml +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/.github/ISSUE_TEMPLATE/feature-request.yml +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/.github/workflows/lint-test.yml +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/.github/workflows/release-brew.yml +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/.github/workflows/release-pypi.yml +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/.github/workflows/release.yml +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/.gitignore +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/.pre-commit-config.yaml +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/AGENT.md +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/LICENSE +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/README.md +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/debian/changelog +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/debian/control +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/debian/rules +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/docs/user_stories.md +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/__init__.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/agent.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/api/__init__.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/api/auth.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/api/errors.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/api/session.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/apps/app_install.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/apps/device.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/apps/device_type.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/apps/tunnel.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/channel.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/colours.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/dda_logs.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/doover_config.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/grpc.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/login.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/renderer/__init__.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/renderer/_base.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/renderer/_basic.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/renderer/_default.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/renderer/_json.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/report.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/simulator.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/user.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/utils/__init__.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/utils/api.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/utils/context.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/utils/crud/__init__.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/utils/crud/commands.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/utils/crud/lookup.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/utils/crud/prompting.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/utils/crud/schema.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/utils/crud/values.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/utils/errors.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/utils/formatters.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/utils/misc.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/utils/parsers.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/utils/prompt.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/utils/sentry.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/utils/shell_commands.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/src/doover_cli/utils/state.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/__init__.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/conftest.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/test_agent.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/test_app_install.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/test_apps.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/test_auth_integration.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/test_auth_unit.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/test_basic.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/test_crud_commands.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/test_crud_lookup.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/test_crud_prompting.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/test_crud_schema.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/test_crud_values.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/test_default_renderer.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/test_device.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/test_device_cli_integration.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/test_device_type.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/test_device_type_cli_integration.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/test_prompt.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/test_sentry.py +0 -0
- {doover_cli-0.4.2 → doover_cli-0.5.0}/tests/test_user.py +0 -0
|
@@ -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
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
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
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
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
|
|
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="
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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="
|
|
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
|
-
|
|
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
|
-
|
|
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 "
|
|
370
|
-
#
|
|
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
|
|
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(
|
|
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.
|
|
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.
|
|
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/
|
|
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/
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|