scitex 2.8.1__py3-none-any.whl → 2.10.0__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 (415) hide show
  1. scitex/__init__.py +15 -7
  2. scitex/__version__.py +1 -2
  3. scitex/_install_guide.py +250 -0
  4. scitex/_optional_deps.py +206 -39
  5. scitex/ai/_gen_ai/_Groq.py +2 -4
  6. scitex/ai/_gen_ai/_OpenAI.py +5 -2
  7. scitex/ai/_gen_ai/_Perplexity.py +20 -6
  8. scitex/audio/__init__.py +24 -15
  9. scitex/audio/_cross_process_lock.py +139 -0
  10. scitex/audio/_mcp_handlers.py +256 -0
  11. scitex/audio/_mcp_tool_schemas.py +203 -0
  12. scitex/audio/engines/elevenlabs_engine.py +5 -2
  13. scitex/audio/mcp_server.py +98 -457
  14. scitex/bridge/__init__.py +30 -19
  15. scitex/bridge/_figrecipe.py +245 -0
  16. scitex/bridge/_helpers.py +2 -1
  17. scitex/bridge/_plt_vis.py +23 -10
  18. scitex/bridge/_stats_plt.py +18 -5
  19. scitex/bridge/_stats_vis.py +16 -2
  20. scitex/browser/__init__.py +84 -44
  21. scitex/browser/automation/__init__.py +5 -1
  22. scitex/browser/core/BrowserMixin.py +17 -4
  23. scitex/browser/core/__init__.py +11 -2
  24. scitex/browser/remote/CaptchaHandler.py +1 -1
  25. scitex/browser/remote/ZenRowsAPIClient.py +1 -1
  26. scitex/capture/grid.py +487 -0
  27. scitex/capture/mcp_handlers.py +401 -0
  28. scitex/capture/mcp_tool_defs.py +192 -0
  29. scitex/capture/mcp_tools.py +241 -0
  30. scitex/capture/mcp_utils.py +30 -0
  31. scitex/cli/convert.py +421 -0
  32. scitex/cli/main.py +6 -4
  33. scitex/datetime/__init__.py +46 -0
  34. scitex/datetime/_linspace.py +100 -0
  35. scitex/datetime/_normalize_timestamp.py +306 -0
  36. scitex/db/_delete_duplicates.py +4 -4
  37. scitex/db/_sqlite3/_delete_duplicates.py +11 -2
  38. scitex/dev/plt/__init__.py +61 -62
  39. scitex/dev/plt/demo_plotters/__init__.py +0 -0
  40. scitex/dev/plt/demo_plotters/plot_mpl_axhline.py +28 -0
  41. scitex/dev/plt/demo_plotters/plot_mpl_axhspan.py +28 -0
  42. scitex/dev/plt/demo_plotters/plot_mpl_axvline.py +28 -0
  43. scitex/dev/plt/demo_plotters/plot_mpl_axvspan.py +28 -0
  44. scitex/dev/plt/demo_plotters/plot_mpl_bar.py +29 -0
  45. scitex/dev/plt/demo_plotters/plot_mpl_barh.py +29 -0
  46. scitex/dev/plt/demo_plotters/plot_mpl_boxplot.py +28 -0
  47. scitex/dev/plt/demo_plotters/plot_mpl_contour.py +31 -0
  48. scitex/dev/plt/demo_plotters/plot_mpl_contourf.py +31 -0
  49. scitex/dev/plt/demo_plotters/plot_mpl_errorbar.py +30 -0
  50. scitex/dev/plt/demo_plotters/plot_mpl_eventplot.py +28 -0
  51. scitex/dev/plt/demo_plotters/plot_mpl_fill.py +30 -0
  52. scitex/dev/plt/demo_plotters/plot_mpl_fill_between.py +31 -0
  53. scitex/dev/plt/demo_plotters/plot_mpl_hexbin.py +28 -0
  54. scitex/dev/plt/demo_plotters/plot_mpl_hist.py +28 -0
  55. scitex/dev/plt/demo_plotters/plot_mpl_hist2d.py +28 -0
  56. scitex/dev/plt/demo_plotters/plot_mpl_imshow.py +29 -0
  57. scitex/dev/plt/demo_plotters/plot_mpl_pcolormesh.py +31 -0
  58. scitex/dev/plt/demo_plotters/plot_mpl_pie.py +29 -0
  59. scitex/dev/plt/demo_plotters/plot_mpl_plot.py +29 -0
  60. scitex/dev/plt/demo_plotters/plot_mpl_quiver.py +31 -0
  61. scitex/dev/plt/demo_plotters/plot_mpl_scatter.py +28 -0
  62. scitex/dev/plt/demo_plotters/plot_mpl_stackplot.py +31 -0
  63. scitex/dev/plt/demo_plotters/plot_mpl_stem.py +29 -0
  64. scitex/dev/plt/demo_plotters/plot_mpl_step.py +29 -0
  65. scitex/dev/plt/demo_plotters/plot_mpl_violinplot.py +28 -0
  66. scitex/dev/plt/demo_plotters/plot_sns_barplot.py +29 -0
  67. scitex/dev/plt/demo_plotters/plot_sns_boxplot.py +29 -0
  68. scitex/dev/plt/demo_plotters/plot_sns_heatmap.py +28 -0
  69. scitex/dev/plt/demo_plotters/plot_sns_histplot.py +29 -0
  70. scitex/dev/plt/demo_plotters/plot_sns_kdeplot.py +29 -0
  71. scitex/dev/plt/demo_plotters/plot_sns_lineplot.py +31 -0
  72. scitex/dev/plt/demo_plotters/plot_sns_scatterplot.py +29 -0
  73. scitex/dev/plt/demo_plotters/plot_sns_stripplot.py +29 -0
  74. scitex/dev/plt/demo_plotters/plot_sns_swarmplot.py +29 -0
  75. scitex/dev/plt/demo_plotters/plot_sns_violinplot.py +29 -0
  76. scitex/dev/plt/demo_plotters/plot_stx_bar.py +29 -0
  77. scitex/dev/plt/demo_plotters/plot_stx_barh.py +29 -0
  78. scitex/dev/plt/demo_plotters/plot_stx_box.py +28 -0
  79. scitex/dev/plt/demo_plotters/plot_stx_boxplot.py +28 -0
  80. scitex/dev/plt/demo_plotters/plot_stx_conf_mat.py +28 -0
  81. scitex/dev/plt/demo_plotters/plot_stx_contour.py +31 -0
  82. scitex/dev/plt/demo_plotters/plot_stx_ecdf.py +28 -0
  83. scitex/dev/plt/demo_plotters/plot_stx_errorbar.py +30 -0
  84. scitex/dev/plt/demo_plotters/plot_stx_fill_between.py +31 -0
  85. scitex/dev/plt/demo_plotters/plot_stx_fillv.py +28 -0
  86. scitex/dev/plt/demo_plotters/plot_stx_heatmap.py +28 -0
  87. scitex/dev/plt/demo_plotters/plot_stx_image.py +28 -0
  88. scitex/dev/plt/demo_plotters/plot_stx_imshow.py +28 -0
  89. scitex/dev/plt/demo_plotters/plot_stx_joyplot.py +28 -0
  90. scitex/dev/plt/demo_plotters/plot_stx_kde.py +28 -0
  91. scitex/dev/plt/demo_plotters/plot_stx_line.py +28 -0
  92. scitex/dev/plt/demo_plotters/plot_stx_mean_ci.py +28 -0
  93. scitex/dev/plt/demo_plotters/plot_stx_mean_std.py +28 -0
  94. scitex/dev/plt/demo_plotters/plot_stx_median_iqr.py +28 -0
  95. scitex/dev/plt/demo_plotters/plot_stx_raster.py +28 -0
  96. scitex/dev/plt/demo_plotters/plot_stx_rectangle.py +28 -0
  97. scitex/dev/plt/demo_plotters/plot_stx_scatter.py +29 -0
  98. scitex/dev/plt/demo_plotters/plot_stx_shaded_line.py +29 -0
  99. scitex/dev/plt/demo_plotters/plot_stx_violin.py +28 -0
  100. scitex/dev/plt/demo_plotters/plot_stx_violinplot.py +28 -0
  101. scitex/dev/plt/mpl/get_dir_ax.py +46 -0
  102. scitex/dev/plt/mpl/get_signatures.py +176 -0
  103. scitex/dev/plt/mpl/get_signatures_details.py +522 -0
  104. scitex/dict/_pop_keys.py +1 -7
  105. scitex/dsp/__init__.py +15 -10
  106. scitex/dsp/add_noise.py +5 -2
  107. scitex/dsp/example.py +35 -22
  108. scitex/dsp/filt.py +8 -3
  109. scitex/dsp/reference.py +3 -2
  110. scitex/dsp/utils/__init__.py +2 -1
  111. scitex/dsp/utils/_differential_bandpass_filters.py +14 -4
  112. scitex/dt/__init__.py +39 -2
  113. scitex/errors.py +82 -521
  114. scitex/fig/__init__.py +4 -4
  115. scitex/fig/editor/edit/panel_loader.py +1 -1
  116. scitex/fig/io/_bundle.py +7 -7
  117. scitex/fts/README.md +262 -0
  118. scitex/fts/TODO.md +66 -0
  119. scitex/fts/__init__.py +90 -0
  120. scitex/fts/_bundle/README_IN_BUNDLE.md +102 -0
  121. scitex/fts/_bundle/_FTS.py +657 -0
  122. scitex/fts/_bundle/__init__.py +38 -0
  123. scitex/fts/_bundle/_children.py +216 -0
  124. scitex/fts/_bundle/_conversion/__init__.py +15 -0
  125. scitex/fts/_bundle/_conversion/_bundle2dict.py +44 -0
  126. scitex/fts/_bundle/_conversion/_dict2bundle.py +50 -0
  127. scitex/fts/_bundle/_dataclasses/_Axes.py +57 -0
  128. scitex/fts/_bundle/_dataclasses/_BBox.py +54 -0
  129. scitex/fts/_bundle/_dataclasses/_ColumnDef.py +72 -0
  130. scitex/fts/_bundle/_dataclasses/_DataFormat.py +40 -0
  131. scitex/fts/_bundle/_dataclasses/_DataInfo.py +135 -0
  132. scitex/fts/_bundle/_dataclasses/_DataSource.py +44 -0
  133. scitex/fts/_bundle/_dataclasses/_Node.py +319 -0
  134. scitex/fts/_bundle/_dataclasses/_NodeRefs.py +45 -0
  135. scitex/fts/_bundle/_dataclasses/_SizeMM.py +38 -0
  136. scitex/fts/_bundle/_dataclasses/__init__.py +35 -0
  137. scitex/fts/_bundle/_extractors/__init__.py +32 -0
  138. scitex/fts/_bundle/_extractors/_extract_bar.py +131 -0
  139. scitex/fts/_bundle/_extractors/_extract_line.py +71 -0
  140. scitex/fts/_bundle/_extractors/_extract_scatter.py +79 -0
  141. scitex/fts/_bundle/_loader.py +134 -0
  142. scitex/fts/_bundle/_mpl_helpers.py +389 -0
  143. scitex/fts/_bundle/_saver.py +269 -0
  144. scitex/fts/_bundle/_storage.py +200 -0
  145. scitex/fts/_bundle/_utils/__init__.py +55 -0
  146. scitex/fts/_bundle/_utils/_const.py +26 -0
  147. scitex/fts/_bundle/_utils/_errors.py +73 -0
  148. scitex/fts/_bundle/_utils/_generate.py +21 -0
  149. scitex/fts/_bundle/_utils/_types.py +76 -0
  150. scitex/fts/_bundle/_validation.py +434 -0
  151. scitex/fts/_bundle/_zipbundle.py +165 -0
  152. scitex/fts/_fig/__init__.py +22 -0
  153. scitex/fts/_fig/_backend/__init__.py +53 -0
  154. scitex/fts/_fig/_backend/_export.py +165 -0
  155. scitex/fts/_fig/_backend/_parser.py +188 -0
  156. scitex/fts/_fig/_backend/_render.py +538 -0
  157. scitex/fts/_fig/_composite.py +345 -0
  158. scitex/fts/_fig/_dataclasses/_ChannelEncoding.py +46 -0
  159. scitex/fts/_fig/_dataclasses/_Encoding.py +82 -0
  160. scitex/fts/_fig/_dataclasses/_Theme.py +441 -0
  161. scitex/fts/_fig/_dataclasses/_TraceEncoding.py +52 -0
  162. scitex/fts/_fig/_dataclasses/__init__.py +47 -0
  163. scitex/fts/_fig/_editor/__init__.py +14 -0
  164. scitex/fts/_fig/_editor/_cui/__init__.py +33 -0
  165. scitex/fts/_fig/_editor/_cui/_backend_detector.py +39 -0
  166. scitex/fts/_fig/_editor/_cui/_bundle_resolver.py +366 -0
  167. scitex/fts/_fig/_editor/_cui/_editor_launcher.py +175 -0
  168. scitex/fts/_fig/_editor/_cui/_manual_handler.py +52 -0
  169. scitex/fts/_fig/_editor/_cui/_panel_loader.py +246 -0
  170. scitex/fts/_fig/_editor/_cui/_path_resolver.py +66 -0
  171. scitex/fts/_fig/_editor/_defaults.py +300 -0
  172. scitex/fts/_fig/_editor/_gui/__init__.py +11 -0
  173. scitex/fts/_fig/_editor/_gui/_flask_editor/__init__.py +20 -0
  174. scitex/fts/_fig/_editor/_gui/_flask_editor/_bbox.py +1339 -0
  175. scitex/fts/_fig/_editor/_gui/_flask_editor/_core.py +1688 -0
  176. scitex/fts/_fig/_editor/_gui/_flask_editor/_plotter.py +664 -0
  177. scitex/fts/_fig/_editor/_gui/_flask_editor/_renderer.py +853 -0
  178. scitex/fts/_fig/_editor/_gui/_flask_editor/_utils.py +79 -0
  179. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/reset.css +41 -0
  180. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/typography.css +16 -0
  181. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/variables.css +85 -0
  182. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/buttons.css +217 -0
  183. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/context-menu.css +93 -0
  184. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/dropdown.css +57 -0
  185. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/forms.css +112 -0
  186. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/modal.css +59 -0
  187. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/sections.css +212 -0
  188. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/canvas.css +176 -0
  189. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/element-inspector.css +190 -0
  190. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/loading.css +59 -0
  191. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/overlay.css +45 -0
  192. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/panel-grid.css +95 -0
  193. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/selection.css +101 -0
  194. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/statistics.css +138 -0
  195. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/index.css +31 -0
  196. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/container.css +7 -0
  197. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/controls.css +56 -0
  198. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/preview.css +78 -0
  199. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/axis.js +314 -0
  200. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/basic.js +107 -0
  201. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/distribute.js +54 -0
  202. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/canvas.js +172 -0
  203. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/dragging.js +258 -0
  204. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/resize.js +48 -0
  205. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/selection.js +71 -0
  206. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/api.js +288 -0
  207. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/state.js +143 -0
  208. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/utils.js +245 -0
  209. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/dev/element-inspector.js +992 -0
  210. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/bbox.js +339 -0
  211. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/element-drag.js +286 -0
  212. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/overlay.js +371 -0
  213. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/preview.js +293 -0
  214. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/main.js +426 -0
  215. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/context-menu.js +152 -0
  216. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/keyboard.js +265 -0
  217. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/controls.js +184 -0
  218. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/download.js +57 -0
  219. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/help.js +100 -0
  220. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/theme.js +34 -0
  221. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/__init__.py +124 -0
  222. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_html.py +851 -0
  223. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_scripts.py +4932 -0
  224. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_styles.py +1657 -0
  225. scitex/fts/_fig/_editor/_gui/_flask_editor.py +36 -0
  226. scitex/fts/_fig/_models/_Annotations.py +115 -0
  227. scitex/fts/_fig/_models/_Axes.py +152 -0
  228. scitex/fts/_fig/_models/_Figure.py +138 -0
  229. scitex/fts/_fig/_models/_Guides.py +104 -0
  230. scitex/fts/_fig/_models/_Plot.py +123 -0
  231. scitex/fts/_fig/_models/_Styles.py +245 -0
  232. scitex/fts/_fig/_models/__init__.py +80 -0
  233. scitex/fts/_fig/_models/_plot_types/__init__.py +156 -0
  234. scitex/fts/_fig/_models/_plot_types/_bar.py +43 -0
  235. scitex/fts/_fig/_models/_plot_types/_box.py +38 -0
  236. scitex/fts/_fig/_models/_plot_types/_distribution.py +36 -0
  237. scitex/fts/_fig/_models/_plot_types/_errorbar.py +60 -0
  238. scitex/fts/_fig/_models/_plot_types/_histogram.py +30 -0
  239. scitex/fts/_fig/_models/_plot_types/_image.py +61 -0
  240. scitex/fts/_fig/_models/_plot_types/_line.py +57 -0
  241. scitex/fts/_fig/_models/_plot_types/_scatter.py +30 -0
  242. scitex/fts/_fig/_models/_plot_types/_seaborn.py +121 -0
  243. scitex/fts/_fig/_models/_plot_types/_violin.py +36 -0
  244. scitex/fts/_fig/_utils/__init__.py +129 -0
  245. scitex/fts/_fig/_utils/_auto_layout.py +127 -0
  246. scitex/fts/_fig/_utils/_calc_bounds.py +111 -0
  247. scitex/fts/_fig/_utils/_const_sizes.py +48 -0
  248. scitex/fts/_fig/_utils/_convert_coords.py +77 -0
  249. scitex/fts/_fig/_utils/_get_template.py +178 -0
  250. scitex/fts/_fig/_utils/_normalize.py +73 -0
  251. scitex/fts/_fig/_utils/_plot_layout.py +397 -0
  252. scitex/fts/_fig/_utils/_validate.py +197 -0
  253. scitex/fts/_kinds/__init__.py +45 -0
  254. scitex/fts/_kinds/_figure/__init__.py +19 -0
  255. scitex/fts/_kinds/_figure/_composite.py +345 -0
  256. scitex/fts/_kinds/_plot/__init__.py +25 -0
  257. scitex/fts/_kinds/_plot/_backend/__init__.py +53 -0
  258. scitex/fts/_kinds/_plot/_backend/_export.py +165 -0
  259. scitex/fts/_kinds/_plot/_backend/_parser.py +188 -0
  260. scitex/fts/_kinds/_plot/_backend/_render.py +538 -0
  261. scitex/fts/_kinds/_plot/_dataclasses/_ChannelEncoding.py +46 -0
  262. scitex/fts/_kinds/_plot/_dataclasses/_Encoding.py +82 -0
  263. scitex/fts/_kinds/_plot/_dataclasses/_Theme.py +441 -0
  264. scitex/fts/_kinds/_plot/_dataclasses/_TraceEncoding.py +52 -0
  265. scitex/fts/_kinds/_plot/_dataclasses/__init__.py +47 -0
  266. scitex/fts/_kinds/_plot/_models/_Annotations.py +115 -0
  267. scitex/fts/_kinds/_plot/_models/_Axes.py +152 -0
  268. scitex/fts/_kinds/_plot/_models/_Figure.py +138 -0
  269. scitex/fts/_kinds/_plot/_models/_Guides.py +104 -0
  270. scitex/fts/_kinds/_plot/_models/_Plot.py +123 -0
  271. scitex/fts/_kinds/_plot/_models/_Styles.py +245 -0
  272. scitex/fts/_kinds/_plot/_models/__init__.py +80 -0
  273. scitex/fts/_kinds/_plot/_models/_plot_types/__init__.py +156 -0
  274. scitex/fts/_kinds/_plot/_models/_plot_types/_bar.py +43 -0
  275. scitex/fts/_kinds/_plot/_models/_plot_types/_box.py +38 -0
  276. scitex/fts/_kinds/_plot/_models/_plot_types/_distribution.py +36 -0
  277. scitex/fts/_kinds/_plot/_models/_plot_types/_errorbar.py +60 -0
  278. scitex/fts/_kinds/_plot/_models/_plot_types/_histogram.py +30 -0
  279. scitex/fts/_kinds/_plot/_models/_plot_types/_image.py +61 -0
  280. scitex/fts/_kinds/_plot/_models/_plot_types/_line.py +57 -0
  281. scitex/fts/_kinds/_plot/_models/_plot_types/_scatter.py +30 -0
  282. scitex/fts/_kinds/_plot/_models/_plot_types/_seaborn.py +121 -0
  283. scitex/fts/_kinds/_plot/_models/_plot_types/_violin.py +36 -0
  284. scitex/fts/_kinds/_plot/_utils/__init__.py +129 -0
  285. scitex/fts/_kinds/_plot/_utils/_auto_layout.py +127 -0
  286. scitex/fts/_kinds/_plot/_utils/_calc_bounds.py +111 -0
  287. scitex/fts/_kinds/_plot/_utils/_const_sizes.py +48 -0
  288. scitex/fts/_kinds/_plot/_utils/_convert_coords.py +77 -0
  289. scitex/fts/_kinds/_plot/_utils/_get_template.py +178 -0
  290. scitex/fts/_kinds/_plot/_utils/_normalize.py +73 -0
  291. scitex/fts/_kinds/_plot/_utils/_plot_layout.py +397 -0
  292. scitex/fts/_kinds/_plot/_utils/_validate.py +197 -0
  293. scitex/fts/_kinds/_shape/__init__.py +141 -0
  294. scitex/fts/_kinds/_stats/__init__.py +56 -0
  295. scitex/fts/_kinds/_stats/_dataclasses/_Stats.py +423 -0
  296. scitex/fts/_kinds/_stats/_dataclasses/__init__.py +48 -0
  297. scitex/fts/_kinds/_table/__init__.py +72 -0
  298. scitex/fts/_kinds/_table/_latex/__init__.py +93 -0
  299. scitex/fts/_kinds/_table/_latex/_editor/__init__.py +11 -0
  300. scitex/fts/_kinds/_table/_latex/_editor/_app.py +725 -0
  301. scitex/fts/_kinds/_table/_latex/_export.py +279 -0
  302. scitex/fts/_kinds/_table/_latex/_figure_exporter.py +153 -0
  303. scitex/fts/_kinds/_table/_latex/_stats_formatter.py +274 -0
  304. scitex/fts/_kinds/_table/_latex/_table_exporter.py +362 -0
  305. scitex/fts/_kinds/_table/_latex/_utils.py +369 -0
  306. scitex/fts/_kinds/_table/_latex/_validator.py +445 -0
  307. scitex/fts/_kinds/_text/__init__.py +77 -0
  308. scitex/fts/_schemas/data_info.schema.json +75 -0
  309. scitex/fts/_schemas/encoding.schema.json +90 -0
  310. scitex/fts/_schemas/node.schema.json +145 -0
  311. scitex/fts/_schemas/render_manifest.schema.json +62 -0
  312. scitex/fts/_schemas/stats.schema.json +132 -0
  313. scitex/fts/_schemas/theme.schema.json +141 -0
  314. scitex/fts/_stats/__init__.py +48 -0
  315. scitex/fts/_stats/_dataclasses/_Stats.py +423 -0
  316. scitex/fts/_stats/_dataclasses/__init__.py +48 -0
  317. scitex/fts/_tables/__init__.py +65 -0
  318. scitex/fts/_tables/_latex/__init__.py +93 -0
  319. scitex/fts/_tables/_latex/_editor/__init__.py +11 -0
  320. scitex/fts/_tables/_latex/_editor/_app.py +725 -0
  321. scitex/fts/_tables/_latex/_export.py +279 -0
  322. scitex/fts/_tables/_latex/_figure_exporter.py +153 -0
  323. scitex/fts/_tables/_latex/_stats_formatter.py +274 -0
  324. scitex/fts/_tables/_latex/_table_exporter.py +362 -0
  325. scitex/fts/_tables/_latex/_utils.py +369 -0
  326. scitex/fts/_tables/_latex/_validator.py +445 -0
  327. scitex/gen/__init__.py +66 -25
  328. scitex/gen/misc.py +28 -0
  329. scitex/io/__init__.py +47 -32
  330. scitex/io/_load.py +87 -36
  331. scitex/io/_load_modules/__init__.py +10 -7
  332. scitex/io/_load_modules/_pandas.py +6 -1
  333. scitex/io/_save.py +299 -1556
  334. scitex/io/_save_modules/__init__.py +76 -19
  335. scitex/io/_save_modules/_figure_utils.py +90 -0
  336. scitex/io/_save_modules/_image_csv.py +497 -0
  337. scitex/io/_save_modules/_legends.py +91 -0
  338. scitex/io/_save_modules/_pltz_bundle.py +356 -0
  339. scitex/io/_save_modules/_pltz_stx.py +536 -0
  340. scitex/io/_save_modules/_stx_bundle.py +104 -0
  341. scitex/io/_save_modules/_symlink.py +96 -0
  342. scitex/io/_save_modules/_yaml.py +1 -1
  343. scitex/io/_save_modules/_zarr.py +64 -18
  344. scitex/io/bundle/README.md +212 -0
  345. scitex/io/bundle/__init__.py +110 -0
  346. scitex/io/{_bundle.py → bundle/_core.py} +168 -97
  347. scitex/io/bundle/_nested.py +713 -0
  348. scitex/io/bundle/_types.py +74 -0
  349. scitex/io/{_zip_bundle.py → bundle/_zip.py} +93 -45
  350. scitex/io/utils/h5_to_zarr.py +1 -1
  351. scitex/logging/__init__.py +108 -13
  352. scitex/logging/_errors.py +508 -0
  353. scitex/logging/_formatters.py +30 -6
  354. scitex/logging/_warnings.py +261 -0
  355. scitex/plt/__init__.py +4 -1
  356. scitex/plt/_figrecipe.py +236 -0
  357. scitex/plt/_subplots/_AxisWrapper.py +6 -0
  358. scitex/plt/_subplots/_AxisWrapperMixins/_UnitAwareMixin.py +112 -1
  359. scitex/plt/_subplots/_FigWrapper.py +15 -0
  360. scitex/plt/_subplots/_SubplotsWrapper.py +125 -489
  361. scitex/plt/_subplots/_export_as_csv.py +11 -0
  362. scitex/plt/_subplots/_export_as_csv_formatters/__init__.py +2 -0
  363. scitex/plt/_subplots/_export_as_csv_formatters/_format_pcolormesh.py +66 -0
  364. scitex/plt/_subplots/_export_as_csv_formatters/_format_stackplot.py +62 -0
  365. scitex/plt/_subplots/_export_as_csv_formatters/test_formatters.py +208 -0
  366. scitex/plt/_subplots/_fonts.py +71 -0
  367. scitex/plt/_subplots/_mm_layout.py +282 -0
  368. scitex/plt/gallery/__init__.py +99 -2
  369. scitex/plt/styles/_plot_postprocess.py +3 -1
  370. scitex/plt/utils/_configure_mpl.py +16 -19
  371. scitex/repro/_RandomStateManager.py +13 -8
  372. scitex/resource/__init__.py +19 -1
  373. scitex/resource/_utils/_get_env_info.py +13 -25
  374. scitex/schema/__init__.py +149 -160
  375. scitex/schema/_encoding.py +273 -0
  376. scitex/schema/_figure_elements.py +406 -0
  377. scitex/schema/_theme.py +360 -0
  378. scitex/schema/_validation.py +0 -98
  379. scitex/scholar/__init__.py +56 -14
  380. scitex/scholar/auth/ScholarAuthManager.py +1 -1
  381. scitex/scholar/auth/__init__.py +11 -2
  382. scitex/scholar/auth/providers/BaseAuthenticator.py +1 -1
  383. scitex/scholar/auth/providers/EZProxyAuthenticator.py +1 -1
  384. scitex/scholar/auth/providers/OpenAthensAuthenticator.py +1 -1
  385. scitex/scholar/auth/providers/ShibbolethAuthenticator.py +1 -1
  386. scitex/scholar/config/ScholarConfig.py +1 -1
  387. scitex/scholar/core/Scholar.py +1 -1
  388. scitex/session/_decorator.py +18 -16
  389. scitex/session/_lifecycle.py +9 -11
  390. scitex/session/template.py +9 -8
  391. scitex/sh/test_sh.py +72 -0
  392. scitex/sh/test_sh_simple.py +61 -0
  393. scitex/stats/__init__.py +221 -97
  394. scitex/stats/_schema.py +21 -22
  395. scitex/stats/descriptive/_circular.py +212 -351
  396. scitex/stats/descriptive/_describe.py +81 -132
  397. scitex/stats/descriptive/_nan.py +205 -433
  398. scitex/stats/descriptive/_real.py +127 -141
  399. scitex/str/_format_plot_text.py +5 -5
  400. scitex/str/_latex.py +26 -84
  401. scitex/str/_latex_fallback.py +53 -47
  402. scitex/web/_search_pubmed.py +5 -4
  403. scitex/writer/tests/test_diff_between.py +451 -0
  404. scitex/writer/tests/test_document_section.py +311 -0
  405. scitex/writer/tests/test_document_workflow.py +393 -0
  406. scitex/writer/tests/test_writer.py +361 -0
  407. scitex/writer/tests/test_writer_integration.py +303 -0
  408. {scitex-2.8.1.dist-info → scitex-2.10.0.dist-info}/METADATA +364 -181
  409. {scitex-2.8.1.dist-info → scitex-2.10.0.dist-info}/RECORD +412 -97
  410. scitex/scholar/docs/to_claude/guidelines/examples/mgmt/ARCHITECTURE_EXAMPLE.md +0 -905
  411. scitex/scholar/docs/to_claude/guidelines/examples/mgmt/BULLETIN_BOARD_EXAMPLE.md +0 -99
  412. scitex/scholar/docs/to_claude/guidelines/examples/mgmt/PROJECT_DESCRIPTION_EXAMPLE.md +0 -96
  413. {scitex-2.8.1.dist-info → scitex-2.10.0.dist-info}/WHEEL +0 -0
  414. {scitex-2.8.1.dist-info → scitex-2.10.0.dist-info}/entry_points.txt +0 -0
  415. {scitex-2.8.1.dist-info → scitex-2.10.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,508 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # Timestamp: "2025-12-21"
4
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/logging/_errors.py
5
+
6
+ """Error classes for SciTeX.
7
+
8
+ All SciTeX exceptions are defined here for unified error handling.
9
+ Re-exported from scitex.logging for backwards compatibility.
10
+
11
+ Usage:
12
+ from scitex.logging import SciTeXError, SaveError, LoadError
13
+ # or (backwards compatible)
14
+ from scitex.logging import SciTeXError, SaveError, LoadError
15
+ """
16
+
17
+ from typing import Optional, Union
18
+
19
+
20
+ # =============================================================================
21
+ # Base Errors
22
+ # =============================================================================
23
+
24
+
25
+ class SciTeXError(Exception):
26
+ """Base Exception class for all SciTeX errors."""
27
+
28
+ def __init__(
29
+ self,
30
+ message: str,
31
+ context: Optional[dict] = None,
32
+ suggestion: Optional[str] = None,
33
+ ):
34
+ """Initialize SciTeX error with detailed information.
35
+
36
+ Parameters
37
+ ----------
38
+ message : str
39
+ The error message
40
+ context : dict, optional
41
+ Additional context information (e.g., file paths, variable values)
42
+ suggestion : str, optional
43
+ Suggested fix or action
44
+ """
45
+ self.message = message
46
+ self.context = context or {}
47
+ self.suggestion = suggestion
48
+
49
+ # Build the full error message
50
+ error_parts = [f"SciTeX Error: {message}"]
51
+
52
+ if context:
53
+ error_parts.append("\nContext:")
54
+ for key, value in context.items():
55
+ error_parts.append(f" {key}: {value}")
56
+
57
+ if suggestion:
58
+ error_parts.append(f"\nSuggestion: {suggestion}")
59
+
60
+ super().__init__("\n".join(error_parts))
61
+
62
+
63
+ # =============================================================================
64
+ # Configuration Errors
65
+ # =============================================================================
66
+
67
+
68
+ class ConfigurationError(SciTeXError):
69
+ """Raised when there are issues with SciTeX configuration."""
70
+
71
+ pass
72
+
73
+
74
+ class ConfigFileNotFoundError(ConfigurationError):
75
+ """Raised when a required configuration file is not found."""
76
+
77
+ def __init__(self, filepath: str):
78
+ super().__init__(
79
+ f"Configuration file not found: {filepath}",
80
+ context={"filepath": filepath},
81
+ suggestion="Ensure the configuration file exists in ./config/ directory",
82
+ )
83
+
84
+
85
+ class ConfigKeyError(ConfigurationError):
86
+ """Raised when a required configuration key is missing."""
87
+
88
+ def __init__(self, key: str, available_keys: Optional[list] = None):
89
+ context = {"missing_key": key}
90
+ if available_keys:
91
+ context["available_keys"] = available_keys
92
+
93
+ super().__init__(
94
+ f"Configuration key '{key}' not found",
95
+ context=context,
96
+ suggestion=f"Add '{key}' to your configuration file or check for typos",
97
+ )
98
+
99
+
100
+ # =============================================================================
101
+ # IO Errors
102
+ # =============================================================================
103
+
104
+
105
+ class IOError(SciTeXError):
106
+ """Base class for input/output related errors."""
107
+
108
+ pass
109
+
110
+
111
+ class FileFormatError(IOError):
112
+ """Raised when file format is not supported or incorrect."""
113
+
114
+ def __init__(
115
+ self,
116
+ filepath: str,
117
+ expected_format: Optional[str] = None,
118
+ actual_format: Optional[str] = None,
119
+ ):
120
+ context = {"filepath": filepath}
121
+ if expected_format:
122
+ context["expected_format"] = expected_format
123
+ if actual_format:
124
+ context["actual_format"] = actual_format
125
+
126
+ message = f"File format error for: {filepath}"
127
+ if expected_format and actual_format:
128
+ message += f" (expected: {expected_format}, got: {actual_format})"
129
+
130
+ super().__init__(
131
+ message,
132
+ context=context,
133
+ suggestion="Check the file extension and content format",
134
+ )
135
+
136
+
137
+ class SaveError(IOError):
138
+ """Raised when saving data fails."""
139
+
140
+ def __init__(self, filepath: str, reason: str):
141
+ super().__init__(
142
+ f"Failed to save to {filepath}: {reason}",
143
+ context={"filepath": filepath, "reason": reason},
144
+ suggestion="Check file permissions and disk space",
145
+ )
146
+
147
+
148
+ class LoadError(IOError):
149
+ """Raised when loading data fails."""
150
+
151
+ def __init__(self, filepath: str, reason: str):
152
+ super().__init__(
153
+ f"Failed to load from {filepath}: {reason}",
154
+ context={"filepath": filepath, "reason": reason},
155
+ suggestion="Verify the file exists and is not corrupted",
156
+ )
157
+
158
+
159
+ # =============================================================================
160
+ # Scholar Module Errors
161
+ # =============================================================================
162
+
163
+
164
+ class ScholarError(SciTeXError):
165
+ """Base class for scholar module errors."""
166
+
167
+ pass
168
+
169
+
170
+ class SearchError(ScholarError):
171
+ """Raised when paper search fails."""
172
+
173
+ def __init__(self, query: str, source: str, reason: str):
174
+ super().__init__(
175
+ f"Search failed for query '{query}' on {source}",
176
+ context={"query": query, "source": source, "reason": reason},
177
+ suggestion="Check your internet connection and API keys",
178
+ )
179
+
180
+
181
+ class EnrichmentError(ScholarError):
182
+ """Raised when paper enrichment fails."""
183
+
184
+ def __init__(self, paper_title: str, reason: str):
185
+ super().__init__(
186
+ f"Failed to enrich paper: {paper_title}",
187
+ context={"paper_title": paper_title, "reason": reason},
188
+ suggestion="Verify journal information is available",
189
+ )
190
+
191
+
192
+ class PDFDownloadError(ScholarError):
193
+ """Raised when PDF download fails."""
194
+
195
+ def __init__(self, url: str, reason: str):
196
+ super().__init__(
197
+ f"Failed to download PDF from {url}",
198
+ context={"url": url, "reason": reason},
199
+ suggestion="Check if the paper is open access",
200
+ )
201
+
202
+
203
+ class DOIResolutionError(ScholarError):
204
+ """Raised when DOI resolution fails."""
205
+
206
+ def __init__(self, doi: str, reason: str):
207
+ super().__init__(
208
+ f"Failed to resolve DOI: {doi}",
209
+ context={"doi": doi, "reason": reason},
210
+ suggestion="Verify the DOI is correct and try again",
211
+ )
212
+
213
+
214
+ class PDFExtractionError(ScholarError):
215
+ """Raised when PDF text extraction fails."""
216
+
217
+ def __init__(self, filepath: str, reason: str):
218
+ super().__init__(
219
+ f"Failed to extract text from PDF: {filepath}",
220
+ context={"filepath": filepath, "reason": reason},
221
+ suggestion="Ensure the PDF is not corrupted or encrypted",
222
+ )
223
+
224
+
225
+ class BibTeXEnrichmentError(ScholarError):
226
+ """Raised when BibTeX enrichment fails."""
227
+
228
+ def __init__(self, bibtex_file: str, reason: str):
229
+ super().__init__(
230
+ f"Failed to enrich BibTeX file: {bibtex_file}",
231
+ context={"bibtex_file": bibtex_file, "reason": reason},
232
+ suggestion="Check the BibTeX format and ensure all entries are valid",
233
+ )
234
+
235
+
236
+ class TranslatorError(ScholarError):
237
+ """Raised when Zotero translator operations fail."""
238
+
239
+ def __init__(self, translator_name: str, reason: str):
240
+ super().__init__(
241
+ f"Translator error in {translator_name}: {reason}",
242
+ context={"translator": translator_name, "reason": reason},
243
+ suggestion="Check translator compatibility and JavaScript environment",
244
+ )
245
+
246
+
247
+ class AuthenticationError(ScholarError):
248
+ """Raised when authentication fails."""
249
+
250
+ def __init__(self, provider: str, reason: str = ""):
251
+ super().__init__(
252
+ f"Authentication failed for {provider}: {reason}",
253
+ context={"provider": provider, "reason": reason},
254
+ suggestion="Check your credentials and authentication settings",
255
+ )
256
+
257
+
258
+ # =============================================================================
259
+ # Plotting Errors
260
+ # =============================================================================
261
+
262
+
263
+ class PlottingError(SciTeXError):
264
+ """Base class for plotting-related errors."""
265
+
266
+ pass
267
+
268
+
269
+ class FigureNotFoundError(PlottingError):
270
+ """Raised when attempting to operate on a non-existent figure."""
271
+
272
+ def __init__(self, fig_id: Union[int, str]):
273
+ super().__init__(
274
+ f"Figure {fig_id} not found",
275
+ context={"figure_id": fig_id},
276
+ suggestion="Ensure the figure was created before attempting to save/modify it",
277
+ )
278
+
279
+
280
+ class AxisError(PlottingError):
281
+ """Raised when there are issues with plot axes."""
282
+
283
+ def __init__(self, message: str, axis_info: Optional[dict] = None):
284
+ super().__init__(
285
+ message,
286
+ context={"axis_info": axis_info} if axis_info else None,
287
+ suggestion="Check axis indices and subplot configuration",
288
+ )
289
+
290
+
291
+ # =============================================================================
292
+ # Data Processing Errors
293
+ # =============================================================================
294
+
295
+
296
+ class DataError(SciTeXError):
297
+ """Base class for data processing errors."""
298
+
299
+ pass
300
+
301
+
302
+ class ShapeError(DataError):
303
+ """Raised when data shapes are incompatible."""
304
+
305
+ def __init__(self, expected_shape: tuple, actual_shape: tuple, operation: str):
306
+ super().__init__(
307
+ f"Shape mismatch in {operation}",
308
+ context={
309
+ "expected_shape": expected_shape,
310
+ "actual_shape": actual_shape,
311
+ "operation": operation,
312
+ },
313
+ suggestion="Reshape or transpose your data to match expected dimensions",
314
+ )
315
+
316
+
317
+ class DTypeError(DataError):
318
+ """Raised when data types are incompatible."""
319
+
320
+ def __init__(self, expected_dtype: str, actual_dtype: str, operation: str):
321
+ super().__init__(
322
+ f"Data type mismatch in {operation}",
323
+ context={
324
+ "expected_dtype": expected_dtype,
325
+ "actual_dtype": actual_dtype,
326
+ "operation": operation,
327
+ },
328
+ suggestion=f"Convert data to {expected_dtype} using appropriate casting",
329
+ )
330
+
331
+
332
+ # =============================================================================
333
+ # Path Errors
334
+ # =============================================================================
335
+
336
+
337
+ class PathError(SciTeXError):
338
+ """Base class for path-related errors."""
339
+
340
+ pass
341
+
342
+
343
+ class InvalidPathError(PathError):
344
+ """Raised when a path is invalid or doesn't follow SciTeX conventions."""
345
+
346
+ def __init__(self, path: str, reason: str):
347
+ super().__init__(
348
+ f"Invalid path: {path}",
349
+ context={"path": path, "reason": reason},
350
+ suggestion="Use relative paths starting with './' or '../'",
351
+ )
352
+
353
+
354
+ class PathNotFoundError(PathError):
355
+ """Raised when a required path doesn't exist."""
356
+
357
+ def __init__(self, path: str):
358
+ super().__init__(
359
+ f"Path not found: {path}",
360
+ context={"path": path},
361
+ suggestion="Check if the path exists and is accessible",
362
+ )
363
+
364
+
365
+ # =============================================================================
366
+ # Template Errors
367
+ # =============================================================================
368
+
369
+
370
+ class TemplateError(SciTeXError):
371
+ """Base class for template-related errors."""
372
+
373
+ pass
374
+
375
+
376
+ class TemplateViolationError(TemplateError):
377
+ """Raised when SciTeX template is not followed."""
378
+
379
+ def __init__(self, filepath: str, violation: str):
380
+ super().__init__(
381
+ f"Template violation in {filepath}: {violation}",
382
+ context={"filepath": filepath, "violation": violation},
383
+ suggestion="Follow the SciTeX template structure as defined in IMPORTANT-SCITEX-02-file-template.md",
384
+ )
385
+
386
+
387
+ # =============================================================================
388
+ # Neural Network Errors
389
+ # =============================================================================
390
+
391
+
392
+ class NNError(SciTeXError):
393
+ """Base class for neural network module errors."""
394
+
395
+ pass
396
+
397
+
398
+ class ModelError(NNError):
399
+ """Raised when there are issues with neural network models."""
400
+
401
+ def __init__(self, model_name: str, reason: str):
402
+ super().__init__(
403
+ f"Model error in {model_name}: {reason}",
404
+ context={"model_name": model_name, "reason": reason},
405
+ )
406
+
407
+
408
+ # =============================================================================
409
+ # Statistics Errors
410
+ # =============================================================================
411
+
412
+
413
+ class StatsError(SciTeXError):
414
+ """Base class for statistics module errors."""
415
+
416
+ pass
417
+
418
+
419
+ class TestError(StatsError):
420
+ """Raised when statistical tests fail."""
421
+
422
+ def __init__(self, test_name: str, reason: str):
423
+ super().__init__(
424
+ f"Statistical test '{test_name}' failed: {reason}",
425
+ context={"test_name": test_name, "reason": reason},
426
+ )
427
+
428
+
429
+ # =============================================================================
430
+ # Validation Helpers
431
+ # =============================================================================
432
+
433
+
434
+ def check_path(path: str) -> None:
435
+ """Validate a path according to SciTeX conventions."""
436
+ if not isinstance(path, str):
437
+ raise InvalidPathError(str(path), "Path must be a string")
438
+
439
+ if not (path.startswith("./") or path.startswith("../")):
440
+ raise InvalidPathError(
441
+ path, "Path must be relative and start with './' or '../'"
442
+ )
443
+
444
+
445
+ def check_file_exists(filepath: str) -> None:
446
+ """Check if a file exists."""
447
+ import os
448
+
449
+ if not os.path.exists(filepath):
450
+ raise PathNotFoundError(filepath)
451
+
452
+
453
+ def check_shape_compatibility(shape1: tuple, shape2: tuple, operation: str) -> None:
454
+ """Check if two shapes are compatible for an operation."""
455
+ if shape1 != shape2:
456
+ raise ShapeError(shape1, shape2, operation)
457
+
458
+
459
+ __all__ = [
460
+ # Base errors
461
+ "SciTeXError",
462
+ # Configuration
463
+ "ConfigurationError",
464
+ "ConfigFileNotFoundError",
465
+ "ConfigKeyError",
466
+ # IO
467
+ "IOError",
468
+ "FileFormatError",
469
+ "SaveError",
470
+ "LoadError",
471
+ # Scholar
472
+ "ScholarError",
473
+ "SearchError",
474
+ "EnrichmentError",
475
+ "PDFDownloadError",
476
+ "DOIResolutionError",
477
+ "PDFExtractionError",
478
+ "BibTeXEnrichmentError",
479
+ "TranslatorError",
480
+ "AuthenticationError",
481
+ # Plotting
482
+ "PlottingError",
483
+ "FigureNotFoundError",
484
+ "AxisError",
485
+ # Data
486
+ "DataError",
487
+ "ShapeError",
488
+ "DTypeError",
489
+ # Path
490
+ "PathError",
491
+ "InvalidPathError",
492
+ "PathNotFoundError",
493
+ # Template
494
+ "TemplateError",
495
+ "TemplateViolationError",
496
+ # Neural Network
497
+ "NNError",
498
+ "ModelError",
499
+ # Statistics
500
+ "StatsError",
501
+ "TestError",
502
+ # Validation helpers
503
+ "check_path",
504
+ "check_file_exists",
505
+ "check_shape_compatibility",
506
+ ]
507
+
508
+ # EOF
@@ -21,6 +21,10 @@ import sys
21
21
  # SCITEX_LOG_FORMAT=debug python script.py
22
22
  LOG_FORMAT = os.getenv("SCITEX_LOG_FORMAT", "default")
23
23
 
24
+ # Force color output even when stdout is not a TTY (e.g., when piping through tee)
25
+ # SCITEX_FORCE_COLOR=1 python script.py | tee output.log
26
+ FORCE_COLOR = os.getenv("SCITEX_FORCE_COLOR", "").lower() in ("1", "true", "yes")
27
+
24
28
  # Available format templates
25
29
  FORMAT_TEMPLATES = {
26
30
  "minimal": "%(levelname)s: %(message)s",
@@ -80,6 +84,14 @@ class SciTeXConsoleFormatter(logging.Formatter):
80
84
  self.indent_width = indent_width
81
85
 
82
86
  def format(self, record):
87
+ # Handle leading newlines: extract and preserve them
88
+ msg = str(record.msg) if record.msg else ""
89
+ leading_newlines = ""
90
+ while msg.startswith("\n"):
91
+ leading_newlines += "\n"
92
+ msg = msg[1:]
93
+ record.msg = msg
94
+
83
95
  # Apply indentation if specified in record
84
96
  indent_level = getattr(record, "indent", 0)
85
97
  if indent_level > 0:
@@ -89,12 +101,23 @@ class SciTeXConsoleFormatter(logging.Formatter):
89
101
  # Use parent formatter to apply template
90
102
  formatted = super().format(record)
91
103
 
92
- # Check if we can use colors (stdout is a tty and not closed)
104
+ # Handle internal newlines: each line gets the level prefix
105
+ if "\n" in formatted:
106
+ lines = formatted.split("\n")
107
+ # First line already has prefix from parent formatter
108
+ # Add prefix to each continuation line
109
+ prefix = f"{record.levelname}: "
110
+ formatted = lines[0] + "\n" + "\n".join(
111
+ prefix + line if line.strip() else line
112
+ for line in lines[1:]
113
+ )
114
+
115
+ # Check if we can use colors (stdout is a tty and not closed, or forced)
93
116
  try:
94
- use_colors = hasattr(sys.stdout, "isatty") and sys.stdout.isatty()
117
+ use_colors = FORCE_COLOR or (hasattr(sys.stdout, "isatty") and sys.stdout.isatty())
95
118
  except ValueError:
96
119
  # stdout/stderr is closed
97
- use_colors = False
120
+ use_colors = FORCE_COLOR
98
121
 
99
122
  if use_colors:
100
123
  # Check for custom color override
@@ -103,15 +126,15 @@ class SciTeXConsoleFormatter(logging.Formatter):
103
126
  if custom_color and custom_color in self.COLOR_NAMES:
104
127
  # Use custom color
105
128
  color = self.COLOR_NAMES[custom_color]
106
- return f"{color}{formatted}{self.RESET}"
129
+ return f"{leading_newlines}{color}{formatted}{self.RESET}"
107
130
  else:
108
131
  # Use default color for log level
109
132
  levelname = record.levelname
110
133
  if levelname in self.COLORS:
111
134
  color = self.COLORS[levelname]
112
- return f"{color}{formatted}{self.RESET}"
135
+ return f"{leading_newlines}{color}{formatted}{self.RESET}"
113
136
 
114
- return formatted
137
+ return f"{leading_newlines}{formatted}"
115
138
 
116
139
 
117
140
  class SciTeXFileFormatter(logging.Formatter):
@@ -129,6 +152,7 @@ __all__ = [
129
152
  "SciTeXFileFormatter",
130
153
  "LOG_FORMAT",
131
154
  "FORMAT_TEMPLATES",
155
+ "FORCE_COLOR",
132
156
  ]
133
157
 
134
158
  # EOF