scitex 2.14.0__py3-none-any.whl → 2.15.3__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 (264) hide show
  1. scitex/__init__.py +71 -17
  2. scitex/_env_loader.py +156 -0
  3. scitex/_mcp_resources/__init__.py +37 -0
  4. scitex/_mcp_resources/_cheatsheet.py +135 -0
  5. scitex/_mcp_resources/_figrecipe.py +138 -0
  6. scitex/_mcp_resources/_formats.py +102 -0
  7. scitex/_mcp_resources/_modules.py +337 -0
  8. scitex/_mcp_resources/_session.py +149 -0
  9. scitex/_mcp_tools/__init__.py +4 -0
  10. scitex/_mcp_tools/audio.py +66 -0
  11. scitex/_mcp_tools/diagram.py +11 -95
  12. scitex/_mcp_tools/introspect.py +210 -0
  13. scitex/_mcp_tools/plt.py +260 -305
  14. scitex/_mcp_tools/scholar.py +74 -0
  15. scitex/_mcp_tools/social.py +27 -0
  16. scitex/_mcp_tools/template.py +24 -0
  17. scitex/_mcp_tools/writer.py +17 -210
  18. scitex/ai/_gen_ai/_PARAMS.py +10 -7
  19. scitex/ai/classification/reporters/_SingleClassificationReporter.py +45 -1603
  20. scitex/ai/classification/reporters/_mixins/__init__.py +36 -0
  21. scitex/ai/classification/reporters/_mixins/_constants.py +67 -0
  22. scitex/ai/classification/reporters/_mixins/_cv_summary.py +387 -0
  23. scitex/ai/classification/reporters/_mixins/_feature_importance.py +119 -0
  24. scitex/ai/classification/reporters/_mixins/_metrics.py +275 -0
  25. scitex/ai/classification/reporters/_mixins/_plotting.py +179 -0
  26. scitex/ai/classification/reporters/_mixins/_reports.py +153 -0
  27. scitex/ai/classification/reporters/_mixins/_storage.py +160 -0
  28. scitex/ai/classification/timeseries/_TimeSeriesSlidingWindowSplit.py +30 -1550
  29. scitex/ai/classification/timeseries/_sliding_window_core.py +467 -0
  30. scitex/ai/classification/timeseries/_sliding_window_plotting.py +369 -0
  31. scitex/audio/README.md +40 -36
  32. scitex/audio/__init__.py +129 -61
  33. scitex/audio/_branding.py +185 -0
  34. scitex/audio/_mcp/__init__.py +32 -0
  35. scitex/audio/_mcp/handlers.py +59 -6
  36. scitex/audio/_mcp/speak_handlers.py +238 -0
  37. scitex/audio/_relay.py +225 -0
  38. scitex/audio/_tts.py +18 -10
  39. scitex/audio/engines/base.py +17 -10
  40. scitex/audio/engines/elevenlabs_engine.py +7 -2
  41. scitex/audio/mcp_server.py +228 -75
  42. scitex/canvas/README.md +1 -1
  43. scitex/canvas/editor/_dearpygui/__init__.py +25 -0
  44. scitex/canvas/editor/_dearpygui/_editor.py +147 -0
  45. scitex/canvas/editor/_dearpygui/_handlers.py +476 -0
  46. scitex/canvas/editor/_dearpygui/_panels/__init__.py +17 -0
  47. scitex/canvas/editor/_dearpygui/_panels/_control.py +119 -0
  48. scitex/canvas/editor/_dearpygui/_panels/_element_controls.py +190 -0
  49. scitex/canvas/editor/_dearpygui/_panels/_preview.py +43 -0
  50. scitex/canvas/editor/_dearpygui/_panels/_sections.py +390 -0
  51. scitex/canvas/editor/_dearpygui/_plotting.py +187 -0
  52. scitex/canvas/editor/_dearpygui/_rendering.py +504 -0
  53. scitex/canvas/editor/_dearpygui/_selection.py +295 -0
  54. scitex/canvas/editor/_dearpygui/_state.py +93 -0
  55. scitex/canvas/editor/_dearpygui/_utils.py +61 -0
  56. scitex/canvas/editor/flask_editor/_core/__init__.py +27 -0
  57. scitex/canvas/editor/flask_editor/_core/_bbox_extraction.py +200 -0
  58. scitex/canvas/editor/flask_editor/_core/_editor.py +173 -0
  59. scitex/canvas/editor/flask_editor/_core/_export_helpers.py +353 -0
  60. scitex/canvas/editor/flask_editor/_core/_routes_basic.py +190 -0
  61. scitex/canvas/editor/flask_editor/_core/_routes_export.py +332 -0
  62. scitex/canvas/editor/flask_editor/_core/_routes_panels.py +252 -0
  63. scitex/canvas/editor/flask_editor/_core/_routes_save.py +218 -0
  64. scitex/canvas/editor/flask_editor/_core.py +25 -1684
  65. scitex/canvas/editor/flask_editor/templates/__init__.py +32 -70
  66. scitex/cli/__init__.py +38 -43
  67. scitex/cli/audio.py +160 -41
  68. scitex/cli/capture.py +133 -20
  69. scitex/cli/introspect.py +488 -0
  70. scitex/cli/main.py +200 -109
  71. scitex/cli/mcp.py +60 -34
  72. scitex/cli/plt.py +414 -0
  73. scitex/cli/repro.py +15 -8
  74. scitex/cli/resource.py +15 -8
  75. scitex/cli/scholar/__init__.py +154 -8
  76. scitex/cli/scholar/_crossref_scitex.py +296 -0
  77. scitex/cli/scholar/_fetch.py +25 -3
  78. scitex/cli/social.py +355 -0
  79. scitex/cli/stats.py +136 -11
  80. scitex/cli/template.py +129 -12
  81. scitex/cli/tex.py +15 -8
  82. scitex/cli/writer.py +49 -299
  83. scitex/cloud/__init__.py +41 -2
  84. scitex/config/README.md +1 -1
  85. scitex/config/__init__.py +16 -2
  86. scitex/config/_env_registry.py +256 -0
  87. scitex/context/__init__.py +22 -0
  88. scitex/dev/__init__.py +20 -1
  89. scitex/diagram/__init__.py +42 -19
  90. scitex/diagram/mcp_server.py +13 -125
  91. scitex/gen/__init__.py +50 -14
  92. scitex/gen/_list_packages.py +4 -4
  93. scitex/introspect/__init__.py +82 -0
  94. scitex/introspect/_call_graph.py +303 -0
  95. scitex/introspect/_class_hierarchy.py +163 -0
  96. scitex/introspect/_core.py +41 -0
  97. scitex/introspect/_docstring.py +131 -0
  98. scitex/introspect/_examples.py +113 -0
  99. scitex/introspect/_imports.py +271 -0
  100. scitex/{gen/_inspect_module.py → introspect/_list_api.py} +48 -56
  101. scitex/introspect/_mcp/__init__.py +41 -0
  102. scitex/introspect/_mcp/handlers.py +233 -0
  103. scitex/introspect/_members.py +155 -0
  104. scitex/introspect/_resolve.py +89 -0
  105. scitex/introspect/_signature.py +131 -0
  106. scitex/introspect/_source.py +80 -0
  107. scitex/introspect/_type_hints.py +172 -0
  108. scitex/io/_save.py +1 -2
  109. scitex/io/bundle/README.md +1 -1
  110. scitex/logging/_formatters.py +19 -9
  111. scitex/mcp_server.py +98 -5
  112. scitex/os/__init__.py +4 -0
  113. scitex/{gen → os}/_check_host.py +4 -5
  114. scitex/plt/__init__.py +245 -550
  115. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +5 -10
  116. scitex/plt/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  117. scitex/plt/gallery/README.md +1 -1
  118. scitex/plt/utils/_hitmap/__init__.py +82 -0
  119. scitex/plt/utils/_hitmap/_artist_extraction.py +343 -0
  120. scitex/plt/utils/_hitmap/_color_application.py +346 -0
  121. scitex/plt/utils/_hitmap/_color_conversion.py +121 -0
  122. scitex/plt/utils/_hitmap/_constants.py +40 -0
  123. scitex/plt/utils/_hitmap/_hitmap_core.py +334 -0
  124. scitex/plt/utils/_hitmap/_path_extraction.py +357 -0
  125. scitex/plt/utils/_hitmap/_query.py +113 -0
  126. scitex/plt/utils/_hitmap.py +46 -1616
  127. scitex/plt/utils/_metadata/__init__.py +80 -0
  128. scitex/plt/utils/_metadata/_artists/__init__.py +25 -0
  129. scitex/plt/utils/_metadata/_artists/_base.py +195 -0
  130. scitex/plt/utils/_metadata/_artists/_collections.py +356 -0
  131. scitex/plt/utils/_metadata/_artists/_extract.py +57 -0
  132. scitex/plt/utils/_metadata/_artists/_images.py +80 -0
  133. scitex/plt/utils/_metadata/_artists/_lines.py +261 -0
  134. scitex/plt/utils/_metadata/_artists/_patches.py +247 -0
  135. scitex/plt/utils/_metadata/_artists/_text.py +106 -0
  136. scitex/plt/utils/_metadata/_csv.py +416 -0
  137. scitex/plt/utils/_metadata/_detect.py +225 -0
  138. scitex/plt/utils/_metadata/_legend.py +127 -0
  139. scitex/plt/utils/_metadata/_rounding.py +117 -0
  140. scitex/plt/utils/_metadata/_verification.py +202 -0
  141. scitex/schema/README.md +1 -1
  142. scitex/scholar/__init__.py +8 -0
  143. scitex/scholar/_mcp/crossref_handlers.py +265 -0
  144. scitex/scholar/core/Scholar.py +63 -1700
  145. scitex/scholar/core/_mixins/__init__.py +36 -0
  146. scitex/scholar/core/_mixins/_enrichers.py +270 -0
  147. scitex/scholar/core/_mixins/_library_handlers.py +100 -0
  148. scitex/scholar/core/_mixins/_loaders.py +103 -0
  149. scitex/scholar/core/_mixins/_pdf_download.py +375 -0
  150. scitex/scholar/core/_mixins/_pipeline.py +312 -0
  151. scitex/scholar/core/_mixins/_project_handlers.py +125 -0
  152. scitex/scholar/core/_mixins/_savers.py +69 -0
  153. scitex/scholar/core/_mixins/_search.py +103 -0
  154. scitex/scholar/core/_mixins/_services.py +88 -0
  155. scitex/scholar/core/_mixins/_url_finding.py +105 -0
  156. scitex/scholar/crossref_scitex.py +367 -0
  157. scitex/scholar/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  158. scitex/scholar/examples/00_run_all.sh +120 -0
  159. scitex/scholar/jobs/_executors.py +27 -3
  160. scitex/scholar/pdf_download/ScholarPDFDownloader.py +38 -416
  161. scitex/scholar/pdf_download/_cli.py +154 -0
  162. scitex/scholar/pdf_download/strategies/__init__.py +11 -8
  163. scitex/scholar/pdf_download/strategies/manual_download_fallback.py +80 -3
  164. scitex/scholar/pipelines/ScholarPipelineBibTeX.py +73 -121
  165. scitex/scholar/pipelines/ScholarPipelineParallel.py +80 -138
  166. scitex/scholar/pipelines/ScholarPipelineSingle.py +43 -63
  167. scitex/scholar/pipelines/_single_steps.py +71 -36
  168. scitex/scholar/storage/_LibraryManager.py +97 -1695
  169. scitex/scholar/storage/_mixins/__init__.py +30 -0
  170. scitex/scholar/storage/_mixins/_bibtex_handlers.py +128 -0
  171. scitex/scholar/storage/_mixins/_library_operations.py +218 -0
  172. scitex/scholar/storage/_mixins/_metadata_conversion.py +226 -0
  173. scitex/scholar/storage/_mixins/_paper_saving.py +456 -0
  174. scitex/scholar/storage/_mixins/_resolution.py +376 -0
  175. scitex/scholar/storage/_mixins/_storage_helpers.py +121 -0
  176. scitex/scholar/storage/_mixins/_symlink_handlers.py +226 -0
  177. scitex/security/README.md +3 -3
  178. scitex/session/README.md +1 -1
  179. scitex/session/__init__.py +26 -7
  180. scitex/session/_decorator.py +1 -1
  181. scitex/sh/README.md +1 -1
  182. scitex/sh/__init__.py +7 -4
  183. scitex/social/__init__.py +155 -0
  184. scitex/social/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  185. scitex/stats/_mcp/_handlers/__init__.py +31 -0
  186. scitex/stats/_mcp/_handlers/_corrections.py +113 -0
  187. scitex/stats/_mcp/_handlers/_descriptive.py +78 -0
  188. scitex/stats/_mcp/_handlers/_effect_size.py +106 -0
  189. scitex/stats/_mcp/_handlers/_format.py +94 -0
  190. scitex/stats/_mcp/_handlers/_normality.py +110 -0
  191. scitex/stats/_mcp/_handlers/_posthoc.py +224 -0
  192. scitex/stats/_mcp/_handlers/_power.py +247 -0
  193. scitex/stats/_mcp/_handlers/_recommend.py +102 -0
  194. scitex/stats/_mcp/_handlers/_run_test.py +279 -0
  195. scitex/stats/_mcp/_handlers/_stars.py +48 -0
  196. scitex/stats/_mcp/handlers.py +19 -1171
  197. scitex/stats/auto/_stat_style.py +175 -0
  198. scitex/stats/auto/_style_definitions.py +411 -0
  199. scitex/stats/auto/_styles.py +22 -620
  200. scitex/stats/descriptive/__init__.py +11 -8
  201. scitex/stats/descriptive/_ci.py +39 -0
  202. scitex/stats/power/_power.py +15 -4
  203. scitex/str/__init__.py +2 -1
  204. scitex/str/_title_case.py +63 -0
  205. scitex/template/README.md +1 -1
  206. scitex/template/__init__.py +25 -10
  207. scitex/template/_code_templates.py +147 -0
  208. scitex/template/_mcp/handlers.py +81 -0
  209. scitex/template/_mcp/tool_schemas.py +55 -0
  210. scitex/template/_templates/__init__.py +51 -0
  211. scitex/template/_templates/audio.py +233 -0
  212. scitex/template/_templates/canvas.py +312 -0
  213. scitex/template/_templates/capture.py +268 -0
  214. scitex/template/_templates/config.py +43 -0
  215. scitex/template/_templates/diagram.py +294 -0
  216. scitex/template/_templates/io.py +107 -0
  217. scitex/template/_templates/module.py +53 -0
  218. scitex/template/_templates/plt.py +202 -0
  219. scitex/template/_templates/scholar.py +267 -0
  220. scitex/template/_templates/session.py +130 -0
  221. scitex/template/_templates/session_minimal.py +43 -0
  222. scitex/template/_templates/session_plot.py +67 -0
  223. scitex/template/_templates/session_stats.py +77 -0
  224. scitex/template/_templates/stats.py +323 -0
  225. scitex/template/_templates/writer.py +296 -0
  226. scitex/template/clone_writer_directory.py +5 -5
  227. scitex/ui/_backends/_email.py +10 -2
  228. scitex/ui/_backends/_webhook.py +5 -1
  229. scitex/web/_search_pubmed.py +10 -6
  230. scitex/writer/README.md +1 -1
  231. scitex/writer/__init__.py +43 -34
  232. scitex/writer/_mcp/handlers.py +11 -744
  233. scitex/writer/_mcp/tool_schemas.py +5 -335
  234. scitex-2.15.3.dist-info/METADATA +667 -0
  235. {scitex-2.14.0.dist-info → scitex-2.15.3.dist-info}/RECORD +241 -120
  236. scitex/canvas/editor/flask_editor/templates/_scripts.py +0 -4933
  237. scitex/canvas/editor/flask_editor/templates/_styles.py +0 -1658
  238. scitex/diagram/_compile.py +0 -312
  239. scitex/diagram/_diagram.py +0 -355
  240. scitex/diagram/_mcp/__init__.py +0 -4
  241. scitex/diagram/_mcp/handlers.py +0 -400
  242. scitex/diagram/_mcp/tool_schemas.py +0 -157
  243. scitex/diagram/_presets.py +0 -173
  244. scitex/diagram/_schema.py +0 -182
  245. scitex/diagram/_split.py +0 -278
  246. scitex/gen/_ci.py +0 -12
  247. scitex/gen/_title_case.py +0 -89
  248. scitex/plt/_mcp/__init__.py +0 -4
  249. scitex/plt/_mcp/_handlers_annotation.py +0 -102
  250. scitex/plt/_mcp/_handlers_figure.py +0 -195
  251. scitex/plt/_mcp/_handlers_plot.py +0 -252
  252. scitex/plt/_mcp/_handlers_style.py +0 -219
  253. scitex/plt/_mcp/handlers.py +0 -74
  254. scitex/plt/_mcp/tool_schemas.py +0 -497
  255. scitex/plt/mcp_server.py +0 -231
  256. scitex/scholar/examples/SUGGESTIONS.md +0 -865
  257. scitex/scholar/examples/dev.py +0 -38
  258. scitex-2.14.0.dist-info/METADATA +0 -1238
  259. /scitex/{gen → context}/_detect_environment.py +0 -0
  260. /scitex/{gen → context}/_get_notebook_path.py +0 -0
  261. /scitex/{gen/_shell.py → sh/_shell_legacy.py} +0 -0
  262. {scitex-2.14.0.dist-info → scitex-2.15.3.dist-info}/WHEEL +0 -0
  263. {scitex-2.14.0.dist-info → scitex-2.15.3.dist-info}/entry_points.txt +0 -0
  264. {scitex-2.14.0.dist-info → scitex-2.15.3.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,488 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ SciTeX CLI - Introspection Commands
4
+
5
+ Provides IPython-like introspection for Python packages.
6
+ """
7
+
8
+ import json
9
+ import sys
10
+
11
+ import click
12
+
13
+
14
+ def _normalize_path(ctx, param, value):
15
+ """Normalize dotted path: convert hyphens to underscores for Python module names."""
16
+ if value:
17
+ return value.replace("-", "_")
18
+ return value
19
+
20
+
21
+ @click.group(
22
+ context_settings={"help_option_names": ["-h", "--help"]},
23
+ invoke_without_command=True,
24
+ )
25
+ @click.option("--help-recursive", is_flag=True, help="Show help for all subcommands")
26
+ @click.pass_context
27
+ def introspect(ctx, help_recursive):
28
+ """
29
+ Python package introspection utilities
30
+
31
+ \b
32
+ IPython-like introspection for any Python package:
33
+ q - Function/class signature (like func?)
34
+ qq - Full source code (like func??)
35
+ dir - List module/class members (like dir())
36
+ api - Full module API tree
37
+ docstring - Extract docstrings
38
+ exports - Show __all__ exports
39
+ examples - Find usage examples
40
+
41
+ \b
42
+ Examples:
43
+ scitex introspect q scitex.plt.plot
44
+ scitex introspect qq scitex.stats.run_test --max-lines 50
45
+ scitex introspect dir scitex.plt --kind functions
46
+ scitex introspect api scitex --max-depth 2
47
+ """
48
+ if help_recursive:
49
+ from . import print_help_recursive
50
+
51
+ print_help_recursive(ctx, introspect)
52
+ ctx.exit(0)
53
+ elif ctx.invoked_subcommand is None:
54
+ click.echo(ctx.get_help())
55
+
56
+
57
+ @introspect.command()
58
+ @click.argument("dotted_path", callback=_normalize_path)
59
+ @click.option("--no-defaults", is_flag=True, help="Exclude default values")
60
+ @click.option("--no-annotations", is_flag=True, help="Exclude type annotations")
61
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
62
+ def q(dotted_path, no_defaults, no_annotations, as_json):
63
+ """
64
+ Get function/class signature (like IPython's func?)
65
+
66
+ \b
67
+ Examples:
68
+ scitex introspect q scitex.plt.plot
69
+ scitex introspect q scitex.audio.speak --json
70
+ scitex introspect q json.dumps
71
+ """
72
+ from scitex.introspect import q as get_q
73
+
74
+ result = get_q(
75
+ dotted_path,
76
+ include_defaults=not no_defaults,
77
+ include_annotations=not no_annotations,
78
+ )
79
+
80
+ if not result.get("success", False):
81
+ click.secho(f"Error: {result.get('error', 'Unknown error')}", fg="red")
82
+ sys.exit(1)
83
+
84
+ if as_json:
85
+ click.echo(json.dumps(result, indent=2))
86
+ else:
87
+ click.secho(result["signature"], fg="green", bold=True)
88
+ if result.get("parameters"):
89
+ click.echo("\nParameters:")
90
+ for p in result["parameters"]:
91
+ line = f" {p['name']}"
92
+ if "annotation" in p:
93
+ line += f": {p['annotation']}"
94
+ if "default" in p:
95
+ line += f" = {p['default']}"
96
+ click.echo(line)
97
+
98
+
99
+ @introspect.command()
100
+ @click.argument("dotted_path", callback=_normalize_path)
101
+ @click.option("--max-lines", "-n", type=int, help="Limit output to N lines")
102
+ @click.option("--no-decorators", is_flag=True, help="Exclude decorator lines")
103
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
104
+ def qq(dotted_path, max_lines, no_decorators, as_json):
105
+ """
106
+ Get source code of a Python object (like IPython's func??)
107
+
108
+ \b
109
+ Examples:
110
+ scitex introspect qq scitex.plt.plot
111
+ scitex introspect qq scitex.audio.speak --max-lines 50
112
+ """
113
+ from scitex.introspect import qq as get_qq
114
+
115
+ result = get_qq(
116
+ dotted_path,
117
+ max_lines=max_lines,
118
+ include_decorators=not no_decorators,
119
+ )
120
+
121
+ if not result.get("success", False):
122
+ click.secho(f"Error: {result.get('error', 'Unknown error')}", fg="red")
123
+ sys.exit(1)
124
+
125
+ if as_json:
126
+ click.echo(json.dumps(result, indent=2))
127
+ else:
128
+ click.secho(f"# File: {result['file']}:{result['line_start']}", fg="cyan")
129
+ click.secho(f"# Lines: {result['line_count']}", fg="cyan")
130
+ click.echo()
131
+ click.echo(result["source"])
132
+
133
+
134
+ @introspect.command("dir")
135
+ @click.argument("dotted_path", callback=_normalize_path)
136
+ @click.option(
137
+ "--filter",
138
+ "-f",
139
+ type=click.Choice(["all", "public", "private", "dunder"]),
140
+ default="public",
141
+ help="Filter members",
142
+ )
143
+ @click.option(
144
+ "--kind",
145
+ "-k",
146
+ type=click.Choice(["all", "functions", "classes", "data", "modules"]),
147
+ help="Filter by type",
148
+ )
149
+ @click.option("--inherited", is_flag=True, help="Include inherited members (classes)")
150
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
151
+ def dir_cmd(dotted_path, filter, kind, inherited, as_json):
152
+ """
153
+ List members of a module or class (like dir())
154
+
155
+ \b
156
+ Examples:
157
+ scitex introspect dir scitex.plt
158
+ scitex introspect dir scitex.audio --kind functions
159
+ scitex introspect dir scitex.plt.AxisWrapper --filter all
160
+ """
161
+ from scitex.introspect import dir as get_dir
162
+
163
+ result = get_dir(
164
+ dotted_path,
165
+ filter=filter,
166
+ kind=kind,
167
+ include_inherited=inherited,
168
+ )
169
+
170
+ if not result.get("success", False):
171
+ click.secho(f"Error: {result.get('error', 'Unknown error')}", fg="red")
172
+ sys.exit(1)
173
+
174
+ if as_json:
175
+ click.echo(json.dumps(result, indent=2))
176
+ else:
177
+ click.secho(f"Members of {dotted_path} ({result['count']}):", fg="cyan")
178
+ for m in result["members"]:
179
+ kind_str = click.style(f"[{m['kind']}]", fg="yellow")
180
+ name_str = click.style(m["name"], fg="green", bold=True)
181
+ summary = f" - {m['summary']}" if m["summary"] else ""
182
+ click.echo(f" {kind_str} {name_str}{summary}")
183
+
184
+
185
+ @introspect.command()
186
+ @click.argument("dotted_path", callback=_normalize_path)
187
+ @click.option("--max-depth", "-d", type=int, default=5, help="Max recursion depth")
188
+ @click.option("--docstring", is_flag=True, help="Include docstrings")
189
+ @click.option("--root-only", is_flag=True, help="Show only root-level items")
190
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
191
+ def api(dotted_path, max_depth, docstring, root_only, as_json):
192
+ """
193
+ List the full API tree of a module recursively
194
+
195
+ \b
196
+ Examples:
197
+ scitex introspect api scitex --max-depth 2
198
+ scitex introspect api scitex.plt --docstring
199
+ scitex introspect api scitex.audio --root-only
200
+ """
201
+ from scitex.introspect import list_api
202
+
203
+ df = list_api(
204
+ dotted_path,
205
+ max_depth=max_depth,
206
+ docstring=docstring,
207
+ root_only=root_only,
208
+ )
209
+
210
+ if as_json:
211
+ click.echo(json.dumps(df.to_dict(orient="records"), indent=2))
212
+ else:
213
+ click.secho(f"API tree of {dotted_path} ({len(df)} items):", fg="cyan")
214
+ for _, row in df.iterrows():
215
+ indent = " " * row["Depth"]
216
+ type_str = click.style(f"[{row['Type']}]", fg="yellow")
217
+ name = row["Name"].split(".")[-1]
218
+ name_str = click.style(name, fg="green", bold=True)
219
+ doc = f" - {row['Docstring'][:50]}..." if row.get("Docstring") else ""
220
+ click.echo(f"{indent}{type_str} {name_str}{doc}")
221
+
222
+
223
+ @introspect.command()
224
+ @click.argument("dotted_path", callback=_normalize_path)
225
+ @click.option(
226
+ "--format",
227
+ "-f",
228
+ type=click.Choice(["raw", "parsed", "summary"]),
229
+ default="raw",
230
+ help="Output format",
231
+ )
232
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
233
+ def docstring(dotted_path, format, as_json):
234
+ """
235
+ Get docstring of a Python object
236
+
237
+ \b
238
+ Formats:
239
+ raw - Full docstring as-is
240
+ parsed - Parse into sections (summary, parameters, returns, etc.)
241
+ summary - First line/paragraph only
242
+
243
+ \b
244
+ Examples:
245
+ scitex introspect docstring scitex.plt.plot
246
+ scitex introspect docstring scitex.audio.speak --format parsed
247
+ """
248
+ from scitex.introspect import get_docstring
249
+
250
+ result = get_docstring(dotted_path, format=format)
251
+
252
+ if not result.get("success", False):
253
+ click.secho(f"Error: {result.get('error', 'Unknown error')}", fg="red")
254
+ sys.exit(1)
255
+
256
+ if as_json:
257
+ click.echo(json.dumps(result, indent=2))
258
+ else:
259
+ click.echo(result["docstring"])
260
+ if format == "parsed" and result.get("sections"):
261
+ click.echo("\n--- Parsed Sections ---")
262
+ for key, value in result["sections"].items():
263
+ if value:
264
+ click.secho(f"\n[{key}]", fg="cyan", bold=True)
265
+ click.echo(value)
266
+
267
+
268
+ @introspect.command()
269
+ @click.argument("dotted_path", callback=_normalize_path)
270
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
271
+ def exports(dotted_path, as_json):
272
+ """
273
+ Get __all__ exports of a module
274
+
275
+ \b
276
+ Examples:
277
+ scitex introspect exports scitex.audio
278
+ scitex introspect exports scitex.plt
279
+ """
280
+ from scitex.introspect import get_exports
281
+
282
+ result = get_exports(dotted_path)
283
+
284
+ if not result.get("success", False):
285
+ click.secho(f"Error: {result.get('error', 'Unknown error')}", fg="red")
286
+ sys.exit(1)
287
+
288
+ if as_json:
289
+ click.echo(json.dumps(result, indent=2))
290
+ else:
291
+ has_all = "defined" if result["has_all"] else "not defined (showing public)"
292
+ click.secho(f"__all__ is {has_all}", fg="cyan")
293
+ click.secho(f"Exports ({result['count']}):", fg="cyan")
294
+ for name in result["exports"]:
295
+ click.echo(f" {name}")
296
+
297
+
298
+ @introspect.command()
299
+ @click.argument("dotted_path", callback=_normalize_path)
300
+ @click.option("--search-paths", "-p", help="Comma-separated search paths")
301
+ @click.option("--max-results", "-n", type=int, default=10, help="Max examples")
302
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
303
+ def examples(dotted_path, search_paths, max_results, as_json):
304
+ """
305
+ Find usage examples in tests/examples directories
306
+
307
+ \b
308
+ Examples:
309
+ scitex introspect examples scitex.plt.plot
310
+ scitex introspect examples scitex.audio.speak --max-results 5
311
+ """
312
+ from scitex.introspect import find_examples
313
+
314
+ paths_list = None
315
+ if search_paths:
316
+ paths_list = [p.strip() for p in search_paths.split(",")]
317
+
318
+ result = find_examples(
319
+ dotted_path,
320
+ search_paths=paths_list,
321
+ max_results=max_results,
322
+ )
323
+
324
+ if not result.get("success", False):
325
+ click.secho(f"Error: {result.get('error', 'Unknown error')}", fg="red")
326
+ sys.exit(1)
327
+
328
+ if as_json:
329
+ click.echo(json.dumps(result, indent=2))
330
+ else:
331
+ click.secho(f"Found {result['count']} examples:", fg="cyan")
332
+ for ex in result["examples"]:
333
+ click.echo()
334
+ click.secho(f"--- {ex['file']}:{ex['line']} ---", fg="yellow")
335
+ click.echo(ex["context"])
336
+
337
+
338
+ # Advanced introspection commands
339
+
340
+
341
+ @introspect.command("hierarchy")
342
+ @click.argument("dotted_path", callback=_normalize_path)
343
+ @click.option("--builtins", is_flag=True, help="Include builtin classes")
344
+ @click.option("--max-depth", "-d", type=int, default=10, help="Max subclass depth")
345
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
346
+ def class_hierarchy(dotted_path, builtins, max_depth, as_json):
347
+ """Get class inheritance hierarchy (MRO + subclasses)"""
348
+ from scitex.introspect import get_class_hierarchy
349
+
350
+ result = get_class_hierarchy(
351
+ dotted_path, include_builtins=builtins, max_depth=max_depth
352
+ )
353
+
354
+ if not result.get("success", False):
355
+ click.secho(f"Error: {result.get('error', 'Unknown error')}", fg="red")
356
+ sys.exit(1)
357
+
358
+ if as_json:
359
+ click.echo(json.dumps(result, indent=2))
360
+ else:
361
+ click.secho(f"Class: {dotted_path}", fg="cyan", bold=True)
362
+ click.secho(f"\nMRO ({result['mro_count']} classes):", fg="yellow")
363
+ for cls in result["mro"]:
364
+ click.echo(f" {cls['qualname']}")
365
+ click.secho(f"\nSubclasses ({result['subclass_count']}):", fg="yellow")
366
+ _print_subclasses(result.get("subclasses", []), indent=2)
367
+
368
+
369
+ def _print_subclasses(subclasses, indent=0):
370
+ """Helper to print subclass tree."""
371
+ for sub in subclasses:
372
+ click.echo(" " * indent + f"- {sub['qualname']}")
373
+ if "subclasses" in sub:
374
+ _print_subclasses(sub["subclasses"], indent + 2)
375
+
376
+
377
+ @introspect.command("hints")
378
+ @click.argument("dotted_path", callback=_normalize_path)
379
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
380
+ def type_hints(dotted_path, as_json):
381
+ """Get detailed type hint analysis"""
382
+ from scitex.introspect import get_type_hints_detailed
383
+
384
+ result = get_type_hints_detailed(dotted_path)
385
+
386
+ if not result.get("success", False):
387
+ click.secho(f"Error: {result.get('error', 'Unknown error')}", fg="red")
388
+ sys.exit(1)
389
+
390
+ if as_json:
391
+ click.echo(json.dumps(result, indent=2))
392
+ else:
393
+ click.secho(f"Type hints ({result['hint_count']}):", fg="cyan")
394
+ for name, info in result.get("hints", {}).items():
395
+ opt = " (optional)" if info.get("is_optional") else ""
396
+ click.echo(f" {name}: {info['raw']}{opt}")
397
+ if result.get("return_hint"):
398
+ click.secho(f"\nReturn: {result['return_hint']['raw']}", fg="green")
399
+
400
+
401
+ @introspect.command("imports")
402
+ @click.argument("dotted_path", callback=_normalize_path)
403
+ @click.option("--no-categorize", is_flag=True, help="Don't group by category")
404
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
405
+ def imports(dotted_path, no_categorize, as_json):
406
+ """Get all imports from a module (AST-based)"""
407
+ from scitex.introspect import get_imports
408
+
409
+ result = get_imports(dotted_path, categorize=not no_categorize)
410
+
411
+ if not result.get("success", False):
412
+ click.secho(f"Error: {result.get('error', 'Unknown error')}", fg="red")
413
+ sys.exit(1)
414
+
415
+ if as_json:
416
+ click.echo(json.dumps(result, indent=2))
417
+ else:
418
+ click.secho(f"Imports ({result['import_count']}):", fg="cyan")
419
+ if result.get("categories"):
420
+ for cat, imps in result["categories"].items():
421
+ if imps:
422
+ click.secho(f"\n [{cat}] ({len(imps)}):", fg="yellow")
423
+ for imp in imps:
424
+ click.echo(f" {imp['module']}")
425
+ else:
426
+ for imp in result["imports"]:
427
+ click.echo(f" {imp['module']}")
428
+
429
+
430
+ @introspect.command("deps")
431
+ @click.argument("dotted_path", callback=_normalize_path)
432
+ @click.option("--recursive", "-r", is_flag=True, help="Recursive analysis")
433
+ @click.option("--max-depth", "-d", type=int, default=3, help="Max recursion depth")
434
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
435
+ def dependencies(dotted_path, recursive, max_depth, as_json):
436
+ """Get module dependencies"""
437
+ from scitex.introspect import get_dependencies
438
+
439
+ result = get_dependencies(dotted_path, recursive=recursive, max_depth=max_depth)
440
+
441
+ if not result.get("success", False):
442
+ click.secho(f"Error: {result.get('error', 'Unknown error')}", fg="red")
443
+ sys.exit(1)
444
+
445
+ if as_json:
446
+ click.echo(json.dumps(result, indent=2))
447
+ else:
448
+ click.secho(f"Dependencies ({result['dependency_count']}):", fg="cyan")
449
+ for dep in result.get("dependencies", []):
450
+ click.echo(f" {dep}")
451
+
452
+
453
+ @introspect.command("calls")
454
+ @click.argument("dotted_path", callback=_normalize_path)
455
+ @click.option("--timeout", "-t", type=int, default=10, help="Timeout in seconds")
456
+ @click.option("--all", "all_calls", is_flag=True, help="Include external calls")
457
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
458
+ def call_graph(dotted_path, timeout, all_calls, as_json):
459
+ """Get function call graph (with timeout protection)"""
460
+ from scitex.introspect import get_call_graph
461
+
462
+ result = get_call_graph(
463
+ dotted_path, timeout_seconds=timeout, internal_only=not all_calls
464
+ )
465
+
466
+ if not result.get("success", False):
467
+ click.secho(f"Error: {result.get('error', 'Unknown error')}", fg="red")
468
+ sys.exit(1)
469
+
470
+ if as_json:
471
+ click.echo(json.dumps(result, indent=2))
472
+ else:
473
+ if "calls" in result:
474
+ click.secho(f"Calls ({result['call_count']}):", fg="cyan")
475
+ for call in result["calls"]:
476
+ click.echo(f" -> {call['name']} (line {call['line']})")
477
+ click.secho(f"\nCalled by ({result['caller_count']}):", fg="yellow")
478
+ for caller in result.get("called_by", []):
479
+ click.echo(f" <- {caller['name']} (line {caller['line']})")
480
+ elif "graph" in result:
481
+ click.secho(
482
+ f"Module call graph ({result['function_count']} functions):", fg="cyan"
483
+ )
484
+ for func, info in result["graph"].items():
485
+ calls = ", ".join(c["name"] for c in info["calls"][:5])
486
+ if len(info["calls"]) > 5:
487
+ calls += "..."
488
+ click.echo(f" {func}: {calls or '(no calls)'}")