doover-cli 0.6.7__tar.gz → 0.6.8__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.6.7 → doover_cli-0.6.8}/PKG-INFO +1 -1
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/__init__.py +1 -1
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/config_schema.py +5 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/notification_schema.py +2 -2
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/utils/apps.py +65 -10
- {doover_cli-0.6.7 → doover_cli-0.6.8}/tests/test_schema_export.py +117 -2
- {doover_cli-0.6.7 → doover_cli-0.6.8}/.github/CONTRIBUTING.md +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/.github/ISSUE_TEMPLATE/bug-report.yaml +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/.github/ISSUE_TEMPLATE/feature-request.yml +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/.github/workflows/lint-test.yml +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/.github/workflows/release-brew.yml +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/.github/workflows/release-pypi.yml +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/.github/workflows/release.yml +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/.gitignore +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/.pre-commit-config.yaml +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/AGENT.md +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/LICENSE +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/README.md +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/debian/changelog +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/debian/control +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/debian/rules +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/docs/user_stories.md +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/pyproject.toml +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/agent.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/api/__init__.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/api/auth.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/api/errors.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/api/session.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/apps/app_install.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/apps/apps.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/apps/device.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/apps/device_type.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/apps/migrate.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/apps/tunnel.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/apps/widget_plugin.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/channel.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/colours.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/dda_logs.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/doover_config.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/grpc.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/login.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/registry.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/renderer/__init__.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/renderer/_base.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/renderer/_basic.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/renderer/_default.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/renderer/_json.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/report.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/simulator.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/ui_schema.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/user.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/utils/__init__.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/utils/api.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/utils/context.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/utils/crud/__init__.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/utils/crud/commands.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/utils/crud/lookup.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/utils/crud/prompting.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/utils/crud/schema.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/utils/crud/values.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/utils/errors.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/utils/formatters.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/utils/misc.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/utils/parsers.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/utils/prompt.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/utils/sentry.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/utils/shell_commands.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/src/doover_cli/utils/state.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/tests/__init__.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/tests/conftest.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/tests/test_agent.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/tests/test_app_install.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/tests/test_apps.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/tests/test_auth_integration.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/tests/test_auth_unit.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/tests/test_basic.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/tests/test_crud_commands.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/tests/test_crud_lookup.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/tests/test_crud_prompting.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/tests/test_crud_schema.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/tests/test_crud_values.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/tests/test_default_renderer.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/tests/test_device.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/tests/test_device_cli_integration.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/tests/test_device_type.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/tests/test_device_type_cli_integration.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/tests/test_discover.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/tests/test_migrate.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/tests/test_prompt.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/tests/test_registry.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/tests/test_sentry.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/tests/test_user.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/tests/test_utils_apps.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/tests/test_widget_plugin.py +0 -0
- {doover_cli-0.6.7 → doover_cli-0.6.8}/uv.lock +0 -0
|
@@ -14,6 +14,7 @@ from .utils.apps import (
|
|
|
14
14
|
get_app_config,
|
|
15
15
|
preserve_file,
|
|
16
16
|
run_schema_export,
|
|
17
|
+
RUST_EXPORT_COMMAND,
|
|
17
18
|
)
|
|
18
19
|
|
|
19
20
|
app = typer.Typer(no_args_is_help=True)
|
|
@@ -65,6 +66,10 @@ def export(
|
|
|
65
66
|
"export-config",
|
|
66
67
|
app_name=app_config.name,
|
|
67
68
|
cwd=app_fp,
|
|
69
|
+
# The one exporter that drives a Rust binary: its `export` writes
|
|
70
|
+
# every schema the app has, so the UI and notification exporters
|
|
71
|
+
# have nothing left to run.
|
|
72
|
+
rust_default=RUST_EXPORT_COMMAND,
|
|
68
73
|
)
|
|
69
74
|
else:
|
|
70
75
|
config = get_app_config(app_fp, app_name=app_name)
|
|
@@ -46,16 +46,16 @@ def export(
|
|
|
46
46
|
print("App requested no notification export. Skipping...")
|
|
47
47
|
return
|
|
48
48
|
|
|
49
|
+
print("Exporting notification schema...")
|
|
49
50
|
run_schema_export(
|
|
50
51
|
root_fp,
|
|
51
52
|
app_config.export_notification_command,
|
|
52
53
|
"export-notifications",
|
|
54
|
+
optional=True,
|
|
53
55
|
app_name=app_config.name,
|
|
54
56
|
cwd=app_fp,
|
|
55
57
|
)
|
|
56
58
|
|
|
57
|
-
print("Exporting notification schema...")
|
|
58
|
-
|
|
59
59
|
if validate_ is True:
|
|
60
60
|
print("Validating notification schema...")
|
|
61
61
|
ctx.invoke(validate, ctx, app_fp=app_fp, export_=False)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import json
|
|
3
3
|
import sys
|
|
4
|
+
import tomllib
|
|
4
5
|
import base64
|
|
5
6
|
import shutil
|
|
6
7
|
import contextlib
|
|
@@ -624,40 +625,94 @@ def detect_language(app_dir: Path) -> str | None:
|
|
|
624
625
|
|
|
625
626
|
|
|
626
627
|
# What a Rust app runs to regenerate its schemas. The framework gives every
|
|
627
|
-
# application binary
|
|
628
|
-
# the config schema and, when the app has one, the UI schema --
|
|
629
|
-
#
|
|
630
|
-
#
|
|
631
|
-
#
|
|
628
|
+
# application binary a single `export [path] [--app-name NAME]` subcommand that
|
|
629
|
+
# writes the config schema and, when the app has one, the UI schema -- with the
|
|
630
|
+
# same read-merge-write over doover_config.json that pydoover's exporters do.
|
|
631
|
+
# Driven through cargo so it works from a plain checkout with nothing built yet,
|
|
632
|
+
# which is the state CI is always in.
|
|
633
|
+
#
|
|
634
|
+
# One command for every schema, which is why only the config exporter passes it
|
|
635
|
+
# as its `rust_default`: see run_schema_export.
|
|
632
636
|
RUST_EXPORT_COMMAND = "cargo run --quiet -- export doover_config.json"
|
|
633
637
|
|
|
634
638
|
|
|
639
|
+
def python_declares_script(app_dir: Path, script: str) -> bool | None:
|
|
640
|
+
"""Whether `app_dir`'s pyproject.toml declares the `script` console script.
|
|
641
|
+
|
|
642
|
+
None when there is nothing to go on -- no pyproject.toml, or one this can't
|
|
643
|
+
read -- so a caller can fall back to running the script and finding out.
|
|
644
|
+
"""
|
|
645
|
+
pyproject = app_dir / "pyproject.toml"
|
|
646
|
+
if not pyproject.exists():
|
|
647
|
+
return None
|
|
648
|
+
try:
|
|
649
|
+
data = tomllib.loads(pyproject.read_text())
|
|
650
|
+
except (tomllib.TOMLDecodeError, OSError):
|
|
651
|
+
return None
|
|
652
|
+
|
|
653
|
+
scripts = data.get("project", {}).get("scripts")
|
|
654
|
+
if not isinstance(scripts, dict):
|
|
655
|
+
# A pyproject with no scripts table declares no exporters at all, which
|
|
656
|
+
# is an answer rather than a gap.
|
|
657
|
+
return False
|
|
658
|
+
return script in scripts
|
|
659
|
+
|
|
660
|
+
|
|
635
661
|
def run_schema_export(
|
|
636
662
|
app_dir: Path,
|
|
637
663
|
command: str | None,
|
|
638
664
|
python_default: str,
|
|
639
665
|
app_name: str | None = None,
|
|
640
666
|
cwd: Path | None = None,
|
|
667
|
+
rust_default: str | None = None,
|
|
668
|
+
optional: bool = False,
|
|
641
669
|
) -> None:
|
|
642
670
|
"""Regenerate an app's schemas by running its own exporter.
|
|
643
671
|
|
|
644
|
-
Python apps run a console script under uv, as they always have
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
672
|
+
Python apps run a console script under uv, as they always have: one script
|
|
673
|
+
per schema, each with its own default.
|
|
674
|
+
|
|
675
|
+
Rust apps have no such split. The framework gives a binary one `export`
|
|
676
|
+
subcommand that writes every schema it has, so exactly one exporter drives
|
|
677
|
+
it -- the config one, which passes `rust_default`. The others do nothing for
|
|
678
|
+
a Rust app unless it names a command explicitly, because there is nothing
|
|
679
|
+
left for them to write and no second subcommand to call.
|
|
680
|
+
|
|
681
|
+
That asymmetry is not cosmetic. Inventing `cargo run -- export` as the
|
|
682
|
+
default for *any* schema kind means a repo whose binary is not a doover-rs
|
|
683
|
+
application runs it anyway: doover-device-agent's binary rejects `export`
|
|
684
|
+
and fails the publish, and doover-device-runtime's supervisor accepts any
|
|
685
|
+
argv, so it started supervising and hung a CI job for 75 minutes. Both had
|
|
686
|
+
already opted out of every exporter that existed when they were written; the
|
|
687
|
+
next one to be added found them again.
|
|
648
688
|
|
|
649
689
|
`command` is the app's own `export_*_command`, honoured verbatim when set --
|
|
650
690
|
an app whose binary is not what `cargo run` picks by default (a workspace
|
|
651
691
|
with several) says so there rather than being unbuildable by CI.
|
|
652
692
|
"""
|
|
653
693
|
if detect_language(app_dir) == "rs":
|
|
654
|
-
cmd = command or
|
|
694
|
+
cmd = command or rust_default
|
|
695
|
+
if cmd is None:
|
|
696
|
+
print(
|
|
697
|
+
"Rust app: its `export` subcommand writes every schema it has, "
|
|
698
|
+
"and the config export already ran it. Skipping..."
|
|
699
|
+
)
|
|
700
|
+
return
|
|
655
701
|
# The binary otherwise falls back to APP_KEY or its own file name, which
|
|
656
702
|
# is not the application name in a repo whose binary is spelled with
|
|
657
703
|
# hyphens and whose app is spelled with underscores.
|
|
658
704
|
if app_name and "--app-name" not in cmd:
|
|
659
705
|
cmd = f"{cmd} --app-name {app_name}"
|
|
660
706
|
run(cmd, cwd=app_dir)
|
|
707
|
+
elif (
|
|
708
|
+
optional
|
|
709
|
+
and command is None
|
|
710
|
+
and (python_declares_script(app_dir, python_default) is False)
|
|
711
|
+
):
|
|
712
|
+
print(
|
|
713
|
+
f"App declares no `{python_default}` script, so it has no schema of "
|
|
714
|
+
"this kind. Skipping..."
|
|
715
|
+
)
|
|
661
716
|
else:
|
|
662
717
|
call_with_uv(command or python_default, in_shell=True, cwd=cwd)
|
|
663
718
|
|
|
@@ -60,7 +60,13 @@ class TestRunSchemaExport:
|
|
|
60
60
|
def test_rust_app_runs_its_export_subcommand(self, tmp_path, calls):
|
|
61
61
|
(tmp_path / "Cargo.toml").touch()
|
|
62
62
|
|
|
63
|
-
apps_utils.run_schema_export(
|
|
63
|
+
apps_utils.run_schema_export(
|
|
64
|
+
tmp_path,
|
|
65
|
+
None,
|
|
66
|
+
"export-config",
|
|
67
|
+
app_name="foo",
|
|
68
|
+
rust_default=apps_utils.RUST_EXPORT_COMMAND,
|
|
69
|
+
)
|
|
64
70
|
|
|
65
71
|
assert calls["uv"] == []
|
|
66
72
|
cmd, cwd = calls["run"][0]
|
|
@@ -71,10 +77,44 @@ class TestRunSchemaExport:
|
|
|
71
77
|
def test_rust_app_without_an_app_name_leaves_the_flag_off(self, tmp_path, calls):
|
|
72
78
|
(tmp_path / "Cargo.toml").touch()
|
|
73
79
|
|
|
74
|
-
apps_utils.run_schema_export(
|
|
80
|
+
apps_utils.run_schema_export(
|
|
81
|
+
tmp_path, None, "export-config", rust_default=apps_utils.RUST_EXPORT_COMMAND
|
|
82
|
+
)
|
|
75
83
|
|
|
76
84
|
assert "--app-name" not in calls["run"][0][0]
|
|
77
85
|
|
|
86
|
+
def test_a_rust_app_runs_nothing_for_a_second_schema_kind(self, tmp_path, calls):
|
|
87
|
+
"""The UI and notification exporters, which pass no rust_default.
|
|
88
|
+
|
|
89
|
+
A doover-rs binary has one `export` that writes every schema it has, so
|
|
90
|
+
the config exporter already ran it. Inventing the same command again is
|
|
91
|
+
what hung doover-device-runtime for 75 minutes: its binary is a
|
|
92
|
+
supervisor that accepts any argv, so `export` started it supervising.
|
|
93
|
+
"""
|
|
94
|
+
(tmp_path / "Cargo.toml").touch()
|
|
95
|
+
|
|
96
|
+
apps_utils.run_schema_export(tmp_path, None, "export-ui", app_name="foo")
|
|
97
|
+
|
|
98
|
+
assert calls["run"] == []
|
|
99
|
+
assert calls["uv"] == []
|
|
100
|
+
|
|
101
|
+
def test_a_rust_app_still_honours_an_explicit_command(self, tmp_path, calls):
|
|
102
|
+
"""An app that really does have a second exporter says so."""
|
|
103
|
+
(tmp_path / "Cargo.toml").touch()
|
|
104
|
+
|
|
105
|
+
apps_utils.run_schema_export(
|
|
106
|
+
tmp_path, "cargo run --bin exporter -- ui", "export-ui", app_name="foo"
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
assert calls["run"][0][0].startswith("cargo run --bin exporter -- ui")
|
|
110
|
+
|
|
111
|
+
def test_a_python_app_is_unaffected_by_the_rust_default(self, tmp_path, calls):
|
|
112
|
+
(tmp_path / "pyproject.toml").touch()
|
|
113
|
+
|
|
114
|
+
apps_utils.run_schema_export(tmp_path, None, "export-ui", app_name="foo")
|
|
115
|
+
|
|
116
|
+
assert calls["uv"][0][0] == ("export-ui",)
|
|
117
|
+
|
|
78
118
|
def test_a_configured_rust_command_wins(self, tmp_path, calls):
|
|
79
119
|
"""A workspace with several binaries names the one to run itself."""
|
|
80
120
|
(tmp_path / "Cargo.toml").touch()
|
|
@@ -104,6 +144,81 @@ class TestRunSchemaExport:
|
|
|
104
144
|
assert calls["run"][0][0].endswith("explicit")
|
|
105
145
|
|
|
106
146
|
|
|
147
|
+
class TestOptionalSchema:
|
|
148
|
+
"""`optional=True` -- the notification schema, which most apps don't have.
|
|
149
|
+
|
|
150
|
+
An app declares its exporters in its own `[project.scripts]`, so the absence
|
|
151
|
+
of one there is the app saying it has no notifications. Running it anyway
|
|
152
|
+
meant `uv run export-notifications` failed on nearly every Python app: a red
|
|
153
|
+
error and a reported handled exception for the ordinary case.
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
def _pyproject(self, tmp_path, body):
|
|
157
|
+
(tmp_path / "pyproject.toml").write_text(body)
|
|
158
|
+
return tmp_path
|
|
159
|
+
|
|
160
|
+
def test_skipped_when_the_script_is_not_declared(self, tmp_path, calls):
|
|
161
|
+
self._pyproject(
|
|
162
|
+
tmp_path,
|
|
163
|
+
'[project.scripts]\nexport-config = "app.app_config:export"\n',
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
apps_utils.run_schema_export(
|
|
167
|
+
tmp_path, None, "export-notifications", app_name="foo", optional=True
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
assert calls["uv"] == []
|
|
171
|
+
assert calls["run"] == []
|
|
172
|
+
|
|
173
|
+
def test_run_when_the_script_is_declared(self, tmp_path, calls):
|
|
174
|
+
self._pyproject(
|
|
175
|
+
tmp_path,
|
|
176
|
+
"[project.scripts]\n"
|
|
177
|
+
'export-notifications = "app.app_notifications:export"\n',
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
apps_utils.run_schema_export(
|
|
181
|
+
tmp_path, None, "export-notifications", app_name="foo", optional=True
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
assert calls["uv"][0][0] == ("export-notifications",)
|
|
185
|
+
|
|
186
|
+
def test_an_explicit_command_is_never_probed(self, tmp_path, calls):
|
|
187
|
+
"""An app naming its own exporter has said it has one, whatever the
|
|
188
|
+
scripts table looks like -- a second app in the repo exports through a
|
|
189
|
+
differently-named script."""
|
|
190
|
+
self._pyproject(tmp_path, '[project]\nname = "app"\n')
|
|
191
|
+
|
|
192
|
+
apps_utils.run_schema_export(
|
|
193
|
+
tmp_path,
|
|
194
|
+
"export-notifications-processor",
|
|
195
|
+
"export-notifications",
|
|
196
|
+
app_name="foo",
|
|
197
|
+
optional=True,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
assert calls["uv"][0][0] == ("export-notifications-processor",)
|
|
201
|
+
|
|
202
|
+
def test_an_unreadable_pyproject_still_runs_the_export(self, tmp_path, calls):
|
|
203
|
+
"""No answer is not the same as `no`: run it and let it fail loudly."""
|
|
204
|
+
self._pyproject(tmp_path, "this is not valid toml [[[")
|
|
205
|
+
|
|
206
|
+
apps_utils.run_schema_export(
|
|
207
|
+
tmp_path, None, "export-notifications", app_name="foo", optional=True
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
assert calls["uv"][0][0] == ("export-notifications",)
|
|
211
|
+
|
|
212
|
+
def test_a_required_schema_is_run_regardless(self, tmp_path, calls):
|
|
213
|
+
"""Config and UI are not optional: every app of their type has one, so a
|
|
214
|
+
missing script there is a real failure worth surfacing."""
|
|
215
|
+
self._pyproject(tmp_path, '[project]\nname = "app"\n')
|
|
216
|
+
|
|
217
|
+
apps_utils.run_schema_export(tmp_path, None, "export-config", app_name="foo")
|
|
218
|
+
|
|
219
|
+
assert calls["uv"][0][0] == ("export-config",)
|
|
220
|
+
|
|
221
|
+
|
|
107
222
|
class TestNoExport:
|
|
108
223
|
"""`export_config_command: NO_EXPORT` -- an app that holds no config of its
|
|
109
224
|
own. Publishing one used to require giving it an exporter that wrote
|
|
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
|
|
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
|