diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml deleted file mode 100644 index 462ac90..0000000 --- a/.github/workflows/build-docs.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Manually publish documentation - -on: - workflow_dispatch: - inputs: - sphinxDocVersionManual: - description: "Version to set for documentation" - required: true - type: string - -jobs: - build_docs: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - - name: Set up Python 3.9 - uses: actions/setup-python@v2 - with: - python-version: 3.9 - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install tox - - - name: Build docs - env: - SPHINX_DOC_VERSION_MANUAL: ${{ inputs.sphinxDocVersionManual }} - run: tox -e docs - - - name: Add nojekyll file - 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 diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml index 31b9b03..7fd791c 100644 --- a/.github/workflows/pypi-publish.yml +++ b/.github/workflows/pypi-publish.yml @@ -12,11 +12,11 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Set up Python 3.9 - uses: actions/setup-python@v2 + - uses: actions/checkout@v4 + - name: Set up Python 3.11 + uses: actions/setup-python@v5 with: - python-version: 3.9 + python-version: 3.11 - name: Install dependencies run: | diff --git a/.github/workflows/pypi-test.yml b/.github/workflows/pypi-test.yml index 2c7984e..19713b1 100644 --- a/.github/workflows/pypi-test.yml +++ b/.github/workflows/pypi-test.yml @@ -14,14 +14,14 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: [ '3.9', '3.10', '3.11', '3.12', '3.13' ] name: Python ${{ matrix.python-version }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Setup Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} cache: "pip" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3c9601c..e60a5f4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,19 +17,19 @@ repos: - id: mixed-line-ending args: ['--fix=auto'] # replace 'auto' with 'lf' to enforce Linux/Mac line endings or 'crlf' for Windows -- repo: https://github.com/PyCQA/docformatter - rev: v1.7.5 - hooks: - - id: docformatter - additional_dependencies: [tomli] - args: [--in-place, --wrap-descriptions=120, --wrap-summaries=120] - # --config, ./pyproject.toml +# - repo: https://github.com/PyCQA/docformatter +# rev: master +# hooks: +# - id: docformatter +# additional_dependencies: [tomli] +# args: [--in-place, --wrap-descriptions=120, --wrap-summaries=120] +# # --config, ./pyproject.toml -- repo: https://github.com/psf/black - rev: 24.8.0 - hooks: - - id: black - language_version: python3 +# - repo: https://github.com/psf/black +# rev: 24.8.0 +# hooks: +# - id: black +# language_version: python3 - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. @@ -37,6 +37,7 @@ repos: hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] + - id: ruff-format ## If like to embrace black styles even in the docs: # - repo: https://github.com/asottile/blacken-docs diff --git a/CHANGELOG.md b/CHANGELOG.md index 16db670..e5704a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## Version 0.6.0 + +- chore: Remove Python 3.8 (EOL) +- precommit: Replace docformatter with ruff's formatter + ## Version 0.5.11 - Methods to flatten a nested `BiocFrame` object. diff --git a/docs/conf.py b/docs/conf.py index 6458dd1..ea11f12 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -72,6 +72,7 @@ "sphinx.ext.ifconfig", "sphinx.ext.mathjax", "sphinx.ext.napoleon", + "sphinx_autodoc_typehints", ] # Add any paths that contain templates here, relative to this directory. diff --git a/docs/requirements.txt b/docs/requirements.txt index 2058619..c20cf60 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -7,4 +7,3 @@ myst-nb myst-parser[linkify] sphinx>=3.2.1 sphinx-autodoc-typehints -# nbsphinx diff --git a/pyproject.toml b/pyproject.toml index a7cea75..00aa968 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,10 @@ extend-ignore = ["F821"] [tool.ruff.pydocstyle] convention = "google" +[tool.ruff.format] +docstring-code-format = true +docstring-code-line-length = 20 + [tool.ruff.per-file-ignores] "__init__.py" = ["E402", "F401"] diff --git a/setup.cfg b/setup.cfg index 3127d13..ab16426 100644 --- a/setup.cfg +++ b/setup.cfg @@ -41,7 +41,7 @@ package_dir = =src # Require a min/specific Python version (comma-separated conditions) -python_requires = >=3.8 +python_requires = >=3.9 # Add here dependencies of your project (line-separated), e.g. requests>=2.2,<3.0. # Version specifiers like >=2.2,<3.0 avoid problems due to API changes in diff --git a/src/biocframe/BiocFrame.py b/src/biocframe/BiocFrame.py index 18d4e45..5ec0a87 100644 --- a/src/biocframe/BiocFrame.py +++ b/src/biocframe/BiocFrame.py @@ -50,8 +50,7 @@ def _validate_rows( if row_names is not None: if len(row_names) != number_of_rows: raise ValueError( - "Length of `row_names` and `number_of_rows` do not match, " - f"{len(row_names)} != {number_of_rows}" + "Length of `row_names` and `number_of_rows` do not match, " f"{len(row_names)} != {number_of_rows}" ) if any(x is None for x in row_names): raise ValueError("`row_names` cannot contain None values.") @@ -67,9 +66,7 @@ def _validate_columns( if column_data is not None: if column_data.shape[0] != len(column_names): - raise ValueError( - "Number of rows in `column_data` should be equal to the number of columns." - ) + raise ValueError("Number of rows in `column_data` should be equal to the number of columns.") ############################ @@ -96,11 +93,7 @@ def __iter__(self): def __next__(self): if self._current_index < len(self._bframe): - iter_row_index = ( - self._bframe.row_names[self._current_index] - if self._bframe.row_names is not None - else None - ) + iter_row_index = self._bframe.row_names[self._current_index] if self._bframe.row_names is not None else None iter_slice = self._bframe.row(self._current_index) self._current_index += 1 @@ -280,9 +273,7 @@ def __str__(self) -> str: data = self._data[col] showed = ut.show_as_cell(data, indices) header = [col, "<" + ut.print_type(data) + ">"] - showed = ut.truncate_strings( - showed, width=max(40, len(header[0]), len(header[1])) - ) + showed = ut.truncate_strings(showed, width=max(40, len(header[0]), len(header[1]))) if insert_ellipsis: showed = showed[:3] + ["..."] + showed[3:] columns.append(header + showed) @@ -333,9 +324,7 @@ def get_row_names(self) -> Optional[ut.Names]: """ return self._row_names - def set_row_names( - self, names: Optional[List], in_place: bool = False - ) -> "BiocFrame": + def set_row_names(self, names: Optional[List], in_place: bool = False) -> "BiocFrame": """ Args: names: @@ -505,9 +494,7 @@ def get_column_data(self, with_names: bool = True) -> Union[None, "BiocFrame"]: output = output.set_row_names(self._column_names) return output - def set_column_data( - self, column_data: Union[None, "BiocFrame"], in_place: bool = False - ) -> "BiocFrame": + def set_column_data(self, column_data: Union[None, "BiocFrame"], in_place: bool = False) -> "BiocFrame": """ Args: column_data: @@ -524,9 +511,7 @@ def set_column_data( """ if column_data is not None: if column_data.shape[0] != self.shape[1]: - raise ValueError( - "Number of rows in `column_data` should be equal to the number of columns." - ) + raise ValueError("Number of rows in `column_data` should be equal to the number of columns.") output = self._define_output(in_place) output._column_data = column_data @@ -570,9 +555,7 @@ def set_metadata(self, metadata: dict, in_place: bool = False) -> "BiocFrame": or as a reference to the (in-place-modified) original. """ if not isinstance(metadata, dict): - raise TypeError( - f"`metadata` must be a dictionary, provided {type(metadata)}." - ) + raise TypeError(f"`metadata` must be a dictionary, provided {type(metadata)}.") output = self._define_output(in_place) output._metadata = metadata return output @@ -633,9 +616,7 @@ def get_column(self, column: Union[str, int]) -> Any: return self._data[column] - raise TypeError( - f"'column' must be a string or integer, provided '{type(column)}'." - ) + raise TypeError(f"'column' must be a string or integer, provided '{type(column)}'.") def column(self, column: Union[str, int]) -> Any: """Alias for :py:meth:`~get_column`, provided for back-compatibility only.""" @@ -714,9 +695,7 @@ def get_slice( """ new_column_names = self._column_names if not (isinstance(columns, slice) and columns == slice(None)): - new_column_indices, _ = ut.normalize_subscript( - columns, len(new_column_names), new_column_names - ) + new_column_indices, _ = ut.normalize_subscript(columns, len(new_column_names), new_column_names) new_column_names = ut.subset_sequence(new_column_names, new_column_indices) new_data = {} @@ -727,9 +706,7 @@ def get_slice( new_number_of_rows = self.shape[0] if not (isinstance(rows, slice) and rows == slice(None)): new_row_names = self.row_names - new_row_indices, _ = ut.normalize_subscript( - rows, self.shape[0], new_row_names - ) + new_row_indices, _ = ut.normalize_subscript(rows, self.shape[0], new_row_names) new_number_of_rows = len(new_row_indices) for k, v in new_data.items(): @@ -761,9 +738,7 @@ def slice(self, rows: Sequence, columns: Sequence) -> "BiocFrame": columns = slice(None) return self.__getitem__((rows, columns)) - def __getitem__( - self, args: Union[int, str, Sequence, tuple] - ) -> Union["BiocFrame", Any]: + def __getitem__(self, args: Union[int, str, Sequence, tuple]) -> Union["BiocFrame", Any]: """Wrapper around :py:attr:`~get_column` and :py:attr:`~get_slice` to obtain a slice of a ``BiocFrame`` or any of its columns. @@ -879,13 +854,9 @@ def set_slice( if not in_place: output._data = copy(output._data) - row_idx, _ = ut.normalize_subscript( - rows, output.shape[0], names=output._row_names - ) + row_idx, _ = ut.normalize_subscript(rows, output.shape[0], names=output._row_names) - col_idx, _ = ut.normalize_subscript( - columns, output.shape[1], names=output._column_names - ) + col_idx, _ = ut.normalize_subscript(columns, output.shape[1], names=output._column_names) for i, x in enumerate(col_idx): nm = output._column_names[x] @@ -912,9 +883,7 @@ def __delitem__(self, name: str): ) self.remove_column(name, in_place=True) - def set_column( - self, column: Union[int, str], value: Any, in_place: bool = False - ) -> "BiocFrame": + def set_column(self, column: Union[int, str], value: Any, in_place: bool = False) -> "BiocFrame": """Modify an existing column or add a new column. This is a convenience wrapper around :py:attr:`~set_columns`. Args: @@ -935,9 +904,7 @@ def set_column( """ return self.set_columns({column: value}, in_place=in_place) - def set_columns( - self, columns: Dict[str, Any], in_place: bool = False - ) -> "BiocFrame": + def set_columns(self, columns: Dict[str, Any], in_place: bool = False) -> "BiocFrame": """Modify existing columns or add new columns. Args: @@ -984,9 +951,7 @@ def set_columns( return output - def remove_column( - self, column: Union[int, str], in_place: bool = False - ) -> "BiocFrame": + def remove_column(self, column: Union[int, str], in_place: bool = False) -> "BiocFrame": """Remove a column. This is a convenience wrapper around :py:attr:`~remove_columns`. Args: @@ -1002,9 +967,7 @@ def remove_column( """ return self.remove_columns([column], in_place=in_place) - def remove_columns( - self, columns: Sequence[Union[int, str]], in_place: bool = False - ) -> "BiocFrame": + def remove_columns(self, columns: Sequence[Union[int, str]], in_place: bool = False) -> "BiocFrame": """Remove any number of existing columns. Args: @@ -1057,9 +1020,7 @@ def __deepcopy__(self, memo=None, _nil=[]): _num_rows_copy = deepcopy(self._number_of_rows) _rownames_copy = deepcopy(self.row_names) _metadata_copy = deepcopy(self.metadata) - _column_data_copy = ( - deepcopy(self._column_data) if self._column_data is not None else None - ) + _column_data_copy = deepcopy(self._column_data) if self._column_data is not None else None # copy dictionary first _data_copy = OrderedDict() @@ -1067,9 +1028,7 @@ def __deepcopy__(self, memo=None, _nil=[]): try: _data_copy[col] = deepcopy(self.column(col)) except Exception as e: - raise Exception( - f"Cannot `deepcopy` column '{col}', full error: {str(e)}" - ) from e + raise Exception(f"Cannot `deepcopy` column '{col}', full error: {str(e)}") from e current_class_const = type(self) return current_class_const( @@ -1106,9 +1065,7 @@ def copy(self): ######>> split by <<###### ########################## - def split( - self, column_name: str, only_indices: bool = False - ) -> Dict[str, Union["BiocFrame", List[int]]]: + def split(self, column_name: str, only_indices: bool = False) -> Dict[str, Union["BiocFrame", List[int]]]: """Split the object by a column. Args: @@ -1246,9 +1203,7 @@ def to_polars(self): ######>> Miscellaneous <<###### ############################### - def flatten( - self, as_type: Literal["dict", "biocframe"] = "dict", delim: str = "." - ) -> "BiocFrame": + def flatten(self, as_type: Literal["dict", "biocframe"] = "dict", delim: str = ".") -> "BiocFrame": """Flatten a nested BiocFrame object. Args: @@ -1404,11 +1359,7 @@ def _combine_cols_bframes(*x: BiocFrame): all_column_data.append(df._column_data) for n in df._column_names: if n in all_data: - raise ValueError( - "All objects to combine must have different columns (duplicated '" - + n - + "')." - ) + raise ValueError("All objects to combine must have different columns (duplicated '" + n + "').") all_data[n] = df._data[n] combined_column_data = None @@ -1419,10 +1370,7 @@ def _combine_cols_bframes(*x: BiocFrame): try: combined_column_data = ut.combine_rows(*all_column_data) except Exception as ex: - raise ValueError( - "Failed to combine 'column_data' when combining 'BiocFrame' objects by column. " - + str(ex) - ) + raise ValueError("Failed to combine 'column_data' when combining 'BiocFrame' objects by column. " + str(ex)) current_class_const = type(first) return current_class_const( @@ -1463,9 +1411,7 @@ def _show_as_cell_BiocFrame(x: BiocFrame, indices: Sequence[int]) -> List[str]: @ut.assign_rows.register(BiocFrame) -def _assign_rows_BiocFrame( - x: BiocFrame, indices: Sequence[int], replacement: BiocFrame -) -> BiocFrame: +def _assign_rows_BiocFrame(x: BiocFrame, indices: Sequence[int], replacement: BiocFrame) -> BiocFrame: return x.set_slice(indices, replacement.get_column_names(), replacement) @@ -1536,20 +1482,12 @@ def relaxed_combine_rows(*x: BiocFrame) -> BiocFrame: def _normalize_merge_key_to_index(x, i, by): if by is None: if x[i]._row_names is None: - raise ValueError( - "Row names required as key but are absent in object " + str(i) + "." - ) + raise ValueError("Row names required as key but are absent in object " + str(i) + ".") return None elif isinstance(by, int): nc = x[i].shape[1] if by < -nc or by >= nc: - raise ValueError( - "Integer 'by' is out of range for object " - + str(i) - + " (" - + str(nc) - + " columns)." - ) + raise ValueError("Integer 'by' is out of range for object " + str(i) + " (" + str(nc) + " columns).") if by < 0: return by + nc else: @@ -1560,9 +1498,7 @@ def _normalize_merge_key_to_index(x, i, by): raise ValueError("No key column '" + by + "' in object " + str(i) + ".") return ib else: - raise TypeError( - "Unknown type '" + type(by).__name__ + "' for the 'by' argument." - ) + raise TypeError("Unknown type '" + type(by).__name__ + "' for the 'by' argument.") def _get_merge_key(x, i, by): @@ -1679,11 +1615,7 @@ def merge( counter += 1 y = original + " (" + str(counter) + ")" elif y in new_data: - raise ValueError( - "Detected duplicate columns across objects to be merged ('" - + y - + "')." - ) + raise ValueError("Detected duplicate columns across objects to be merged ('" + y + "').") new_columns.append(y) if noop: