From e48e95e08bf0214fa31274d4b3d6a3210eaecfaf Mon Sep 17 00:00:00 2001 From: Jayaram Kancherla Date: Wed, 31 Dec 2025 17:49:45 -0800 Subject: [PATCH 1/4] Bump se package version --- .github/workflows/publish-pypi.yml | 23 +++---- .github/workflows/run-tests.yml | 69 +++++++++++++++---- setup.cfg | 2 +- .../SingleCellExperiment.py | 54 ++++++++------- 4 files changed, 95 insertions(+), 53 deletions(-) diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 1760f1d..405fee0 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -1,6 +1,3 @@ -# This workflow will install Python dependencies, run tests and lint with a single version of Python -# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions - name: Publish to PyPI on: @@ -19,10 +16,10 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up Python 3.11 + - name: Set up Python 3.12 uses: actions/setup-python@v5 with: - python-version: 3.11 + python-version: 3.12 - name: Install dependencies run: | @@ -33,6 +30,14 @@ jobs: run: | tox + - name: Build Project and Publish + run: | + python -m tox -e clean,build + + # This uses the trusted publisher workflow so no token is required. + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + - name: Build docs run: | tox -e docs @@ -45,11 +50,3 @@ jobs: branch: gh-pages # The branch the action should deploy to. folder: ./docs/_build/html clean: true # Automatically remove deleted files from the deploy branch - - - name: Build Project and Publish - run: | - python -m tox -e clean,build - - # This uses the trusted publisher workflow so no token is required. - - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index d0c96cd..3d3bc95 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -1,32 +1,73 @@ -name: Run tests +name: Test the library on: push: - branches: [master] + branches: + - master # for legacy repos + - main pull_request: + branches: + - master # for legacy repos + - main + workflow_dispatch: # Allow manually triggering the workflow + schedule: + # Run roughly every 15 days at 00:00 UTC + # (useful to check if updates on dependencies break the package) + - cron: "0 0 1,16 * *" + +permissions: + contents: read + +concurrency: + group: >- + ${{ github.workflow }}-${{ github.ref_type }}- + ${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true jobs: - build: - runs-on: ubuntu-latest + test: strategy: matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] - - name: Python ${{ matrix.python-version }} + python: ["3.10", "3.11", "3.12", "3.13", "3.14"] + platform: + - ubuntu-latest + - macos-latest + # - windows-latest + runs-on: ${{ matrix.platform }} + name: Python ${{ matrix.python }}, ${{ matrix.platform }} steps: - uses: actions/checkout@v4 - - name: Setup Python - uses: actions/setup-python@v5 + - uses: actions/setup-python@v5 + id: setup-python with: - python-version: ${{ matrix.python-version }} - cache: "pip" + python-version: ${{ matrix.python }} - name: Install dependencies run: | python -m pip install --upgrade pip - pip install tox + pip install tox coverage - - name: Test with tox - run: | + - name: Run tests + run: >- + pipx run --python '${{ steps.setup-python.outputs.python-path }}' tox + -- -rFEx --durations 10 --color yes --cov --cov-branch --cov-report=xml # pytest args + + - name: Check for codecov token availability + id: codecov-check + shell: bash + run: | + if [ ${{ secrets.CODECOV_TOKEN }} != '' ]; then + echo "codecov=true" >> $GITHUB_OUTPUT; + else + echo "codecov=false" >> $GITHUB_OUTPUT; + fi + + - name: Upload coverage reports to Codecov with GitHub Action + uses: codecov/codecov-action@v5 + if: ${{ steps.codecov-check.outputs.codecov == 'true' }} + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + slug: ${{ github.repository }} + flags: ${{ matrix.platform }} - py${{ matrix.python }} diff --git a/setup.cfg b/setup.cfg index 1c08fa8..c2d55b7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -49,7 +49,7 @@ python_requires = >=3.9 # For more information, check out https://semver.org/. install_requires = importlib-metadata; python_version<"3.8" - summarizedexperiment>=0.5.3 + summarizedexperiment>=0.6.0 [options.packages.find] where = src diff --git a/src/singlecellexperiment/SingleCellExperiment.py b/src/singlecellexperiment/SingleCellExperiment.py index 68043cc..cf73e0d 100644 --- a/src/singlecellexperiment/SingleCellExperiment.py +++ b/src/singlecellexperiment/SingleCellExperiment.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from collections import OrderedDict from typing import Any, Dict, List, Optional, Sequence, Union from warnings import warn @@ -11,7 +13,7 @@ merge_se_rownames, relaxed_merge_assays, ) -from summarizedexperiment.RangedSummarizedExperiment import ( +from summarizedexperiment.rse import ( GRangesOrGRangesList, RangedSummarizedExperiment, ) @@ -105,7 +107,7 @@ def __init__( column_data: Optional[biocframe.BiocFrame] = None, row_names: Optional[List[str]] = None, column_names: Optional[List[str]] = None, - metadata: Optional[dict] = None, + metadata: Optional[Union[Dict[str, Any], ut.NamedList]] = None, reduced_dimensions: Optional[Dict[str, Any]] = None, reduced_dims: Optional[Dict[str, Any]] = None, # deprecated name main_experiment_name: Optional[str] = None, @@ -113,7 +115,7 @@ def __init__( row_pairs: Optional[Any] = None, column_pairs: Optional[Any] = None, alternative_experiment_check_dim_names: bool = True, - validate: bool = True, + _validate: bool = True, **kwargs, ) -> None: """Initialize a single-cell experiment. @@ -198,7 +200,7 @@ def __init__( Defaults to None. - validate: + _validate: Internal use only. kwargs: @@ -213,7 +215,7 @@ def __init__( row_names=row_names, column_names=column_names, metadata=metadata, - validate=validate, + _validate=_validate, **kwargs, ) self._main_experiment_name = main_experiment_name @@ -234,7 +236,7 @@ def __init__( self._row_pairs = row_pairs if row_pairs is not None else {} self._column_pairs = column_pairs if column_pairs is not None else {} - if validate: + if _validate: _validate_reduced_dims(self._reduced_dims, self._shape) _validate_alternative_experiments( self._alternative_experiments, @@ -283,6 +285,7 @@ def __deepcopy__(self, memo=None, _nil=[]): alternative_experiments=_alt_expt_copy, row_pairs=_row_pair_copy, column_pairs=_col_pair_copy, + _validate=False, ) def __copy__(self): @@ -304,6 +307,7 @@ def __copy__(self): alternative_experiments=self._alternative_experiments, row_pairs=self._row_pairs, column_pairs=self._column_pairs, + _validate=False, ) def copy(self): @@ -403,7 +407,7 @@ def get_reduced_dims(self) -> Dict[str, Any]: """Alias for :py:meth:`~get_reduced_dimensions`, for back-compatibility.""" return self.get_reduced_dimensions() - def set_reduced_dimensions(self, reduced_dims: Dict[str, Any], in_place: bool = False) -> "SingleCellExperiment": + def set_reduced_dimensions(self, reduced_dims: Dict[str, Any], in_place: bool = False) -> SingleCellExperiment: """Set new reduced dimensions. Args: @@ -423,7 +427,7 @@ def set_reduced_dimensions(self, reduced_dims: Dict[str, Any], in_place: bool = output._reduced_dims = reduced_dims return output - def set_reduced_dims(self, reduced_dims: Dict[str, Any], in_place: bool = False) -> "SingleCellExperiment": + def set_reduced_dims(self, reduced_dims: Dict[str, Any], in_place: bool = False) -> SingleCellExperiment: """Alias for :py:meth:`~set_reduced_dimensions`, for back-compatibility.""" return self.set_reduced_dimensions(reduced_dims=reduced_dims, in_place=in_place) @@ -471,7 +475,7 @@ def get_reduced_dim_names(self) -> Dict[str, Any]: """Alias for :py:meth:`~get_reduced_dimension_names`, for back-compatibility.""" return self.get_reduced_dimension_names() - def set_reduced_dimension_names(self, names: List[str], in_place: bool = False) -> "SingleCellExperiment": + def set_reduced_dimension_names(self, names: List[str], in_place: bool = False) -> SingleCellExperiment: """Replace :py:attr:`~.reduced_dims`'s names. Args: @@ -497,7 +501,7 @@ def set_reduced_dimension_names(self, names: List[str], in_place: bool = False) output._reduced_dims = new_reduced_dims return output - def set_reduced_dim_names(self, names: List[str], in_place: bool = False) -> "SingleCellExperiment": + def set_reduced_dim_names(self, names: List[str], in_place: bool = False) -> SingleCellExperiment: """Alias for :py:meth:`~set_reduced_dimension_names`, for back-compatibility.""" return self.set_reduced_dimension_names(names=names, in_place=in_place) @@ -573,7 +577,7 @@ def reduced_dimension(self, name: Union[str, int]) -> Any: """Alias for :py:meth:`~get_reduced_dimension`, for back-compatibility.""" return self.get_reduced_dimension(name=name) - def set_reduced_dimension(self, name: str, embedding: Any, in_place: bool = False) -> "SingleCellExperiment": + def set_reduced_dimension(self, name: str, embedding: Any, in_place: bool = False) -> SingleCellExperiment: """Add or replace :py:attr:`~singlecellexperiment.SingleCellExperiment.reduced_dimension`'s. Args: @@ -613,7 +617,7 @@ def get_main_experiment_name(self) -> Optional[str]: """ return self._main_experiment_name - def set_main_experiment_name(self, name: Optional[str], in_place: bool = False) -> "SingleCellExperiment": + def set_main_experiment_name(self, name: Optional[str], in_place: bool = False) -> SingleCellExperiment: """Set new experiment data (assays). Args: @@ -671,7 +675,7 @@ def get_alternative_experiments(self, with_dim_names: bool = True) -> Dict[str, def set_alternative_experiments( self, alternative_experiments: Dict[str, Any], with_dim_names: bool = True, in_place: bool = False - ) -> "SingleCellExperiment": + ) -> SingleCellExperiment: """Set new alternative experiments. Args: @@ -725,7 +729,7 @@ def get_alternative_experiment_names(self) -> List[str]: """ return list(self._alternative_experiments.keys()) - def set_alternative_experiment_names(self, names: List[str], in_place: bool = False) -> "SingleCellExperiment": + def set_alternative_experiment_names(self, names: List[str], in_place: bool = False) -> SingleCellExperiment: """Replace :py:attr:`~.alternative_experiment`'s names. Args: @@ -821,7 +825,7 @@ def alternative_experiment(self, name: Union[str, int]) -> Any: def set_alternative_experiment( self, name: str, alternative_experiment: Any, with_dim_names: bool = True, in_place: bool = False - ) -> "SingleCellExperiment": + ) -> SingleCellExperiment: """Add or replace :py:attr:`~singlecellexperiment.SingleCellExperiment.alternative_experiment`'s. Args: @@ -872,7 +876,7 @@ def get_row_pairs(self) -> Dict[str, Any]: """ return self._row_pairs - def set_row_pairs(self, pairs: Dict[str, Any], in_place: bool = False) -> "SingleCellExperiment": + def set_row_pairs(self, pairs: Dict[str, Any], in_place: bool = False) -> SingleCellExperiment: """Replace :py:attr:`~.row_pairs`'s names. Args: @@ -918,7 +922,7 @@ def get_row_pair_names(self) -> List[str]: """ return list(self._row_pairs.keys()) - def set_row_pair_names(self, names: List[str], in_place: bool = False) -> "SingleCellExperiment": + def set_row_pair_names(self, names: List[str], in_place: bool = False) -> SingleCellExperiment: """Replace :py:attr:`~.row_pair`'s names. Args: @@ -970,7 +974,7 @@ def get_column_pairs(self) -> Dict[str, Any]: """ return self._column_pairs - def set_column_pairs(self, pairs: Dict[str, Any], in_place: bool = False) -> "SingleCellExperiment": + def set_column_pairs(self, pairs: Dict[str, Any], in_place: bool = False) -> SingleCellExperiment: """Replace :py:attr:`~.column_pairs`'s names. Args: @@ -1016,7 +1020,7 @@ def get_column_pair_names(self) -> List[str]: """ return list(self._column_pairs.keys()) - def set_column_pair_names(self, names: List[str], in_place: bool = False) -> "SingleCellExperiment": + def set_column_pair_names(self, names: List[str], in_place: bool = False) -> SingleCellExperiment: """Replace :py:attr:`~.column_pair`'s names. Args: @@ -1066,7 +1070,7 @@ def get_slice( self, rows: Optional[Union[str, int, bool, Sequence]], columns: Optional[Union[str, int, bool, Sequence]], - ) -> "SingleCellExperiment": + ) -> SingleCellExperiment: """Alias for :py:attr:`~__getitem__`.""" slicer = self._generic_slice(rows=rows, columns=columns) @@ -1183,7 +1187,7 @@ def to_anndata(self, include_alternative_experiments: bool = False): return obj, adatas @classmethod - def from_anndata(cls, input: "anndata.AnnData") -> "SingleCellExperiment": + def from_anndata(cls, input: "anndata.AnnData") -> SingleCellExperiment: """Create a ``SingleCellExperiment`` from :py:class:`~anndata.AnnData`. Args: @@ -1253,19 +1257,19 @@ def to_mudata(self): ######>> combine ops <<##### ############################ - def relaxed_combine_rows(self, *other) -> "SingleCellExperiment": + def relaxed_combine_rows(self, *other) -> SingleCellExperiment: """Wrapper around :py:func:`~relaxed_combine_rows`.""" return relaxed_combine_rows(self, *other) - def relaxed_combine_columns(self, *other) -> "SingleCellExperiment": + def relaxed_combine_columns(self, *other) -> SingleCellExperiment: """Wrapper around :py:func:`~relaxed_combine_columns`.""" return relaxed_combine_columns(self, *other) - def combine_rows(self, *other) -> "SingleCellExperiment": + def combine_rows(self, *other) -> SingleCellExperiment: """Wrapper around :py:func:`~combine_rows`.""" return combine_rows(self, *other) - def combine_columns(self, *other) -> "SingleCellExperiment": + def combine_columns(self, *other) -> SingleCellExperiment: """Wrapper around :py:func:`~combine_columns`.""" return combine_columns(self, *other) From eea1aedcc5e73380c589a12418d594f05db16aef Mon Sep 17 00:00:00 2001 From: Jayaram Kancherla Date: Fri, 2 Jan 2026 18:38:17 -0800 Subject: [PATCH 2/4] Bump compressed lists --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index c2d55b7..bd70807 100644 --- a/setup.cfg +++ b/setup.cfg @@ -50,6 +50,7 @@ python_requires = >=3.9 install_requires = importlib-metadata; python_version<"3.8" summarizedexperiment>=0.6.0 + compressed-lists>=0.4.3 [options.packages.find] where = src From f875f92514c3a7e58bce45ad7606a7ff2e7fd77f Mon Sep 17 00:00:00 2001 From: Jayaram Kancherla Date: Sat, 3 Jan 2026 15:18:15 -0800 Subject: [PATCH 3/4] bump package versions --- setup.cfg | 2 +- src/singlecellexperiment/SingleCellExperiment.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index bd70807..fda72f0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -49,7 +49,7 @@ python_requires = >=3.9 # For more information, check out https://semver.org/. install_requires = importlib-metadata; python_version<"3.8" - summarizedexperiment>=0.6.0 + summarizedexperiment>=0.6.3 compressed-lists>=0.4.3 [options.packages.find] diff --git a/src/singlecellexperiment/SingleCellExperiment.py b/src/singlecellexperiment/SingleCellExperiment.py index cf73e0d..40f7391 100644 --- a/src/singlecellexperiment/SingleCellExperiment.py +++ b/src/singlecellexperiment/SingleCellExperiment.py @@ -13,7 +13,7 @@ merge_se_rownames, relaxed_merge_assays, ) -from summarizedexperiment.rse import ( +from summarizedexperiment.RangedSummarizedExperiment import ( GRangesOrGRangesList, RangedSummarizedExperiment, ) From def845b41d1102e4c3aff776e111ab75b04d70f4 Mon Sep 17 00:00:00 2001 From: Jayaram Kancherla Date: Sat, 3 Jan 2026 15:21:03 -0800 Subject: [PATCH 4/4] update changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0506f2f..ee56ac6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## Version 0.6.0 + +- Changed related to SummarizedExperiment and implementation of `CompressedGenomicRangesList` in the genomic ranges package. +- Update versions of relevant dependency packages. + ## Version 0.5.8 - 0.5.9 - Rename `reduced_dims` to `reduced_dimensions`. Constructor accepts both these arguments for backwards compatibility.