scitex 2.14.0__py3-none-any.whl → 2.15.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (264) hide show
  1. scitex/__init__.py +71 -17
  2. scitex/_env_loader.py +156 -0
  3. scitex/_mcp_resources/__init__.py +37 -0
  4. scitex/_mcp_resources/_cheatsheet.py +135 -0
  5. scitex/_mcp_resources/_figrecipe.py +138 -0
  6. scitex/_mcp_resources/_formats.py +102 -0
  7. scitex/_mcp_resources/_modules.py +337 -0
  8. scitex/_mcp_resources/_session.py +149 -0
  9. scitex/_mcp_tools/__init__.py +4 -0
  10. scitex/_mcp_tools/audio.py +66 -0
  11. scitex/_mcp_tools/diagram.py +11 -95
  12. scitex/_mcp_tools/introspect.py +210 -0
  13. scitex/_mcp_tools/plt.py +260 -305
  14. scitex/_mcp_tools/scholar.py +74 -0
  15. scitex/_mcp_tools/social.py +27 -0
  16. scitex/_mcp_tools/template.py +24 -0
  17. scitex/_mcp_tools/writer.py +17 -210
  18. scitex/ai/_gen_ai/_PARAMS.py +10 -7
  19. scitex/ai/classification/reporters/_SingleClassificationReporter.py +45 -1603
  20. scitex/ai/classification/reporters/_mixins/__init__.py +36 -0
  21. scitex/ai/classification/reporters/_mixins/_constants.py +67 -0
  22. scitex/ai/classification/reporters/_mixins/_cv_summary.py +387 -0
  23. scitex/ai/classification/reporters/_mixins/_feature_importance.py +119 -0
  24. scitex/ai/classification/reporters/_mixins/_metrics.py +275 -0
  25. scitex/ai/classification/reporters/_mixins/_plotting.py +179 -0
  26. scitex/ai/classification/reporters/_mixins/_reports.py +153 -0
  27. scitex/ai/classification/reporters/_mixins/_storage.py +160 -0
  28. scitex/ai/classification/timeseries/_TimeSeriesSlidingWindowSplit.py +30 -1550
  29. scitex/ai/classification/timeseries/_sliding_window_core.py +467 -0
  30. scitex/ai/classification/timeseries/_sliding_window_plotting.py +369 -0
  31. scitex/audio/README.md +40 -36
  32. scitex/audio/__init__.py +129 -61
  33. scitex/audio/_branding.py +185 -0
  34. scitex/audio/_mcp/__init__.py +32 -0
  35. scitex/audio/_mcp/handlers.py +59 -6
  36. scitex/audio/_mcp/speak_handlers.py +238 -0
  37. scitex/audio/_relay.py +225 -0
  38. scitex/audio/_tts.py +18 -10
  39. scitex/audio/engines/base.py +17 -10
  40. scitex/audio/engines/elevenlabs_engine.py +7 -2
  41. scitex/audio/mcp_server.py +228 -75
  42. scitex/canvas/README.md +1 -1
  43. scitex/canvas/editor/_dearpygui/__init__.py +25 -0
  44. scitex/canvas/editor/_dearpygui/_editor.py +147 -0
  45. scitex/canvas/editor/_dearpygui/_handlers.py +476 -0
  46. scitex/canvas/editor/_dearpygui/_panels/__init__.py +17 -0
  47. scitex/canvas/editor/_dearpygui/_panels/_control.py +119 -0
  48. scitex/canvas/editor/_dearpygui/_panels/_element_controls.py +190 -0
  49. scitex/canvas/editor/_dearpygui/_panels/_preview.py +43 -0
  50. scitex/canvas/editor/_dearpygui/_panels/_sections.py +390 -0
  51. scitex/canvas/editor/_dearpygui/_plotting.py +187 -0
  52. scitex/canvas/editor/_dearpygui/_rendering.py +504 -0
  53. scitex/canvas/editor/_dearpygui/_selection.py +295 -0
  54. scitex/canvas/editor/_dearpygui/_state.py +93 -0
  55. scitex/canvas/editor/_dearpygui/_utils.py +61 -0
  56. scitex/canvas/editor/flask_editor/_core/__init__.py +27 -0
  57. scitex/canvas/editor/flask_editor/_core/_bbox_extraction.py +200 -0
  58. scitex/canvas/editor/flask_editor/_core/_editor.py +173 -0
  59. scitex/canvas/editor/flask_editor/_core/_export_helpers.py +353 -0
  60. scitex/canvas/editor/flask_editor/_core/_routes_basic.py +190 -0
  61. scitex/canvas/editor/flask_editor/_core/_routes_export.py +332 -0
  62. scitex/canvas/editor/flask_editor/_core/_routes_panels.py +252 -0
  63. scitex/canvas/editor/flask_editor/_core/_routes_save.py +218 -0
  64. scitex/canvas/editor/flask_editor/_core.py +25 -1684
  65. scitex/canvas/editor/flask_editor/templates/__init__.py +32 -70
  66. scitex/cli/__init__.py +38 -43
  67. scitex/cli/audio.py +160 -41
  68. scitex/cli/capture.py +133 -20
  69. scitex/cli/introspect.py +488 -0
  70. scitex/cli/main.py +200 -109
  71. scitex/cli/mcp.py +60 -34
  72. scitex/cli/plt.py +414 -0
  73. scitex/cli/repro.py +15 -8
  74. scitex/cli/resource.py +15 -8
  75. scitex/cli/scholar/__init__.py +154 -8
  76. scitex/cli/scholar/_crossref_scitex.py +296 -0
  77. scitex/cli/scholar/_fetch.py +25 -3
  78. scitex/cli/social.py +355 -0
  79. scitex/cli/stats.py +136 -11
  80. scitex/cli/template.py +129 -12
  81. scitex/cli/tex.py +15 -8
  82. scitex/cli/writer.py +49 -299
  83. scitex/cloud/__init__.py +41 -2
  84. scitex/config/README.md +1 -1
  85. scitex/config/__init__.py +16 -2
  86. scitex/config/_env_registry.py +256 -0
  87. scitex/context/__init__.py +22 -0
  88. scitex/dev/__init__.py +20 -1
  89. scitex/diagram/__init__.py +42 -19
  90. scitex/diagram/mcp_server.py +13 -125
  91. scitex/gen/__init__.py +50 -14
  92. scitex/gen/_list_packages.py +4 -4
  93. scitex/introspect/__init__.py +82 -0
  94. scitex/introspect/_call_graph.py +303 -0
  95. scitex/introspect/_class_hierarchy.py +163 -0
  96. scitex/introspect/_core.py +41 -0
  97. scitex/introspect/_docstring.py +131 -0
  98. scitex/introspect/_examples.py +113 -0
  99. scitex/introspect/_imports.py +271 -0
  100. scitex/{gen/_inspect_module.py → introspect/_list_api.py} +48 -56
  101. scitex/introspect/_mcp/__init__.py +41 -0
  102. scitex/introspect/_mcp/handlers.py +233 -0
  103. scitex/introspect/_members.py +155 -0
  104. scitex/introspect/_resolve.py +89 -0
  105. scitex/introspect/_signature.py +131 -0
  106. scitex/introspect/_source.py +80 -0
  107. scitex/introspect/_type_hints.py +172 -0
  108. scitex/io/_save.py +1 -2
  109. scitex/io/bundle/README.md +1 -1
  110. scitex/logging/_formatters.py +19 -9
  111. scitex/mcp_server.py +98 -5
  112. scitex/os/__init__.py +4 -0
  113. scitex/{gen → os}/_check_host.py +4 -5
  114. scitex/plt/__init__.py +245 -550
  115. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +5 -10
  116. scitex/plt/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  117. scitex/plt/gallery/README.md +1 -1
  118. scitex/plt/utils/_hitmap/__init__.py +82 -0
  119. scitex/plt/utils/_hitmap/_artist_extraction.py +343 -0
  120. scitex/plt/utils/_hitmap/_color_application.py +346 -0
  121. scitex/plt/utils/_hitmap/_color_conversion.py +121 -0
  122. scitex/plt/utils/_hitmap/_constants.py +40 -0
  123. scitex/plt/utils/_hitmap/_hitmap_core.py +334 -0
  124. scitex/plt/utils/_hitmap/_path_extraction.py +357 -0
  125. scitex/plt/utils/_hitmap/_query.py +113 -0
  126. scitex/plt/utils/_hitmap.py +46 -1616
  127. scitex/plt/utils/_metadata/__init__.py +80 -0
  128. scitex/plt/utils/_metadata/_artists/__init__.py +25 -0
  129. scitex/plt/utils/_metadata/_artists/_base.py +195 -0
  130. scitex/plt/utils/_metadata/_artists/_collections.py +356 -0
  131. scitex/plt/utils/_metadata/_artists/_extract.py +57 -0
  132. scitex/plt/utils/_metadata/_artists/_images.py +80 -0
  133. scitex/plt/utils/_metadata/_artists/_lines.py +261 -0
  134. scitex/plt/utils/_metadata/_artists/_patches.py +247 -0
  135. scitex/plt/utils/_metadata/_artists/_text.py +106 -0
  136. scitex/plt/utils/_metadata/_csv.py +416 -0
  137. scitex/plt/utils/_metadata/_detect.py +225 -0
  138. scitex/plt/utils/_metadata/_legend.py +127 -0
  139. scitex/plt/utils/_metadata/_rounding.py +117 -0
  140. scitex/plt/utils/_metadata/_verification.py +202 -0
  141. scitex/schema/README.md +1 -1
  142. scitex/scholar/__init__.py +8 -0
  143. scitex/scholar/_mcp/crossref_handlers.py +265 -0
  144. scitex/scholar/core/Scholar.py +63 -1700
  145. scitex/scholar/core/_mixins/__init__.py +36 -0
  146. scitex/scholar/core/_mixins/_enrichers.py +270 -0
  147. scitex/scholar/core/_mixins/_library_handlers.py +100 -0
  148. scitex/scholar/core/_mixins/_loaders.py +103 -0
  149. scitex/scholar/core/_mixins/_pdf_download.py +375 -0
  150. scitex/scholar/core/_mixins/_pipeline.py +312 -0
  151. scitex/scholar/core/_mixins/_project_handlers.py +125 -0
  152. scitex/scholar/core/_mixins/_savers.py +69 -0
  153. scitex/scholar/core/_mixins/_search.py +103 -0
  154. scitex/scholar/core/_mixins/_services.py +88 -0
  155. scitex/scholar/core/_mixins/_url_finding.py +105 -0
  156. scitex/scholar/crossref_scitex.py +367 -0
  157. scitex/scholar/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  158. scitex/scholar/examples/00_run_all.sh +120 -0
  159. scitex/scholar/jobs/_executors.py +27 -3
  160. scitex/scholar/pdf_download/ScholarPDFDownloader.py +38 -416
  161. scitex/scholar/pdf_download/_cli.py +154 -0
  162. scitex/scholar/pdf_download/strategies/__init__.py +11 -8
  163. scitex/scholar/pdf_download/strategies/manual_download_fallback.py +80 -3
  164. scitex/scholar/pipelines/ScholarPipelineBibTeX.py +73 -121
  165. scitex/scholar/pipelines/ScholarPipelineParallel.py +80 -138
  166. scitex/scholar/pipelines/ScholarPipelineSingle.py +43 -63
  167. scitex/scholar/pipelines/_single_steps.py +71 -36
  168. scitex/scholar/storage/_LibraryManager.py +97 -1695
  169. scitex/scholar/storage/_mixins/__init__.py +30 -0
  170. scitex/scholar/storage/_mixins/_bibtex_handlers.py +128 -0
  171. scitex/scholar/storage/_mixins/_library_operations.py +218 -0
  172. scitex/scholar/storage/_mixins/_metadata_conversion.py +226 -0
  173. scitex/scholar/storage/_mixins/_paper_saving.py +456 -0
  174. scitex/scholar/storage/_mixins/_resolution.py +376 -0
  175. scitex/scholar/storage/_mixins/_storage_helpers.py +121 -0
  176. scitex/scholar/storage/_mixins/_symlink_handlers.py +226 -0
  177. scitex/security/README.md +3 -3
  178. scitex/session/README.md +1 -1
  179. scitex/session/__init__.py +26 -7
  180. scitex/session/_decorator.py +1 -1
  181. scitex/sh/README.md +1 -1
  182. scitex/sh/__init__.py +7 -4
  183. scitex/social/__init__.py +155 -0
  184. scitex/social/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  185. scitex/stats/_mcp/_handlers/__init__.py +31 -0
  186. scitex/stats/_mcp/_handlers/_corrections.py +113 -0
  187. scitex/stats/_mcp/_handlers/_descriptive.py +78 -0
  188. scitex/stats/_mcp/_handlers/_effect_size.py +106 -0
  189. scitex/stats/_mcp/_handlers/_format.py +94 -0
  190. scitex/stats/_mcp/_handlers/_normality.py +110 -0
  191. scitex/stats/_mcp/_handlers/_posthoc.py +224 -0
  192. scitex/stats/_mcp/_handlers/_power.py +247 -0
  193. scitex/stats/_mcp/_handlers/_recommend.py +102 -0
  194. scitex/stats/_mcp/_handlers/_run_test.py +279 -0
  195. scitex/stats/_mcp/_handlers/_stars.py +48 -0
  196. scitex/stats/_mcp/handlers.py +19 -1171
  197. scitex/stats/auto/_stat_style.py +175 -0
  198. scitex/stats/auto/_style_definitions.py +411 -0
  199. scitex/stats/auto/_styles.py +22 -620
  200. scitex/stats/descriptive/__init__.py +11 -8
  201. scitex/stats/descriptive/_ci.py +39 -0
  202. scitex/stats/power/_power.py +15 -4
  203. scitex/str/__init__.py +2 -1
  204. scitex/str/_title_case.py +63 -0
  205. scitex/template/README.md +1 -1
  206. scitex/template/__init__.py +25 -10
  207. scitex/template/_code_templates.py +147 -0
  208. scitex/template/_mcp/handlers.py +81 -0
  209. scitex/template/_mcp/tool_schemas.py +55 -0
  210. scitex/template/_templates/__init__.py +51 -0
  211. scitex/template/_templates/audio.py +233 -0
  212. scitex/template/_templates/canvas.py +312 -0
  213. scitex/template/_templates/capture.py +268 -0
  214. scitex/template/_templates/config.py +43 -0
  215. scitex/template/_templates/diagram.py +294 -0
  216. scitex/template/_templates/io.py +107 -0
  217. scitex/template/_templates/module.py +53 -0
  218. scitex/template/_templates/plt.py +202 -0
  219. scitex/template/_templates/scholar.py +267 -0
  220. scitex/template/_templates/session.py +130 -0
  221. scitex/template/_templates/session_minimal.py +43 -0
  222. scitex/template/_templates/session_plot.py +67 -0
  223. scitex/template/_templates/session_stats.py +77 -0
  224. scitex/template/_templates/stats.py +323 -0
  225. scitex/template/_templates/writer.py +296 -0
  226. scitex/template/clone_writer_directory.py +5 -5
  227. scitex/ui/_backends/_email.py +10 -2
  228. scitex/ui/_backends/_webhook.py +5 -1
  229. scitex/web/_search_pubmed.py +10 -6
  230. scitex/writer/README.md +1 -1
  231. scitex/writer/__init__.py +43 -34
  232. scitex/writer/_mcp/handlers.py +11 -744
  233. scitex/writer/_mcp/tool_schemas.py +5 -335
  234. scitex-2.15.3.dist-info/METADATA +667 -0
  235. {scitex-2.14.0.dist-info → scitex-2.15.3.dist-info}/RECORD +241 -120
  236. scitex/canvas/editor/flask_editor/templates/_scripts.py +0 -4933
  237. scitex/canvas/editor/flask_editor/templates/_styles.py +0 -1658
  238. scitex/diagram/_compile.py +0 -312
  239. scitex/diagram/_diagram.py +0 -355
  240. scitex/diagram/_mcp/__init__.py +0 -4
  241. scitex/diagram/_mcp/handlers.py +0 -400
  242. scitex/diagram/_mcp/tool_schemas.py +0 -157
  243. scitex/diagram/_presets.py +0 -173
  244. scitex/diagram/_schema.py +0 -182
  245. scitex/diagram/_split.py +0 -278
  246. scitex/gen/_ci.py +0 -12
  247. scitex/gen/_title_case.py +0 -89
  248. scitex/plt/_mcp/__init__.py +0 -4
  249. scitex/plt/_mcp/_handlers_annotation.py +0 -102
  250. scitex/plt/_mcp/_handlers_figure.py +0 -195
  251. scitex/plt/_mcp/_handlers_plot.py +0 -252
  252. scitex/plt/_mcp/_handlers_style.py +0 -219
  253. scitex/plt/_mcp/handlers.py +0 -74
  254. scitex/plt/_mcp/tool_schemas.py +0 -497
  255. scitex/plt/mcp_server.py +0 -231
  256. scitex/scholar/examples/SUGGESTIONS.md +0 -865
  257. scitex/scholar/examples/dev.py +0 -38
  258. scitex-2.14.0.dist-info/METADATA +0 -1238
  259. /scitex/{gen → context}/_detect_environment.py +0 -0
  260. /scitex/{gen → context}/_get_notebook_path.py +0 -0
  261. /scitex/{gen/_shell.py → sh/_shell_legacy.py} +0 -0
  262. {scitex-2.14.0.dist-info → scitex-2.15.3.dist-info}/WHEEL +0 -0
  263. {scitex-2.14.0.dist-info → scitex-2.15.3.dist-info}/entry_points.txt +0 -0
  264. {scitex-2.14.0.dist-info → scitex-2.15.3.dist-info}/licenses/LICENSE +0 -0
scitex/gen/__init__.py CHANGED
@@ -1,15 +1,45 @@
1
1
  #!/usr/bin/env python3
2
- """Scitex gen module."""
2
+ """Scitex gen module.
3
+
4
+ NOTE: This module is being refactored. Many functions are being moved to
5
+ more appropriate locations. For backward compatibility, they are re-exported
6
+ here with deprecation warnings.
7
+
8
+ Recommended imports:
9
+ - ci -> scitex.stats.descriptive.ci
10
+ - check_host, is_host, verify_host -> scitex.os
11
+ - detect_environment, is_notebook, is_script -> scitex.context
12
+ - list_api -> scitex.introspect
13
+ - run_shellcommand, run_shellscript -> scitex.sh
14
+ - xml2dict, XmlDictConfig, XmlListConfig -> scitex.io
15
+ - title_case -> scitex.str
16
+ - symlink -> scitex.path
17
+ """
18
+
19
+ import warnings
20
+
21
+
22
+ def _deprecation_warning(old_path, new_path):
23
+ warnings.warn(
24
+ f"{old_path} is deprecated, use {new_path} instead",
25
+ DeprecationWarning,
26
+ stacklevel=3,
27
+ )
28
+
29
+
30
+ # ci -> scitex.stats.descriptive.ci (with re-export for backward compat)
31
+ from scitex.stats.descriptive import ci
3
32
 
4
33
  # Optional: DimHandler requires torch
5
34
  try:
6
35
  from ._DimHandler import DimHandler
7
36
  except ImportError:
8
37
  DimHandler = None
38
+ # check_host moved to scitex.os (re-export for backward compatibility)
39
+ from scitex.os import check_host, is_host, verify_host
40
+
9
41
  from ._alternate_kwarg import alternate_kwarg
10
42
  from ._cache import cache
11
- from ._check_host import check_host, is_host, verify_host
12
- from ._ci import ci
13
43
  from ._deprecated_close import (
14
44
  close as _deprecated_close,
15
45
  )
@@ -27,7 +57,9 @@ try:
27
57
  from ._embed import embed
28
58
  except ImportError:
29
59
  embed = None
30
- from ._inspect_module import inspect_module
60
+ # list_api moved to scitex.introspect (re-export for backward compatibility)
61
+ from scitex.introspect import list_api
62
+
31
63
  from ._is_ipython import is_ipython, is_script
32
64
  from ._less import less
33
65
  from ._list_packages import list_packages, main
@@ -51,9 +83,11 @@ except ImportError:
51
83
  to_nanz = None
52
84
  to_z = None
53
85
  unbias = None
86
+ # shell functions moved to scitex.sh (re-export for backward compatibility)
87
+ from scitex.sh import run_shellcommand, run_shellscript
88
+
54
89
  from ._paste import paste
55
90
  from ._print_config import print_config, print_config_main
56
- from ._shell import run_shellcommand, run_shellscript
57
91
  from ._src import src
58
92
  from ._TimeStamper import TimeStamper
59
93
 
@@ -62,20 +96,23 @@ start = _deprecated_start
62
96
  close = _deprecated_close
63
97
  running2finished = _deprecated_running2finished
64
98
 
65
- from ._detect_environment import (
99
+ # environment detection moved to scitex.context (re-export for backward compatibility)
100
+ from scitex.context import (
66
101
  detect_environment,
67
- get_output_directory,
68
- is_notebook,
69
- )
70
- from ._get_notebook_path import (
71
102
  get_notebook_directory,
103
+ get_notebook_info_simple,
72
104
  get_notebook_name,
73
105
  get_notebook_path,
106
+ get_output_directory,
107
+ is_notebook,
74
108
  )
109
+
110
+ # title_case moved to scitex.str (re-export for backward compatibility)
111
+ from scitex.str import title_case
112
+
75
113
  from ._symlink import symlink
76
114
  from ._symlog import symlog
77
115
  from ._title2path import title2path
78
- from ._title_case import main, title_case
79
116
  from ._to_even import to_even
80
117
  from ._to_odd import to_odd
81
118
 
@@ -99,10 +136,9 @@ except ImportError:
99
136
  pass # Already set to None above
100
137
  from ._wrap import wrap
101
138
  from ._xml2dict import XmlDictConfig, XmlListConfig, xml2dict
102
- from .misc import float_linspace
103
139
 
104
140
  # Import from misc module
105
- from .misc import connect_nums
141
+ from .misc import connect_nums, float_linspace
106
142
 
107
143
  __all__ = [
108
144
  "ArrayLike",
@@ -122,7 +158,7 @@ __all__ = [
122
158
  "dir2npy",
123
159
  "embed",
124
160
  "float_linspace",
125
- "inspect_module",
161
+ "list_api",
126
162
  "is_host",
127
163
  "is_ipython",
128
164
  "is_script",
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
2
  # Time-stamp: "2024-11-03 02:11:54 (ywatanabe)"
4
3
  # File: ./scitex_repo/src/scitex/gen/_list_packages.py
5
4
  """
@@ -25,8 +24,6 @@ except ImportError:
25
24
  # Fallback for older Python versions
26
25
  from importlib_metadata import distributions
27
26
 
28
- from ._inspect_module import inspect_module
29
-
30
27
 
31
28
  def list_packages(
32
29
  max_depth: int = 1,
@@ -80,10 +77,12 @@ def list_packages(
80
77
  pkg for pkg in installed_packages if pkg not in safelist
81
78
  ]
82
79
 
80
+ from scitex.introspect import list_api
81
+
83
82
  all_dfs = []
84
83
  for package_name in installed_packages:
85
84
  try:
86
- df = inspect_module(
85
+ df = list_api(
87
86
  package_name,
88
87
  docstring=False, # Speed up by skipping docstrings
89
88
  print_output=False,
@@ -116,6 +115,7 @@ def main() -> Optional[int]:
116
115
 
117
116
  if __name__ == "__main__":
118
117
  import matplotlib.pyplot as plt
118
+
119
119
  import scitex
120
120
 
121
121
  CONFIG, sys.stdout, sys.stderr, plt, CC = scitex.session.start(
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2025-01-20
3
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/introspect/__init__.py
4
+
5
+ """
6
+ Introspection utilities for Python packages.
7
+
8
+ Provides IPython-like introspection capabilities for any Python package.
9
+
10
+ IPython-style shortcuts:
11
+ - q: Function/class signature with type hints (like `func?`)
12
+ - qq: Full source code (like `func??`)
13
+ - dir: List attributes/methods (like `dir()`)
14
+ - list_api: Recursive module API tree
15
+
16
+ Basic Introspection:
17
+ - get_docstring: Docstring extraction with parsing
18
+ - get_exports: Module's __all__ contents
19
+ - find_examples: Find usage examples in tests/examples
20
+
21
+ Advanced Introspection:
22
+ - get_class_hierarchy: Inheritance tree (MRO + subclasses)
23
+ - get_mro: Method Resolution Order only
24
+ - get_type_hints_detailed: Detailed type annotation analysis
25
+ - get_class_annotations: Class variable and method annotations
26
+ - get_imports: Static import analysis using AST
27
+ - get_dependencies: Module dependency analysis
28
+ - get_call_graph: Function call graph (with timeout protection)
29
+ - get_function_calls: Simple outgoing calls list
30
+ """
31
+
32
+ from ._core import (
33
+ # IPython-style names
34
+ dir,
35
+ # Basic
36
+ find_examples,
37
+ # Advanced - Call graph
38
+ get_call_graph,
39
+ # Advanced - Type hints
40
+ get_class_annotations,
41
+ # Advanced - Class hierarchy
42
+ get_class_hierarchy,
43
+ # Advanced - Imports
44
+ get_dependencies,
45
+ get_docstring,
46
+ get_exports,
47
+ get_function_calls,
48
+ get_imports,
49
+ get_mro,
50
+ get_type_hints_detailed,
51
+ get_type_info,
52
+ q,
53
+ qq,
54
+ resolve_object,
55
+ )
56
+ from ._list_api import list_api
57
+
58
+ __all__ = [
59
+ # IPython-style names
60
+ "q",
61
+ "qq",
62
+ "dir",
63
+ "list_api",
64
+ # Basic
65
+ "get_docstring",
66
+ "get_exports",
67
+ "find_examples",
68
+ "resolve_object",
69
+ "get_type_info",
70
+ # Advanced - Class hierarchy
71
+ "get_class_hierarchy",
72
+ "get_mro",
73
+ # Advanced - Type hints
74
+ "get_type_hints_detailed",
75
+ "get_class_annotations",
76
+ # Advanced - Imports
77
+ "get_imports",
78
+ "get_dependencies",
79
+ # Advanced - Call graph
80
+ "get_call_graph",
81
+ "get_function_calls",
82
+ ]
@@ -0,0 +1,303 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2025-01-20
3
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/introspect/_call_graph.py
4
+
5
+ """Call graph analysis using AST with timeout protection."""
6
+
7
+ from __future__ import annotations
8
+
9
+ import ast
10
+ import inspect
11
+ import signal
12
+ from contextlib import contextmanager
13
+ from pathlib import Path
14
+
15
+ from ._resolve import get_type_info, resolve_object
16
+
17
+
18
+ class TimeoutError(Exception):
19
+ """Raised when operation times out."""
20
+
21
+ pass
22
+
23
+
24
+ @contextmanager
25
+ def timeout(seconds: int):
26
+ """Context manager for timeout (Unix only)."""
27
+
28
+ def handler(signum, frame):
29
+ raise TimeoutError(f"Operation timed out after {seconds}s")
30
+
31
+ # Only works on Unix
32
+ try:
33
+ old_handler = signal.signal(signal.SIGALRM, handler)
34
+ signal.alarm(seconds)
35
+ try:
36
+ yield
37
+ finally:
38
+ signal.alarm(0)
39
+ signal.signal(signal.SIGALRM, old_handler)
40
+ except (ValueError, AttributeError):
41
+ # Windows or signal not available - no timeout
42
+ yield
43
+
44
+
45
+ def get_call_graph(
46
+ dotted_path: str,
47
+ max_depth: int = 2,
48
+ timeout_seconds: int = 10,
49
+ internal_only: bool = True,
50
+ ) -> dict:
51
+ """
52
+ Get the call graph of a function or module using static AST analysis.
53
+
54
+ Parameters
55
+ ----------
56
+ dotted_path : str
57
+ Dotted path to the function or module
58
+ max_depth : int
59
+ Maximum depth to traverse calls
60
+ timeout_seconds : int
61
+ Timeout in seconds (0 = no timeout)
62
+ internal_only : bool
63
+ Only show calls to functions in the same module
64
+
65
+ Returns
66
+ -------
67
+ dict
68
+ calls: list[dict] - Functions this function calls
69
+ called_by: list[dict] - Functions that call this (if module)
70
+ graph: dict - Full call graph tree
71
+
72
+ Examples
73
+ --------
74
+ >>> get_call_graph("scitex.audio.speak")
75
+ """
76
+ try:
77
+ if timeout_seconds > 0:
78
+ with timeout(timeout_seconds):
79
+ return _analyze_call_graph(dotted_path, max_depth, internal_only)
80
+ else:
81
+ return _analyze_call_graph(dotted_path, max_depth, internal_only)
82
+ except TimeoutError as e:
83
+ return {
84
+ "success": False,
85
+ "error": str(e),
86
+ "partial": True,
87
+ }
88
+
89
+
90
+ def _analyze_call_graph(
91
+ dotted_path: str,
92
+ max_depth: int,
93
+ internal_only: bool,
94
+ ) -> dict:
95
+ """Perform the actual call graph analysis."""
96
+ obj, error = resolve_object(dotted_path)
97
+ if error:
98
+ return {"success": False, "error": error}
99
+
100
+ type_info = get_type_info(obj)
101
+
102
+ # Get source file
103
+ try:
104
+ source_file = inspect.getfile(obj)
105
+ source = Path(source_file).read_text()
106
+ tree = ast.parse(source)
107
+ except Exception as e:
108
+ return {
109
+ "success": False,
110
+ "error": f"Cannot parse source: {e}",
111
+ "type_info": type_info,
112
+ }
113
+
114
+ # Build function index for the module
115
+ func_index = _build_function_index(tree)
116
+
117
+ if inspect.isfunction(obj):
118
+ # Analyze single function
119
+ func_name = obj.__name__
120
+ if func_name not in func_index:
121
+ return {
122
+ "success": False,
123
+ "error": f"Function '{func_name}' not found in source",
124
+ "type_info": type_info,
125
+ }
126
+
127
+ calls = _get_function_calls(func_index[func_name], internal_only, func_index)
128
+ called_by = _find_callers(func_name, func_index)
129
+
130
+ return {
131
+ "success": True,
132
+ "function": func_name,
133
+ "calls": calls,
134
+ "call_count": len(calls),
135
+ "called_by": called_by,
136
+ "caller_count": len(called_by),
137
+ "type_info": type_info,
138
+ }
139
+
140
+ elif inspect.ismodule(obj):
141
+ # Analyze entire module
142
+ graph = {}
143
+ for func_name, func_node in func_index.items():
144
+ calls = _get_function_calls(func_node, internal_only, func_index)
145
+ graph[func_name] = {
146
+ "calls": calls,
147
+ "line": func_node.lineno,
148
+ }
149
+
150
+ return {
151
+ "success": True,
152
+ "module": dotted_path,
153
+ "graph": graph,
154
+ "function_count": len(graph),
155
+ "type_info": type_info,
156
+ }
157
+
158
+ else:
159
+ return {
160
+ "success": False,
161
+ "error": "Can only analyze functions or modules",
162
+ "type_info": type_info,
163
+ }
164
+
165
+
166
+ def _build_function_index(tree: ast.AST) -> dict[str, ast.FunctionDef]:
167
+ """Build index of all functions in the AST."""
168
+ index = {}
169
+ for node in ast.walk(tree):
170
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
171
+ index[node.name] = node
172
+ return index
173
+
174
+
175
+ def _get_function_calls(
176
+ func_node: ast.FunctionDef,
177
+ internal_only: bool,
178
+ func_index: dict,
179
+ ) -> list[dict]:
180
+ """Extract all function calls from a function."""
181
+ calls = []
182
+ seen = set()
183
+
184
+ for node in ast.walk(func_node):
185
+ if isinstance(node, ast.Call):
186
+ call_info = _extract_call_info(node)
187
+ if call_info and call_info["name"] not in seen:
188
+ # Filter to internal only if requested
189
+ if internal_only and call_info["name"] not in func_index:
190
+ continue
191
+ seen.add(call_info["name"])
192
+ calls.append(call_info)
193
+
194
+ return calls
195
+
196
+
197
+ def _extract_call_info(node: ast.Call) -> dict | None:
198
+ """Extract information about a function call."""
199
+ func = node.func
200
+
201
+ if isinstance(func, ast.Name):
202
+ # Simple call: func()
203
+ return {
204
+ "name": func.id,
205
+ "type": "function",
206
+ "line": node.lineno,
207
+ }
208
+ elif isinstance(func, ast.Attribute):
209
+ # Method call: obj.method()
210
+ if isinstance(func.value, ast.Name):
211
+ return {
212
+ "name": f"{func.value.id}.{func.attr}",
213
+ "type": "method",
214
+ "object": func.value.id,
215
+ "method": func.attr,
216
+ "line": node.lineno,
217
+ }
218
+ else:
219
+ return {
220
+ "name": func.attr,
221
+ "type": "method",
222
+ "method": func.attr,
223
+ "line": node.lineno,
224
+ }
225
+
226
+ return None
227
+
228
+
229
+ def _find_callers(
230
+ func_name: str,
231
+ func_index: dict[str, ast.FunctionDef],
232
+ ) -> list[dict]:
233
+ """Find all functions that call the given function."""
234
+ callers = []
235
+
236
+ for caller_name, caller_node in func_index.items():
237
+ if caller_name == func_name:
238
+ continue
239
+
240
+ for node in ast.walk(caller_node):
241
+ if isinstance(node, ast.Call):
242
+ call_info = _extract_call_info(node)
243
+ if call_info and call_info["name"] == func_name:
244
+ callers.append(
245
+ {
246
+ "name": caller_name,
247
+ "line": caller_node.lineno,
248
+ }
249
+ )
250
+ break
251
+
252
+ return callers
253
+
254
+
255
+ def get_function_calls(
256
+ dotted_path: str,
257
+ include_methods: bool = True,
258
+ include_builtins: bool = False,
259
+ ) -> dict:
260
+ """
261
+ Get just the outgoing calls from a function.
262
+
263
+ Simpler version of get_call_graph for quick lookup.
264
+
265
+ Parameters
266
+ ----------
267
+ dotted_path : str
268
+ Dotted path to the function
269
+ include_methods : bool
270
+ Include method calls (obj.method())
271
+ include_builtins : bool
272
+ Include builtin function calls
273
+
274
+ Returns
275
+ -------
276
+ dict
277
+ calls: list[str] - Names of called functions
278
+ """
279
+ result = get_call_graph(dotted_path, max_depth=1, internal_only=False)
280
+
281
+ if not result.get("success"):
282
+ return result
283
+
284
+ calls = result.get("calls", [])
285
+
286
+ # Filter
287
+ filtered = []
288
+ builtins = {"print", "len", "range", "str", "int", "float", "list", "dict", "set"}
289
+
290
+ for call in calls:
291
+ name = call["name"]
292
+ if not include_methods and call.get("type") == "method":
293
+ continue
294
+ if not include_builtins and name in builtins:
295
+ continue
296
+ filtered.append(name)
297
+
298
+ return {
299
+ "success": True,
300
+ "function": dotted_path,
301
+ "calls": filtered,
302
+ "call_count": len(filtered),
303
+ }
@@ -0,0 +1,163 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2025-01-20
3
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/introspect/_class_hierarchy.py
4
+
5
+ """Class hierarchy analysis utilities."""
6
+
7
+ from __future__ import annotations
8
+
9
+ import inspect
10
+
11
+ from ._resolve import get_type_info, resolve_object
12
+
13
+
14
+ def get_class_hierarchy(
15
+ dotted_path: str,
16
+ include_builtins: bool = False,
17
+ max_depth: int = 10,
18
+ ) -> dict:
19
+ """
20
+ Get the inheritance hierarchy of a class.
21
+
22
+ Shows both parent classes (MRO) and known subclasses.
23
+
24
+ Parameters
25
+ ----------
26
+ dotted_path : str
27
+ Dotted path to the class (e.g., 'pandas.DataFrame')
28
+ include_builtins : bool
29
+ Include builtin classes like object, type in hierarchy
30
+ max_depth : int
31
+ Maximum depth for subclass traversal
32
+
33
+ Returns
34
+ -------
35
+ dict
36
+ mro: list[str] - Method Resolution Order (parent classes)
37
+ subclasses: list[dict] - Known subclasses (recursive)
38
+ type_info: dict
39
+
40
+ Examples
41
+ --------
42
+ >>> get_class_hierarchy("collections.abc.Mapping")
43
+ """
44
+ obj, error = resolve_object(dotted_path)
45
+ if error:
46
+ return {"success": False, "error": error}
47
+
48
+ type_info = get_type_info(obj)
49
+
50
+ if not inspect.isclass(obj):
51
+ return {
52
+ "success": False,
53
+ "error": f"'{dotted_path}' is not a class",
54
+ "type_info": type_info,
55
+ }
56
+
57
+ # Get MRO (parent classes)
58
+ mro = []
59
+ for cls in inspect.getmro(obj):
60
+ if not include_builtins and cls.__module__ == "builtins":
61
+ continue
62
+ mro.append(
63
+ {
64
+ "name": cls.__name__,
65
+ "module": cls.__module__,
66
+ "qualname": f"{cls.__module__}.{cls.__name__}",
67
+ }
68
+ )
69
+
70
+ # Get subclasses recursively
71
+ subclasses = _get_subclasses_recursive(obj, max_depth, include_builtins)
72
+
73
+ return {
74
+ "success": True,
75
+ "class": dotted_path,
76
+ "mro": mro,
77
+ "mro_count": len(mro),
78
+ "subclasses": subclasses,
79
+ "subclass_count": _count_subclasses(subclasses),
80
+ "type_info": type_info,
81
+ }
82
+
83
+
84
+ def _get_subclasses_recursive(
85
+ cls: type,
86
+ max_depth: int,
87
+ include_builtins: bool,
88
+ current_depth: int = 0,
89
+ ) -> list[dict]:
90
+ """Recursively get all subclasses."""
91
+ if current_depth >= max_depth:
92
+ return []
93
+
94
+ result = []
95
+ try:
96
+ for sub in cls.__subclasses__():
97
+ if not include_builtins and sub.__module__ == "builtins":
98
+ continue
99
+
100
+ sub_info = {
101
+ "name": sub.__name__,
102
+ "module": sub.__module__,
103
+ "qualname": f"{sub.__module__}.{sub.__name__}",
104
+ }
105
+
106
+ children = _get_subclasses_recursive(
107
+ sub, max_depth, include_builtins, current_depth + 1
108
+ )
109
+ if children:
110
+ sub_info["subclasses"] = children
111
+
112
+ result.append(sub_info)
113
+ except Exception:
114
+ pass
115
+
116
+ return result
117
+
118
+
119
+ def _count_subclasses(subclasses: list[dict]) -> int:
120
+ """Count total subclasses including nested."""
121
+ count = len(subclasses)
122
+ for sub in subclasses:
123
+ if "subclasses" in sub:
124
+ count += _count_subclasses(sub["subclasses"])
125
+ return count
126
+
127
+
128
+ def get_mro(dotted_path: str, include_builtins: bool = False) -> dict:
129
+ """
130
+ Get just the Method Resolution Order (parent classes).
131
+
132
+ Simpler version of get_class_hierarchy for just parents.
133
+
134
+ Parameters
135
+ ----------
136
+ dotted_path : str
137
+ Dotted path to the class
138
+ include_builtins : bool
139
+ Include builtin classes
140
+
141
+ Returns
142
+ -------
143
+ dict
144
+ mro: list[str] - Qualified names in MRO order
145
+ """
146
+ obj, error = resolve_object(dotted_path)
147
+ if error:
148
+ return {"success": False, "error": error}
149
+
150
+ if not inspect.isclass(obj):
151
+ return {"success": False, "error": f"'{dotted_path}' is not a class"}
152
+
153
+ mro = []
154
+ for cls in inspect.getmro(obj):
155
+ if not include_builtins and cls.__module__ == "builtins":
156
+ continue
157
+ mro.append(f"{cls.__module__}.{cls.__name__}")
158
+
159
+ return {
160
+ "success": True,
161
+ "class": dotted_path,
162
+ "mro": mro,
163
+ }