qapu-cli 0.76.0__tar.gz → 0.77.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.
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/PKG-INFO +2 -2
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/README.md +1 -1
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/pyproject.toml +1 -1
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/qapu_cli/tui/screens/equipment_form.py +35 -0
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/qapu_cli/tui/screens/infrastructure.py +7 -3
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/qapu_cli.egg-info/PKG-INFO +2 -2
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/qapu_cli.egg-info/SOURCES.txt +1 -0
- qapu_cli-0.77.0/tests/test_tui_electrical_parent.py +62 -0
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/qapu_cli/__init__.py +0 -0
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/qapu_cli/client.py +0 -0
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/qapu_cli/command_packages.py +0 -0
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/qapu_cli/main.py +0 -0
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/qapu_cli/tui/__init__.py +0 -0
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/qapu_cli/tui/app.py +0 -0
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/qapu_cli/tui/app.tcss +0 -0
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/qapu_cli/tui/data.py +0 -0
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/qapu_cli/tui/rendering.py +0 -0
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/qapu_cli/tui/screens/__init__.py +0 -0
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/qapu_cli/tui/screens/catalog.py +0 -0
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/qapu_cli/tui/screens/catalog_form.py +0 -0
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/qapu_cli/tui/screens/catalog_picker.py +0 -0
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/qapu_cli/tui/screens/confirm.py +0 -0
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/qapu_cli/tui/screens/dashboard.py +0 -0
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/qapu_cli/tui/screens/device_authorization.py +0 -0
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/qapu_cli/tui/screens/device_edit.py +0 -0
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/qapu_cli/tui/screens/device_list.py +0 -0
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/qapu_cli/tui/screens/group_detail.py +0 -0
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/qapu_cli/tui/screens/group_form.py +0 -0
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/qapu_cli/tui/screens/group_list.py +0 -0
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/qapu_cli/tui/widgets/__init__.py +0 -0
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/qapu_cli/tui/widgets/device_table.py +0 -0
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/qapu_cli/visibility.py +0 -0
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/qapu_cli.egg-info/dependency_links.txt +0 -0
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/qapu_cli.egg-info/entry_points.txt +0 -0
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/qapu_cli.egg-info/requires.txt +0 -0
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/qapu_cli.egg-info/top_level.txt +0 -0
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/setup.cfg +0 -0
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/tests/test_command_packages_complete.py +0 -0
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/tests/test_login.py +0 -0
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/tests/test_logout.py +0 -0
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/tests/test_tui_authorization.py +0 -0
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/tests/test_tui_catalog.py +0 -0
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/tests/test_tui_device_edit.py +0 -0
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/tests/test_tui_groups.py +0 -0
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/tests/test_tui_infrastructure.py +0 -0
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/tests/test_tui_smoke.py +0 -0
- {qapu_cli-0.76.0 → qapu_cli-0.77.0}/tests/test_visibility.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qapu-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.77.0
|
|
4
4
|
Summary: CLI client for the Qapu API - built for the Hermes agent, but usable by anyone talking to api.ovoo.com.tr from outside the Swarm.
|
|
5
5
|
Author: OVOO Technology
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -730,7 +730,7 @@ Live-tested: a real 404 (`synthesis get 99999`) rendered as a red panel with the
|
|
|
730
730
|
|
|
731
731
|
Getting set up: `qapu login` (Username+Password, the default - prompts for whatever's missing), then `qapu auth-token create <label> --packages catalog:read,telemetry:read,...` (must be a subset of your own account's granted packages - ask an admin if a `403` says you're missing one) - that command offers to save the freshly-minted token as this machine's active one right there, since the raw value is only ever shown once. `qapu auth-token use <token>` saves one you already have (e.g. minted on another machine, or printed by an earlier run you didn't save at the time) without minting a new one. The saved value lives at `~/.config/qapu/credentials.json`'s `Hermes_API_Token` key (mode 600) - a SEPARATE key from `Access_Token` (the `qapu login`-issued JWT, deliberately scope-limited to token management only and not itself a valid Hermes credential) - `qapu logout` clears the login session but deliberately leaves a saved Hermes token alone, since it has its own independent expiry/revocation lifecycle. `qapu whoami` shows whether each of the two is currently configured.
|
|
732
732
|
|
|
733
|
-
This is the exact same token `services/mcp`'s full-access instance and `
|
|
733
|
+
This is the exact same token `services/mcp`'s full-access instance and `Cinga_AI/runtime/service` now use to reach Hermes too (forwarded/minted per-caller rather than a shared secret) - see those services' own READMEs.
|
|
734
734
|
|
|
735
735
|
## Running locally
|
|
736
736
|
|
|
@@ -713,7 +713,7 @@ Live-tested: a real 404 (`synthesis get 99999`) rendered as a red panel with the
|
|
|
713
713
|
|
|
714
714
|
Getting set up: `qapu login` (Username+Password, the default - prompts for whatever's missing), then `qapu auth-token create <label> --packages catalog:read,telemetry:read,...` (must be a subset of your own account's granted packages - ask an admin if a `403` says you're missing one) - that command offers to save the freshly-minted token as this machine's active one right there, since the raw value is only ever shown once. `qapu auth-token use <token>` saves one you already have (e.g. minted on another machine, or printed by an earlier run you didn't save at the time) without minting a new one. The saved value lives at `~/.config/qapu/credentials.json`'s `Hermes_API_Token` key (mode 600) - a SEPARATE key from `Access_Token` (the `qapu login`-issued JWT, deliberately scope-limited to token management only and not itself a valid Hermes credential) - `qapu logout` clears the login session but deliberately leaves a saved Hermes token alone, since it has its own independent expiry/revocation lifecycle. `qapu whoami` shows whether each of the two is currently configured.
|
|
715
715
|
|
|
716
|
-
This is the exact same token `services/mcp`'s full-access instance and `
|
|
716
|
+
This is the exact same token `services/mcp`'s full-access instance and `Cinga_AI/runtime/service` now use to reach Hermes too (forwarded/minted per-caller rather than a shared secret) - see those services' own READMEs.
|
|
717
717
|
|
|
718
718
|
## Running locally
|
|
719
719
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "qapu-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.77.0"
|
|
4
4
|
description = "CLI client for the Qapu API - built for the Hermes agent, but usable by anyone talking to api.ovoo.com.tr from outside the Swarm."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -24,6 +24,30 @@ from textual.screen import ModalScreen
|
|
|
24
24
|
from textual.widgets import Button, Select, Static, Input
|
|
25
25
|
|
|
26
26
|
|
|
27
|
+
# Electrical Parent Choices (2026-10-21) - Every Item on the Same Site/Tree
|
|
28
|
+
# Except the Item Itself and Anything it (Transitively) Feeds, Since
|
|
29
|
+
# Picking One of Those Would Close a Loop. Pure, so it's Testable Without
|
|
30
|
+
# Textual; the API Refuses a Loop on its own Too.
|
|
31
|
+
def electrical_parent_candidates(items: list[dict[str, Any]], equipment_id: Optional[int] = None) -> list[tuple[str, Any]]:
|
|
32
|
+
fed_by: dict[Any, list[Any]] = {}
|
|
33
|
+
for item in items:
|
|
34
|
+
fed_by.setdefault(item.get("Electrical_Parent_ID"), []).append(item.get("ID"))
|
|
35
|
+
excluded: set = set()
|
|
36
|
+
if equipment_id is not None:
|
|
37
|
+
frontier = [equipment_id]
|
|
38
|
+
while frontier:
|
|
39
|
+
current = frontier.pop()
|
|
40
|
+
if current in excluded:
|
|
41
|
+
continue
|
|
42
|
+
excluded.add(current)
|
|
43
|
+
frontier.extend(fed_by.get(current, []))
|
|
44
|
+
return [
|
|
45
|
+
(f"#{item['ID']} {(item.get('Type') or {}).get('Name') or '?'}: {item.get('Name') or '-'}", item["ID"])
|
|
46
|
+
for item in items
|
|
47
|
+
if item.get("ID") is not None and item["ID"] not in excluded
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
|
|
27
51
|
class EquipmentFormScreen(ModalScreen[Optional[dict[str, Any]]]):
|
|
28
52
|
|
|
29
53
|
def __init__(
|
|
@@ -32,10 +56,12 @@ class EquipmentFormScreen(ModalScreen[Optional[dict[str, Any]]]):
|
|
|
32
56
|
controls_options: list[tuple[str, Any]],
|
|
33
57
|
parent_equipment_id: Optional[int] = None,
|
|
34
58
|
equipment: Optional[dict[str, Any]] = None,
|
|
59
|
+
electrical_parent_options: Optional[list[tuple[str, Any]]] = None,
|
|
35
60
|
) -> None:
|
|
36
61
|
super().__init__()
|
|
37
62
|
self.equipment_type_options = equipment_type_options
|
|
38
63
|
self.controls_options = controls_options
|
|
64
|
+
self.electrical_parent_options = electrical_parent_options or []
|
|
39
65
|
self.parent_equipment_id = parent_equipment_id
|
|
40
66
|
self.equipment = equipment
|
|
41
67
|
self.is_edit = equipment is not None
|
|
@@ -44,6 +70,9 @@ class EquipmentFormScreen(ModalScreen[Optional[dict[str, Any]]]):
|
|
|
44
70
|
equipment = self.equipment or {}
|
|
45
71
|
type_name = (equipment.get("Type") or {}).get("Name")
|
|
46
72
|
current_controls = equipment.get("Controls_Equipment_ID")
|
|
73
|
+
current_electrical_parent = equipment.get("Electrical_Parent_ID")
|
|
74
|
+
if current_electrical_parent not in {value for _, value in self.electrical_parent_options}:
|
|
75
|
+
current_electrical_parent = None
|
|
47
76
|
specs = equipment.get("Specs") or {}
|
|
48
77
|
|
|
49
78
|
with VerticalScroll(id="equipment-form"):
|
|
@@ -63,6 +92,8 @@ class EquipmentFormScreen(ModalScreen[Optional[dict[str, Any]]]):
|
|
|
63
92
|
yield Input(value=equipment.get("Serial_Number") or "", id="form-serial-number")
|
|
64
93
|
yield Static("Besler/Sürer (Controls)")
|
|
65
94
|
yield Select(self.controls_options, allow_blank=True, value=current_controls if current_controls is not None else Select.NULL, id="form-controls")
|
|
95
|
+
yield Static("Elektrik Beslemesi (Electrical Parent)")
|
|
96
|
+
yield Select(self.electrical_parent_options, allow_blank=True, value=current_electrical_parent if current_electrical_parent is not None else Select.NULL, id="form-electrical-parent")
|
|
66
97
|
yield Static("Özellikler (Specs, JSON)")
|
|
67
98
|
yield Input(value=json.dumps(specs, ensure_ascii=False) if specs else "", id="form-specs")
|
|
68
99
|
yield Static("Not")
|
|
@@ -120,6 +151,10 @@ class EquipmentFormScreen(ModalScreen[Optional[dict[str, Any]]]):
|
|
|
120
151
|
if controls_select.value is not Select.NULL:
|
|
121
152
|
body["Controls_Equipment_ID"] = controls_select.value
|
|
122
153
|
|
|
154
|
+
electrical_parent_select = self.query_one("#form-electrical-parent", Select)
|
|
155
|
+
if electrical_parent_select.value is not Select.NULL:
|
|
156
|
+
body["Electrical_Parent_ID"] = electrical_parent_select.value
|
|
157
|
+
|
|
123
158
|
self.dismiss(body)
|
|
124
159
|
|
|
125
160
|
# End of File
|
|
@@ -37,7 +37,7 @@ from textual.widgets.tree import TreeNode
|
|
|
37
37
|
from qapu_cli import main as cli_main
|
|
38
38
|
from qapu_cli.tui import data as tui_data
|
|
39
39
|
from qapu_cli.tui.screens.confirm import ConfirmScreen
|
|
40
|
-
from qapu_cli.tui.screens.equipment_form import EquipmentFormScreen
|
|
40
|
+
from qapu_cli.tui.screens.equipment_form import EquipmentFormScreen, electrical_parent_candidates
|
|
41
41
|
|
|
42
42
|
# A Unique Sentinel, Distinct From None - MeasurementPointScreen's "İptal"
|
|
43
43
|
# (Cancel the Whole Assign Action) vs. Picking "Bilinmiyor"/Leaving it
|
|
@@ -217,6 +217,8 @@ class InfrastructureScreen(Screen):
|
|
|
217
217
|
model_str = " ".join(part for part in (model.get("Manufacturer_Name"), model.get("Name")) if part) or "-"
|
|
218
218
|
controls_id = item.get("Controls_Equipment_ID")
|
|
219
219
|
controls_str = f"#{controls_id}" if controls_id is not None else "-"
|
|
220
|
+
electrical_parent_id = item.get("Electrical_Parent_ID")
|
|
221
|
+
enclosure_id = item.get("Enclosure_ID")
|
|
220
222
|
return "\n".join([
|
|
221
223
|
f"[bold]{(item.get('Type') or {}).get('Name') or '-'}[/bold] (#{item.get('ID')})",
|
|
222
224
|
f"Ad: {item.get('Name') or '-'}",
|
|
@@ -225,6 +227,8 @@ class InfrastructureScreen(Screen):
|
|
|
225
227
|
f"Seri No: {item.get('Serial_Number') or '-'}",
|
|
226
228
|
f"Özellikler: {specs or '-'}",
|
|
227
229
|
f"Besler/Sürer: {controls_str}",
|
|
230
|
+
f"Elektrik Beslemesi: {f'#{electrical_parent_id}' if electrical_parent_id is not None else '-'}",
|
|
231
|
+
f"Bulunduğu Pano: {f'#{enclosure_id}' if enclosure_id is not None else '-'}",
|
|
228
232
|
f"Not: {item.get('Notes') or '-'}",
|
|
229
233
|
])
|
|
230
234
|
|
|
@@ -258,7 +262,7 @@ class InfrastructureScreen(Screen):
|
|
|
258
262
|
return
|
|
259
263
|
type_options = [(t.get("Name") or f"#{t.get('ID')}", t.get("ID")) for t in equipment_types if t.get("ID") is not None]
|
|
260
264
|
parent_id = self._current_item.get("ID") if self._current_item else None
|
|
261
|
-
body = await self.app.push_screen_wait(EquipmentFormScreen(type_options, self._controls_options(), parent_equipment_id=parent_id))
|
|
265
|
+
body = await self.app.push_screen_wait(EquipmentFormScreen(type_options, self._controls_options(), parent_equipment_id=parent_id, electrical_parent_options=electrical_parent_candidates(list(self._equipment_by_id.values()))))
|
|
262
266
|
if body is None:
|
|
263
267
|
return
|
|
264
268
|
result, error = await self.run_worker(partial(tui_data.add_equipment, body), thread=True).wait()
|
|
@@ -276,7 +280,7 @@ class InfrastructureScreen(Screen):
|
|
|
276
280
|
self.app.notify("Önce bir ekipman seçin.", severity="warning", title="Altyapı")
|
|
277
281
|
return
|
|
278
282
|
item = self._current_item
|
|
279
|
-
body = await self.app.push_screen_wait(EquipmentFormScreen([], self._controls_options(exclude_id=item.get("ID")), equipment=item))
|
|
283
|
+
body = await self.app.push_screen_wait(EquipmentFormScreen([], self._controls_options(exclude_id=item.get("ID")), equipment=item, electrical_parent_options=electrical_parent_candidates(list(self._equipment_by_id.values()), item.get("ID"))))
|
|
280
284
|
if body is None:
|
|
281
285
|
return
|
|
282
286
|
result, error = await self.run_worker(partial(tui_data.update_equipment, item["ID"], body), thread=True).wait()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qapu-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.77.0
|
|
4
4
|
Summary: CLI client for the Qapu API - built for the Hermes agent, but usable by anyone talking to api.ovoo.com.tr from outside the Swarm.
|
|
5
5
|
Author: OVOO Technology
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -730,7 +730,7 @@ Live-tested: a real 404 (`synthesis get 99999`) rendered as a red panel with the
|
|
|
730
730
|
|
|
731
731
|
Getting set up: `qapu login` (Username+Password, the default - prompts for whatever's missing), then `qapu auth-token create <label> --packages catalog:read,telemetry:read,...` (must be a subset of your own account's granted packages - ask an admin if a `403` says you're missing one) - that command offers to save the freshly-minted token as this machine's active one right there, since the raw value is only ever shown once. `qapu auth-token use <token>` saves one you already have (e.g. minted on another machine, or printed by an earlier run you didn't save at the time) without minting a new one. The saved value lives at `~/.config/qapu/credentials.json`'s `Hermes_API_Token` key (mode 600) - a SEPARATE key from `Access_Token` (the `qapu login`-issued JWT, deliberately scope-limited to token management only and not itself a valid Hermes credential) - `qapu logout` clears the login session but deliberately leaves a saved Hermes token alone, since it has its own independent expiry/revocation lifecycle. `qapu whoami` shows whether each of the two is currently configured.
|
|
732
732
|
|
|
733
|
-
This is the exact same token `services/mcp`'s full-access instance and `
|
|
733
|
+
This is the exact same token `services/mcp`'s full-access instance and `Cinga_AI/runtime/service` now use to reach Hermes too (forwarded/minted per-caller rather than a shared secret) - see those services' own READMEs.
|
|
734
734
|
|
|
735
735
|
## Running locally
|
|
736
736
|
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Electrical Parent in the Equipment Form (2026-10-21) - the candidate list
|
|
2
|
+
# never offers the item itself or anything it feeds, and the chosen value
|
|
3
|
+
# is sent as Electrical_Parent_ID. Same headless App.run_test() pattern as
|
|
4
|
+
# test_tui_infrastructure.py.
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from qapu_cli.tui.app import QapuTUIApp
|
|
9
|
+
from qapu_cli.tui.screens.equipment_form import EquipmentFormScreen, electrical_parent_candidates
|
|
10
|
+
|
|
11
|
+
BOX = {"ID": 17, "Type": {"Name": "Electric_Box"}, "Name": "Pano"}
|
|
12
|
+
MAIN = {"ID": 19, "Type": {"Name": "Main_Contactor"}, "Name": "Ana Kontaktör", "Electrical_Parent_ID": 17}
|
|
13
|
+
PUMP = {"ID": 18, "Type": {"Name": "Pump"}, "Name": "Pompa", "Electrical_Parent_ID": 19}
|
|
14
|
+
FUSE = {"ID": 28, "Type": {"Name": "Main_Fuse"}, "Name": "Sigorta", "Electrical_Parent_ID": 17}
|
|
15
|
+
ITEMS = [BOX, MAIN, PUMP, FUSE]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _ids(options):
|
|
19
|
+
return [value for _, value in options]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_candidates_exclude_the_item_and_everything_it_feeds():
|
|
23
|
+
assert _ids(electrical_parent_candidates(ITEMS, 17)) == [] # the panel feeds everything here
|
|
24
|
+
assert _ids(electrical_parent_candidates(ITEMS, 19)) == [17, 28] # not itself, not the pump it feeds
|
|
25
|
+
assert _ids(electrical_parent_candidates(ITEMS, 18)) == [17, 19, 28] # a leaf can pick anything else
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_candidates_for_a_new_item_offer_everything():
|
|
29
|
+
assert _ids(electrical_parent_candidates(ITEMS)) == [17, 19, 18, 28]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_candidates_survive_a_loop_already_in_the_data():
|
|
33
|
+
looped = [{"ID": 1, "Electrical_Parent_ID": 2}, {"ID": 2, "Electrical_Parent_ID": 1}, {"ID": 3}]
|
|
34
|
+
assert _ids(electrical_parent_candidates(looped, 1)) == [3]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@pytest.mark.asyncio
|
|
38
|
+
async def test_form_sends_the_chosen_electrical_parent():
|
|
39
|
+
results = []
|
|
40
|
+
app = QapuTUIApp()
|
|
41
|
+
async with app.run_test() as pilot:
|
|
42
|
+
form = EquipmentFormScreen([], [], equipment=PUMP, electrical_parent_options=electrical_parent_candidates(ITEMS, 18))
|
|
43
|
+
await app.push_screen(form, results.append)
|
|
44
|
+
await pilot.pause(0.2)
|
|
45
|
+
from textual.widgets import Button, Select
|
|
46
|
+
select = app.screen.query_one("#form-electrical-parent", Select)
|
|
47
|
+
assert select.value == 19 # current value pre-selected
|
|
48
|
+
select.value = 17
|
|
49
|
+
app.screen.query_one("#form-save", Button).press()
|
|
50
|
+
await pilot.pause(0.2)
|
|
51
|
+
assert results and results[0]["Electrical_Parent_ID"] == 17
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@pytest.mark.asyncio
|
|
55
|
+
async def test_form_does_not_preselect_a_value_that_would_loop():
|
|
56
|
+
app = QapuTUIApp()
|
|
57
|
+
async with app.run_test() as pilot:
|
|
58
|
+
looped = dict(MAIN, Electrical_Parent_ID=18) # bad data: the contactor "fed" by its own pump
|
|
59
|
+
await app.push_screen(EquipmentFormScreen([], [], equipment=looped, electrical_parent_options=electrical_parent_candidates(ITEMS, 19)))
|
|
60
|
+
await pilot.pause(0.2)
|
|
61
|
+
from textual.widgets import Select
|
|
62
|
+
assert app.screen.query_one("#form-electrical-parent", Select).value is Select.NULL
|
|
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
|