db-process 0.1.0__tar.gz → 0.2.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.
- {db_process-0.1.0 → db_process-0.2.0}/LICENSE +2 -2
- db_process-0.2.0/PKG-INFO +226 -0
- db_process-0.2.0/README.md +210 -0
- {db_process-0.1.0 → db_process-0.2.0}/db_process/__init__.py +20 -1
- db_process-0.2.0/db_process/__main__.py +6 -0
- db_process-0.2.0/db_process/cli.py +152 -0
- db_process-0.2.0/db_process/diagnostics.py +308 -0
- {db_process-0.1.0 → db_process-0.2.0}/db_process/runner.py +32 -0
- db_process-0.2.0/db_process.egg-info/PKG-INFO +226 -0
- {db_process-0.1.0 → db_process-0.2.0}/db_process.egg-info/SOURCES.txt +8 -1
- db_process-0.2.0/db_process.egg-info/entry_points.txt +2 -0
- {db_process-0.1.0 → db_process-0.2.0}/pyproject.toml +11 -4
- db_process-0.2.0/tests/test_cli.py +108 -0
- db_process-0.2.0/tests/test_diagnostics.py +192 -0
- db_process-0.2.0/tests/test_status.py +59 -0
- db_process-0.1.0/PKG-INFO +0 -18
- db_process-0.1.0/README.md +0 -2
- db_process-0.1.0/db_process.egg-info/PKG-INFO +0 -18
- {db_process-0.1.0 → db_process-0.2.0}/db_process/commands.py +0 -0
- {db_process-0.1.0 → db_process-0.2.0}/db_process/executable.py +0 -0
- {db_process-0.1.0 → db_process-0.2.0}/db_process.egg-info/dependency_links.txt +0 -0
- {db_process-0.1.0 → db_process-0.2.0}/db_process.egg-info/requires.txt +0 -0
- {db_process-0.1.0 → db_process-0.2.0}/db_process.egg-info/top_level.txt +0 -0
- {db_process-0.1.0 → db_process-0.2.0}/setup.cfg +0 -0
- {db_process-0.1.0 → db_process-0.2.0}/tests/test_commands.py +0 -0
- {db_process-0.1.0 → db_process-0.2.0}/tests/test_executable.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2026
|
|
3
|
+
Copyright (c) 2026 DesignBuilder Software
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
18
18
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
19
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
20
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: db-process
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Python interface to DesignBuilder command line processing.
|
|
5
|
+
Author: DesignBuilder Software
|
|
6
|
+
Project-URL: Homepage, https://github.com/DesignBuilderSoftware/db-process
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/DesignBuilderSoftware/db-process/issues
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Dist: psutil>=5.9.0
|
|
12
|
+
Provides-Extra: dev
|
|
13
|
+
Requires-Dist: pytest; extra == "dev"
|
|
14
|
+
Requires-Dist: ruff; extra == "dev"
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# db-process
|
|
18
|
+
|
|
19
|
+
Python interface to DesignBuilder's command-line driver — typed `/process=` commands,
|
|
20
|
+
subprocess execution, process discovery, and diagnostics-log parsing.
|
|
21
|
+
|
|
22
|
+
DesignBuilder is a Windows GUI app, but it exposes a `/process=` argument
|
|
23
|
+
that runs a sequence of commands non-interactively and exits. `db-process`
|
|
24
|
+
wraps that surface in real Python: dataclass commands instead of stringly-typed
|
|
25
|
+
flags, a `ProcessChain` builder, blocking and non-blocking runners, helpers
|
|
26
|
+
to find/kill the process, and a parser for the rolling diagnostics logs
|
|
27
|
+
DesignBuilder writes under `%LOCALAPPDATA%`.
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install db-process
|
|
33
|
+
# or, from the cloned repo:
|
|
34
|
+
pip install -e .[dev]
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Requires Python 3.10+ and `psutil`. DesignBuilder itself only runs on Windows;
|
|
38
|
+
on other platforms, the package imports fine but `run()` and the process
|
|
39
|
+
helpers will raise `FileNotFoundError`.
|
|
40
|
+
|
|
41
|
+
## Quick start
|
|
42
|
+
|
|
43
|
+
### Python — run a model with a chain of commands
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from db_process import ProcessChain, Screen, run
|
|
47
|
+
|
|
48
|
+
chain = (
|
|
49
|
+
ProcessChain()
|
|
50
|
+
.use_sim_manager()
|
|
51
|
+
.switch_screen(Screen.SIMULATION)
|
|
52
|
+
.sim_start_date(1, 1)
|
|
53
|
+
.sim_end_date(31, 12)
|
|
54
|
+
.change_attribute("OccupancyValue", 0.5)
|
|
55
|
+
.run()
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
result = run("models/office.dsb", chain, timeout=600)
|
|
59
|
+
print(result.success, result.duration_seconds)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Python — convenience factories for common runs
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from db_process import eplus_simulation, run
|
|
66
|
+
|
|
67
|
+
# A pre-built ProcessChain for "open model, run EnergyPlus, close".
|
|
68
|
+
result = run("models/office.dsb", eplus_simulation())
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Other factories: `sbem_calculation`, `export_xml`, `heating_and_cooling_design`,
|
|
72
|
+
`daylighting`, `cfd_simulation`.
|
|
73
|
+
|
|
74
|
+
### Python — non-blocking with idle-detection kill
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from db_process import run_async
|
|
78
|
+
|
|
79
|
+
handle = run_async("models/office.dsb")
|
|
80
|
+
# Block until DesignBuilder finishes its work and CPU drops, then kill it.
|
|
81
|
+
handle.kill_when_idle(idle_threshold=10, cpu_threshold=0.1)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`kill_when_idle` is the workaround for runs where DesignBuilder finishes the
|
|
85
|
+
calculation but doesn't exit — common with the GUI Sim Manager path.
|
|
86
|
+
|
|
87
|
+
### CLI
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
db-process status # is DesignBuilder running? show pid + exe path
|
|
91
|
+
db-process open # launch DesignBuilder
|
|
92
|
+
db-process open path/to.dsb # launch with a model
|
|
93
|
+
db-process close # kill any running DesignBuilder
|
|
94
|
+
db-process restart [model] # close + open (with a 1.5 s settle)
|
|
95
|
+
|
|
96
|
+
db-process diag list # list recent diagnostic logs (newest first)
|
|
97
|
+
db-process diag latest # print a one-paragraph summary of the newest log
|
|
98
|
+
db-process diag latest --errors # also list every error line found
|
|
99
|
+
db-process diag summary <path> # summarise a specific log
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`db-process` is the entry point installed by the package; you can also run it
|
|
103
|
+
as `python -m db_process`.
|
|
104
|
+
|
|
105
|
+
## Public API
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
from db_process import (
|
|
109
|
+
# Executable discovery
|
|
110
|
+
find_designbuilder, # () -> Path
|
|
111
|
+
|
|
112
|
+
# Process status
|
|
113
|
+
is_running, find_process, status, # status() -> ProcessStatus
|
|
114
|
+
kill_process, kill_when_idle,
|
|
115
|
+
ProcessStatus, # is_running, pid, exe_path
|
|
116
|
+
|
|
117
|
+
# Run
|
|
118
|
+
run, run_async, RunHandle, RunResult,
|
|
119
|
+
|
|
120
|
+
# Command model
|
|
121
|
+
ProcessChain, Screen,
|
|
122
|
+
SwitchScreen, RunCalculation, TabChange,
|
|
123
|
+
SimStartDate, SimEndDate, ChangeAttributeValue,
|
|
124
|
+
ExternalCommand, ImportModelData, ImportLibraryData,
|
|
125
|
+
ExportAsXML, UseSimManager, NoClose,
|
|
126
|
+
|
|
127
|
+
# Convenience factories
|
|
128
|
+
eplus_simulation, sbem_calculation, export_xml,
|
|
129
|
+
heating_and_cooling_design, daylighting, cfd_simulation,
|
|
130
|
+
|
|
131
|
+
# Diagnostics logs
|
|
132
|
+
DiagnosticsLog, find_logs, latest_log,
|
|
133
|
+
parse_filename_timestamp, DEFAULT_DIAGNOSTICS_DIR,
|
|
134
|
+
)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Executable discovery
|
|
138
|
+
|
|
139
|
+
`find_designbuilder(exe_path=None)` resolves DesignBuilder.exe in this order:
|
|
140
|
+
|
|
141
|
+
1. The explicit `exe_path` argument, if given.
|
|
142
|
+
2. `DESIGNBUILDER_EXE` environment variable.
|
|
143
|
+
3. `C:\Program Files (x86)\DesignBuilder\DesignBuilder.exe`,
|
|
144
|
+
then `C:\Program Files\DesignBuilder\DesignBuilder.exe`.
|
|
145
|
+
4. The system `PATH` (`shutil.which("DesignBuilder")`).
|
|
146
|
+
|
|
147
|
+
Raises `FileNotFoundError` with a hint to set `DESIGNBUILDER_EXE` if none match.
|
|
148
|
+
|
|
149
|
+
### Process status
|
|
150
|
+
|
|
151
|
+
`status()` is a one-shot snapshot:
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
from db_process import status
|
|
155
|
+
|
|
156
|
+
s = status()
|
|
157
|
+
# ProcessStatus(is_running=True, pid=12345, exe_path='C:\\...\\DesignBuilder.exe')
|
|
158
|
+
if not s.is_running and s.exe_path:
|
|
159
|
+
# Discovered but not running — safe to launch.
|
|
160
|
+
...
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
`exe_path` is populated regardless of whether the process is running, so
|
|
164
|
+
callers can decide whether to launch without a second `find_designbuilder`
|
|
165
|
+
call.
|
|
166
|
+
|
|
167
|
+
### `ProcessChain` — building a `/process=` argument
|
|
168
|
+
|
|
169
|
+
`ProcessChain` is a fluent builder for the `/process=` command sequence
|
|
170
|
+
documented in DesignBuilder Help. Example output of `chain.to_string()`:
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
/process=UseSimManager, miGSS, SimStartDate 1 1, SimEndDate 31 12,
|
|
174
|
+
ChangeAttributeValue OccupancyValue 0.5, miTUpdate
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Every command is also exposed as a plain dataclass, so you can construct
|
|
178
|
+
chains without the builder when that's clearer.
|
|
179
|
+
|
|
180
|
+
### Diagnostics logs
|
|
181
|
+
|
|
182
|
+
DesignBuilder writes a per-session diagnostics log to
|
|
183
|
+
`%LOCALAPPDATA%\DesignBuilder\Diagnostics\` named like
|
|
184
|
+
`DesignBuilder_diagnostic_2026-04-23_15-12-08.txt`. `DiagnosticsLog`
|
|
185
|
+
parses one of those files into a structured object:
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
from db_process import latest_log, DiagnosticsLog
|
|
189
|
+
|
|
190
|
+
log = DiagnosticsLog.from_file(latest_log())
|
|
191
|
+
print(log.summary())
|
|
192
|
+
print(log.is_complete, log.duration, log.error_count)
|
|
193
|
+
for line_no, text in log.errors:
|
|
194
|
+
print(f"L{line_no}: {text}")
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Error detection uses word-boundary matching (`error`, `failed`, `exception`,
|
|
198
|
+
`fatal`, `crash`) plus a small negative list to suppress known benign markers
|
|
199
|
+
(e.g. the `GL_KHR_no_error` OpenGL extension).
|
|
200
|
+
|
|
201
|
+
## Compatibility
|
|
202
|
+
|
|
203
|
+
- **DesignBuilder**: any version that supports the `/process=` argument.
|
|
204
|
+
Tested against the v25.1 / v26.1 lines.
|
|
205
|
+
- **Python**: 3.10+.
|
|
206
|
+
- **OS**: Windows for live runs. The command model and diagnostics parser
|
|
207
|
+
are pure Python and import on any platform — useful for unit tests on
|
|
208
|
+
Linux CI.
|
|
209
|
+
|
|
210
|
+
## Development
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
pip install -e .[dev]
|
|
214
|
+
pytest
|
|
215
|
+
ruff check .
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Tests are split by area:
|
|
219
|
+
|
|
220
|
+
| File | Covers |
|
|
221
|
+
| --- | --- |
|
|
222
|
+
| `test_commands.py` | `ProcessChain` building + every command's `to_string()` |
|
|
223
|
+
| `test_executable.py` | `find_designbuilder` resolution order |
|
|
224
|
+
| `test_status.py` | `status` / `ProcessStatus` |
|
|
225
|
+
| `test_diagnostics.py` | log parsing, error detection, filename timestamps |
|
|
226
|
+
| `test_cli.py` | CLI argparse wiring (mocked subprocess) |
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# db-process
|
|
2
|
+
|
|
3
|
+
Python interface to DesignBuilder's command-line driver — typed `/process=` commands,
|
|
4
|
+
subprocess execution, process discovery, and diagnostics-log parsing.
|
|
5
|
+
|
|
6
|
+
DesignBuilder is a Windows GUI app, but it exposes a `/process=` argument
|
|
7
|
+
that runs a sequence of commands non-interactively and exits. `db-process`
|
|
8
|
+
wraps that surface in real Python: dataclass commands instead of stringly-typed
|
|
9
|
+
flags, a `ProcessChain` builder, blocking and non-blocking runners, helpers
|
|
10
|
+
to find/kill the process, and a parser for the rolling diagnostics logs
|
|
11
|
+
DesignBuilder writes under `%LOCALAPPDATA%`.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install db-process
|
|
17
|
+
# or, from the cloned repo:
|
|
18
|
+
pip install -e .[dev]
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Requires Python 3.10+ and `psutil`. DesignBuilder itself only runs on Windows;
|
|
22
|
+
on other platforms, the package imports fine but `run()` and the process
|
|
23
|
+
helpers will raise `FileNotFoundError`.
|
|
24
|
+
|
|
25
|
+
## Quick start
|
|
26
|
+
|
|
27
|
+
### Python — run a model with a chain of commands
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from db_process import ProcessChain, Screen, run
|
|
31
|
+
|
|
32
|
+
chain = (
|
|
33
|
+
ProcessChain()
|
|
34
|
+
.use_sim_manager()
|
|
35
|
+
.switch_screen(Screen.SIMULATION)
|
|
36
|
+
.sim_start_date(1, 1)
|
|
37
|
+
.sim_end_date(31, 12)
|
|
38
|
+
.change_attribute("OccupancyValue", 0.5)
|
|
39
|
+
.run()
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
result = run("models/office.dsb", chain, timeout=600)
|
|
43
|
+
print(result.success, result.duration_seconds)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Python — convenience factories for common runs
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from db_process import eplus_simulation, run
|
|
50
|
+
|
|
51
|
+
# A pre-built ProcessChain for "open model, run EnergyPlus, close".
|
|
52
|
+
result = run("models/office.dsb", eplus_simulation())
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Other factories: `sbem_calculation`, `export_xml`, `heating_and_cooling_design`,
|
|
56
|
+
`daylighting`, `cfd_simulation`.
|
|
57
|
+
|
|
58
|
+
### Python — non-blocking with idle-detection kill
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from db_process import run_async
|
|
62
|
+
|
|
63
|
+
handle = run_async("models/office.dsb")
|
|
64
|
+
# Block until DesignBuilder finishes its work and CPU drops, then kill it.
|
|
65
|
+
handle.kill_when_idle(idle_threshold=10, cpu_threshold=0.1)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`kill_when_idle` is the workaround for runs where DesignBuilder finishes the
|
|
69
|
+
calculation but doesn't exit — common with the GUI Sim Manager path.
|
|
70
|
+
|
|
71
|
+
### CLI
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
db-process status # is DesignBuilder running? show pid + exe path
|
|
75
|
+
db-process open # launch DesignBuilder
|
|
76
|
+
db-process open path/to.dsb # launch with a model
|
|
77
|
+
db-process close # kill any running DesignBuilder
|
|
78
|
+
db-process restart [model] # close + open (with a 1.5 s settle)
|
|
79
|
+
|
|
80
|
+
db-process diag list # list recent diagnostic logs (newest first)
|
|
81
|
+
db-process diag latest # print a one-paragraph summary of the newest log
|
|
82
|
+
db-process diag latest --errors # also list every error line found
|
|
83
|
+
db-process diag summary <path> # summarise a specific log
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`db-process` is the entry point installed by the package; you can also run it
|
|
87
|
+
as `python -m db_process`.
|
|
88
|
+
|
|
89
|
+
## Public API
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
from db_process import (
|
|
93
|
+
# Executable discovery
|
|
94
|
+
find_designbuilder, # () -> Path
|
|
95
|
+
|
|
96
|
+
# Process status
|
|
97
|
+
is_running, find_process, status, # status() -> ProcessStatus
|
|
98
|
+
kill_process, kill_when_idle,
|
|
99
|
+
ProcessStatus, # is_running, pid, exe_path
|
|
100
|
+
|
|
101
|
+
# Run
|
|
102
|
+
run, run_async, RunHandle, RunResult,
|
|
103
|
+
|
|
104
|
+
# Command model
|
|
105
|
+
ProcessChain, Screen,
|
|
106
|
+
SwitchScreen, RunCalculation, TabChange,
|
|
107
|
+
SimStartDate, SimEndDate, ChangeAttributeValue,
|
|
108
|
+
ExternalCommand, ImportModelData, ImportLibraryData,
|
|
109
|
+
ExportAsXML, UseSimManager, NoClose,
|
|
110
|
+
|
|
111
|
+
# Convenience factories
|
|
112
|
+
eplus_simulation, sbem_calculation, export_xml,
|
|
113
|
+
heating_and_cooling_design, daylighting, cfd_simulation,
|
|
114
|
+
|
|
115
|
+
# Diagnostics logs
|
|
116
|
+
DiagnosticsLog, find_logs, latest_log,
|
|
117
|
+
parse_filename_timestamp, DEFAULT_DIAGNOSTICS_DIR,
|
|
118
|
+
)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Executable discovery
|
|
122
|
+
|
|
123
|
+
`find_designbuilder(exe_path=None)` resolves DesignBuilder.exe in this order:
|
|
124
|
+
|
|
125
|
+
1. The explicit `exe_path` argument, if given.
|
|
126
|
+
2. `DESIGNBUILDER_EXE` environment variable.
|
|
127
|
+
3. `C:\Program Files (x86)\DesignBuilder\DesignBuilder.exe`,
|
|
128
|
+
then `C:\Program Files\DesignBuilder\DesignBuilder.exe`.
|
|
129
|
+
4. The system `PATH` (`shutil.which("DesignBuilder")`).
|
|
130
|
+
|
|
131
|
+
Raises `FileNotFoundError` with a hint to set `DESIGNBUILDER_EXE` if none match.
|
|
132
|
+
|
|
133
|
+
### Process status
|
|
134
|
+
|
|
135
|
+
`status()` is a one-shot snapshot:
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
from db_process import status
|
|
139
|
+
|
|
140
|
+
s = status()
|
|
141
|
+
# ProcessStatus(is_running=True, pid=12345, exe_path='C:\\...\\DesignBuilder.exe')
|
|
142
|
+
if not s.is_running and s.exe_path:
|
|
143
|
+
# Discovered but not running — safe to launch.
|
|
144
|
+
...
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
`exe_path` is populated regardless of whether the process is running, so
|
|
148
|
+
callers can decide whether to launch without a second `find_designbuilder`
|
|
149
|
+
call.
|
|
150
|
+
|
|
151
|
+
### `ProcessChain` — building a `/process=` argument
|
|
152
|
+
|
|
153
|
+
`ProcessChain` is a fluent builder for the `/process=` command sequence
|
|
154
|
+
documented in DesignBuilder Help. Example output of `chain.to_string()`:
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
/process=UseSimManager, miGSS, SimStartDate 1 1, SimEndDate 31 12,
|
|
158
|
+
ChangeAttributeValue OccupancyValue 0.5, miTUpdate
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Every command is also exposed as a plain dataclass, so you can construct
|
|
162
|
+
chains without the builder when that's clearer.
|
|
163
|
+
|
|
164
|
+
### Diagnostics logs
|
|
165
|
+
|
|
166
|
+
DesignBuilder writes a per-session diagnostics log to
|
|
167
|
+
`%LOCALAPPDATA%\DesignBuilder\Diagnostics\` named like
|
|
168
|
+
`DesignBuilder_diagnostic_2026-04-23_15-12-08.txt`. `DiagnosticsLog`
|
|
169
|
+
parses one of those files into a structured object:
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
from db_process import latest_log, DiagnosticsLog
|
|
173
|
+
|
|
174
|
+
log = DiagnosticsLog.from_file(latest_log())
|
|
175
|
+
print(log.summary())
|
|
176
|
+
print(log.is_complete, log.duration, log.error_count)
|
|
177
|
+
for line_no, text in log.errors:
|
|
178
|
+
print(f"L{line_no}: {text}")
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Error detection uses word-boundary matching (`error`, `failed`, `exception`,
|
|
182
|
+
`fatal`, `crash`) plus a small negative list to suppress known benign markers
|
|
183
|
+
(e.g. the `GL_KHR_no_error` OpenGL extension).
|
|
184
|
+
|
|
185
|
+
## Compatibility
|
|
186
|
+
|
|
187
|
+
- **DesignBuilder**: any version that supports the `/process=` argument.
|
|
188
|
+
Tested against the v25.1 / v26.1 lines.
|
|
189
|
+
- **Python**: 3.10+.
|
|
190
|
+
- **OS**: Windows for live runs. The command model and diagnostics parser
|
|
191
|
+
are pure Python and import on any platform — useful for unit tests on
|
|
192
|
+
Linux CI.
|
|
193
|
+
|
|
194
|
+
## Development
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
pip install -e .[dev]
|
|
198
|
+
pytest
|
|
199
|
+
ruff check .
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Tests are split by area:
|
|
203
|
+
|
|
204
|
+
| File | Covers |
|
|
205
|
+
| --- | --- |
|
|
206
|
+
| `test_commands.py` | `ProcessChain` building + every command's `to_string()` |
|
|
207
|
+
| `test_executable.py` | `find_designbuilder` resolution order |
|
|
208
|
+
| `test_status.py` | `status` / `ProcessStatus` |
|
|
209
|
+
| `test_diagnostics.py` | log parsing, error detection, filename timestamps |
|
|
210
|
+
| `test_cli.py` | CLI argparse wiring (mocked subprocess) |
|
|
@@ -36,15 +36,25 @@ from db_process.commands import (
|
|
|
36
36
|
heating_and_cooling_design,
|
|
37
37
|
sbem_calculation,
|
|
38
38
|
)
|
|
39
|
+
from db_process.diagnostics import (
|
|
40
|
+
DEFAULT_DIAGNOSTICS_DIR,
|
|
41
|
+
DiagnosticsLog,
|
|
42
|
+
find_logs,
|
|
43
|
+
latest_log,
|
|
44
|
+
parse_filename_timestamp,
|
|
45
|
+
)
|
|
39
46
|
from db_process.executable import find_designbuilder
|
|
40
47
|
from db_process.runner import (
|
|
48
|
+
ProcessStatus,
|
|
41
49
|
RunHandle,
|
|
42
50
|
RunResult,
|
|
43
51
|
find_process,
|
|
52
|
+
is_running,
|
|
44
53
|
kill_process,
|
|
45
54
|
kill_when_idle,
|
|
46
55
|
run,
|
|
47
56
|
run_async,
|
|
57
|
+
status,
|
|
48
58
|
)
|
|
49
59
|
|
|
50
60
|
__all__ = [
|
|
@@ -72,12 +82,21 @@ __all__ = [
|
|
|
72
82
|
"export_xml",
|
|
73
83
|
"heating_and_cooling_design",
|
|
74
84
|
"sbem_calculation",
|
|
75
|
-
# Runner
|
|
85
|
+
# Runner / status
|
|
76
86
|
"RunHandle",
|
|
77
87
|
"RunResult",
|
|
88
|
+
"ProcessStatus",
|
|
78
89
|
"find_process",
|
|
90
|
+
"is_running",
|
|
79
91
|
"kill_process",
|
|
80
92
|
"kill_when_idle",
|
|
81
93
|
"run",
|
|
82
94
|
"run_async",
|
|
95
|
+
"status",
|
|
96
|
+
# Diagnostics
|
|
97
|
+
"DEFAULT_DIAGNOSTICS_DIR",
|
|
98
|
+
"DiagnosticsLog",
|
|
99
|
+
"find_logs",
|
|
100
|
+
"latest_log",
|
|
101
|
+
"parse_filename_timestamp",
|
|
83
102
|
]
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""db-process CLI.
|
|
2
|
+
|
|
3
|
+
Small command-line wrapper over the existing process-control primitives:
|
|
4
|
+
|
|
5
|
+
db-process status # is DB running? where's the exe?
|
|
6
|
+
db-process close # kill any running DesignBuilder
|
|
7
|
+
db-process open [MODEL.dsb] # launch DB; opens MODEL if given
|
|
8
|
+
db-process restart [MODEL.dsb] # close + open
|
|
9
|
+
|
|
10
|
+
Equivalent to:
|
|
11
|
+
|
|
12
|
+
python -m db_process <subcommand> [args]
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
import subprocess
|
|
19
|
+
import sys
|
|
20
|
+
import time
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
from .diagnostics import DiagnosticsLog, find_logs, latest_log
|
|
24
|
+
from .executable import find_designbuilder
|
|
25
|
+
from .runner import find_process, is_running, kill_process, run_async, status as proc_status
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def cmd_status(args: argparse.Namespace) -> int:
|
|
29
|
+
s = proc_status()
|
|
30
|
+
print(f"DesignBuilder exe : {s.exe_path or '<not found>'}")
|
|
31
|
+
if not s.is_running:
|
|
32
|
+
print("DesignBuilder process : not running")
|
|
33
|
+
return 1
|
|
34
|
+
print(f"DesignBuilder process : pid={s.pid}")
|
|
35
|
+
return 0
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def cmd_close(args: argparse.Namespace) -> int:
|
|
39
|
+
if kill_process():
|
|
40
|
+
print("Closed running DesignBuilder.")
|
|
41
|
+
return 0
|
|
42
|
+
print("No DesignBuilder process to close.")
|
|
43
|
+
return 1
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def cmd_open(args: argparse.Namespace) -> int:
|
|
47
|
+
exe = find_designbuilder()
|
|
48
|
+
if args.model is not None:
|
|
49
|
+
print(f"Opening {args.model} with {exe} ...")
|
|
50
|
+
run_async(str(args.model))
|
|
51
|
+
else:
|
|
52
|
+
print(f"Launching {exe} (no model)...")
|
|
53
|
+
subprocess.Popen([str(exe)])
|
|
54
|
+
return 0
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def cmd_restart(args: argparse.Namespace) -> int:
|
|
58
|
+
if kill_process():
|
|
59
|
+
print(f"Closed existing process; settling for {args.settle}s ...")
|
|
60
|
+
time.sleep(args.settle)
|
|
61
|
+
return cmd_open(args)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def cmd_diag_list(args: argparse.Namespace) -> int:
|
|
65
|
+
paths = find_logs(args.dir)
|
|
66
|
+
if not paths:
|
|
67
|
+
print("No diagnostic logs found.")
|
|
68
|
+
return 1
|
|
69
|
+
for p in paths[: args.limit]:
|
|
70
|
+
size_kb = p.stat().st_size // 1024
|
|
71
|
+
print(f"{p.stat().st_mtime:>13.0f} {size_kb:>5} KB {p.name}")
|
|
72
|
+
return 0
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def cmd_diag_latest(args: argparse.Namespace) -> int:
|
|
76
|
+
path = latest_log(args.dir)
|
|
77
|
+
if path is None:
|
|
78
|
+
print("No diagnostic logs found.")
|
|
79
|
+
return 1
|
|
80
|
+
log = DiagnosticsLog.from_file(path)
|
|
81
|
+
print(log.summary())
|
|
82
|
+
if args.errors and log.errors:
|
|
83
|
+
print("\nError lines:")
|
|
84
|
+
for line_no, text in log.errors:
|
|
85
|
+
print(f" {line_no:5}: {text}")
|
|
86
|
+
return 0
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def cmd_diag_summary(args: argparse.Namespace) -> int:
|
|
90
|
+
log = DiagnosticsLog.from_file(args.path)
|
|
91
|
+
print(log.summary())
|
|
92
|
+
if args.errors and log.errors:
|
|
93
|
+
print("\nError lines:")
|
|
94
|
+
for line_no, text in log.errors:
|
|
95
|
+
print(f" {line_no:5}: {text}")
|
|
96
|
+
return 0
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
100
|
+
p = argparse.ArgumentParser(prog="db-process",
|
|
101
|
+
description="DesignBuilder process control")
|
|
102
|
+
sub = p.add_subparsers(dest="cmd", required=True)
|
|
103
|
+
|
|
104
|
+
sub.add_parser("status", help="Show DesignBuilder exe path and PID").set_defaults(func=cmd_status)
|
|
105
|
+
sub.add_parser("close", help="Kill any running DesignBuilder").set_defaults(func=cmd_close)
|
|
106
|
+
|
|
107
|
+
p_open = sub.add_parser("open", help="Launch DesignBuilder")
|
|
108
|
+
p_open.add_argument("model", nargs="?", type=Path, default=None,
|
|
109
|
+
help="Optional .dsb model to open")
|
|
110
|
+
p_open.set_defaults(func=cmd_open)
|
|
111
|
+
|
|
112
|
+
p_restart = sub.add_parser("restart", help="Close then open DesignBuilder")
|
|
113
|
+
p_restart.add_argument("model", nargs="?", type=Path, default=None,
|
|
114
|
+
help="Optional .dsb model to open after restart")
|
|
115
|
+
p_restart.add_argument("--settle", type=float, default=1.5,
|
|
116
|
+
help="Seconds to wait between close and open (default 1.5)")
|
|
117
|
+
p_restart.set_defaults(func=cmd_restart)
|
|
118
|
+
|
|
119
|
+
# ── diag — diagnostic-log inspection ──────────────────────────────────
|
|
120
|
+
p_diag = sub.add_parser("diag", help="Inspect DesignBuilder diagnostic logs")
|
|
121
|
+
diag_sub = p_diag.add_subparsers(dest="diag_cmd", required=True)
|
|
122
|
+
|
|
123
|
+
p_diag_list = diag_sub.add_parser("list", help="List recent diagnostic logs")
|
|
124
|
+
p_diag_list.add_argument("--dir", type=Path, default=None,
|
|
125
|
+
help="Diagnostics folder (default: LOCALAPPDATA/DesignBuilder/Diagnostics)")
|
|
126
|
+
p_diag_list.add_argument("-n", "--limit", type=int, default=10,
|
|
127
|
+
help="Number of logs to show (default 10)")
|
|
128
|
+
p_diag_list.set_defaults(func=cmd_diag_list)
|
|
129
|
+
|
|
130
|
+
p_diag_latest = diag_sub.add_parser("latest", help="Summarise the newest log")
|
|
131
|
+
p_diag_latest.add_argument("--dir", type=Path, default=None,
|
|
132
|
+
help="Diagnostics folder")
|
|
133
|
+
p_diag_latest.add_argument("--errors", action="store_true",
|
|
134
|
+
help="Also print every error line found")
|
|
135
|
+
p_diag_latest.set_defaults(func=cmd_diag_latest)
|
|
136
|
+
|
|
137
|
+
p_diag_sum = diag_sub.add_parser("summary", help="Summarise a specific log")
|
|
138
|
+
p_diag_sum.add_argument("path", type=Path, help="Path to a diagnostic log")
|
|
139
|
+
p_diag_sum.add_argument("--errors", action="store_true",
|
|
140
|
+
help="Also print every error line found")
|
|
141
|
+
p_diag_sum.set_defaults(func=cmd_diag_summary)
|
|
142
|
+
|
|
143
|
+
return p
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def main(argv: list[str] | None = None) -> int:
|
|
147
|
+
args = build_parser().parse_args(argv)
|
|
148
|
+
return args.func(args)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
if __name__ == "__main__":
|
|
152
|
+
sys.exit(main())
|