python-tty 0.2.0__tar.gz → 0.2.2__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.
Files changed (106) hide show
  1. python_tty-0.2.2/PKG-INFO +290 -0
  2. python_tty-0.2.2/README.md +259 -0
  3. python_tty-0.2.2/README_zh.md +259 -0
  4. {python_tty-0.2.0 → python_tty-0.2.2}/docs/LOG.md +51 -0
  5. python_tty-0.2.2/docs/Schema.md +155 -0
  6. python_tty-0.2.2/src/python_tty/__init__.py +69 -0
  7. python_tty-0.2.2/src/python_tty/executor/__init__.py +29 -0
  8. python_tty-0.2.2/src/python_tty/testing/__init__.py +12 -0
  9. python_tty-0.2.2/src/python_tty/testing/capture.py +72 -0
  10. python_tty-0.2.2/src/python_tty/testing/decorators.py +67 -0
  11. python_tty-0.2.2/src/python_tty/testing/discovery.py +263 -0
  12. python_tty-0.2.2/src/python_tty/testing/harness.py +295 -0
  13. python_tty-0.2.2/src/python_tty/testing/results.py +17 -0
  14. python_tty-0.2.2/src/python_tty.egg-info/PKG-INFO +290 -0
  15. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty.egg-info/SOURCES.txt +9 -1
  16. python_tty-0.2.2/tests/test_testing_api_mvp.py +278 -0
  17. python_tty-0.2.0/PKG-INFO +0 -215
  18. python_tty-0.2.0/README.md +0 -184
  19. python_tty-0.2.0/README_zh.md +0 -184
  20. python_tty-0.2.0/src/python_tty/__init__.py +0 -25
  21. python_tty-0.2.0/src/python_tty/executor/__init__.py +0 -10
  22. python_tty-0.2.0/src/python_tty.egg-info/PKG-INFO +0 -215
  23. {python_tty-0.2.0 → python_tty-0.2.2}/.github/workflows/python-publish.yml +0 -0
  24. {python_tty-0.2.0 → python_tty-0.2.2}/.gitignore +0 -0
  25. {python_tty-0.2.0 → python_tty-0.2.2}/LICENSE +0 -0
  26. {python_tty-0.2.0 → python_tty-0.2.2}/MANIFEST.in +0 -0
  27. {python_tty-0.2.0 → python_tty-0.2.2}/NOTICE +0 -0
  28. {python_tty-0.2.0 → python_tty-0.2.2}/demos/__init__.py +0 -0
  29. {python_tty-0.2.0 → python_tty-0.2.2}/demos/chat_room/__init__.py +0 -0
  30. {python_tty-0.2.0 → python_tty-0.2.2}/demos/file_manager/__init__.py +0 -0
  31. {python_tty-0.2.0 → python_tty-0.2.2}/demos/file_manager/commands/__init__.py +0 -0
  32. {python_tty-0.2.0 → python_tty-0.2.2}/demos/file_manager/commands/root_commands.py +0 -0
  33. {python_tty-0.2.0 → python_tty-0.2.2}/demos/file_manager/consoles/__init__.py +0 -0
  34. {python_tty-0.2.0 → python_tty-0.2.2}/demos/file_manager/consoles/root.py +0 -0
  35. {python_tty-0.2.0 → python_tty-0.2.2}/demos/file_manager/core/__init__.py +0 -0
  36. {python_tty-0.2.0 → python_tty-0.2.2}/demos/file_manager/core/file_manager.py +0 -0
  37. {python_tty-0.2.0 → python_tty-0.2.2}/demos/file_manager/exceptions/__init__.py +0 -0
  38. {python_tty-0.2.0 → python_tty-0.2.2}/demos/file_manager/exceptions/console_exception.py +0 -0
  39. {python_tty-0.2.0 → python_tty-0.2.2}/demos/file_manager/main.py +0 -0
  40. {python_tty-0.2.0 → python_tty-0.2.2}/demos/file_manager/setup.py +0 -0
  41. {python_tty-0.2.0 → python_tty-0.2.2}/demos/file_manager/utils/__init__.py +0 -0
  42. {python_tty-0.2.0 → python_tty-0.2.2}/demos/file_manager/utils/table.py +0 -0
  43. {python_tty-0.2.0 → python_tty-0.2.2}/docs/context.md +0 -0
  44. {python_tty-0.2.0 → python_tty-0.2.2}/pyproject.toml +0 -0
  45. {python_tty-0.2.0 → python_tty-0.2.2}/requirements.txt +0 -0
  46. {python_tty-0.2.0 → python_tty-0.2.2}/setup.cfg +0 -0
  47. {python_tty-0.2.0 → python_tty-0.2.2}/setup.py +0 -0
  48. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/audit/__init__.py +0 -0
  49. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/audit/sink.py +0 -0
  50. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/audit/ui_logger.py +0 -0
  51. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/commands/__init__.py +0 -0
  52. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/commands/core.py +0 -0
  53. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/commands/decorators.py +0 -0
  54. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/commands/examples/__init__.py +0 -0
  55. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/commands/examples/root_commands.py +0 -0
  56. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/commands/examples/sub_commands.py +0 -0
  57. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/commands/general.py +0 -0
  58. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/commands/mixins.py +0 -0
  59. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/commands/registry.py +0 -0
  60. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/config/__init__.py +0 -0
  61. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/config/config.py +0 -0
  62. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/console_factory.py +0 -0
  63. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/consoles/__init__.py +0 -0
  64. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/consoles/core.py +0 -0
  65. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/consoles/decorators.py +0 -0
  66. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/consoles/examples/__init__.py +0 -0
  67. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/consoles/examples/root_console.py +0 -0
  68. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/consoles/examples/sub_console.py +0 -0
  69. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/consoles/loader.py +0 -0
  70. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/consoles/manager.py +0 -0
  71. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/consoles/registry.py +0 -0
  72. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/exceptions/__init__.py +0 -0
  73. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/exceptions/console_exception.py +0 -0
  74. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/executor/execution.py +0 -0
  75. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/executor/executor.py +0 -0
  76. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/executor/models.py +0 -0
  77. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/frontends/__init__.py +0 -0
  78. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/frontends/rpc/__init__.py +0 -0
  79. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/frontends/rpc/core.py +0 -0
  80. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/frontends/rpc/proto/runtime.proto +0 -0
  81. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/frontends/rpc/server.py +0 -0
  82. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/frontends/web/__init__.py +0 -0
  83. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/frontends/web/core.py +0 -0
  84. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/frontends/web/server.py +0 -0
  85. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/meta/__init__.py +0 -0
  86. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/runtime/__init__.py +0 -0
  87. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/runtime/context.py +0 -0
  88. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/runtime/event_bus.py +0 -0
  89. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/runtime/events.py +0 -0
  90. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/runtime/jobs.py +0 -0
  91. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/runtime/provider.py +0 -0
  92. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/runtime/router.py +0 -0
  93. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/runtime/sinks.py +0 -0
  94. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/session/__init__.py +0 -0
  95. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/session/callbacks.py +0 -0
  96. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/session/manager.py +0 -0
  97. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/session/models.py +0 -0
  98. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/session/policy.py +0 -0
  99. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/session/store.py +0 -0
  100. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/utils/__init__.py +0 -0
  101. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/utils/table.py +0 -0
  102. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty/utils/tokenize.py +0 -0
  103. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty.egg-info/dependency_links.txt +0 -0
  104. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty.egg-info/requires.txt +0 -0
  105. {python_tty-0.2.0 → python_tty-0.2.2}/src/python_tty.egg-info/top_level.txt +0 -0
  106. {python_tty-0.2.0 → python_tty-0.2.2}/tests/__init__.py +0 -0
@@ -0,0 +1,290 @@
1
+ Metadata-Version: 2.4
2
+ Name: python-tty
3
+ Version: 0.2.2
4
+ Summary: A multi-console TTY framework for complex CLI/TTY apps
5
+ Home-page: https://github.com/ROOKIEMIE/python-tty
6
+ Author: ROOKIEMIE
7
+ License: Apache-2.0
8
+ Classifier: License :: OSI Approved :: Apache Software License
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3 :: Only
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ License-File: NOTICE
15
+ Requires-Dist: fastapi>=0.110.0
16
+ Requires-Dist: grpcio>=1.60.0
17
+ Requires-Dist: prompt_toolkit>=3.0.32
18
+ Requires-Dist: protobuf>=4.25.0
19
+ Requires-Dist: tqdm
20
+ Requires-Dist: uvicorn>=0.27.0
21
+ Dynamic: author
22
+ Dynamic: classifier
23
+ Dynamic: description
24
+ Dynamic: description-content-type
25
+ Dynamic: home-page
26
+ Dynamic: license
27
+ Dynamic: license-file
28
+ Dynamic: requires-dist
29
+ Dynamic: requires-python
30
+ Dynamic: summary
31
+
32
+ # Command Line Framework (TTY + Executor + RPC/Web)
33
+
34
+ [中文](README_zh.md)
35
+
36
+ ## Project Introduction
37
+
38
+ `python-tty` is a multi-console command framework centered on TTY interaction, with a shared runtime execution model (`Invocation -> Executor -> RuntimeEvent`) that can be reused by RPC/Web frontends.
39
+
40
+ This repository now includes an official lightweight testing API (`python_tty.testing`) so external projects can test individual command methods without booting the full TTY kernel.
41
+
42
+ Architecture and module-relationship details were moved out of README and are maintained in [docs/Schema.md](docs/Schema.md).
43
+
44
+ ## Quick Start Example
45
+
46
+ ```python
47
+ from python_tty.console_factory import ConsoleFactory
48
+
49
+ # service can be any business object that commands access via self.console.service
50
+ service = object()
51
+
52
+ factory = ConsoleFactory(service=service)
53
+ factory.start()
54
+ ```
55
+
56
+ Typical setup flow:
57
+ 1. Define command classes with `@register_command`.
58
+ 2. Bind command classes to consoles with `@commands`.
59
+ 3. Register console topology with `@root` / `@sub` / `@multi`.
60
+ 4. Start `ConsoleFactory`.
61
+
62
+ Decorator registration example (`commands`, `root`, `sub`, `multi`):
63
+
64
+ ```python
65
+ from prompt_toolkit.styles import Style
66
+
67
+ from python_tty.commands import BaseCommands
68
+ from python_tty.commands.decorators import commands, register_command
69
+ from python_tty.consoles import MainConsole, SubConsole, multi, root, sub
70
+
71
+
72
+ class RootCommands(BaseCommands):
73
+ @register_command("ping", "health check")
74
+ def run_ping(self):
75
+ return "pong"
76
+
77
+
78
+ class ModuleCommands(BaseCommands):
79
+ @register_command("status", "module status")
80
+ def run_status(self):
81
+ return "ok"
82
+
83
+
84
+ @root
85
+ @commands(RootCommands)
86
+ class RootConsole(MainConsole):
87
+ console_name = "root"
88
+ def __init__(self, parent=None, manager=None):
89
+ super().__init__([("class:prompt", "root> ")], Style.from_dict({"": ""}), parent=parent, manager=manager)
90
+
91
+
92
+ @sub("root")
93
+ @commands(ModuleCommands)
94
+ class ModuleConsole(SubConsole):
95
+ console_name = "module"
96
+ def __init__(self, parent=None, manager=None):
97
+ super().__init__([("class:prompt", "module> ")], Style.from_dict({"": ""}), parent=parent, manager=manager)
98
+
99
+
100
+ @multi({"root": "tools", "module": "tools"})
101
+ @commands(ModuleCommands)
102
+ class ToolsConsole(SubConsole):
103
+ # runtime names become root_tools / module_tools
104
+ def __init__(self, parent=None, manager=None):
105
+ super().__init__([("class:prompt", "tools> ")], Style.from_dict({"": ""}), parent=parent, manager=manager)
106
+ ```
107
+
108
+ ## Detailed Configuration Explanation
109
+
110
+ Configuration entry: `python_tty/config/config.py`.
111
+ Top-level type: `Config`.
112
+
113
+ ### ConsoleFactoryConfig
114
+
115
+ - `run_mode`: `"tty"` or `"concurrent"`.
116
+ - `start_executor`: auto start executor during factory startup.
117
+ - `executor_in_thread`: in tty mode, run executor loop in a background thread.
118
+ - `executor_thread_name`: executor thread name.
119
+ - `tty_thread_name`: tty thread name in concurrent mode.
120
+ - `shutdown_executor`: shutdown executor when factory stops.
121
+
122
+ ### ExecutorConfig
123
+
124
+ - `workers`: worker task count.
125
+ - `retain_last_n`: keep only last N completed runs.
126
+ - `ttl_seconds`: TTL for completed runs.
127
+ - `pop_on_wait`: remove run state after wait result.
128
+ - `exempt_exceptions`: exceptions treated as cancellation.
129
+ - `emit_run_events`: emit state events (`start/success/failure/...`).
130
+ - `event_history_max`: max history events per run.
131
+ - `event_history_ttl`: history TTL per run.
132
+ - `sync_in_threadpool`: run sync handlers in threadpool.
133
+ - `threadpool_workers`: threadpool size.
134
+ - `audit`: nested `AuditConfig`.
135
+
136
+ ### AuditConfig
137
+
138
+ - `enabled`: enable audit sink.
139
+ - `file_path`: jsonl output file.
140
+ - `stream`: output stream (mutually exclusive with `file_path`).
141
+ - `async_mode`: async sink writer.
142
+ - `flush_interval`: async flush interval.
143
+ - `keep_in_memory`: keep records in memory for tests.
144
+ - `sink`: custom sink instance.
145
+
146
+ ### RPCConfig
147
+
148
+ - `enabled`: start gRPC server.
149
+ - `bind_host` / `port`: bind address.
150
+ - `max_message_bytes`: gRPC payload limit.
151
+ - `keepalive_time_ms` / `keepalive_timeout_ms` / `keepalive_permit_without_calls`: keepalive controls.
152
+ - `max_concurrent_rpcs`: concurrency limit.
153
+ - `max_streams_per_client`: stream fanout limit.
154
+ - `stream_backpressure_queue_size`: stream queue size.
155
+ - `default_deny`: deny by default when exposure is missing.
156
+ - `require_rpc_exposed`: require `exposure.rpc=True`.
157
+ - `allowed_principals`: principal allowlist.
158
+ - `admin_principals`: admin principal list (allowlist bypass only).
159
+ - `require_audit`: require audit for RPC invoke.
160
+ - `trust_client_principal`: trust request principal directly.
161
+ - `mtls`: nested `MTLSServerConfig`.
162
+
163
+ ### MTLSServerConfig
164
+
165
+ - `enabled`: enable mTLS.
166
+ - `server_cert_file` / `server_key_file`: server cert/key.
167
+ - `client_ca_file`: CA bundle for client cert verification.
168
+ - `require_client_cert`: enforce client cert.
169
+ - `principal_keys`: auth_context keys for principal extraction.
170
+
171
+ ### WebConfig
172
+
173
+ - `enabled`: start web server.
174
+ - `bind_host` / `port`: bind address.
175
+ - `root_path`: reverse proxy root path.
176
+ - `cors_allow_origins` / `cors_allow_credentials` / `cors_allow_methods` / `cors_allow_headers`: CORS controls.
177
+ - `meta_enabled`: enable `/meta` endpoint.
178
+ - `meta_cache_control_max_age`: `/meta` cache-control max-age.
179
+ - `ws_snapshot_enabled`: enable snapshot websocket.
180
+ - `ws_snapshot_include_jobs`: include running jobs in snapshots.
181
+ - `ws_max_connections`: websocket connection limit.
182
+ - `ws_heartbeat_interval`: heartbeat interval.
183
+ - `ws_send_queue_size`: websocket send queue size.
184
+
185
+ ## Testing API Usage Examples
186
+
187
+ Public imports:
188
+
189
+ ```python
190
+ from python_tty.testing import (
191
+ tty_testable,
192
+ discover_tests,
193
+ CommandHarness,
194
+ InvocationHarness,
195
+ TestRunResult,
196
+ )
197
+ ```
198
+
199
+ ### 1. Mark command class or command method
200
+
201
+ ```python
202
+ from python_tty.commands import BaseCommands
203
+ from python_tty.commands.decorators import register_command
204
+ from python_tty.testing import tty_testable
205
+
206
+ @tty_testable(component="smoke")
207
+ class UserCommands(BaseCommands):
208
+ @register_command("ping", "health check")
209
+ def run_ping(self):
210
+ return "pong"
211
+
212
+ class PartialCommands(BaseCommands):
213
+ @tty_testable(component="critical")
214
+ @register_command("login", "login flow")
215
+ def run_login(self, username):
216
+ return username
217
+
218
+ @register_command("debug", "not included unless explicitly selected")
219
+ def run_debug(self):
220
+ return "debug"
221
+ ```
222
+
223
+ ### 2. Explicit discovery (test-only, no startup auto-scan)
224
+
225
+ ```python
226
+ import my_project.commands as command_module
227
+ from python_tty.testing import discover_tests
228
+
229
+ suite = discover_tests(command_module)
230
+ all_cases = suite.list_cases()
231
+
232
+ case = suite.get_case("cmd:usercommands:ping")
233
+
234
+ # explicit selection still works without decorators
235
+ explicit_suite = discover_tests(
236
+ command_module,
237
+ command_ids=["cmd:partialcommands:debug"],
238
+ )
239
+ ```
240
+
241
+ ### 3. Run a command with CommandHarness
242
+
243
+ ```python
244
+ from types import SimpleNamespace
245
+ from python_tty.testing import CommandHarness
246
+
247
+ harness = CommandHarness(service=SimpleNamespace(name="svc"), suite=suite)
248
+ result: TestRunResult = harness.run("cmd:usercommands:ping")
249
+
250
+ assert result.ok
251
+ assert result.return_value == "pong"
252
+ ```
253
+
254
+ ### 4. Toggle validation on/off
255
+
256
+ ```python
257
+ # validator enabled
258
+ result_on = harness.run("cmd:partialcommands:login", argv=[], validate=True)
259
+ assert result_on.ok is False
260
+ assert result_on.validator_ran is True
261
+
262
+ # validator disabled
263
+ result_off = harness.run("cmd:partialcommands:login", argv=[], validate=False)
264
+ assert result_off.validator_ran is False
265
+ ```
266
+
267
+ ### 5. Run through InvocationHarness runtime path
268
+
269
+ ```python
270
+ inv_harness = InvocationHarness(suite=suite)
271
+
272
+ # direct runtime binding execution
273
+ direct_result = inv_harness.run("cmd:usercommands:ping", through_executor=False)
274
+
275
+ # executor-backed execution (captures state/runtime events)
276
+ executor_result = inv_harness.run("cmd:usercommands:ping", through_executor=True)
277
+ ```
278
+
279
+ ### 6. Inspect TestRunResult
280
+
281
+ ```python
282
+ result = inv_harness.run("cmd:usercommands:ping", through_executor=True)
283
+
284
+ print(result.ok)
285
+ print(result.return_value)
286
+ print(result.exception)
287
+ print(result.outputs) # normalized output records
288
+ print(result.runtime_events) # raw RuntimeEvent objects
289
+ print(result.run_id)
290
+ ```
@@ -0,0 +1,259 @@
1
+ # Command Line Framework (TTY + Executor + RPC/Web)
2
+
3
+ [中文](README_zh.md)
4
+
5
+ ## Project Introduction
6
+
7
+ `python-tty` is a multi-console command framework centered on TTY interaction, with a shared runtime execution model (`Invocation -> Executor -> RuntimeEvent`) that can be reused by RPC/Web frontends.
8
+
9
+ This repository now includes an official lightweight testing API (`python_tty.testing`) so external projects can test individual command methods without booting the full TTY kernel.
10
+
11
+ Architecture and module-relationship details were moved out of README and are maintained in [docs/Schema.md](docs/Schema.md).
12
+
13
+ ## Quick Start Example
14
+
15
+ ```python
16
+ from python_tty.console_factory import ConsoleFactory
17
+
18
+ # service can be any business object that commands access via self.console.service
19
+ service = object()
20
+
21
+ factory = ConsoleFactory(service=service)
22
+ factory.start()
23
+ ```
24
+
25
+ Typical setup flow:
26
+ 1. Define command classes with `@register_command`.
27
+ 2. Bind command classes to consoles with `@commands`.
28
+ 3. Register console topology with `@root` / `@sub` / `@multi`.
29
+ 4. Start `ConsoleFactory`.
30
+
31
+ Decorator registration example (`commands`, `root`, `sub`, `multi`):
32
+
33
+ ```python
34
+ from prompt_toolkit.styles import Style
35
+
36
+ from python_tty.commands import BaseCommands
37
+ from python_tty.commands.decorators import commands, register_command
38
+ from python_tty.consoles import MainConsole, SubConsole, multi, root, sub
39
+
40
+
41
+ class RootCommands(BaseCommands):
42
+ @register_command("ping", "health check")
43
+ def run_ping(self):
44
+ return "pong"
45
+
46
+
47
+ class ModuleCommands(BaseCommands):
48
+ @register_command("status", "module status")
49
+ def run_status(self):
50
+ return "ok"
51
+
52
+
53
+ @root
54
+ @commands(RootCommands)
55
+ class RootConsole(MainConsole):
56
+ console_name = "root"
57
+ def __init__(self, parent=None, manager=None):
58
+ super().__init__([("class:prompt", "root> ")], Style.from_dict({"": ""}), parent=parent, manager=manager)
59
+
60
+
61
+ @sub("root")
62
+ @commands(ModuleCommands)
63
+ class ModuleConsole(SubConsole):
64
+ console_name = "module"
65
+ def __init__(self, parent=None, manager=None):
66
+ super().__init__([("class:prompt", "module> ")], Style.from_dict({"": ""}), parent=parent, manager=manager)
67
+
68
+
69
+ @multi({"root": "tools", "module": "tools"})
70
+ @commands(ModuleCommands)
71
+ class ToolsConsole(SubConsole):
72
+ # runtime names become root_tools / module_tools
73
+ def __init__(self, parent=None, manager=None):
74
+ super().__init__([("class:prompt", "tools> ")], Style.from_dict({"": ""}), parent=parent, manager=manager)
75
+ ```
76
+
77
+ ## Detailed Configuration Explanation
78
+
79
+ Configuration entry: `python_tty/config/config.py`.
80
+ Top-level type: `Config`.
81
+
82
+ ### ConsoleFactoryConfig
83
+
84
+ - `run_mode`: `"tty"` or `"concurrent"`.
85
+ - `start_executor`: auto start executor during factory startup.
86
+ - `executor_in_thread`: in tty mode, run executor loop in a background thread.
87
+ - `executor_thread_name`: executor thread name.
88
+ - `tty_thread_name`: tty thread name in concurrent mode.
89
+ - `shutdown_executor`: shutdown executor when factory stops.
90
+
91
+ ### ExecutorConfig
92
+
93
+ - `workers`: worker task count.
94
+ - `retain_last_n`: keep only last N completed runs.
95
+ - `ttl_seconds`: TTL for completed runs.
96
+ - `pop_on_wait`: remove run state after wait result.
97
+ - `exempt_exceptions`: exceptions treated as cancellation.
98
+ - `emit_run_events`: emit state events (`start/success/failure/...`).
99
+ - `event_history_max`: max history events per run.
100
+ - `event_history_ttl`: history TTL per run.
101
+ - `sync_in_threadpool`: run sync handlers in threadpool.
102
+ - `threadpool_workers`: threadpool size.
103
+ - `audit`: nested `AuditConfig`.
104
+
105
+ ### AuditConfig
106
+
107
+ - `enabled`: enable audit sink.
108
+ - `file_path`: jsonl output file.
109
+ - `stream`: output stream (mutually exclusive with `file_path`).
110
+ - `async_mode`: async sink writer.
111
+ - `flush_interval`: async flush interval.
112
+ - `keep_in_memory`: keep records in memory for tests.
113
+ - `sink`: custom sink instance.
114
+
115
+ ### RPCConfig
116
+
117
+ - `enabled`: start gRPC server.
118
+ - `bind_host` / `port`: bind address.
119
+ - `max_message_bytes`: gRPC payload limit.
120
+ - `keepalive_time_ms` / `keepalive_timeout_ms` / `keepalive_permit_without_calls`: keepalive controls.
121
+ - `max_concurrent_rpcs`: concurrency limit.
122
+ - `max_streams_per_client`: stream fanout limit.
123
+ - `stream_backpressure_queue_size`: stream queue size.
124
+ - `default_deny`: deny by default when exposure is missing.
125
+ - `require_rpc_exposed`: require `exposure.rpc=True`.
126
+ - `allowed_principals`: principal allowlist.
127
+ - `admin_principals`: admin principal list (allowlist bypass only).
128
+ - `require_audit`: require audit for RPC invoke.
129
+ - `trust_client_principal`: trust request principal directly.
130
+ - `mtls`: nested `MTLSServerConfig`.
131
+
132
+ ### MTLSServerConfig
133
+
134
+ - `enabled`: enable mTLS.
135
+ - `server_cert_file` / `server_key_file`: server cert/key.
136
+ - `client_ca_file`: CA bundle for client cert verification.
137
+ - `require_client_cert`: enforce client cert.
138
+ - `principal_keys`: auth_context keys for principal extraction.
139
+
140
+ ### WebConfig
141
+
142
+ - `enabled`: start web server.
143
+ - `bind_host` / `port`: bind address.
144
+ - `root_path`: reverse proxy root path.
145
+ - `cors_allow_origins` / `cors_allow_credentials` / `cors_allow_methods` / `cors_allow_headers`: CORS controls.
146
+ - `meta_enabled`: enable `/meta` endpoint.
147
+ - `meta_cache_control_max_age`: `/meta` cache-control max-age.
148
+ - `ws_snapshot_enabled`: enable snapshot websocket.
149
+ - `ws_snapshot_include_jobs`: include running jobs in snapshots.
150
+ - `ws_max_connections`: websocket connection limit.
151
+ - `ws_heartbeat_interval`: heartbeat interval.
152
+ - `ws_send_queue_size`: websocket send queue size.
153
+
154
+ ## Testing API Usage Examples
155
+
156
+ Public imports:
157
+
158
+ ```python
159
+ from python_tty.testing import (
160
+ tty_testable,
161
+ discover_tests,
162
+ CommandHarness,
163
+ InvocationHarness,
164
+ TestRunResult,
165
+ )
166
+ ```
167
+
168
+ ### 1. Mark command class or command method
169
+
170
+ ```python
171
+ from python_tty.commands import BaseCommands
172
+ from python_tty.commands.decorators import register_command
173
+ from python_tty.testing import tty_testable
174
+
175
+ @tty_testable(component="smoke")
176
+ class UserCommands(BaseCommands):
177
+ @register_command("ping", "health check")
178
+ def run_ping(self):
179
+ return "pong"
180
+
181
+ class PartialCommands(BaseCommands):
182
+ @tty_testable(component="critical")
183
+ @register_command("login", "login flow")
184
+ def run_login(self, username):
185
+ return username
186
+
187
+ @register_command("debug", "not included unless explicitly selected")
188
+ def run_debug(self):
189
+ return "debug"
190
+ ```
191
+
192
+ ### 2. Explicit discovery (test-only, no startup auto-scan)
193
+
194
+ ```python
195
+ import my_project.commands as command_module
196
+ from python_tty.testing import discover_tests
197
+
198
+ suite = discover_tests(command_module)
199
+ all_cases = suite.list_cases()
200
+
201
+ case = suite.get_case("cmd:usercommands:ping")
202
+
203
+ # explicit selection still works without decorators
204
+ explicit_suite = discover_tests(
205
+ command_module,
206
+ command_ids=["cmd:partialcommands:debug"],
207
+ )
208
+ ```
209
+
210
+ ### 3. Run a command with CommandHarness
211
+
212
+ ```python
213
+ from types import SimpleNamespace
214
+ from python_tty.testing import CommandHarness
215
+
216
+ harness = CommandHarness(service=SimpleNamespace(name="svc"), suite=suite)
217
+ result: TestRunResult = harness.run("cmd:usercommands:ping")
218
+
219
+ assert result.ok
220
+ assert result.return_value == "pong"
221
+ ```
222
+
223
+ ### 4. Toggle validation on/off
224
+
225
+ ```python
226
+ # validator enabled
227
+ result_on = harness.run("cmd:partialcommands:login", argv=[], validate=True)
228
+ assert result_on.ok is False
229
+ assert result_on.validator_ran is True
230
+
231
+ # validator disabled
232
+ result_off = harness.run("cmd:partialcommands:login", argv=[], validate=False)
233
+ assert result_off.validator_ran is False
234
+ ```
235
+
236
+ ### 5. Run through InvocationHarness runtime path
237
+
238
+ ```python
239
+ inv_harness = InvocationHarness(suite=suite)
240
+
241
+ # direct runtime binding execution
242
+ direct_result = inv_harness.run("cmd:usercommands:ping", through_executor=False)
243
+
244
+ # executor-backed execution (captures state/runtime events)
245
+ executor_result = inv_harness.run("cmd:usercommands:ping", through_executor=True)
246
+ ```
247
+
248
+ ### 6. Inspect TestRunResult
249
+
250
+ ```python
251
+ result = inv_harness.run("cmd:usercommands:ping", through_executor=True)
252
+
253
+ print(result.ok)
254
+ print(result.return_value)
255
+ print(result.exception)
256
+ print(result.outputs) # normalized output records
257
+ print(result.runtime_events) # raw RuntimeEvent objects
258
+ print(result.run_id)
259
+ ```