opencode-pyneruntime 6.6.4__py3-none-any.whl

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 (261) hide show
  1. opencode_pyneruntime-6.6.4.dist-info/METADATA +281 -0
  2. opencode_pyneruntime-6.6.4.dist-info/RECORD +261 -0
  3. opencode_pyneruntime-6.6.4.dist-info/WHEEL +5 -0
  4. opencode_pyneruntime-6.6.4.dist-info/entry_points.txt +6 -0
  5. opencode_pyneruntime-6.6.4.dist-info/licenses/LICENSE +201 -0
  6. opencode_pyneruntime-6.6.4.dist-info/licenses/NOTICE +21 -0
  7. opencode_pyneruntime-6.6.4.dist-info/top_level.txt +1 -0
  8. pynecore/__init__.py +6 -0
  9. pynecore/cli/__init__.py +2 -0
  10. pynecore/cli/app.py +238 -0
  11. pynecore/cli/commands/__init__.py +343 -0
  12. pynecore/cli/commands/benchmark.py +186 -0
  13. pynecore/cli/commands/compile.py +198 -0
  14. pynecore/cli/commands/data.py +857 -0
  15. pynecore/cli/commands/debug.py +63 -0
  16. pynecore/cli/commands/optimize.py +956 -0
  17. pynecore/cli/commands/plugin.py +242 -0
  18. pynecore/cli/commands/run.py +2006 -0
  19. pynecore/cli/pluggable.py +132 -0
  20. pynecore/cli/utils/__init__.py +0 -0
  21. pynecore/cli/utils/api_error_handler.py +168 -0
  22. pynecore/cli/utils/broker_picker.py +330 -0
  23. pynecore/cli/utils/error_hook.py +28 -0
  24. pynecore/cli/utils/keyreader.py +178 -0
  25. pynecore/cli/utils/provider_picker.py +19 -0
  26. pynecore/cli/utils/symbol_browser.py +1149 -0
  27. pynecore/core/__init__.py +0 -0
  28. pynecore/core/aggregator.py +257 -0
  29. pynecore/core/bar_magnifier.py +168 -0
  30. pynecore/core/broker/__init__.py +64 -0
  31. pynecore/core/broker/defaults.py +113 -0
  32. pynecore/core/broker/disappearance.py +927 -0
  33. pynecore/core/broker/emulator.py +345 -0
  34. pynecore/core/broker/exceptions.py +346 -0
  35. pynecore/core/broker/idempotency.py +401 -0
  36. pynecore/core/broker/intent_builder.py +334 -0
  37. pynecore/core/broker/journal.py +1785 -0
  38. pynecore/core/broker/models.py +1600 -0
  39. pynecore/core/broker/native_failsafe_manager.py +1436 -0
  40. pynecore/core/broker/one_way_emulator.py +1128 -0
  41. pynecore/core/broker/position.py +787 -0
  42. pynecore/core/broker/run_identity.py +126 -0
  43. pynecore/core/broker/software_entry_stop_engine.py +351 -0
  44. pynecore/core/broker/software_partial_bracket_engine.py +1379 -0
  45. pynecore/core/broker/spot_inventory.py +1327 -0
  46. pynecore/core/broker/storage.py +2655 -0
  47. pynecore/core/broker/store_helpers.py +2161 -0
  48. pynecore/core/broker/sync_engine.py +16070 -0
  49. pynecore/core/broker/validation.py +382 -0
  50. pynecore/core/class_property.py +7 -0
  51. pynecore/core/config.py +392 -0
  52. pynecore/core/csv_file.py +547 -0
  53. pynecore/core/currency.py +262 -0
  54. pynecore/core/data_converter.py +1002 -0
  55. pynecore/core/datetime.py +296 -0
  56. pynecore/core/download_info.py +71 -0
  57. pynecore/core/download_runner.py +274 -0
  58. pynecore/core/htf_aggregator.py +181 -0
  59. pynecore/core/import_hook.py +358 -0
  60. pynecore/core/instance_state.py +494 -0
  61. pynecore/core/live_ltf_collector.py +442 -0
  62. pynecore/core/live_ltf_window.py +189 -0
  63. pynecore/core/live_runner.py +1347 -0
  64. pynecore/core/module_property.py +26 -0
  65. pynecore/core/ohlcv_file.py +1888 -0
  66. pynecore/core/overload.py +371 -0
  67. pynecore/core/pine_cast.py +113 -0
  68. pynecore/core/pine_export.py +95 -0
  69. pynecore/core/pine_method.py +244 -0
  70. pynecore/core/pine_range.py +86 -0
  71. pynecore/core/pine_udt.py +69 -0
  72. pynecore/core/plugin/__init__.py +394 -0
  73. pynecore/core/plugin/broker.py +781 -0
  74. pynecore/core/plugin/cli.py +96 -0
  75. pynecore/core/plugin/live_provider.py +208 -0
  76. pynecore/core/plugin/provider.py +331 -0
  77. pynecore/core/provider_string.py +148 -0
  78. pynecore/core/random.py +40 -0
  79. pynecore/core/resampler.py +686 -0
  80. pynecore/core/safe_convert.py +64 -0
  81. pynecore/core/script.py +1011 -0
  82. pynecore/core/script_runner.py +3202 -0
  83. pynecore/core/security.py +1749 -0
  84. pynecore/core/security_process.py +1253 -0
  85. pynecore/core/security_shm.py +456 -0
  86. pynecore/core/series.py +417 -0
  87. pynecore/core/strategy_stats.py +669 -0
  88. pynecore/core/symbol_map.py +134 -0
  89. pynecore/core/syminfo.py +505 -0
  90. pynecore/core/viz.py +591 -0
  91. pynecore/lib/__init__.py +1771 -0
  92. pynecore/lib/_fixnan.py +32 -0
  93. pynecore/lib/_math_stateful.py +202 -0
  94. pynecore/lib/_timeframe_change.py +101 -0
  95. pynecore/lib/adjustment.py +6 -0
  96. pynecore/lib/alert.py +39 -0
  97. pynecore/lib/alert.pyi +14 -0
  98. pynecore/lib/array.py +1051 -0
  99. pynecore/lib/barmerge.py +60 -0
  100. pynecore/lib/barstate.py +30 -0
  101. pynecore/lib/box.py +415 -0
  102. pynecore/lib/chart.py +128 -0
  103. pynecore/lib/color.py +152 -0
  104. pynecore/lib/color.pyi +50 -0
  105. pynecore/lib/currency.py +62 -0
  106. pynecore/lib/dayofweek.py +36 -0
  107. pynecore/lib/dayofweek.pyi +18 -0
  108. pynecore/lib/display.py +8 -0
  109. pynecore/lib/dividends.py +9 -0
  110. pynecore/lib/earnings.py +11 -0
  111. pynecore/lib/extend.py +6 -0
  112. pynecore/lib/font.py +5 -0
  113. pynecore/lib/footprint.py +79 -0
  114. pynecore/lib/format.py +11 -0
  115. pynecore/lib/hline.py +67 -0
  116. pynecore/lib/hline.pyi +24 -0
  117. pynecore/lib/label.py +409 -0
  118. pynecore/lib/line.py +433 -0
  119. pynecore/lib/linefill.py +93 -0
  120. pynecore/lib/location.py +11 -0
  121. pynecore/lib/log.py +362 -0
  122. pynecore/lib/map.py +150 -0
  123. pynecore/lib/math.py +385 -0
  124. pynecore/lib/matrix.py +708 -0
  125. pynecore/lib/order.py +8 -0
  126. pynecore/lib/pivotpointtype.py +8 -0
  127. pynecore/lib/plot.py +95 -0
  128. pynecore/lib/plot.pyi +33 -0
  129. pynecore/lib/polyline.py +91 -0
  130. pynecore/lib/position.py +15 -0
  131. pynecore/lib/request.py +281 -0
  132. pynecore/lib/runtime.py +5 -0
  133. pynecore/lib/scale.py +9 -0
  134. pynecore/lib/session.py +267 -0
  135. pynecore/lib/session.pyi +12 -0
  136. pynecore/lib/shape.py +18 -0
  137. pynecore/lib/size.py +12 -0
  138. pynecore/lib/splits.py +4 -0
  139. pynecore/lib/strategy/__init__.py +4778 -0
  140. pynecore/lib/strategy/closedtrades.py +347 -0
  141. pynecore/lib/strategy/closedtrades.pyi +53 -0
  142. pynecore/lib/strategy/commission.py +9 -0
  143. pynecore/lib/strategy/direction.py +9 -0
  144. pynecore/lib/strategy/oca.py +13 -0
  145. pynecore/lib/strategy/opentrades.py +281 -0
  146. pynecore/lib/strategy/opentrades.pyi +49 -0
  147. pynecore/lib/strategy/risk.py +109 -0
  148. pynecore/lib/string.py +649 -0
  149. pynecore/lib/syminfo.py +84 -0
  150. pynecore/lib/ta.py +2230 -0
  151. pynecore/lib/table.py +290 -0
  152. pynecore/lib/text.py +17 -0
  153. pynecore/lib/ticker.py +207 -0
  154. pynecore/lib/timeframe.py +293 -0
  155. pynecore/lib/volume_row.py +67 -0
  156. pynecore/lib/xloc.py +4 -0
  157. pynecore/lib/yloc.py +5 -0
  158. pynecore/providers/__init__.py +0 -0
  159. pynecore/providers/ccxt.py +664 -0
  160. pynecore/providers/replay.py +187 -0
  161. pynecore/pynesys/__init__.py +0 -0
  162. pynecore/pynesys/api.py +498 -0
  163. pynecore/pynesys/compiler.py +112 -0
  164. pynecore/standalone.py +99 -0
  165. pynecore/testing/__init__.py +1 -0
  166. pynecore/testing/broker_lab/__init__.py +41 -0
  167. pynecore/testing/broker_lab/__main__.py +5 -0
  168. pynecore/testing/broker_lab/cli.py +87 -0
  169. pynecore/testing/broker_lab/generate.py +47 -0
  170. pynecore/testing/broker_lab/model.py +84 -0
  171. pynecore/testing/broker_lab/reference.py +645 -0
  172. pynecore/testing/broker_lab/runner.py +372 -0
  173. pynecore/testing/broker_lab/scheduler.py +50 -0
  174. pynecore/testing/broker_lab/subprocess.py +73 -0
  175. pynecore/transformers/__init__.py +0 -0
  176. pynecore/transformers/builtin_shadow.py +136 -0
  177. pynecore/transformers/closure_arguments_transformer.py +428 -0
  178. pynecore/transformers/display_rewrite.py +140 -0
  179. pynecore/transformers/dynamic_default.py +147 -0
  180. pynecore/transformers/function_isolation.py +757 -0
  181. pynecore/transformers/import_lifter.py +61 -0
  182. pynecore/transformers/import_normalizer.py +328 -0
  183. pynecore/transformers/inline_series_hoist.py +178 -0
  184. pynecore/transformers/input_transformer.py +175 -0
  185. pynecore/transformers/lib_series.py +201 -0
  186. pynecore/transformers/locations.py +70 -0
  187. pynecore/transformers/module_properties.json +3387 -0
  188. pynecore/transformers/module_property.py +221 -0
  189. pynecore/transformers/ne_guard.py +70 -0
  190. pynecore/transformers/persistent.py +320 -0
  191. pynecore/transformers/persistent_series.py +76 -0
  192. pynecore/transformers/safe_convert_transformer.py +97 -0
  193. pynecore/transformers/safe_division_transformer.py +95 -0
  194. pynecore/transformers/script_requirements.py +308 -0
  195. pynecore/transformers/security.py +752 -0
  196. pynecore/transformers/security_instantiation.py +274 -0
  197. pynecore/transformers/series.py +275 -0
  198. pynecore/transformers/slot_layout.py +381 -0
  199. pynecore/transformers/type_checking_stripper.py +25 -0
  200. pynecore/transformers/unused_series_detector.py +267 -0
  201. pynecore/types/__init__.py +21 -0
  202. pynecore/types/alert.py +5 -0
  203. pynecore/types/barmerge.py +5 -0
  204. pynecore/types/base.py +39 -0
  205. pynecore/types/box.py +37 -0
  206. pynecore/types/chart.py +17 -0
  207. pynecore/types/color.py +107 -0
  208. pynecore/types/currency.py +5 -0
  209. pynecore/types/datetime.py +6 -0
  210. pynecore/types/display.py +5 -0
  211. pynecore/types/dividends.py +5 -0
  212. pynecore/types/earnings.py +5 -0
  213. pynecore/types/extend.py +5 -0
  214. pynecore/types/font.py +5 -0
  215. pynecore/types/footprint.py +41 -0
  216. pynecore/types/format.py +5 -0
  217. pynecore/types/hline.py +24 -0
  218. pynecore/types/ib_persistent.py +8 -0
  219. pynecore/types/ib_persistent.pyi +10 -0
  220. pynecore/types/label.py +35 -0
  221. pynecore/types/line.py +32 -0
  222. pynecore/types/linefill.py +13 -0
  223. pynecore/types/location.py +5 -0
  224. pynecore/types/matrix.py +999 -0
  225. pynecore/types/na.py +237 -0
  226. pynecore/types/na.pyi +83 -0
  227. pynecore/types/ohlcv.py +12 -0
  228. pynecore/types/order.py +5 -0
  229. pynecore/types/persistent.py +8 -0
  230. pynecore/types/persistent.pyi +13 -0
  231. pynecore/types/pine_types.py +11 -0
  232. pynecore/types/pine_types.pyi +15 -0
  233. pynecore/types/pivotpointtype.py +5 -0
  234. pynecore/types/plot.py +12 -0
  235. pynecore/types/plot_meta.py +60 -0
  236. pynecore/types/polyline.py +40 -0
  237. pynecore/types/position.py +5 -0
  238. pynecore/types/scale.py +5 -0
  239. pynecore/types/script_type.py +15 -0
  240. pynecore/types/series.py +23 -0
  241. pynecore/types/series.pyi +19 -0
  242. pynecore/types/session.py +35 -0
  243. pynecore/types/shape.py +5 -0
  244. pynecore/types/size.py +5 -0
  245. pynecore/types/source.py +33 -0
  246. pynecore/types/splits.py +5 -0
  247. pynecore/types/strategy.py +45 -0
  248. pynecore/types/table.py +87 -0
  249. pynecore/types/text.py +13 -0
  250. pynecore/types/type_checker.py +7 -0
  251. pynecore/types/type_checker.pyi +48 -0
  252. pynecore/types/volume_row.py +36 -0
  253. pynecore/types/weekdays.py +11 -0
  254. pynecore/types/xloc.py +5 -0
  255. pynecore/types/yloc.py +5 -0
  256. pynecore/utils/__init__.py +0 -0
  257. pynecore/utils/file_utils.py +50 -0
  258. pynecore/utils/rich/__init__.py +0 -0
  259. pynecore/utils/rich/date_column.py +25 -0
  260. pynecore/utils/sequence_view.py +92 -0
  261. pynecore/utils/stdlib_checker.py +17 -0
@@ -0,0 +1,242 @@
1
+ from typer import Typer, Option, Argument, Exit, secho, colors
2
+
3
+ from ..app import app
4
+
5
+ __all__ = []
6
+
7
+ app_plugin = Typer(help="Plugin management commands")
8
+ app.add_typer(app_plugin, name="plugin")
9
+
10
+
11
+ def _get_capabilities(cls: type) -> list[str]:
12
+ """Determine plugin capabilities from its class hierarchy."""
13
+ from ...core.plugin import ProviderPlugin, CLIPlugin
14
+ from ...core.plugin.broker import BrokerPlugin
15
+
16
+ caps = []
17
+ if isinstance(cls, type) and issubclass(cls, ProviderPlugin):
18
+ caps.append('provider')
19
+ if isinstance(cls, type) and issubclass(cls, BrokerPlugin):
20
+ caps.append('broker')
21
+ if isinstance(cls, type) and issubclass(cls, CLIPlugin):
22
+ caps.append('cli')
23
+ return caps
24
+
25
+
26
+ @app_plugin.command("list")
27
+ def list_plugins(
28
+ plugin_type: str = Option(
29
+ None, '--type', '-t',
30
+ help="Filter by capability (e.g. 'provider', 'cli')",
31
+ ),
32
+ as_json: bool = Option(
33
+ False, '--json',
34
+ help="Machine readable output for tooling",
35
+ ),
36
+ ):
37
+ """
38
+ List all installed PyneCore plugins.
39
+ """
40
+ from ...core.plugin import (
41
+ discover_plugin_entry_points, get_plugin_metadata, get_plugin_package, get_plugin_summary
42
+ )
43
+
44
+ plugins = discover_plugin_entry_points()
45
+ if not plugins and not as_json:
46
+ secho("No plugins installed.", fg=colors.YELLOW)
47
+ secho("")
48
+ return
49
+
50
+ # Collect data first to calculate column widths. A plugin name may be
51
+ # declared by several packages — list every one of them, flagged.
52
+ rows = []
53
+ errors = []
54
+ for name, eps in sorted(plugins.items()):
55
+ conflict = len(eps) > 1
56
+ for ep in eps:
57
+ try:
58
+ cls = ep.load()
59
+ meta = get_plugin_metadata(ep)
60
+ caps = _get_capabilities(cls)
61
+
62
+ if plugin_type and plugin_type not in caps:
63
+ continue
64
+
65
+ display_name = getattr(cls, 'plugin_name', '') or name
66
+ version = f"v{meta['version']}" if meta['version'] else ''
67
+ caps_str = ', '.join(caps) if caps else 'library'
68
+ summary = get_plugin_summary(cls) or meta['description']
69
+ rows.append((name, display_name, version, caps_str, summary,
70
+ meta['package'], conflict))
71
+ except Exception as e:
72
+ errors.append((f"{name} ({get_plugin_package(ep)})" if conflict else name, str(e)))
73
+
74
+ if as_json:
75
+ import json
76
+ secho(json.dumps({
77
+ 'plugins': [
78
+ {
79
+ 'name': name,
80
+ 'display_name': display_name,
81
+ 'version': version.lstrip('v'),
82
+ 'capabilities': caps_str.split(', '),
83
+ 'summary': summary,
84
+ 'package': package,
85
+ 'conflict': conflict,
86
+ }
87
+ for name, display_name, version, caps_str, summary, package, conflict in rows
88
+ ],
89
+ 'errors': [{'name': name, 'error': error} for name, error in errors],
90
+ }))
91
+ return
92
+
93
+ if not rows and not errors:
94
+ secho("No plugins found for the given filter.", fg=colors.YELLOW)
95
+ secho("")
96
+ return
97
+
98
+ # Calculate column widths
99
+ w_name = max((len(r[0]) for r in rows), default=0)
100
+ w_disp = max((len(r[1]) for r in rows), default=0)
101
+ w_ver = max((len(r[2]) for r in rows), default=0)
102
+
103
+ secho(f"\n Installed plugins:\n", fg=colors.BRIGHT_WHITE, bold=True)
104
+
105
+ indent = 4 + w_name + 3
106
+ conflicting: dict[str, list[str]] = {}
107
+ for name, display_name, version, caps_str, summary, package, conflict in rows:
108
+ prefix = " ! " if conflict else " "
109
+ secho(f"{prefix}{name:<{w_name}} {display_name:<{w_disp}} {version:<{w_ver}} [{caps_str}]",
110
+ fg=colors.YELLOW if conflict else None)
111
+ if summary:
112
+ secho(f"{' ' * indent}└─ {summary}", dim=True)
113
+ if conflict:
114
+ conflicting.setdefault(name, []).append(package)
115
+
116
+ for name, error in errors:
117
+ secho(f" {name:<{w_name}} (failed to load: {error})", fg=colors.RED)
118
+
119
+ if conflicting:
120
+ secho("")
121
+ secho(" ! Name conflicts:", fg=colors.YELLOW, bold=True)
122
+ for name, packages in sorted(conflicting.items()):
123
+ secho(f" '{name}' is declared by: {', '.join(packages)}", fg=colors.YELLOW)
124
+ secho(" Loading such a plugin fails until all but one package is uninstalled.", dim=True)
125
+
126
+ secho("")
127
+ secho(" Use 'pyne plugin info <name>' for details.", dim=True)
128
+ secho("")
129
+
130
+
131
+ @app_plugin.command("info")
132
+ def plugin_info(
133
+ name: str = Argument(..., help="Plugin name (e.g. 'ccxt', 'capitalcom')"),
134
+ ):
135
+ """
136
+ Show detailed information about an installed plugin.
137
+ """
138
+ from ...core.plugin import (
139
+ discover_plugin_entry_points, get_plugin_metadata, get_plugin_description, get_plugin_package
140
+ )
141
+ from rich.console import Console
142
+ from rich.markdown import Markdown
143
+ from rich.padding import Padding
144
+ import dataclasses
145
+
146
+ plugins = discover_plugin_entry_points()
147
+ candidates = plugins.get(name)
148
+ if not candidates:
149
+ secho(f"Plugin '{name}' is not installed.", fg=colors.RED, err=True)
150
+ secho(f"Available plugins: {', '.join(sorted(plugins)) or '(none)'}", fg=colors.YELLOW, err=True)
151
+ raise Exit(1)
152
+
153
+ if len(candidates) > 1:
154
+ secho(f"Plugin name '{name}' is ambiguous — declared by {len(candidates)} installed packages:",
155
+ fg=colors.RED, err=True)
156
+ for candidate in candidates:
157
+ secho(f" {get_plugin_package(candidate)} ({candidate.value})", fg=colors.YELLOW, err=True)
158
+ secho("Uninstall all but the one you want to use.", fg=colors.YELLOW, err=True)
159
+ raise Exit(1)
160
+
161
+ ep = candidates[0]
162
+ try:
163
+ cls = ep.load()
164
+ except Exception as e:
165
+ secho(f"Failed to load plugin '{name}': {e}", fg=colors.RED, err=True)
166
+ raise Exit(1)
167
+
168
+ meta = get_plugin_metadata(ep)
169
+ caps = _get_capabilities(cls)
170
+
171
+ secho(f"\n Plugin: {name}", fg=colors.BRIGHT_WHITE, bold=True)
172
+ secho(f" Package: {meta['package']}")
173
+ secho(f" Version: {meta['version'] or 'unknown'}")
174
+ secho(f" Description: {meta['description'] or '-'}")
175
+ secho(f" Needs PyneCore: {meta['requires_pynecore'] or 'any'}")
176
+ secho(f" Capabilities: {', '.join(caps) if caps else 'library'}")
177
+ secho(f" Entry point: {ep.value}")
178
+
179
+ description = get_plugin_description(cls)
180
+ if description:
181
+ secho("\n Details:", fg=colors.BRIGHT_WHITE, bold=True)
182
+ Console().print(Padding(Markdown(description), (0, 0, 0, 2)))
183
+
184
+ if 'broker' in caps:
185
+ from rich.table import Table
186
+ from rich import box
187
+ from ...core.broker.models import CapabilityLevel
188
+
189
+ # ``get_capabilities`` is an instance method but well-behaved broker
190
+ # plugins return a static dataclass with no I/O. Try a default
191
+ # construction first; if the plugin requires its Config dataclass
192
+ # (e.g. Capital.com asserts on it), retry with a default-constructed
193
+ # Config. Fall back to a warning if even that fails — long-term fix
194
+ # is to make ``get_capabilities`` a classmethod.
195
+ ex_caps = None
196
+ intro_error: Exception | None = None
197
+ try:
198
+ ex_caps = cls().get_capabilities() # type: ignore[call-arg]
199
+ except Exception as e1:
200
+ intro_error = e1
201
+ cfg_cls = getattr(cls, 'Config', None)
202
+ if cfg_cls is not None and dataclasses.is_dataclass(cfg_cls):
203
+ try:
204
+ ex_caps = cls(config=cfg_cls()).get_capabilities() # type: ignore[call-arg]
205
+ intro_error = None
206
+ except Exception as e2:
207
+ intro_error = e2
208
+ if intro_error is not None:
209
+ secho(
210
+ f"\n Exchange Capabilities: (cannot introspect: {intro_error})",
211
+ fg=colors.YELLOW,
212
+ )
213
+ else:
214
+ assert ex_caps is not None
215
+ secho("\n Exchange Capabilities:", fg=colors.BRIGHT_WHITE, bold=True)
216
+ level_color = {
217
+ CapabilityLevel.NATIVE: "green",
218
+ CapabilityLevel.PARTIAL_NATIVE: "yellow",
219
+ CapabilityLevel.SOFTWARE: "cyan",
220
+ CapabilityLevel.UNSUPPORTED: "dim white",
221
+ }
222
+ table = Table(box=box.SIMPLE, show_header=False, padding=(0, 2, 0, 2))
223
+ table.add_column("Capability", style="white")
224
+ table.add_column("Level")
225
+ # noinspection PyDataclass
226
+ for f in dataclasses.fields(ex_caps):
227
+ value = getattr(ex_caps, f.name)
228
+ colour = level_color.get(value, "cyan")
229
+ table.add_row(f.name, f"[{colour}]{value.value}[/{colour}]")
230
+ Console().print(Padding(table, (0, 0, 0, 2)))
231
+
232
+ config_cls: type | None = getattr(cls, 'Config', None)
233
+ if config_cls is not None and dataclasses.is_dataclass(config_cls):
234
+ # noinspection PyDataclass
235
+ fields = dataclasses.fields(config_cls)
236
+ if fields:
237
+ secho(f"\n Config fields (defaults):")
238
+ for f in fields:
239
+ default = f"= {f.default!r}" if f.default is not dataclasses.MISSING else "(required)"
240
+ secho(f" {f.name:20s} {default}")
241
+
242
+ secho("")