scitex 2.7.3__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 (563) 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/dev/plt/plot_mpl_axhline.py +0 -0
  105. scitex/dev/plt/plot_mpl_axhspan.py +0 -0
  106. scitex/dev/plt/plot_mpl_axvline.py +0 -0
  107. scitex/dev/plt/plot_mpl_axvspan.py +0 -0
  108. scitex/dev/plt/plot_mpl_bar.py +0 -0
  109. scitex/dev/plt/plot_mpl_barh.py +0 -0
  110. scitex/dev/plt/plot_mpl_boxplot.py +0 -0
  111. scitex/dev/plt/plot_mpl_contour.py +0 -0
  112. scitex/dev/plt/plot_mpl_contourf.py +0 -0
  113. scitex/dev/plt/plot_mpl_errorbar.py +0 -0
  114. scitex/dev/plt/plot_mpl_eventplot.py +0 -0
  115. scitex/dev/plt/plot_mpl_fill.py +0 -0
  116. scitex/dev/plt/plot_mpl_fill_between.py +0 -0
  117. scitex/dev/plt/plot_mpl_hexbin.py +0 -0
  118. scitex/dev/plt/plot_mpl_hist.py +0 -0
  119. scitex/dev/plt/plot_mpl_hist2d.py +0 -0
  120. scitex/dev/plt/plot_mpl_imshow.py +0 -0
  121. scitex/dev/plt/plot_mpl_pcolormesh.py +0 -0
  122. scitex/dev/plt/plot_mpl_pie.py +0 -0
  123. scitex/dev/plt/plot_mpl_plot.py +0 -0
  124. scitex/dev/plt/plot_mpl_quiver.py +0 -0
  125. scitex/dev/plt/plot_mpl_scatter.py +0 -0
  126. scitex/dev/plt/plot_mpl_stackplot.py +0 -0
  127. scitex/dev/plt/plot_mpl_stem.py +0 -0
  128. scitex/dev/plt/plot_mpl_step.py +0 -0
  129. scitex/dev/plt/plot_mpl_violinplot.py +0 -0
  130. scitex/dev/plt/plot_sns_barplot.py +0 -0
  131. scitex/dev/plt/plot_sns_boxplot.py +0 -0
  132. scitex/dev/plt/plot_sns_heatmap.py +0 -0
  133. scitex/dev/plt/plot_sns_histplot.py +0 -0
  134. scitex/dev/plt/plot_sns_kdeplot.py +0 -0
  135. scitex/dev/plt/plot_sns_lineplot.py +0 -0
  136. scitex/dev/plt/plot_sns_scatterplot.py +0 -0
  137. scitex/dev/plt/plot_sns_stripplot.py +0 -0
  138. scitex/dev/plt/plot_sns_swarmplot.py +0 -0
  139. scitex/dev/plt/plot_sns_violinplot.py +0 -0
  140. scitex/dev/plt/plot_stx_bar.py +0 -0
  141. scitex/dev/plt/plot_stx_barh.py +0 -0
  142. scitex/dev/plt/plot_stx_box.py +0 -0
  143. scitex/dev/plt/plot_stx_boxplot.py +0 -0
  144. scitex/dev/plt/plot_stx_conf_mat.py +0 -0
  145. scitex/dev/plt/plot_stx_contour.py +0 -0
  146. scitex/dev/plt/plot_stx_ecdf.py +0 -0
  147. scitex/dev/plt/plot_stx_errorbar.py +0 -0
  148. scitex/dev/plt/plot_stx_fill_between.py +0 -0
  149. scitex/dev/plt/plot_stx_fillv.py +0 -0
  150. scitex/dev/plt/plot_stx_heatmap.py +0 -0
  151. scitex/dev/plt/plot_stx_image.py +0 -0
  152. scitex/dev/plt/plot_stx_imshow.py +0 -0
  153. scitex/dev/plt/plot_stx_joyplot.py +0 -0
  154. scitex/dev/plt/plot_stx_kde.py +0 -0
  155. scitex/dev/plt/plot_stx_line.py +0 -0
  156. scitex/dev/plt/plot_stx_mean_ci.py +0 -0
  157. scitex/dev/plt/plot_stx_mean_std.py +0 -0
  158. scitex/dev/plt/plot_stx_median_iqr.py +0 -0
  159. scitex/dev/plt/plot_stx_raster.py +0 -0
  160. scitex/dev/plt/plot_stx_rectangle.py +0 -0
  161. scitex/dev/plt/plot_stx_scatter.py +0 -0
  162. scitex/dev/plt/plot_stx_shaded_line.py +0 -0
  163. scitex/dev/plt/plot_stx_violin.py +0 -0
  164. scitex/dev/plt/plot_stx_violinplot.py +0 -0
  165. scitex/diagram/README.md +197 -0
  166. scitex/diagram/__init__.py +48 -0
  167. scitex/diagram/_compile.py +312 -0
  168. scitex/diagram/_diagram.py +355 -0
  169. scitex/diagram/_presets.py +173 -0
  170. scitex/diagram/_schema.py +182 -0
  171. scitex/diagram/_split.py +278 -0
  172. scitex/dict/_pop_keys.py +1 -7
  173. scitex/dsp/__init__.py +15 -10
  174. scitex/dsp/add_noise.py +5 -2
  175. scitex/dsp/example.py +35 -22
  176. scitex/dsp/filt.py +8 -3
  177. scitex/dsp/reference.py +3 -2
  178. scitex/dsp/utils/__init__.py +2 -1
  179. scitex/dsp/utils/_differential_bandpass_filters.py +14 -4
  180. scitex/dt/__init__.py +39 -2
  181. scitex/errors.py +82 -521
  182. scitex/fig/__init__.py +4 -4
  183. scitex/fig/editor/__init__.py +5 -2
  184. scitex/fig/editor/_dearpygui_editor.py +1 -1
  185. scitex/fig/editor/_mpl_editor.py +1 -1
  186. scitex/fig/editor/_qt_editor.py +1 -1
  187. scitex/fig/editor/_tkinter_editor.py +1 -1
  188. scitex/fig/editor/edit/__init__.py +50 -0
  189. scitex/fig/editor/edit/backend_detector.py +109 -0
  190. scitex/fig/editor/edit/bundle_resolver.py +240 -0
  191. scitex/fig/editor/edit/editor_launcher.py +239 -0
  192. scitex/fig/editor/edit/manual_handler.py +53 -0
  193. scitex/fig/editor/edit/panel_loader.py +232 -0
  194. scitex/fig/editor/edit/path_resolver.py +67 -0
  195. scitex/fig/editor/flask_editor/_bbox.py +23 -0
  196. scitex/fig/editor/flask_editor/_core.py +908 -103
  197. scitex/fig/editor/flask_editor/_renderer.py +74 -0
  198. scitex/fig/editor/flask_editor/static/css/base/reset.css +41 -0
  199. scitex/fig/editor/flask_editor/static/css/base/typography.css +16 -0
  200. scitex/fig/editor/flask_editor/static/css/base/variables.css +85 -0
  201. scitex/fig/editor/flask_editor/static/css/components/buttons.css +217 -0
  202. scitex/fig/editor/flask_editor/static/css/components/context-menu.css +93 -0
  203. scitex/fig/editor/flask_editor/static/css/components/dropdown.css +57 -0
  204. scitex/fig/editor/flask_editor/static/css/components/forms.css +112 -0
  205. scitex/fig/editor/flask_editor/static/css/components/modal.css +59 -0
  206. scitex/fig/editor/flask_editor/static/css/components/sections.css +212 -0
  207. scitex/fig/editor/flask_editor/static/css/features/canvas.css +176 -0
  208. scitex/fig/editor/flask_editor/static/css/features/element-inspector.css +190 -0
  209. scitex/fig/editor/flask_editor/static/css/features/loading.css +59 -0
  210. scitex/fig/editor/flask_editor/static/css/features/overlay.css +45 -0
  211. scitex/fig/editor/flask_editor/static/css/features/panel-grid.css +95 -0
  212. scitex/fig/editor/flask_editor/static/css/features/selection.css +101 -0
  213. scitex/fig/editor/flask_editor/static/css/features/statistics.css +138 -0
  214. scitex/fig/editor/flask_editor/static/css/index.css +31 -0
  215. scitex/fig/editor/flask_editor/static/css/layout/container.css +7 -0
  216. scitex/fig/editor/flask_editor/static/css/layout/controls.css +56 -0
  217. scitex/fig/editor/flask_editor/static/css/layout/preview.css +78 -0
  218. scitex/fig/editor/flask_editor/static/js/alignment/axis.js +314 -0
  219. scitex/fig/editor/flask_editor/static/js/alignment/basic.js +107 -0
  220. scitex/fig/editor/flask_editor/static/js/alignment/distribute.js +54 -0
  221. scitex/fig/editor/flask_editor/static/js/canvas/canvas.js +172 -0
  222. scitex/fig/editor/flask_editor/static/js/canvas/dragging.js +258 -0
  223. scitex/fig/editor/flask_editor/static/js/canvas/resize.js +48 -0
  224. scitex/fig/editor/flask_editor/static/js/canvas/selection.js +71 -0
  225. scitex/fig/editor/flask_editor/static/js/core/api.js +288 -0
  226. scitex/fig/editor/flask_editor/static/js/core/state.js +143 -0
  227. scitex/fig/editor/flask_editor/static/js/core/utils.js +245 -0
  228. scitex/fig/editor/flask_editor/static/js/dev/element-inspector.js +992 -0
  229. scitex/fig/editor/flask_editor/static/js/editor/bbox.js +339 -0
  230. scitex/fig/editor/flask_editor/static/js/editor/element-drag.js +286 -0
  231. scitex/fig/editor/flask_editor/static/js/editor/overlay.js +371 -0
  232. scitex/fig/editor/flask_editor/static/js/editor/preview.js +293 -0
  233. scitex/fig/editor/flask_editor/static/js/main.js +426 -0
  234. scitex/fig/editor/flask_editor/static/js/shortcuts/context-menu.js +152 -0
  235. scitex/fig/editor/flask_editor/static/js/shortcuts/keyboard.js +265 -0
  236. scitex/fig/editor/flask_editor/static/js/ui/controls.js +184 -0
  237. scitex/fig/editor/flask_editor/static/js/ui/download.js +57 -0
  238. scitex/fig/editor/flask_editor/static/js/ui/help.js +100 -0
  239. scitex/fig/editor/flask_editor/static/js/ui/theme.js +34 -0
  240. scitex/fig/editor/flask_editor/templates/__init__.py +95 -5
  241. scitex/fig/editor/flask_editor/templates/_html.py +27 -9
  242. scitex/fig/editor/flask_editor/templates/_scripts.py +1928 -131
  243. scitex/fig/editor/flask_editor/templates/_styles.py +363 -51
  244. scitex/fig/io/_bundle.py +104 -19
  245. scitex/fts/README.md +262 -0
  246. scitex/fts/TODO.md +66 -0
  247. scitex/fts/__init__.py +90 -0
  248. scitex/fts/_bundle/README_IN_BUNDLE.md +102 -0
  249. scitex/fts/_bundle/_FTS.py +657 -0
  250. scitex/fts/_bundle/__init__.py +38 -0
  251. scitex/fts/_bundle/_children.py +216 -0
  252. scitex/fts/_bundle/_conversion/__init__.py +15 -0
  253. scitex/fts/_bundle/_conversion/_bundle2dict.py +44 -0
  254. scitex/fts/_bundle/_conversion/_dict2bundle.py +50 -0
  255. scitex/fts/_bundle/_dataclasses/_Axes.py +57 -0
  256. scitex/fts/_bundle/_dataclasses/_BBox.py +54 -0
  257. scitex/fts/_bundle/_dataclasses/_ColumnDef.py +72 -0
  258. scitex/fts/_bundle/_dataclasses/_DataFormat.py +40 -0
  259. scitex/fts/_bundle/_dataclasses/_DataInfo.py +135 -0
  260. scitex/fts/_bundle/_dataclasses/_DataSource.py +44 -0
  261. scitex/fts/_bundle/_dataclasses/_Node.py +319 -0
  262. scitex/fts/_bundle/_dataclasses/_NodeRefs.py +45 -0
  263. scitex/fts/_bundle/_dataclasses/_SizeMM.py +38 -0
  264. scitex/fts/_bundle/_dataclasses/__init__.py +35 -0
  265. scitex/fts/_bundle/_extractors/__init__.py +32 -0
  266. scitex/fts/_bundle/_extractors/_extract_bar.py +131 -0
  267. scitex/fts/_bundle/_extractors/_extract_line.py +71 -0
  268. scitex/fts/_bundle/_extractors/_extract_scatter.py +79 -0
  269. scitex/fts/_bundle/_loader.py +134 -0
  270. scitex/fts/_bundle/_mpl_helpers.py +389 -0
  271. scitex/fts/_bundle/_saver.py +269 -0
  272. scitex/fts/_bundle/_storage.py +200 -0
  273. scitex/fts/_bundle/_utils/__init__.py +55 -0
  274. scitex/fts/_bundle/_utils/_const.py +26 -0
  275. scitex/fts/_bundle/_utils/_errors.py +73 -0
  276. scitex/fts/_bundle/_utils/_generate.py +21 -0
  277. scitex/fts/_bundle/_utils/_types.py +76 -0
  278. scitex/fts/_bundle/_validation.py +434 -0
  279. scitex/fts/_bundle/_zipbundle.py +165 -0
  280. scitex/fts/_fig/__init__.py +22 -0
  281. scitex/fts/_fig/_backend/__init__.py +53 -0
  282. scitex/fts/_fig/_backend/_export.py +165 -0
  283. scitex/fts/_fig/_backend/_parser.py +188 -0
  284. scitex/fts/_fig/_backend/_render.py +538 -0
  285. scitex/fts/_fig/_composite.py +345 -0
  286. scitex/fts/_fig/_dataclasses/_ChannelEncoding.py +46 -0
  287. scitex/fts/_fig/_dataclasses/_Encoding.py +82 -0
  288. scitex/fts/_fig/_dataclasses/_Theme.py +441 -0
  289. scitex/fts/_fig/_dataclasses/_TraceEncoding.py +52 -0
  290. scitex/fts/_fig/_dataclasses/__init__.py +47 -0
  291. scitex/fts/_fig/_editor/__init__.py +14 -0
  292. scitex/fts/_fig/_editor/_cui/__init__.py +33 -0
  293. scitex/fts/_fig/_editor/_cui/_backend_detector.py +39 -0
  294. scitex/fts/_fig/_editor/_cui/_bundle_resolver.py +366 -0
  295. scitex/fts/_fig/_editor/_cui/_editor_launcher.py +175 -0
  296. scitex/fts/_fig/_editor/_cui/_manual_handler.py +52 -0
  297. scitex/fts/_fig/_editor/_cui/_panel_loader.py +246 -0
  298. scitex/fts/_fig/_editor/_cui/_path_resolver.py +66 -0
  299. scitex/fts/_fig/_editor/_defaults.py +300 -0
  300. scitex/fts/_fig/_editor/_gui/__init__.py +11 -0
  301. scitex/fts/_fig/_editor/_gui/_flask_editor/__init__.py +20 -0
  302. scitex/fts/_fig/_editor/_gui/_flask_editor/_bbox.py +1339 -0
  303. scitex/fts/_fig/_editor/_gui/_flask_editor/_core.py +1688 -0
  304. scitex/fts/_fig/_editor/_gui/_flask_editor/_plotter.py +664 -0
  305. scitex/fts/_fig/_editor/_gui/_flask_editor/_renderer.py +853 -0
  306. scitex/fts/_fig/_editor/_gui/_flask_editor/_utils.py +79 -0
  307. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/reset.css +41 -0
  308. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/typography.css +16 -0
  309. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/variables.css +85 -0
  310. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/buttons.css +217 -0
  311. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/context-menu.css +93 -0
  312. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/dropdown.css +57 -0
  313. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/forms.css +112 -0
  314. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/modal.css +59 -0
  315. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/sections.css +212 -0
  316. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/canvas.css +176 -0
  317. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/element-inspector.css +190 -0
  318. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/loading.css +59 -0
  319. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/overlay.css +45 -0
  320. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/panel-grid.css +95 -0
  321. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/selection.css +101 -0
  322. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/statistics.css +138 -0
  323. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/index.css +31 -0
  324. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/container.css +7 -0
  325. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/controls.css +56 -0
  326. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/preview.css +78 -0
  327. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/axis.js +314 -0
  328. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/basic.js +107 -0
  329. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/distribute.js +54 -0
  330. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/canvas.js +172 -0
  331. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/dragging.js +258 -0
  332. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/resize.js +48 -0
  333. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/selection.js +71 -0
  334. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/api.js +288 -0
  335. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/state.js +143 -0
  336. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/utils.js +245 -0
  337. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/dev/element-inspector.js +992 -0
  338. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/bbox.js +339 -0
  339. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/element-drag.js +286 -0
  340. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/overlay.js +371 -0
  341. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/preview.js +293 -0
  342. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/main.js +426 -0
  343. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/context-menu.js +152 -0
  344. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/keyboard.js +265 -0
  345. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/controls.js +184 -0
  346. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/download.js +57 -0
  347. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/help.js +100 -0
  348. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/theme.js +34 -0
  349. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/__init__.py +124 -0
  350. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_html.py +851 -0
  351. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_scripts.py +4932 -0
  352. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_styles.py +1657 -0
  353. scitex/fts/_fig/_editor/_gui/_flask_editor.py +36 -0
  354. scitex/fts/_fig/_models/_Annotations.py +115 -0
  355. scitex/fts/_fig/_models/_Axes.py +152 -0
  356. scitex/fts/_fig/_models/_Figure.py +138 -0
  357. scitex/fts/_fig/_models/_Guides.py +104 -0
  358. scitex/fts/_fig/_models/_Plot.py +123 -0
  359. scitex/fts/_fig/_models/_Styles.py +245 -0
  360. scitex/fts/_fig/_models/__init__.py +80 -0
  361. scitex/fts/_fig/_models/_plot_types/__init__.py +156 -0
  362. scitex/fts/_fig/_models/_plot_types/_bar.py +43 -0
  363. scitex/fts/_fig/_models/_plot_types/_box.py +38 -0
  364. scitex/fts/_fig/_models/_plot_types/_distribution.py +36 -0
  365. scitex/fts/_fig/_models/_plot_types/_errorbar.py +60 -0
  366. scitex/fts/_fig/_models/_plot_types/_histogram.py +30 -0
  367. scitex/fts/_fig/_models/_plot_types/_image.py +61 -0
  368. scitex/fts/_fig/_models/_plot_types/_line.py +57 -0
  369. scitex/fts/_fig/_models/_plot_types/_scatter.py +30 -0
  370. scitex/fts/_fig/_models/_plot_types/_seaborn.py +121 -0
  371. scitex/fts/_fig/_models/_plot_types/_violin.py +36 -0
  372. scitex/fts/_fig/_utils/__init__.py +129 -0
  373. scitex/fts/_fig/_utils/_auto_layout.py +127 -0
  374. scitex/fts/_fig/_utils/_calc_bounds.py +111 -0
  375. scitex/fts/_fig/_utils/_const_sizes.py +48 -0
  376. scitex/fts/_fig/_utils/_convert_coords.py +77 -0
  377. scitex/fts/_fig/_utils/_get_template.py +178 -0
  378. scitex/fts/_fig/_utils/_normalize.py +73 -0
  379. scitex/fts/_fig/_utils/_plot_layout.py +397 -0
  380. scitex/fts/_fig/_utils/_validate.py +197 -0
  381. scitex/fts/_kinds/__init__.py +45 -0
  382. scitex/fts/_kinds/_figure/__init__.py +19 -0
  383. scitex/fts/_kinds/_figure/_composite.py +345 -0
  384. scitex/fts/_kinds/_plot/__init__.py +25 -0
  385. scitex/fts/_kinds/_plot/_backend/__init__.py +53 -0
  386. scitex/fts/_kinds/_plot/_backend/_export.py +165 -0
  387. scitex/fts/_kinds/_plot/_backend/_parser.py +188 -0
  388. scitex/fts/_kinds/_plot/_backend/_render.py +538 -0
  389. scitex/fts/_kinds/_plot/_dataclasses/_ChannelEncoding.py +46 -0
  390. scitex/fts/_kinds/_plot/_dataclasses/_Encoding.py +82 -0
  391. scitex/fts/_kinds/_plot/_dataclasses/_Theme.py +441 -0
  392. scitex/fts/_kinds/_plot/_dataclasses/_TraceEncoding.py +52 -0
  393. scitex/fts/_kinds/_plot/_dataclasses/__init__.py +47 -0
  394. scitex/fts/_kinds/_plot/_models/_Annotations.py +115 -0
  395. scitex/fts/_kinds/_plot/_models/_Axes.py +152 -0
  396. scitex/fts/_kinds/_plot/_models/_Figure.py +138 -0
  397. scitex/fts/_kinds/_plot/_models/_Guides.py +104 -0
  398. scitex/fts/_kinds/_plot/_models/_Plot.py +123 -0
  399. scitex/fts/_kinds/_plot/_models/_Styles.py +245 -0
  400. scitex/fts/_kinds/_plot/_models/__init__.py +80 -0
  401. scitex/fts/_kinds/_plot/_models/_plot_types/__init__.py +156 -0
  402. scitex/fts/_kinds/_plot/_models/_plot_types/_bar.py +43 -0
  403. scitex/fts/_kinds/_plot/_models/_plot_types/_box.py +38 -0
  404. scitex/fts/_kinds/_plot/_models/_plot_types/_distribution.py +36 -0
  405. scitex/fts/_kinds/_plot/_models/_plot_types/_errorbar.py +60 -0
  406. scitex/fts/_kinds/_plot/_models/_plot_types/_histogram.py +30 -0
  407. scitex/fts/_kinds/_plot/_models/_plot_types/_image.py +61 -0
  408. scitex/fts/_kinds/_plot/_models/_plot_types/_line.py +57 -0
  409. scitex/fts/_kinds/_plot/_models/_plot_types/_scatter.py +30 -0
  410. scitex/fts/_kinds/_plot/_models/_plot_types/_seaborn.py +121 -0
  411. scitex/fts/_kinds/_plot/_models/_plot_types/_violin.py +36 -0
  412. scitex/fts/_kinds/_plot/_utils/__init__.py +129 -0
  413. scitex/fts/_kinds/_plot/_utils/_auto_layout.py +127 -0
  414. scitex/fts/_kinds/_plot/_utils/_calc_bounds.py +111 -0
  415. scitex/fts/_kinds/_plot/_utils/_const_sizes.py +48 -0
  416. scitex/fts/_kinds/_plot/_utils/_convert_coords.py +77 -0
  417. scitex/fts/_kinds/_plot/_utils/_get_template.py +178 -0
  418. scitex/fts/_kinds/_plot/_utils/_normalize.py +73 -0
  419. scitex/fts/_kinds/_plot/_utils/_plot_layout.py +397 -0
  420. scitex/fts/_kinds/_plot/_utils/_validate.py +197 -0
  421. scitex/fts/_kinds/_shape/__init__.py +141 -0
  422. scitex/fts/_kinds/_stats/__init__.py +56 -0
  423. scitex/fts/_kinds/_stats/_dataclasses/_Stats.py +423 -0
  424. scitex/fts/_kinds/_stats/_dataclasses/__init__.py +48 -0
  425. scitex/fts/_kinds/_table/__init__.py +72 -0
  426. scitex/fts/_kinds/_table/_latex/__init__.py +93 -0
  427. scitex/fts/_kinds/_table/_latex/_editor/__init__.py +11 -0
  428. scitex/fts/_kinds/_table/_latex/_editor/_app.py +725 -0
  429. scitex/fts/_kinds/_table/_latex/_export.py +279 -0
  430. scitex/fts/_kinds/_table/_latex/_figure_exporter.py +153 -0
  431. scitex/fts/_kinds/_table/_latex/_stats_formatter.py +274 -0
  432. scitex/fts/_kinds/_table/_latex/_table_exporter.py +362 -0
  433. scitex/fts/_kinds/_table/_latex/_utils.py +369 -0
  434. scitex/fts/_kinds/_table/_latex/_validator.py +445 -0
  435. scitex/fts/_kinds/_text/__init__.py +77 -0
  436. scitex/fts/_schemas/data_info.schema.json +75 -0
  437. scitex/fts/_schemas/encoding.schema.json +90 -0
  438. scitex/fts/_schemas/node.schema.json +145 -0
  439. scitex/fts/_schemas/render_manifest.schema.json +62 -0
  440. scitex/fts/_schemas/stats.schema.json +132 -0
  441. scitex/fts/_schemas/theme.schema.json +141 -0
  442. scitex/fts/_stats/__init__.py +48 -0
  443. scitex/fts/_stats/_dataclasses/_Stats.py +423 -0
  444. scitex/fts/_stats/_dataclasses/__init__.py +48 -0
  445. scitex/fts/_tables/__init__.py +65 -0
  446. scitex/fts/_tables/_latex/__init__.py +93 -0
  447. scitex/fts/_tables/_latex/_editor/__init__.py +11 -0
  448. scitex/fts/_tables/_latex/_editor/_app.py +725 -0
  449. scitex/fts/_tables/_latex/_export.py +279 -0
  450. scitex/fts/_tables/_latex/_figure_exporter.py +153 -0
  451. scitex/fts/_tables/_latex/_stats_formatter.py +274 -0
  452. scitex/fts/_tables/_latex/_table_exporter.py +362 -0
  453. scitex/fts/_tables/_latex/_utils.py +369 -0
  454. scitex/fts/_tables/_latex/_validator.py +445 -0
  455. scitex/gen/__init__.py +66 -25
  456. scitex/gen/misc.py +28 -0
  457. scitex/io/__init__.py +47 -20
  458. scitex/io/_load.py +87 -36
  459. scitex/io/_load_modules/__init__.py +10 -7
  460. scitex/io/_load_modules/_pandas.py +6 -1
  461. scitex/io/_save.py +299 -1556
  462. scitex/io/_save_modules/__init__.py +76 -19
  463. scitex/io/_save_modules/_figure_utils.py +90 -0
  464. scitex/io/_save_modules/_image_csv.py +497 -0
  465. scitex/io/_save_modules/_legends.py +91 -0
  466. scitex/io/_save_modules/_pltz_bundle.py +356 -0
  467. scitex/io/_save_modules/_pltz_stx.py +536 -0
  468. scitex/io/_save_modules/_stx_bundle.py +104 -0
  469. scitex/io/_save_modules/_symlink.py +96 -0
  470. scitex/io/_save_modules/_yaml.py +1 -1
  471. scitex/io/_save_modules/_zarr.py +64 -18
  472. scitex/io/bundle/README.md +212 -0
  473. scitex/io/bundle/__init__.py +110 -0
  474. scitex/io/{_bundle.py → bundle/_core.py} +219 -89
  475. scitex/io/bundle/_nested.py +713 -0
  476. scitex/io/bundle/_types.py +74 -0
  477. scitex/io/bundle/_zip.py +487 -0
  478. scitex/io/utils/h5_to_zarr.py +1 -1
  479. scitex/logging/__init__.py +108 -13
  480. scitex/logging/_errors.py +508 -0
  481. scitex/logging/_formatters.py +30 -6
  482. scitex/logging/_warnings.py +261 -0
  483. scitex/plt/__init__.py +4 -1
  484. scitex/plt/_figrecipe.py +236 -0
  485. scitex/plt/_subplots/_AxisWrapper.py +6 -0
  486. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/__init__.py +0 -0
  487. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_labels.py +0 -0
  488. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_metadata.py +0 -0
  489. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_visual.py +0 -0
  490. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/__init__.py +0 -0
  491. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_base.py +0 -0
  492. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_scientific.py +0 -0
  493. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_statistical.py +0 -0
  494. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_stx_aliases.py +0 -0
  495. scitex/plt/_subplots/_AxisWrapperMixins/_RawMatplotlibMixin.py +0 -0
  496. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/__init__.py +0 -0
  497. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_base.py +0 -0
  498. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +0 -0
  499. scitex/plt/_subplots/_AxisWrapperMixins/_UnitAwareMixin.py +112 -1
  500. scitex/plt/_subplots/_FigWrapper.py +15 -0
  501. scitex/plt/_subplots/_SubplotsWrapper.py +125 -489
  502. scitex/plt/_subplots/_export_as_csv.py +11 -0
  503. scitex/plt/_subplots/_export_as_csv_formatters/__init__.py +2 -0
  504. scitex/plt/_subplots/_export_as_csv_formatters/_format_pcolormesh.py +66 -0
  505. scitex/plt/_subplots/_export_as_csv_formatters/_format_stackplot.py +62 -0
  506. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_bar.py +0 -0
  507. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_barh.py +0 -0
  508. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_errorbar.py +0 -0
  509. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_scatter.py +0 -0
  510. scitex/plt/_subplots/_export_as_csv_formatters/test_formatters.py +208 -0
  511. scitex/plt/_subplots/_fonts.py +71 -0
  512. scitex/plt/_subplots/_mm_layout.py +282 -0
  513. scitex/plt/gallery/__init__.py +99 -2
  514. scitex/plt/io/_layered_bundle.py +0 -0
  515. scitex/plt/styles/_plot_postprocess.py +3 -1
  516. scitex/plt/utils/_configure_mpl.py +16 -19
  517. scitex/repro/_RandomStateManager.py +13 -8
  518. scitex/resource/__init__.py +19 -1
  519. scitex/resource/_utils/_get_env_info.py +13 -25
  520. scitex/schema/__init__.py +149 -160
  521. scitex/schema/_encoding.py +273 -0
  522. scitex/schema/_figure_elements.py +406 -0
  523. scitex/schema/_plot.py +0 -0
  524. scitex/schema/_theme.py +360 -0
  525. scitex/schema/_validation.py +0 -98
  526. scitex/scholar/__init__.py +56 -14
  527. scitex/scholar/auth/ScholarAuthManager.py +1 -1
  528. scitex/scholar/auth/__init__.py +11 -2
  529. scitex/scholar/auth/providers/BaseAuthenticator.py +1 -1
  530. scitex/scholar/auth/providers/EZProxyAuthenticator.py +1 -1
  531. scitex/scholar/auth/providers/OpenAthensAuthenticator.py +1 -1
  532. scitex/scholar/auth/providers/ShibbolethAuthenticator.py +1 -1
  533. scitex/scholar/config/ScholarConfig.py +1 -1
  534. scitex/scholar/core/Scholar.py +1 -1
  535. scitex/session/_decorator.py +18 -16
  536. scitex/session/_lifecycle.py +9 -11
  537. scitex/session/template.py +9 -8
  538. scitex/sh/test_sh.py +72 -0
  539. scitex/sh/test_sh_simple.py +61 -0
  540. scitex/stats/__init__.py +221 -97
  541. scitex/stats/_schema.py +21 -22
  542. scitex/stats/descriptive/_circular.py +212 -351
  543. scitex/stats/descriptive/_describe.py +81 -132
  544. scitex/stats/descriptive/_nan.py +205 -433
  545. scitex/stats/descriptive/_real.py +127 -141
  546. scitex/str/_format_plot_text.py +5 -5
  547. scitex/str/_latex.py +26 -84
  548. scitex/str/_latex_fallback.py +53 -47
  549. scitex/web/_search_pubmed.py +5 -4
  550. scitex/writer/tests/test_diff_between.py +451 -0
  551. scitex/writer/tests/test_document_section.py +311 -0
  552. scitex/writer/tests/test_document_workflow.py +393 -0
  553. scitex/writer/tests/test_writer.py +361 -0
  554. scitex/writer/tests/test_writer_integration.py +303 -0
  555. {scitex-2.7.3.dist-info → scitex-2.10.0.dist-info}/METADATA +364 -181
  556. {scitex-2.7.3.dist-info → scitex-2.10.0.dist-info}/RECORD +479 -108
  557. scitex/fig/editor/_edit.py +0 -751
  558. scitex/scholar/docs/to_claude/guidelines/examples/mgmt/ARCHITECTURE_EXAMPLE.md +0 -905
  559. scitex/scholar/docs/to_claude/guidelines/examples/mgmt/BULLETIN_BOARD_EXAMPLE.md +0 -99
  560. scitex/scholar/docs/to_claude/guidelines/examples/mgmt/PROJECT_DESCRIPTION_EXAMPLE.md +0 -96
  561. {scitex-2.7.3.dist-info → scitex-2.10.0.dist-info}/WHEEL +0 -0
  562. {scitex-2.7.3.dist-info → scitex-2.10.0.dist-info}/entry_points.txt +0 -0
  563. {scitex-2.7.3.dist-info → scitex-2.10.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,311 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Comprehensive tests for DocumentSection class.
5
+
6
+ Tests cover:
7
+ - File read/write operations
8
+ - Git operations (commit, history, diff, checkout)
9
+ - Error handling and edge cases
10
+ - Integration with git repository
11
+ """
12
+
13
+ import shutil
14
+ import subprocess
15
+ import tempfile
16
+ from pathlib import Path
17
+ from unittest.mock import MagicMock, patch
18
+
19
+ import pytest
20
+
21
+ from scitex.writer.dataclasses.core._DocumentSection import DocumentSection
22
+
23
+
24
+ class TestDocumentSectionReadWrite:
25
+ """Test file read/write operations."""
26
+
27
+ @pytest.fixture
28
+ def temp_file(self):
29
+ """Create temporary file."""
30
+ temp_dir = tempfile.mkdtemp(prefix="scitex_doc_")
31
+ temp_file = Path(temp_dir) / "test.tex"
32
+ temp_file.write_text("Initial content\n")
33
+
34
+ yield temp_file
35
+
36
+ # Cleanup
37
+ if Path(temp_dir).exists():
38
+ shutil.rmtree(temp_dir)
39
+
40
+ def test_read_existing_file(self, temp_file):
41
+ """Test reading existing file."""
42
+ section = DocumentSection(temp_file)
43
+ content = section.read()
44
+
45
+ assert content is not None
46
+ assert "Initial content" in str(content)
47
+
48
+ def test_read_nonexistent_file(self):
49
+ """Test reading non-existent file returns None."""
50
+ section = DocumentSection(Path("/nonexistent/file.tex"))
51
+ content = section.read()
52
+
53
+ assert content is None
54
+
55
+ def test_write_file(self, temp_file):
56
+ """Test writing to file."""
57
+ section = DocumentSection(temp_file)
58
+ result = section.write("New content\n")
59
+
60
+ assert result is True
61
+ assert temp_file.read_text() == "New content\n"
62
+
63
+ def test_write_with_list(self, temp_file):
64
+ """Test writing list of lines."""
65
+ section = DocumentSection(temp_file)
66
+ result = section.write(["Line 1", "Line 2", "Line 3"])
67
+
68
+ assert result is True
69
+ content = temp_file.read_text()
70
+ assert "Line 1\nLine 2\nLine 3" in content
71
+
72
+ def test_write_nonexistent_directory(self):
73
+ """Test writing to non-existent directory fails gracefully."""
74
+ section = DocumentSection(Path("/nonexistent/dir/file.tex"))
75
+ result = section.write("content")
76
+
77
+ assert result is False
78
+
79
+ def test_read_write_roundtrip(self, temp_file):
80
+ """Test reading and writing back same content."""
81
+ section = DocumentSection(temp_file)
82
+ original = section.read()
83
+
84
+ section.write(original)
85
+ readback = section.read()
86
+
87
+ assert original == readback
88
+
89
+
90
+ class TestDocumentSectionGitOperations:
91
+ """Test git-based operations."""
92
+
93
+ @pytest.fixture
94
+ def git_repo_with_file(self):
95
+ """Create git repository with test file."""
96
+ temp_dir = tempfile.mkdtemp(prefix="scitex_git_")
97
+ repo_path = Path(temp_dir)
98
+
99
+ # Initialize git repo
100
+ subprocess.run(
101
+ ["git", "init"],
102
+ cwd=repo_path,
103
+ capture_output=True,
104
+ check=True,
105
+ )
106
+
107
+ # Configure git
108
+ subprocess.run(
109
+ ["git", "config", "user.email", "test@example.com"],
110
+ cwd=repo_path,
111
+ capture_output=True,
112
+ check=True,
113
+ )
114
+ subprocess.run(
115
+ ["git", "config", "user.name", "Test User"],
116
+ cwd=repo_path,
117
+ capture_output=True,
118
+ check=True,
119
+ )
120
+
121
+ # Create test file
122
+ test_file = repo_path / "test.tex"
123
+ test_file.write_text("Initial content\n")
124
+
125
+ # Initial commit
126
+ subprocess.run(
127
+ ["git", "add", "test.tex"],
128
+ cwd=repo_path,
129
+ capture_output=True,
130
+ check=True,
131
+ )
132
+ subprocess.run(
133
+ ["git", "commit", "-m", "Initial commit"],
134
+ cwd=repo_path,
135
+ capture_output=True,
136
+ check=True,
137
+ )
138
+
139
+ yield test_file, repo_path
140
+
141
+ # Cleanup
142
+ if repo_path.exists():
143
+ shutil.rmtree(repo_path)
144
+
145
+ def test_commit_file(self, git_repo_with_file):
146
+ """Test committing file to git."""
147
+ test_file, repo_path = git_repo_with_file
148
+
149
+ section = DocumentSection(test_file, git_root=repo_path)
150
+ section.write("Updated content\n")
151
+ result = section.commit("Update test file")
152
+
153
+ assert result is True
154
+
155
+ # Verify commit was made
156
+ log_result = subprocess.run(
157
+ ["git", "log", "--oneline"],
158
+ cwd=repo_path,
159
+ capture_output=True,
160
+ text=True,
161
+ check=True,
162
+ )
163
+ assert "Update test file" in log_result.stdout
164
+
165
+ def test_history_shows_commits(self, git_repo_with_file):
166
+ """Test getting file history."""
167
+ test_file, repo_path = git_repo_with_file
168
+
169
+ section = DocumentSection(test_file, git_root=repo_path)
170
+ section.write("Update 1\n")
171
+ section.commit("Commit 1")
172
+ section.write("Update 2\n")
173
+ section.commit("Commit 2")
174
+
175
+ history = section.history()
176
+
177
+ assert len(history) >= 2
178
+ assert any("Commit 1" in h for h in history)
179
+ assert any("Commit 2" in h for h in history)
180
+
181
+ def test_diff_shows_changes(self, git_repo_with_file):
182
+ """Test getting diff of changes."""
183
+ test_file, repo_path = git_repo_with_file
184
+
185
+ section = DocumentSection(test_file, git_root=repo_path)
186
+ section.write("Changed content\n")
187
+
188
+ diff = section.diff()
189
+
190
+ assert len(diff) > 0
191
+ assert "Changed content" in diff or "-" in diff
192
+
193
+ def test_checkout_restores_file(self, git_repo_with_file):
194
+ """Test checking out file from git."""
195
+ test_file, repo_path = git_repo_with_file
196
+
197
+ section = DocumentSection(test_file, git_root=repo_path)
198
+
199
+ # Modify file
200
+ section.write("Modified content\n")
201
+ modified = section.read()
202
+
203
+ # Restore from HEAD
204
+ result = section.checkout("HEAD")
205
+
206
+ assert result is True
207
+ restored = section.read()
208
+ assert restored != modified
209
+ assert "Initial content" in str(restored)
210
+
211
+ def test_history_empty_without_git(self):
212
+ """Test history returns empty list without git repo."""
213
+ temp_file = Path(tempfile.mktemp(suffix=".tex"))
214
+ temp_file.write_text("content\n")
215
+
216
+ section = DocumentSection(temp_file)
217
+ history = section.history()
218
+
219
+ assert history == []
220
+
221
+ # Cleanup
222
+ temp_file.unlink()
223
+
224
+ def test_diff_empty_without_git(self):
225
+ """Test diff returns empty string without git repo."""
226
+ temp_file = Path(tempfile.mktemp(suffix=".tex"))
227
+ temp_file.write_text("content\n")
228
+
229
+ section = DocumentSection(temp_file)
230
+ diff = section.diff()
231
+
232
+ assert diff == ""
233
+
234
+ # Cleanup
235
+ temp_file.unlink()
236
+
237
+ def test_commit_fails_without_git(self):
238
+ """Test commit returns False without git repo."""
239
+ temp_file = Path(tempfile.mktemp(suffix=".tex"))
240
+ temp_file.write_text("content\n")
241
+
242
+ section = DocumentSection(temp_file)
243
+ result = section.commit("test message")
244
+
245
+ assert result is False
246
+
247
+ # Cleanup
248
+ temp_file.unlink()
249
+
250
+
251
+ class TestDocumentSectionRepresentation:
252
+ """Test string representation."""
253
+
254
+ def test_repr(self):
255
+ """Test __repr__ shows filename."""
256
+ section = DocumentSection(Path("/path/to/introduction.tex"))
257
+ repr_str = repr(section)
258
+
259
+ assert "DocumentSection" in repr_str
260
+ assert "introduction.tex" in repr_str
261
+
262
+
263
+ class TestDocumentSectionPathHandling:
264
+ """Test path handling."""
265
+
266
+ def test_path_attribute(self):
267
+ """Test path attribute is stored."""
268
+ path = Path("/path/to/file.tex")
269
+ section = DocumentSection(path)
270
+
271
+ assert section.path == path
272
+
273
+ def test_git_root_passed_explicitly(self):
274
+ """Test git_root is stored when passed."""
275
+ git_root = Path("/path/to/git")
276
+ section = DocumentSection(Path("/path/to/file.tex"), git_root=git_root)
277
+
278
+ assert section.git_root == git_root
279
+
280
+
281
+ class TestDocumentSectionErrorHandling:
282
+ """Test error handling."""
283
+
284
+ def test_read_unicode_fallback(self):
285
+ """Test read handles encoding errors gracefully."""
286
+ temp_dir = tempfile.mkdtemp(prefix="scitex_enc_")
287
+ temp_file = Path(temp_dir) / "test.tex"
288
+
289
+ # Write with UTF-8
290
+ temp_file.write_text("Valid UTF-8 content\n", encoding="utf-8")
291
+
292
+ section = DocumentSection(temp_file)
293
+ content = section.read()
294
+
295
+ assert content is not None
296
+ assert "Valid UTF-8 content" in str(content)
297
+
298
+ # Cleanup
299
+ shutil.rmtree(temp_dir)
300
+
301
+ def test_write_handles_exceptions(self):
302
+ """Test write handles exceptions gracefully."""
303
+ # Use a path that will fail to write
304
+ section = DocumentSection(Path("/root/impossible/path/file.tex"))
305
+ result = section.write("content")
306
+
307
+ assert result is False
308
+
309
+
310
+ if __name__ == "__main__":
311
+ pytest.main([__file__, "-v"])
@@ -0,0 +1,393 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ End-to-end workflow tests for Writer document operations.
5
+
6
+ Tests complete workflows:
7
+ - Create project → access section → read/write/commit
8
+ - Multi-section editing
9
+ - Document tree navigation
10
+ - Git history tracking
11
+ """
12
+
13
+ import shutil
14
+ import subprocess
15
+ import tempfile
16
+ from pathlib import Path
17
+ from unittest.mock import patch
18
+
19
+ import pytest
20
+
21
+ from scitex.writer.Writer import Writer
22
+
23
+
24
+ class TestWriterDocumentWorkflow:
25
+ """Test complete document editing workflows."""
26
+
27
+ @pytest.fixture
28
+ def valid_project_dir(self):
29
+ """Create a valid project structure."""
30
+ temp_dir = tempfile.mkdtemp(prefix="scitex_workflow_")
31
+ project_dir = Path(temp_dir)
32
+
33
+ # Create required structure with contents subdirectory
34
+ (project_dir / "01_manuscript" / "contents").mkdir(parents=True, exist_ok=True)
35
+ (project_dir / "02_supplementary").mkdir(parents=True, exist_ok=True)
36
+ (project_dir / "03_revision").mkdir(parents=True, exist_ok=True)
37
+
38
+ # Create manuscript files
39
+ (project_dir / "01_manuscript" / "base.tex").write_text("\\documentclass{article}\n")
40
+ (project_dir / "01_manuscript" / "README.md").write_text("# Manuscript\n")
41
+
42
+ # Create manuscript content files
43
+ (project_dir / "01_manuscript" / "contents" / "abstract.tex").write_text("Abstract content\n")
44
+ (project_dir / "01_manuscript" / "contents" / "introduction.tex").write_text("Introduction content\n")
45
+ (project_dir / "01_manuscript" / "contents" / "methods.tex").write_text("Methods content\n")
46
+ (project_dir / "01_manuscript" / "contents" / "results.tex").write_text("Results content\n")
47
+ (project_dir / "01_manuscript" / "contents" / "discussion.tex").write_text("Discussion content\n")
48
+
49
+ # Create directories for figures and tables
50
+ (project_dir / "01_manuscript" / "contents" / "figures").mkdir(exist_ok=True)
51
+ (project_dir / "01_manuscript" / "contents" / "tables").mkdir(exist_ok=True)
52
+
53
+ # Initialize git repo
54
+ subprocess.run(
55
+ ["git", "init"],
56
+ cwd=project_dir,
57
+ capture_output=True,
58
+ check=True,
59
+ )
60
+ subprocess.run(
61
+ ["git", "config", "user.email", "test@example.com"],
62
+ cwd=project_dir,
63
+ capture_output=True,
64
+ check=True,
65
+ )
66
+ subprocess.run(
67
+ ["git", "config", "user.name", "Test User"],
68
+ cwd=project_dir,
69
+ capture_output=True,
70
+ check=True,
71
+ )
72
+ subprocess.run(
73
+ ["git", "add", "."],
74
+ cwd=project_dir,
75
+ capture_output=True,
76
+ check=True,
77
+ )
78
+ subprocess.run(
79
+ ["git", "commit", "-m", "Initial commit"],
80
+ cwd=project_dir,
81
+ capture_output=True,
82
+ check=True,
83
+ )
84
+
85
+ yield project_dir
86
+
87
+ # Cleanup
88
+ if project_dir.exists():
89
+ shutil.rmtree(project_dir)
90
+
91
+ def test_access_manuscript_introduction(self, valid_project_dir):
92
+ """Test accessing manuscript introduction section."""
93
+ from scitex.writer.dataclasses.tree._ManuscriptTree import ManuscriptTree
94
+
95
+ # Create actual tree without mocking
96
+ tree = ManuscriptTree(
97
+ valid_project_dir / "01_manuscript",
98
+ git_root=valid_project_dir,
99
+ )
100
+
101
+ # Access introduction through contents
102
+ intro = tree.contents.introduction
103
+ assert intro is not None
104
+ assert intro.path.name == "introduction.tex"
105
+
106
+ def test_read_section_content(self, valid_project_dir):
107
+ """Test reading section content."""
108
+ from scitex.writer.dataclasses.core._DocumentSection import DocumentSection
109
+
110
+ intro_path = valid_project_dir / "01_manuscript" / "contents" / "introduction.tex"
111
+ section = DocumentSection(intro_path, git_root=valid_project_dir)
112
+
113
+ content = section.read()
114
+ assert content is not None
115
+ assert "Introduction content" in str(content)
116
+
117
+ def test_write_section_content(self, valid_project_dir):
118
+ """Test writing to section."""
119
+ from scitex.writer.dataclasses.core._DocumentSection import DocumentSection
120
+
121
+ intro_path = valid_project_dir / "01_manuscript" / "contents" / "introduction.tex"
122
+ section = DocumentSection(intro_path, git_root=valid_project_dir)
123
+
124
+ new_content = "Updated introduction with new content\n"
125
+ result = section.write(new_content)
126
+
127
+ assert result is True
128
+ assert intro_path.read_text() == new_content
129
+
130
+ def test_commit_section_changes(self, valid_project_dir):
131
+ """Test committing changes to section."""
132
+ from scitex.writer.dataclasses.core._DocumentSection import DocumentSection
133
+
134
+ intro_path = valid_project_dir / "01_manuscript" / "contents" / "introduction.tex"
135
+ section = DocumentSection(intro_path, git_root=valid_project_dir)
136
+
137
+ # Modify and commit
138
+ section.write("Updated introduction\n")
139
+ result = section.commit("Update introduction section")
140
+
141
+ assert result is True
142
+
143
+ # Verify commit exists
144
+ log = subprocess.run(
145
+ ["git", "log", "--oneline"],
146
+ cwd=valid_project_dir,
147
+ capture_output=True,
148
+ text=True,
149
+ check=True,
150
+ )
151
+ assert "Update introduction section" in log.stdout
152
+
153
+ def test_access_multiple_sections(self, valid_project_dir):
154
+ """Test accessing multiple sections."""
155
+ from scitex.writer.dataclasses.contents._ManuscriptContents import (
156
+ ManuscriptContents,
157
+ )
158
+
159
+ contents = ManuscriptContents(
160
+ valid_project_dir / "01_manuscript" / "contents",
161
+ git_root=valid_project_dir,
162
+ )
163
+
164
+ # Access different sections
165
+ assert contents.abstract is not None
166
+ assert contents.introduction is not None
167
+ assert contents.methods is not None
168
+ assert contents.results is not None
169
+ assert contents.discussion is not None
170
+
171
+ # All should be DocumentSection instances
172
+ assert hasattr(contents.abstract, "read")
173
+ assert hasattr(contents.introduction, "commit")
174
+
175
+ def test_read_write_commit_workflow(self, valid_project_dir):
176
+ """Test complete read-write-commit workflow."""
177
+ from scitex.writer.dataclasses.core._DocumentSection import DocumentSection
178
+
179
+ intro_path = valid_project_dir / "01_manuscript" / "contents" / "introduction.tex"
180
+ section = DocumentSection(intro_path, git_root=valid_project_dir)
181
+
182
+ # Read initial
183
+ original = section.read()
184
+ assert original is not None
185
+
186
+ # Modify
187
+ modified = "Completely new introduction\n"
188
+ section.write(modified)
189
+
190
+ # Verify modification (read returns what scitex.io gives us - could be list or string)
191
+ readback = section.read()
192
+ if isinstance(readback, list):
193
+ assert any("Completely new introduction" in line for line in readback)
194
+ else:
195
+ assert "Completely new introduction" in str(readback)
196
+
197
+ # Commit
198
+ commit_result = section.commit("Rewrite introduction")
199
+ assert commit_result is True
200
+
201
+ # Check history shows both commits
202
+ history = section.history()
203
+ assert len(history) >= 1
204
+ assert any("Rewrite introduction" in h for h in history)
205
+
206
+ def test_diff_shows_changes(self, valid_project_dir):
207
+ """Test diff operation shows changes."""
208
+ from scitex.writer.dataclasses.core._DocumentSection import DocumentSection
209
+
210
+ intro_path = valid_project_dir / "01_manuscript" / "contents" / "introduction.tex"
211
+ section = DocumentSection(intro_path, git_root=valid_project_dir)
212
+
213
+ # Modify file
214
+ section.write("Modified introduction\n")
215
+
216
+ # Get diff
217
+ diff = section.diff()
218
+
219
+ assert len(diff) > 0
220
+
221
+ def test_checkout_reverts_changes(self, valid_project_dir):
222
+ """Test checking out file reverts to previous version."""
223
+ from scitex.writer.dataclasses.core._DocumentSection import DocumentSection
224
+
225
+ intro_path = valid_project_dir / "01_manuscript" / "contents" / "introduction.tex"
226
+ section = DocumentSection(intro_path, git_root=valid_project_dir)
227
+
228
+ original = section.read()
229
+
230
+ # Modify
231
+ section.write("New content\n")
232
+ modified = section.read()
233
+ assert modified != original
234
+
235
+ # Checkout HEAD (revert)
236
+ result = section.checkout("HEAD")
237
+ assert result is True
238
+
239
+ # Verify reverted
240
+ reverted = section.read()
241
+ assert reverted == original
242
+
243
+ def test_history_tracks_commits(self, valid_project_dir):
244
+ """Test history tracks multiple commits."""
245
+ from scitex.writer.dataclasses.core._DocumentSection import DocumentSection
246
+
247
+ intro_path = valid_project_dir / "01_manuscript" / "contents" / "introduction.tex"
248
+ section = DocumentSection(intro_path, git_root=valid_project_dir)
249
+
250
+ # Make multiple commits
251
+ section.write("Version 1\n")
252
+ section.commit("Version 1")
253
+
254
+ section.write("Version 2\n")
255
+ section.commit("Version 2")
256
+
257
+ section.write("Version 3\n")
258
+ section.commit("Version 3")
259
+
260
+ # Check history
261
+ history = section.history()
262
+ assert len(history) >= 3
263
+ assert any("Version 1" in h for h in history)
264
+ assert any("Version 2" in h for h in history)
265
+ assert any("Version 3" in h for h in history)
266
+
267
+ def test_tree_structure_verification(self, valid_project_dir):
268
+ """Test verifying manuscript tree structure."""
269
+ from scitex.writer.dataclasses.tree._ManuscriptTree import ManuscriptTree
270
+
271
+ tree = ManuscriptTree(
272
+ valid_project_dir / "01_manuscript",
273
+ git_root=valid_project_dir,
274
+ )
275
+
276
+ is_valid, missing = tree.verify_structure()
277
+ assert is_valid is True
278
+ assert len(missing) == 0
279
+
280
+
281
+ class TestManuscriptContentsAccess:
282
+ """Test accessing all manuscript content sections."""
283
+
284
+ @pytest.fixture
285
+ def manuscript_contents_dir(self):
286
+ """Create manuscript contents directory with all files."""
287
+ temp_dir = tempfile.mkdtemp(prefix="scitex_contents_")
288
+ contents_dir = Path(temp_dir)
289
+
290
+ # Create all section files
291
+ sections = [
292
+ "abstract.tex",
293
+ "introduction.tex",
294
+ "methods.tex",
295
+ "results.tex",
296
+ "discussion.tex",
297
+ "title.tex",
298
+ "authors.tex",
299
+ "keywords.tex",
300
+ "journal_name.tex",
301
+ "graphical_abstract.tex",
302
+ "highlights.tex",
303
+ "data_availability.tex",
304
+ "additional_info.tex",
305
+ "wordcount.tex",
306
+ "bibliography.bib",
307
+ ]
308
+
309
+ for section in sections:
310
+ (contents_dir / section).write_text(f"{section} content\n")
311
+
312
+ # Create directories
313
+ (contents_dir / "figures").mkdir(exist_ok=True)
314
+ (contents_dir / "tables").mkdir(exist_ok=True)
315
+ (contents_dir / "latex_styles").mkdir(exist_ok=True)
316
+
317
+ yield contents_dir
318
+
319
+ # Cleanup
320
+ if contents_dir.exists():
321
+ shutil.rmtree(contents_dir)
322
+
323
+ def test_access_all_core_sections(self, manuscript_contents_dir):
324
+ """Test accessing all core manuscript sections."""
325
+ from scitex.writer.dataclasses.contents._ManuscriptContents import (
326
+ ManuscriptContents,
327
+ )
328
+
329
+ contents = ManuscriptContents(manuscript_contents_dir)
330
+
331
+ # Core sections
332
+ assert contents.abstract is not None
333
+ assert contents.introduction is not None
334
+ assert contents.methods is not None
335
+ assert contents.results is not None
336
+ assert contents.discussion is not None
337
+
338
+ # Read from each
339
+ assert "abstract.tex content" in str(contents.abstract.read())
340
+ assert "introduction.tex content" in str(contents.introduction.read())
341
+
342
+ def test_access_all_metadata_sections(self, manuscript_contents_dir):
343
+ """Test accessing metadata sections."""
344
+ from scitex.writer.dataclasses.contents._ManuscriptContents import (
345
+ ManuscriptContents,
346
+ )
347
+
348
+ contents = ManuscriptContents(manuscript_contents_dir)
349
+
350
+ assert contents.title is not None
351
+ assert contents.authors is not None
352
+ assert contents.keywords is not None
353
+ assert contents.journal_name is not None
354
+
355
+ def test_access_optional_sections(self, manuscript_contents_dir):
356
+ """Test accessing optional sections."""
357
+ from scitex.writer.dataclasses.contents._ManuscriptContents import (
358
+ ManuscriptContents,
359
+ )
360
+
361
+ contents = ManuscriptContents(manuscript_contents_dir)
362
+
363
+ assert contents.graphical_abstract is not None
364
+ assert contents.highlights is not None
365
+ assert contents.data_availability is not None
366
+
367
+ def test_access_bibliography(self, manuscript_contents_dir):
368
+ """Test accessing bibliography file."""
369
+ from scitex.writer.dataclasses.contents._ManuscriptContents import (
370
+ ManuscriptContents,
371
+ )
372
+
373
+ contents = ManuscriptContents(manuscript_contents_dir)
374
+
375
+ assert contents.bibliography is not None
376
+ assert contents.bibliography.path.name == "bibliography.bib"
377
+
378
+ def test_access_directories(self, manuscript_contents_dir):
379
+ """Test accessing figure and table directories."""
380
+ from scitex.writer.dataclasses.contents._ManuscriptContents import (
381
+ ManuscriptContents,
382
+ )
383
+
384
+ contents = ManuscriptContents(manuscript_contents_dir)
385
+
386
+ assert contents.figures is not None
387
+ assert contents.figures.exists()
388
+ assert contents.tables is not None
389
+ assert contents.tables.exists()
390
+
391
+
392
+ if __name__ == "__main__":
393
+ pytest.main([__file__, "-v"])