scitex 2.14.0__py3-none-any.whl → 2.15.2__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 (300) 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 +244 -0
  16. scitex/_mcp_tools/template.py +24 -0
  17. scitex/_mcp_tools/writer.py +21 -204
  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 +76 -27
  68. scitex/cli/capture.py +13 -20
  69. scitex/cli/introspect.py +481 -0
  70. scitex/cli/main.py +200 -109
  71. scitex/cli/mcp.py +60 -34
  72. scitex/cli/plt.py +357 -0
  73. scitex/cli/repro.py +15 -8
  74. scitex/cli/resource.py +15 -8
  75. scitex/cli/scholar/__init__.py +23 -8
  76. scitex/cli/scholar/_crossref_scitex.py +296 -0
  77. scitex/cli/scholar/_fetch.py +25 -3
  78. scitex/cli/social.py +314 -0
  79. scitex/cli/stats.py +15 -8
  80. scitex/cli/template.py +129 -12
  81. scitex/cli/tex.py +15 -8
  82. scitex/cli/writer.py +132 -8
  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} +43 -54
  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/scholar/url_finder/.tmp/open_url/KNOWN_RESOLVERS.py +462 -0
  178. scitex/scholar/url_finder/.tmp/open_url/README.md +223 -0
  179. scitex/scholar/url_finder/.tmp/open_url/_DOIToURLResolver.py +694 -0
  180. scitex/scholar/url_finder/.tmp/open_url/_OpenURLResolver.py +1160 -0
  181. scitex/scholar/url_finder/.tmp/open_url/_ResolverLinkFinder.py +344 -0
  182. scitex/scholar/url_finder/.tmp/open_url/__init__.py +24 -0
  183. scitex/security/README.md +3 -3
  184. scitex/session/README.md +1 -1
  185. scitex/session/__init__.py +26 -7
  186. scitex/session/_decorator.py +1 -1
  187. scitex/sh/README.md +1 -1
  188. scitex/sh/__init__.py +7 -4
  189. scitex/social/__init__.py +155 -0
  190. scitex/social/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  191. scitex/stats/_mcp/_handlers/__init__.py +31 -0
  192. scitex/stats/_mcp/_handlers/_corrections.py +113 -0
  193. scitex/stats/_mcp/_handlers/_descriptive.py +78 -0
  194. scitex/stats/_mcp/_handlers/_effect_size.py +106 -0
  195. scitex/stats/_mcp/_handlers/_format.py +94 -0
  196. scitex/stats/_mcp/_handlers/_normality.py +110 -0
  197. scitex/stats/_mcp/_handlers/_posthoc.py +224 -0
  198. scitex/stats/_mcp/_handlers/_power.py +247 -0
  199. scitex/stats/_mcp/_handlers/_recommend.py +102 -0
  200. scitex/stats/_mcp/_handlers/_run_test.py +279 -0
  201. scitex/stats/_mcp/_handlers/_stars.py +48 -0
  202. scitex/stats/_mcp/handlers.py +19 -1171
  203. scitex/stats/auto/_stat_style.py +175 -0
  204. scitex/stats/auto/_style_definitions.py +411 -0
  205. scitex/stats/auto/_styles.py +22 -620
  206. scitex/stats/descriptive/__init__.py +11 -8
  207. scitex/stats/descriptive/_ci.py +39 -0
  208. scitex/stats/power/_power.py +15 -4
  209. scitex/str/__init__.py +2 -1
  210. scitex/str/_title_case.py +63 -0
  211. scitex/template/README.md +1 -1
  212. scitex/template/__init__.py +25 -10
  213. scitex/template/_code_templates.py +147 -0
  214. scitex/template/_mcp/handlers.py +81 -0
  215. scitex/template/_mcp/tool_schemas.py +55 -0
  216. scitex/template/_templates/__init__.py +51 -0
  217. scitex/template/_templates/audio.py +233 -0
  218. scitex/template/_templates/canvas.py +312 -0
  219. scitex/template/_templates/capture.py +268 -0
  220. scitex/template/_templates/config.py +43 -0
  221. scitex/template/_templates/diagram.py +294 -0
  222. scitex/template/_templates/io.py +107 -0
  223. scitex/template/_templates/module.py +53 -0
  224. scitex/template/_templates/plt.py +202 -0
  225. scitex/template/_templates/scholar.py +267 -0
  226. scitex/template/_templates/session.py +130 -0
  227. scitex/template/_templates/session_minimal.py +43 -0
  228. scitex/template/_templates/session_plot.py +67 -0
  229. scitex/template/_templates/session_stats.py +77 -0
  230. scitex/template/_templates/stats.py +323 -0
  231. scitex/template/_templates/writer.py +296 -0
  232. scitex/template/clone_writer_directory.py +5 -5
  233. scitex/ui/_backends/_email.py +10 -2
  234. scitex/ui/_backends/_webhook.py +5 -1
  235. scitex/web/_search_pubmed.py +10 -6
  236. scitex/writer/README.md +1 -1
  237. scitex/writer/_mcp/handlers.py +11 -744
  238. scitex/writer/_mcp/tool_schemas.py +5 -335
  239. scitex-2.15.2.dist-info/METADATA +648 -0
  240. {scitex-2.14.0.dist-info → scitex-2.15.2.dist-info}/RECORD +246 -150
  241. scitex/canvas/editor/flask_editor/templates/_scripts.py +0 -4933
  242. scitex/canvas/editor/flask_editor/templates/_styles.py +0 -1658
  243. scitex/dev/plt/data/mpl/PLOTTING_FUNCTIONS.yaml +0 -90
  244. scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES.yaml +0 -1571
  245. scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES_DETAILED.yaml +0 -6262
  246. scitex/dev/plt/data/mpl/SIGNATURES_FLATTENED.yaml +0 -1274
  247. scitex/dev/plt/data/mpl/dir_ax.txt +0 -459
  248. scitex/diagram/_compile.py +0 -312
  249. scitex/diagram/_diagram.py +0 -355
  250. scitex/diagram/_mcp/__init__.py +0 -4
  251. scitex/diagram/_mcp/handlers.py +0 -400
  252. scitex/diagram/_mcp/tool_schemas.py +0 -157
  253. scitex/diagram/_presets.py +0 -173
  254. scitex/diagram/_schema.py +0 -182
  255. scitex/diagram/_split.py +0 -278
  256. scitex/gen/_ci.py +0 -12
  257. scitex/gen/_title_case.py +0 -89
  258. scitex/plt/_mcp/__init__.py +0 -4
  259. scitex/plt/_mcp/_handlers_annotation.py +0 -102
  260. scitex/plt/_mcp/_handlers_figure.py +0 -195
  261. scitex/plt/_mcp/_handlers_plot.py +0 -252
  262. scitex/plt/_mcp/_handlers_style.py +0 -219
  263. scitex/plt/_mcp/handlers.py +0 -74
  264. scitex/plt/_mcp/tool_schemas.py +0 -497
  265. scitex/plt/mcp_server.py +0 -231
  266. scitex/scholar/data/.gitkeep +0 -0
  267. scitex/scholar/data/README.md +0 -44
  268. scitex/scholar/data/bib_files/bibliography.bib +0 -1952
  269. scitex/scholar/data/bib_files/neurovista.bib +0 -277
  270. scitex/scholar/data/bib_files/neurovista_enriched.bib +0 -441
  271. scitex/scholar/data/bib_files/neurovista_enriched_enriched.bib +0 -441
  272. scitex/scholar/data/bib_files/neurovista_processed.bib +0 -338
  273. scitex/scholar/data/bib_files/openaccess.bib +0 -89
  274. scitex/scholar/data/bib_files/pac-seizure_prediction_enriched.bib +0 -2178
  275. scitex/scholar/data/bib_files/pac.bib +0 -698
  276. scitex/scholar/data/bib_files/pac_enriched.bib +0 -1061
  277. scitex/scholar/data/bib_files/pac_processed.bib +0 -0
  278. scitex/scholar/data/bib_files/pac_titles.txt +0 -75
  279. scitex/scholar/data/bib_files/paywalled.bib +0 -98
  280. scitex/scholar/data/bib_files/related-papers-by-coauthors.bib +0 -58
  281. scitex/scholar/data/bib_files/related-papers-by-coauthors_enriched.bib +0 -87
  282. scitex/scholar/data/bib_files/seizure_prediction.bib +0 -694
  283. scitex/scholar/data/bib_files/seizure_prediction_processed.bib +0 -0
  284. scitex/scholar/data/bib_files/test_complete_enriched.bib +0 -437
  285. scitex/scholar/data/bib_files/test_final_enriched.bib +0 -437
  286. scitex/scholar/data/bib_files/test_seizure.bib +0 -46
  287. scitex/scholar/data/impact_factor/JCR_IF_2022.xlsx +0 -0
  288. scitex/scholar/data/impact_factor/JCR_IF_2024.db +0 -0
  289. scitex/scholar/data/impact_factor/JCR_IF_2024.xlsx +0 -0
  290. scitex/scholar/data/impact_factor/JCR_IF_2024_v01.db +0 -0
  291. scitex/scholar/data/impact_factor.db +0 -0
  292. scitex/scholar/examples/SUGGESTIONS.md +0 -865
  293. scitex/scholar/examples/dev.py +0 -38
  294. scitex-2.14.0.dist-info/METADATA +0 -1238
  295. /scitex/{gen → context}/_detect_environment.py +0 -0
  296. /scitex/{gen → context}/_get_notebook_path.py +0 -0
  297. /scitex/{gen/_shell.py → sh/_shell_legacy.py} +0 -0
  298. {scitex-2.14.0.dist-info → scitex-2.15.2.dist-info}/WHEEL +0 -0
  299. {scitex-2.14.0.dist-info → scitex-2.15.2.dist-info}/entry_points.txt +0 -0
  300. {scitex-2.14.0.dist-info → scitex-2.15.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,481 @@
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
+ @click.group(
15
+ context_settings={"help_option_names": ["-h", "--help"]},
16
+ invoke_without_command=True,
17
+ )
18
+ @click.option("--help-recursive", is_flag=True, help="Show help for all subcommands")
19
+ @click.pass_context
20
+ def introspect(ctx, help_recursive):
21
+ """
22
+ Python package introspection utilities
23
+
24
+ \b
25
+ IPython-like introspection for any Python package:
26
+ q - Function/class signature (like func?)
27
+ qq - Full source code (like func??)
28
+ dir - List module/class members (like dir())
29
+ api - Full module API tree
30
+ docstring - Extract docstrings
31
+ exports - Show __all__ exports
32
+ examples - Find usage examples
33
+
34
+ \b
35
+ Examples:
36
+ scitex introspect q scitex.plt.plot
37
+ scitex introspect qq scitex.stats.run_test --max-lines 50
38
+ scitex introspect dir scitex.plt --kind functions
39
+ scitex introspect api scitex --max-depth 2
40
+ """
41
+ if help_recursive:
42
+ from . import print_help_recursive
43
+
44
+ print_help_recursive(ctx, introspect)
45
+ ctx.exit(0)
46
+ elif ctx.invoked_subcommand is None:
47
+ click.echo(ctx.get_help())
48
+
49
+
50
+ @introspect.command()
51
+ @click.argument("dotted_path")
52
+ @click.option("--no-defaults", is_flag=True, help="Exclude default values")
53
+ @click.option("--no-annotations", is_flag=True, help="Exclude type annotations")
54
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
55
+ def q(dotted_path, no_defaults, no_annotations, as_json):
56
+ """
57
+ Get function/class signature (like IPython's func?)
58
+
59
+ \b
60
+ Examples:
61
+ scitex introspect q scitex.plt.plot
62
+ scitex introspect q scitex.audio.speak --json
63
+ scitex introspect q json.dumps
64
+ """
65
+ from scitex.introspect import q as get_q
66
+
67
+ result = get_q(
68
+ dotted_path,
69
+ include_defaults=not no_defaults,
70
+ include_annotations=not no_annotations,
71
+ )
72
+
73
+ if not result.get("success", False):
74
+ click.secho(f"Error: {result.get('error', 'Unknown error')}", fg="red")
75
+ sys.exit(1)
76
+
77
+ if as_json:
78
+ click.echo(json.dumps(result, indent=2))
79
+ else:
80
+ click.secho(result["signature"], fg="green", bold=True)
81
+ if result.get("parameters"):
82
+ click.echo("\nParameters:")
83
+ for p in result["parameters"]:
84
+ line = f" {p['name']}"
85
+ if "annotation" in p:
86
+ line += f": {p['annotation']}"
87
+ if "default" in p:
88
+ line += f" = {p['default']}"
89
+ click.echo(line)
90
+
91
+
92
+ @introspect.command()
93
+ @click.argument("dotted_path")
94
+ @click.option("--max-lines", "-n", type=int, help="Limit output to N lines")
95
+ @click.option("--no-decorators", is_flag=True, help="Exclude decorator lines")
96
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
97
+ def qq(dotted_path, max_lines, no_decorators, as_json):
98
+ """
99
+ Get source code of a Python object (like IPython's func??)
100
+
101
+ \b
102
+ Examples:
103
+ scitex introspect qq scitex.plt.plot
104
+ scitex introspect qq scitex.audio.speak --max-lines 50
105
+ """
106
+ from scitex.introspect import qq as get_qq
107
+
108
+ result = get_qq(
109
+ dotted_path,
110
+ max_lines=max_lines,
111
+ include_decorators=not no_decorators,
112
+ )
113
+
114
+ if not result.get("success", False):
115
+ click.secho(f"Error: {result.get('error', 'Unknown error')}", fg="red")
116
+ sys.exit(1)
117
+
118
+ if as_json:
119
+ click.echo(json.dumps(result, indent=2))
120
+ else:
121
+ click.secho(f"# File: {result['file']}:{result['line_start']}", fg="cyan")
122
+ click.secho(f"# Lines: {result['line_count']}", fg="cyan")
123
+ click.echo()
124
+ click.echo(result["source"])
125
+
126
+
127
+ @introspect.command("dir")
128
+ @click.argument("dotted_path")
129
+ @click.option(
130
+ "--filter",
131
+ "-f",
132
+ type=click.Choice(["all", "public", "private", "dunder"]),
133
+ default="public",
134
+ help="Filter members",
135
+ )
136
+ @click.option(
137
+ "--kind",
138
+ "-k",
139
+ type=click.Choice(["all", "functions", "classes", "data", "modules"]),
140
+ help="Filter by type",
141
+ )
142
+ @click.option("--inherited", is_flag=True, help="Include inherited members (classes)")
143
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
144
+ def dir_cmd(dotted_path, filter, kind, inherited, as_json):
145
+ """
146
+ List members of a module or class (like dir())
147
+
148
+ \b
149
+ Examples:
150
+ scitex introspect dir scitex.plt
151
+ scitex introspect dir scitex.audio --kind functions
152
+ scitex introspect dir scitex.plt.AxisWrapper --filter all
153
+ """
154
+ from scitex.introspect import dir as get_dir
155
+
156
+ result = get_dir(
157
+ dotted_path,
158
+ filter=filter,
159
+ kind=kind,
160
+ include_inherited=inherited,
161
+ )
162
+
163
+ if not result.get("success", False):
164
+ click.secho(f"Error: {result.get('error', 'Unknown error')}", fg="red")
165
+ sys.exit(1)
166
+
167
+ if as_json:
168
+ click.echo(json.dumps(result, indent=2))
169
+ else:
170
+ click.secho(f"Members of {dotted_path} ({result['count']}):", fg="cyan")
171
+ for m in result["members"]:
172
+ kind_str = click.style(f"[{m['kind']}]", fg="yellow")
173
+ name_str = click.style(m["name"], fg="green", bold=True)
174
+ summary = f" - {m['summary']}" if m["summary"] else ""
175
+ click.echo(f" {kind_str} {name_str}{summary}")
176
+
177
+
178
+ @introspect.command()
179
+ @click.argument("dotted_path")
180
+ @click.option("--max-depth", "-d", type=int, default=5, help="Max recursion depth")
181
+ @click.option("--docstring", is_flag=True, help="Include docstrings")
182
+ @click.option("--root-only", is_flag=True, help="Show only root-level items")
183
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
184
+ def api(dotted_path, max_depth, docstring, root_only, as_json):
185
+ """
186
+ List the full API tree of a module recursively
187
+
188
+ \b
189
+ Examples:
190
+ scitex introspect api scitex --max-depth 2
191
+ scitex introspect api scitex.plt --docstring
192
+ scitex introspect api scitex.audio --root-only
193
+ """
194
+ from scitex.introspect import list_api
195
+
196
+ df = list_api(
197
+ dotted_path,
198
+ max_depth=max_depth,
199
+ docstring=docstring,
200
+ root_only=root_only,
201
+ )
202
+
203
+ if as_json:
204
+ click.echo(json.dumps(df.to_dict(orient="records"), indent=2))
205
+ else:
206
+ click.secho(f"API tree of {dotted_path} ({len(df)} items):", fg="cyan")
207
+ for _, row in df.iterrows():
208
+ indent = " " * row["Depth"]
209
+ type_str = click.style(f"[{row['Type']}]", fg="yellow")
210
+ name = row["Name"].split(".")[-1]
211
+ name_str = click.style(name, fg="green", bold=True)
212
+ doc = f" - {row['Docstring'][:50]}..." if row.get("Docstring") else ""
213
+ click.echo(f"{indent}{type_str} {name_str}{doc}")
214
+
215
+
216
+ @introspect.command()
217
+ @click.argument("dotted_path")
218
+ @click.option(
219
+ "--format",
220
+ "-f",
221
+ type=click.Choice(["raw", "parsed", "summary"]),
222
+ default="raw",
223
+ help="Output format",
224
+ )
225
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
226
+ def docstring(dotted_path, format, as_json):
227
+ """
228
+ Get docstring of a Python object
229
+
230
+ \b
231
+ Formats:
232
+ raw - Full docstring as-is
233
+ parsed - Parse into sections (summary, parameters, returns, etc.)
234
+ summary - First line/paragraph only
235
+
236
+ \b
237
+ Examples:
238
+ scitex introspect docstring scitex.plt.plot
239
+ scitex introspect docstring scitex.audio.speak --format parsed
240
+ """
241
+ from scitex.introspect import get_docstring
242
+
243
+ result = get_docstring(dotted_path, format=format)
244
+
245
+ if not result.get("success", False):
246
+ click.secho(f"Error: {result.get('error', 'Unknown error')}", fg="red")
247
+ sys.exit(1)
248
+
249
+ if as_json:
250
+ click.echo(json.dumps(result, indent=2))
251
+ else:
252
+ click.echo(result["docstring"])
253
+ if format == "parsed" and result.get("sections"):
254
+ click.echo("\n--- Parsed Sections ---")
255
+ for key, value in result["sections"].items():
256
+ if value:
257
+ click.secho(f"\n[{key}]", fg="cyan", bold=True)
258
+ click.echo(value)
259
+
260
+
261
+ @introspect.command()
262
+ @click.argument("dotted_path")
263
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
264
+ def exports(dotted_path, as_json):
265
+ """
266
+ Get __all__ exports of a module
267
+
268
+ \b
269
+ Examples:
270
+ scitex introspect exports scitex.audio
271
+ scitex introspect exports scitex.plt
272
+ """
273
+ from scitex.introspect import get_exports
274
+
275
+ result = get_exports(dotted_path)
276
+
277
+ if not result.get("success", False):
278
+ click.secho(f"Error: {result.get('error', 'Unknown error')}", fg="red")
279
+ sys.exit(1)
280
+
281
+ if as_json:
282
+ click.echo(json.dumps(result, indent=2))
283
+ else:
284
+ has_all = "defined" if result["has_all"] else "not defined (showing public)"
285
+ click.secho(f"__all__ is {has_all}", fg="cyan")
286
+ click.secho(f"Exports ({result['count']}):", fg="cyan")
287
+ for name in result["exports"]:
288
+ click.echo(f" {name}")
289
+
290
+
291
+ @introspect.command()
292
+ @click.argument("dotted_path")
293
+ @click.option("--search-paths", "-p", help="Comma-separated search paths")
294
+ @click.option("--max-results", "-n", type=int, default=10, help="Max examples")
295
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
296
+ def examples(dotted_path, search_paths, max_results, as_json):
297
+ """
298
+ Find usage examples in tests/examples directories
299
+
300
+ \b
301
+ Examples:
302
+ scitex introspect examples scitex.plt.plot
303
+ scitex introspect examples scitex.audio.speak --max-results 5
304
+ """
305
+ from scitex.introspect import find_examples
306
+
307
+ paths_list = None
308
+ if search_paths:
309
+ paths_list = [p.strip() for p in search_paths.split(",")]
310
+
311
+ result = find_examples(
312
+ dotted_path,
313
+ search_paths=paths_list,
314
+ max_results=max_results,
315
+ )
316
+
317
+ if not result.get("success", False):
318
+ click.secho(f"Error: {result.get('error', 'Unknown error')}", fg="red")
319
+ sys.exit(1)
320
+
321
+ if as_json:
322
+ click.echo(json.dumps(result, indent=2))
323
+ else:
324
+ click.secho(f"Found {result['count']} examples:", fg="cyan")
325
+ for ex in result["examples"]:
326
+ click.echo()
327
+ click.secho(f"--- {ex['file']}:{ex['line']} ---", fg="yellow")
328
+ click.echo(ex["context"])
329
+
330
+
331
+ # Advanced introspection commands
332
+
333
+
334
+ @introspect.command("hierarchy")
335
+ @click.argument("dotted_path")
336
+ @click.option("--builtins", is_flag=True, help="Include builtin classes")
337
+ @click.option("--max-depth", "-d", type=int, default=10, help="Max subclass depth")
338
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
339
+ def class_hierarchy(dotted_path, builtins, max_depth, as_json):
340
+ """Get class inheritance hierarchy (MRO + subclasses)"""
341
+ from scitex.introspect import get_class_hierarchy
342
+
343
+ result = get_class_hierarchy(
344
+ dotted_path, include_builtins=builtins, max_depth=max_depth
345
+ )
346
+
347
+ if not result.get("success", False):
348
+ click.secho(f"Error: {result.get('error', 'Unknown error')}", fg="red")
349
+ sys.exit(1)
350
+
351
+ if as_json:
352
+ click.echo(json.dumps(result, indent=2))
353
+ else:
354
+ click.secho(f"Class: {dotted_path}", fg="cyan", bold=True)
355
+ click.secho(f"\nMRO ({result['mro_count']} classes):", fg="yellow")
356
+ for cls in result["mro"]:
357
+ click.echo(f" {cls['qualname']}")
358
+ click.secho(f"\nSubclasses ({result['subclass_count']}):", fg="yellow")
359
+ _print_subclasses(result.get("subclasses", []), indent=2)
360
+
361
+
362
+ def _print_subclasses(subclasses, indent=0):
363
+ """Helper to print subclass tree."""
364
+ for sub in subclasses:
365
+ click.echo(" " * indent + f"- {sub['qualname']}")
366
+ if "subclasses" in sub:
367
+ _print_subclasses(sub["subclasses"], indent + 2)
368
+
369
+
370
+ @introspect.command("hints")
371
+ @click.argument("dotted_path")
372
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
373
+ def type_hints(dotted_path, as_json):
374
+ """Get detailed type hint analysis"""
375
+ from scitex.introspect import get_type_hints_detailed
376
+
377
+ result = get_type_hints_detailed(dotted_path)
378
+
379
+ if not result.get("success", False):
380
+ click.secho(f"Error: {result.get('error', 'Unknown error')}", fg="red")
381
+ sys.exit(1)
382
+
383
+ if as_json:
384
+ click.echo(json.dumps(result, indent=2))
385
+ else:
386
+ click.secho(f"Type hints ({result['hint_count']}):", fg="cyan")
387
+ for name, info in result.get("hints", {}).items():
388
+ opt = " (optional)" if info.get("is_optional") else ""
389
+ click.echo(f" {name}: {info['raw']}{opt}")
390
+ if result.get("return_hint"):
391
+ click.secho(f"\nReturn: {result['return_hint']['raw']}", fg="green")
392
+
393
+
394
+ @introspect.command("imports")
395
+ @click.argument("dotted_path")
396
+ @click.option("--no-categorize", is_flag=True, help="Don't group by category")
397
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
398
+ def imports(dotted_path, no_categorize, as_json):
399
+ """Get all imports from a module (AST-based)"""
400
+ from scitex.introspect import get_imports
401
+
402
+ result = get_imports(dotted_path, categorize=not no_categorize)
403
+
404
+ if not result.get("success", False):
405
+ click.secho(f"Error: {result.get('error', 'Unknown error')}", fg="red")
406
+ sys.exit(1)
407
+
408
+ if as_json:
409
+ click.echo(json.dumps(result, indent=2))
410
+ else:
411
+ click.secho(f"Imports ({result['import_count']}):", fg="cyan")
412
+ if result.get("categories"):
413
+ for cat, imps in result["categories"].items():
414
+ if imps:
415
+ click.secho(f"\n [{cat}] ({len(imps)}):", fg="yellow")
416
+ for imp in imps:
417
+ click.echo(f" {imp['module']}")
418
+ else:
419
+ for imp in result["imports"]:
420
+ click.echo(f" {imp['module']}")
421
+
422
+
423
+ @introspect.command("deps")
424
+ @click.argument("dotted_path")
425
+ @click.option("--recursive", "-r", is_flag=True, help="Recursive analysis")
426
+ @click.option("--max-depth", "-d", type=int, default=3, help="Max recursion depth")
427
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
428
+ def dependencies(dotted_path, recursive, max_depth, as_json):
429
+ """Get module dependencies"""
430
+ from scitex.introspect import get_dependencies
431
+
432
+ result = get_dependencies(dotted_path, recursive=recursive, max_depth=max_depth)
433
+
434
+ if not result.get("success", False):
435
+ click.secho(f"Error: {result.get('error', 'Unknown error')}", fg="red")
436
+ sys.exit(1)
437
+
438
+ if as_json:
439
+ click.echo(json.dumps(result, indent=2))
440
+ else:
441
+ click.secho(f"Dependencies ({result['dependency_count']}):", fg="cyan")
442
+ for dep in result.get("dependencies", []):
443
+ click.echo(f" {dep}")
444
+
445
+
446
+ @introspect.command("calls")
447
+ @click.argument("dotted_path")
448
+ @click.option("--timeout", "-t", type=int, default=10, help="Timeout in seconds")
449
+ @click.option("--all", "all_calls", is_flag=True, help="Include external calls")
450
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
451
+ def call_graph(dotted_path, timeout, all_calls, as_json):
452
+ """Get function call graph (with timeout protection)"""
453
+ from scitex.introspect import get_call_graph
454
+
455
+ result = get_call_graph(
456
+ dotted_path, timeout_seconds=timeout, internal_only=not all_calls
457
+ )
458
+
459
+ if not result.get("success", False):
460
+ click.secho(f"Error: {result.get('error', 'Unknown error')}", fg="red")
461
+ sys.exit(1)
462
+
463
+ if as_json:
464
+ click.echo(json.dumps(result, indent=2))
465
+ else:
466
+ if "calls" in result:
467
+ click.secho(f"Calls ({result['call_count']}):", fg="cyan")
468
+ for call in result["calls"]:
469
+ click.echo(f" -> {call['name']} (line {call['line']})")
470
+ click.secho(f"\nCalled by ({result['caller_count']}):", fg="yellow")
471
+ for caller in result.get("called_by", []):
472
+ click.echo(f" <- {caller['name']} (line {caller['line']})")
473
+ elif "graph" in result:
474
+ click.secho(
475
+ f"Module call graph ({result['function_count']} functions):", fg="cyan"
476
+ )
477
+ for func, info in result["graph"].items():
478
+ calls = ", ".join(c["name"] for c in info["calls"][:5])
479
+ if len(info["calls"]) > 5:
480
+ calls += "..."
481
+ click.echo(f" {func}: {calls or '(no calls)'}")