From 7a9a84fcfc028fc03682c6c64fe9f8b24e834aee Mon Sep 17 00:00:00 2001 From: Jayaram Kancherla Date: Tue, 18 Nov 2025 16:52:48 -0800 Subject: [PATCH 01/10] minor changes --- src/biocutils/combine_rows.py | 2 +- src/biocutils/package_utils.py | 6 ++-- src/biocutils/subset_rows.py | 49 ++++++++++++++++++++++++----- src/biocutils/subset_sequence.py | 54 +++++++++++++++++++++++--------- 4 files changed, 86 insertions(+), 25 deletions(-) diff --git a/src/biocutils/combine_rows.py b/src/biocutils/combine_rows.py index 1783dcf..9102d1e 100644 --- a/src/biocutils/combine_rows.py +++ b/src/biocutils/combine_rows.py @@ -77,7 +77,7 @@ def _combine_rows_sparse_arrays(*x): _check_array_dimensions(x, 0) if is_list_of_type(x, sp.sparray): combined = sp.vstack(x) - return _coerce_sparse_array(first, combined, sp) + return _coerce_sparse_array(x[0], combined, sp) warn("not all elements are SciPy sparse arrays") x = [convert_to_dense(y) for y in x] diff --git a/src/biocutils/package_utils.py b/src/biocutils/package_utils.py index e6e4b3a..dcf03d1 100644 --- a/src/biocutils/package_utils.py +++ b/src/biocutils/package_utils.py @@ -4,13 +4,13 @@ def is_package_installed(package_name: str) -> bool: - """Check if the package is installed. + """Check if a package is installed. Args: - package_name (str): Package name. + package_name: Package name. Returns: - bool: True if package is installed, otherwise False. + True if package is installed, otherwise False. """ _installed = False try: diff --git a/src/biocutils/subset_rows.py b/src/biocutils/subset_rows.py index a09fadb..e5b31cb 100644 --- a/src/biocutils/subset_rows.py +++ b/src/biocutils/subset_rows.py @@ -1,19 +1,21 @@ from functools import singledispatch from typing import Any, Sequence +import numpy + +from .package_utils import is_package_installed + @singledispatch def subset_rows(x: Any, indices: Sequence[int]) -> Any: - """ + """Subset a high-dimensional object by indices on the first dimension. + Subset ``x`` by ``indices`` on the first dimension. The default - method attempts to use ``x``'s ``__getitem__`` method, + method attempts to use ``x``'s ``__getitem__`` method. Args: - x: - Any high-dimensional object. - - indices: - Sequence of non-negative integers specifying the integers of interest. + x: Any high-dimensional object. + indices: Sequence of non-negative integers specifying the rows of interest. Returns: The result of slicing ``x`` by ``indices``. The exact type @@ -22,3 +24,36 @@ def subset_rows(x: Any, indices: Sequence[int]) -> Any: tmp = [slice(None)] * len(x.shape) tmp[0] = indices return x[(*tmp,)] + + +@subset_rows.register +def _subset_rows_numpy(x: numpy.ndarray, indices: Sequence[int]) -> numpy.ndarray: + """Subset a NumPy array by row indices. + + Args: + x: NumPy array to subset. + indices: Sequence of non-negative integers specifying rows. + + Returns: + Subsetted NumPy array. + """ + tmp = [slice(None)] * len(x.shape) + tmp[0] = indices + return x[(*tmp,)] + + +if is_package_installed("pandas"): + from pandas import DataFrame + + @subset_rows.register(DataFrame) + def _subset_rows_dataframe(x: DataFrame, indices: Sequence[int]) -> DataFrame: + """Subset a pandas DataFrame by row indices. + + Args: + x: DataFrame to subset. + indices: Sequence of non-negative integers specifying rows. + + Returns: + Subsetted DataFrame. + """ + return x.iloc[indices, :] diff --git a/src/biocutils/subset_sequence.py b/src/biocutils/subset_sequence.py index 609027e..1c39517 100644 --- a/src/biocutils/subset_sequence.py +++ b/src/biocutils/subset_sequence.py @@ -4,17 +4,15 @@ @singledispatch def subset_sequence(x: Any, indices: Sequence[int]) -> Any: - """ + """Subset a sequence-like object by indices. + Subset ``x`` by ``indices`` to obtain a new object. The default method attempts to use ``x``'s ``__getitem__`` method. Args: - x: - Any object that supports ``__getitem__`` with an integer sequence. - - indices: - Sequence of non-negative integers specifying the integers of interest. - All indices should be less than ``len(x)``. + x: Any object that supports ``__getitem__`` with an integer sequence. + indices: Sequence of non-negative integers specifying the positions of + interest. All indices should be less than ``len(x)``. Returns: The result of slicing ``x`` by ``indices``. The exact type @@ -24,19 +22,47 @@ def subset_sequence(x: Any, indices: Sequence[int]) -> Any: @subset_sequence.register -def _subset_sequence_list(x: list, indices: Sequence) -> list: +def _subset_sequence_list(x: list, indices: Sequence[int]) -> list: + """Subset a list by indices. + + Args: + x: List to subset. + indices: Sequence of non-negative integers specifying positions. + + Returns: + A new list containing the specified elements. + """ return type(x)(x[i] for i in indices) @subset_sequence.register -def _subset_sequence_range(x: range, indices: Sequence) -> Union[list, range]: +def _subset_sequence_range(x: range, indices: Sequence[int]) -> Union[list, range]: + """Subset a range by indices. + + Args: + x: Range object to subset. + indices: Sequence of non-negative integers or a range object. + + Returns: + A range if indices is a range, otherwise a list. + """ if isinstance(indices, range): # We can just assume that all 'indices' are in [0, len(x)), # so no need to handle out-of-range indices. - return range( - x.start + x.step * indices.start, - x.start + x.step * indices.stop, - x.step * indices.step - ) + return range(x.start + x.step * indices.start, x.start + x.step * indices.stop, x.step * indices.step) else: return [x[i] for i in indices] + + +@subset_sequence.register +def _subset_sequence_tuple(x: tuple, indices: Sequence[int]) -> tuple: + """Subset a tuple by indices. + + Args: + x: Tuple to subset. + indices: Sequence of non-negative integers specifying positions. + + Returns: + A new tuple containing the specified elements. + """ + return tuple(x[i] for i in indices) From ef1ca9678e7561c1654b88628d545d9d2e64502a Mon Sep 17 00:00:00 2001 From: Jayaram Kancherla Date: Tue, 18 Nov 2025 16:55:18 -0800 Subject: [PATCH 02/10] update actions --- .github/workflows/publish-pypi.yml | 52 +++++++++++++++++++++ .github/workflows/pypi-publish.yml | 51 --------------------- .github/workflows/pypi-test.yml | 40 ---------------- .github/workflows/run-tests.yml | 73 ++++++++++++++++++++++++++++++ 4 files changed, 125 insertions(+), 91 deletions(-) create mode 100644 .github/workflows/publish-pypi.yml delete mode 100644 .github/workflows/pypi-publish.yml delete mode 100644 .github/workflows/pypi-test.yml create mode 100644 .github/workflows/run-tests.yml diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml new file mode 100644 index 0000000..405fee0 --- /dev/null +++ b/.github/workflows/publish-pypi.yml @@ -0,0 +1,52 @@ +name: Publish to PyPI + +on: + push: + tags: "*" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + repository-projects: write + contents: write + pages: write + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python 3.12 + uses: actions/setup-python@v5 + with: + python-version: 3.12 + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install tox + + - name: Test with tox + 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 + + - run: touch ./docs/_build/html/.nojekyll + + - name: GH Pages Deployment + uses: JamesIves/github-pages-deploy-action@v4 + with: + branch: gh-pages # The branch the action should deploy to. + folder: ./docs/_build/html + clean: true # Automatically remove deleted files from the deploy branch diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml deleted file mode 100644 index 030cd10..0000000 --- a/.github/workflows/pypi-publish.yml +++ /dev/null @@ -1,51 +0,0 @@ -# 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: - push: - tags: "*" - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - name: Set up Python 3.11 - uses: actions/setup-python@v5 - with: - python-version: 3.11 - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install flake8 pytest tox - # - name: Lint with flake8 - # run: | - # # stop the build if there are Python syntax errors or undefined names - # flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - # # flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - - name: Test with tox - run: | - tox - - name: Build docs - run: | - tox -e docs - - run: touch ./docs/_build/html/.nojekyll - - name: GH Pages Deployment - uses: JamesIves/github-pages-deploy-action@4.1.3 - with: - 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 - - name: Publish package - uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 - with: - user: __token__ - password: ${{ secrets.PYPI_PASSWORD }} diff --git a/.github/workflows/pypi-test.yml b/.github/workflows/pypi-test.yml deleted file mode 100644 index 22f6c4a..0000000 --- a/.github/workflows/pypi-test.yml +++ /dev/null @@ -1,40 +0,0 @@ -# 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: Test the library - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -jobs: - build: - - runs-on: ubuntu-latest - strategy: - matrix: - python-version: [ '3.9', '3.10', '3.11', '3.12', '3.13' ] - - name: Python ${{ matrix.python-version }} - steps: - - uses: actions/checkout@v4 - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - cache: 'pip' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install flake8 pytest tox - # - name: Lint with flake8 - # run: | - # # stop the build if there are Python syntax errors or undefined names - # flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - # # flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - - name: Test with tox - run: | - tox diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml new file mode 100644 index 0000000..e8ab6fa --- /dev/null +++ b/.github/workflows/run-tests.yml @@ -0,0 +1,73 @@ +name: Test the library + +on: + push: + 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: + test: + strategy: + matrix: + python: ["3.9", "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 + + - uses: actions/setup-python@v5 + id: setup-python + with: + python-version: ${{ matrix.python }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install tox coverage + + - 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 }} From 43775ea197ddfbed0128eb5674f571e45bd80dd0 Mon Sep 17 00:00:00 2001 From: Jayaram Kancherla Date: Tue, 18 Nov 2025 17:40:48 -0800 Subject: [PATCH 03/10] minor docstring updates --- src/biocutils/BooleanList.py | 19 +++++++++++++++++-- src/biocutils/FloatList.py | 19 +++++++++++++++++-- src/biocutils/IntegerList.py | 19 +++++++++++++++++-- src/biocutils/StringList.py | 19 +++++++++++++++++-- src/biocutils/extract_column_names.py | 2 +- src/biocutils/extract_row_names.py | 2 +- src/biocutils/is_high_dimensional.py | 9 +++++---- src/biocutils/is_missing_scalar.py | 10 ++++++---- src/biocutils/match.py | 2 +- src/biocutils/normalize_subscript.py | 24 +++++++++++++----------- src/biocutils/print_wrapped_table.py | 11 +++++++---- src/biocutils/reverse_index.py | 6 +++--- src/biocutils/which.py | 2 +- 13 files changed, 106 insertions(+), 38 deletions(-) diff --git a/src/biocutils/BooleanList.py b/src/biocutils/BooleanList.py index 1e46826..f309322 100644 --- a/src/biocutils/BooleanList.py +++ b/src/biocutils/BooleanList.py @@ -10,10 +10,25 @@ def _coerce_to_bool(x: Any): class _SubscriptCoercer: - def __init__(self, data): + """Coercer for subscript operations on BooleanList.""" + + def __init__(self, data: Sequence) -> None: + """Initialize the coercer. + + Args: + data: Sequence of values to coerce. + """ self._data = data - def __getitem__(self, index): + def __getitem__(self, index: int) -> Optional[bool]: + """Get an item and coerce it to boolean. + + Args: + index: Index of the item. + + Returns: + Coerced boolean value. + """ return _coerce_to_bool(self._data[index]) diff --git a/src/biocutils/FloatList.py b/src/biocutils/FloatList.py index 3b587bd..3249da5 100644 --- a/src/biocutils/FloatList.py +++ b/src/biocutils/FloatList.py @@ -15,10 +15,25 @@ def _coerce_to_float(x: Any): class _SubscriptCoercer: - def __init__(self, data): + """Coercer for subscript operations on FloatList.""" + + def __init__(self, data: Sequence) -> None: + """Initialize the coercer. + + Args: + data: Sequence of values to coerce. + """ self._data = data - def __getitem__(self, index): + def __getitem__(self, index: int) -> Optional[float]: + """Get an item and coerce it to float. + + Args: + index: Index of the item. + + Returns: + Coerced float value. + """ return _coerce_to_float(self._data[index]) diff --git a/src/biocutils/IntegerList.py b/src/biocutils/IntegerList.py index c06e3e6..8f8f191 100644 --- a/src/biocutils/IntegerList.py +++ b/src/biocutils/IntegerList.py @@ -15,10 +15,25 @@ def _coerce_to_int(x: Any): class _SubscriptCoercer: - def __init__(self, data): + """Coercer for subscript operations on IntegerList.""" + + def __init__(self, data: Sequence) -> None: + """Initialize the coercer. + + Args: + data: Sequence of values to coerce. + """ self._data = data - def __getitem__(self, index): + def __getitem__(self, index: int) -> Optional[int]: + """Get an item and coerce it to integer. + + Args: + index: Index of the item. + + Returns: + Coerced integer value. + """ return _coerce_to_int(self._data[index]) diff --git a/src/biocutils/StringList.py b/src/biocutils/StringList.py index c16366b..518b729 100644 --- a/src/biocutils/StringList.py +++ b/src/biocutils/StringList.py @@ -10,10 +10,25 @@ def _coerce_to_str(x: Any): class _SubscriptCoercer: - def __init__(self, data): + """Coercer for subscript operations on StringList.""" + + def __init__(self, data: Sequence) -> None: + """Initialize the coercer. + + Args: + data: Sequence of values to coerce. + """ self._data = data - def __getitem__(self, index): + def __getitem__(self, index: int) -> Optional[str]: + """Get an item and coerce it to string. + + Args: + index: Index of the item. + + Returns: + Coerced string value. + """ return _coerce_to_str(self._data[index]) diff --git a/src/biocutils/extract_column_names.py b/src/biocutils/extract_column_names.py index 5b63683..6f98521 100644 --- a/src/biocutils/extract_column_names.py +++ b/src/biocutils/extract_column_names.py @@ -15,7 +15,7 @@ def extract_column_names(x: Any) -> numpy.ndarray: """Access column names from 2-dimensional representations. Args: - x: Any object. + x: Any object with column names. Returns: Array of strings containing column names. diff --git a/src/biocutils/extract_row_names.py b/src/biocutils/extract_row_names.py index 81b81fb..5ea3927 100644 --- a/src/biocutils/extract_row_names.py +++ b/src/biocutils/extract_row_names.py @@ -15,7 +15,7 @@ def extract_row_names(x: Any) -> numpy.ndarray: """Access row names from 2-dimensional representations. Args: - x: Any object. + x: Any object with row names. Returns: Array of strings containing row names. diff --git a/src/biocutils/is_high_dimensional.py b/src/biocutils/is_high_dimensional.py index 9d9d5d2..3bdfc6f 100644 --- a/src/biocutils/is_high_dimensional.py +++ b/src/biocutils/is_high_dimensional.py @@ -1,15 +1,16 @@ from functools import singledispatch +from typing import Any @singledispatch -def is_high_dimensional(x): - """ +def is_high_dimensional(x: Any) -> bool: + """Check if an object is high-dimensional. + Whether an object is high-dimensional, i.e., has a ``shape`` attribute that is of length greater than 1. Args: - x: - Some kind of object. + x: Some kind of object. Returns: Whether ``x`` is high-dimensional. diff --git a/src/biocutils/is_missing_scalar.py b/src/biocutils/is_missing_scalar.py index 8392fdf..f32dd60 100644 --- a/src/biocutils/is_missing_scalar.py +++ b/src/biocutils/is_missing_scalar.py @@ -1,11 +1,13 @@ +from typing import Any + import numpy -def is_missing_scalar(x) -> bool: - """ +def is_missing_scalar(x: Any) -> bool: + """Check if a scalar value is missing. + Args: - x: - Any scalar value. + x: Any scalar value. Returns: Whether ``x`` is None or a NumPy masked constant. diff --git a/src/biocutils/match.py b/src/biocutils/match.py index 2881bec..0e237c5 100644 --- a/src/biocutils/match.py +++ b/src/biocutils/match.py @@ -8,7 +8,7 @@ def match( x: Sequence, targets: Union[dict, Sequence], duplicate_method: DUPLICATE_METHOD = "first", - dtype: Optional[numpy.ndarray] = None, + dtype: Optional[numpy.dtype] = None, fail_missing: Optional[bool] = None, ) -> numpy.ndarray: """Find a matching value of each element of ``x`` in ``target``. diff --git a/src/biocutils/normalize_subscript.py b/src/biocutils/normalize_subscript.py index 890a9b3..da37a7e 100644 --- a/src/biocutils/normalize_subscript.py +++ b/src/biocutils/normalize_subscript.py @@ -23,11 +23,11 @@ class NormalizedSubscript: such that :py:func:`~normalize_subscript` is just a no-op. """ - def __init__(self, subscript: Sequence[int]): - """ + def __init__(self, subscript: Sequence[int]) -> None: + """Initialize a NormalizedSubscript. + Args: - subscript: - Sequence of integers for a normalized subscript. + subscript: Sequence of integers for a normalized subscript. """ self._subscript = subscript @@ -40,11 +40,11 @@ def subscript(self) -> Sequence[int]: return self._subscript def __getitem__(self, index: Any) -> Any: - """ + """Get an item from the subscript. + Args: - index: - Any argument accepted by the ``__getitem__`` method of the - :py:attr:`~subscript`. + index: Any argument accepted by the ``__getitem__`` method of the + subscript. Returns: The same return value as the ``__getitem__`` method of the @@ -53,7 +53,8 @@ def __getitem__(self, index: Any) -> Any: return self._subscript[index] def __len__(self) -> int: - """ + """Get the length of the subscript. + Returns: Length of the subscript. """ @@ -68,8 +69,9 @@ def normalize_subscript( length: int, names: Optional[Sequence[str]] = None, non_negative_only: bool = True, -) -> Tuple: - """ +) -> Tuple[Sequence[int], bool]: + """Normalize a subscript into a sequence of integer indices. + Normalize a subscript for ``__getitem__`` or friends into a sequence of integer indices, for consistent downstream use. diff --git a/src/biocutils/print_wrapped_table.py b/src/biocutils/print_wrapped_table.py index cbfcb36..12f602f 100644 --- a/src/biocutils/print_wrapped_table.py +++ b/src/biocutils/print_wrapped_table.py @@ -144,14 +144,17 @@ def truncate_strings(values: List[str], width: int = 40) -> List[str]: return replacement -def print_type(x) -> str: - """Print the type of an object, with some special behavior for certain classes (e.g., to add the data type of NumPy - arrays). This is intended for display at the top of the columns of :py:meth:`~print_wrapped_table`. +def print_type(x: Any) -> str: + """Print the type of an object. + + Print the type of an object, with some special behavior for certain classes + (e.g., to add the data type of NumPy arrays). This is intended for display + at the top of the columns of :py:meth:`~print_wrapped_table`. Args: x: Some object. - Return: + Returns: String containing the class of the object. """ cls = type(x).__name__ diff --git a/src/biocutils/reverse_index.py b/src/biocutils/reverse_index.py index 3b64c9e..528f1ee 100644 --- a/src/biocutils/reverse_index.py +++ b/src/biocutils/reverse_index.py @@ -1,16 +1,16 @@ from typing import Sequence -def build_reverse_index(obj: Sequence[str]): +def build_reverse_index(obj: Sequence[str]) -> dict: """Build a reverse index by name, for fast lookup operations. - Only contains the first occurence of a term. + Only contains the first occurrence of a term. Args: obj: List of names. Returns: - A map of keys and their index positions. + A dictionary mapping names to their index positions. """ revmap = {} for i, n in enumerate(obj): diff --git a/src/biocutils/which.py b/src/biocutils/which.py index 2a0c0d1..87d2fdd 100644 --- a/src/biocutils/which.py +++ b/src/biocutils/which.py @@ -4,7 +4,7 @@ def which( x: Sequence, - dtype: Optional[numpy.ndarray] = None, + dtype: Optional[numpy.dtype] = None, ) -> numpy.ndarray: """Report the indices of all elements of ``x`` that are truthy. From 591af1f33975019007a47053e344359d20ac9bbf Mon Sep 17 00:00:00 2001 From: Jayaram Kancherla Date: Tue, 18 Nov 2025 21:14:57 -0800 Subject: [PATCH 04/10] missing import --- src/biocutils/print_wrapped_table.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/biocutils/print_wrapped_table.py b/src/biocutils/print_wrapped_table.py index 12f602f..583f583 100644 --- a/src/biocutils/print_wrapped_table.py +++ b/src/biocutils/print_wrapped_table.py @@ -1,4 +1,4 @@ -from typing import List, Optional, Sequence +from typing import Any, List, Optional, Sequence import numpy From 21b8bfcd530389de97e12527c30c83421656080a Mon Sep 17 00:00:00 2001 From: Jayaram Kancherla Date: Thu, 18 Dec 2025 14:58:07 -0800 Subject: [PATCH 05/10] renaming to follow pep guidelines --- src/biocutils/Factor.py | 4 ++-- src/biocutils/__init__.py | 14 +++++++------- src/biocutils/bioc_object.py | 2 +- src/biocutils/{BooleanList.py => boolean_list.py} | 4 ++-- src/biocutils/{FloatList.py => float_list.py} | 4 ++-- src/biocutils/{IntegerList.py => integer_list.py} | 4 ++-- src/biocutils/{NamedList.py => named_list.py} | 2 +- src/biocutils/normalize_subscript.py | 6 +++--- src/biocutils/{StringList.py => string_list.py} | 4 ++-- tests/test_biocobject.py | 2 +- 10 files changed, 23 insertions(+), 23 deletions(-) rename src/biocutils/{BooleanList.py => boolean_list.py} (98%) rename src/biocutils/{FloatList.py => float_list.py} (98%) rename src/biocutils/{IntegerList.py => integer_list.py} (98%) rename src/biocutils/{NamedList.py => named_list.py} (99%) rename src/biocutils/{StringList.py => string_list.py} (98%) diff --git a/src/biocutils/Factor.py b/src/biocutils/Factor.py index 374b34e..fedf75e 100644 --- a/src/biocutils/Factor.py +++ b/src/biocutils/Factor.py @@ -10,14 +10,14 @@ from .is_list_of_type import is_list_of_type from .is_missing_scalar import is_missing_scalar from .match import match -from .Names import Names, _combine_names, _name_to_position, _sanitize_names +from .names import Names, _combine_names, _name_to_position, _sanitize_names from .normalize_subscript import ( NormalizedSubscript, SubscriptTypes, normalize_subscript, ) from .print_truncated import print_truncated_list -from .StringList import StringList +from .string_list import StringList from .subset_sequence import subset_sequence diff --git a/src/biocutils/__init__.py b/src/biocutils/__init__.py index 6f2326f..13fa31c 100644 --- a/src/biocutils/__init__.py +++ b/src/biocutils/__init__.py @@ -15,13 +15,13 @@ finally: del version, PackageNotFoundError -from .Factor import Factor -from .StringList import StringList -from .IntegerList import IntegerList -from .FloatList import FloatList -from .BooleanList import BooleanList -from .Names import Names -from .NamedList import NamedList +from .factor import Factor +from .string_list import StringList +from .integer_list import IntegerList +from .float_list import FloatList +from .boolean_list import BooleanList +from .names import Names +from .named_list import NamedList from .factorize import factorize from .intersect import intersect diff --git a/src/biocutils/bioc_object.py b/src/biocutils/bioc_object.py index 1302663..ac40431 100644 --- a/src/biocutils/bioc_object.py +++ b/src/biocutils/bioc_object.py @@ -9,7 +9,7 @@ except ImportError: Self = "BiocObject" -from .NamedList import NamedList +from .named_list import NamedList __author__ = "Jayaram Kancherla" __copyright__ = "jkanche" diff --git a/src/biocutils/BooleanList.py b/src/biocutils/boolean_list.py similarity index 98% rename from src/biocutils/BooleanList.py rename to src/biocutils/boolean_list.py index f309322..6f19900 100644 --- a/src/biocutils/BooleanList.py +++ b/src/biocutils/boolean_list.py @@ -1,7 +1,7 @@ from typing import Any, Iterable, Optional, Sequence, Union -from .NamedList import NamedList -from .Names import Names +from .named_list import NamedList +from .names import Names from .normalize_subscript import SubscriptTypes diff --git a/src/biocutils/FloatList.py b/src/biocutils/float_list.py similarity index 98% rename from src/biocutils/FloatList.py rename to src/biocutils/float_list.py index 3249da5..ccf8bd4 100644 --- a/src/biocutils/FloatList.py +++ b/src/biocutils/float_list.py @@ -1,7 +1,7 @@ from typing import Any, Iterable, Optional, Sequence, Union -from .NamedList import NamedList -from .Names import Names +from .named_list import NamedList +from .names import Names from .normalize_subscript import SubscriptTypes diff --git a/src/biocutils/IntegerList.py b/src/biocutils/integer_list.py similarity index 98% rename from src/biocutils/IntegerList.py rename to src/biocutils/integer_list.py index 8f8f191..9ca9fbb 100644 --- a/src/biocutils/IntegerList.py +++ b/src/biocutils/integer_list.py @@ -1,7 +1,7 @@ from typing import Any, Iterable, Optional, Sequence, Union -from .NamedList import NamedList -from .Names import Names +from .named_list import NamedList +from .names import Names from .normalize_subscript import SubscriptTypes diff --git a/src/biocutils/NamedList.py b/src/biocutils/named_list.py similarity index 99% rename from src/biocutils/NamedList.py rename to src/biocutils/named_list.py index af4670a..0d971d0 100644 --- a/src/biocutils/NamedList.py +++ b/src/biocutils/named_list.py @@ -3,7 +3,7 @@ from .assign_sequence import assign_sequence from .combine_sequences import combine_sequences -from .Names import Names, _name_to_position, _sanitize_names +from .names import Names, _name_to_position, _sanitize_names from .normalize_subscript import ( NormalizedSubscript, SubscriptTypes, diff --git a/src/biocutils/normalize_subscript.py b/src/biocutils/normalize_subscript.py index da37a7e..d85c6d0 100644 --- a/src/biocutils/normalize_subscript.py +++ b/src/biocutils/normalize_subscript.py @@ -103,7 +103,7 @@ def normalize_subscript( names: List of names for each entry in the object. If not None, this should have length equal to ``length``. Some optimizations - are possible if this is a :py:class:`~Names.Names` object. + are possible if this is a :py:class:`~Names.names` object. non_negative_only: Whether negative indices must be converted into non-negative @@ -138,7 +138,7 @@ def normalize_subscript( + "' for vector-like object with no names" ) i = -1 - from .Names import Names + from .names import Names if isinstance(names, Names): i = names.map(sub) @@ -197,7 +197,7 @@ def normalize_subscript( output = [] has_strings = set() string_positions = [] - from .Names import Names + from .names import Names are_names_indexed = isinstance(names, Names) diff --git a/src/biocutils/StringList.py b/src/biocutils/string_list.py similarity index 98% rename from src/biocutils/StringList.py rename to src/biocutils/string_list.py index 518b729..8fe1d84 100644 --- a/src/biocutils/StringList.py +++ b/src/biocutils/string_list.py @@ -1,7 +1,7 @@ from typing import Any, Iterable, Optional, Sequence, Union -from .NamedList import NamedList -from .Names import Names +from .named_list import NamedList +from .names import Names from .normalize_subscript import SubscriptTypes diff --git a/tests/test_biocobject.py b/tests/test_biocobject.py index 4315995..3645936 100644 --- a/tests/test_biocobject.py +++ b/tests/test_biocobject.py @@ -1,7 +1,7 @@ import pytest from copy import copy from biocutils.bioc_object import BiocObject -from biocutils.NamedList import NamedList +from biocutils.named_list import NamedList def test_init_empty(): From dea34833f2949f67d32b3f1117427820713417d3 Mon Sep 17 00:00:00 2001 From: Jayaram Kancherla Date: Thu, 18 Dec 2025 15:04:18 -0800 Subject: [PATCH 06/10] rename files --- src/biocutils/{Factor.py => factor.py} | 0 src/biocutils/{Names.py => names.py} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/biocutils/{Factor.py => factor.py} (100%) rename src/biocutils/{Names.py => names.py} (100%) diff --git a/src/biocutils/Factor.py b/src/biocutils/factor.py similarity index 100% rename from src/biocutils/Factor.py rename to src/biocutils/factor.py diff --git a/src/biocutils/Names.py b/src/biocutils/names.py similarity index 100% rename from src/biocutils/Names.py rename to src/biocutils/names.py From 5692f35342bbfe7cf57a83561e6107e3a0b656cc Mon Sep 17 00:00:00 2001 From: Jayaram Kancherla Date: Thu, 18 Dec 2025 15:06:02 -0800 Subject: [PATCH 07/10] remove 3.9 from actions --- .github/workflows/run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index e8ab6fa..e0f247d 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -28,7 +28,7 @@ jobs: test: strategy: matrix: - python: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] + python: ["3.10", "3.11", "3.12", "3.13", "3.14"] platform: - ubuntu-latest - macos-latest From 31bb62783dc16399bb19e74d48e0d0b783564d71 Mon Sep 17 00:00:00 2001 From: Jayaram Kancherla Date: Thu, 18 Dec 2025 15:07:45 -0800 Subject: [PATCH 08/10] export biocobject --- src/biocutils/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/biocutils/__init__.py b/src/biocutils/__init__.py index 13fa31c..ed683fd 100644 --- a/src/biocutils/__init__.py +++ b/src/biocutils/__init__.py @@ -60,3 +60,5 @@ from .get_height import get_height from .is_high_dimensional import is_high_dimensional + +from .bioc_object import BiocObject \ No newline at end of file From c8aa74082d62afa12186bd4bf21dc287dc73d31f Mon Sep 17 00:00:00 2001 From: Jayaram Kancherla Date: Thu, 18 Dec 2025 15:13:25 -0800 Subject: [PATCH 09/10] NamedList() refer to use from_dict instead --- src/biocutils/named_list.py | 32 +++++++++----------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/src/biocutils/named_list.py b/src/biocutils/named_list.py index 0d971d0..f51b050 100644 --- a/src/biocutils/named_list.py +++ b/src/biocutils/named_list.py @@ -39,6 +39,9 @@ def __init__( _validate: Internal use only. """ + if isinstance(data, dict): + raise TypeError("'data' is a dictionary, use 'NamedList.from_dict' instead.") + if _validate: if data is None: data = [] @@ -86,14 +89,7 @@ def __str__(self) -> str: names if any exist. """ if self._names is not None: - return ( - "[" - + ", ".join( - repr(self._names[i]) + "=" + repr(x) - for i, x in enumerate(self._data) - ) - + "]" - ) + return "[" + ", ".join(repr(self._names[i]) + "=" + repr(x) for i, x in enumerate(self._data)) + "]" else: return repr(self._data) @@ -204,9 +200,7 @@ def __getitem__(self, index: SubscriptTypes) -> Union["NamedList", Any]: else: return self.get_slice(NormalizedSubscript(index)) - def set_value( - self, index: Union[str, int], value: Any, in_place: bool = False - ) -> "NamedList": + def set_value(self, index: Union[str, int], value: Any, in_place: bool = False) -> "NamedList": """ Args: index: @@ -253,9 +247,7 @@ def set_value( return output - def set_slice( - self, index: SubscriptTypes, value: Sequence, in_place: bool = False - ) -> "NamedList": + def set_slice(self, index: SubscriptTypes, value: Sequence, in_place: bool = False) -> "NamedList": """ Args: index: @@ -324,9 +316,7 @@ def _define_output(self, in_place: bool) -> "NamedList": else: return self.copy() - def safe_insert( - self, index: Union[int, str], value: Any, in_place: bool = False - ) -> "NamedList": + def safe_insert(self, index: Union[int, str], value: Any, in_place: bool = False) -> "NamedList": """ Args: index: @@ -530,9 +520,7 @@ def _combine_sequences_NamedList(*x: NamedList) -> NamedList: @assign_sequence.register -def _assign_sequence_NamedList( - x: NamedList, indices: Sequence[int], other: Sequence -) -> NamedList: +def _assign_sequence_NamedList(x: NamedList, indices: Sequence[int], other: Sequence) -> NamedList: if isinstance(other, NamedList): # Do NOT set the names if 'other' is a NamedList. Names don't change # during assignment/setting operations, as a matter of policy. This is @@ -541,6 +529,4 @@ def _assign_sequence_NamedList( # of names, and it would be weird for the same sequence of names to # suddently become an invalid indexing vector after an assignment. other = other._data - return type(x)( - assign_sequence(x._data, NormalizedSubscript(indices), other), names=x._names - ) + return type(x)(assign_sequence(x._data, NormalizedSubscript(indices), other), names=x._names) From cd0fa74bc7f460ce77287eaa5a9ee0198e94cc18 Mon Sep 17 00:00:00 2001 From: Jayaram Kancherla Date: Thu, 18 Dec 2025 15:17:16 -0800 Subject: [PATCH 10/10] switch to classmethod --- src/biocutils/named_list.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/biocutils/named_list.py b/src/biocutils/named_list.py index f51b050..94918bd 100644 --- a/src/biocutils/named_list.py +++ b/src/biocutils/named_list.py @@ -482,28 +482,28 @@ def as_dict(self) -> Dict[str, Any]: output[n] = self[i] return output - @staticmethod - def from_list(x: list) -> "NamedList": + @classmethod + def from_list(cls, x: list) -> "NamedList": """ Args: x: List of data elements. Returns: - A ``NamedList`` instance with the contents of ``x`` and no names. + A instance with the contents of ``x`` and no names. """ - return NamedList(x) + return cls(x) - @staticmethod - def from_dict(x: dict) -> "NamedList": + @classmethod + def from_dict(cls, x: dict) -> "NamedList": """ Args: x: Dictionary where keys are strings (or can be coerced to them). Returns: - A ``NamedList`` instance where the list elements are the values of + A instance where the list elements are the values of ``x`` and the names are the stringified keys. """ - return NamedList(list(x.values()), names=Names(str(y) for y in x.keys())) + return cls(list(x.values()), names=Names(str(y) for y in x.keys())) @subset_sequence.register