scitex 2.14.0__py3-none-any.whl → 2.15.1__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 (218) hide show
  1. scitex/__init__.py +47 -0
  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 +191 -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/writer.py +21 -204
  17. scitex/ai/_gen_ai/_PARAMS.py +10 -7
  18. scitex/ai/classification/reporters/_SingleClassificationReporter.py +45 -1603
  19. scitex/ai/classification/reporters/_mixins/__init__.py +36 -0
  20. scitex/ai/classification/reporters/_mixins/_constants.py +67 -0
  21. scitex/ai/classification/reporters/_mixins/_cv_summary.py +387 -0
  22. scitex/ai/classification/reporters/_mixins/_feature_importance.py +119 -0
  23. scitex/ai/classification/reporters/_mixins/_metrics.py +275 -0
  24. scitex/ai/classification/reporters/_mixins/_plotting.py +179 -0
  25. scitex/ai/classification/reporters/_mixins/_reports.py +153 -0
  26. scitex/ai/classification/reporters/_mixins/_storage.py +160 -0
  27. scitex/audio/README.md +40 -36
  28. scitex/audio/__init__.py +127 -59
  29. scitex/audio/_branding.py +185 -0
  30. scitex/audio/_mcp/__init__.py +32 -0
  31. scitex/audio/_mcp/handlers.py +59 -6
  32. scitex/audio/_mcp/speak_handlers.py +238 -0
  33. scitex/audio/_relay.py +225 -0
  34. scitex/audio/engines/elevenlabs_engine.py +6 -1
  35. scitex/audio/mcp_server.py +228 -75
  36. scitex/canvas/README.md +1 -1
  37. scitex/canvas/editor/_dearpygui/__init__.py +25 -0
  38. scitex/canvas/editor/_dearpygui/_editor.py +147 -0
  39. scitex/canvas/editor/_dearpygui/_handlers.py +476 -0
  40. scitex/canvas/editor/_dearpygui/_panels/__init__.py +17 -0
  41. scitex/canvas/editor/_dearpygui/_panels/_control.py +119 -0
  42. scitex/canvas/editor/_dearpygui/_panels/_element_controls.py +190 -0
  43. scitex/canvas/editor/_dearpygui/_panels/_preview.py +43 -0
  44. scitex/canvas/editor/_dearpygui/_panels/_sections.py +390 -0
  45. scitex/canvas/editor/_dearpygui/_plotting.py +187 -0
  46. scitex/canvas/editor/_dearpygui/_rendering.py +504 -0
  47. scitex/canvas/editor/_dearpygui/_selection.py +295 -0
  48. scitex/canvas/editor/_dearpygui/_state.py +93 -0
  49. scitex/canvas/editor/_dearpygui/_utils.py +61 -0
  50. scitex/canvas/editor/flask_editor/templates/__init__.py +32 -70
  51. scitex/cli/__init__.py +38 -43
  52. scitex/cli/audio.py +76 -27
  53. scitex/cli/capture.py +13 -20
  54. scitex/cli/introspect.py +443 -0
  55. scitex/cli/main.py +198 -109
  56. scitex/cli/mcp.py +60 -34
  57. scitex/cli/scholar/__init__.py +8 -0
  58. scitex/cli/scholar/_crossref_scitex.py +296 -0
  59. scitex/cli/scholar/_fetch.py +25 -3
  60. scitex/cli/social.py +314 -0
  61. scitex/cli/writer.py +117 -0
  62. scitex/config/README.md +1 -1
  63. scitex/config/__init__.py +16 -2
  64. scitex/config/_env_registry.py +191 -0
  65. scitex/diagram/__init__.py +42 -19
  66. scitex/diagram/mcp_server.py +13 -125
  67. scitex/introspect/__init__.py +75 -0
  68. scitex/introspect/_call_graph.py +303 -0
  69. scitex/introspect/_class_hierarchy.py +163 -0
  70. scitex/introspect/_core.py +42 -0
  71. scitex/introspect/_docstring.py +131 -0
  72. scitex/introspect/_examples.py +113 -0
  73. scitex/introspect/_imports.py +271 -0
  74. scitex/introspect/_mcp/__init__.py +37 -0
  75. scitex/introspect/_mcp/handlers.py +208 -0
  76. scitex/introspect/_members.py +151 -0
  77. scitex/introspect/_resolve.py +89 -0
  78. scitex/introspect/_signature.py +131 -0
  79. scitex/introspect/_source.py +80 -0
  80. scitex/introspect/_type_hints.py +172 -0
  81. scitex/io/bundle/README.md +1 -1
  82. scitex/mcp_server.py +98 -5
  83. scitex/plt/__init__.py +248 -550
  84. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +5 -10
  85. scitex/plt/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  86. scitex/plt/gallery/README.md +1 -1
  87. scitex/plt/utils/_hitmap/__init__.py +82 -0
  88. scitex/plt/utils/_hitmap/_artist_extraction.py +343 -0
  89. scitex/plt/utils/_hitmap/_color_application.py +346 -0
  90. scitex/plt/utils/_hitmap/_color_conversion.py +121 -0
  91. scitex/plt/utils/_hitmap/_constants.py +40 -0
  92. scitex/plt/utils/_hitmap/_hitmap_core.py +334 -0
  93. scitex/plt/utils/_hitmap/_path_extraction.py +357 -0
  94. scitex/plt/utils/_hitmap/_query.py +113 -0
  95. scitex/plt/utils/_hitmap.py +46 -1616
  96. scitex/plt/utils/_metadata/__init__.py +80 -0
  97. scitex/plt/utils/_metadata/_artists/__init__.py +25 -0
  98. scitex/plt/utils/_metadata/_artists/_base.py +195 -0
  99. scitex/plt/utils/_metadata/_artists/_collections.py +356 -0
  100. scitex/plt/utils/_metadata/_artists/_extract.py +57 -0
  101. scitex/plt/utils/_metadata/_artists/_images.py +80 -0
  102. scitex/plt/utils/_metadata/_artists/_lines.py +261 -0
  103. scitex/plt/utils/_metadata/_artists/_patches.py +247 -0
  104. scitex/plt/utils/_metadata/_artists/_text.py +106 -0
  105. scitex/plt/utils/_metadata/_csv.py +416 -0
  106. scitex/plt/utils/_metadata/_detect.py +225 -0
  107. scitex/plt/utils/_metadata/_legend.py +127 -0
  108. scitex/plt/utils/_metadata/_rounding.py +117 -0
  109. scitex/plt/utils/_metadata/_verification.py +202 -0
  110. scitex/schema/README.md +1 -1
  111. scitex/scholar/__init__.py +8 -0
  112. scitex/scholar/_mcp/crossref_handlers.py +265 -0
  113. scitex/scholar/core/Scholar.py +63 -1700
  114. scitex/scholar/core/_mixins/__init__.py +36 -0
  115. scitex/scholar/core/_mixins/_enrichers.py +270 -0
  116. scitex/scholar/core/_mixins/_library_handlers.py +100 -0
  117. scitex/scholar/core/_mixins/_loaders.py +103 -0
  118. scitex/scholar/core/_mixins/_pdf_download.py +375 -0
  119. scitex/scholar/core/_mixins/_pipeline.py +312 -0
  120. scitex/scholar/core/_mixins/_project_handlers.py +125 -0
  121. scitex/scholar/core/_mixins/_savers.py +69 -0
  122. scitex/scholar/core/_mixins/_search.py +103 -0
  123. scitex/scholar/core/_mixins/_services.py +88 -0
  124. scitex/scholar/core/_mixins/_url_finding.py +105 -0
  125. scitex/scholar/crossref_scitex.py +367 -0
  126. scitex/scholar/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  127. scitex/scholar/examples/00_run_all.sh +120 -0
  128. scitex/scholar/jobs/_executors.py +27 -3
  129. scitex/scholar/pdf_download/ScholarPDFDownloader.py +38 -416
  130. scitex/scholar/pdf_download/_cli.py +154 -0
  131. scitex/scholar/pdf_download/strategies/__init__.py +11 -8
  132. scitex/scholar/pdf_download/strategies/manual_download_fallback.py +80 -3
  133. scitex/scholar/pipelines/ScholarPipelineBibTeX.py +73 -121
  134. scitex/scholar/pipelines/ScholarPipelineParallel.py +80 -138
  135. scitex/scholar/pipelines/ScholarPipelineSingle.py +43 -63
  136. scitex/scholar/pipelines/_single_steps.py +71 -36
  137. scitex/scholar/storage/_LibraryManager.py +97 -1695
  138. scitex/scholar/storage/_mixins/__init__.py +30 -0
  139. scitex/scholar/storage/_mixins/_bibtex_handlers.py +128 -0
  140. scitex/scholar/storage/_mixins/_library_operations.py +218 -0
  141. scitex/scholar/storage/_mixins/_metadata_conversion.py +226 -0
  142. scitex/scholar/storage/_mixins/_paper_saving.py +456 -0
  143. scitex/scholar/storage/_mixins/_resolution.py +376 -0
  144. scitex/scholar/storage/_mixins/_storage_helpers.py +121 -0
  145. scitex/scholar/storage/_mixins/_symlink_handlers.py +226 -0
  146. scitex/scholar/url_finder/.tmp/open_url/KNOWN_RESOLVERS.py +462 -0
  147. scitex/scholar/url_finder/.tmp/open_url/README.md +223 -0
  148. scitex/scholar/url_finder/.tmp/open_url/_DOIToURLResolver.py +694 -0
  149. scitex/scholar/url_finder/.tmp/open_url/_OpenURLResolver.py +1160 -0
  150. scitex/scholar/url_finder/.tmp/open_url/_ResolverLinkFinder.py +344 -0
  151. scitex/scholar/url_finder/.tmp/open_url/__init__.py +24 -0
  152. scitex/security/README.md +3 -3
  153. scitex/session/README.md +1 -1
  154. scitex/sh/README.md +1 -1
  155. scitex/social/__init__.py +153 -0
  156. scitex/social/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  157. scitex/template/README.md +1 -1
  158. scitex/template/clone_writer_directory.py +5 -5
  159. scitex/writer/README.md +1 -1
  160. scitex/writer/_mcp/handlers.py +11 -744
  161. scitex/writer/_mcp/tool_schemas.py +5 -335
  162. scitex-2.15.1.dist-info/METADATA +648 -0
  163. {scitex-2.14.0.dist-info → scitex-2.15.1.dist-info}/RECORD +166 -111
  164. scitex/canvas/editor/flask_editor/templates/_scripts.py +0 -4933
  165. scitex/canvas/editor/flask_editor/templates/_styles.py +0 -1658
  166. scitex/dev/plt/data/mpl/PLOTTING_FUNCTIONS.yaml +0 -90
  167. scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES.yaml +0 -1571
  168. scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES_DETAILED.yaml +0 -6262
  169. scitex/dev/plt/data/mpl/SIGNATURES_FLATTENED.yaml +0 -1274
  170. scitex/dev/plt/data/mpl/dir_ax.txt +0 -459
  171. scitex/diagram/_compile.py +0 -312
  172. scitex/diagram/_diagram.py +0 -355
  173. scitex/diagram/_mcp/__init__.py +0 -4
  174. scitex/diagram/_mcp/handlers.py +0 -400
  175. scitex/diagram/_mcp/tool_schemas.py +0 -157
  176. scitex/diagram/_presets.py +0 -173
  177. scitex/diagram/_schema.py +0 -182
  178. scitex/diagram/_split.py +0 -278
  179. scitex/plt/_mcp/__init__.py +0 -4
  180. scitex/plt/_mcp/_handlers_annotation.py +0 -102
  181. scitex/plt/_mcp/_handlers_figure.py +0 -195
  182. scitex/plt/_mcp/_handlers_plot.py +0 -252
  183. scitex/plt/_mcp/_handlers_style.py +0 -219
  184. scitex/plt/_mcp/handlers.py +0 -74
  185. scitex/plt/_mcp/tool_schemas.py +0 -497
  186. scitex/plt/mcp_server.py +0 -231
  187. scitex/scholar/data/.gitkeep +0 -0
  188. scitex/scholar/data/README.md +0 -44
  189. scitex/scholar/data/bib_files/bibliography.bib +0 -1952
  190. scitex/scholar/data/bib_files/neurovista.bib +0 -277
  191. scitex/scholar/data/bib_files/neurovista_enriched.bib +0 -441
  192. scitex/scholar/data/bib_files/neurovista_enriched_enriched.bib +0 -441
  193. scitex/scholar/data/bib_files/neurovista_processed.bib +0 -338
  194. scitex/scholar/data/bib_files/openaccess.bib +0 -89
  195. scitex/scholar/data/bib_files/pac-seizure_prediction_enriched.bib +0 -2178
  196. scitex/scholar/data/bib_files/pac.bib +0 -698
  197. scitex/scholar/data/bib_files/pac_enriched.bib +0 -1061
  198. scitex/scholar/data/bib_files/pac_processed.bib +0 -0
  199. scitex/scholar/data/bib_files/pac_titles.txt +0 -75
  200. scitex/scholar/data/bib_files/paywalled.bib +0 -98
  201. scitex/scholar/data/bib_files/related-papers-by-coauthors.bib +0 -58
  202. scitex/scholar/data/bib_files/related-papers-by-coauthors_enriched.bib +0 -87
  203. scitex/scholar/data/bib_files/seizure_prediction.bib +0 -694
  204. scitex/scholar/data/bib_files/seizure_prediction_processed.bib +0 -0
  205. scitex/scholar/data/bib_files/test_complete_enriched.bib +0 -437
  206. scitex/scholar/data/bib_files/test_final_enriched.bib +0 -437
  207. scitex/scholar/data/bib_files/test_seizure.bib +0 -46
  208. scitex/scholar/data/impact_factor/JCR_IF_2022.xlsx +0 -0
  209. scitex/scholar/data/impact_factor/JCR_IF_2024.db +0 -0
  210. scitex/scholar/data/impact_factor/JCR_IF_2024.xlsx +0 -0
  211. scitex/scholar/data/impact_factor/JCR_IF_2024_v01.db +0 -0
  212. scitex/scholar/data/impact_factor.db +0 -0
  213. scitex/scholar/examples/SUGGESTIONS.md +0 -865
  214. scitex/scholar/examples/dev.py +0 -38
  215. scitex-2.14.0.dist-info/METADATA +0 -1238
  216. {scitex-2.14.0.dist-info → scitex-2.15.1.dist-info}/WHEEL +0 -0
  217. {scitex-2.14.0.dist-info → scitex-2.15.1.dist-info}/entry_points.txt +0 -0
  218. {scitex-2.14.0.dist-info → scitex-2.15.1.dist-info}/licenses/LICENSE +0 -0
scitex/cli/audio.py CHANGED
@@ -10,8 +10,13 @@ import sys
10
10
  import click
11
11
 
12
12
 
13
- @click.group(context_settings={"help_option_names": ["-h", "--help"]})
14
- def audio():
13
+ @click.group(
14
+ context_settings={"help_option_names": ["-h", "--help"]},
15
+ invoke_without_command=True,
16
+ )
17
+ @click.option("--help-recursive", is_flag=True, help="Show help for all subcommands")
18
+ @click.pass_context
19
+ def audio(ctx, help_recursive):
15
20
  """
16
21
  Text-to-speech utilities
17
22
 
@@ -28,25 +33,13 @@ def audio():
28
33
  scitex audio backends # List available backends
29
34
  scitex audio check # Check audio status (WSL)
30
35
  """
31
- pass
32
-
36
+ if help_recursive:
37
+ from . import print_help_recursive
33
38
 
34
- @audio.command("help-recursive")
35
- @click.pass_context
36
- def help_recursive(ctx):
37
- """Show help for all commands recursively."""
38
- fake_parent = click.Context(click.Group(), info_name="scitex")
39
- parent_ctx = click.Context(audio, info_name="audio", parent=fake_parent)
40
- click.secho("━━━ scitex audio ━━━", fg="cyan", bold=True)
41
- click.echo(audio.get_help(parent_ctx))
42
- for name in sorted(audio.list_commands(ctx) or []):
43
- cmd = audio.get_command(ctx, name)
44
- if cmd is None or name == "help-recursive":
45
- continue
46
- click.echo()
47
- click.secho(f"━━━ scitex audio {name} ━━━", fg="cyan", bold=True)
48
- with click.Context(cmd, info_name=name, parent=parent_ctx) as sub_ctx:
49
- click.echo(cmd.get_help(sub_ctx))
39
+ print_help_recursive(ctx, audio)
40
+ ctx.exit(0)
41
+ elif ctx.invoked_subcommand is None:
42
+ click.echo(ctx.get_help())
50
43
 
51
44
 
52
45
  @audio.command()
@@ -262,9 +255,9 @@ def stop():
262
255
  )
263
256
  @click.option(
264
257
  "--port",
265
- default=8084,
258
+ default=31293,
266
259
  type=int,
267
- help="Port for HTTP/SSE transport (default: 8084)",
260
+ help="Port for HTTP/SSE transport (default: 31293)",
268
261
  )
269
262
  def serve(transport, host, port):
270
263
  """
@@ -284,18 +277,18 @@ def serve(transport, host, port):
284
277
  scitex audio serve
285
278
 
286
279
  # HTTP server for remote agents
287
- scitex audio serve -t http --port 8084
280
+ scitex audio serve -t http --port 31293
288
281
 
289
282
  # SSE server
290
- scitex audio serve -t sse --port 8084
283
+ scitex audio serve -t sse --port 31293
291
284
 
292
285
  \b
293
286
  Remote Setup:
294
- 1. Local: scitex audio serve -t http --port 8084
287
+ 1. Local: scitex audio serve -t http --port 31293
295
288
  2. SSH: Add to ~/.ssh/config:
296
- LocalForward 8084 127.0.0.1:8084
289
+ LocalForward 31293 127.0.0.1:31293
297
290
  3. Remote MCP config:
298
- {"type": "sse", "url": "http://localhost:8084/sse"}
291
+ {"type": "sse", "url": "http://localhost:31293/sse"}
299
292
  """
300
293
  try:
301
294
  from scitex.audio.mcp_server import FASTMCP_AVAILABLE, run_server
@@ -327,5 +320,61 @@ def serve(transport, host, port):
327
320
  sys.exit(1)
328
321
 
329
322
 
323
+ @audio.command()
324
+ @click.option(
325
+ "--host",
326
+ default="0.0.0.0",
327
+ help="Host to bind (default: 0.0.0.0)",
328
+ )
329
+ @click.option(
330
+ "--port",
331
+ default=31293,
332
+ type=int,
333
+ help="Port to bind (default: 31293)",
334
+ )
335
+ def relay(host, port):
336
+ """
337
+ Run simple HTTP relay server for remote audio playback
338
+
339
+ Unlike 'serve' (MCP server), this exposes simple REST endpoints
340
+ that remote agents can POST to for audio playback.
341
+
342
+ \b
343
+ Endpoints:
344
+ POST /speak - Play text-to-speech
345
+ GET /health - Health check
346
+ GET /list_backends - List available backends
347
+
348
+ \b
349
+ Example:
350
+ # On your local machine (where you want audio)
351
+ scitex audio relay --port 31293
352
+
353
+ # On remote server, set env var
354
+ export SCITEX_AUDIO_RELAY_URL=http://YOUR_LOCAL_IP:31293
355
+
356
+ # Or use SSH reverse tunnel
357
+ ssh -R 31293:localhost:31293 remote-server
358
+ """
359
+ try:
360
+ from scitex.audio.mcp_server import run_relay_server
361
+
362
+ click.secho(f"Starting audio relay server", fg="cyan")
363
+ click.echo(f" Host: {host}")
364
+ click.echo(f" Port: {port}")
365
+ click.echo()
366
+ click.echo("Endpoints:")
367
+ click.echo(" POST /speak - Play text-to-speech")
368
+ click.echo(" GET /health - Health check")
369
+ click.echo(" GET /list_backends - List backends")
370
+ click.echo()
371
+
372
+ run_relay_server(host=host, port=port)
373
+
374
+ except Exception as e:
375
+ click.secho(f"Error: {e}", fg="red", err=True)
376
+ sys.exit(1)
377
+
378
+
330
379
  if __name__ == "__main__":
331
380
  audio()
scitex/cli/capture.py CHANGED
@@ -10,8 +10,13 @@ import sys
10
10
  import click
11
11
 
12
12
 
13
- @click.group(context_settings={"help_option_names": ["-h", "--help"]})
14
- def capture():
13
+ @click.group(
14
+ context_settings={"help_option_names": ["-h", "--help"]},
15
+ invoke_without_command=True,
16
+ )
17
+ @click.option("--help-recursive", is_flag=True, help="Show help for all subcommands")
18
+ @click.pass_context
19
+ def capture(ctx, help_recursive):
15
20
  """
16
21
  Screen capture and monitoring utilities
17
22
 
@@ -33,25 +38,13 @@ def capture():
33
38
  scitex capture gif # Create GIF from latest session
34
39
  scitex capture info # List monitors and windows
35
40
  """
36
- pass
41
+ if help_recursive:
42
+ from . import print_help_recursive
37
43
 
38
-
39
- @capture.command("help-recursive")
40
- @click.pass_context
41
- def help_recursive(ctx):
42
- """Show help for all commands recursively."""
43
- fake_parent = click.Context(click.Group(), info_name="scitex")
44
- parent_ctx = click.Context(capture, info_name="capture", parent=fake_parent)
45
- click.secho("━━━ scitex capture ━━━", fg="cyan", bold=True)
46
- click.echo(capture.get_help(parent_ctx))
47
- for name in sorted(capture.list_commands(ctx) or []):
48
- cmd = capture.get_command(ctx, name)
49
- if cmd is None or name == "help-recursive":
50
- continue
51
- click.echo()
52
- click.secho(f"━━━ scitex capture {name} ━━━", fg="cyan", bold=True)
53
- with click.Context(cmd, info_name=name, parent=parent_ctx) as sub_ctx:
54
- click.echo(cmd.get_help(sub_ctx))
44
+ print_help_recursive(ctx, capture)
45
+ ctx.exit(0)
46
+ elif ctx.invoked_subcommand is None:
47
+ click.echo(ctx.get_help())
55
48
 
56
49
 
57
50
  @capture.command()
@@ -0,0 +1,443 @@
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
+ signature - Function/class signature (like func?)
27
+ docstring - Extract docstrings
28
+ source - Full source code (like func??)
29
+ members - List module/class members (like dir())
30
+ exports - Show __all__ exports
31
+ examples - Find usage examples
32
+
33
+ \b
34
+ Examples:
35
+ scitex introspect signature scitex.plt.plot
36
+ scitex introspect docstring scitex.audio.speak --format parsed
37
+ scitex introspect source scitex.stats.run_test --max-lines 50
38
+ scitex introspect members scitex.plt --kind functions
39
+ scitex introspect exports scitex.audio
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 signature(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 signature scitex.plt.plot
62
+ scitex introspect signature scitex.audio.speak --json
63
+ scitex introspect signature json.dumps
64
+ """
65
+ from scitex.introspect import get_signature
66
+
67
+ result = get_signature(
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(
95
+ "--format",
96
+ "-f",
97
+ type=click.Choice(["raw", "parsed", "summary"]),
98
+ default="raw",
99
+ help="Output format",
100
+ )
101
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
102
+ def docstring(dotted_path, format, as_json):
103
+ """
104
+ Get docstring of a Python object
105
+
106
+ \b
107
+ Formats:
108
+ raw - Full docstring as-is
109
+ parsed - Parse into sections (summary, parameters, returns, etc.)
110
+ summary - First line/paragraph only
111
+
112
+ \b
113
+ Examples:
114
+ scitex introspect docstring scitex.plt.plot
115
+ scitex introspect docstring scitex.audio.speak --format parsed
116
+ """
117
+ from scitex.introspect import get_docstring
118
+
119
+ result = get_docstring(dotted_path, format=format)
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.echo(result["docstring"])
129
+ if format == "parsed" and result.get("sections"):
130
+ click.echo("\n--- Parsed Sections ---")
131
+ for key, value in result["sections"].items():
132
+ if value:
133
+ click.secho(f"\n[{key}]", fg="cyan", bold=True)
134
+ click.echo(value)
135
+
136
+
137
+ @introspect.command()
138
+ @click.argument("dotted_path")
139
+ @click.option("--max-lines", "-n", type=int, help="Limit output to N lines")
140
+ @click.option("--no-decorators", is_flag=True, help="Exclude decorator lines")
141
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
142
+ def source(dotted_path, max_lines, no_decorators, as_json):
143
+ """
144
+ Get source code of a Python object (like IPython's func??)
145
+
146
+ \b
147
+ Examples:
148
+ scitex introspect source scitex.plt.plot
149
+ scitex introspect source scitex.audio.speak --max-lines 50
150
+ """
151
+ from scitex.introspect import get_source
152
+
153
+ result = get_source(
154
+ dotted_path,
155
+ max_lines=max_lines,
156
+ include_decorators=not no_decorators,
157
+ )
158
+
159
+ if not result.get("success", False):
160
+ click.secho(f"Error: {result.get('error', 'Unknown error')}", fg="red")
161
+ sys.exit(1)
162
+
163
+ if as_json:
164
+ click.echo(json.dumps(result, indent=2))
165
+ else:
166
+ click.secho(f"# File: {result['file']}:{result['line_start']}", fg="cyan")
167
+ click.secho(f"# Lines: {result['line_count']}", fg="cyan")
168
+ click.echo()
169
+ click.echo(result["source"])
170
+
171
+
172
+ @introspect.command()
173
+ @click.argument("dotted_path")
174
+ @click.option(
175
+ "--filter",
176
+ "-f",
177
+ type=click.Choice(["all", "public", "private", "dunder"]),
178
+ default="public",
179
+ help="Filter members",
180
+ )
181
+ @click.option(
182
+ "--kind",
183
+ "-k",
184
+ type=click.Choice(["all", "functions", "classes", "data", "modules"]),
185
+ help="Filter by type",
186
+ )
187
+ @click.option("--inherited", is_flag=True, help="Include inherited members (classes)")
188
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
189
+ def members(dotted_path, filter, kind, inherited, as_json):
190
+ """
191
+ List members of a module or class (like dir())
192
+
193
+ \b
194
+ Examples:
195
+ scitex introspect members scitex.plt
196
+ scitex introspect members scitex.audio --kind functions
197
+ scitex introspect members scitex.plt.AxisWrapper --filter all
198
+ """
199
+ from scitex.introspect import list_members
200
+
201
+ result = list_members(
202
+ dotted_path,
203
+ filter=filter,
204
+ kind=kind,
205
+ include_inherited=inherited,
206
+ )
207
+
208
+ if not result.get("success", False):
209
+ click.secho(f"Error: {result.get('error', 'Unknown error')}", fg="red")
210
+ sys.exit(1)
211
+
212
+ if as_json:
213
+ click.echo(json.dumps(result, indent=2))
214
+ else:
215
+ click.secho(f"Members of {dotted_path} ({result['count']}):", fg="cyan")
216
+ for m in result["members"]:
217
+ kind_str = click.style(f"[{m['kind']}]", fg="yellow")
218
+ name_str = click.style(m["name"], fg="green", bold=True)
219
+ summary = f" - {m['summary']}" if m["summary"] else ""
220
+ click.echo(f" {kind_str} {name_str}{summary}")
221
+
222
+
223
+ @introspect.command()
224
+ @click.argument("dotted_path")
225
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
226
+ def exports(dotted_path, as_json):
227
+ """
228
+ Get __all__ exports of a module
229
+
230
+ \b
231
+ Examples:
232
+ scitex introspect exports scitex.audio
233
+ scitex introspect exports scitex.plt
234
+ """
235
+ from scitex.introspect import get_exports
236
+
237
+ result = get_exports(dotted_path)
238
+
239
+ if not result.get("success", False):
240
+ click.secho(f"Error: {result.get('error', 'Unknown error')}", fg="red")
241
+ sys.exit(1)
242
+
243
+ if as_json:
244
+ click.echo(json.dumps(result, indent=2))
245
+ else:
246
+ has_all = "defined" if result["has_all"] else "not defined (showing public)"
247
+ click.secho(f"__all__ is {has_all}", fg="cyan")
248
+ click.secho(f"Exports ({result['count']}):", fg="cyan")
249
+ for name in result["exports"]:
250
+ click.echo(f" {name}")
251
+
252
+
253
+ @introspect.command()
254
+ @click.argument("dotted_path")
255
+ @click.option("--search-paths", "-p", help="Comma-separated search paths")
256
+ @click.option("--max-results", "-n", type=int, default=10, help="Max examples")
257
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
258
+ def examples(dotted_path, search_paths, max_results, as_json):
259
+ """
260
+ Find usage examples in tests/examples directories
261
+
262
+ \b
263
+ Examples:
264
+ scitex introspect examples scitex.plt.plot
265
+ scitex introspect examples scitex.audio.speak --max-results 5
266
+ """
267
+ from scitex.introspect import find_examples
268
+
269
+ paths_list = None
270
+ if search_paths:
271
+ paths_list = [p.strip() for p in search_paths.split(",")]
272
+
273
+ result = find_examples(
274
+ dotted_path,
275
+ search_paths=paths_list,
276
+ max_results=max_results,
277
+ )
278
+
279
+ if not result.get("success", False):
280
+ click.secho(f"Error: {result.get('error', 'Unknown error')}", fg="red")
281
+ sys.exit(1)
282
+
283
+ if as_json:
284
+ click.echo(json.dumps(result, indent=2))
285
+ else:
286
+ click.secho(f"Found {result['count']} examples:", fg="cyan")
287
+ for ex in result["examples"]:
288
+ click.echo()
289
+ click.secho(f"--- {ex['file']}:{ex['line']} ---", fg="yellow")
290
+ click.echo(ex["context"])
291
+
292
+
293
+ # Advanced introspection commands
294
+
295
+
296
+ @introspect.command("hierarchy")
297
+ @click.argument("dotted_path")
298
+ @click.option("--builtins", is_flag=True, help="Include builtin classes")
299
+ @click.option("--max-depth", "-d", type=int, default=10, help="Max subclass depth")
300
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
301
+ def class_hierarchy(dotted_path, builtins, max_depth, as_json):
302
+ """Get class inheritance hierarchy (MRO + subclasses)"""
303
+ from scitex.introspect import get_class_hierarchy
304
+
305
+ result = get_class_hierarchy(
306
+ dotted_path, include_builtins=builtins, max_depth=max_depth
307
+ )
308
+
309
+ if not result.get("success", False):
310
+ click.secho(f"Error: {result.get('error', 'Unknown error')}", fg="red")
311
+ sys.exit(1)
312
+
313
+ if as_json:
314
+ click.echo(json.dumps(result, indent=2))
315
+ else:
316
+ click.secho(f"Class: {dotted_path}", fg="cyan", bold=True)
317
+ click.secho(f"\nMRO ({result['mro_count']} classes):", fg="yellow")
318
+ for cls in result["mro"]:
319
+ click.echo(f" {cls['qualname']}")
320
+ click.secho(f"\nSubclasses ({result['subclass_count']}):", fg="yellow")
321
+ _print_subclasses(result.get("subclasses", []), indent=2)
322
+
323
+
324
+ def _print_subclasses(subclasses, indent=0):
325
+ """Helper to print subclass tree."""
326
+ for sub in subclasses:
327
+ click.echo(" " * indent + f"- {sub['qualname']}")
328
+ if "subclasses" in sub:
329
+ _print_subclasses(sub["subclasses"], indent + 2)
330
+
331
+
332
+ @introspect.command("hints")
333
+ @click.argument("dotted_path")
334
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
335
+ def type_hints(dotted_path, as_json):
336
+ """Get detailed type hint analysis"""
337
+ from scitex.introspect import get_type_hints_detailed
338
+
339
+ result = get_type_hints_detailed(dotted_path)
340
+
341
+ if not result.get("success", False):
342
+ click.secho(f"Error: {result.get('error', 'Unknown error')}", fg="red")
343
+ sys.exit(1)
344
+
345
+ if as_json:
346
+ click.echo(json.dumps(result, indent=2))
347
+ else:
348
+ click.secho(f"Type hints ({result['hint_count']}):", fg="cyan")
349
+ for name, info in result.get("hints", {}).items():
350
+ opt = " (optional)" if info.get("is_optional") else ""
351
+ click.echo(f" {name}: {info['raw']}{opt}")
352
+ if result.get("return_hint"):
353
+ click.secho(f"\nReturn: {result['return_hint']['raw']}", fg="green")
354
+
355
+
356
+ @introspect.command("imports")
357
+ @click.argument("dotted_path")
358
+ @click.option("--no-categorize", is_flag=True, help="Don't group by category")
359
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
360
+ def imports(dotted_path, no_categorize, as_json):
361
+ """Get all imports from a module (AST-based)"""
362
+ from scitex.introspect import get_imports
363
+
364
+ result = get_imports(dotted_path, categorize=not no_categorize)
365
+
366
+ if not result.get("success", False):
367
+ click.secho(f"Error: {result.get('error', 'Unknown error')}", fg="red")
368
+ sys.exit(1)
369
+
370
+ if as_json:
371
+ click.echo(json.dumps(result, indent=2))
372
+ else:
373
+ click.secho(f"Imports ({result['import_count']}):", fg="cyan")
374
+ if result.get("categories"):
375
+ for cat, imps in result["categories"].items():
376
+ if imps:
377
+ click.secho(f"\n [{cat}] ({len(imps)}):", fg="yellow")
378
+ for imp in imps:
379
+ click.echo(f" {imp['module']}")
380
+ else:
381
+ for imp in result["imports"]:
382
+ click.echo(f" {imp['module']}")
383
+
384
+
385
+ @introspect.command("deps")
386
+ @click.argument("dotted_path")
387
+ @click.option("--recursive", "-r", is_flag=True, help="Recursive analysis")
388
+ @click.option("--max-depth", "-d", type=int, default=3, help="Max recursion depth")
389
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
390
+ def dependencies(dotted_path, recursive, max_depth, as_json):
391
+ """Get module dependencies"""
392
+ from scitex.introspect import get_dependencies
393
+
394
+ result = get_dependencies(dotted_path, recursive=recursive, max_depth=max_depth)
395
+
396
+ if not result.get("success", False):
397
+ click.secho(f"Error: {result.get('error', 'Unknown error')}", fg="red")
398
+ sys.exit(1)
399
+
400
+ if as_json:
401
+ click.echo(json.dumps(result, indent=2))
402
+ else:
403
+ click.secho(f"Dependencies ({result['dependency_count']}):", fg="cyan")
404
+ for dep in result.get("dependencies", []):
405
+ click.echo(f" {dep}")
406
+
407
+
408
+ @introspect.command("calls")
409
+ @click.argument("dotted_path")
410
+ @click.option("--timeout", "-t", type=int, default=10, help="Timeout in seconds")
411
+ @click.option("--all", "all_calls", is_flag=True, help="Include external calls")
412
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
413
+ def call_graph(dotted_path, timeout, all_calls, as_json):
414
+ """Get function call graph (with timeout protection)"""
415
+ from scitex.introspect import get_call_graph
416
+
417
+ result = get_call_graph(
418
+ dotted_path, timeout_seconds=timeout, internal_only=not all_calls
419
+ )
420
+
421
+ if not result.get("success", False):
422
+ click.secho(f"Error: {result.get('error', 'Unknown error')}", fg="red")
423
+ sys.exit(1)
424
+
425
+ if as_json:
426
+ click.echo(json.dumps(result, indent=2))
427
+ else:
428
+ if "calls" in result:
429
+ click.secho(f"Calls ({result['call_count']}):", fg="cyan")
430
+ for call in result["calls"]:
431
+ click.echo(f" -> {call['name']} (line {call['line']})")
432
+ click.secho(f"\nCalled by ({result['caller_count']}):", fg="yellow")
433
+ for caller in result.get("called_by", []):
434
+ click.echo(f" <- {caller['name']} (line {caller['line']})")
435
+ elif "graph" in result:
436
+ click.secho(
437
+ f"Module call graph ({result['function_count']} functions):", fg="cyan"
438
+ )
439
+ for func, info in result["graph"].items():
440
+ calls = ", ".join(c["name"] for c in info["calls"][:5])
441
+ if len(info["calls"]) > 5:
442
+ calls += "..."
443
+ click.echo(f" {func}: {calls or '(no calls)'}")