specmod 0.6.0__tar.gz → 0.7.0__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 (275) hide show
  1. {specmod-0.6.0 → specmod-0.7.0}/.github/workflows/build.yml +2 -0
  2. {specmod-0.6.0 → specmod-0.7.0}/.github/workflows/docs.yml +2 -0
  3. {specmod-0.6.0 → specmod-0.7.0}/.github/workflows/release.yml +2 -0
  4. {specmod-0.6.0 → specmod-0.7.0}/.github/workflows/test.yml +6 -0
  5. specmod-0.7.0/.release-please-manifest.json +3 -0
  6. {specmod-0.6.0 → specmod-0.7.0}/CHANGELOG.md +21 -0
  7. {specmod-0.6.0 → specmod-0.7.0}/PKG-INFO +1 -1
  8. {specmod-0.6.0 → specmod-0.7.0}/docs/_builders/specmod_tutorial.py +6 -4
  9. {specmod-0.6.0 → specmod-0.7.0}/docs/releasing-data.md +7 -0
  10. {specmod-0.6.0 → specmod-0.7.0}/docs/roadmap.md +26 -4
  11. {specmod-0.6.0 → specmod-0.7.0}/docs/upgrading.md +71 -6
  12. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/acquire.py +6 -3
  13. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/cli.py +23 -0
  14. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/config/sections.py +9 -4
  15. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/datasets.py +142 -4
  16. {specmod-0.6.0 → specmod-0.7.0}/stubs/obspy/core/utcdatetime.pyi +1 -0
  17. {specmod-0.6.0 → specmod-0.7.0}/tests/test_acquire.py +28 -5
  18. specmod-0.7.0/tests/test_path_names.py +220 -0
  19. {specmod-0.6.0 → specmod-0.7.0}/tests/test_release_config.py +32 -0
  20. {specmod-0.6.0 → specmod-0.7.0}/tutorial/specmod-tutorial.ipynb +6 -4
  21. specmod-0.6.0/.release-please-manifest.json +0 -3
  22. {specmod-0.6.0 → specmod-0.7.0}/.git-blame-ignore-revs +0 -0
  23. {specmod-0.6.0 → specmod-0.7.0}/.github/workflows/pr-title.yml +0 -0
  24. {specmod-0.6.0 → specmod-0.7.0}/.gitignore +0 -0
  25. {specmod-0.6.0 → specmod-0.7.0}/.pre-commit-config.yaml +0 -0
  26. {specmod-0.6.0 → specmod-0.7.0}/.readthedocs.yaml +0 -0
  27. {specmod-0.6.0 → specmod-0.7.0}/AGENTS.md +0 -0
  28. {specmod-0.6.0 → specmod-0.7.0}/CITATION.cff +0 -0
  29. {specmod-0.6.0 → specmod-0.7.0}/CLAUDE.md +0 -0
  30. {specmod-0.6.0 → specmod-0.7.0}/CONTRIBUTING.md +0 -0
  31. {specmod-0.6.0 → specmod-0.7.0}/LICENSE +0 -0
  32. {specmod-0.6.0 → specmod-0.7.0}/README.md +0 -0
  33. {specmod-0.6.0 → specmod-0.7.0}/datasets/magna_2020.toml +0 -0
  34. {specmod-0.6.0 → specmod-0.7.0}/datasets/pnr_2019.toml +0 -0
  35. {specmod-0.6.0 → specmod-0.7.0}/docs/REFACTOR_PLAN.md +0 -0
  36. {specmod-0.6.0 → specmod-0.7.0}/docs/_builders/_notebook.py +0 -0
  37. {specmod-0.6.0 → specmod-0.7.0}/docs/_builders/choosing_a_transform.py +0 -0
  38. {specmod-0.6.0 → specmod-0.7.0}/docs/_static/academic.css +0 -0
  39. {specmod-0.6.0 → specmod-0.7.0}/docs/_static/fonts/OFL-fira-code.txt +0 -0
  40. {specmod-0.6.0 → specmod-0.7.0}/docs/_static/fonts/OFL-merriweather.txt +0 -0
  41. {specmod-0.6.0 → specmod-0.7.0}/docs/_static/fonts/OFL-open-sans.txt +0 -0
  42. {specmod-0.6.0 → specmod-0.7.0}/docs/_static/fonts/README.md +0 -0
  43. {specmod-0.6.0 → specmod-0.7.0}/docs/_static/fonts/fira-code-400-greek.woff2 +0 -0
  44. {specmod-0.6.0 → specmod-0.7.0}/docs/_static/fonts/fira-code-400-latin-ext.woff2 +0 -0
  45. {specmod-0.6.0 → specmod-0.7.0}/docs/_static/fonts/fira-code-400-latin.woff2 +0 -0
  46. {specmod-0.6.0 → specmod-0.7.0}/docs/_static/fonts/merriweather-latin-ext.woff2 +0 -0
  47. {specmod-0.6.0 → specmod-0.7.0}/docs/_static/fonts/merriweather-latin.woff2 +0 -0
  48. {specmod-0.6.0 → specmod-0.7.0}/docs/_static/fonts/open-sans-greek.woff2 +0 -0
  49. {specmod-0.6.0 → specmod-0.7.0}/docs/_static/fonts/open-sans-latin-ext.woff2 +0 -0
  50. {specmod-0.6.0 → specmod-0.7.0}/docs/_static/fonts/open-sans-latin.woff2 +0 -0
  51. {specmod-0.6.0 → specmod-0.7.0}/docs/_static/fonts.css +0 -0
  52. {specmod-0.6.0 → specmod-0.7.0}/docs/_static/katex/LICENSE +0 -0
  53. {specmod-0.6.0 → specmod-0.7.0}/docs/_static/katex/README.md +0 -0
  54. {specmod-0.6.0 → specmod-0.7.0}/docs/_static/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  55. {specmod-0.6.0 → specmod-0.7.0}/docs/_static/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  56. {specmod-0.6.0 → specmod-0.7.0}/docs/_static/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  57. {specmod-0.6.0 → specmod-0.7.0}/docs/_static/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  58. {specmod-0.6.0 → specmod-0.7.0}/docs/_static/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  59. {specmod-0.6.0 → specmod-0.7.0}/docs/_static/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
  60. {specmod-0.6.0 → specmod-0.7.0}/docs/_static/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  61. {specmod-0.6.0 → specmod-0.7.0}/docs/_static/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
  62. {specmod-0.6.0 → specmod-0.7.0}/docs/_static/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
  63. {specmod-0.6.0 → specmod-0.7.0}/docs/_static/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  64. {specmod-0.6.0 → specmod-0.7.0}/docs/_static/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
  65. {specmod-0.6.0 → specmod-0.7.0}/docs/_static/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  66. {specmod-0.6.0 → specmod-0.7.0}/docs/_static/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  67. {specmod-0.6.0 → specmod-0.7.0}/docs/_static/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  68. {specmod-0.6.0 → specmod-0.7.0}/docs/_static/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
  69. {specmod-0.6.0 → specmod-0.7.0}/docs/_static/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  70. {specmod-0.6.0 → specmod-0.7.0}/docs/_static/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  71. {specmod-0.6.0 → specmod-0.7.0}/docs/_static/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  72. {specmod-0.6.0 → specmod-0.7.0}/docs/_static/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  73. {specmod-0.6.0 → specmod-0.7.0}/docs/_static/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  74. {specmod-0.6.0 → specmod-0.7.0}/docs/_static/katex/katex.min.css +0 -0
  75. {specmod-0.6.0 → specmod-0.7.0}/docs/_static/specmod-logo-academic-dark-mark.svg +0 -0
  76. {specmod-0.6.0 → specmod-0.7.0}/docs/_static/specmod-logo-academic-dark.svg +0 -0
  77. {specmod-0.6.0 → specmod-0.7.0}/docs/_static/specmod-logo-academic-mark.svg +0 -0
  78. {specmod-0.6.0 → specmod-0.7.0}/docs/_static/specmod-logo-academic.svg +0 -0
  79. {specmod-0.6.0 → specmod-0.7.0}/docs/api.md +0 -0
  80. {specmod-0.6.0 → specmod-0.7.0}/docs/branding.md +0 -0
  81. {specmod-0.6.0 → specmod-0.7.0}/docs/choosing-a-transform.md +0 -0
  82. {specmod-0.6.0 → specmod-0.7.0}/docs/conf.py +0 -0
  83. {specmod-0.6.0 → specmod-0.7.0}/docs/contributing.md +0 -0
  84. {specmod-0.6.0 → specmod-0.7.0}/docs/development.md +0 -0
  85. {specmod-0.6.0 → specmod-0.7.0}/docs/documentation.md +0 -0
  86. {specmod-0.6.0 → specmod-0.7.0}/docs/getting-started.md +0 -0
  87. {specmod-0.6.0 → specmod-0.7.0}/docs/guides.md +0 -0
  88. {specmod-0.6.0 → specmod-0.7.0}/docs/index.md +0 -0
  89. {specmod-0.6.0 → specmod-0.7.0}/docs/notebooks/choosing-a-transform.ipynb +0 -0
  90. {specmod-0.6.0 → specmod-0.7.0}/docs/notes/api-audit.md +0 -0
  91. {specmod-0.6.0 → specmod-0.7.0}/docs/notes/window-position.md +0 -0
  92. {specmod-0.6.0 → specmod-0.7.0}/docs/pick-formats.md +0 -0
  93. {specmod-0.6.0 → specmod-0.7.0}/docs/processing.md +0 -0
  94. {specmod-0.6.0 → specmod-0.7.0}/docs/releasing.md +0 -0
  95. {specmod-0.6.0 → specmod-0.7.0}/pyproject.toml +0 -0
  96. {specmod-0.6.0 → specmod-0.7.0}/release-please-config.json +0 -0
  97. {specmod-0.6.0 → specmod-0.7.0}/requirements.txt +0 -0
  98. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/__init__.py +0 -0
  99. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/_vendor/__init__.py +0 -0
  100. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/_vendor/qiinv.py +0 -0
  101. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/api.py +0 -0
  102. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/config/__init__.py +0 -0
  103. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/config/layers.py +0 -0
  104. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/config/provenance.py +0 -0
  105. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/config/serialize.py +0 -0
  106. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/core/__init__.py +0 -0
  107. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/core/bandwidth.py +0 -0
  108. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/core/collection.py +0 -0
  109. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/core/noise.py +0 -0
  110. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/core/scalogram.py +0 -0
  111. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/core/spectrum.py +0 -0
  112. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/core/units.py +0 -0
  113. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/distance.py +0 -0
  114. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/exceptions.py +0 -0
  115. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/fitting/__init__.py +0 -0
  116. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/fitting/base.py +0 -0
  117. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/fitting/event.py +0 -0
  118. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/fitting/guess.py +0 -0
  119. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/fitting/spectrum.py +0 -0
  120. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/io/__init__.py +0 -0
  121. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/io/backend.py +0 -0
  122. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/io/hdf5.py +0 -0
  123. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/io/layout.py +0 -0
  124. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/magnitude.py +0 -0
  125. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/picks/__init__.py +0 -0
  126. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/picks/base.py +0 -0
  127. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/picks/delimited.py +0 -0
  128. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/picks/events.py +0 -0
  129. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/picks/resolution.py +0 -0
  130. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/picks/snuffler.py +0 -0
  131. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/pipeline.py +0 -0
  132. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/plotting/__init__.py +0 -0
  133. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/plotting/grid.py +0 -0
  134. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/plotting/pair.py +0 -0
  135. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/plotting/style.py +0 -0
  136. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/preprocess.py +0 -0
  137. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/smoothing/__init__.py +0 -0
  138. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/smoothing/base.py +0 -0
  139. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/smoothing/konno_ohmachi.py +0 -0
  140. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/smoothing/log_bins.py +0 -0
  141. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/smoothing/log_window.py +0 -0
  142. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/smoothing/savitzky_golay.py +0 -0
  143. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/sources/__init__.py +0 -0
  144. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/sources/attenuation.py +0 -0
  145. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/sources/composite.py +0 -0
  146. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/sources/motion.py +0 -0
  147. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/sources/source.py +0 -0
  148. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/spreading.py +0 -0
  149. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/staged.py +0 -0
  150. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/tables.py +0 -0
  151. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/transforms/__init__.py +0 -0
  152. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/transforms/base.py +0 -0
  153. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/transforms/cwt.py +0 -0
  154. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/transforms/fft.py +0 -0
  155. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/transforms/multitaper.py +0 -0
  156. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/transforms/prieto.py +0 -0
  157. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/transforms/quadratic.py +0 -0
  158. {specmod-0.6.0 → specmod-0.7.0}/src/specmod/utils.py +0 -0
  159. {specmod-0.6.0 → specmod-0.7.0}/stubs/README.md +0 -0
  160. {specmod-0.6.0 → specmod-0.7.0}/stubs/lmfit/__init__.pyi +0 -0
  161. {specmod-0.6.0 → specmod-0.7.0}/stubs/lmfit/model.pyi +0 -0
  162. {specmod-0.6.0 → specmod-0.7.0}/stubs/lmfit/parameter.pyi +0 -0
  163. {specmod-0.6.0 → specmod-0.7.0}/stubs/obspy/__init__.pyi +0 -0
  164. {specmod-0.6.0 → specmod-0.7.0}/stubs/obspy/clients/__init__.pyi +0 -0
  165. {specmod-0.6.0 → specmod-0.7.0}/stubs/obspy/clients/fdsn/__init__.pyi +0 -0
  166. {specmod-0.6.0 → specmod-0.7.0}/stubs/obspy/core/__init__.pyi +0 -0
  167. {specmod-0.6.0 → specmod-0.7.0}/stubs/obspy/core/event.pyi +0 -0
  168. {specmod-0.6.0 → specmod-0.7.0}/stubs/obspy/core/inventory.pyi +0 -0
  169. {specmod-0.6.0 → specmod-0.7.0}/stubs/obspy/core/stream.pyi +0 -0
  170. {specmod-0.6.0 → specmod-0.7.0}/stubs/obspy/core/trace.pyi +0 -0
  171. {specmod-0.6.0 → specmod-0.7.0}/stubs/obspy/core/util/__init__.pyi +0 -0
  172. {specmod-0.6.0 → specmod-0.7.0}/stubs/obspy/core/util/base.pyi +0 -0
  173. {specmod-0.6.0 → specmod-0.7.0}/stubs/obspy/geodetics/__init__.pyi +0 -0
  174. {specmod-0.6.0 → specmod-0.7.0}/stubs/obspy/signal/__init__.pyi +0 -0
  175. {specmod-0.6.0 → specmod-0.7.0}/stubs/obspy/signal/konnoohmachismoothing.pyi +0 -0
  176. {specmod-0.6.0 → specmod-0.7.0}/studies/magna_2020_paper.toml +0 -0
  177. {specmod-0.6.0 → specmod-0.7.0}/tests/__init__.py +0 -0
  178. {specmod-0.6.0 → specmod-0.7.0}/tests/conftest.py +0 -0
  179. {specmod-0.6.0 → specmod-0.7.0}/tests/golden/motion_reference.json +0 -0
  180. {specmod-0.6.0 → specmod-0.7.0}/tests/golden/pipeline_reference.json +0 -0
  181. {specmod-0.6.0 → specmod-0.7.0}/tests/golden/reference.toml +0 -0
  182. {specmod-0.6.0 → specmod-0.7.0}/tests/golden/window_reference.json +0 -0
  183. {specmod-0.6.0 → specmod-0.7.0}/tests/test_ambient_state.py +0 -0
  184. {specmod-0.6.0 → specmod-0.7.0}/tests/test_api_surface.py +0 -0
  185. {specmod-0.6.0 → specmod-0.7.0}/tests/test_collection.py +0 -0
  186. {specmod-0.6.0 → specmod-0.7.0}/tests/test_config.py +0 -0
  187. {specmod-0.6.0 → specmod-0.7.0}/tests/test_config_pinning.py +0 -0
  188. {specmod-0.6.0 → specmod-0.7.0}/tests/test_cwt.py +0 -0
  189. {specmod-0.6.0 → specmod-0.7.0}/tests/test_datasets.py +0 -0
  190. {specmod-0.6.0 → specmod-0.7.0}/tests/test_docs_are_current.py +0 -0
  191. {specmod-0.6.0 → specmod-0.7.0}/tests/test_end_to_end.py +0 -0
  192. {specmod-0.6.0 → specmod-0.7.0}/tests/test_fitting_defaults.py +0 -0
  193. {specmod-0.6.0 → specmod-0.7.0}/tests/test_golden_reference.py +0 -0
  194. {specmod-0.6.0 → specmod-0.7.0}/tests/test_import.py +0 -0
  195. {specmod-0.6.0 → specmod-0.7.0}/tests/test_io_and_plotting.py +0 -0
  196. {specmod-0.6.0 → specmod-0.7.0}/tests/test_legacy_fixes.py +0 -0
  197. {specmod-0.6.0 → specmod-0.7.0}/tests/test_magnitude.py +0 -0
  198. {specmod-0.6.0 → specmod-0.7.0}/tests/test_make_golden.py +0 -0
  199. {specmod-0.6.0 → specmod-0.7.0}/tests/test_pick_plugins.py +0 -0
  200. {specmod-0.6.0 → specmod-0.7.0}/tests/test_pick_readers.py +0 -0
  201. {specmod-0.6.0 → specmod-0.7.0}/tests/test_picks.py +0 -0
  202. {specmod-0.6.0 → specmod-0.7.0}/tests/test_pipeline.py +0 -0
  203. {specmod-0.6.0 → specmod-0.7.0}/tests/test_pipeline_smoke.py +0 -0
  204. {specmod-0.6.0 → specmod-0.7.0}/tests/test_pr_title.py +0 -0
  205. {specmod-0.6.0 → specmod-0.7.0}/tests/test_preprocess.py +0 -0
  206. {specmod-0.6.0 → specmod-0.7.0}/tests/test_prieto.py +0 -0
  207. {specmod-0.6.0 → specmod-0.7.0}/tests/test_quadratic.py +0 -0
  208. {specmod-0.6.0 → specmod-0.7.0}/tests/test_smoothing.py +0 -0
  209. {specmod-0.6.0 → specmod-0.7.0}/tests/test_sources.py +0 -0
  210. {specmod-0.6.0 → specmod-0.7.0}/tests/test_spectral_wiring.py +0 -0
  211. {specmod-0.6.0 → specmod-0.7.0}/tests/test_staged.py +0 -0
  212. {specmod-0.6.0 → specmod-0.7.0}/tests/test_stubs.py +0 -0
  213. {specmod-0.6.0 → specmod-0.7.0}/tests/test_tools_track_the_api.py +0 -0
  214. {specmod-0.6.0 → specmod-0.7.0}/tests/test_transforms.py +0 -0
  215. {specmod-0.6.0 → specmod-0.7.0}/tests/test_tutorial.py +0 -0
  216. {specmod-0.6.0 → specmod-0.7.0}/tests/test_typing_backlog.py +0 -0
  217. {specmod-0.6.0 → specmod-0.7.0}/tests/test_utils.py +0 -0
  218. {specmod-0.6.0 → specmod-0.7.0}/tests/test_versioning.py +0 -0
  219. {specmod-0.6.0 → specmod-0.7.0}/tools/check_built_version.py +0 -0
  220. {specmod-0.6.0 → specmod-0.7.0}/tools/check_floors.py +0 -0
  221. {specmod-0.6.0 → specmod-0.7.0}/tools/check_pr_title.py +0 -0
  222. {specmod-0.6.0 → specmod-0.7.0}/tools/make_golden.py +0 -0
  223. {specmod-0.6.0 → specmod-0.7.0}/tools/make_logo.py +0 -0
  224. {specmod-0.6.0 → specmod-0.7.0}/tools/measure_docs.py +0 -0
  225. {specmod-0.6.0 → specmod-0.7.0}/tools/vendor_fonts.py +0 -0
  226. {specmod-0.6.0 → specmod-0.7.0}/tools/vendor_katex.py +0 -0
  227. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/picks/2019-08-26T07:30:47.000000Z.picks → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/picks/20190826T073047.000000Z.picks +0 -0
  228. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/picks/2019-08-26T07:30:47.000000Z.xml → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/picks/20190826T073047.000000Z.xml +0 -0
  229. {specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z → specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z}/stations/inventory.xml +0 -0
  230. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L001..HHE_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/LV.L001..HHE_20190826T073047.000000Z +0 -0
  231. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L001..HHN_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/LV.L001..HHN_20190826T073047.000000Z +0 -0
  232. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L001..HHZ_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/LV.L001..HHZ_20190826T073047.000000Z +0 -0
  233. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L002..HHE_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/LV.L002..HHE_20190826T073047.000000Z +0 -0
  234. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L002..HHN_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/LV.L002..HHN_20190826T073047.000000Z +0 -0
  235. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L002..HHZ_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/LV.L002..HHZ_20190826T073047.000000Z +0 -0
  236. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L006..HHE_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/LV.L006..HHE_20190826T073047.000000Z +0 -0
  237. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L006..HHN_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/LV.L006..HHN_20190826T073047.000000Z +0 -0
  238. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L006..HHZ_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/LV.L006..HHZ_20190826T073047.000000Z +0 -0
  239. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L007..HHE_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/LV.L007..HHE_20190826T073047.000000Z +0 -0
  240. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L007..HHN_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/LV.L007..HHN_20190826T073047.000000Z +0 -0
  241. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L007..HHZ_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/LV.L007..HHZ_20190826T073047.000000Z +0 -0
  242. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L008..HHE_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/LV.L008..HHE_20190826T073047.000000Z +0 -0
  243. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L008..HHN_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/LV.L008..HHN_20190826T073047.000000Z +0 -0
  244. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L008..HHZ_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/LV.L008..HHZ_20190826T073047.000000Z +0 -0
  245. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L009..HHE_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/LV.L009..HHE_20190826T073047.000000Z +0 -0
  246. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L009..HHN_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/LV.L009..HHN_20190826T073047.000000Z +0 -0
  247. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L009..HHZ_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/LV.L009..HHZ_20190826T073047.000000Z +0 -0
  248. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.LD06..HH1_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/LV.LD06..HH1_20190826T073047.000000Z +0 -0
  249. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.LD06..HH2_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/LV.LD06..HH2_20190826T073047.000000Z +0 -0
  250. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.LD06..HH3_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/LV.LD06..HH3_20190826T073047.000000Z +0 -0
  251. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ01.00.HHE_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/UR.AQ01.00.HHE_20190826T073047.000000Z +0 -0
  252. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ01.00.HHN_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/UR.AQ01.00.HHN_20190826T073047.000000Z +0 -0
  253. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ01.00.HHZ_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/UR.AQ01.00.HHZ_20190826T073047.000000Z +0 -0
  254. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ02.00.HHZ_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/UR.AQ02.00.HHZ_20190826T073047.000000Z +0 -0
  255. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ03.00.HHE_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/UR.AQ03.00.HHE_20190826T073047.000000Z +0 -0
  256. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ03.00.HHN_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/UR.AQ03.00.HHN_20190826T073047.000000Z +0 -0
  257. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ03.00.HHZ_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/UR.AQ03.00.HHZ_20190826T073047.000000Z +0 -0
  258. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ04.00.HHE_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/UR.AQ04.00.HHE_20190826T073047.000000Z +0 -0
  259. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ04.00.HHN_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/UR.AQ04.00.HHN_20190826T073047.000000Z +0 -0
  260. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ04.00.HHZ_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/UR.AQ04.00.HHZ_20190826T073047.000000Z +0 -0
  261. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ05.00.HHE_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/UR.AQ05.00.HHE_20190826T073047.000000Z +0 -0
  262. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ05.00.HHN_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/UR.AQ05.00.HHN_20190826T073047.000000Z +0 -0
  263. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ05.00.HHZ_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/UR.AQ05.00.HHZ_20190826T073047.000000Z +0 -0
  264. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ06.00.HHE_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/UR.AQ06.00.HHE_20190826T073047.000000Z +0 -0
  265. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ06.00.HHN_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/UR.AQ06.00.HHN_20190826T073047.000000Z +0 -0
  266. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ06.00.HHZ_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/UR.AQ06.00.HHZ_20190826T073047.000000Z +0 -0
  267. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ07.00.HHE_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/UR.AQ07.00.HHE_20190826T073047.000000Z +0 -0
  268. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ07.00.HHN_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/UR.AQ07.00.HHN_20190826T073047.000000Z +0 -0
  269. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ07.00.HHZ_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/UR.AQ07.00.HHZ_20190826T073047.000000Z +0 -0
  270. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ09.00.HHE_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/UR.AQ09.00.HHE_20190826T073047.000000Z +0 -0
  271. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ09.00.HHN_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/UR.AQ09.00.HHN_20190826T073047.000000Z +0 -0
  272. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ09.00.HHZ_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/UR.AQ09.00.HHZ_20190826T073047.000000Z +0 -0
  273. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ10.00.HHE_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/UR.AQ10.00.HHE_20190826T073047.000000Z +0 -0
  274. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ10.00.HHN_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/UR.AQ10.00.HHN_20190826T073047.000000Z +0 -0
  275. /specmod-0.6.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ10.00.HHZ_2019-08-26T07:30:47.000000Z → /specmod-0.7.0/tutorial/data/events/20190826T073047.000000Z/waveforms/UR.AQ10.00.HHZ_20190826T073047.000000Z +0 -0
@@ -18,6 +18,8 @@ jobs:
18
18
  # fails every job at "Getting action download info", before a step runs.
19
19
  # Bump this deliberately; do not "tidy" it to `@v10`.
20
20
  - uses: astral-sh/setup-uv@v10.0.1
21
+ with:
22
+ version: "0.13.7" # see UV_VERSION in tests/test_release_config.py
21
23
  - run: uv python install 3.11
22
24
 
23
25
  - name: Build sdist and wheel
@@ -32,6 +32,8 @@ jobs:
32
32
  # fails every job at "Getting action download info", before a step runs.
33
33
  # Bump this deliberately; do not "tidy" it to `@v10`.
34
34
  - uses: astral-sh/setup-uv@v10.0.1
35
+ with:
36
+ version: "0.13.7" # see UV_VERSION in tests/test_release_config.py
35
37
  - run: uv python install 3.11
36
38
  - run: uv venv # create a venv to install into
37
39
 
@@ -90,6 +90,8 @@ jobs:
90
90
  # fails every job at "Getting action download info", before a step runs.
91
91
  # Bump this deliberately; do not "tidy" it to `@v10`.
92
92
  - uses: astral-sh/setup-uv@v10.0.1
93
+ with:
94
+ version: "0.13.7" # see UV_VERSION in tests/test_release_config.py
93
95
  - run: uv python install 3.11
94
96
  - run: uv build
95
97
 
@@ -21,6 +21,7 @@ jobs:
21
21
  # Bump this deliberately; do not "tidy" it to `@v10`.
22
22
  - uses: astral-sh/setup-uv@v10.0.1
23
23
  with:
24
+ version: "0.13.7" # see UV_VERSION in tests/test_release_config.py
24
25
  enable-cache: true
25
26
  - run: uv python install 3.11
26
27
  - run: uv venv
@@ -41,6 +42,7 @@ jobs:
41
42
  # Bump this deliberately; do not "tidy" it to `@v10`.
42
43
  - uses: astral-sh/setup-uv@v10.0.1
43
44
  with:
45
+ version: "0.13.7" # see UV_VERSION in tests/test_release_config.py
44
46
  enable-cache: true
45
47
  - run: uv python install 3.11
46
48
  - run: uv venv
@@ -65,6 +67,7 @@ jobs:
65
67
  # Bump this deliberately; do not "tidy" it to `@v10`.
66
68
  - uses: astral-sh/setup-uv@v10.0.1
67
69
  with:
70
+ version: "0.13.7" # see UV_VERSION in tests/test_release_config.py
68
71
  enable-cache: true
69
72
  - run: uv python install ${{ matrix.python-version }}
70
73
  - run: uv venv
@@ -93,6 +96,8 @@ jobs:
93
96
  # fails every job at "Getting action download info", before a step runs.
94
97
  # Bump this deliberately; do not "tidy" it to `@v10`.
95
98
  - uses: astral-sh/setup-uv@v10.0.1
99
+ with:
100
+ version: "0.13.7" # see UV_VERSION in tests/test_release_config.py
96
101
  - run: uv python install 3.11
97
102
  - run: uv venv
98
103
  # Exercise the declared minimums. CI otherwise installs the newest of
@@ -117,6 +122,7 @@ jobs:
117
122
  # Bump this deliberately; do not "tidy" it to `@v10`.
118
123
  - uses: astral-sh/setup-uv@v10.0.1
119
124
  with:
125
+ version: "0.13.7" # see UV_VERSION in tests/test_release_config.py
120
126
  enable-cache: true
121
127
  - run: uv python install 3.11
122
128
  - run: uv venv
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.7.0"
3
+ }
@@ -9,6 +9,27 @@ release could have this problem — every later one has a real predecessor tag
9
9
  so leave subsequent headings as generated. release-please only prepends to this
10
10
  file, so this edit survives. -->
11
11
 
12
+ ## [0.7.0](https://github.com/sgjholt/SpecMod/compare/v0.6.0...v0.7.0) (2026-11-08)
13
+
14
+
15
+ ### ⚠ BREAKING CHANGES
16
+
17
+ * **datasets:** event directories, waveform files and pick files are named in ISO 8601 basic format (`20190826T073047.000000Z`) instead of extended format (`2019-08-26T07:30:47.000000Z`), and `specmod fetch` writes the new names. Run `specmod migrate-names <dir>` on data fetched before 0.7; `Event.directory()` reads the old names with a DeprecationWarning until 0.8. Code that built a path from `Event.origin` or `SpectrumSet.event` should call `specmod.datasets.path_name` on it. See docs/upgrading.md, From 0.6 to 0.7.
18
+
19
+ ### Features
20
+
21
+ * **datasets:** name times on disk without colons, and migrate existing data ([52168de](https://github.com/sgjholt/SpecMod/commit/52168dea5cd9ffd8ff16dd9dd70996824c0bbb93))
22
+
23
+
24
+ ### Code Refactoring
25
+
26
+ * **config:** narrow the default_factory sentinel instead of suppressing mypy ([961da50](https://github.com/sgjholt/SpecMod/commit/961da50c28aa8ba785879a2b2287c219f64ec216))
27
+
28
+
29
+ ### Documentation
30
+
31
+ * **roadmap:** file the default change and the config pin as shipped in v0.6.0 ([92eff1a](https://github.com/sgjholt/SpecMod/commit/92eff1af91dceea5ee65da5a55b460c026cb75de))
32
+
12
33
  ## [0.6.0](https://github.com/sgjholt/SpecMod/compare/v0.5.0...v0.6.0) (2026-09-27)
13
34
 
14
35
  <!-- One Documentation line below was removed by hand, the third edit in this
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: specmod
3
- Version: 0.6.0
3
+ Version: 0.7.0
4
4
  Summary: A Python toolbox for processing and modelling seismic spectra
5
5
  Project-URL: Homepage, https://github.com/sgjholt/SpecMod
6
6
  Project-URL: Repository, https://github.com/sgjholt/SpecMod
@@ -91,13 +91,15 @@ from pandas.core.series import Series
91
91
  import specmod.preprocess as pre
92
92
  import specmod.utils as ut
93
93
  from specmod.core.collection import SpectrumPair, SpectrumSet
94
+ from specmod.datasets import path_name
94
95
  from specmod.pipeline import spectrum_set_from_streams
95
96
  from specmod.staged import StagedFit
96
97
 
97
98
  # Relative to this notebook, so it runs from wherever it is opened.
98
99
  EVENTS = Path("data/events")
99
100
  EVENT = "2019-08-26T07:30:47.000000Z" # Event origin time
100
- DATA: Path = EVENTS / EVENT
101
+ # Named without colons, which Windows does not allow: 20190826T073047.000000Z.
102
+ DATA: Path = EVENTS / path_name(EVENT)
101
103
  WAVEFORMS: Path = DATA / "waveforms"
102
104
  STATIONS: Path = DATA / "stations" / "*.xml"
103
105
  PICKS: Path = DATA / "picks"
@@ -1004,7 +1006,7 @@ it, because journal supplements want one.
1004
1006
  code("""
1005
1007
  from specmod.io import load, save
1006
1008
 
1007
- path: Path = save(path=OUTPUT / spectra.event, spectra=spectra)
1009
+ path: Path = save(path=OUTPUT / path_name(spectra.event), spectra=spectra)
1008
1010
  print(f"{path} ({path.stat().st_size // 1024} KB)")
1009
1011
 
1010
1012
  back: SpectrumSet = load(path)
@@ -1016,8 +1018,8 @@ print(
1016
1018
 
1017
1019
 
1018
1020
  code("""
1019
- FitSpectra.write_flatfile(path=OUTPUT / "flatfiles" / f"{spectra.event}.parquet", fits=fits)
1020
- FitSpectra.write_flatfile(path=OUTPUT / "flatfiles" / f"{spectra.event}.csv", fits=fits)
1021
+ FitSpectra.write_flatfile(path=OUTPUT / "flatfiles" / f"{path_name(spectra.event)}.parquet", fits=fits)
1022
+ FitSpectra.write_flatfile(path=OUTPUT / "flatfiles" / f"{path_name(spectra.event)}.csv", fits=fits)
1021
1023
  sorted(p.name for p in (OUTPUT / "flatfiles").iterdir())
1022
1024
  """)
1023
1025
 
@@ -30,6 +30,13 @@ station wildcards against the data centre, cuts each channel to the window
30
30
  relative to origin, and writes an `EventDirectory` with a `manifest.json`
31
31
  beside it.
32
32
 
33
+ The event directory and each waveform file carry the origin in ISO 8601 basic
34
+ format — `20200318T130931.000000Z`, not `2020-03-18T13:09:31.000000Z` — because
35
+ the colons are not legal in a Windows file name, and a source distribution or
36
+ archive containing one is refused by uv 0.13. A fetch made before 0.7 has the
37
+ old names: run `specmod migrate-names build/magna_2020` before archiving it,
38
+ which renames them and updates the manifest to match.
39
+
33
40
  Expect it to take a few minutes — Magna is 88 stations across seven networks.
34
41
 
35
42
  **Check the manifest before going further.** It records what came back, not
@@ -191,9 +191,7 @@ found by a live fetch rather than by the suite:
191
191
  as two independent records of one ground motion. `channel_priorities` and
192
192
  `location_priorities` rank them, first match wins per station.
193
193
 
194
- ## In progress — not yet released
195
-
196
- Merged on `main`, and it will name its version when a release goes out.
194
+ ## Shipped in v0.6.0
197
195
 
198
196
  ### The default is FFT with Konno-Ohmachi smoothing
199
197
 
@@ -227,6 +225,22 @@ indistinguishable from a regression. They now carry their own settings in
227
225
  `tests/golden/reference.toml`, and a default change leaves every one of them
228
226
  untouched.
229
227
 
228
+ ## In progress — not yet released
229
+
230
+ Merged on `main`, and it will name its version when a release goes out.
231
+
232
+ ### Names on disk carry no colons
233
+
234
+ Event directories and the files in them were named by the origin as ObsPy
235
+ prints it, `2019-08-26T07:30:47.000000Z`. Windows does not allow the colons,
236
+ and uv 0.13 refuses to build a source distribution containing them, which
237
+ stopped the release. Names are now ISO 8601 basic format,
238
+ `20190826T073047.000000Z`, from `specmod.datasets.path_name`, and
239
+ `specmod fetch` writes them. `specmod migrate-names` renames data written
240
+ earlier and keeps its manifest verifying; until 0.8, data that has not been
241
+ migrated is still found, with a warning.
242
+ [Upgrading](upgrading.md#from-06-to-07) has the steps.
243
+
230
244
  ## Planned
231
245
 
232
246
  ### 1.0 — the API stops moving
@@ -268,7 +282,15 @@ Twice in a row makes it worth naming: what keeps moving is not the API, it is
268
282
  what the API *does* with a setting that was already there. A deprecation cycle
269
283
  does not protect against that, since there is nothing to deprecate; the
270
284
  [upgrading guide](upgrading.md) is the only thing that does, and 1.0 will not
271
- change that. The count stands at zero.
285
+ change that.
286
+
287
+ **v0.6.0 is not clean either, and is the first to break on purpose.** Its
288
+ default estimator and smoother changed, so every result moves unless a
289
+ configuration pinned both; the change is marked breaking, and
290
+ [Upgrading](upgrading.md#from-05-to-06) says what to pin. One signature moved
291
+ as well: the first argument of `load_config` is positional-only, so
292
+ `load_config(start=path)` is now read as a `[start]` configuration section and
293
+ refused. The count stands at zero.
272
294
 
273
295
  ## After 1.0
274
296
 
@@ -1,10 +1,70 @@
1
1
  # Upgrading
2
2
 
3
- Five moves are documented here. Most readers porting code want `0.2` to `0.3`,
3
+ Six moves are documented here. Most readers porting code want `0.2` to `0.3`,
4
4
  which renamed ten functions in `specmod.preprocess` and changed what they do
5
- with the stream they are given. The three moves above it rename nothing: they
6
- change what a setting does, or which one is the default, so the break is in the
7
- numbers rather than in the call.
5
+ with the stream they are given. Three of the moves above it rename nothing:
6
+ they change what a setting does, or which one is the default, so the break is
7
+ in the numbers rather than in the call. `0.6` to `0.7` renames files on disk,
8
+ and comes with a command that does it for you.
9
+
10
+ ## From 0.6 to 0.7
11
+
12
+ **Times in file and directory names no longer contain colons.** An event
13
+ directory and the files inside it were named by the origin as ObsPy prints it,
14
+ `2019-08-26T07:30:47.000000Z`. They are now named in ISO 8601 basic format,
15
+ `20190826T073047.000000Z`, through `specmod.datasets.path_name`. Nothing in any
16
+ file changes, only names; no result moves.
17
+
18
+ The old names had two problems. Windows does not allow a colon in a file name,
19
+ so the repository could not be checked out there, and uv 0.13 refuses to build
20
+ a source distribution containing one, which stopped the release.
21
+
22
+ | | Before 0.7 | From 0.7 |
23
+ |---|---|---|
24
+ | Event directory | `events/2019-08-26T07:30:47.000000Z/` | `events/20190826T073047.000000Z/` |
25
+ | Waveform file | `LV.L001..HHE_2019-08-26T07:30:47.000000Z` | `LV.L001..HHE_20190826T073047.000000Z` |
26
+ | Pick file | `picks/2019-08-26T07:30:47.000000Z.picks` | `picks/20190826T073047.000000Z.picks` |
27
+ | `manifest.json` `files` keys | the old paths | the new paths |
28
+
29
+ **`specmod fetch` writes the new names.** Data you fetched with an earlier
30
+ version keeps the old ones until you migrate it.
31
+
32
+ **To migrate a directory**, point the command at anything that contains event
33
+ data — one fetch, a tree of them, or a checkout's `tutorial/data/events`:
34
+
35
+ ```bash
36
+ specmod migrate-names build/magna_2020 --dry-run # list what would change
37
+ specmod migrate-names build/magna_2020
38
+ specmod fetch datasets/magna_2020.toml -o build/magna_2020 --verify
39
+ ```
40
+
41
+ It rewrites every extended-format time in every name under the directory, and
42
+ the `files` keys of any `manifest.json` there, so `fetch --verify` still passes:
43
+ the hashes are of file contents, which the migration does not touch. From
44
+ Python it is `specmod.datasets.migrate_names(root, dry_run=False)`, which
45
+ returns the `(old, new)` pairs. Running it twice is safe; the second run finds
46
+ nothing to do. It refuses, before renaming anything, when a new name is
47
+ already taken, which happens when one event exists in both layouts. Which copy
48
+ to keep is yours to decide.
49
+
50
+ **Unmigrated data is still read, until 0.8.** `Event.directory()` looks for the
51
+ new name first and falls back to the old one with a `DeprecationWarning` naming
52
+ the command to run. The fallback goes in 0.8, under the usual one-minor
53
+ deprecation.
54
+
55
+ **Code that built a path from the origin string** has to call `path_name`
56
+ instead:
57
+
58
+ ```python
59
+ from specmod.datasets import path_name
60
+
61
+ event_dir = out / path_name(event.origin) # was: out / event.origin
62
+ save(spectra_dir / path_name(spectra.event), s) # was: spectra_dir / spectra.event
63
+ ```
64
+
65
+ The origin itself is unchanged everywhere it is a time rather than a name:
66
+ `Event.origin`, `datasets/*.toml`, the manifest's `resolved.origin`, and
67
+ `SpectrumSet.event` all keep the extended format.
8
68
 
9
69
  `0.x` ships breaking changes in minor bumps without a deprecation cycle,
10
70
  because shimming an API still being worked out costs more than it protects.
@@ -13,8 +73,8 @@ See [Releasing the software](releasing.md#what-a-version-number-means-while-this
13
73
  ## From 0.5 to 0.6
14
74
 
15
75
  **The default estimator is `fft` and the default smoother is `konno_ohmachi`.**
16
- They were `multitaper` and `log_bins`. Nothing is renamed and no signature
17
- moves; **every result changes** unless your configuration already pinned both.
76
+ They were `multitaper` and `log_bins`. Nothing is renamed; **every result
77
+ changes** unless your configuration already pinned both.
18
78
 
19
79
  The two do different jobs. The estimator sets the spectrum that is fitted.
20
80
  The smoother sets the band the fit is confined to — the signal-to-noise ratio
@@ -72,6 +132,11 @@ with config.using("studies/my_study.toml"):
72
132
  The local file and the environment are excluded inside the block, and an
73
133
  explicit `load_config(project_file=...)` still wins.
74
134
 
135
+ **The first argument of `load_config` is positional-only.**
136
+ `load_config(start=path)` now reads `start` as a configuration section and
137
+ raises `ValueError: Configuration section [start] must be a table`; write
138
+ `load_config(path)`. The keyword arguments are unchanged.
139
+
75
140
  ## From 0.4 to 0.5
76
141
 
77
142
  **`max_radius_km` and `min_radius_km` now mean kilometres.** They were converted
@@ -38,7 +38,7 @@ from pathlib import Path
38
38
  from typing import Any
39
39
 
40
40
  from . import __version__
41
- from .datasets import Event, EventDirectory
41
+ from .datasets import Event, EventDirectory, path_name
42
42
 
43
43
  __all__ = [
44
44
  "AcquisitionConfig",
@@ -567,7 +567,10 @@ def fetch(
567
567
  bulk = _bulk_request(inventory, config, start, end)
568
568
  stream = client.get_waveforms_bulk(bulk)
569
569
 
570
- paths = EventDirectory(Path(out) / event.origin)
570
+ # Named by `path_name`, never by the origin string itself: its colons are
571
+ # not legal in a Windows name, and a source distribution carrying one is
572
+ # refused by uv 0.13.
573
+ paths = EventDirectory(Path(out) / path_name(event.origin))
571
574
  paths.waveforms.mkdir(parents=True, exist_ok=True)
572
575
  paths.stations.mkdir(parents=True, exist_ok=True)
573
576
  paths.picks.mkdir(parents=True, exist_ok=True)
@@ -576,7 +579,7 @@ def fetch(
576
579
  for trace in stream:
577
580
  # One file per channel, named as the shipped data is, so a fetched
578
581
  # event and a committed one are read by the same code.
579
- name = f"{trace.id}_{trace.stats.starttime}"
582
+ name = f"{trace.id}_{path_name(trace.stats.starttime)}"
580
583
  target = paths.waveforms / name
581
584
  trace.write(str(target), format="MSEED")
582
585
  written[str(target.relative_to(Path(out)))] = _sha256(target)
@@ -19,6 +19,7 @@ from .acquire import fetch, verify
19
19
  from .config import load_config
20
20
  from .config.provenance import Provenance
21
21
  from .config.serialize import to_toml
22
+ from .datasets import migrate_names
22
23
 
23
24
  #: Shared by every command that resolves configuration, so the layering flags
24
25
  #: cannot drift apart between them.
@@ -135,5 +136,27 @@ def fetch_command(config_file: str, out: str, verify_only: bool) -> None:
135
136
  )
136
137
 
137
138
 
139
+ @main.command("migrate-names")
140
+ @click.argument("root", type=click.Path(exists=True, file_okay=False))
141
+ @click.option(
142
+ "--dry-run",
143
+ is_flag=True,
144
+ help="List what would be renamed, and rename nothing.",
145
+ )
146
+ def migrate_names_command(root: str, dry_run: bool) -> None:
147
+ """Rename event data written before 0.7 to colon-free names.
148
+
149
+ Every time in a file or directory name under ROOT is rewritten from
150
+ 2019-08-26T07:30:47.000000Z to 20190826T073047.000000Z, and any
151
+ manifest.json there is updated to match, so `fetch --verify` still
152
+ passes. File contents are untouched. Safe to run twice.
153
+ """
154
+ renames = migrate_names(root, dry_run=dry_run)
155
+ for old, new in renames:
156
+ click.echo(f"{old} -> {new.name}")
157
+ verb = "would rename" if dry_run else "renamed"
158
+ click.echo(f"{root}: {verb} {len(renames)} path(s)")
159
+
160
+
138
161
  if __name__ == "__main__": # pragma: no cover
139
162
  main()
@@ -12,7 +12,7 @@ values used for the published Magna run. Those live in
12
12
 
13
13
  from __future__ import annotations
14
14
 
15
- from dataclasses import dataclass, field, fields, is_dataclass
15
+ from dataclasses import MISSING, dataclass, field, fields, is_dataclass
16
16
  from typing import Any, Literal, Self
17
17
 
18
18
  __all__ = [
@@ -396,9 +396,14 @@ class Config:
396
396
  f"Valid sections are {sorted(known)}."
397
397
  )
398
398
  kwargs: dict[str, Any] = {}
399
- for name, f in ((f.name, f) for f in fields(cls)):
400
- if name in data:
401
- kwargs[name] = _build_section(f.default_factory(), data[name], name) # type: ignore[misc]
399
+ for f in fields(cls):
400
+ if f.name in data:
401
+ # Every section field has a default_factory; the check narrows
402
+ # the MISSING sentinel away for the type checker.
403
+ assert f.default_factory is not MISSING
404
+ kwargs[f.name] = _build_section(
405
+ f.default_factory(), data[f.name], f.name
406
+ )
402
407
  return cls(**kwargs)
403
408
 
404
409
 
@@ -4,7 +4,7 @@ The layout follows ObsPy's ``mass_downloader``, which writes ``waveforms/``
4
4
  and ``stations/`` beneath a per-event directory. This adds the three things a
5
5
  spectral workflow needs alongside them::
6
6
 
7
- tutorial/data/events/<origin>/
7
+ tutorial/data/events/<path_name(origin)>/
8
8
  event.xml # QuakeML: origin, magnitudes, uncertainties
9
9
  waveforms/ # one miniSEED file per channel
10
10
  stations/inventory.xml # StationXML for those channels
@@ -20,6 +20,14 @@ Datasets that ship with the repository have their own loader —
20
20
  :func:`load_pnr_2019` — and need no network. Published ones are fetched by
21
21
  :func:`load`, cached by pooch and pinned by hash. :mod:`specmod.acquire`
22
22
  produces both.
23
+
24
+ A time in a file or directory name is written by :func:`path_name`, in ISO 8601
25
+ basic format — ``20190826T073047.000000Z`` rather than
26
+ ``2019-08-26T07:30:47.000000Z``. The extended form's colons are not legal in a
27
+ Windows file name, and uv 0.13 refuses to build a source distribution that
28
+ contains one. Directories written before 0.7 used the extended form;
29
+ :func:`migrate_names` renames them, and :meth:`Event.directory` still finds an
30
+ unmigrated one until 0.8.
23
31
  """
24
32
 
25
33
  from __future__ import annotations
@@ -27,6 +35,8 @@ from __future__ import annotations
27
35
  import glob
28
36
  import json
29
37
  import os
38
+ import re
39
+ import warnings
30
40
  from dataclasses import dataclass
31
41
  from pathlib import Path
32
42
  from typing import Any
@@ -42,11 +52,45 @@ __all__ = [
42
52
  "data_dir",
43
53
  "load",
44
54
  "load_pnr_2019",
55
+ "migrate_names",
56
+ "path_name",
45
57
  ]
46
58
 
47
59
  #: Event directories, relative to the repository root.
48
60
  EVENTS = Path("tutorial") / "data" / "events"
49
61
 
62
+ #: A time in ISO 8601 extended format, as names were written before 0.7: the
63
+ #: form :func:`str` gives an ``obspy.UTCDateTime``. The colons are what
64
+ #: :func:`migrate_names` exists to remove, so a time without them is left alone.
65
+ _EXTENDED_TIME = re.compile(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?")
66
+
67
+
68
+ def path_name(time: Any) -> str:
69
+ """``time`` as it is written in a file or directory name.
70
+
71
+ ISO 8601 basic format with microseconds, ``20190826T073047.000000Z``: no
72
+ colons, which Windows does not allow in a name and uv 0.13 does not allow
73
+ in a source distribution, and the same fixed width for every time, so names
74
+ sort chronologically. Microseconds are kept so that two events in the same
75
+ second do not share a directory.
76
+
77
+ Takes anything ``obspy.UTCDateTime`` accepts, including a name this
78
+ function produced, so applying it twice changes nothing.
79
+
80
+ >>> path_name("2019-08-26T07:30:47.000000Z")
81
+ '20190826T073047.000000Z'
82
+ >>> path_name("2019-08-26T07:49:24.2")
83
+ '20190826T074924.200000Z'
84
+ """
85
+ import obspy # noqa: PLC0415
86
+
87
+ return obspy.UTCDateTime(time).strftime("%Y%m%dT%H%M%S.%fZ")
88
+
89
+
90
+ def _renamed(name: str) -> str:
91
+ """``name`` with every extended-format time in it rewritten by :func:`path_name`."""
92
+ return _EXTENDED_TIME.sub(lambda match: path_name(match.group(0)), name)
93
+
50
94
 
51
95
  @dataclass(frozen=True)
52
96
  class EventDirectory:
@@ -133,13 +177,32 @@ class Event:
133
177
  catalogue_magnitude_type: str | None = None
134
178
 
135
179
  def directory(self, project_root: Path | str) -> EventDirectory:
136
- """Locate this event beneath ``project_root``, the repository root."""
137
- return EventDirectory(Path(project_root) / EVENTS / self.origin)
180
+ """Locate this event beneath ``project_root``, the repository root.
181
+
182
+ The directory is named by :func:`path_name`. One written before 0.7,
183
+ named by the origin itself, is still found when the new name is
184
+ absent, with a :class:`DeprecationWarning`; that fallback is removed
185
+ in 0.8. ``specmod migrate-names`` renames it.
186
+ """
187
+ events = Path(project_root) / EVENTS
188
+ current = events / path_name(self.origin)
189
+ legacy = events / self.origin
190
+ if not current.exists() and legacy.is_dir():
191
+ warnings.warn(
192
+ f"{legacy} is named in the pre-0.7 layout, whose colons are "
193
+ f"not legal on Windows; it is read for now, and will not be "
194
+ f"found from 0.8. Run `specmod migrate-names {events}` to "
195
+ f"rename it to {current.name}.",
196
+ DeprecationWarning,
197
+ stacklevel=2,
198
+ )
199
+ return EventDirectory(legacy)
200
+ return EventDirectory(current)
138
201
 
139
202
 
140
203
  #: Preston New Road, 26 August 2019 — the induced event the tutorial and both
141
204
  #: golden references are built around, and the largest of the PNR-2 sequence.
142
- #: The origin time doubles as the directory name.
205
+ #: The origin time, through :func:`path_name`, is the directory name.
143
206
  #:
144
207
  #: ``Mw 2.9`` is from the PNR-2 catalogue published with Cuadrilla's
145
208
  #: hydraulic-fracture monitoring (NGDC, `709cbc2f-af5c-4d09-a4ea-6deb5aa8c5d8
@@ -314,3 +377,78 @@ def load(name: str, *, downloader: Any = None) -> Dataset:
314
377
  json.loads(manifest_file.read_text()) if manifest_file.is_file() else None
315
378
  )
316
379
  return Dataset(event=spec.event, paths=paths, manifest=manifest)
380
+
381
+
382
+ # --------------------------------------------------------------- migrating
383
+
384
+
385
+ def migrate_names(
386
+ root: Path | str, *, dry_run: bool = False
387
+ ) -> list[tuple[Path, Path]]:
388
+ """Rename every file and directory under ``root`` to the 0.7 layout.
389
+
390
+ Each ISO 8601 extended-format time in a name is rewritten by
391
+ :func:`path_name`, so ``2019-08-26T07:30:47.000000Z/`` becomes
392
+ ``20190826T073047.000000Z/`` and a waveform file
393
+ ``LV.L001..HHE_2019-08-26T07:30:47.000000Z`` becomes
394
+ ``LV.L001..HHE_20190826T073047.000000Z``. Nothing else in a name changes,
395
+ and no file's contents change, so every hash a manifest records stays
396
+ valid. What a ``manifest.json`` under ``root`` changes is its ``files``
397
+ keys, which are paths: they are rewritten the same way, so
398
+ ``specmod fetch --verify`` still passes afterwards.
399
+
400
+ Returns ``(old, new)`` for every rename, deepest first, which is the order
401
+ they are made in. With ``dry_run`` nothing is renamed or rewritten. Running
402
+ it again on a migrated tree finds nothing to do.
403
+
404
+ Refuses before renaming anything if a new name is already taken, so a tree
405
+ that holds both layouts of one event is left for a person to reconcile
406
+ rather than half-migrated.
407
+ """
408
+ root = Path(root)
409
+ if not root.is_dir():
410
+ raise NotADirectoryError(f"{root} is not a directory")
411
+
412
+ # Deepest first, so a directory is renamed after everything inside it and
413
+ # no path in the plan is invalidated by an earlier rename.
414
+ renames: list[tuple[Path, Path]] = []
415
+ for path in sorted(root.rglob("*"), key=lambda p: len(p.parts), reverse=True):
416
+ new_name = _renamed(path.name)
417
+ if new_name != path.name:
418
+ renames.append((path, path.with_name(new_name)))
419
+
420
+ targets = [new for _, new in renames]
421
+ taken = [new for new in targets if new.exists()]
422
+ if len(set(targets)) != len(targets) or taken:
423
+ clash = taken or [t for t in targets if targets.count(t) > 1]
424
+ raise FileExistsError(
425
+ f"migrating {root} would overwrite {sorted(set(map(str, clash)))}. "
426
+ f"Nothing was renamed: this tree holds both layouts of the same "
427
+ f"data, and which copy to keep is not a choice to make silently."
428
+ )
429
+
430
+ # Where each manifest will be once its directories are renamed.
431
+ manifests = [
432
+ root.joinpath(*(_renamed(part) for part in path.relative_to(root).parts))
433
+ for path in root.rglob("manifest.json")
434
+ if path.is_file()
435
+ ]
436
+ if dry_run:
437
+ return renames
438
+
439
+ for old, new in renames:
440
+ old.rename(new)
441
+
442
+ for manifest_file in manifests:
443
+ manifest = json.loads(manifest_file.read_text())
444
+ files = manifest.get("files")
445
+ if not isinstance(files, dict):
446
+ continue
447
+ migrated = {
448
+ "/".join(_renamed(part) for part in name.split("/")): digest
449
+ for name, digest in files.items()
450
+ }
451
+ if migrated != files:
452
+ manifest["files"] = dict(sorted(migrated.items()))
453
+ manifest_file.write_text(json.dumps(manifest, indent=1) + "\n")
454
+ return renames
@@ -41,3 +41,4 @@ class UTCDateTime:
41
41
  def second(self) -> int: ...
42
42
  @property
43
43
  def microsecond(self) -> int: ...
44
+ def strftime(self, format: str) -> str: ...