ms-qg 0.12.1__tar.gz

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 (295) hide show
  1. ms_qg-0.12.1/.claude/skills/qg-config-authoring/SKILL.md +250 -0
  2. ms_qg-0.12.1/.dockerignore +26 -0
  3. ms_qg-0.12.1/.env.example +6 -0
  4. ms_qg-0.12.1/.github/workflows/ci.yml +237 -0
  5. ms_qg-0.12.1/.github/workflows/pages.yml +57 -0
  6. ms_qg-0.12.1/.github/workflows/publish.yml +101 -0
  7. ms_qg-0.12.1/.github/workflows/security.yml +26 -0
  8. ms_qg-0.12.1/.gitignore +45 -0
  9. ms_qg-0.12.1/.gitlab-ci.yml +95 -0
  10. ms_qg-0.12.1/.pre-commit-config.yaml +119 -0
  11. ms_qg-0.12.1/.python-version +1 -0
  12. ms_qg-0.12.1/.qg_settings.toml.example +9 -0
  13. ms_qg-0.12.1/.zenodo.json +60 -0
  14. ms_qg-0.12.1/AGENTS.md +620 -0
  15. ms_qg-0.12.1/CHANGELOG.md +365 -0
  16. ms_qg-0.12.1/CITATION.cff +53 -0
  17. ms_qg-0.12.1/CLAUDE.md +7 -0
  18. ms_qg-0.12.1/CONTRIBUTING.md +52 -0
  19. ms_qg-0.12.1/Dockerfile +7 -0
  20. ms_qg-0.12.1/LICENSE +161 -0
  21. ms_qg-0.12.1/Makefile +121 -0
  22. ms_qg-0.12.1/PKG-INFO +154 -0
  23. ms_qg-0.12.1/README.md +111 -0
  24. ms_qg-0.12.1/Snakefile +16 -0
  25. ms_qg-0.12.1/docs/Makefile +21 -0
  26. ms_qg-0.12.1/docs/bfabric.md +85 -0
  27. ms_qg-0.12.1/docs/developers/deployment.md +72 -0
  28. ms_qg-0.12.1/docs/developers/forge_sync.md +109 -0
  29. ms_qg-0.12.1/docs/developers/history.md +132 -0
  30. ms_qg-0.12.1/docs/developers/marimo_shared_app_architecture.md +247 -0
  31. ms_qg-0.12.1/docs/developers/user_modes.md +50 -0
  32. ms_qg-0.12.1/docs/examples/README.md +61 -0
  33. ms_qg-0.12.1/docs/examples/figures/README.md +45 -0
  34. ms_qg-0.12.1/docs/examples/figures/editor_overview.png +0 -0
  35. ms_qg-0.12.1/docs/examples/figures/editor_qc_layouts_well.png +0 -0
  36. ms_qg-0.12.1/docs/examples/figures/editor_queue_patterns.png +0 -0
  37. ms_qg-0.12.1/docs/examples/figures/editor_samples.png +0 -0
  38. ms_qg-0.12.1/docs/examples/figures/imbalance_sweep_stats.csv +28 -0
  39. ms_qg-0.12.1/docs/examples/figures/make_imbalance_sweep_figure.py +203 -0
  40. ms_qg-0.12.1/docs/examples/figures/make_plate_figure.py +142 -0
  41. ms_qg-0.12.1/docs/examples/figures/make_randomization_figure.py +204 -0
  42. ms_qg-0.12.1/docs/examples/figures/make_seed_sweep_figure.py +293 -0
  43. ms_qg-0.12.1/docs/examples/figures/seed_sweep_stats.csv +9 -0
  44. ms_qg-0.12.1/docs/examples/figures/shot_editor.py +63 -0
  45. ms_qg-0.12.1/docs/examples/figures/shot_local_tabs.py +84 -0
  46. ms_qg-0.12.1/docs/examples/figures/shot_viz.py +185 -0
  47. ms_qg-0.12.1/docs/examples/multi_container_samples.csv +31 -0
  48. ms_qg-0.12.1/docs/examples/plate_different_groups.csv +43 -0
  49. ms_qg-0.12.1/docs/examples/plate_samples_5x5.csv +26 -0
  50. ms_qg-0.12.1/docs/examples/plate_samples_80.csv +81 -0
  51. ms_qg-0.12.1/docs/examples/vial_different_groups.csv +43 -0
  52. ms_qg-0.12.1/docs/examples/vial_samples_5x5.csv +26 -0
  53. ms_qg-0.12.1/docs/examples/vial_samples_5x5.xlsx +0 -0
  54. ms_qg-0.12.1/docs/examples/vial_samples_80.csv +81 -0
  55. ms_qg-0.12.1/docs/index.md +46 -0
  56. ms_qg-0.12.1/docs/reference/algorithm.md +241 -0
  57. ms_qg-0.12.1/docs/reference/config.md +403 -0
  58. ms_qg-0.12.1/docs/users/editor_guide.md +110 -0
  59. ms_qg-0.12.1/docs/users/local_app.md +81 -0
  60. ms_qg-0.12.1/mkdocs.yml +69 -0
  61. ms_qg-0.12.1/pyproject.toml +209 -0
  62. ms_qg-0.12.1/qg_configs/README.md +21 -0
  63. ms_qg-0.12.1/qg_configs/core/formatting/instruments.csv +17 -0
  64. ms_qg-0.12.1/qg_configs/core/formatting/output_formats.toml +92 -0
  65. ms_qg-0.12.1/qg_configs/core/methods/Lipidomics/EXPLORIS_3_methods.csv +3 -0
  66. ms_qg-0.12.1/qg_configs/core/methods/Lipidomics/EXPLORIS_4_methods.csv +3 -0
  67. ms_qg-0.12.1/qg_configs/core/methods/Lipidomics/QEXACTIVEHF_2_methods.csv +3 -0
  68. ms_qg-0.12.1/qg_configs/core/methods/Metabolomics/EXPLORIS_3_methods.csv +3 -0
  69. ms_qg-0.12.1/qg_configs/core/methods/Metabolomics/QEXACTIVEHF_2_methods.csv +3 -0
  70. ms_qg-0.12.1/qg_configs/core/methods/Metabolomics/QUANTIVA_1_methods.csv +3 -0
  71. ms_qg-0.12.1/qg_configs/core/methods/Proteomics/ASCEND_1_methods.csv +9 -0
  72. ms_qg-0.12.1/qg_configs/core/methods/Proteomics/ASTRAL_1_methods.csv +6 -0
  73. ms_qg-0.12.1/qg_configs/core/methods/Proteomics/EXPLORIS_1_methods.csv +9 -0
  74. ms_qg-0.12.1/qg_configs/core/methods/Proteomics/EXPLORIS_2_methods.csv +5 -0
  75. ms_qg-0.12.1/qg_configs/core/methods/Proteomics/EXPLORIS_5_methods.csv +5 -0
  76. ms_qg-0.12.1/qg_configs/core/methods/Proteomics/LUMOS_2_methods.csv +7 -0
  77. ms_qg-0.12.1/qg_configs/core/methods/Proteomics/QEXACTIVE_1_methods.csv +5 -0
  78. ms_qg-0.12.1/qg_configs/core/methods/Proteomics/TIMSTOFFLEX_1_methods.csv +5 -0
  79. ms_qg-0.12.1/qg_configs/core/methods/Proteomics/TIMSTOF_1_methods.csv +5 -0
  80. ms_qg-0.12.1/qg_configs/core/methods/Testing/ASTRAL_1_methods.csv +5 -0
  81. ms_qg-0.12.1/qg_configs/core/position/plate_layouts.toml +19 -0
  82. ms_qg-0.12.1/qg_configs/core/position/qc_layouts_tip.csv +6 -0
  83. ms_qg-0.12.1/qg_configs/core/position/qc_layouts_well.csv +90 -0
  84. ms_qg-0.12.1/qg_configs/core/position/sampler.toml +23 -0
  85. ms_qg-0.12.1/qg_configs/core/position/sampler_plate_layouts.csv +6 -0
  86. ms_qg-0.12.1/qg_configs/core/structure/queue_patterns.toml +121 -0
  87. ms_qg-0.12.1/qg_configs/core/structure/samples.csv +39 -0
  88. ms_qg-0.12.1/qg_configs/ui/instrument_config.csv +23 -0
  89. ms_qg-0.12.1/qg_configs/ui/tech_area_defaults.toml +32 -0
  90. ms_qg-0.12.1/scripts/core_profile_check.py +43 -0
  91. ms_qg-0.12.1/scripts/package_smoke.py +60 -0
  92. ms_qg-0.12.1/scripts/release.py +182 -0
  93. ms_qg-0.12.1/scripts/sync_forges.py +240 -0
  94. ms_qg-0.12.1/src/qg/__init__.py +5 -0
  95. ms_qg-0.12.1/src/qg/apps/__init__.py +1 -0
  96. ms_qg-0.12.1/src/qg/apps/_bfabric_auth.py +157 -0
  97. ms_qg-0.12.1/src/qg/apps/bfabric_app.py +28 -0
  98. ms_qg-0.12.1/src/qg/apps/bfabric_app_editor.py +19 -0
  99. ms_qg-0.12.1/src/qg/apps/config_editor.py +904 -0
  100. ms_qg-0.12.1/src/qg/apps/editor_core.py +270 -0
  101. ms_qg-0.12.1/src/qg/apps/integrations/__init__.py +5 -0
  102. ms_qg-0.12.1/src/qg/apps/integrations/bfabric_context.py +10 -0
  103. ms_qg-0.12.1/src/qg/apps/integrations/bfabric_workunit.py +55 -0
  104. ms_qg-0.12.1/src/qg/apps/integrations/example_params.py +73 -0
  105. ms_qg-0.12.1/src/qg/apps/integrations/example_samples.py +108 -0
  106. ms_qg-0.12.1/src/qg/apps/integrations/local_samples.py +134 -0
  107. ms_qg-0.12.1/src/qg/apps/launcher_local.py +21 -0
  108. ms_qg-0.12.1/src/qg/apps/queue_app.py +1147 -0
  109. ms_qg-0.12.1/src/qg/apps/queue_app_local.py +973 -0
  110. ms_qg-0.12.1/src/qg/apps/queue_app_shared.py +1036 -0
  111. ms_qg-0.12.1/src/qg/artifacts.py +83 -0
  112. ms_qg-0.12.1/src/qg/bfabric_samples.py +357 -0
  113. ms_qg-0.12.1/src/qg/bfabric_utils.py +188 -0
  114. ms_qg-0.12.1/src/qg/cli/__init__.py +1 -0
  115. ms_qg-0.12.1/src/qg/cli/assign_positions.py +52 -0
  116. ms_qg-0.12.1/src/qg/cli/find_projects.py +235 -0
  117. ms_qg-0.12.1/src/qg/cli/generate_queues.py +69 -0
  118. ms_qg-0.12.1/src/qg/cli/refresh_cache.py +93 -0
  119. ms_qg-0.12.1/src/qg/cli/validate_config.py +33 -0
  120. ms_qg-0.12.1/src/qg/config_models/__init__.py +0 -0
  121. ms_qg-0.12.1/src/qg/config_models/formatting.py +227 -0
  122. ms_qg-0.12.1/src/qg/config_models/loader.py +788 -0
  123. ms_qg-0.12.1/src/qg/config_models/methods.py +215 -0
  124. ms_qg-0.12.1/src/qg/config_models/positions.py +433 -0
  125. ms_qg-0.12.1/src/qg/config_models/resolved.py +160 -0
  126. ms_qg-0.12.1/src/qg/config_models/structure.py +328 -0
  127. ms_qg-0.12.1/src/qg/config_models/ui.py +183 -0
  128. ms_qg-0.12.1/src/qg/examples/__init__.py +6 -0
  129. ms_qg-0.12.1/src/qg/examples/params/__init__.py +1 -0
  130. ms_qg-0.12.1/src/qg/examples/params/lipidomics_standard.json +446 -0
  131. ms_qg-0.12.1/src/qg/examples/params/repro_proteomics_12.json +327 -0
  132. ms_qg-0.12.1/src/qg/examples/sample_tables/__init__.py +1 -0
  133. ms_qg-0.12.1/src/qg/examples/sample_tables/multi_container_samples.csv +31 -0
  134. ms_qg-0.12.1/src/qg/examples/sample_tables/plate_different_groups.csv +43 -0
  135. ms_qg-0.12.1/src/qg/examples/sample_tables/plate_samples_5x5.csv +26 -0
  136. ms_qg-0.12.1/src/qg/examples/sample_tables/plate_samples_80.csv +81 -0
  137. ms_qg-0.12.1/src/qg/examples/sample_tables/vial_different_groups.csv +43 -0
  138. ms_qg-0.12.1/src/qg/examples/sample_tables/vial_samples_5x5.csv +26 -0
  139. ms_qg-0.12.1/src/qg/examples/sample_tables/vial_samples_5x5.xlsx +0 -0
  140. ms_qg-0.12.1/src/qg/examples/sample_tables/vial_samples_80.csv +81 -0
  141. ms_qg-0.12.1/src/qg/generator.py +477 -0
  142. ms_qg-0.12.1/src/qg/gitlab/__init__.py +0 -0
  143. ms_qg-0.12.1/src/qg/gitlab/_git.py +25 -0
  144. ms_qg-0.12.1/src/qg/gitlab/config_bridge.py +64 -0
  145. ms_qg-0.12.1/src/qg/gitlab/launcher.py +105 -0
  146. ms_qg-0.12.1/src/qg/gitlab/service.py +93 -0
  147. ms_qg-0.12.1/src/qg/gitlab/settings.py +95 -0
  148. ms_qg-0.12.1/src/qg/hystar_xml_writer.py +50 -0
  149. ms_qg-0.12.1/src/qg/logging_setup.py +73 -0
  150. ms_qg-0.12.1/src/qg/params_models.py +354 -0
  151. ms_qg-0.12.1/src/qg/positionV2.py +549 -0
  152. ms_qg-0.12.1/src/qg/qc_layout.py +97 -0
  153. ms_qg-0.12.1/src/qg/qc_positions.py +137 -0
  154. ms_qg-0.12.1/src/qg/queue_builder.py +137 -0
  155. ms_qg-0.12.1/src/qg/queue_structure.py +172 -0
  156. ms_qg-0.12.1/src/qg/randomize.py +163 -0
  157. ms_qg-0.12.1/src/qg/sample_rows.py +98 -0
  158. ms_qg-0.12.1/src/qg/utils.py +107 -0
  159. ms_qg-0.12.1/src/qg/viz/__init__.py +1 -0
  160. ms_qg-0.12.1/src/qg/viz/balance.py +125 -0
  161. ms_qg-0.12.1/src/qg/viz/plate.py +299 -0
  162. ms_qg-0.12.1/src/qg/viz/timeline.py +253 -0
  163. ms_qg-0.12.1/src/qg/writers.py +52 -0
  164. ms_qg-0.12.1/tests/__init__.py +1 -0
  165. ms_qg-0.12.1/tests/gui/AGENTS.md +107 -0
  166. ms_qg-0.12.1/tests/gui/README.md +117 -0
  167. ms_qg-0.12.1/tests/gui/__init__.py +0 -0
  168. ms_qg-0.12.1/tests/gui/_fake_bfabric.py +227 -0
  169. ms_qg-0.12.1/tests/gui/_helpers.py +288 -0
  170. ms_qg-0.12.1/tests/gui/_test_app.py +106 -0
  171. ms_qg-0.12.1/tests/gui/conftest.py +250 -0
  172. ms_qg-0.12.1/tests/gui/features/concentrations.feature +20 -0
  173. ms_qg-0.12.1/tests/gui/features/employee_empty_order.feature +10 -0
  174. ms_qg-0.12.1/tests/gui/features/generation_error.feature +13 -0
  175. ms_qg-0.12.1/tests/gui/features/happy_path.feature +18 -0
  176. ms_qg-0.12.1/tests/gui/features/hystar_output.feature +18 -0
  177. ms_qg-0.12.1/tests/gui/features/launching_order.feature +15 -0
  178. ms_qg-0.12.1/tests/gui/features/lipidomics.feature +15 -0
  179. ms_qg-0.12.1/tests/gui/features/metabolomics_download.feature +15 -0
  180. ms_qg-0.12.1/tests/gui/features/metabolomics_polarity.feature +26 -0
  181. ms_qg-0.12.1/tests/gui/features/multi_container.feature +21 -0
  182. ms_qg-0.12.1/tests/gui/features/non_employee.feature +18 -0
  183. ms_qg-0.12.1/tests/gui/features/non_employee_empty.feature +9 -0
  184. ms_qg-0.12.1/tests/gui/features/noqc.feature +40 -0
  185. ms_qg-0.12.1/tests/gui/features/order_queue_type.feature +71 -0
  186. ms_qg-0.12.1/tests/gui/features/overrides.feature +28 -0
  187. ms_qg-0.12.1/tests/gui/features/params_json.feature +17 -0
  188. ms_qg-0.12.1/tests/gui/features/plate_picker.feature +46 -0
  189. ms_qg-0.12.1/tests/gui/features/project_queue_type.feature +28 -0
  190. ms_qg-0.12.1/tests/gui/features/qc_visits.feature +14 -0
  191. ms_qg-0.12.1/tests/gui/features/queue_name.feature +27 -0
  192. ms_qg-0.12.1/tests/gui/features/randomization.feature +17 -0
  193. ms_qg-0.12.1/tests/gui/features/sample_exclusion.feature +17 -0
  194. ms_qg-0.12.1/tests/gui/features/sample_name_suffix.feature +18 -0
  195. ms_qg-0.12.1/tests/gui/features/sample_source.feature +30 -0
  196. ms_qg-0.12.1/tests/gui/features/show_plate.feature +18 -0
  197. ms_qg-0.12.1/tests/gui/features/tray_capacity.feature +22 -0
  198. ms_qg-0.12.1/tests/gui/features/upload_workunit.feature +15 -0
  199. ms_qg-0.12.1/tests/gui/features/valid_combinations_tab.feature +10 -0
  200. ms_qg-0.12.1/tests/gui/features/validation_required.feature +8 -0
  201. ms_qg-0.12.1/tests/gui/features/vial_mode.feature +26 -0
  202. ms_qg-0.12.1/tests/gui/fixtures/bfabric/container_37190.json +10 -0
  203. ms_qg-0.12.1/tests/gui/fixtures/bfabric/orderitems_37180.json +6 -0
  204. ms_qg-0.12.1/tests/gui/fixtures/bfabric/orderitems_37181.json +3 -0
  205. ms_qg-0.12.1/tests/gui/fixtures/bfabric/orderitems_37182.json +6 -0
  206. ms_qg-0.12.1/tests/gui/fixtures/bfabric/orderitems_37183.json +10 -0
  207. ms_qg-0.12.1/tests/gui/fixtures/bfabric/orderitems_37190.json +3 -0
  208. ms_qg-0.12.1/tests/gui/fixtures/bfabric/orderitems_37195.json +3 -0
  209. ms_qg-0.12.1/tests/gui/fixtures/bfabric/orderitems_37200.json +7 -0
  210. ms_qg-0.12.1/tests/gui/fixtures/bfabric/plates_37180.json +19 -0
  211. ms_qg-0.12.1/tests/gui/fixtures/bfabric/plates_37181.json +11 -0
  212. ms_qg-0.12.1/tests/gui/fixtures/bfabric/plates_37183.json +11 -0
  213. ms_qg-0.12.1/tests/gui/fixtures/bfabric/plates_37190.json +15 -0
  214. ms_qg-0.12.1/tests/gui/fixtures/bfabric/plates_37195.json +13 -0
  215. ms_qg-0.12.1/tests/gui/fixtures/bfabric/plates_37200.json +22 -0
  216. ms_qg-0.12.1/tests/gui/fixtures/bfabric/samples_37180.json +14 -0
  217. ms_qg-0.12.1/tests/gui/fixtures/bfabric/samples_37181.json +6 -0
  218. ms_qg-0.12.1/tests/gui/fixtures/bfabric/samples_37182.json +8 -0
  219. ms_qg-0.12.1/tests/gui/fixtures/bfabric/samples_37183.json +10 -0
  220. ms_qg-0.12.1/tests/gui/fixtures/bfabric/samples_37190.json +10 -0
  221. ms_qg-0.12.1/tests/gui/fixtures/bfabric/samples_37195.json +8 -0
  222. ms_qg-0.12.1/tests/gui/fixtures/bfabric/samples_37196.json +5 -0
  223. ms_qg-0.12.1/tests/gui/fixtures/bfabric/samples_37200.json +12 -0
  224. ms_qg-0.12.1/tests/gui/fixtures/bfabric/samples_99999.json +1 -0
  225. ms_qg-0.12.1/tests/gui/fixtures/bfabric/scenario_project_support/plates_37210.json +11 -0
  226. ms_qg-0.12.1/tests/gui/fixtures/bfabric/scenario_project_support/samples_37210.json +6 -0
  227. ms_qg-0.12.1/tests/gui/fixtures/expected/proteomics_astral1_37180.csv +22 -0
  228. ms_qg-0.12.1/tests/gui/fixtures/projects.csv +11 -0
  229. ms_qg-0.12.1/tests/gui/test_concentrations.py +114 -0
  230. ms_qg-0.12.1/tests/gui/test_employee_empty_order.py +44 -0
  231. ms_qg-0.12.1/tests/gui/test_generation_error.py +41 -0
  232. ms_qg-0.12.1/tests/gui/test_happy_path.py +114 -0
  233. ms_qg-0.12.1/tests/gui/test_hystar_output.py +93 -0
  234. ms_qg-0.12.1/tests/gui/test_launching_order.py +56 -0
  235. ms_qg-0.12.1/tests/gui/test_lipidomics.py +73 -0
  236. ms_qg-0.12.1/tests/gui/test_metabolomics_download.py +73 -0
  237. ms_qg-0.12.1/tests/gui/test_metabolomics_polarity.py +80 -0
  238. ms_qg-0.12.1/tests/gui/test_multi_container.py +158 -0
  239. ms_qg-0.12.1/tests/gui/test_non_employee.py +52 -0
  240. ms_qg-0.12.1/tests/gui/test_non_employee_empty.py +34 -0
  241. ms_qg-0.12.1/tests/gui/test_noqc.py +52 -0
  242. ms_qg-0.12.1/tests/gui/test_order_queue_type.py +66 -0
  243. ms_qg-0.12.1/tests/gui/test_overrides.py +101 -0
  244. ms_qg-0.12.1/tests/gui/test_params_json.py +105 -0
  245. ms_qg-0.12.1/tests/gui/test_plate_picker.py +53 -0
  246. ms_qg-0.12.1/tests/gui/test_project_queue_type.py +49 -0
  247. ms_qg-0.12.1/tests/gui/test_qc_visits.py +65 -0
  248. ms_qg-0.12.1/tests/gui/test_queue_name.py +60 -0
  249. ms_qg-0.12.1/tests/gui/test_randomization.py +86 -0
  250. ms_qg-0.12.1/tests/gui/test_sample_exclusion.py +110 -0
  251. ms_qg-0.12.1/tests/gui/test_sample_name_suffix.py +111 -0
  252. ms_qg-0.12.1/tests/gui/test_sample_source.py +60 -0
  253. ms_qg-0.12.1/tests/gui/test_show_plate.py +74 -0
  254. ms_qg-0.12.1/tests/gui/test_tray_capacity.py +45 -0
  255. ms_qg-0.12.1/tests/gui/test_upload_workunit.py +57 -0
  256. ms_qg-0.12.1/tests/gui/test_valid_combinations_tab.py +47 -0
  257. ms_qg-0.12.1/tests/gui/test_validation_required.py +25 -0
  258. ms_qg-0.12.1/tests/gui/test_vial_mode.py +63 -0
  259. ms_qg-0.12.1/tests/gui_local/__init__.py +0 -0
  260. ms_qg-0.12.1/tests/gui_local/conftest.py +121 -0
  261. ms_qg-0.12.1/tests/gui_local/features/local_happy_path.feature +27 -0
  262. ms_qg-0.12.1/tests/gui_local/features/local_viz.feature +18 -0
  263. ms_qg-0.12.1/tests/gui_local/test_local_happy_path.py +103 -0
  264. ms_qg-0.12.1/tests/gui_local/test_local_viz.py +74 -0
  265. ms_qg-0.12.1/tests/gui_local/test_reproduce_mode.py +61 -0
  266. ms_qg-0.12.1/tests/helpers.py +139 -0
  267. ms_qg-0.12.1/tests/test_artifacts.py +117 -0
  268. ms_qg-0.12.1/tests/test_bfabric_auth.py +132 -0
  269. ms_qg-0.12.1/tests/test_bfabric_samples.py +400 -0
  270. ms_qg-0.12.1/tests/test_bfabric_utils.py +323 -0
  271. ms_qg-0.12.1/tests/test_cli.py +314 -0
  272. ms_qg-0.12.1/tests/test_config_authoring.py +197 -0
  273. ms_qg-0.12.1/tests/test_config_integration.py +497 -0
  274. ms_qg-0.12.1/tests/test_config_models_integration.py +499 -0
  275. ms_qg-0.12.1/tests/test_editor_core.py +137 -0
  276. ms_qg-0.12.1/tests/test_example_samples.py +79 -0
  277. ms_qg-0.12.1/tests/test_generator.py +1740 -0
  278. ms_qg-0.12.1/tests/test_gitlab.py +421 -0
  279. ms_qg-0.12.1/tests/test_hystar_xml_writer.py +125 -0
  280. ms_qg-0.12.1/tests/test_local_samples.py +155 -0
  281. ms_qg-0.12.1/tests/test_positioning.py +93 -0
  282. ms_qg-0.12.1/tests/test_positions.py +929 -0
  283. ms_qg-0.12.1/tests/test_queue_app_shared.py +709 -0
  284. ms_qg-0.12.1/tests/test_queue_builder.py +241 -0
  285. ms_qg-0.12.1/tests/test_queue_structure.py +358 -0
  286. ms_qg-0.12.1/tests/test_queue_structure_explicit.py +133 -0
  287. ms_qg-0.12.1/tests/test_randomize.py +481 -0
  288. ms_qg-0.12.1/tests/test_refresh_cache.py +44 -0
  289. ms_qg-0.12.1/tests/test_resolved_config.py +285 -0
  290. ms_qg-0.12.1/tests/test_utils.py +76 -0
  291. ms_qg-0.12.1/tests/test_viz_balance.py +124 -0
  292. ms_qg-0.12.1/tests/test_viz_plate.py +210 -0
  293. ms_qg-0.12.1/tests/test_viz_timeline.py +186 -0
  294. ms_qg-0.12.1/tests/test_writers.py +68 -0
  295. ms_qg-0.12.1/uv.lock +2876 -0
@@ -0,0 +1,250 @@
1
+ ---
2
+ name: qg-config-authoring
3
+ description: Use when adding to or editing the qg queue-generation configuration under qg_configs/ — standing up a new technology, adding an instrument, adding a plate layout with its QC samples and a queue pattern, or adding/adjusting an output format. Triggers when the user mentions qg_configs, instruments.csv, queue_patterns.toml, plate_layouts.toml, qc_layouts, samples.csv, output_formats.toml, adding an instrument / technology / QC sample / plate layout, or keeping the config files consistent. Explains which files each change touches and the cross-file rules qg_configuration() enforces at load.
4
+ ---
5
+
6
+ # Authoring qg configuration
7
+
8
+ `qg` is configuration-driven: technologies, instruments, samplers, plate layouts,
9
+ QC samples, patterns, and output formats are all rows in CSVs or tables in TOML
10
+ files under `qg_configs/`. Adding any of them is a config edit, not a code change.
11
+ This skill is the playbook for the recurring edits and — crucially — the cross-file
12
+ dependencies between them.
13
+
14
+ ## How to make a change (and the one rule that matters)
15
+
16
+ The config files are **not** cross-validated incrementally as you edit them. They
17
+ are validated as a set, at load, by `qg_configuration()`
18
+ (`src/qg/config_models/loader.py:664`), which reads all twelve config files and then
19
+ runs the cross-reference validators (`loader.py:122-261`). A mismatch surfaces there
20
+ as a `ConfigValidationError` — **except** `ui/instrument_config.csv`, which is not
21
+ cross-validated and instead fails later as a runtime lookup in the app. So:
22
+
23
+ 1. Make the edits across **all** the files a change touches (the stories below list them).
24
+ 2. Run `uv run qg-validate` (add `-c <dir>` for a non-default config dir). This loads
25
+ and cross-validates everything; a clean exit means the set is consistent.
26
+ 3. Keep `ui/instrument_config.csv` in sync by hand — `qg-validate` will not catch a
27
+ missing or mismatched row there.
28
+
29
+ **The consistency rule — the QC-sample → reserved-position cascade.** Every QC sample
30
+ id a queue pattern references (in its `start` / `middle` / `end` / `separation` /
31
+ `middle_extended` lists) must:
32
+
33
+ - exist in `core/structure/samples.csv` for the **same** `tech_area`, and
34
+ - have a **reserved position** in a QC layout
35
+ (`core/position/qc_layouts_well.csv` for well samplers, `qc_layouts_tip.csv` for tip
36
+ samplers) for the `(tech_area, qc_layout_name, plate_layout)` the queue uses.
37
+
38
+ This is why "add a QC sample to a pattern" is never a one-file edit: it cascades into
39
+ the QC layout, and a new layout into the plate-layout and sampler-mapping files. The
40
+ validators that enforce it are `_validate_pattern_sample_refs`,
41
+ `_validate_layout_sample_refs`, and `_validate_pattern_layout_compatibility`
42
+ (`loader.py:122-261`).
43
+
44
+ For the full field-by-field schema of each file, see
45
+ [docs/reference/config.md](../../../docs/reference/config.md). For the instrument-row
46
+ specifics, see "Adding a New Instrument" in [AGENTS.md](../../../AGENTS.md).
47
+
48
+ ---
49
+
50
+ ## Story 1 — Stand up a new technology end-to-end
51
+
52
+ This is the spine: every other story is a slice of it. The worked example below is a
53
+ complete, minimal technology `Testing_v3` (one instrument, the reused Vanquish well
54
+ sampler, the reused `xcalibur` output format, a new 12-well plate layout, three sample
55
+ types, one QC pattern). It is exactly what `tests/test_config_authoring.py` builds and
56
+ asserts is loadable, valid, and usable — copy it and adapt.
57
+
58
+ Author the files in this order (technology → plate layout → pattern):
59
+
60
+ **1. Declare the technology** — `qg_configs/ui/tech_area_defaults.toml`:
61
+
62
+ ```toml
63
+ [Testing_v3]
64
+ default_user = ""
65
+ default_polarities = ["pos"]
66
+ bfabric_areas = []
67
+ ```
68
+
69
+ **2. Define the sample types** — append to `qg_configs/core/structure/samples.csv`
70
+ (header: `tech_area,sample_id,sample_name,sample_type,level,description,inj_vol,file_name_template`):
71
+
72
+ ```csv
73
+ Testing_v3,default,"",Unknown,,Default Testing_v3 user sample,2.0,{date}_{run}_C{container}_S{sample_id}_{sample_name}
74
+ Testing_v3,QCv3,QCv3,QC,,Testing_v3 QC sample,2.0,{date}_{run}_C{container}_{sample_name}
75
+ Testing_v3,cleanv3,cleanv3,Blank,,Testing_v3 column cleanup blank,2.0,{date}_{run}_C{container}_{sample_name}
76
+ ```
77
+
78
+ Every technology needs a `default` row (the user-sample template). QC and blank rows
79
+ are the injections a pattern can reference.
80
+
81
+ **3. Add the instrument + its methods file.** Append to
82
+ `qg_configs/core/formatting/instruments.csv`
83
+ (header: `tech_area,instrument,methods_file,path_template`):
84
+
85
+ ```csv
86
+ Testing_v3,TESTING_V3_1,methods/Testing_v3/TESTING_V3_1_methods.csv,D:\Data2San\p{container}\Testing_v3\TESTING_V3_1\{user}_{date}
87
+ ```
88
+
89
+ Create the file the `methods_file` column points at,
90
+ `qg_configs/core/methods/Testing_v3/TESTING_V3_1_methods.csv`
91
+ (header: `sample_type,polarity,method_name,method_path`):
92
+
93
+ ```csv
94
+ default,pos,DDA,C:\Xcalibur\methods\
95
+ QCv3,pos,DDA,C:\Xcalibur\methods\__autoQC\QCv3
96
+ ```
97
+
98
+ A single-polarity technology (`pos` only) needs just `pos` rows; metabolomics /
99
+ lipidomics need both `pos` and `neg` because of polarity expansion. The `default` row
100
+ covers user samples; add a per-QC row for any QC sample placed in the queue.
101
+
102
+ **4. Add the plate layout + sampler mapping.** Append to
103
+ `qg_configs/core/position/plate_layouts.toml`:
104
+
105
+ ```toml
106
+ [TestingV3_12]
107
+ rows = ["A", "B"]
108
+ cols = [1, 2, 3, 4, 5, 6]
109
+ ```
110
+
111
+ Map it to a sampler + queue type in
112
+ `qg_configs/core/position/sampler_plate_layouts.csv`
113
+ (header: `sampler,plate_layout,queue_type`):
114
+
115
+ ```csv
116
+ Vanquish,TestingV3_12,Vial
117
+ ```
118
+
119
+ (Reusing the existing `Vanquish` sampler — no `sampler.toml` edit needed. Adding a new
120
+ sampler is a separate, larger change.)
121
+
122
+ **5. Reserve QC positions** for the layout — append to
123
+ `qg_configs/core/position/qc_layouts_well.csv`
124
+ (header: `tech_area,qc_layout_name,plate_layout,sample_id,tray,row,col`):
125
+
126
+ ```csv
127
+ Testing_v3,v3std,TestingV3_12,QCv3,B,B,6
128
+ ```
129
+
130
+ `row` must be one of the layout's `rows` and `col` one of its `cols`. This is the
131
+ reserved-position half of the cascade rule.
132
+
133
+ **6. Create the pattern** that uses those QC samples — append to
134
+ `qg_configs/core/structure/queue_patterns.toml`:
135
+
136
+ ```toml
137
+ [Testing_v3.simple]
138
+ description = "Testing_v3 simple pattern: QC at start and end"
139
+ run_QC_after_n_samples = 4
140
+ start = ["QCv3"]
141
+ middle = []
142
+ end = ["QCv3"]
143
+ ```
144
+
145
+ Reference **only** sample ids defined for `Testing_v3` in `samples.csv`, and make sure
146
+ each appears in the QC layout from step 5.
147
+
148
+ **7. Expose it in the UI** — append to `qg_configs/ui/instrument_config.csv`
149
+ (header: `tech_area,instrument,sampler,output_format,default_pattern`):
150
+
151
+ ```csv
152
+ Testing_v3,TESTING_V3_1,Vanquish,xcalibur,simple
153
+ ```
154
+
155
+ `sampler`, `output_format`, and `default_pattern` must all exist (in `sampler.toml`,
156
+ `output_formats.toml`, and `queue_patterns.toml` respectively). This file is **not**
157
+ cross-validated at load — double-check it manually.
158
+
159
+ Then validate: `uv run qg-validate`. A clean exit means the technology is consistent
160
+ and queue generation will resolve it.
161
+
162
+ ---
163
+
164
+ ## Story 2 — Add an instrument to an existing technology
165
+
166
+ Three files, and they must agree (this is step 3 of Story 1, plus the UI row):
167
+
168
+ - `qg_configs/core/formatting/instruments.csv` — a row whose `methods_file` points at…
169
+ - `qg_configs/core/methods/<Technology>/<Instrument>_methods.csv` — the methods file you create.
170
+ - `qg_configs/ui/instrument_config.csv` — one row per (instrument, sampler) pairing so
171
+ the instrument appears in the app menu.
172
+
173
+ The `instruments.csv` `methods_file` path and the actual file must match exactly;
174
+ `MethodsConfig.load()` discovers methods files from these rows. See "Adding a New
175
+ Instrument" in [AGENTS.md](../../../AGENTS.md) for the full checklist.
176
+
177
+ ---
178
+
179
+ ## Story 3 — Add a plate layout, its QC samples, and a pattern
180
+
181
+ This is the cascade in concrete form. Touches:
182
+
183
+ - `qg_configs/core/position/plate_layouts.toml` — the geometry (`rows`, `cols`).
184
+ - `qg_configs/core/position/sampler_plate_layouts.csv` — map the layout to a sampler + `queue_type`.
185
+ - `qg_configs/core/structure/samples.csv` — any **new** QC / blank sample types.
186
+ - `qg_configs/core/position/qc_layouts_well.csv` (well samplers) **or** `qc_layouts_tip.csv`
187
+ (tip samplers, e.g. Evosep) — a reserved position for **every** QC sample the pattern
188
+ will reference, for this `(tech_area, qc_layout_name, plate_layout)`.
189
+ - `qg_configs/core/structure/queue_patterns.toml` — the pattern, referencing only those
190
+ QC sample ids.
191
+
192
+ If you add a QC sample to a pattern but forget its reserved position,
193
+ `_validate_pattern_layout_compatibility` fails at load with a message naming the
194
+ required sample ids. Add the position; don't remove the reference.
195
+
196
+ ---
197
+
198
+ ## Story 4 — Add or adjust an output format
199
+
200
+ One file: `qg_configs/core/formatting/output_formats.toml`. Each format is a base table
201
+ plus a `columns` sub-table mapping output column name → internal field:
202
+
203
+ ```toml
204
+ [xcalibur]
205
+ description = "Thermo XCalibur format"
206
+ file_extension = ".csv"
207
+ writer = "xcalibur_csv"
208
+ position_format = "{tray}:{grid_position}"
209
+ grid_position_format = "{row},{col}"
210
+ grid_position_conversion = "identity"
211
+
212
+ [xcalibur.columns]
213
+ "File Name" = "file_name"
214
+ Path = "data_path"
215
+ Position = "position"
216
+ "Inj Vol" = "inj_vol"
217
+ "Sample ID" = "sample_id"
218
+ "Sample Name" = "sample_name"
219
+ "Instrument Method" = "method"
220
+ ```
221
+
222
+ `writer` must be a known writer (`xcalibur_csv`, `chronos_csv`, `hystar_xml`). To add a
223
+ column for **one technology only**, use a per-tech overlay sub-table — it merges over
224
+ the base `columns`:
225
+
226
+ ```toml
227
+ # Metabolomics-only extra columns, inherited base columns otherwise
228
+ [xcalibur_sii.columns.Metabolomics]
229
+ "Sample Type" = "sample_type"
230
+ "Level" = "level"
231
+ ```
232
+
233
+ A literal value uses the `literal:` prefix (e.g. `"L3 Laboratory" = "literal:FGCZ"`).
234
+
235
+ ---
236
+
237
+ ## Verify your change
238
+
239
+ ```bash
240
+ uv run qg-validate # loads + cross-validates the committed config
241
+ uv run qg-validate -c <config_dir> # …or a custom config directory
242
+ ```
243
+
244
+ Then generate a sample queue to confirm it is usable end-to-end — either through the
245
+ app, or programmatically as `tests/test_config_authoring.py` does
246
+ (`qg_configuration(<dir>)` → `QueueGenerator(cfg, queue_input).generate()`), or via the
247
+ CLI: `uv run qg <input.json> -o <out.csv> -c <config_dir>`.
248
+
249
+ If you add a real (committed) technology or instrument, also follow the Release Process
250
+ in [AGENTS.md](../../../AGENTS.md): a `CHANGELOG.md` bullet and a version bump.
@@ -0,0 +1,26 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Config file
10
+ .env
11
+
12
+ # Virtual environments
13
+ .venv
14
+ __marimo__/
15
+ .claude/
16
+ queue_files/
17
+ *.pdf
18
+ *.svg
19
+ .snakemake/
20
+ *.html
21
+ *_files/
22
+ *.png
23
+ plans/
24
+
25
+ # actual data
26
+ bfabric_cache
@@ -0,0 +1,6 @@
1
+ VALIDATION_BFABRIC_INSTANCE=https://fgcz-bfabric.uzh.ch/bfabric/
2
+ SUPPORTED_BFABRIC_INSTANCES=["https://fgcz-bfabric.uzh.ch/bfabric/","https://fgcz-bfabric-test.uzh.ch/bfabric/"]
3
+ # It's recommended to define a relatively long and safely random secret key
4
+ #SECRET_KEY=
5
+ _FEEDER_PWD="TODO"
6
+ FEEDER_USER_CREDENTIALS={"https://fgcz-bfabric.uzh.ch/bfabric/":{"login":"pfeeder","password":"${_FEEDER_PWD}"},"https://fgcz-bfabric-test.uzh.ch/bfabric/":{"login":"pfeeder","password":"${_FEEDER_PWD}"}}
@@ -0,0 +1,237 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ workflow_dispatch:
8
+ # Callable so publish.yml can run this whole suite as a release gate before
9
+ # building the image (restores GitLab's test -> build stage ordering).
10
+ workflow_call:
11
+ inputs:
12
+ # publish.yml passes a unique value here so the release-gate run gets its
13
+ # own concurrency group and can't be cancelled by a concurrent push to main
14
+ # (a tag release is already safe — its ref makes the group unique).
15
+ concurrency-group:
16
+ type: string
17
+ required: false
18
+ default: ""
19
+
20
+ permissions:
21
+ contents: read
22
+
23
+ # Mirror GitLab's workflow.auto_cancel.on_new_commit: interruptible — a new push
24
+ # to the same ref cancels the in-flight run. When called as a release gate the
25
+ # caller supplies a unique group (above), so the gate itself is never cancelled.
26
+ concurrency:
27
+ group: ${{ inputs.concurrency-group || format('ci-{0}', github.ref) }}
28
+ cancel-in-progress: true
29
+
30
+ jobs:
31
+ # Fast feedback: every non-browser test. No playwright, no apt — fails fast.
32
+ unit:
33
+ runs-on: ubuntu-latest
34
+ steps:
35
+ - name: Checkout
36
+ uses: actions/checkout@v7
37
+ with:
38
+ fetch-depth: 0
39
+ - name: Install uv
40
+ uses: astral-sh/setup-uv@v8.2.0
41
+ with:
42
+ enable-cache: true
43
+ - name: Install Python
44
+ run: uv python install
45
+ - name: Sync (dev + portal)
46
+ run: uv sync --frozen
47
+ - name: Run unit tests
48
+ run: >-
49
+ uv run --no-sync pytest
50
+ --ignore=tests/gui
51
+ --ignore=tests/gui_local
52
+ --cov=qg
53
+ --cov-branch
54
+ --cov-report=xml:coverage.xml
55
+ --cov-report=term-missing:skip-covered
56
+ - name: Check changed-line coverage
57
+ if: github.event_name == 'pull_request'
58
+ run: >-
59
+ uv run --no-sync diff-cover coverage.xml
60
+ --compare-branch="origin/${{ github.base_ref }}"
61
+ --fail-under=60
62
+
63
+ quality:
64
+ runs-on: ubuntu-latest
65
+ steps:
66
+ - name: Checkout
67
+ uses: actions/checkout@v7
68
+ - name: Install uv
69
+ uses: astral-sh/setup-uv@v8.2.0
70
+ with:
71
+ enable-cache: true
72
+ - name: Install Python
73
+ run: uv python install
74
+ - name: Sync (dev + portal)
75
+ run: uv sync --frozen
76
+ - name: Ruff lint
77
+ run: uv run --no-sync ruff check . --output-format=github
78
+ - name: Ruff format
79
+ run: uv run --no-sync ruff format --check .
80
+ - name: Dependency declarations
81
+ run: uv run --no-sync deptry .
82
+
83
+ package-smoke:
84
+ runs-on: ubuntu-latest
85
+ steps:
86
+ - name: Checkout
87
+ uses: actions/checkout@v7
88
+ - name: Install uv
89
+ uses: astral-sh/setup-uv@v8.2.0
90
+ with:
91
+ enable-cache: true
92
+ - name: Install Python
93
+ run: uv python install
94
+ - name: Sync core development environment
95
+ run: uv sync --frozen --no-group portal
96
+ - name: Build and inspect distributions
97
+ run: uv run --no-sync python scripts/package_smoke.py
98
+
99
+ # Floor gate: `pip install qg` on the oldest supported interpreter must import,
100
+ # expose its CLIs, resolve its bundled configs, and pass the non-browser suite.
101
+ # Installs the built wheel with pip (not uv sync) so this mirrors what a reader
102
+ # of the README actually runs, and guards the requires-python floor from drifting
103
+ # up to the development interpreter unnoticed.
104
+ install-floor:
105
+ runs-on: ubuntu-latest
106
+ steps:
107
+ - name: Checkout
108
+ uses: actions/checkout@v7
109
+ - name: Install uv
110
+ uses: astral-sh/setup-uv@v8.2.0
111
+ with:
112
+ enable-cache: true
113
+ cache-suffix: floor
114
+ - name: Build wheel
115
+ run: uv build --wheel --out-dir dist
116
+ - name: Install the wheel on the floor interpreter
117
+ run: |
118
+ uv venv --python 3.13 --seed .venv-floor
119
+ .venv-floor/bin/pip install dist/ms_qg-*.whl pytest pytest-mock pytest-bdd xlsxwriter
120
+ - name: Import, CLI and bundled-config checks
121
+ run: |
122
+ .venv-floor/bin/python -c "import qg"
123
+ .venv-floor/bin/qg --help > /dev/null
124
+ .venv-floor/bin/python -c "from qg.config_models.loader import _DEFAULT_CONFIG_DIR as d; assert d.is_dir(), d"
125
+ - name: Run the non-browser suite against the installed wheel
126
+ run: >-
127
+ .venv-floor/bin/python -m pytest
128
+ --ignore=tests/gui
129
+ --ignore=tests/gui_local
130
+ -m "not bfabric and not bfabric_test_instance and not local_gui"
131
+
132
+ docs:
133
+ runs-on: ubuntu-latest
134
+ steps:
135
+ - name: Checkout
136
+ uses: actions/checkout@v7
137
+ - name: Install uv
138
+ uses: astral-sh/setup-uv@v8.2.0
139
+ with:
140
+ enable-cache: true
141
+ cache-suffix: docs
142
+ - name: Install Python
143
+ run: uv python install
144
+ - name: Sync docs environment
145
+ run: uv sync --frozen --only-group docs
146
+ - name: Build documentation (strict)
147
+ run: uv run --no-sync mkdocs build --strict
148
+
149
+ # Keep the checked-in Python sources aligned with the repository's Pyright
150
+ # configuration. This mirrors the local pre-commit hook.
151
+ pyright:
152
+ runs-on: ubuntu-latest
153
+ steps:
154
+ - name: Checkout
155
+ uses: actions/checkout@v7
156
+ - name: Install uv
157
+ uses: astral-sh/setup-uv@v8.2.0
158
+ with:
159
+ enable-cache: true
160
+ - name: Install Python
161
+ run: uv python install
162
+ - name: Sync (dev + portal)
163
+ run: uv sync --frozen
164
+ - name: Run Pyright
165
+ run: uv run --no-sync python -m pyright
166
+
167
+ # Core-install gate: prove `qg` installs, imports, and its non-B-Fabric tests
168
+ # pass WITHOUT the portal extra (no bfabric/fastapi/starlette/python-gitlab).
169
+ # Syncs only the `dev` group, omitting the `portal` default-group; asserts
170
+ # bfabric is absent, then runs the B-Fabric-free suite. Guards the publication
171
+ # path (a plain `pip install qg` stays B-Fabric-free).
172
+ core-no-bfabric:
173
+ runs-on: ubuntu-latest
174
+ steps:
175
+ - name: Checkout
176
+ uses: actions/checkout@v7
177
+ - name: Install uv
178
+ uses: astral-sh/setup-uv@v8.2.0
179
+ with:
180
+ enable-cache: true
181
+ # Own cache namespace: this job installs a portal-less subset, so it
182
+ # must not share the uv.lock-keyed cache with the full-install jobs
183
+ # (otherwise whichever saves first freezes the wrong dependency set).
184
+ cache-suffix: core
185
+ - name: Install Python
186
+ run: uv python install
187
+ - name: Sync core only (no portal group)
188
+ run: uv sync --frozen --no-group portal
189
+ - name: Run core profile checks
190
+ run: uv run --no-sync --no-group portal python scripts/core_profile_check.py
191
+
192
+ # Browser suite: tests/gui (portal app) + tests/gui_local (standalone app),
193
+ # driven in headless Chromium via pytest-playwright. Runs on Microsoft's
194
+ # official Playwright image, which ships the browsers AND their ~67 apt system
195
+ # libs pre-baked — so this job needs no `playwright install` and no apt step.
196
+ # The image tag MUST track the `playwright` pin in uv.lock (currently 1.62.0);
197
+ # a mismatch surfaces as a clear "browser not found / version" error.
198
+ # Do NOT set PLAYWRIGHT_BROWSERS_PATH here, or the baked browsers hide.
199
+ gui:
200
+ runs-on: ubuntu-latest
201
+ container: mcr.microsoft.com/playwright:v1.62.0-noble
202
+ steps:
203
+ - name: Checkout
204
+ uses: actions/checkout@v7
205
+ # setup-uv supports container jobs; if it ever fails to fetch/cache uv
206
+ # inside this image, the known-good fallback (used by the old GitLab job)
207
+ # is the static installer:
208
+ # run: curl -LsSf https://astral.sh/uv/install.sh | sh && echo "$HOME/.local/bin" >> "$GITHUB_PATH"
209
+ - name: Install uv
210
+ uses: astral-sh/setup-uv@v8.2.0
211
+ with:
212
+ enable-cache: true
213
+ - name: Install Python
214
+ run: uv python install
215
+ - name: Sync (dev + portal)
216
+ run: uv sync --frozen
217
+ - name: Run GUI tests
218
+ run: uv run --no-sync pytest tests/gui tests/gui_local
219
+
220
+ # Static lint of the marimo apps. --strict makes warnings (e.g. the
221
+ # branch-expression rule, where a bare mo.md(...) inside if/elif/else is
222
+ # computed then discarded so the output never renders) fail the pipeline.
223
+ marimo-check:
224
+ runs-on: ubuntu-latest
225
+ steps:
226
+ - name: Checkout
227
+ uses: actions/checkout@v7
228
+ - name: Install uv
229
+ uses: astral-sh/setup-uv@v8.2.0
230
+ with:
231
+ enable-cache: true
232
+ - name: Install Python
233
+ run: uv python install
234
+ - name: Sync (dev + portal)
235
+ run: uv sync --frozen
236
+ - name: marimo check (strict)
237
+ run: uv run --no-sync marimo check --strict src/qg/apps/queue_app.py src/qg/apps/queue_app_local.py src/qg/apps/config_editor.py
@@ -0,0 +1,57 @@
1
+ name: pages
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ workflow_dispatch:
7
+
8
+ permissions:
9
+ contents: read
10
+ pages: write
11
+ id-token: write
12
+
13
+ concurrency:
14
+ group: pages
15
+ cancel-in-progress: false
16
+
17
+ jobs:
18
+ build:
19
+ runs-on: ubuntu-latest
20
+ steps:
21
+ - name: Checkout
22
+ uses: actions/checkout@v7
23
+
24
+ - name: Install uv
25
+ uses: astral-sh/setup-uv@v8.2.0
26
+ with:
27
+ enable-cache: true
28
+
29
+ - name: Install Python
30
+ run: uv python install
31
+
32
+ # Docs-only: mkdocs-material lives in the `docs` group and the site needs
33
+ # no plugins that import the package, so we install just that group.
34
+ - name: Install docs dependencies
35
+ run: uv sync --frozen --only-group docs
36
+
37
+ - name: Configure Pages
38
+ uses: actions/configure-pages@v6
39
+
40
+ - name: Build docs
41
+ run: uv run --no-sync mkdocs build --strict
42
+
43
+ - name: Upload Pages artifact
44
+ uses: actions/upload-pages-artifact@v5
45
+ with:
46
+ path: public
47
+
48
+ deploy:
49
+ runs-on: ubuntu-latest
50
+ needs: build
51
+ environment:
52
+ name: github-pages
53
+ url: ${{ steps.deployment.outputs.page_url }}
54
+ steps:
55
+ - name: Deploy to GitHub Pages
56
+ id: deployment
57
+ uses: actions/deploy-pages@v5
@@ -0,0 +1,101 @@
1
+ name: publish
2
+
3
+ # CD for the GitHub remote: build the container image and push it to the GitHub
4
+ # Container Registry (ghcr.io/<owner>/<repo>). GitLab separately retains the
5
+ # production arm64 OCI-to-NFS path. Tagging a release (vX.Y.Z) publishes this
6
+ # multi-platform image; workflow_dispatch supports an explicit GitHub build.
7
+ on:
8
+ push:
9
+ tags: ["v*"]
10
+ workflow_dispatch:
11
+
12
+ permissions:
13
+ contents: read
14
+ packages: write
15
+
16
+ concurrency:
17
+ group: publish-${{ github.ref }}
18
+ cancel-in-progress: false
19
+
20
+ env:
21
+ IMAGE: ghcr.io/${{ github.repository }}
22
+
23
+ jobs:
24
+ # Release gate: run the full CI suite against the tagged commit first. Mirrors
25
+ # GitLab's stages: [test, build] ordering — a red test stage blocks the build.
26
+ test:
27
+ uses: ./.github/workflows/ci.yml
28
+ with:
29
+ # Unique group so this gate run isn't cancelled by a concurrent push to main.
30
+ concurrency-group: release-gate-${{ github.run_id }}
31
+
32
+ build-push:
33
+ needs: test
34
+ runs-on: ubuntu-latest
35
+ steps:
36
+ - name: Checkout
37
+ uses: actions/checkout@v7
38
+
39
+ # arm64 emulation so a single amd64 runner can build the arm64 leg.
40
+ - name: Set up QEMU
41
+ uses: docker/setup-qemu-action@v3
42
+
43
+ - name: Set up Buildx
44
+ uses: docker/setup-buildx-action@v3
45
+
46
+ - name: Log in to ghcr.io
47
+ uses: docker/login-action@v3
48
+ with:
49
+ registry: ghcr.io
50
+ username: ${{ github.actor }}
51
+ password: ${{ secrets.GITHUB_TOKEN }}
52
+
53
+ - name: Derive image tags and labels
54
+ id: meta
55
+ uses: docker/metadata-action@v5
56
+ with:
57
+ images: ${{ env.IMAGE }}
58
+ # tag push -> the version tag (vX.Y.Z); manual dispatch -> the branch
59
+ # name (e.g. main), so manual builds get a readable tag, not sha-only.
60
+ tags: |
61
+ type=ref,event=tag
62
+ type=ref,event=branch
63
+ type=sha
64
+
65
+ # Builds the existing Dockerfile (uv base, portal extra by default, uvicorn
66
+ # entrypoint on :8000) for both platforms. The arm64 image is what the
67
+ # deploy host runs; amd64 lets the image run on amd64 dev machines too.
68
+ - name: Build and push
69
+ uses: docker/build-push-action@v6
70
+ with:
71
+ context: .
72
+ platforms: linux/amd64,linux/arm64
73
+ push: true
74
+ tags: ${{ steps.meta.outputs.tags }}
75
+ labels: ${{ steps.meta.outputs.labels }}
76
+ cache-from: type=gha
77
+ cache-to: type=gha,mode=max
78
+
79
+ # Publishes the sdist and wheel to PyPI on a version tag, gated by the same CI
80
+ # suite. Uses PyPI Trusted Publishing (OIDC), so no API token is stored here;
81
+ # the PyPI project must list this repository and the `pypi` environment as a
82
+ # trusted publisher. Skipped on workflow_dispatch, where the version is usually
83
+ # already published and PyPI rejects a re-upload.
84
+ publish-pypi:
85
+ needs: test
86
+ if: startsWith(github.ref, 'refs/tags/v')
87
+ runs-on: ubuntu-latest
88
+ environment: pypi
89
+ permissions:
90
+ id-token: write
91
+ steps:
92
+ - name: Checkout
93
+ uses: actions/checkout@v7
94
+ - name: Install uv
95
+ uses: astral-sh/setup-uv@v8.2.0
96
+ with:
97
+ enable-cache: true
98
+ - name: Build sdist and wheel
99
+ run: uv build --out-dir dist
100
+ - name: Publish to PyPI
101
+ run: uv publish --trusted-publishing always