Skip to content

Release v0.8.2#138

Merged
MitchellAcoustics merged 81 commits into
mainfrom
dev
May 10, 2026
Merged

Release v0.8.2#138
MitchellAcoustics merged 81 commits into
mainfrom
dev

Conversation

@MitchellAcoustics

Copy link
Copy Markdown
Owner

This PR brings dev to main for the v0.8.2 release. It consolidates the entire v0.8.0rc1..v0.8.0rc10 + v0.8.2.dev1 pre-release line into a single shipping version. The last user-facing stable on PyPI was v0.7.8, so most users are upgrading across a much larger gap than the version number suggests.

Summary of what dev adds since main (v0.8.0rc10)

User-visible code

  • CircE / SATP module (#130, #132) — fit_circe, ipsatize, CircE/CircEResults, CircModelE, SATPVersion enum, normalize_polar_angles, person_center. Schema validation now raises by default; pass errors="warn" for the older permissive behaviour. Default ipsatize method is now grand_mean (matches the published R reference).
  • Embedded CircE R runtime — CircE R scripts are bundled and sourced through rpy2; CircE no longer needs to be installed from GitHub.
  • R wrapper cleanup (#127) — DataFrame mutation bug fixed, R output capture for cleaner debugging.
  • RTHORR removed (#125).

Project tooling

  • Switched from uv-only to mixed uv + pixi (#134) — pixi owns env management & dev tasks (lint, tests, docs build, conda publish); uv still owns wheel/sdist build, version updates, and PyPI publish.
  • Manual uv-managed versioningpixi run version wraps uv version; no setuptools-scm.
  • Revamped testing setup — per-directory conftests use pytest.importorskip; new slim-install test-import-tripwire task; matrix envs test/test-audio/test-r/test-all.
  • Docs migrated from mkdocs to zensicalzensical.toml replaces mkdocs.yml.
  • SPEC 1 lazy loading (#137) — top-level via lazy_loader.attach_stub driven by __init__.pyi; level-2 inside audio, spi, satp. New _optional.py helper standardises ImportError messages. PEP 561 stubs let mypy/pyright/IDEs resolve from soundscapy.audio import Binaural.

Release prep included in this PR

  • Bumped pyproject.toml version 0.8.2.dev1 → 0.8.2.
  • Reorganised CHANGELOG.md and docs/CHANGELOG.md to lead with breaking changes under a single 0.8.2 heading.
  • Added docs/news.md release announcement.
  • New docs/migration-0.7-to-0.8.md upgrade guide; wired into zensical.toml nav.
  • Refreshed README.md disclaimer + upgrade signpost.

For the user-facing upgrade story (CircumplexPlot → ISOPlot, Plotly removal, [r] extras, fit_circe/ipsatize default changes), see docs/migration-0.7-to-0.8.md.

Test plan

  • test.yml CI matrix passes on this PR
  • test-conda-install.yml passes
  • Slim-install test-import-tripwire confirms import soundscapy pulls no optional deps
  • After tag v0.8.2, tag-release.yml builds wheel + sdist, publishes to PyPI, creates GitHub release
  • Post-publish: pip install soundscapy==0.8.2 and pip install "soundscapy[audio]==0.8.2" smoke tests pass via the existing retry-install steps in tag-release.yml

🤖 Generated with Claude Code

MitchellAcoustics and others added 30 commits September 18, 2025 00:57
Signed-off-by: Andrew Mitchell <a.j.mitchell@ucl.ac.uk>
- Add CircE package availability checking and version validation
- Integrate CircE package into R session initialization and management
- Update package installation to handle GitHub-hosted CircE package
- Modify session getter to return CircE package instance
- Update tests to verify CircE package checking functionality
- Implement BFGS algorithm wrapper for circe package
- Add model type enumeration and constraints handling
- Update rpy2 dependency to include [all] extras
- Export EQUAL_ANGLES from survey_utils module
Add CircEResult dataclass and bfgs wrapper function to public API, with improved type validation and result extraction from R circe package.
…d update imports

Signed-off-by: Andrew Mitchell <a.j.mitchell@ucl.ac.uk>
Signed-off-by: Andrew Mitchell <a.j.mitchell@ucl.ac.uk>
Signed-off-by: Andrew Mitchell <a.j.mitchell@ucl.ac.uk>
Signed-off-by: Andrew Mitchell <a.j.mitchell@ucl.ac.uk>
Signed-off-by: Andrew Mitchell <a.j.mitchell@ucl.ac.uk>
Signed-off-by: Andrew Mitchell <a.j.mitchell@ucl.ac.uk>
Signed-off-by: Andrew Mitchell <a.j.mitchell@ucl.ac.uk>
Signed-off-by: Andrew Mitchell <a.j.mitchell@ucl.ac.uk>
Signed-off-by: Andrew Mitchell <a.j.mitchell@ucl.ac.uk>
Signed-off-by: Andrew Mitchell <a.j.mitchell@ucl.ac.uk>
refactor: remove RTHORR package integration and related checks
#127)

* fix: remove global rpy2 converters, fix n bug, and lazy R session init

- Replace deprecated numpy2ri.activate()/pandas2ri.activate() with
  explicit context managers throughout _rsn_wrapper.py, resolving
  issue #111
- Add _r2np() helper for consistent R→numpy conversion via context manager
- Fix correctness bug: bfgs() now requires explicit n (sample count)
  parameter instead of silently using data_cor.shape[0] (which returned
  8 — the variable count — not the participant count), causing incorrect
  chi-square and RMSEA statistics
- Update CircE.compute_bfgs_fit() and SATP.run() to pass the correct n
- Remove module-level get_r_session() calls from _circe_wrapper.py and
  _rsn_wrapper.py; session is now acquired lazily inside each function
- Remove _raw_bfgs_fit: ro.ListVector from CircE dataclass to avoid
  keeping live R objects in long-lived Python dataclasses
- Remove unused ro import and ConfigDict from circe.py

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: use scipy for chi2 p-value and restore ConfigDict in CircE

Replace stats_package.pchisq() with scipy.chi2.sf() in extract_bfgs_fit()
to avoid the rpy2 py2rpy conversion failure: pandas2ri.converter produces
pandas Series from rpy2py, which cannot be passed back to R outside the
context. scipy.chi2.sf(x, df) == 1 - pchisq(x, df) by definition.

Also restore ConfigDict(arbitrary_types_allowed=True) on the CircE
dataclass, required for the polar_angles: pd.DataFrame | None field.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: remove R object storage from MultiSkewNorm and tighten bfgs() context

MultiSkewNorm.fit() was storing the rpy2 RS4 selm_model object as an
instance attribute, creating a persistent reference into R's heap that
can outlive the R session. Since cp and dp are extracted immediately
after fitting, the R object is no longer needed. Sampling now always
goes through the dp (Direct Parameters) branch, which constructs the
required R vectors on the fly.

Also extend the converter context in bfgs() to include the as_matrix()
call, keeping all DataFrame→R operations within the same context boundary.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: lazy-load R modules and improve optional-dep handling

soundscapy/__init__.py
- Replace unconditional try/except imports of spi/satp with module-level
  __getattr__ + __dir__ (PEP 562). R now only starts when the user first
  accesses sspy.satp, sspy.spi, sspy.SATP, sspy.MultiSkewNorm, etc.
- Use importlib.import_module() for dynamic sub-module loading.
- __dir__ exposes all lazy names to dir() and IDE autocomplete.

_r_wrapper.py
- Add RSession NamedTuple return type for get_r_session(). Callers now
  use r.sn / r.base / r.circe instead of fragile positional unpacking.
- Add _ver() helper for tuple-based version comparison, fixing a
  lexicographic bug where "1.10" < "1.2" was True (affected CircE
  and sn version checks).
- Rename copy-paste error _raise_sn_check_error -> _raise_circe_check_error
  inside check_circe_package().
- Remove stale comment about imports "not used in the code".

_circe_wrapper.py / _rsn_wrapper.py
- Update all get_r_session() call sites to use RSession named fields.
- Move base.as_matrix() call back outside the pandas2ri converter context
  to prevent its R-matrix return value being auto-converted to numpy.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: update MSN tests to reflect removal of selm_model attribute

Remove `selm_model` assertions from test_init and three fit tests
(test_fit_with_dataframe, test_fit_with_numpy_array, test_fit_with_x_y)
since the R RS4 object is no longer stored on the instance.

Update test_sample_not_fitted_or_defined error message match to reflect
the new message raised by MultiSkewNorm.sample() rather than the old
message from the underlying _rsn_wrapper.py sample_msn() function.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: correct p-value df, clean up r_wrapper, add CircE integration tests

- Fix p-value calculation in extract_bfgs_fit() to use model df ('d') not
  null model df ('dfnull'); p was ~0.95 instead of the correct ~0.041
- Fix circe.py CircE.df field to map from 'd' not 'dfnull'
- Remove dead 'R not found' error paths from check_r_availability()
  (R is always already running by the time the function is called)
- Rename shutdown_r_session() -> reset_r_session() to accurately reflect
  that only Python package references are cleared; R process stays alive
- Update test_r_wrapper.py and test_MSN.py for the above changes
- Replace stale test_cp2dp skip with a dp→cp→dp2cp round-trip test
- Add test/satp/test_circe.py: integration tests for bfgs() and
  extract_bfgs_fit() verified against the vocational interests example
  from CircE.BFGS.Rd (exact reference values from the R package), plus
  structural tests for CircE.compute_bfgs_fit() and SATP pipeline

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: improve public API, install prompt, and warning placement

- Strip r_wrapper.__all__ to empty: it is an internal implementation
  package; user-facing names live in soundscapy.spi / soundscapy.satp
- Replace AUTO_INSTALL_R_PACKAGES=True with _confirm_install_r_packages():
  checks SOUNDSCAPY_AUTO_INSTALL_R env var first (CI/script opt-in),
  then prompts interactively when stdin is a TTY, otherwise does nothing
- Move experimental UserWarning from module-level into SATP.__init__ and
  MultiSkewNorm.__init__ so stacklevel=2 points at the user's call site
  and Python's default filter deduplicates per call location
- Remove now-unused `import warnings` from satp/__init__ and spi/__init__
- Add spi_score to _SPI_ATTRS so sspy.spi_score works via lazy loading
- Add comment in _r_wrapper.py and _rsn_wrapper.py explaining that R
  starts unconditionally when rpy2 is imported

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: polar_angles key name, type comparison, scalar normalisation, PKG_SRC ordering

- circe.py from_bfgs(): fix model_type comparison — was comparing a ModelType
  dataclass against CircModelE enum members (always False); must compare
  model_type.name against the enum
- circe.py from_bfgs(): fix key name — R returns 'polar.angles' (dot), not
  'polar_angles' (underscore); polar_angles was always None for every model
- _circe_wrapper.py extract_bfgs_fit(): normalise all shape-(1,) numpy arrays
  to Python scalars so callers never need .item() and numpy >= 1.25
  DeprecationWarnings are eliminated
- _r_wrapper.py: move PKG_SRC definition above _confirm_install_r_packages()
  which references it, fixing forward-reference style issue
- test_circe.py: add polar_angles tests for free-angle and constrained models;
  update .item() calls that are now redundant after scalar normalisation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: remove unused calc_cp and calc_dp from _rsn_wrapper

Both functions are dead code — no call site exists in src, tests, or docs.
MultiSkewNorm.fit() calls selm() + extract_cp() + extract_dp() separately
to share the single fitted model object; calc_cp/dp would call selm() twice,
doubling R computation for the most common use case.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: clean up r_wrapper error handling, fix DataFrame mutation bug, improve docs and test coverage

- Remove redundant `from rpy2 import robjects` re-imports in check_sn_package,
  check_circe_package, and initialize_r_session (module-level import already covers these)
- Replace fragile `if "sn" in str(e)` / `if "CircE" in str(e)` string-matching with
  canonical `except ImportError: raise` / `except Exception as e:` split
- Fix MultiSkewNorm.fit() mutating the caller's DataFrame in-place via data.columns=["x","y"];
  now copies before renaming
- Fix stale MultiSkewNorm class docstring: ks2ds→ks2d2s, spi→spi_score, add sample_mtsn
- Expand msn.py module docstring to document standalone public functions
- Replace vague TODO in DirectParams.from_cp warning with specific message
- Remove dead test_initialize_r_session_fails (always skipped, assertions referenced
  non-existent error strings)
- Add _ver() unit tests covering lexicographic comparison correctness
- Add sample_mtsn() tests: shape, bounds enforcement, sample_data storage, unfitted error
- Add from_params() branch coverage: DirectParams, CentredParams, CP kwargs, no-args error
- Add test_fit_does_not_mutate_input_dataframe regression test
- Replace TODO comments in ks2d2s/spi_score tests with concrete range assertions
- Add test_soundscapy_satp_module and test_satp_import_error to test_basic.py for
  symmetric SATP coverage alongside existing SPI tests
- Remove commented-out dead assertions in test_soundscapy_spi_module

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: address three code-review issues in PR #127

- Regression test for DataFrame mutation was vacuous (MOCK_DF already
  has ["x","y"] columns); use ["ISOPleasant","ISOEventful"] so a
  reintroduced bug would be visible
- Fix PKG_SRC.CIRCE assertion to use .value (str(enum) yields
  "PKG_SRC.CIRCE", not the underlying install-path string)
- from_params(xi=..., omega=..., alpha=...) was leaving instance.cp=None;
  add CentredParams.from_dp() call after setting instance.dp
- Add test_from_params_with_xi_omega_alpha_kwargs_sets_cp to cover the
  fixed branch

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* style: fix ruff lint and format issues in changed modules

- r_wrapper/__init__.py: add noqa F401 on re-export imports
- _r_wrapper.py: noqa N801 (PKG_SRC), noqa BLE001 (exception wrappers),
  restructure try/except/else to satisfy TRY300
- msn.py: shorten over-long comment (E501)
- test_MSN.py: rename df→input_df (PD901), shorten docstring (E501),
  add match= to pytest.raises (PT011)
- test_circe.py: use [*PAQ_IDS, ...] unpacking (RUF005), inline rename
  for RET504, shorten assertion messages (E501), fix docstring (D205)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: address code-review issues (correctness, masking, CI packages)

**CIRCUMPLEX model equal_ang was wrong (correctness bug)**
The four CircE models form a 2×2 grid of {equal, free} × {angles, comms}.
EQUAL_COM should have *free* angles (equal_ang=False), and CIRCUMPLEX
should have *both* constrained (equal_ang=True, equal_com=True). The
previous sets had them swapped, so CIRCUMPLEX and EQUAL_COM were
producing identical CircE_BFGS calls. Confirmed by the polar_angles tests.

**ImportError masking in check_r_availability**
_raise_r_version_too_old_error raises ImportError, which was caught by
the bare `except Exception` and re-wrapped with "Error querying R version",
swallowing the informative "R version X is too old" message. Added
`except ImportError: raise` before the generic handler.

**R version float formula replaced with _ver() tuple comparison**
`major + minor/10` fails for multi-digit minor versions and is
inconsistent with the _ver() utility added for exactly this purpose.
Now uses `paste(major, minor, sep='.')` → _ver() comparison.

**Double-ipsatize guard**
ipsatize() had an _ipsatized flag but never checked it. A second call
would KeyError because groupby.transform drops the `participant` column.
Added an early return with a warning.

**numpy import moved to module level in _circe_wrapper.py**

**DESCRIPTION: add CircE GitHub remote for CI**
setup-r-dependencies installs packages from DESCRIPTION. Only `sn` was
listed; `CircE` (from GitHub) was missing, causing the py311-r CI job to
fail with PackageNotInstalledError.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: add CircE GitHub dependency via usethis

Use usethis::use_dev_package() to add CircE to DESCRIPTION in the
canonical R format: Imports field with version pin (>= 1.1) plus a
Remotes entry pointing to MitchellAcoustics/CircE-R on GitHub.

r-lib/actions/setup-r-dependencies reads this via pak, which fetches
the GitHub repo DESCRIPTION to resolve the package name → source
mapping. No workflow changes needed; this is the standard R approach
for non-CRAN dependencies.

Also reverts the erroneous extra-packages workflow addition from the
previous commit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: use pak-required Package=user/repo syntax for CircE GitHub remote

pak/pkgdepends cannot auto-map a package name to a repository when they
differ (CircE package vs CircE-R repo). The explicit `CircE=MitchellAcoustics/CircE-R`
syntax in the Remotes field tells pak which GitHub repo satisfies the
`CircE (>= 1.1)` Imports dependency, fixing the CI failure in
setup-r-dependencies.

Also removes the commented-out (dead) Rscript lines from the py-r tox
environment.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: add explicit R package install to py-r tox env and fix CircE pak syntax

setup-r-dependencies installs R packages into a library that rpy2
cannot see from within the tox virtualenv. The explicit
`Rscript -e "if(!require(...)) { pak::... }"` pattern in commands_pre
is the reliable workaround (same pattern py-all already uses successfully).

Changes:
- py-r: add Rscript install steps for sn and CircE (was missing entirely)
- py-all, py-tutorials: update CircE pak ref to explicit Package=user/repo
  syntax (`CircE=MitchellAcoustics/CircE-R`) required when the package name
  differs from the repo name

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* style: clean up tox.ini comments and remove redundant header

- Remove redundant `# tox.ini` file header
- Unify R install comment across py-r, py-all, py-tutorials to accurately
  describe both packages being installed (sn and CircE)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: address six code-review issues in R wrapper and tox config

- _r_wrapper.py: add missing single-quotes around PKG_SRC.CIRCE.value in
  three CircE error-message f-strings (produced invalid R syntax)
- _r_wrapper.py: remove dead 'tvtnorm' from install_r_packages() default
  list and update stale docstring
- _r_wrapper.py: reset _sn_checked/_circe_checked in reset_r_session() and
  update docstring to accurately describe re-verification behaviour
- _rsn_wrapper.py: add max_iter=100_000 guard to sample_mtsn() rejection
  loop; raises RuntimeError with a clear message instead of hanging forever
  when the distribution has negligible mass inside [a, b]
- circe.py: change self.data annotation from pandera DataFrame to pd.DataFrame
  since ipsatize() overwrites it with an unvalidated result
- tox.ini: replace pak::local_install_deps() with pak::pkg_install('sn') for
  direct, reliable sn installation; add quietly=TRUE to both require() guards

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: code-review round 2 — sentinel bug, type fixes, rename CircE.df→d

- sample_mtsn: replace sentinel-row + np.append loop with list
  accumulation + np.vstack; add max_iter guard against infinite loops
- REQUIRED_R_VERSION: change from float 3.6 to str "3.6" to avoid
  str(4.10) == "4.1" pitfall with future two-digit minor versions
- reset_r_session: reset _sn_checked/_circe_checked flags so next call
  re-verifies package availability from scratch
- test_r_wrapper: remove unreachable SPI_DEPS branching; tests already
  gated by optional_deps("r") so both branches always run with R present
- msn.py: remove ks2d() from module docstring — no such top-level fn
- CircE.df → CircE.d to match raw R output key; update from_bfgs(),
  test_circe.py (4 refs), and scratch/circe_test.py integration script

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: consolidate 9 module globals into RSession dataclass

Replace the nine scattered module-level globals (_r_checked, _sn_checked,
_circe_checked, _r_session, _sn_package, _circe_package, _stats_package,
_base_package, _session_active) with a single @DataClass RSession instance
(_state).

Key changes:
- RSession promoted from NamedTuple return type to mutable @DataClass state
  container, adding active/r_checked/sn_checked/circe_checked fields and an
  is_ready property
- _state = RSession() is the sole module-level state; functions mutate its
  attributes directly — no global declarations needed (mutating an object
  attribute does not rebind the module-level name)
- reset_r_session() becomes: global _state; _state = RSession() — atomically
  clears all state including check flags, making it impossible to forget a
  field (fixes the _r_checked omission found in code review)
- get_r_session() validation collapses to: if not _state.is_ready
- initialize_r_session() error path calls reset_r_session() for clean retry
- is_session_active() needs no global declaration
- Module docstring updated to explain the _state singleton pattern

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Andrew Mitchell <andrew.mitchell.research@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…iner

Implements the top-priority recommendations from the SATP circumplex
validation analysis review:

- fix: SATPSchema now raises SchemaErrors by default instead of silently
  dropping invalid rows (drop_invalid_rows=False); fit_circe gains an
  errors='raise'|'warn' parameter for opt-in lenient behaviour

- fix: fit_circe uses grand-mean centering (1 scalar/participant) matching
  the published SATP R analysis; column-wise centering (8 scalars/participant)
  was the previous incorrect default, causing SRMR values ~0.005 higher than
  canonical R values

- feat: add soundscapy.surveys.ipsatize(method='grand_mean'|'column_wise'|
  'row_wise') — unified ipsatization entry point living in core surveys/
  module (no R dependency); person_center() becomes a thin wrapper with a
  deprecation note

- feat: fit_circe returns CircEResults instead of a bare DataFrame; provides
  .table (DataFrame), .for_model(CircModelE), ._repr_html_(), and len();
  CircE dataclass instances are preserved with all typed attributes

- test: new TestIpsatize in test/surveys/ (core, no R); new CircEResults
  tests; existing test_circe.py updated for new return type

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- fix(ipsatize): use np.nanmean() for grand-mean centering so participants
  with partial NaN data get a valid grand mean from available values, rather
  than the whole participant being silently wiped when any NaN is present

- fix(fit_circe): use .loc[~index.isin()] instead of .drop(index=) in the
  errors='warn' path to correctly handle duplicate DataFrame indices; report
  accurate remaining row count; wrap second validate() call to prevent
  unhandled SchemaErrors leaking from the warn path

- docs: clarify models=[] returns CircEResults not a DataFrame; disambiguate
  'the default' in person_center deprecation note

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
MitchellAcoustics and others added 10 commits May 10, 2026 14:20
Replaces marker-based optional test filtering with import-based skipping to match runtime behavior.
Adds slim-install guard coverage and helper-unit tests to prevent eager optional imports.
Aligns local and CI tasks so matrix jobs exercise the same test entry points.
Explains the new gate-and-deferral model and clarifies how contributors add optional features safely.
Updates workflow guidance to reflect current tooling and task structure.
Captures the full developer-experience improvements in release notes.
Improves version-task flexibility and ensures publish flows refresh generated environment artifacts.
Updates environment constraints for compatibility and removes redundant runtime variables.
Cleans up obsolete packaging metadata while advancing development versioning.
Removes outdated lint suppressions and adopts the standard library typing import where available.
Stabilizes logging tests by restoring logger state after debug assertions.
Keeps incidental quality fixes separate from larger behavior changes for easier review.
Switches local IDE environment and package manager defaults to the project-managed workflow.
Reduces setup drift for contributors working in the shared development environment.
Both branches added dev dependencies; combined types-tqdm (our branch)
and jupyter-repo2docker (dev branch) in feature.dev.dependencies.
Kept our improved test tasks and pixi.lock.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…oading (#137)

refactor: standardize optional dependency handling with SPEC 1 lazy loading
…bump

Consolidates the v0.8.0rc1..v0.8.0rc10 + v0.8.2.dev1 pre-release line into
a single shipping release. Reorganises CHANGELOG.md (and the docs mirror)
to lead with breaking changes, adds a dedicated upgrade-from-0.7 guide,
adds a release announcement to docs/news.md, wires the migration page
into the zensical nav, refreshes the README disclaimer, and bumps
pyproject.toml from 0.8.2.dev1 to 0.8.2.

No code changes — release prep only.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Release PR that merges dev into main for Soundscapy v0.8.2, consolidating the long prerelease line into a single stable release. It updates packaging/tooling (Pixi + uv, Zensical docs), adds/ships embedded CircE R scripts, and standardizes optional-dependency gating + lazy loading.

Changes:

  • Finalize release packaging/tooling: version bump to 0.8.2, Pixi task matrix, uv build backend, CI workflow updates, and Zensical docs config/nav.
  • Adopt SPEC 1-style lazy loading + standardized optional-dependency gates (_optional.py) with slim-install tests.
  • Documentation + changelog + migration materials refreshed for the 0.7 → 0.8 upgrade path; tests reorganized to use pytest.importorskip per optional subtree.

Reviewed changes

Copilot reviewed 129 out of 149 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
zensical.toml New Zensical site configuration + nav for migrated docs.
tox.ini Updates tox envs (docs via Zensical; spi→r extras rename).
test/test_sspylogging.py Ensures logging is cleaned up after enabling debug in tests.
test/test_slim_install.py Adds slim-install tests verifying lazy import + gate error messages.
test/test_optional_helper.py Unit tests for the new optional-deps helper.
test/test_basic.py Updates basic import tests to use pytest.importorskip for optional deps.
test/surveys/test_survey_utils.py Test refactors (naming/style + minor assertion modernizations).
test/spi/test_r_wrapper.py Updates R wrapper tests and adds pure-Python _ver tests.
test/spi/conftest.py Skips SPI test tree unless rpy2 is installed.
test/satp/conftest.py Skips SATP test tree unless rpy2 is installed.
test/satp/init.py Declares SATP test package (enables per-tree behavior).
test/plotting/test_plotting_deprecated.py Adjusts deprecated plotting tests (typing ignores, warning matching).
test/plotting/test_plot_functions.py Plot function tests updates (incl. RNG handling change).
test/generate_baselines.py Baseline generation script cleanup (Path/logging, RNG call).
test/databases/test_satp.py Updates SATP dataset load test to new import path.
test/databases/test_isd.py Test refactors + regex match tweak + type checks modernization.
test/audio/test_metrics.py Migrates unittest-style assertions to pytest asserts.
test/audio/test_binaural.py Uses default_rng for deterministic arrays; tightens error match.
test/audio/test_audio_analysis.py Uses Path.open, improves assertion formatting, tightens error match.
test/audio/test_analysis_settings.py Uses Path.open and simplifies channel assertions.
test/audio/conftest.py Skips audio test tree unless audio extras are installed.
src/soundscapy/surveys/survey_utils.py Adds a Pandera DataFrameModel schema for PAQ frames + docstring cleanup.
src/soundscapy/surveys/init.py Re-exports ipsatize from surveys processing.
src/soundscapy/sspylogging.py Docstring formatting tweaks for logging helpers.
src/soundscapy/spi/ks2d.py Docstring formatting tweaks.
src/soundscapy/spi/init.pyi Adds PEP 561 stub for lazy-loaded SPI exports.
src/soundscapy/spi/init.py Switches SPI to gate + lazy loading via stub.
src/soundscapy/satp/init.pyi Adds PEP 561 stub for lazy-loaded SATP exports.
src/soundscapy/satp/init.py Adds SATP gate + lazy loading via stub.
src/soundscapy/r_wrapper/r_circe/residual.CircE.R Bundles embedded CircE R scripts.
src/soundscapy/r_wrapper/r_circe/CircE.Plot.R Bundles embedded CircE R scripts.
src/soundscapy/r_wrapper/r_circe/char.assign.R Bundles embedded CircE R scripts.
src/soundscapy/r_wrapper/r_circe/bound.assign.R Bundles embedded CircE R scripts.
src/soundscapy/r_wrapper/_rsn_wrapper.py Refactors skew-normal wrapper conversion + adds max_iter guard for truncated sampling.
src/soundscapy/r_wrapper/_circe_wrapper.py Adds CircE BFGS wrapper + fit-stat extraction in Python.
src/soundscapy/r_wrapper/init.py Adds r_wrapper gate + controlled internal exports.
src/soundscapy/plotting/plot_context.py Docstring formatting tweaks.
src/soundscapy/plotting/likert.py Docstring improvements + removes unnecessary type: ignore on apply; adds stacked_likert docstring.
src/soundscapy/plotting/layers.py Docstring formatting tweaks + minor comment formatting.
src/soundscapy/plotting/backends_deprecated.py Tightens ruff ignore list.
src/soundscapy/databases/satp.py Adds SATPVersion enum + version normalization + updated examples.
src/soundscapy/databases/isd.py Docstring and parameter docs improvements.
src/soundscapy/audio/parallel_processing.py Docstring cleanup, error handling lint suppression, and main-guard script tweaks.
src/soundscapy/audio/audio_analysis.py Docstring cleanup + removes PERF noqa from broad exception block.
src/soundscapy/audio/analysis_settings.py Docstring cleanup + corrects “Parameters”→“Attributes” wording for models.
src/soundscapy/audio/init.pyi Adds PEP 561 stub for lazy-loaded audio exports.
src/soundscapy/audio/init.py Switches audio to gate + lazy loading via stub.
src/soundscapy/_optional.py New helper for consistent optional-dependency ImportErrors without side effects.
src/soundscapy/init.pyi Adds PEP 561 stub driving top-level lazy re-exports.
src/soundscapy/init.py Adds SPEC 1 lazy loading + switches __version__ to package metadata + reorganizes exports.
scripts/update-environment-yml.py Adds script to sync exported conda env pip spec with pyproject version.
README.md Updates upgrade messaging + adds [r] extra installation guidance.
pyproject.toml Release version bump, uv build backend config, new deps, extras rename spir, and ruff/pytest config updates.
pixi.toml New Pixi workspace + feature envs + tasks for lint/tests/docs/build/publish.
mkdocs.yml Removes mkdocs configuration (docs migrated to Zensical).
examples/updated_config.yaml Adds/updates example audio config file.
examples/example_settings.yaml Adds example settings YAML for audio analysis.
examples/1_Understanding_Soundscape_Analysis.ipynb Updates tutorial notebook content/metadata.
examples/.gitignore Ignores Quarto/Zensical tutorial render artifacts.
environment.yml Adds exported conda environment definition for R+audio installs.
docs/tutorials/.gitignore Ignores rendered tutorial outputs directory.
docs/stylesheets/extra.css Tweaks theme CSS (grid width, minor formatting).
docs/reference/surveys.md Reworks reference page structure for mkdocstrings under Zensical.
docs/reference/spi/msn.md Removes standalone msn reference page (consolidated).
docs/reference/spi.md Reworks SPI reference layout (separate sections).
docs/reference/soundscapy.md Adds top-level soundscapy API reference page.
docs/reference/satp.md Adds SATP reference page.
docs/reference/plotting/plot_functions.md Renames heading + adds mkdocstrings options.
docs/reference/plotting/plot_context.md Renames heading + adds mkdocstrings options.
docs/reference/plotting/param_models.md Cleans up page + mkdocstrings options.
docs/reference/plotting/likert.md Simplifies heading + mkdocstrings options.
docs/reference/plotting/layers.md Simplifies heading + mkdocstrings options.
docs/reference/plotting/iso_plot.md Renames heading + mkdocstrings options.
docs/reference/plotting/index.md Adds plotting index page for Zensical nav.
docs/reference/plotting/defaults.md Renames heading + mkdocstrings options.
docs/reference/plotting.md Removes old aggregated plotting reference page.
docs/reference/internals/utilities.md Adds developer reference page for internal utilities.
docs/reference/internals/r-internals.md Adds developer reference page for R wrappers.
docs/reference/internals/plotting-internals.md Adds developer reference page for plotting internals.
docs/reference/internals/logging.md Adds developer reference page for logging helpers.
docs/reference/internals/index.md Adds developer reference landing page.
docs/reference/index.md Adds API reference landing page + curated module links.
docs/reference/databases.md Reworks databases reference page for Zensical + mkdocstrings.
docs/reference/audio/parallel_processing.md Adds per-module audio reference page.
docs/reference/audio/metrics.md Adds per-module audio reference page.
docs/reference/audio/index.md Adds audio reference index page + structure.
docs/reference/audio/binaural.md Adds per-module audio reference page.
docs/reference/audio/audio_analysis.md Adds per-module audio reference page.
docs/reference/audio.md Removes old aggregated audio reference page.
docs/reference/api.md Removes old “core API” reference page.
docs/news.md Adds v0.8.2 release announcement + embedded CircE runtime note.
docs/migration-0.7-to-0.8.md Adds 0.7→0.8.2 migration guide.
docs/license.md Updates license include/snippet syntax for new docs toolchain.
docs/javascripts/mathjax.js Updates MathJax handling for annotation components.
docs/index.md Updates landing page messaging + adds [r] extra guidance.
docs/CHANGELOG.md Reworks docs changelog with 0.8.2 release structure and details.
DESCRIPTION Removes obsolete R DESCRIPTION file.
CHANGELOG.md Reworks root changelog with 0.8.2 release structure and details.
.vscode/settings.json Adds VS Code settings aligned with Pixi workflow and tooling.
.readthedocs.yaml Switches RTD build to Zensical output copy.
.pre-commit-config.yaml Updates pre-commit hooks (tabs, prettier, optional pyrefly stub).
.markdownlint.yaml Disables MD046 rule.
.gitignore Updates ignores (pixi envs, conda build outputs, editor configs).
.github/workflows/test.yml Migrates CI tests from tox/uv to Pixi task matrix across OSes.
.github/workflows/test-conda-install.yml Adds workflow to build and locally install conda package via Pixi.
.github/workflows/linting.yml Switches lint workflow to Pixi + Prek action.
.github/workflows/docs.yml Migrates docs workflow to Pixi + Zensical; adds Pages deploy job.
.gitattributes Marks pixi.lock as binary/linguist-generated to avoid merge conflicts.
.envrc Adds direnv setup for Pixi and local publish credentials (from local files).
.devcontainer/Dockerfile Adds devcontainer base with Pixi installed.
.devcontainer/devcontainer.json Adds devcontainer config for Pixi-driven development.
.devcontainer/devcontainer-lock.json Pins devcontainer feature versions.
Comments suppressed due to low confidence (1)

pyproject.toml:99

  • Pytest is no longer configured with an optional_deps(...) marker (see [tool.pytest.ini_options].markers), but many tests in test/spi/ and test/satp/ still use @pytest.mark.optional_deps(...). This will emit PytestUnknownMarkWarning (and can fail under stricter warning settings). Either re-add the marker declaration here, or remove/replace the marker usage now that per-directory conftest.py uses pytest.importorskip.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread test/plotting/test_plot_functions.py
Comment thread test/generate_baselines.py Outdated
Comment thread conftest.py Outdated
Comment thread tox.ini Outdated
Comment thread environment.yml
Comment thread docs/index.md
Comment thread src/soundscapy/databases/satp.py
MitchellAcoustics and others added 5 commits May 10, 2026 15:39
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Agent-Logs-Url: https://github.com/MitchellAcoustics/Soundscapy/sessions/739e517d-1701-4f3b-9b7d-dcbe307344a9

Co-authored-by: MitchellAcoustics <22335636+MitchellAcoustics@users.noreply.github.com>
Agent-Logs-Url: https://github.com/MitchellAcoustics/Soundscapy/sessions/739e517d-1701-4f3b-9b7d-dcbe307344a9

Co-authored-by: MitchellAcoustics <22335636+MitchellAcoustics@users.noreply.github.com>
MitchellAcoustics and others added 6 commits May 10, 2026 15:54
tox.ini is fully superseded by pixi tasks and workflows — CI has used
pixi exclusively since PR #134. Three dead tox references in pyproject.toml
are cleaned up alongside the deletion (coverage paths, tomlsort overrides).

The @pytest.mark.optional_deps marker was never registered in pyproject.toml
and did nothing: per-directory conftest.py files already gate test/satp/ and
test/spi/ via pytest.importorskip("rpy2") at collection time.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
test.yml: remove workflow_run chain (redundant since push/pull_request
both trigger directly), scope push to main only to prevent double runs
when pushing to a PR branch, remove broken workflow_call outputs block
(referenced github.event.inputs which was never declared).

docs.yml: remove same broken workflow_call outputs block.

linting.yml: add branch scoping (was firing on every push to every
branch in the repo); scope to push/main + pull_request/main,dev.

test-conda-install.yml: align pixi version with other workflows
(v0.67.2 → v0.68.0, setup-pixi v0.9.4 → v0.9.5).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Delete tag-release.yml and test-tag-release.yml (tag-first approach).
Add release.yml which triggers on push to main and:
  - Skips if version has a pre-release suffix (.dev, rc, a, b)
  - Skips if the git tag already exists (idempotent; handles manual publish)
  - Otherwise: runs tests + docs, builds, publishes to PyPI via OIDC,
    creates the git tag, and creates the GitHub release

If PyPI rejects a duplicate upload (version published manually without a
tag), the publish step fails and prints the git tag command needed to
reconcile the state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Allows manually re-triggering the release if a push-triggered run fails
and the follow-up fix commit is paths-ignored (e.g. a markdown-only change).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pins OIDC token issuance to the pypi GitHub environment, which is
restricted to deployments from main. PyPI's Trusted Publisher will
reject tokens issued outside this environment.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
environment.yml is a derived file from pixi.toml and pyproject.toml.
It now updates automatically on main whenever either source changes,
completely decoupled from the release process.

release.yml is simplified: no git commits, no contents:write until the
final tag step. build, publish-conda, tests-pass, and docs-pass all run
in parallel after check, with only publish-pypi waiting on build + tests
+ docs before PyPI upload.

Also adds uv >= 0.9 to [package.build-dependencies] in pixi.toml so
pixi has uv available during pixi publish (conda build step).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@MitchellAcoustics MitchellAcoustics merged commit f791cfe into main May 10, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants