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
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
2
  # Time-stamp: "2024-11-13 14:30:43 (ywatanabe)"
4
3
  # File: ./scitex_repo/src/scitex/web/_search_pubmed.py
5
4
 
@@ -27,10 +26,10 @@ import xml.etree.ElementTree as ET
27
26
  from typing import Any, Dict, List, Optional, Union
28
27
 
29
28
  import aiohttp
30
- import scitex
31
- import pandas as pd
32
29
  import requests
33
30
 
31
+ import scitex
32
+
34
33
  """Functions & Classes"""
35
34
 
36
35
 
@@ -292,7 +291,8 @@ def format_bibtex(paper: Dict[str, Any], pmid: str, abstract_data: tuple) -> str
292
291
 
293
292
  authors = paper.get("authors", [{"name": "Unknown"}])
294
293
  author_names = " and ".join(author["name"] for author in authors)
295
- year = paper.get("pubdate", "").split()[0]
294
+ pubdate = paper.get("pubdate", "")
295
+ year = pubdate.split()[0] if pubdate.strip() else ""
296
296
  title = paper.get("title", "No Title")
297
297
 
298
298
  # Name formatting
@@ -475,6 +475,7 @@ def run_main() -> None:
475
475
  import sys
476
476
 
477
477
  import matplotlib.pyplot as plt
478
+
478
479
  import scitex
479
480
 
480
481
  CONFIG, sys.stdout, sys.stderr, plt, CC = scitex.session.start(
@@ -0,0 +1,451 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Tests for DocumentSection.diff_between() and ref resolution.
5
+
6
+ Tests cover:
7
+ - Comparing two arbitrary git references
8
+ - Human-readable reference resolution
9
+ - Relative time specifications (N days ago, etc.)
10
+ - Absolute date specifications
11
+ - Timestamp-based commit finding
12
+ """
13
+
14
+ import shutil
15
+ import subprocess
16
+ import tempfile
17
+ import time
18
+ from datetime import datetime, timedelta
19
+ from pathlib import Path
20
+
21
+ import pytest
22
+
23
+ from scitex.writer.dataclasses.core._DocumentSection import DocumentSection
24
+
25
+
26
+ class TestDiffBetween:
27
+ """Test diff_between() for comparing two references."""
28
+
29
+ @pytest.fixture
30
+ def git_repo_with_history(self):
31
+ """Create git repository with multiple commits."""
32
+ temp_dir = tempfile.mkdtemp(prefix="scitex_diff_between_")
33
+ repo_path = Path(temp_dir)
34
+
35
+ # Initialize git repo
36
+ subprocess.run(
37
+ ["git", "init"],
38
+ cwd=repo_path,
39
+ capture_output=True,
40
+ check=True,
41
+ )
42
+
43
+ # Configure git
44
+ subprocess.run(
45
+ ["git", "config", "user.email", "test@example.com"],
46
+ cwd=repo_path,
47
+ capture_output=True,
48
+ check=True,
49
+ )
50
+ subprocess.run(
51
+ ["git", "config", "user.name", "Test User"],
52
+ cwd=repo_path,
53
+ capture_output=True,
54
+ check=True,
55
+ )
56
+
57
+ # Create test file with initial content
58
+ test_file = repo_path / "test.tex"
59
+ test_file.write_text("Initial content\n")
60
+
61
+ # Initial commit
62
+ subprocess.run(
63
+ ["git", "add", "test.tex"],
64
+ cwd=repo_path,
65
+ capture_output=True,
66
+ check=True,
67
+ )
68
+ subprocess.run(
69
+ ["git", "commit", "-m", "Initial commit"],
70
+ cwd=repo_path,
71
+ capture_output=True,
72
+ check=True,
73
+ )
74
+
75
+ # Commit 2: Add first section
76
+ test_file.write_text("Initial content\n\nSection 1\n")
77
+ subprocess.run(
78
+ ["git", "add", "test.tex"],
79
+ cwd=repo_path,
80
+ capture_output=True,
81
+ check=True,
82
+ )
83
+ subprocess.run(
84
+ ["git", "commit", "-m", "Add section 1"],
85
+ cwd=repo_path,
86
+ capture_output=True,
87
+ check=True,
88
+ )
89
+
90
+ # Commit 3: Add second section
91
+ test_file.write_text("Initial content\n\nSection 1\n\nSection 2\n")
92
+ subprocess.run(
93
+ ["git", "add", "test.tex"],
94
+ cwd=repo_path,
95
+ capture_output=True,
96
+ check=True,
97
+ )
98
+ subprocess.run(
99
+ ["git", "commit", "-m", "Add section 2"],
100
+ cwd=repo_path,
101
+ capture_output=True,
102
+ check=True,
103
+ )
104
+
105
+ # Create a tag
106
+ subprocess.run(
107
+ ["git", "tag", "v1.0"],
108
+ cwd=repo_path,
109
+ capture_output=True,
110
+ check=True,
111
+ )
112
+
113
+ yield test_file, repo_path
114
+
115
+ # Cleanup
116
+ if repo_path.exists():
117
+ shutil.rmtree(repo_path)
118
+
119
+ def test_diff_between_commits(self, git_repo_with_history):
120
+ """Test comparing two commits."""
121
+ test_file, repo_path = git_repo_with_history
122
+
123
+ section = DocumentSection(test_file, git_root=repo_path)
124
+
125
+ # Compare first commit to last
126
+ diff = section.diff_between("HEAD~2", "HEAD")
127
+
128
+ assert len(diff) > 0
129
+ assert "Section 1" in diff or "+" in diff
130
+
131
+ def test_diff_between_tag_and_head(self, git_repo_with_history):
132
+ """Test comparing tag to HEAD."""
133
+ test_file, repo_path = git_repo_with_history
134
+
135
+ section = DocumentSection(test_file, git_root=repo_path)
136
+
137
+ # Compare v1.0 tag to HEAD
138
+ diff = section.diff_between("v1.0", "HEAD")
139
+
140
+ # Should show nothing changed after tag
141
+ # (because we didn't add commits after creating tag in this fixture)
142
+ assert isinstance(diff, str)
143
+
144
+ def test_diff_between_empty_when_no_changes(self, git_repo_with_history):
145
+ """Test diff_between returns empty when commits are identical."""
146
+ test_file, repo_path = git_repo_with_history
147
+
148
+ section = DocumentSection(test_file, git_root=repo_path)
149
+
150
+ # Compare HEAD to itself
151
+ diff = section.diff_between("HEAD", "HEAD")
152
+
153
+ assert diff == ""
154
+
155
+ def test_diff_between_invalid_ref1(self, git_repo_with_history):
156
+ """Test diff_between with invalid first reference."""
157
+ test_file, repo_path = git_repo_with_history
158
+
159
+ section = DocumentSection(test_file, git_root=repo_path)
160
+
161
+ # Try with non-existent ref
162
+ diff = section.diff_between("nonexistent123", "HEAD")
163
+
164
+ assert diff == ""
165
+
166
+
167
+ class TestRefResolution:
168
+ """Test _resolve_ref() for human-readable references."""
169
+
170
+ @pytest.fixture
171
+ def simple_git_repo(self):
172
+ """Create simple git repository with one file."""
173
+ temp_dir = tempfile.mkdtemp(prefix="scitex_ref_resolve_")
174
+ repo_path = Path(temp_dir)
175
+
176
+ # Initialize git repo
177
+ subprocess.run(
178
+ ["git", "init"],
179
+ cwd=repo_path,
180
+ capture_output=True,
181
+ check=True,
182
+ )
183
+
184
+ # Configure git
185
+ subprocess.run(
186
+ ["git", "config", "user.email", "test@example.com"],
187
+ cwd=repo_path,
188
+ capture_output=True,
189
+ check=True,
190
+ )
191
+ subprocess.run(
192
+ ["git", "config", "user.name", "Test User"],
193
+ cwd=repo_path,
194
+ capture_output=True,
195
+ check=True,
196
+ )
197
+
198
+ # Create and commit file
199
+ test_file = repo_path / "test.tex"
200
+ test_file.write_text("Test\n")
201
+
202
+ subprocess.run(
203
+ ["git", "add", "test.tex"],
204
+ cwd=repo_path,
205
+ capture_output=True,
206
+ check=True,
207
+ )
208
+ subprocess.run(
209
+ ["git", "commit", "-m", "Initial"],
210
+ cwd=repo_path,
211
+ capture_output=True,
212
+ check=True,
213
+ )
214
+
215
+ yield test_file, repo_path
216
+
217
+ # Cleanup
218
+ if repo_path.exists():
219
+ shutil.rmtree(repo_path)
220
+
221
+ def test_resolve_head(self, simple_git_repo):
222
+ """Test resolving HEAD reference."""
223
+ test_file, repo_path = simple_git_repo
224
+
225
+ section = DocumentSection(test_file, git_root=repo_path)
226
+ resolved = section._resolve_ref("HEAD")
227
+
228
+ assert resolved == "HEAD"
229
+
230
+ def test_resolve_now_to_head(self, simple_git_repo):
231
+ """Test resolving 'now' to HEAD."""
232
+ test_file, repo_path = simple_git_repo
233
+
234
+ section = DocumentSection(test_file, git_root=repo_path)
235
+ resolved = section._resolve_ref("now")
236
+
237
+ assert resolved == "HEAD"
238
+
239
+ def test_resolve_invalid_ref(self, simple_git_repo):
240
+ """Test resolving invalid reference returns None."""
241
+ test_file, repo_path = simple_git_repo
242
+
243
+ section = DocumentSection(test_file, git_root=repo_path)
244
+ resolved = section._resolve_ref("nonexistent_ref_xyz")
245
+
246
+ assert resolved is None
247
+
248
+
249
+ class TestTimeResolution:
250
+ """Test time-based reference resolution."""
251
+
252
+ @pytest.fixture
253
+ def timed_git_repo(self):
254
+ """Create git repository with time-spaced commits."""
255
+ temp_dir = tempfile.mkdtemp(prefix="scitex_time_resolve_")
256
+ repo_path = Path(temp_dir)
257
+
258
+ # Initialize git repo
259
+ subprocess.run(
260
+ ["git", "init"],
261
+ cwd=repo_path,
262
+ capture_output=True,
263
+ check=True,
264
+ )
265
+
266
+ # Configure git
267
+ subprocess.run(
268
+ ["git", "config", "user.email", "test@example.com"],
269
+ cwd=repo_path,
270
+ capture_output=True,
271
+ check=True,
272
+ )
273
+ subprocess.run(
274
+ ["git", "config", "user.name", "Test User"],
275
+ cwd=repo_path,
276
+ capture_output=True,
277
+ check=True,
278
+ )
279
+
280
+ # Create initial commit
281
+ test_file = repo_path / "test.tex"
282
+ test_file.write_text("v1\n")
283
+
284
+ subprocess.run(
285
+ ["git", "add", "test.tex"],
286
+ cwd=repo_path,
287
+ capture_output=True,
288
+ check=True,
289
+ )
290
+ subprocess.run(
291
+ ["git", "commit", "-m", "v1"],
292
+ cwd=repo_path,
293
+ capture_output=True,
294
+ check=True,
295
+ )
296
+
297
+ # Wait a bit, then create second commit
298
+ time.sleep(1)
299
+ test_file.write_text("v2\n")
300
+ subprocess.run(
301
+ ["git", "add", "test.tex"],
302
+ cwd=repo_path,
303
+ capture_output=True,
304
+ check=True,
305
+ )
306
+ subprocess.run(
307
+ ["git", "commit", "-m", "v2"],
308
+ cwd=repo_path,
309
+ capture_output=True,
310
+ check=True,
311
+ )
312
+
313
+ yield test_file, repo_path
314
+
315
+ # Cleanup
316
+ if repo_path.exists():
317
+ shutil.rmtree(repo_path)
318
+
319
+ def test_parse_relative_time_days(self, timed_git_repo):
320
+ """Test parsing '2 days ago' specification."""
321
+ test_file, repo_path = timed_git_repo
322
+
323
+ section = DocumentSection(test_file, git_root=repo_path)
324
+ dt = section._parse_relative_time("2 days ago")
325
+
326
+ assert dt is not None
327
+ assert isinstance(dt, datetime)
328
+ # Should be about 2 days in the past
329
+ now = datetime.now()
330
+ delta = (now - dt).total_seconds()
331
+ assert 48 * 3600 - 60 < delta < 48 * 3600 + 60 # Within 1 minute of 2 days
332
+
333
+ def test_parse_relative_time_hours(self, timed_git_repo):
334
+ """Test parsing '24 hours ago' specification."""
335
+ test_file, repo_path = timed_git_repo
336
+
337
+ section = DocumentSection(test_file, git_root=repo_path)
338
+ dt = section._parse_relative_time("24 hours ago")
339
+
340
+ assert dt is not None
341
+ assert isinstance(dt, datetime)
342
+
343
+ def test_parse_relative_time_invalid(self, timed_git_repo):
344
+ """Test parsing invalid time specification."""
345
+ test_file, repo_path = timed_git_repo
346
+
347
+ section = DocumentSection(test_file, git_root=repo_path)
348
+ dt = section._parse_relative_time("invalid time spec")
349
+
350
+ assert dt is None
351
+
352
+ def test_parse_absolute_date_ymd(self):
353
+ """Test parsing absolute date in YYYY-MM-DD format."""
354
+ section = DocumentSection(Path("/tmp/test.tex"))
355
+ dt = section._parse_absolute_date("2025-10-28")
356
+
357
+ assert dt is not None
358
+ assert dt.year == 2025
359
+ assert dt.month == 10
360
+ assert dt.day == 28
361
+
362
+ def test_parse_absolute_date_ymd_hm(self):
363
+ """Test parsing absolute date with time in YYYY-MM-DD HH:MM format."""
364
+ section = DocumentSection(Path("/tmp/test.tex"))
365
+ dt = section._parse_absolute_date("2025-10-28 14:30")
366
+
367
+ assert dt is not None
368
+ assert dt.year == 2025
369
+ assert dt.month == 10
370
+ assert dt.day == 28
371
+ assert dt.hour == 14
372
+ assert dt.minute == 30
373
+
374
+ def test_parse_absolute_date_invalid(self):
375
+ """Test parsing invalid date specification."""
376
+ section = DocumentSection(Path("/tmp/test.tex"))
377
+ dt = section._parse_absolute_date("not a date")
378
+
379
+ assert dt is None
380
+
381
+
382
+ class TestIsValidGitRef:
383
+ """Test _is_valid_git_ref() validation."""
384
+
385
+ @pytest.fixture
386
+ def simple_git_repo(self):
387
+ """Create simple git repository."""
388
+ temp_dir = tempfile.mkdtemp(prefix="scitex_valid_ref_")
389
+ repo_path = Path(temp_dir)
390
+
391
+ subprocess.run(
392
+ ["git", "init"],
393
+ cwd=repo_path,
394
+ capture_output=True,
395
+ check=True,
396
+ )
397
+ subprocess.run(
398
+ ["git", "config", "user.email", "test@example.com"],
399
+ cwd=repo_path,
400
+ capture_output=True,
401
+ check=True,
402
+ )
403
+ subprocess.run(
404
+ ["git", "config", "user.name", "Test User"],
405
+ cwd=repo_path,
406
+ capture_output=True,
407
+ check=True,
408
+ )
409
+
410
+ test_file = repo_path / "test.tex"
411
+ test_file.write_text("Test\n")
412
+
413
+ subprocess.run(
414
+ ["git", "add", "test.tex"],
415
+ cwd=repo_path,
416
+ capture_output=True,
417
+ check=True,
418
+ )
419
+ subprocess.run(
420
+ ["git", "commit", "-m", "Initial"],
421
+ cwd=repo_path,
422
+ capture_output=True,
423
+ check=True,
424
+ )
425
+
426
+ yield test_file, repo_path
427
+
428
+ if repo_path.exists():
429
+ shutil.rmtree(repo_path)
430
+
431
+ def test_valid_ref_head(self, simple_git_repo):
432
+ """Test that HEAD is a valid reference."""
433
+ test_file, repo_path = simple_git_repo
434
+
435
+ section = DocumentSection(test_file, git_root=repo_path)
436
+ is_valid = section._is_valid_git_ref("HEAD")
437
+
438
+ assert is_valid is True
439
+
440
+ def test_invalid_ref_nonexistent(self, simple_git_repo):
441
+ """Test that nonexistent reference is invalid."""
442
+ test_file, repo_path = simple_git_repo
443
+
444
+ section = DocumentSection(test_file, git_root=repo_path)
445
+ is_valid = section._is_valid_git_ref("nonexistent_xyz")
446
+
447
+ assert is_valid is False
448
+
449
+
450
+ if __name__ == "__main__":
451
+ pytest.main([__file__, "-v"])