doover-cli 0.6.6__tar.gz → 0.6.7__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.6 → doover_cli-0.6.7}/PKG-INFO +2 -2
- {doover_cli-0.6.6 → doover_cli-0.6.7}/pyproject.toml +1 -1
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/__init__.py +7 -1
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/apps/apps.py +32 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/apps/migrate.py +1 -1
- doover_cli-0.6.7/src/doover_cli/notification_schema.py +153 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/utils/apps.py +8 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/uv.lock +4 -4
- {doover_cli-0.6.6 → doover_cli-0.6.7}/.github/CONTRIBUTING.md +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/.github/ISSUE_TEMPLATE/bug-report.yaml +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/.github/ISSUE_TEMPLATE/feature-request.yml +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/.github/workflows/lint-test.yml +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/.github/workflows/release-brew.yml +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/.github/workflows/release-pypi.yml +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/.github/workflows/release.yml +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/.gitignore +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/.pre-commit-config.yaml +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/AGENT.md +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/LICENSE +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/README.md +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/debian/changelog +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/debian/control +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/debian/rules +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/docs/user_stories.md +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/agent.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/api/__init__.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/api/auth.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/api/errors.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/api/session.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/apps/app_install.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/apps/device.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/apps/device_type.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/apps/tunnel.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/apps/widget_plugin.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/channel.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/colours.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/config_schema.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/dda_logs.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/doover_config.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/grpc.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/login.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/registry.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/renderer/__init__.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/renderer/_base.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/renderer/_basic.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/renderer/_default.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/renderer/_json.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/report.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/simulator.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/ui_schema.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/user.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/utils/__init__.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/utils/api.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/utils/context.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/utils/crud/__init__.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/utils/crud/commands.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/utils/crud/lookup.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/utils/crud/prompting.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/utils/crud/schema.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/utils/crud/values.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/utils/errors.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/utils/formatters.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/utils/misc.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/utils/parsers.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/utils/prompt.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/utils/sentry.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/utils/shell_commands.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/src/doover_cli/utils/state.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/tests/__init__.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/tests/conftest.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/tests/test_agent.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/tests/test_app_install.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/tests/test_apps.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/tests/test_auth_integration.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/tests/test_auth_unit.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/tests/test_basic.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/tests/test_crud_commands.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/tests/test_crud_lookup.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/tests/test_crud_prompting.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/tests/test_crud_schema.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/tests/test_crud_values.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/tests/test_default_renderer.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/tests/test_device.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/tests/test_device_cli_integration.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/tests/test_device_type.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/tests/test_device_type_cli_integration.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/tests/test_discover.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/tests/test_migrate.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/tests/test_prompt.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/tests/test_registry.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/tests/test_schema_export.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/tests/test_sentry.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/tests/test_user.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/tests/test_utils_apps.py +0 -0
- {doover_cli-0.6.6 → doover_cli-0.6.7}/tests/test_widget_plugin.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: doover-cli
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.7
|
|
4
4
|
Summary: CLI for Doover
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Requires-Python: >=3.11
|
|
@@ -10,7 +10,7 @@ Requires-Dist: docker>=7.1.0
|
|
|
10
10
|
Requires-Dist: jsf>=0.11.2
|
|
11
11
|
Requires-Dist: paramiko>=3.5.1
|
|
12
12
|
Requires-Dist: prompt-toolkit>=3.0.52
|
|
13
|
-
Requires-Dist: pydoover>=1.
|
|
13
|
+
Requires-Dist: pydoover>=1.17.1
|
|
14
14
|
Requires-Dist: pytz>=2025.2
|
|
15
15
|
Requires-Dist: questionary>=1.10.0
|
|
16
16
|
Requires-Dist: requests>=2.32.3
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from doover_cli.renderer import Renderer
|
|
2
2
|
from typing import Annotated, Optional
|
|
3
3
|
|
|
4
|
-
__version__ = "0.6.
|
|
4
|
+
__version__ = "0.6.7"
|
|
5
5
|
|
|
6
6
|
import click
|
|
7
7
|
import typer
|
|
@@ -11,6 +11,7 @@ from pydoover.api.auth import ConfigManager
|
|
|
11
11
|
from .apps.apps import app as apps_app
|
|
12
12
|
from .apps.app_install import app as app_install_app
|
|
13
13
|
from .config_schema import app as config_schema_app
|
|
14
|
+
from .notification_schema import app as notification_schema_app
|
|
14
15
|
from .ui_schema import app as ui_schema_app
|
|
15
16
|
from .simulator import app as simulators_app
|
|
16
17
|
from .agent import app as agents_app
|
|
@@ -40,6 +41,11 @@ app.add_typer(
|
|
|
40
41
|
config_schema_app, name="config-schema", help="Manage application config schemas."
|
|
41
42
|
)
|
|
42
43
|
app.add_typer(ui_schema_app, name="ui-schema", help="Manage application UI schemas.")
|
|
44
|
+
app.add_typer(
|
|
45
|
+
notification_schema_app,
|
|
46
|
+
name="notification-schema",
|
|
47
|
+
help="Manage application notification schemas.",
|
|
48
|
+
)
|
|
43
49
|
app.add_typer(
|
|
44
50
|
simulators_app, name="simulator", help="Manage simulators and their configurations."
|
|
45
51
|
)
|
|
@@ -26,6 +26,7 @@ import questionary
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
from ..config_schema import export as export_config_command
|
|
29
|
+
from ..notification_schema import export as export_notification_command
|
|
29
30
|
from ..ui_schema import export as export_ui_command
|
|
30
31
|
from ..utils.api import ProfileAnnotation
|
|
31
32
|
from ..registry import is_doover_registry, login_for_push, publish_github_output
|
|
@@ -397,6 +398,16 @@ def _build_application_payload(
|
|
|
397
398
|
return payload
|
|
398
399
|
|
|
399
400
|
|
|
401
|
+
def _should_export_notifications(app_config) -> bool:
|
|
402
|
+
"""Whether to try the notification exporter for this app.
|
|
403
|
+
|
|
404
|
+
Opt-out rather than opt-in so an app that adds notifications starts
|
|
405
|
+
publishing them without also having to remember a flag, but explicitly
|
|
406
|
+
skippable with ``"export_notification_command": "NO_EXPORT"``.
|
|
407
|
+
"""
|
|
408
|
+
return getattr(app_config, "export_notification_command", None) != "NO_EXPORT"
|
|
409
|
+
|
|
410
|
+
|
|
400
411
|
def _should_export_ui(app_config) -> bool:
|
|
401
412
|
return app_config.type not in ("REP", "INT") and getattr(
|
|
402
413
|
app_config, "generate_ui", True
|
|
@@ -1154,6 +1165,12 @@ def publish(
|
|
|
1154
1165
|
help="Export the application UI schema before publishing.",
|
|
1155
1166
|
),
|
|
1156
1167
|
] = True,
|
|
1168
|
+
export_notifications: Annotated[
|
|
1169
|
+
bool,
|
|
1170
|
+
typer.Option(
|
|
1171
|
+
help="Export the application notification schema before publishing.",
|
|
1172
|
+
),
|
|
1173
|
+
] = True,
|
|
1157
1174
|
build_widget: Annotated[
|
|
1158
1175
|
bool,
|
|
1159
1176
|
typer.Option(
|
|
@@ -1255,6 +1272,21 @@ def publish(
|
|
|
1255
1272
|
else:
|
|
1256
1273
|
rich.print("[green]Exported UI schema.[/green]")
|
|
1257
1274
|
|
|
1275
|
+
# Most apps declare no notifications, and the exporter is only present
|
|
1276
|
+
# on those that do, so a missing one is not worth a prompt -- unlike the
|
|
1277
|
+
# config and UI exports, which every app of their type has.
|
|
1278
|
+
if export_notifications and _should_export_notifications(app_config):
|
|
1279
|
+
try:
|
|
1280
|
+
ctx.invoke(
|
|
1281
|
+
export_notification_command, ctx, app_fp=root_fp, validate_=True
|
|
1282
|
+
)
|
|
1283
|
+
except Exception as exc:
|
|
1284
|
+
rich.print(
|
|
1285
|
+
f"[yellow]Skipping notification schema export: {exc}[/yellow]"
|
|
1286
|
+
)
|
|
1287
|
+
else:
|
|
1288
|
+
rich.print("[green]Exported notification schema.[/green]")
|
|
1289
|
+
|
|
1258
1290
|
app_config = get_app_config(root_fp)
|
|
1259
1291
|
resolved_staging = _resolve_staging(staging)
|
|
1260
1292
|
payload = _build_application_payload(
|
|
@@ -58,7 +58,7 @@ REGISTRY_PROFILE_IDS = {
|
|
|
58
58
|
|
|
59
59
|
# Generated from the app's Python/Rust at publish time. A committed copy is a
|
|
60
60
|
# stale duplicate that nothing reads.
|
|
61
|
-
GENERATED_FIELDS = ("config_schema", "ui_schema")
|
|
61
|
+
GENERATED_FIELDS = ("config_schema", "ui_schema", "notification_schema")
|
|
62
62
|
|
|
63
63
|
# Container registry credentials are minted by the control plane against the
|
|
64
64
|
# app's publish permission now, so a registry profile no longer selects
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Annotated
|
|
5
|
+
|
|
6
|
+
import rich
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
from .utils.apps import (
|
|
10
|
+
app_names_in_config,
|
|
11
|
+
get_app_directory,
|
|
12
|
+
get_app_config,
|
|
13
|
+
preserve_file,
|
|
14
|
+
run_schema_export,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
app = typer.Typer(no_args_is_help=True)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@app.command()
|
|
21
|
+
def export(
|
|
22
|
+
ctx: typer.Context,
|
|
23
|
+
app_fp: Annotated[
|
|
24
|
+
Path, typer.Argument(help="Path to the application directory.")
|
|
25
|
+
] = Path(),
|
|
26
|
+
validate_: Annotated[
|
|
27
|
+
bool,
|
|
28
|
+
typer.Option(
|
|
29
|
+
"--validate",
|
|
30
|
+
help="Validate the notification schema before exporting.",
|
|
31
|
+
),
|
|
32
|
+
] = True,
|
|
33
|
+
app_name: Annotated[
|
|
34
|
+
str | None,
|
|
35
|
+
typer.Option(
|
|
36
|
+
help="Application name in doover_config.json. Required to pick one "
|
|
37
|
+
"non-interactively when the repo defines multiple apps.",
|
|
38
|
+
),
|
|
39
|
+
] = None,
|
|
40
|
+
):
|
|
41
|
+
"""Export the application notification schema to the doover config json file."""
|
|
42
|
+
root_fp = get_app_directory(app_fp)
|
|
43
|
+
app_config = get_app_config(root_fp, app_name=app_name)
|
|
44
|
+
export_command = app_config.export_notification_command or "export-notifications"
|
|
45
|
+
if export_command == "NO_EXPORT":
|
|
46
|
+
print("App requested no notification export. Skipping...")
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
run_schema_export(
|
|
50
|
+
root_fp,
|
|
51
|
+
app_config.export_notification_command,
|
|
52
|
+
"export-notifications",
|
|
53
|
+
app_name=app_config.name,
|
|
54
|
+
cwd=app_fp,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
print("Exporting notification schema...")
|
|
58
|
+
|
|
59
|
+
if validate_ is True:
|
|
60
|
+
print("Validating notification schema...")
|
|
61
|
+
ctx.invoke(validate, ctx, app_fp=app_fp, export_=False)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@app.command()
|
|
65
|
+
def validate(
|
|
66
|
+
ctx: typer.Context,
|
|
67
|
+
app_fp: Annotated[
|
|
68
|
+
Path, typer.Argument(help="Path to the application directory.")
|
|
69
|
+
] = Path(),
|
|
70
|
+
export_: Annotated[
|
|
71
|
+
bool,
|
|
72
|
+
typer.Option(
|
|
73
|
+
"--export/--no-export",
|
|
74
|
+
help="Regenerate the schema from the source before validating. "
|
|
75
|
+
"The export is transient -- doover_config.json is restored "
|
|
76
|
+
"afterwards, so validation never writes to the working tree.",
|
|
77
|
+
),
|
|
78
|
+
] = True,
|
|
79
|
+
app_name: Annotated[
|
|
80
|
+
str | None,
|
|
81
|
+
typer.Option(
|
|
82
|
+
help="Application name in doover_config.json. Required to pick one "
|
|
83
|
+
"non-interactively when the repo defines multiple apps.",
|
|
84
|
+
),
|
|
85
|
+
] = None,
|
|
86
|
+
):
|
|
87
|
+
"""Validate the application notification schema."""
|
|
88
|
+
root_fp = get_app_directory(app_fp)
|
|
89
|
+
config_file = root_fp / "doover_config.json"
|
|
90
|
+
|
|
91
|
+
if export_ is True:
|
|
92
|
+
targets = [app_name] if app_name else app_names_in_config(root_fp) or [None]
|
|
93
|
+
with preserve_file(config_file):
|
|
94
|
+
for target in targets:
|
|
95
|
+
ctx.invoke(
|
|
96
|
+
export, ctx, app_fp=root_fp, validate_=False, app_name=target
|
|
97
|
+
)
|
|
98
|
+
_validate_notification_file(config_file)
|
|
99
|
+
else:
|
|
100
|
+
_validate_notification_file(config_file)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
#: Every event a topic is built from has to survive being a topic segment, and
|
|
104
|
+
#: the policy decides which half of the hierarchy it lands in. Both are checked
|
|
105
|
+
#: here because a bad one is only otherwise discovered when a device in the
|
|
106
|
+
#: field tries to send the notification.
|
|
107
|
+
_VALID_POLICIES = ("default", "opt-in")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _validate_notification_file(config_file: Path):
|
|
111
|
+
if not config_file.exists():
|
|
112
|
+
raise FileNotFoundError(
|
|
113
|
+
"doover_config.json not found. Please ensure there is a "
|
|
114
|
+
"doover_config.json file in the application directory."
|
|
115
|
+
)
|
|
116
|
+
data = json.loads(config_file.read_text())
|
|
117
|
+
|
|
118
|
+
for k, v in data.items():
|
|
119
|
+
if not isinstance(v, dict):
|
|
120
|
+
continue
|
|
121
|
+
|
|
122
|
+
schema = v.get("notification_schema")
|
|
123
|
+
# An explicit null says the same thing as no key: this app declares no
|
|
124
|
+
# notifications. Most apps do not, and an exporter that writes null is
|
|
125
|
+
# how they say so.
|
|
126
|
+
if schema is None:
|
|
127
|
+
continue
|
|
128
|
+
|
|
129
|
+
if not isinstance(schema, dict):
|
|
130
|
+
rich.print(
|
|
131
|
+
f"[red]Notification schema for {k} is not a JSON object "
|
|
132
|
+
f"(got {type(schema).__name__}).[/red]"
|
|
133
|
+
)
|
|
134
|
+
raise typer.Exit(1)
|
|
135
|
+
|
|
136
|
+
for event, entry in schema.items():
|
|
137
|
+
if not isinstance(entry, dict):
|
|
138
|
+
rich.print(
|
|
139
|
+
f"[red]Notification {event!r} for {k} is not a JSON object.[/red]"
|
|
140
|
+
)
|
|
141
|
+
raise typer.Exit(1)
|
|
142
|
+
policy = entry.get("policy")
|
|
143
|
+
if policy is not None and policy not in _VALID_POLICIES:
|
|
144
|
+
rich.print(
|
|
145
|
+
f"[red]Notification {event!r} for {k} has policy {policy!r}; "
|
|
146
|
+
f"expected one of {', '.join(_VALID_POLICIES)}.[/red]"
|
|
147
|
+
)
|
|
148
|
+
raise typer.Exit(1)
|
|
149
|
+
|
|
150
|
+
rich.print(
|
|
151
|
+
f"[green]Notification schema for {k} is valid "
|
|
152
|
+
f"({len(schema)} notification{'' if len(schema) == 1 else 's'}).[/green]"
|
|
153
|
+
)
|
|
@@ -111,6 +111,7 @@ class LocalApplication(ControlApplication):
|
|
|
111
111
|
allow_many: bool | None = None,
|
|
112
112
|
config_schema: Any | None = None,
|
|
113
113
|
ui_schema: Any | None = None,
|
|
114
|
+
notification_schema: Any | None = None,
|
|
114
115
|
depends_on: list[str] | None = None,
|
|
115
116
|
organisation: dict[str, Any] | str | int | None = None,
|
|
116
117
|
approx_installs: int | None = None,
|
|
@@ -127,6 +128,7 @@ class LocalApplication(ControlApplication):
|
|
|
127
128
|
build_widget_command: str | None = None,
|
|
128
129
|
export_config_command: str | None = None,
|
|
129
130
|
export_ui_command: str | None = None,
|
|
131
|
+
export_notification_command: str | None = None,
|
|
130
132
|
generate_ui: bool = True,
|
|
131
133
|
run_command: str | None = None,
|
|
132
134
|
staging_config: dict[str, Any] | None = None,
|
|
@@ -145,6 +147,7 @@ class LocalApplication(ControlApplication):
|
|
|
145
147
|
allow_many=allow_many,
|
|
146
148
|
config_schema=config_schema,
|
|
147
149
|
ui_schema=ui_schema,
|
|
150
|
+
notification_schema=notification_schema,
|
|
148
151
|
depends_on=depends_on,
|
|
149
152
|
organisation=organisation,
|
|
150
153
|
approx_installs=approx_installs,
|
|
@@ -181,6 +184,7 @@ class LocalApplication(ControlApplication):
|
|
|
181
184
|
self.build_args = build_args
|
|
182
185
|
self.export_config_command = export_config_command
|
|
183
186
|
self.export_ui_command = export_ui_command
|
|
187
|
+
self.export_notification_command = export_notification_command
|
|
184
188
|
self.generate_ui = generate_ui
|
|
185
189
|
self.run_command = run_command
|
|
186
190
|
self.staging_config = staging_config or {}
|
|
@@ -306,10 +310,12 @@ class LocalApplication(ControlApplication):
|
|
|
306
310
|
build_widget_command=data.get("build_widget_command"),
|
|
307
311
|
export_config_command=data.get("export_config_command"),
|
|
308
312
|
export_ui_command=data.get("export_ui_command"),
|
|
313
|
+
export_notification_command=data.get("export_notification_command"),
|
|
309
314
|
generate_ui=data.get("generate_ui", True),
|
|
310
315
|
run_command=data.get("run_command"),
|
|
311
316
|
config_schema=data.get("config_schema"),
|
|
312
317
|
ui_schema=data.get("ui_schema"),
|
|
318
|
+
notification_schema=data.get("notification_schema"),
|
|
313
319
|
staging_config=data.get("staging_config", {}),
|
|
314
320
|
icon_url=data.get("icon_url"),
|
|
315
321
|
banner_url=data.get("banner_url"),
|
|
@@ -401,6 +407,7 @@ class LocalApplication(ControlApplication):
|
|
|
401
407
|
"lambda_config": self.lambda_config,
|
|
402
408
|
"config_schema": self.config_schema,
|
|
403
409
|
"ui_schema": self.ui_schema,
|
|
410
|
+
"notification_schema": self.notification_schema,
|
|
404
411
|
"icon_url": self.icon_url,
|
|
405
412
|
"banner_url": self.banner_url,
|
|
406
413
|
}
|
|
@@ -417,6 +424,7 @@ class LocalApplication(ControlApplication):
|
|
|
417
424
|
data["build_widget_command"] = self.build_widget_command
|
|
418
425
|
data["export_config_command"] = self.export_config_command
|
|
419
426
|
data["export_ui_command"] = self.export_ui_command
|
|
427
|
+
data["export_notification_command"] = self.export_notification_command
|
|
420
428
|
data["generate_ui"] = self.generate_ui
|
|
421
429
|
data["run_command"] = self.run_command
|
|
422
430
|
|
|
@@ -591,7 +591,7 @@ requires-dist = [
|
|
|
591
591
|
{ name = "jsf", specifier = ">=0.11.2" },
|
|
592
592
|
{ name = "paramiko", specifier = ">=3.5.1" },
|
|
593
593
|
{ name = "prompt-toolkit", specifier = ">=3.0.52" },
|
|
594
|
-
{ name = "pydoover", specifier = ">=1.
|
|
594
|
+
{ name = "pydoover", specifier = ">=1.17.1" },
|
|
595
595
|
{ name = "pytz", specifier = ">=2025.2" },
|
|
596
596
|
{ name = "questionary", specifier = ">=1.10.0" },
|
|
597
597
|
{ name = "requests", specifier = ">=2.32.3" },
|
|
@@ -1337,15 +1337,15 @@ wheels = [
|
|
|
1337
1337
|
|
|
1338
1338
|
[[package]]
|
|
1339
1339
|
name = "pydoover"
|
|
1340
|
-
version = "1.
|
|
1340
|
+
version = "1.17.1"
|
|
1341
1341
|
source = { registry = "https://pypi.org/simple" }
|
|
1342
1342
|
dependencies = [
|
|
1343
1343
|
{ name = "aiohttp" },
|
|
1344
1344
|
{ name = "httpx" },
|
|
1345
1345
|
]
|
|
1346
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
1346
|
+
sdist = { url = "https://files.pythonhosted.org/packages/d9/99/56164d2368604d81dedde8f969f777f2cfa75b6325cd7235c4a64413714d/pydoover-1.17.1.tar.gz", hash = "sha256:11ae6055a5af53aeb137c5a14fe38942596f7549cd9bdf20db571433f135503d", size = 608525, upload-time = "2026-09-09T03:17:32.98Z" }
|
|
1347
1347
|
wheels = [
|
|
1348
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1348
|
+
{ url = "https://files.pythonhosted.org/packages/2b/8f/f17d1242881565ed5d93604dab48977891421f3d1a1fe189f6150af31ae8/pydoover-1.17.1-py3-none-any.whl", hash = "sha256:dc9e6a67007d161193372d884b691809c7c835409f8c50d6dacbec5dee0ce27a", size = 329229, upload-time = "2026-09-09T03:17:31.696Z" },
|
|
1349
1349
|
]
|
|
1350
1350
|
|
|
1351
1351
|
[[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
|
|
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
|