nullpol 0.2.2__tar.gz → 0.2.3__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 (221) hide show
  1. nullpol-0.2.3/.github/workflows/ci.yml +66 -0
  2. {nullpol-0.2.2 → nullpol-0.2.3}/.github/workflows/codeql.yml +3 -3
  3. {nullpol-0.2.2 → nullpol-0.2.3}/.github/workflows/scheduled-release.yml +1 -1
  4. {nullpol-0.2.2 → nullpol-0.2.3}/.pre-commit-config.yaml +4 -4
  5. {nullpol-0.2.2 → nullpol-0.2.3}/PKG-INFO +1 -1
  6. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/cli/data_analysis.py +1 -1
  7. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/integrations/asimov/tgrflow.py +88 -20
  8. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/integrations/asimov/utility.py +5 -2
  9. {nullpol-0.2.2 → nullpol-0.2.3}/tests/analysis/test_data_context.py +1 -1
  10. nullpol-0.2.3/tests/cli/test_data_analysis_validation.py +79 -0
  11. nullpol-0.2.3/tests/integrations/asimov/__init__.py +1 -0
  12. nullpol-0.2.3/tests/integrations/asimov/conftest.py +78 -0
  13. nullpol-0.2.3/tests/integrations/asimov/test_tgrflow.py +310 -0
  14. nullpol-0.2.3/tests/integrations/asimov/test_tgrflow_integration.py +251 -0
  15. nullpol-0.2.3/tests/integrations/asimov/test_utility.py +85 -0
  16. {nullpol-0.2.2 → nullpol-0.2.3}/tests/smoke_test.py +15 -3
  17. {nullpol-0.2.2 → nullpol-0.2.3}/uv.lock +423 -429
  18. nullpol-0.2.2/.github/workflows/ci.yml +0 -36
  19. {nullpol-0.2.2 → nullpol-0.2.3}/.github/ISSUE_TEMPLATE/bug-report.md +0 -0
  20. {nullpol-0.2.2 → nullpol-0.2.3}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  21. {nullpol-0.2.2 → nullpol-0.2.3}/.github/ISSUE_TEMPLATE/feature-request.md +0 -0
  22. {nullpol-0.2.2 → nullpol-0.2.3}/.github/pull_request_template.md +0 -0
  23. {nullpol-0.2.2 → nullpol-0.2.3}/.github/workflows/documentation.yml +0 -0
  24. {nullpol-0.2.2 → nullpol-0.2.3}/.github/workflows/draft-release.yml +0 -0
  25. {nullpol-0.2.2 → nullpol-0.2.3}/.github/workflows/publish.yml +0 -0
  26. {nullpol-0.2.2 → nullpol-0.2.3}/.github/workflows/publish_testpypi.yml +0 -0
  27. {nullpol-0.2.2 → nullpol-0.2.3}/.github/workflows/release.yml +0 -0
  28. {nullpol-0.2.2 → nullpol-0.2.3}/.github/workflows/semantic-pr-check.yml +0 -0
  29. {nullpol-0.2.2 → nullpol-0.2.3}/.gitignore +0 -0
  30. {nullpol-0.2.2 → nullpol-0.2.3}/.markdownlint.yaml +0 -0
  31. {nullpol-0.2.2 → nullpol-0.2.3}/.prettierignore +0 -0
  32. {nullpol-0.2.2 → nullpol-0.2.3}/.prettierrc.yaml +0 -0
  33. {nullpol-0.2.2 → nullpol-0.2.3}/.typos.toml +0 -0
  34. {nullpol-0.2.2 → nullpol-0.2.3}/CITATION.cff +0 -0
  35. {nullpol-0.2.2 → nullpol-0.2.3}/CODE_OF_CONDUCT.md +0 -0
  36. {nullpol-0.2.2 → nullpol-0.2.3}/CONTRIBUTING.md +0 -0
  37. {nullpol-0.2.2 → nullpol-0.2.3}/LICENSE +0 -0
  38. {nullpol-0.2.2 → nullpol-0.2.3}/README.md +0 -0
  39. {nullpol-0.2.2 → nullpol-0.2.3}/SECURITY.md +0 -0
  40. {nullpol-0.2.2 → nullpol-0.2.3}/cliff.toml +0 -0
  41. {nullpol-0.2.2 → nullpol-0.2.3}/docs/.gitignore +0 -0
  42. {nullpol-0.2.2 → nullpol-0.2.3}/docs/asimov.md +0 -0
  43. {nullpol-0.2.2 → nullpol-0.2.3}/docs/citing.md +0 -0
  44. {nullpol-0.2.2 → nullpol-0.2.3}/docs/contributing.md +0 -0
  45. {nullpol-0.2.2 → nullpol-0.2.3}/docs/examples/scalar-tensor.md +0 -0
  46. {nullpol-0.2.2 → nullpol-0.2.3}/docs/index.md +0 -0
  47. {nullpol-0.2.2 → nullpol-0.2.3}/docs/injections.md +0 -0
  48. {nullpol-0.2.2 → nullpol-0.2.3}/docs/installation.md +0 -0
  49. {nullpol-0.2.2 → nullpol-0.2.3}/docs/javascripts/mathjax.js +0 -0
  50. {nullpol-0.2.2 → nullpol-0.2.3}/docs/reference/index.md +0 -0
  51. {nullpol-0.2.2 → nullpol-0.2.3}/docs/reference/nullpol/analysis/antenna_patterns/index.md +0 -0
  52. {nullpol-0.2.2 → nullpol-0.2.3}/docs/reference/nullpol/analysis/clustering/index.md +0 -0
  53. {nullpol-0.2.2 → nullpol-0.2.3}/docs/reference/nullpol/analysis/index.md +0 -0
  54. {nullpol-0.2.2 → nullpol-0.2.3}/docs/reference/nullpol/analysis/likelihood/index.md +0 -0
  55. {nullpol-0.2.2 → nullpol-0.2.3}/docs/reference/nullpol/analysis/null_stream/index.md +0 -0
  56. {nullpol-0.2.2 → nullpol-0.2.3}/docs/reference/nullpol/analysis/prior/index.md +0 -0
  57. {nullpol-0.2.2 → nullpol-0.2.3}/docs/reference/nullpol/analysis/tf_transforms/index.md +0 -0
  58. {nullpol-0.2.2 → nullpol-0.2.3}/docs/reference/nullpol/cli/index.md +0 -0
  59. {nullpol-0.2.2 → nullpol-0.2.3}/docs/reference/nullpol/index.md +0 -0
  60. {nullpol-0.2.2 → nullpol-0.2.3}/docs/reference/nullpol/integrations/asimov/index.md +0 -0
  61. {nullpol-0.2.2 → nullpol-0.2.3}/docs/reference/nullpol/integrations/htcondor/index.md +0 -0
  62. {nullpol-0.2.2 → nullpol-0.2.3}/docs/reference/nullpol/integrations/index.md +0 -0
  63. {nullpol-0.2.2 → nullpol-0.2.3}/docs/reference/nullpol/simulation/index.md +0 -0
  64. {nullpol-0.2.2 → nullpol-0.2.3}/docs/reference/nullpol/utils/index.md +0 -0
  65. {nullpol-0.2.2 → nullpol-0.2.3}/docs/reference/nullpol/version.md +0 -0
  66. {nullpol-0.2.2 → nullpol-0.2.3}/examples/injection/scalar_tensor/generate_injection_file.sh +0 -0
  67. {nullpol-0.2.2 → nullpol-0.2.3}/examples/injection/scalar_tensor/injections.json +0 -0
  68. {nullpol-0.2.2 → nullpol-0.2.3}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/data/scalar_tensor_injection_data0_0_generation_data_dump.pickle +0 -0
  69. {nullpol-0.2.2 → nullpol-0.2.3}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/log_data_analysis/scalar_tensor_injection_data0_0_analysis_H1L1V1.err +0 -0
  70. {nullpol-0.2.2 → nullpol-0.2.3}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/log_data_analysis/scalar_tensor_injection_data0_0_analysis_H1L1V1.out +0 -0
  71. {nullpol-0.2.2 → nullpol-0.2.3}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/log_data_analysis/scalar_tensor_injection_data0_0_analysis_H1L1V1_final_result.err +0 -0
  72. {nullpol-0.2.2 → nullpol-0.2.3}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/log_data_analysis/scalar_tensor_injection_data0_0_analysis_H1L1V1_final_result.out +0 -0
  73. {nullpol-0.2.2 → nullpol-0.2.3}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/log_data_generation/scalar_tensor_injection_data0_0_generation.err +0 -0
  74. {nullpol-0.2.2 → nullpol-0.2.3}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/log_data_generation/scalar_tensor_injection_data0_0_generation.out +0 -0
  75. {nullpol-0.2.2 → nullpol-0.2.3}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/result/scalar_tensor_injection_data0_0_analysis_H1L1V1_dynesty.pickle +0 -0
  76. {nullpol-0.2.2 → nullpol-0.2.3}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/result/scalar_tensor_injection_data0_0_analysis_H1L1V1_resume.pickle +0 -0
  77. {nullpol-0.2.2 → nullpol-0.2.3}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/results_page/overview.html +0 -0
  78. {nullpol-0.2.2 → nullpol-0.2.3}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/scalar_tensor_injection_config_complete.ini +0 -0
  79. {nullpol-0.2.2 → nullpol-0.2.3}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/submit/bash_scalar_tensor_injection.sh +0 -0
  80. {nullpol-0.2.2 → nullpol-0.2.3}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/submit/dag_scalar_tensor_injection.submit +0 -0
  81. {nullpol-0.2.2 → nullpol-0.2.3}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/submit/dag_scalar_tensor_injection.submit.condor.sub +0 -0
  82. {nullpol-0.2.2 → nullpol-0.2.3}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/submit/dag_scalar_tensor_injection.submit.dagman.out +0 -0
  83. {nullpol-0.2.2 → nullpol-0.2.3}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/submit/dag_scalar_tensor_injection.submit.lib.err +0 -0
  84. {nullpol-0.2.2 → nullpol-0.2.3}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/submit/dag_scalar_tensor_injection.submit.lib.out +0 -0
  85. {nullpol-0.2.2 → nullpol-0.2.3}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/submit/dag_scalar_tensor_injection.submit.metrics +0 -0
  86. {nullpol-0.2.2 → nullpol-0.2.3}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/submit/scalar_tensor_injection_data0_0_analysis_H1L1V1.submit +0 -0
  87. {nullpol-0.2.2 → nullpol-0.2.3}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/submit/scalar_tensor_injection_data0_0_analysis_H1L1V1_final_result.submit +0 -0
  88. {nullpol-0.2.2 → nullpol-0.2.3}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/submit/scalar_tensor_injection_data0_0_generation.submit +0 -0
  89. {nullpol-0.2.2 → nullpol-0.2.3}/examples/injection/scalar_tensor/prior.prior +0 -0
  90. {nullpol-0.2.2 → nullpol-0.2.3}/examples/injection/scalar_tensor/scalar_tensor_injection.ini +0 -0
  91. {nullpol-0.2.2 → nullpol-0.2.3}/examples/injection/scalar_tensor/scalar_tensor_injection.prior +0 -0
  92. {nullpol-0.2.2 → nullpol-0.2.3}/examples/injection/scalar_tensor_fractional_projection/generate_injection_file.sh +0 -0
  93. {nullpol-0.2.2 → nullpol-0.2.3}/examples/injection/scalar_tensor_fractional_projection/scalar_tensor_injection.ini +0 -0
  94. {nullpol-0.2.2 → nullpol-0.2.3}/examples/injection/scalar_tensor_fractional_projection/scalar_tensor_injection.prior +0 -0
  95. {nullpol-0.2.2 → nullpol-0.2.3}/examples/injection/scalar_tensor_gaussian/generate_injection_file.sh +0 -0
  96. {nullpol-0.2.2 → nullpol-0.2.3}/examples/injection/scalar_tensor_gaussian/scalar_tensor_injection.ini +0 -0
  97. {nullpol-0.2.2 → nullpol-0.2.3}/examples/injection/scalar_tensor_gaussian/scalar_tensor_injection.prior +0 -0
  98. {nullpol-0.2.2 → nullpol-0.2.3}/examples/injection/scalar_tensor_h_marginalized/generate_injection_file.sh +0 -0
  99. {nullpol-0.2.2 → nullpol-0.2.3}/examples/injection/scalar_tensor_h_marginalized/scalar_tensor_injection.ini +0 -0
  100. {nullpol-0.2.2 → nullpol-0.2.3}/examples/injection/scalar_tensor_h_marginalized/scalar_tensor_injection.prior +0 -0
  101. {nullpol-0.2.2 → nullpol-0.2.3}/examples/injection/scalar_tensor_weighted_gaussian/generate_injection_file.sh +0 -0
  102. {nullpol-0.2.2 → nullpol-0.2.3}/examples/injection/scalar_tensor_weighted_gaussian/scalar_tensor_injection.ini +0 -0
  103. {nullpol-0.2.2 → nullpol-0.2.3}/examples/injection/scalar_tensor_weighted_gaussian/scalar_tensor_injection.prior +0 -0
  104. {nullpol-0.2.2 → nullpol-0.2.3}/pyproject.toml +0 -0
  105. {nullpol-0.2.2 → nullpol-0.2.3}/renovate.json +0 -0
  106. {nullpol-0.2.2 → nullpol-0.2.3}/scripts/README.md +0 -0
  107. {nullpol-0.2.2 → nullpol-0.2.3}/scripts/auto_generate_init.py +0 -0
  108. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/.gitignore +0 -0
  109. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/__init__.py +0 -0
  110. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/analysis/__init__.py +0 -0
  111. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/analysis/antenna_patterns/__init__.py +0 -0
  112. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/analysis/antenna_patterns/base.py +0 -0
  113. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/analysis/antenna_patterns/conditioning.py +0 -0
  114. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/analysis/antenna_patterns/encoding.py +0 -0
  115. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/analysis/antenna_patterns/processor.py +0 -0
  116. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/analysis/clustering/__init__.py +0 -0
  117. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/analysis/clustering/algorithm.py +0 -0
  118. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/analysis/clustering/application.py +0 -0
  119. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/analysis/clustering/coherent_sky_scan.py +0 -0
  120. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/analysis/clustering/plotting.py +0 -0
  121. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/analysis/clustering/threshold_filter.py +0 -0
  122. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/analysis/data_context.py +0 -0
  123. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/analysis/likelihood/__init__.py +0 -0
  124. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/analysis/likelihood/chi2_tf_likelihood.py +0 -0
  125. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/analysis/likelihood/time_frequency_likelihood.py +0 -0
  126. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/analysis/null_stream/__init__.py +0 -0
  127. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/analysis/null_stream/calculator.py +0 -0
  128. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/analysis/null_stream/projections.py +0 -0
  129. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/analysis/prior/__init__.py +0 -0
  130. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/analysis/prior/default.py +0 -0
  131. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/analysis/prior/prior_files/polarization.prior +0 -0
  132. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/analysis/result.py +0 -0
  133. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/analysis/tf_transforms/__init__.py +0 -0
  134. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/analysis/tf_transforms/inverse_wavelet_freq.py +0 -0
  135. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/analysis/tf_transforms/inverse_wavelet_time.py +0 -0
  136. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/analysis/tf_transforms/stft.py +0 -0
  137. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/analysis/tf_transforms/utils.py +0 -0
  138. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/analysis/tf_transforms/wavelet_freq.py +0 -0
  139. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/analysis/tf_transforms/wavelet_time.py +0 -0
  140. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/analysis/tf_transforms/wavelet_transforms.py +0 -0
  141. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/cli/__init__.py +0 -0
  142. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/cli/create_injection.py +0 -0
  143. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/cli/create_time_frequency_filter_from_sample.py +0 -0
  144. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/cli/data_generation.py +0 -0
  145. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/cli/get_asimov_yaml.py +0 -0
  146. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/cli/input.py +0 -0
  147. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/cli/main.py +0 -0
  148. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/cli/parser.py +0 -0
  149. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/cli/templates/__init__.py +0 -0
  150. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/cli/templates/config.ini +0 -0
  151. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/cli/templates/default_config_create_injection.ini +0 -0
  152. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/cli/templates/default_config_create_time_frequency_filter_from_sample.ini +0 -0
  153. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/cli/templates/example_signal_parameters_create_injection.json +0 -0
  154. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/integrations/__init__.py +0 -0
  155. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/integrations/asimov/__init__.py +0 -0
  156. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/integrations/asimov/asimov.py +0 -0
  157. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/integrations/asimov/pesummary.py +0 -0
  158. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/integrations/asimov/templates/__init__.py +0 -0
  159. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/integrations/asimov/templates/analysis_defaults.yaml +0 -0
  160. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/integrations/asimov/templates/nullpol.ini +0 -0
  161. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/integrations/asimov/templates/nullpol_analysis.yaml +0 -0
  162. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/integrations/htcondor/__init__.py +0 -0
  163. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/integrations/htcondor/analysis_node.py +0 -0
  164. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/integrations/htcondor/generation_node.py +0 -0
  165. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/integrations/htcondor/nullpol_pipe_dag_creator.py +0 -0
  166. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/simulation/__init__.py +0 -0
  167. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/simulation/injection.py +0 -0
  168. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/simulation/source.py +0 -0
  169. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/utils/__init__.py +0 -0
  170. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/utils/error.py +0 -0
  171. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/utils/filesystem.py +0 -0
  172. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/utils/imports.py +0 -0
  173. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/utils/json_utils.py +0 -0
  174. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/utils/log.py +0 -0
  175. {nullpol-0.2.2 → nullpol-0.2.3}/src/nullpol/version.py +0 -0
  176. {nullpol-0.2.2 → nullpol-0.2.3}/tests/.gitignore +0 -0
  177. {nullpol-0.2.2 → nullpol-0.2.3}/tests/analysis/__init__.py +0 -0
  178. {nullpol-0.2.2 → nullpol-0.2.3}/tests/analysis/antenna_patterns/__init__.py +0 -0
  179. {nullpol-0.2.2 → nullpol-0.2.3}/tests/analysis/antenna_patterns/test_base.py +0 -0
  180. {nullpol-0.2.2 → nullpol-0.2.3}/tests/analysis/antenna_patterns/test_conditioning.py +0 -0
  181. {nullpol-0.2.2 → nullpol-0.2.3}/tests/analysis/antenna_patterns/test_encoding.py +0 -0
  182. {nullpol-0.2.2 → nullpol-0.2.3}/tests/analysis/antenna_patterns/test_processor.py +0 -0
  183. {nullpol-0.2.2 → nullpol-0.2.3}/tests/analysis/clustering/__init__.py +0 -0
  184. {nullpol-0.2.2 → nullpol-0.2.3}/tests/analysis/clustering/test_algorithm.py +0 -0
  185. {nullpol-0.2.2 → nullpol-0.2.3}/tests/analysis/clustering/test_application.py +0 -0
  186. {nullpol-0.2.2 → nullpol-0.2.3}/tests/analysis/clustering/test_plotting.py +0 -0
  187. {nullpol-0.2.2 → nullpol-0.2.3}/tests/analysis/clustering/test_threshold_filter.py +0 -0
  188. {nullpol-0.2.2 → nullpol-0.2.3}/tests/analysis/fixtures/injections.json +0 -0
  189. {nullpol-0.2.2 → nullpol-0.2.3}/tests/analysis/likelihood/test_chi2_tf_likelihood.py +0 -0
  190. {nullpol-0.2.2 → nullpol-0.2.3}/tests/analysis/likelihood/test_time_frequency_likelihood.py +0 -0
  191. {nullpol-0.2.2 → nullpol-0.2.3}/tests/analysis/null_stream/__init__.py +0 -0
  192. {nullpol-0.2.2 → nullpol-0.2.3}/tests/analysis/null_stream/test_calculator.py +0 -0
  193. {nullpol-0.2.2 → nullpol-0.2.3}/tests/analysis/null_stream/test_projections.py +0 -0
  194. {nullpol-0.2.2 → nullpol-0.2.3}/tests/analysis/prior/__init__.py +0 -0
  195. {nullpol-0.2.2 → nullpol-0.2.3}/tests/analysis/prior/test_default.py +0 -0
  196. {nullpol-0.2.2 → nullpol-0.2.3}/tests/analysis/test_result.py +0 -0
  197. {nullpol-0.2.2 → nullpol-0.2.3}/tests/analysis/tf_transforms/__init__.py +0 -0
  198. {nullpol-0.2.2 → nullpol-0.2.3}/tests/analysis/tf_transforms/test_inverse_wavelet_freq.py +0 -0
  199. {nullpol-0.2.2 → nullpol-0.2.3}/tests/analysis/tf_transforms/test_inverse_wavelet_time.py +0 -0
  200. {nullpol-0.2.2 → nullpol-0.2.3}/tests/analysis/tf_transforms/test_stft.py +0 -0
  201. {nullpol-0.2.2 → nullpol-0.2.3}/tests/analysis/tf_transforms/test_utils.py +0 -0
  202. {nullpol-0.2.2 → nullpol-0.2.3}/tests/analysis/tf_transforms/test_wavelet_freq.py +0 -0
  203. {nullpol-0.2.2 → nullpol-0.2.3}/tests/analysis/tf_transforms/test_wavelet_time.py +0 -0
  204. {nullpol-0.2.2 → nullpol-0.2.3}/tests/analysis/tf_transforms/test_wavelet_transforms.py +0 -0
  205. {nullpol-0.2.2 → nullpol-0.2.3}/tests/cli/__init__.py +0 -0
  206. {nullpol-0.2.2 → nullpol-0.2.3}/tests/cli/fixtures/mock_psd.txt +0 -0
  207. {nullpol-0.2.2 → nullpol-0.2.3}/tests/cli/test_create_injection.py +0 -0
  208. {nullpol-0.2.2 → nullpol-0.2.3}/tests/cli/test_create_time_frequency_filter.py +0 -0
  209. {nullpol-0.2.2 → nullpol-0.2.3}/tests/integrations/__init__.py +0 -0
  210. {nullpol-0.2.2 → nullpol-0.2.3}/tests/simulation/__init__.py +0 -0
  211. {nullpol-0.2.2 → nullpol-0.2.3}/tests/simulation/test_injection.py +0 -0
  212. {nullpol-0.2.2 → nullpol-0.2.3}/tests/simulation/test_source.py +0 -0
  213. {nullpol-0.2.2 → nullpol-0.2.3}/tests/test_import.py +0 -0
  214. {nullpol-0.2.2 → nullpol-0.2.3}/tests/test_version.py +0 -0
  215. {nullpol-0.2.2 → nullpol-0.2.3}/tests/utils/__init__.py +0 -0
  216. {nullpol-0.2.2 → nullpol-0.2.3}/tests/utils/test_error.py +0 -0
  217. {nullpol-0.2.2 → nullpol-0.2.3}/tests/utils/test_filesystem.py +0 -0
  218. {nullpol-0.2.2 → nullpol-0.2.3}/tests/utils/test_imports.py +0 -0
  219. {nullpol-0.2.2 → nullpol-0.2.3}/tests/utils/test_json_utils.py +0 -0
  220. {nullpol-0.2.2 → nullpol-0.2.3}/tests/utils/test_log.py +0 -0
  221. {nullpol-0.2.2 → nullpol-0.2.3}/zensical.toml +0 -0
@@ -0,0 +1,66 @@
1
+ name: CI
2
+ on:
3
+ push:
4
+ branches: [main]
5
+ pull_request:
6
+ branches: [main]
7
+ workflow_dispatch:
8
+ workflow_call:
9
+
10
+ jobs:
11
+ test:
12
+ strategy:
13
+ matrix:
14
+ python-version: ['3.12', '3.13', '3.14']
15
+ os: [ubuntu-latest, macos-latest]
16
+ runs-on: ${{ matrix.os }}
17
+ steps:
18
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
19
+ with:
20
+ persist-credentials: false
21
+
22
+ - name: Install uv
23
+ uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
24
+ with:
25
+ python-version: ${{ matrix.python-version }}
26
+ enable-cache: 'true'
27
+
28
+ - name: Install dependencies
29
+ run: uv sync --group dev --frozen
30
+
31
+ - name: Run tests with pytest
32
+ run: uv run --frozen pytest
33
+
34
+ - name: Upload coverage to Codecov
35
+ if: ${{ !cancelled() }}
36
+ uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5
37
+ with:
38
+ token: ${{ secrets.CODECOV_TOKEN }}
39
+
40
+ integration:
41
+ # Runs the @pytest.mark.integration suite once (not in the matrix) to
42
+ # keep the fast unit-test matrix cheap while still gating on the
43
+ # heavier integration tests. Installs the optional `asimov` extra so
44
+ # real asimov/cbcflow paths can be exercised now and in the future.
45
+ needs: test
46
+ runs-on: ubuntu-latest
47
+ steps:
48
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
49
+ with:
50
+ persist-credentials: false
51
+
52
+ - name: Install uv
53
+ uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
54
+ with:
55
+ python-version: '3.13'
56
+ enable-cache: 'true'
57
+
58
+ - name: Install dependencies (with asimov extra)
59
+ run: uv sync --group dev --extra asimov --frozen
60
+
61
+ - name: Run integration tests
62
+ # Clear the default addopts (which selects `-m 'not integration'`)
63
+ # and explicitly select the integration marker instead.
64
+ run:
65
+ uv run --frozen pytest -o addopts="" -m integration --cov src
66
+ --cov-report=term-missing
@@ -18,12 +18,12 @@ jobs:
18
18
  - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
19
19
 
20
20
  - name: Initialize CodeQL
21
- uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
21
+ uses: github/codeql-action/init@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4
22
22
  with:
23
23
  languages: python
24
24
 
25
25
  - name: Autobuild
26
- uses: github/codeql-action/autobuild@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
26
+ uses: github/codeql-action/autobuild@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4
27
27
 
28
28
  - name: Perform CodeQL Analysis
29
- uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
29
+ uses: github/codeql-action/analyze@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4
@@ -20,7 +20,7 @@ jobs:
20
20
 
21
21
  - name: Check for new commits
22
22
  id: check_commits
23
- uses: isaac-cf-wong/detect-git-changes-action@484da6639676d11009fc70cf1a09b73d31737fe9 # v0.0.13
23
+ uses: isaac-cf-wong/detect-git-changes-action@ecc6bf915a7987eb27f70d560c8067a69b943cb5 # v0.0.14
24
24
  with:
25
25
  use-latest-tag: true
26
26
 
@@ -29,18 +29,18 @@ repos:
29
29
  - id: taplo-format
30
30
 
31
31
  - repo: https://github.com/crate-ci/typos
32
- rev: v1.47.2
32
+ rev: v1.48.0
33
33
  hooks:
34
34
  - id: typos
35
35
  args: []
36
36
 
37
37
  - repo: https://github.com/rbubley/mirrors-prettier
38
- rev: v3.9.1
38
+ rev: v3.9.4
39
39
  hooks:
40
40
  - id: prettier
41
41
 
42
42
  - repo: https://github.com/DavidAnson/markdownlint-cli2
43
- rev: v0.22.1
43
+ rev: v0.23.0
44
44
  hooks:
45
45
  - id: markdownlint-cli2
46
46
  args:
@@ -48,7 +48,7 @@ repos:
48
48
  - .markdownlint.yaml
49
49
 
50
50
  - repo: https://github.com/astral-sh/uv-pre-commit
51
- rev: 0.11.25
51
+ rev: 0.11.26
52
52
  hooks:
53
53
  # Update the uv lockfile
54
54
  - id: uv-lock
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nullpol
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: A package to perform model-independent polarization test of gravitational-wave signals.
5
5
  Project-URL: Documentation, https://isaac-cf-wong.github.io/nullpol/
6
6
  Project-URL: Source, https://github.com/isaac-cf-wong/nullpol
@@ -147,7 +147,7 @@ class DataAnalysisInput(BInput, Input):
147
147
  raise NullpolError(f"Unsupported polarization-basis input: {modes}")
148
148
 
149
149
  def _validate_polarization_model_setting(self):
150
- supported_modes = ["b", "c", "b", "l", "x", "y"]
150
+ supported_modes = ["b", "c", "p", "l", "x", "y"]
151
151
  # Check whether the modes are supported.
152
152
  for mode in self.polarization_modes:
153
153
  if mode not in supported_modes:
@@ -166,12 +166,12 @@ class Collector:
166
166
  analysis_output["Notes"] = []
167
167
 
168
168
  if analysis.comment is not None and (
169
- corresponding_analysis is None or analysis.comment not in corresponding_analysis["Notes"]
169
+ corresponding_analysis is None or analysis.comment not in corresponding_analysis.get("Notes", [])
170
170
  ):
171
171
  # We only want to add the comment to the notes if it doesn't already exist
172
172
  analysis_output["Notes"].append(analysis.comment)
173
173
 
174
- if analysis.review.status:
174
+ if analysis.review is not None and analysis.review.status:
175
175
  if analysis.review.status.lower() == "approved":
176
176
  analysis_output["ReviewStatus"] = "pass"
177
177
  elif analysis.review.status.lower() == "rejected":
@@ -181,7 +181,8 @@ class Collector:
181
181
  messages = sorted(analysis.review.messages, key=lambda k: k.timestamp)
182
182
  if len(messages) > 0 and (
183
183
  corresponding_analysis is None
184
- or f"{messages[0].timestamp:%Y-%m-%d}: {messages[0].message}" in corresponding_analysis["Notes"]
184
+ or f"{messages[0].timestamp:%Y-%m-%d}: {messages[0].message}"
185
+ not in corresponding_analysis.get("Notes", [])
185
186
  ):
186
187
  analysis_output["Notes"].append(f"{messages[0].timestamp:%Y-%m-%d}: {messages[0].message}")
187
188
 
@@ -285,6 +286,69 @@ class Collector:
285
286
  return {}
286
287
 
287
288
 
289
+ def validate_gr_pe_result(result: dict) -> bool:
290
+ """Validate whether a PE result is suitable as the basis for a TGR analysis.
291
+
292
+ The result must be complete, non-deprecated, and not an online or
293
+ exploratory run.
294
+
295
+ Parameters
296
+ ----------
297
+ result : dict
298
+ Metadata for the result to be validated.
299
+
300
+ Returns:
301
+ -------
302
+ bool
303
+ Validity of the result.
304
+ """
305
+ uid = result.get("UID", "").lower()
306
+ if not uid or uid == "online" or uid.startswith("exp") or uid.startswith("detchar"):
307
+ return False
308
+ if result.get("RunStatus") != "complete":
309
+ return False
310
+ if result.get("ReviewStatus") != "pass":
311
+ return False
312
+ if result.get("Deprecated", False):
313
+ return False
314
+ # Ensure required files are present for actual PE results
315
+ if "ResultFile" not in result or "ConfigFile" not in result:
316
+ return False
317
+ return "PESummaryResultFile" in result
318
+
319
+
320
+ def identify_basis_production(metadata) -> dict:
321
+ """Identify the PE result that will be used as the basis for TGR analyses.
322
+
323
+ The IllustrativeResult is tried first. If it passes full validation
324
+ (complete, non-deprecated, not online/exp*/detchar*, and has all required
325
+ files) it is returned. If it is not available or does not pass, the function
326
+ falls back to the first result that passes the full validation. Returns an
327
+ empty dict if no valid result is found.
328
+
329
+ Parameters
330
+ ----------
331
+ metadata :
332
+ CBCFlow superevent metadata.
333
+
334
+ Returns:
335
+ -------
336
+ dict
337
+ Result metadata for the basis production, or empty dict if none found.
338
+ """
339
+ illustrative_result_name = metadata.data["ParameterEstimation"].get("IllustrativeResult", None)
340
+ if illustrative_result_name is not None:
341
+ for result in metadata.data["ParameterEstimation"].get("Results", []):
342
+ if result["UID"] == illustrative_result_name and validate_gr_pe_result(result):
343
+ return result
344
+
345
+ for result in metadata.data["ParameterEstimation"]["Results"]:
346
+ if validate_gr_pe_result(result):
347
+ return result
348
+
349
+ return {}
350
+
351
+
288
352
  # Functionality for information which flows from cbcflow into Asimov
289
353
  # pylint: disable=too-few-public-methods
290
354
  class Applicator:
@@ -369,6 +433,8 @@ class Applicator:
369
433
  ligo["preferred event"] = event["UID"]
370
434
  ligo["false alarm rate"] = event["FAR"]
371
435
  event_time = event["GPSTime"]
436
+ if event_time is None:
437
+ raise ValueError(f"No preferred GraceDB event found for {sid}. Cannot determine event time.")
372
438
  ligo["sname"] = sid
373
439
 
374
440
  # also add the sampling rate to the output
@@ -387,22 +453,22 @@ class Applicator:
387
453
  "event time": event_time,
388
454
  }
389
455
 
390
- metadata_pe_results = metadata.data["ParameterEstimation"]["Results"]
391
- illustrative_prod = metadata.data["ParameterEstimation"]["IllustrativeResult"]
392
- gr_pe = {"available": False}
393
- for result in metadata_pe_results:
394
- if result["UID"] == illustrative_prod and result["RunStatus"] == "complete":
395
- gr_pe = {
396
- "available": True,
397
- "UID GR PE": result["UID"],
398
- "result file path": "/" + result["ResultFile"]["Path"].split(":/")[-1],
399
- "config file path": "/" + result["ConfigFile"]["Path"].split(":/")[-1],
400
- "pesummary result path": "/" + result["PESummaryResultFile"]["Path"].split(":/")[-1],
401
- }
402
- if "WaveformApproximant" in result:
403
- quality["waveform approximant"] = result["WaveformApproximant"]
404
- if gr_pe["available"] is False:
405
- raise AttributeError("IllustrativeResult not in the library or the PE run is incomplete.")
456
+ basis_result = identify_basis_production(metadata)
457
+ if not basis_result:
458
+ raise ValueError(
459
+ "No valid GR PE result found in the library. The IllustrativeResult "
460
+ "(if set) and all other results either have incomplete run status, "
461
+ "are deprecated, or are excluded by UID."
462
+ )
463
+ gr_pe = {
464
+ "available": True,
465
+ "UID GR PE": basis_result["UID"],
466
+ "result file path": "/" + basis_result["ResultFile"]["Path"].split(":/")[-1],
467
+ "config file path": "/" + basis_result["ConfigFile"]["Path"].split(":/")[-1],
468
+ "pesummary result path": "/" + basis_result["PESummaryResultFile"]["Path"].split(":/")[-1],
469
+ }
470
+ if "WaveformApproximant" in basis_result:
471
+ quality["waveform approximant"] = basis_result["WaveformApproximant"]
406
472
 
407
473
  # now, we need to read in the information from the config file
408
474
  # we need the reference frequency, the psd dict and the calibration dict
@@ -410,8 +476,10 @@ class Applicator:
410
476
  bilby_config = bilby_config_to_asimov(config_file)
411
477
 
412
478
  # read in the psd information if present
413
- if "psds" in bilby_config:
479
+ if bilby_config.get("psds") is not None:
414
480
  gr_pe["psds"] = bilby_config.pop("psds")
481
+ elif "psds" in bilby_config:
482
+ bilby_config.pop("psds")
415
483
  output["gr pe info"] = gr_pe.copy()
416
484
  output["gr pe info"]["approximant"] = bilby_config["waveform"]["approximant"]
417
485
  # config file and cbcflow can disagree about which data to use (for example, preferred frame was updated)
@@ -37,8 +37,11 @@ def read_bilby_ini_file(file: str) -> dict:
37
37
  def _convert_string_to_dict(string):
38
38
  try:
39
39
  return bilby_pipe.utils.convert_string_to_dict(string)
40
- except (bilby_pipe.utils.BilbyPipeError, AttributeError):
41
- return string
40
+ except (bilby_pipe.utils.BilbyPipeError, AttributeError, TypeError):
41
+ try:
42
+ return literal_eval(string)
43
+ except (ValueError, SyntaxError):
44
+ return string
42
45
 
43
46
 
44
47
  def fill_in_pol_specific_metadata(analysis, corresponding_analysis): # pylint: disable=unused-argument
@@ -479,7 +479,7 @@ class TestTimeFrequencyDataContext:
479
479
  # Create filter with wrong shape
480
480
  wrong_filter = np.random.rand(10, 20) # Wrong dimensions
481
481
 
482
- with pytest.raises(AssertionError):
482
+ with pytest.raises(ValueError, match="does not match the time frequency filter"):
483
483
  TimeFrequencyDataContext(
484
484
  interferometers=ifos,
485
485
  wavelet_frequency_resolution=2.0,
@@ -0,0 +1,79 @@
1
+ """Unit tests for :meth:`DataAnalysisInput._validate_polarization_model_setting`.
2
+
3
+ This guards the fix in commit ``74b4fc3`` where the supported-modes list
4
+ contained a duplicate ``"b"`` and was missing ``"p"`` (plus), causing a valid
5
+ ``"p"`` mode to be rejected in both ``polarization-modes`` and
6
+ ``polarization-basis`` validation.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import pytest
12
+
13
+ from nullpol.cli.data_analysis import DataAnalysisInput
14
+ from nullpol.utils import NullpolError
15
+
16
+ SUPPORTED = ["b", "c", "p", "l", "x", "y"]
17
+
18
+
19
+ def _make_input(modes, basis):
20
+ """Build a DataAnalysisInput bypassing the heavy __init__.
21
+
22
+ The validation method only reads the ``polarization_modes`` and
23
+ ``polarization_basis`` properties, which are backed by the private
24
+ ``_polarization_modes`` / ``_polarization_basis`` attributes.
25
+ """
26
+ obj = object.__new__(DataAnalysisInput)
27
+ obj._polarization_modes = list(modes)
28
+ obj._polarization_basis = list(basis)
29
+ return obj
30
+
31
+
32
+ class TestValidatePolarizationModelSetting:
33
+ """Tests for :meth:`_validate_polarization_model_setting`."""
34
+
35
+ @pytest.mark.parametrize("mode", SUPPORTED)
36
+ def test_each_supported_mode_accepted_alone(self, mode):
37
+ """Every one of the six canonical modes is accepted in polarization-modes."""
38
+ _make_input([mode], [mode])._validate_polarization_model_setting()
39
+
40
+ def test_all_six_modes_accepted_together(self):
41
+ """All six modes (b, c, p, l, x, y) pass when used together."""
42
+ _make_input(SUPPORTED, SUPPORTED)._validate_polarization_model_setting()
43
+
44
+ def test_plus_mode_accepted(self):
45
+ """Regression: ``"p"`` (plus) must be accepted -- it was rejected before the fix."""
46
+ _make_input(["p", "c"], ["p"])._validate_polarization_model_setting()
47
+
48
+ def test_plus_mode_rejected_before_fix_list(self):
49
+ """Sanity check: the fixed list no longer contains a duplicate ``b``."""
50
+ # If the old buggy list ["b","c","b","l","x","y"] were restored, "p" would fail.
51
+ assert SUPPORTED == ["b", "c", "p", "l", "x", "y"]
52
+ assert SUPPORTED.count("b") == 1
53
+ assert "p" in SUPPORTED
54
+
55
+ @pytest.mark.parametrize("bad_mode", ["z", "a", "B", "pc", "", "plus"])
56
+ def test_invalid_mode_in_modes_rejected(self, bad_mode):
57
+ """An unsupported mode in ``polarization-modes`` raises NullpolError."""
58
+ with pytest.raises(NullpolError, match="polarization-modes"):
59
+ _make_input([bad_mode], [bad_mode])._validate_polarization_model_setting()
60
+
61
+ @pytest.mark.parametrize("bad_mode", ["z", "a", "P"])
62
+ def test_invalid_mode_in_basis_rejected(self, bad_mode):
63
+ """An unsupported mode in ``polarization-basis`` raises NullpolError."""
64
+ with pytest.raises(NullpolError, match="polarization-basis"):
65
+ _make_input(["p", "c"], [bad_mode])._validate_polarization_model_setting()
66
+
67
+ def test_basis_not_subset_of_modes_rejected(self):
68
+ """A basis mode that is supported but not in ``polarization-modes`` is rejected."""
69
+ # "b" is a supported mode, but it is not in the modes list here.
70
+ with pytest.raises(NullpolError, match="Basis mode"):
71
+ _make_input(["p", "c"], ["b"])._validate_polarization_model_setting()
72
+
73
+ def test_empty_basis_accepted(self):
74
+ """An empty basis is valid (all modes are derived)."""
75
+ _make_input(["p", "c"], [])._validate_polarization_model_setting()
76
+
77
+ def test_basis_subset_of_modes_accepted(self):
78
+ """A basis that is a strict subset of the modes is valid."""
79
+ _make_input(["p", "c", "b", "l"], ["p", "c"])._validate_polarization_model_setting()
@@ -0,0 +1 @@
1
+ """Asimov integration tests."""
@@ -0,0 +1,78 @@
1
+ """Shared fixtures for the asimov integration tests.
2
+
3
+ The ``asimov`` and ``cbcflow`` packages are *optional* dependencies (see the
4
+ ``asimov`` extra in ``pyproject.toml``) and are not installed in the default
5
+ test environment. Importing :mod:`nullpol.integrations.asimov` normally
6
+ triggers ``asimov.py`` / ``pesummary.py`` which require those packages (and
7
+ ``htcondor``) at import time.
8
+
9
+ To allow the pure-Python logic in :mod:`nullpol.integrations.asimov.tgrflow`
10
+ and :mod:`nullpol.integrations.asimov.utility` to be exercised without the
11
+ optional deps, these fixtures:
12
+
13
+ 1. Inject lightweight :class:`~unittest.mock.MagicMock` stub modules for
14
+ ``asimov``, ``asimov.event`` and ``cbcflow`` into :data:`sys.modules`.
15
+ 2. Replace the real ``nullpol.integrations.asimov`` package object with an
16
+ empty package module whose ``__path__`` points at the real source directory,
17
+ so the submodules ``tgrflow`` and ``utility`` can be loaded by the import
18
+ machinery without running the package ``__init__`` (and therefore without
19
+ importing the real ``asimov`` / ``htcondor``).
20
+
21
+ All stubs are scoped to the test via :class:`pytest.MonkeyPatch` and are
22
+ removed again afterwards.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import os
28
+ import sys
29
+ import types
30
+ from unittest import mock
31
+
32
+ import pytest
33
+
34
+ _STUB_MODULE_PATHS = ("asimov", "asimov.event", "cbcflow")
35
+
36
+
37
+ def _make_stub(name: str) -> mock.MagicMock:
38
+ """Build a stub module that auto-creates any accessed attribute."""
39
+ mod = mock.MagicMock(name=name)
40
+ mod.__name__ = name
41
+ mod.__path__ = []
42
+ return mod
43
+
44
+
45
+ @pytest.fixture
46
+ def asimov_libs(monkeypatch):
47
+ """Import the asimov integration modules with optional deps stubbed out.
48
+
49
+ Returns a :class:`types.SimpleNamespace` exposing the loaded ``tgrflow``
50
+ and ``utility`` modules together with the most commonly used symbols.
51
+ """
52
+ for path in _STUB_MODULE_PATHS:
53
+ monkeypatch.setitem(sys.modules, path, _make_stub(path))
54
+
55
+ # Replace the real asimov package __init__ with an empty package module so
56
+ # that importing its submodules does not trigger the heavy asimov.py /
57
+ # pesummary.py imports (which require the real asimov + htcondor packages).
58
+ import nullpol
59
+
60
+ asimov_dir = os.path.join(os.path.dirname(nullpol.__file__), "integrations", "asimov")
61
+ fake_pkg = types.ModuleType("nullpol.integrations.asimov")
62
+ fake_pkg.__path__ = [asimov_dir]
63
+ fake_pkg.__package__ = "nullpol.integrations.asimov"
64
+ monkeypatch.setitem(sys.modules, "nullpol.integrations.asimov", fake_pkg)
65
+
66
+ from nullpol.integrations.asimov import tgrflow, utility
67
+
68
+ return types.SimpleNamespace(
69
+ tgrflow=tgrflow,
70
+ utility=utility,
71
+ validate_gr_pe_result=tgrflow.validate_gr_pe_result,
72
+ identify_basis_production=tgrflow.identify_basis_production,
73
+ Applicator=tgrflow.Applicator,
74
+ Collector=tgrflow.Collector,
75
+ convert_string_to_dict=utility._convert_string_to_dict,
76
+ bilby_config_to_asimov=utility.bilby_config_to_asimov,
77
+ deep_update=utility.deep_update,
78
+ )