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.
- scitex/__init__.py +47 -0
- scitex/_env_loader.py +156 -0
- scitex/_mcp_resources/__init__.py +37 -0
- scitex/_mcp_resources/_cheatsheet.py +135 -0
- scitex/_mcp_resources/_figrecipe.py +138 -0
- scitex/_mcp_resources/_formats.py +102 -0
- scitex/_mcp_resources/_modules.py +337 -0
- scitex/_mcp_resources/_session.py +149 -0
- scitex/_mcp_tools/__init__.py +4 -0
- scitex/_mcp_tools/audio.py +66 -0
- scitex/_mcp_tools/diagram.py +11 -95
- scitex/_mcp_tools/introspect.py +191 -0
- scitex/_mcp_tools/plt.py +260 -305
- scitex/_mcp_tools/scholar.py +74 -0
- scitex/_mcp_tools/social.py +244 -0
- scitex/_mcp_tools/writer.py +21 -204
- scitex/ai/_gen_ai/_PARAMS.py +10 -7
- scitex/ai/classification/reporters/_SingleClassificationReporter.py +45 -1603
- scitex/ai/classification/reporters/_mixins/__init__.py +36 -0
- scitex/ai/classification/reporters/_mixins/_constants.py +67 -0
- scitex/ai/classification/reporters/_mixins/_cv_summary.py +387 -0
- scitex/ai/classification/reporters/_mixins/_feature_importance.py +119 -0
- scitex/ai/classification/reporters/_mixins/_metrics.py +275 -0
- scitex/ai/classification/reporters/_mixins/_plotting.py +179 -0
- scitex/ai/classification/reporters/_mixins/_reports.py +153 -0
- scitex/ai/classification/reporters/_mixins/_storage.py +160 -0
- scitex/audio/README.md +40 -36
- scitex/audio/__init__.py +127 -59
- scitex/audio/_branding.py +185 -0
- scitex/audio/_mcp/__init__.py +32 -0
- scitex/audio/_mcp/handlers.py +59 -6
- scitex/audio/_mcp/speak_handlers.py +238 -0
- scitex/audio/_relay.py +225 -0
- scitex/audio/engines/elevenlabs_engine.py +6 -1
- scitex/audio/mcp_server.py +228 -75
- scitex/canvas/README.md +1 -1
- scitex/canvas/editor/_dearpygui/__init__.py +25 -0
- scitex/canvas/editor/_dearpygui/_editor.py +147 -0
- scitex/canvas/editor/_dearpygui/_handlers.py +476 -0
- scitex/canvas/editor/_dearpygui/_panels/__init__.py +17 -0
- scitex/canvas/editor/_dearpygui/_panels/_control.py +119 -0
- scitex/canvas/editor/_dearpygui/_panels/_element_controls.py +190 -0
- scitex/canvas/editor/_dearpygui/_panels/_preview.py +43 -0
- scitex/canvas/editor/_dearpygui/_panels/_sections.py +390 -0
- scitex/canvas/editor/_dearpygui/_plotting.py +187 -0
- scitex/canvas/editor/_dearpygui/_rendering.py +504 -0
- scitex/canvas/editor/_dearpygui/_selection.py +295 -0
- scitex/canvas/editor/_dearpygui/_state.py +93 -0
- scitex/canvas/editor/_dearpygui/_utils.py +61 -0
- scitex/canvas/editor/flask_editor/templates/__init__.py +32 -70
- scitex/cli/__init__.py +38 -43
- scitex/cli/audio.py +76 -27
- scitex/cli/capture.py +13 -20
- scitex/cli/introspect.py +443 -0
- scitex/cli/main.py +198 -109
- scitex/cli/mcp.py +60 -34
- scitex/cli/scholar/__init__.py +8 -0
- scitex/cli/scholar/_crossref_scitex.py +296 -0
- scitex/cli/scholar/_fetch.py +25 -3
- scitex/cli/social.py +314 -0
- scitex/cli/writer.py +117 -0
- scitex/config/README.md +1 -1
- scitex/config/__init__.py +16 -2
- scitex/config/_env_registry.py +191 -0
- scitex/diagram/__init__.py +42 -19
- scitex/diagram/mcp_server.py +13 -125
- scitex/introspect/__init__.py +75 -0
- scitex/introspect/_call_graph.py +303 -0
- scitex/introspect/_class_hierarchy.py +163 -0
- scitex/introspect/_core.py +42 -0
- scitex/introspect/_docstring.py +131 -0
- scitex/introspect/_examples.py +113 -0
- scitex/introspect/_imports.py +271 -0
- scitex/introspect/_mcp/__init__.py +37 -0
- scitex/introspect/_mcp/handlers.py +208 -0
- scitex/introspect/_members.py +151 -0
- scitex/introspect/_resolve.py +89 -0
- scitex/introspect/_signature.py +131 -0
- scitex/introspect/_source.py +80 -0
- scitex/introspect/_type_hints.py +172 -0
- scitex/io/bundle/README.md +1 -1
- scitex/mcp_server.py +98 -5
- scitex/plt/__init__.py +248 -550
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +5 -10
- scitex/plt/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
- scitex/plt/gallery/README.md +1 -1
- scitex/plt/utils/_hitmap/__init__.py +82 -0
- scitex/plt/utils/_hitmap/_artist_extraction.py +343 -0
- scitex/plt/utils/_hitmap/_color_application.py +346 -0
- scitex/plt/utils/_hitmap/_color_conversion.py +121 -0
- scitex/plt/utils/_hitmap/_constants.py +40 -0
- scitex/plt/utils/_hitmap/_hitmap_core.py +334 -0
- scitex/plt/utils/_hitmap/_path_extraction.py +357 -0
- scitex/plt/utils/_hitmap/_query.py +113 -0
- scitex/plt/utils/_hitmap.py +46 -1616
- scitex/plt/utils/_metadata/__init__.py +80 -0
- scitex/plt/utils/_metadata/_artists/__init__.py +25 -0
- scitex/plt/utils/_metadata/_artists/_base.py +195 -0
- scitex/plt/utils/_metadata/_artists/_collections.py +356 -0
- scitex/plt/utils/_metadata/_artists/_extract.py +57 -0
- scitex/plt/utils/_metadata/_artists/_images.py +80 -0
- scitex/plt/utils/_metadata/_artists/_lines.py +261 -0
- scitex/plt/utils/_metadata/_artists/_patches.py +247 -0
- scitex/plt/utils/_metadata/_artists/_text.py +106 -0
- scitex/plt/utils/_metadata/_csv.py +416 -0
- scitex/plt/utils/_metadata/_detect.py +225 -0
- scitex/plt/utils/_metadata/_legend.py +127 -0
- scitex/plt/utils/_metadata/_rounding.py +117 -0
- scitex/plt/utils/_metadata/_verification.py +202 -0
- scitex/schema/README.md +1 -1
- scitex/scholar/__init__.py +8 -0
- scitex/scholar/_mcp/crossref_handlers.py +265 -0
- scitex/scholar/core/Scholar.py +63 -1700
- scitex/scholar/core/_mixins/__init__.py +36 -0
- scitex/scholar/core/_mixins/_enrichers.py +270 -0
- scitex/scholar/core/_mixins/_library_handlers.py +100 -0
- scitex/scholar/core/_mixins/_loaders.py +103 -0
- scitex/scholar/core/_mixins/_pdf_download.py +375 -0
- scitex/scholar/core/_mixins/_pipeline.py +312 -0
- scitex/scholar/core/_mixins/_project_handlers.py +125 -0
- scitex/scholar/core/_mixins/_savers.py +69 -0
- scitex/scholar/core/_mixins/_search.py +103 -0
- scitex/scholar/core/_mixins/_services.py +88 -0
- scitex/scholar/core/_mixins/_url_finding.py +105 -0
- scitex/scholar/crossref_scitex.py +367 -0
- scitex/scholar/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
- scitex/scholar/examples/00_run_all.sh +120 -0
- scitex/scholar/jobs/_executors.py +27 -3
- scitex/scholar/pdf_download/ScholarPDFDownloader.py +38 -416
- scitex/scholar/pdf_download/_cli.py +154 -0
- scitex/scholar/pdf_download/strategies/__init__.py +11 -8
- scitex/scholar/pdf_download/strategies/manual_download_fallback.py +80 -3
- scitex/scholar/pipelines/ScholarPipelineBibTeX.py +73 -121
- scitex/scholar/pipelines/ScholarPipelineParallel.py +80 -138
- scitex/scholar/pipelines/ScholarPipelineSingle.py +43 -63
- scitex/scholar/pipelines/_single_steps.py +71 -36
- scitex/scholar/storage/_LibraryManager.py +97 -1695
- scitex/scholar/storage/_mixins/__init__.py +30 -0
- scitex/scholar/storage/_mixins/_bibtex_handlers.py +128 -0
- scitex/scholar/storage/_mixins/_library_operations.py +218 -0
- scitex/scholar/storage/_mixins/_metadata_conversion.py +226 -0
- scitex/scholar/storage/_mixins/_paper_saving.py +456 -0
- scitex/scholar/storage/_mixins/_resolution.py +376 -0
- scitex/scholar/storage/_mixins/_storage_helpers.py +121 -0
- scitex/scholar/storage/_mixins/_symlink_handlers.py +226 -0
- scitex/scholar/url_finder/.tmp/open_url/KNOWN_RESOLVERS.py +462 -0
- scitex/scholar/url_finder/.tmp/open_url/README.md +223 -0
- scitex/scholar/url_finder/.tmp/open_url/_DOIToURLResolver.py +694 -0
- scitex/scholar/url_finder/.tmp/open_url/_OpenURLResolver.py +1160 -0
- scitex/scholar/url_finder/.tmp/open_url/_ResolverLinkFinder.py +344 -0
- scitex/scholar/url_finder/.tmp/open_url/__init__.py +24 -0
- scitex/security/README.md +3 -3
- scitex/session/README.md +1 -1
- scitex/sh/README.md +1 -1
- scitex/social/__init__.py +153 -0
- scitex/social/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
- scitex/template/README.md +1 -1
- scitex/template/clone_writer_directory.py +5 -5
- scitex/writer/README.md +1 -1
- scitex/writer/_mcp/handlers.py +11 -744
- scitex/writer/_mcp/tool_schemas.py +5 -335
- scitex-2.15.1.dist-info/METADATA +648 -0
- {scitex-2.14.0.dist-info → scitex-2.15.1.dist-info}/RECORD +166 -111
- scitex/canvas/editor/flask_editor/templates/_scripts.py +0 -4933
- scitex/canvas/editor/flask_editor/templates/_styles.py +0 -1658
- scitex/dev/plt/data/mpl/PLOTTING_FUNCTIONS.yaml +0 -90
- scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES.yaml +0 -1571
- scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES_DETAILED.yaml +0 -6262
- scitex/dev/plt/data/mpl/SIGNATURES_FLATTENED.yaml +0 -1274
- scitex/dev/plt/data/mpl/dir_ax.txt +0 -459
- scitex/diagram/_compile.py +0 -312
- scitex/diagram/_diagram.py +0 -355
- scitex/diagram/_mcp/__init__.py +0 -4
- scitex/diagram/_mcp/handlers.py +0 -400
- scitex/diagram/_mcp/tool_schemas.py +0 -157
- scitex/diagram/_presets.py +0 -173
- scitex/diagram/_schema.py +0 -182
- scitex/diagram/_split.py +0 -278
- scitex/plt/_mcp/__init__.py +0 -4
- scitex/plt/_mcp/_handlers_annotation.py +0 -102
- scitex/plt/_mcp/_handlers_figure.py +0 -195
- scitex/plt/_mcp/_handlers_plot.py +0 -252
- scitex/plt/_mcp/_handlers_style.py +0 -219
- scitex/plt/_mcp/handlers.py +0 -74
- scitex/plt/_mcp/tool_schemas.py +0 -497
- scitex/plt/mcp_server.py +0 -231
- scitex/scholar/data/.gitkeep +0 -0
- scitex/scholar/data/README.md +0 -44
- scitex/scholar/data/bib_files/bibliography.bib +0 -1952
- scitex/scholar/data/bib_files/neurovista.bib +0 -277
- scitex/scholar/data/bib_files/neurovista_enriched.bib +0 -441
- scitex/scholar/data/bib_files/neurovista_enriched_enriched.bib +0 -441
- scitex/scholar/data/bib_files/neurovista_processed.bib +0 -338
- scitex/scholar/data/bib_files/openaccess.bib +0 -89
- scitex/scholar/data/bib_files/pac-seizure_prediction_enriched.bib +0 -2178
- scitex/scholar/data/bib_files/pac.bib +0 -698
- scitex/scholar/data/bib_files/pac_enriched.bib +0 -1061
- scitex/scholar/data/bib_files/pac_processed.bib +0 -0
- scitex/scholar/data/bib_files/pac_titles.txt +0 -75
- scitex/scholar/data/bib_files/paywalled.bib +0 -98
- scitex/scholar/data/bib_files/related-papers-by-coauthors.bib +0 -58
- scitex/scholar/data/bib_files/related-papers-by-coauthors_enriched.bib +0 -87
- scitex/scholar/data/bib_files/seizure_prediction.bib +0 -694
- scitex/scholar/data/bib_files/seizure_prediction_processed.bib +0 -0
- scitex/scholar/data/bib_files/test_complete_enriched.bib +0 -437
- scitex/scholar/data/bib_files/test_final_enriched.bib +0 -437
- scitex/scholar/data/bib_files/test_seizure.bib +0 -46
- scitex/scholar/data/impact_factor/JCR_IF_2022.xlsx +0 -0
- scitex/scholar/data/impact_factor/JCR_IF_2024.db +0 -0
- scitex/scholar/data/impact_factor/JCR_IF_2024.xlsx +0 -0
- scitex/scholar/data/impact_factor/JCR_IF_2024_v01.db +0 -0
- scitex/scholar/data/impact_factor.db +0 -0
- scitex/scholar/examples/SUGGESTIONS.md +0 -865
- scitex/scholar/examples/dev.py +0 -38
- scitex-2.14.0.dist-info/METADATA +0 -1238
- {scitex-2.14.0.dist-info → scitex-2.15.1.dist-info}/WHEEL +0 -0
- {scitex-2.14.0.dist-info → scitex-2.15.1.dist-info}/entry_points.txt +0 -0
- {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(
|
|
14
|
-
|
|
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
|
-
|
|
32
|
-
|
|
36
|
+
if help_recursive:
|
|
37
|
+
from . import print_help_recursive
|
|
33
38
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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=
|
|
258
|
+
default=31293,
|
|
266
259
|
type=int,
|
|
267
|
-
help="Port for HTTP/SSE transport (default:
|
|
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
|
|
280
|
+
scitex audio serve -t http --port 31293
|
|
288
281
|
|
|
289
282
|
# SSE server
|
|
290
|
-
scitex audio serve -t sse --port
|
|
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
|
|
287
|
+
1. Local: scitex audio serve -t http --port 31293
|
|
295
288
|
2. SSH: Add to ~/.ssh/config:
|
|
296
|
-
LocalForward
|
|
289
|
+
LocalForward 31293 127.0.0.1:31293
|
|
297
290
|
3. Remote MCP config:
|
|
298
|
-
{"type": "sse", "url": "http://localhost:
|
|
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(
|
|
14
|
-
|
|
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
|
-
|
|
41
|
+
if help_recursive:
|
|
42
|
+
from . import print_help_recursive
|
|
37
43
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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()
|
scitex/cli/introspect.py
ADDED
|
@@ -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)'}")
|