diff --git a/.github/workflows/.pypi-test b/.github/workflows/.pypi-test new file mode 100644 index 0000000..adab766 --- /dev/null +++ b/.github/workflows/.pypi-test @@ -0,0 +1,90 @@ +# 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 + tags: + - "*" + pull_request: + +jobs: + test: + name: Running tests + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + with: + submodules: true + + - name: "Install dependencies" + run: | + sudo apt-get install zlib1g-dev + + - name: Set up Python 3.9 + uses: actions/setup-python@v2 + with: + python-version: 3.9 + cache: 'pip' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip setuptools + + - name: Test with tox + run: | + pip install tox + tox + + - name: Build docs + if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/') + run: | + tox -e docs + touch ./docs/_build/html/.nojekyll + + - name: GH Pages Deployment + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') + 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 + + build_sdist: + name: Build source distribution + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: true + + - name: Build sdist + run: pipx run build --sdist + + - uses: actions/upload-artifact@v3 + with: + path: dist/*.tar.gz + + upload_pypi: + needs: [test, build_sdist] + runs-on: ubuntu-latest + # upload to PyPI on every tag starting with 'v' + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') + # alternatively, to publish when a GitHub Release is created, use the following rule: + # if: github.event_name == 'release' && github.event.action == 'published' + steps: + - uses: actions/download-artifact@v3 + with: + # unpacks default artifact into dist/ + # if `name: artifact` is omitted, the action will create extra parent dir + name: artifact + path: dist + + - uses: pypa/gh-action-pypi-publish@v1.8.3 + with: + user: __token__ + password: ${{ secrets.PYPI_PASSWORD }} diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml new file mode 100644 index 0000000..7b591a2 --- /dev/null +++ b/.github/workflows/pypi-publish.yml @@ -0,0 +1,51 @@ +# 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@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 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 index adab766..9dc019a 100644 --- a/.github/workflows/pypi-test.yml +++ b/.github/workflows/pypi-test.yml @@ -5,86 +5,36 @@ name: Test the library on: push: - branches: - - master - tags: - - "*" + branches: [ master ] pull_request: + branches: [ master ] jobs: - test: - name: Running tests - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - with: - submodules: true - - - name: "Install dependencies" - run: | - sudo apt-get install zlib1g-dev - - - name: Set up Python 3.9 - uses: actions/setup-python@v2 - with: - python-version: 3.9 - cache: 'pip' - - - name: Install dependencies - run: | - python -m pip install --upgrade pip setuptools - - - name: Test with tox - run: | - pip install tox - tox + build: - - name: Build docs - if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/') - run: | - tox -e docs - touch ./docs/_build/html/.nojekyll - - - name: GH Pages Deployment - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') - 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 - - build_sdist: - name: Build source distribution runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - submodules: true - - - name: Build sdist - run: pipx run build --sdist + strategy: + matrix: + python-version: [ '3.8', '3.9', '3.10', '3.11', '3.12' ] - - uses: actions/upload-artifact@v3 - with: - path: dist/*.tar.gz - - upload_pypi: - needs: [test, build_sdist] - runs-on: ubuntu-latest - # upload to PyPI on every tag starting with 'v' - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') - # alternatively, to publish when a GitHub Release is created, use the following rule: - # if: github.event_name == 'release' && github.event.action == 'published' + name: Python ${{ matrix.python-version }} steps: - - uses: actions/download-artifact@v3 - with: - # unpacks default artifact into dist/ - # if `name: artifact` is omitted, the action will create extra parent dir - name: artifact - path: dist - - - uses: pypa/gh-action-pypi-publish@v1.8.3 - with: - user: __token__ - password: ${{ secrets.PYPI_PASSWORD }} + - uses: actions/checkout@v2 + - name: Setup Python + uses: actions/setup-python@v2 + 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/.isort.cfg b/.isort.cfg deleted file mode 100644 index 69105cf..0000000 --- a/.isort.cfg +++ /dev/null @@ -1,3 +0,0 @@ -[settings] -profile = black -known_first_party = biocutils diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..3cb6861 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,52 @@ +exclude: '^docs/conf.py' + +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: trailing-whitespace + - id: check-added-large-files + - id: check-ast + - id: check-json + - id: check-merge-conflict + - id: check-xml + - id: check-yaml + - id: debug-statements + - id: end-of-file-fixer + - id: requirements-txt-fixer + - 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/psf/black + rev: 23.10.1 + hooks: + - id: black + language_version: python3 + +- repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.1.3 + hooks: + - id: ruff + args: [--fix, --exit-non-zero-on-fix] + +## If like to embrace black styles even in the docs: +# - repo: https://github.com/asottile/blacken-docs +# rev: v1.13.0 +# hooks: +# - id: blacken-docs +# additional_dependencies: [black] + +## Check for misspells in documentation files: +# - repo: https://github.com/codespell-project/codespell +# rev: v2.2.5 +# hooks: +# - id: codespell diff --git a/AUTHORS.md b/AUTHORS.md index 1412e08..281a44d 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -1,3 +1,4 @@ # Contributors -* Aaron Lun [lun.aaron@gene.com](mailto:lun.aaron@gene.com) +* Aaron Lun [infinite.monkeys.with.keyboards@gmail.com](mailto:infinite.monkeys.with.keyboards@gmail.com) +* Jayaram Kancherla [jayaram.kancherla@gmail.com](mailto:jayaram.kancherla@gmail.com) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa4e2f3..506f301 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ # Changelog -## Version 0.0.1 +## Version 0.0.1 - First release of the package. diff --git a/docs/conf.py b/docs/conf.py index c3e13fc..5e211a9 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. @@ -166,6 +167,17 @@ # If this is True, todo emits a warning for each TODO entries. The default is False. todo_emit_warnings = True +autodoc_default_options = { + # 'members': 'var1, var2', + # 'member-order': 'bysource', + 'special-members': True, + 'undoc-members': True, + 'exclude-members': '__weakref__, __dict__, __str__, __module__, __init__' +} + +autosummary_generate = True +autosummary_imported_members = True + # -- Options for HTML output ------------------------------------------------- diff --git a/docs/requirements.txt b/docs/requirements.txt index 0990c2a..daecbf1 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,6 +1,8 @@ +furo # Requirements file for ReadTheDocs, check .readthedocs.yml. # To build the module reference correctly, make sure every external package # under `install_requires` in `setup.cfg` is also listed here! # sphinx_rtd_theme myst-parser[linkify] sphinx>=3.2.1 +sphinx-autodoc-typehints diff --git a/pyproject.toml b/pyproject.toml index 89a5bed..0514df9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,3 +7,18 @@ build-backend = "setuptools.build_meta" # For smarter version schemes and other configuration options, # check out https://github.com/pypa/setuptools_scm version_scheme = "no-guess-dev" + +[tool.ruff] +line-length = 120 +src = ["src"] +exclude = ["tests"] +extend-ignore = ["F821"] + +[tool.ruff.pydocstyle] +convention = "google" + +[tool.ruff.per-file-ignores] +"__init__.py" = ["E402", "F401"] + +[tool.black] +force-exclude = "__init__.py" diff --git a/setup.cfg b/setup.cfg index c3407f2..fe0ece4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,17 +5,17 @@ [metadata] name = biocutils -description = Add a short description here! +description = Utilities to use across the biocpy packages. author = Aaron Lun -author_email = lun.aaron@gene.com +author_email = infinite.monkeys.with.keyboards@gmail.com license = MIT license_files = LICENSE.txt long_description = file: README.md long_description_content_type = text/markdown; charset=UTF-8; variant=GFM -url = https://github.com/pyscaffold/pyscaffold/ +url = https://github.com/biocpy/biocutils # Add here related links, for example: project_urls = - Documentation = https://pyscaffold.org/ + Documentation = https://github.com/biocpy/biocutils # Source = https://github.com/pyscaffold/pyscaffold/ # Changelog = https://pyscaffold.org/en/latest/changelog.html # Tracker = https://github.com/pyscaffold/pyscaffold/issues @@ -41,7 +41,7 @@ package_dir = =src # Require a min/specific Python version (comma-separated conditions) -# python_requires = >=3.8 +python_requires = >=3.8 # 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/setup.py b/setup.py index 8a0c567..7f9e0df 100644 --- a/setup.py +++ b/setup.py @@ -1,10 +1,8 @@ -""" - Setup file for biocutils. - Use setup.cfg to configure your project. +"""Setup file for biocutils. Use setup.cfg to configure your project. - This file was generated with PyScaffold 4.5. - PyScaffold helps you to put up the scaffold of your new Python project. - Learn more under: https://pyscaffold.org/ +This file was generated with PyScaffold 4.5. +PyScaffold helps you to put up the scaffold of your new Python project. +Learn more under: https://pyscaffold.org/ """ from setuptools import setup diff --git a/src/biocutils/__init__.py b/src/biocutils/__init__.py index 87b92cb..390188b 100644 --- a/src/biocutils/__init__.py +++ b/src/biocutils/__init__.py @@ -16,12 +16,12 @@ del version, PackageNotFoundError from .factor import factor -from .match import match -from .map_to_index import map_to_index from .intersect import intersect -from .union import union -from .subset import subset from .is_list_of_type import is_list_of_type +from .map_to_index import map_to_index +from .match import match from .normalize_subscript import normalize_subscript -from .print_truncated import print_truncated, print_truncated_list, print_truncated_dict -from .print_wrapped_table import print_wrapped_table, create_floating_names, truncate_strings, print_type +from .print_truncated import print_truncated, print_truncated_dict, print_truncated_list +from .print_wrapped_table import create_floating_names, print_type, print_wrapped_table, truncate_strings +from .subset import subset +from .union import union diff --git a/src/biocutils/factor.py b/src/biocutils/factor.py index 6d425db..3fc7391 100644 --- a/src/biocutils/factor.py +++ b/src/biocutils/factor.py @@ -1,13 +1,10 @@ -from typing import Sequence, Tuple, Optional +from typing import Optional, Sequence, Tuple def factor( - x: Sequence, - levels: Optional[Sequence] = None, - sort_levels: bool = False + x: Sequence, levels: Optional[Sequence] = None, sort_levels: bool = False ) -> Tuple[list, list]: - """ - Convert a sequence of hashable values into a factor. + """Convert a sequence of hashable values into a factor. Args: x (Sequence): A sequence of hashable values. @@ -25,7 +22,7 @@ def factor( Returns: Tuple[list, list]: Tuple where the first list contains the unique levels and the second list contains the integer index into the first list. - Indexing the first list by the second list will recover ``x``, except + Indexing the first list by the second list will recover ``x``, except for any None values in ``x``, which will be None in the second list. """ diff --git a/src/biocutils/intersect.py b/src/biocutils/intersect.py index eecca7c..aeb99e6 100644 --- a/src/biocutils/intersect.py +++ b/src/biocutils/intersect.py @@ -4,14 +4,14 @@ def intersect(*x: Sequence, duplicate_method: DUPLICATE_METHOD = "first") -> list: - """Identify the intersection of values in multiple sequences, while - preserving the order of values in the first sequence. + """Identify the intersection of values in multiple sequences, while preserving the order of values in the first + sequence. Args: - x (Sequence): + x (Sequence): Zero, one or more sequences of interest. - duplicate_method (DUPLICATE_METHOD): + duplicate_method (DUPLICATE_METHOD): Whether to keep the first or last occurrence of duplicated values when preserving order in the first sequence. @@ -61,8 +61,9 @@ def handler(f): state[0] += 1 state[1] = i - # Going through the first vector again to preserve order. + # Going through the first vector again to preserve order. output = [] + def handler(f): if f is not None and f in occurrences: state = occurrences[f] diff --git a/src/biocutils/is_list_of_type.py b/src/biocutils/is_list_of_type.py index d422f16..55464ff 100644 --- a/src/biocutils/is_list_of_type.py +++ b/src/biocutils/is_list_of_type.py @@ -1,11 +1,13 @@ -from typing import Any, Callable, Union +from typing import Callable, Union __author__ = "jkanche" __copyright__ = "jkanche" __license__ = "MIT" -def is_list_of_type(x: Union[list, tuple], target_type: Callable, ignore_none: bool = False) -> bool: +def is_list_of_type( + x: Union[list, tuple], target_type: Callable, ignore_none: bool = False +) -> bool: """Checks if ``x`` is a list, and whether all elements of the list are of the same type. Args: diff --git a/src/biocutils/map_to_index.py b/src/biocutils/map_to_index.py index dcd5b8c..3355d71 100644 --- a/src/biocutils/map_to_index.py +++ b/src/biocutils/map_to_index.py @@ -1,8 +1,8 @@ -from typing import Sequence, Literal - +from typing import Literal, Sequence DUPLICATE_METHOD = Literal["first", "last"] + def map_to_index(x: Sequence, duplicate_method: DUPLICATE_METHOD = "first") -> dict: """Create a dictionary to map the values of a sequence to its positional indices. diff --git a/src/biocutils/normalize_subscript.py b/src/biocutils/normalize_subscript.py index fa699d7..f3bb214 100644 --- a/src/biocutils/normalize_subscript.py +++ b/src/biocutils/normalize_subscript.py @@ -1,28 +1,38 @@ -from typing import Union, Sequence, Optional, Tuple +from typing import Optional, Sequence, Tuple, Union def _raise_int(idx: int, length): - raise IndexError("subscript (" + str(idx) + ") out of range for vector-like object of length " + str(length)) + raise IndexError( + "subscript (" + + str(idx) + + ") out of range for vector-like object of length " + + str(length) + ) has_numpy = False try: import numpy + has_numpy = True -except: +except Exception: pass -def normalize_subscript(sub: Union[slice, range, Sequence, int, str, bool], length: int, names: Optional[Sequence[str]] = None, non_negative_only: bool = True) -> Tuple: - """ - Normalize a subscript for ``__getitem__`` or friends into a sequence of - integer indices, for consistent downstream use. +def normalize_subscript( + sub: Union[slice, range, Sequence, int, str, bool], + length: int, + names: Optional[Sequence[str]] = None, + non_negative_only: bool = True, +) -> Tuple: + """Normalize a subscript for ``__getitem__`` or friends into a sequence of integer indices, for consistent + downstream use. Args: - sub: + sub: The subscript. This can be any of the following: - - A slice of elements. + - A slice of elements. - A range containing indices to elements. Negative values are allowed. An error is raised if the indices are out of range. - A single integer specifying the index of an element. A negative @@ -54,7 +64,9 @@ def normalize_subscript(sub: Union[slice, range, Sequence, int, str, bool], leng specifying the subscript elements, and (ii) a boolean indicating whether ``sub`` was a scalar. """ - if isinstance(sub, bool) or (has_numpy and isinstance(sub, numpy.bool_)): # before ints, as bools are ints. + if isinstance(sub, bool) or ( + has_numpy and isinstance(sub, numpy.bool_) + ): # before ints, as bools are ints. if sub: return [0], True else: @@ -69,7 +81,11 @@ def normalize_subscript(sub: Union[slice, range, Sequence, int, str, bool], leng if isinstance(sub, str): if names is None: - raise IndexError("failed to find subscript '" + sub + "' for vector-like object with no names") + raise IndexError( + "failed to find subscript '" + + sub + + "' for vector-like object with no names" + ) return [names.index(sub)], True if isinstance(sub, slice): @@ -96,17 +112,22 @@ def normalize_subscript(sub: Union[slice, range, Sequence, int, str, bool], leng if sub.stop < 0: return range(length + sub.start, length + sub.stop, sub.step), False else: - return [ (x < 0) * length + x for x in sub], False + return [(x < 0) * length + x for x in sub], False else: if sub.stop < 0: - return [ (x < 0) * length + x for x in sub], False + return [(x < 0) * length + x for x in sub], False else: return sub, False can_return_early = True for x in sub: - if isinstance(x, str) or isinstance(x, bool) or (has_numpy and isinstance(x, numpy.bool_)) or (x < 0 and non_negative_only): - can_return_early = False; + if ( + isinstance(x, str) + or isinstance(x, bool) + or (has_numpy and isinstance(x, numpy.bool_)) + or (x < 0 and non_negative_only) + ): + can_return_early = False break if can_return_early: @@ -137,13 +158,15 @@ def normalize_subscript(sub: Union[slice, range, Sequence, int, str, bool], leng if len(has_strings): if names is None: - raise IndexError("cannot find string subscripts for vector-like object with no names") + raise IndexError( + "cannot find string subscripts for vector-like object with no names" + ) mapping = {} for i, y in enumerate(names): if y in has_strings: mapping[y] = i - has_strings.remove(y) # remove it so we only consider the first. + has_strings.remove(y) # remove it so we only consider the first. for i in string_positions: output[i] = mapping[sub[i]] diff --git a/src/biocutils/print_truncated.py b/src/biocutils/print_truncated.py index cb003b3..52ab420 100644 --- a/src/biocutils/print_truncated.py +++ b/src/biocutils/print_truncated.py @@ -1,11 +1,9 @@ -from typing import Optional, List, Callable, Dict +from typing import Callable, Dict, List, Optional def print_truncated(x, truncated_to: int = 3, full_threshold: int = 10) -> str: - """ - Pretty-print an object, replacing the middle elements of lists/dictionaries - with an ellipsis if there are too many. This provides a useful preview of - an object without spewing out all of its contents on the screen. + """Pretty-print an object, replacing the middle elements of lists/dictionaries with an ellipsis if there are too + many. This provides a useful preview of an object without spewing out all of its contents on the screen. Args: x: Object to be printed. @@ -22,18 +20,27 @@ def print_truncated(x, truncated_to: int = 3, full_threshold: int = 10) -> str: String containing the pretty-printed contents. """ if isinstance(x, dict): - return print_truncated_dict(x, truncated_to=truncated_to, full_threshold=full_threshold) + return print_truncated_dict( + x, truncated_to=truncated_to, full_threshold=full_threshold + ) elif isinstance(x, list): - return print_truncated_list(x, truncated_to=truncated_to, full_threshold=full_threshold) + return print_truncated_list( + x, truncated_to=truncated_to, full_threshold=full_threshold + ) else: return repr(x) -def print_truncated_list(x: List, truncated_to: int = 3, full_threshold: int = 10, transform: Optional[Callable] = None, sep: str = ", ", include_brackets: bool = True) -> str: - """ - Pretty-print a list, replacing the middle elements with an ellipsis if - there are too many. This provides a useful preview of an object without - spewing out all of its contents on the screen. +def print_truncated_list( + x: List, + truncated_to: int = 3, + full_threshold: int = 10, + transform: Optional[Callable] = None, + sep: str = ", ", + include_brackets: bool = True, +) -> str: + """Pretty-print a list, replacing the middle elements with an ellipsis if there are too many. This provides a useful + preview of an object without spewing out all of its contents on the screen. Args: x: List to be printed. @@ -58,11 +65,15 @@ def print_truncated_list(x: List, truncated_to: int = 3, full_threshold: int = 1 Whether to include the start/end brackets. Returns: - String containing the pretty-printed truncated list. + String containing the pretty-printed truncated list. """ collected = [] if transform is None: - transform = lambda y : print_truncated(y, truncated_to=truncated_to, full_threshold=full_threshold) + + def transform(y): + return print_truncated( + y, truncated_to=truncated_to, full_threshold=full_threshold + ) if len(x) > full_threshold and len(x) > truncated_to * 2: for i in range(truncated_to): @@ -80,11 +91,16 @@ def print_truncated_list(x: List, truncated_to: int = 3, full_threshold: int = 1 return output -def print_truncated_dict(x: Dict, truncated_to: int = 3, full_threshold: int = 10, transform: Optional[Callable] = None, sep: str = ", ", include_brackets: bool = True) -> str: - """ - Pretty-print a dictionary, replacing the middle elements with an ellipsis - if there are too many. This provides a useful preview of an object without - spewing out all of its contents on the screen. +def print_truncated_dict( + x: Dict, + truncated_to: int = 3, + full_threshold: int = 10, + transform: Optional[Callable] = None, + sep: str = ", ", + include_brackets: bool = True, +) -> str: + """Pretty-print a dictionary, replacing the middle elements with an ellipsis if there are too many. This provides a + useful preview of an object without spewing out all of its contents on the screen. Args: x: Dictionary to be printed. @@ -113,7 +129,11 @@ def print_truncated_dict(x: Dict, truncated_to: int = 3, full_threshold: int = 1 """ collected = [] if transform is None: - transform = lambda y : print_truncated(y, truncated_to=truncated_to, full_threshold=full_threshold) + + def transform(y): + return print_truncated( + y, truncated_to=truncated_to, full_threshold=full_threshold + ) all_keys = x.keys() if len(x) > full_threshold and len(x) > truncated_to * 2: diff --git a/src/biocutils/print_wrapped_table.py b/src/biocutils/print_wrapped_table.py index 8e8cdf9..61cd3e7 100644 --- a/src/biocutils/print_wrapped_table.py +++ b/src/biocutils/print_wrapped_table.py @@ -1,4 +1,5 @@ -from typing import Sequence, List, Optional +from typing import List, Optional, Sequence + from .subset import subset @@ -10,12 +11,15 @@ def _get_max_width(col: List[str]): return width -def print_wrapped_table(columns: List[Sequence[str]], floating_names: Optional[Sequence[str]] = None, sep: str = " ", window: Optional[int] = None) -> str: - """ - Pretty-print a table with aligned and wrapped columns. All column contents - are padded so that they are right-justified. Wrapping is performed whenever - a new column would exceed the window width, in which case the entire column - (and all subsequent columns) are printed below the previous columns. +def print_wrapped_table( + columns: List[Sequence[str]], + floating_names: Optional[Sequence[str]] = None, + sep: str = " ", + window: Optional[int] = None, +) -> str: + """Pretty-print a table with aligned and wrapped columns. All column contents are padded so that they are right- + justified. Wrapping is performed whenever a new column would exceed the window width, in which case the entire + column (and all subsequent columns) are printed below the previous columns. Args: columns: @@ -31,7 +35,7 @@ def print_wrapped_table(columns: List[Sequence[str]], floating_names: Optional[S List of strings to be added to the left of the table. This is printed repeatedly for each set of wrapped columns. - See also :py:meth:`~create_floating_names`. + See also :py:meth:`~create_floating_names`. sep: Separator between columns. @@ -48,7 +52,7 @@ def print_wrapped_table(columns: List[Sequence[str]], floating_names: Optional[S try: window = os.get_terminal_size().columns - except: + except Exception: window = 150 if len(columns) == 0: @@ -96,10 +100,11 @@ def reinitialize(): return output -def create_floating_names(names: Optional[List[str]], indices: Sequence[int]) -> List[str]: - """ - Create the floating names to use in :py:meth:`~print_wrapped_table`. If no - names are present, positional indices are used instead. +def create_floating_names( + names: Optional[List[str]], indices: Sequence[int] +) -> List[str]: + """Create the floating names to use in :py:meth:`~print_wrapped_table`. If no names are present, positional indices + are used instead. Args: names: @@ -109,7 +114,7 @@ def create_floating_names(names: Optional[List[str]], indices: Sequence[int]) -> Integer indices for which to obtain the names. Returns: - List of strings containing floating names. + List of strings containing floating names. """ if names is not None: return subset(names, indices) @@ -118,8 +123,7 @@ def create_floating_names(names: Optional[List[str]], indices: Sequence[int]) -> def truncate_strings(values: List[str], width: int = 40) -> List[str]: - """ - Truncate long strings for printing in :py:meth:`~print_wrapped_table`. + """Truncate long strings for printing in :py:meth:`~print_wrapped_table`. Args: values: @@ -139,10 +143,8 @@ def truncate_strings(values: List[str], width: int = 40) -> List[str]: 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`. + """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. @@ -153,6 +155,7 @@ def print_type(x) -> str: cls = type(x).__name__ import sys + if "numpy" in sys.modules: numpy = sys.modules["numpy"] if isinstance(x, numpy.ndarray): diff --git a/src/biocutils/subset.py b/src/biocutils/subset.py index b356445..0724c20 100644 --- a/src/biocutils/subset.py +++ b/src/biocutils/subset.py @@ -1,10 +1,9 @@ -from typing import Union, Sequence, Any +from typing import Any, Sequence, Union def subset(x: Any, indices: Union[Sequence[int], slice]) -> Any: - """Subset ``x`` by ``indices`` to obtain a new object with the desired - subset of elements. This attempts to use ``x``'s ``__getitem__`` method, if - available; otherwise it falls back to iteration over the indices. + """Subset ``x`` by ``indices`` to obtain a new object with the desired subset of elements. This attempts to use + ``x``'s ``__getitem__`` method, if available; otherwise it falls back to iteration over the indices. If ``x`` has a ``shape`` method that returns a tuple (a la NumPy arrays), subsetting is only attempted on the first dimension via the ``__getitem__`` @@ -20,7 +19,7 @@ def subset(x: Any, indices: Union[Sequence[int], slice]) -> Any: of ``x`` to extract. Returns: - Any: The result of slicing ``x`` by ``indices``. The exact type + Any: The result of slicing ``x`` by ``indices``. The exact type depends on what ``x``'s ``__getitem__`` method returns, if it accepts a slice and/or sequence of indices. Otherwise, a list is returned containing the desired entries of ``x``. @@ -33,7 +32,7 @@ def subset(x: Any, indices: Union[Sequence[int], slice]) -> Any: return x[(*expanded,)] else: return x[indices] - except: + except Exception: pass if isinstance(indices, slice): diff --git a/src/biocutils/union.py b/src/biocutils/union.py index 89731fa..5687582 100644 --- a/src/biocutils/union.py +++ b/src/biocutils/union.py @@ -4,14 +4,14 @@ def union(*x: Sequence, duplicate_method: DUPLICATE_METHOD = "first") -> list: - """Identify the union of values in multiple sequences, while preserving the - order of the first (or last) occurence of each value. + """Identify the union of values in multiple sequences, while preserving the order of the first (or last) occurence + of each value. Args: - x (Sequence): + x (Sequence): Zero, one or more sequences of interest. - duplicate_method (DUPLICATE_METHOD): + duplicate_method (DUPLICATE_METHOD): Whether to take the first or last occurrence of each value in the ordering of the output. If first, the first occurrence in the earliest sequence of ``x`` is reported; if last, the last @@ -27,6 +27,7 @@ def union(*x: Sequence, duplicate_method: DUPLICATE_METHOD = "first") -> list: output = [] present = set() + def handler(f): if f is not None and f not in present: output.append(f) diff --git a/tests/conftest.py b/tests/conftest.py index 8cfd151..1407ecd 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,10 +1,9 @@ -""" - Dummy conftest.py for biocutils. +"""Dummy conftest.py for biocutils. - If you don't know what this is for, just leave it empty. - Read more about conftest.py under: - - https://docs.pytest.org/en/stable/fixture.html - - https://docs.pytest.org/en/stable/writing_plugins.html +If you don't know what this is for, just leave it empty. +Read more about conftest.py under: +- https://docs.pytest.org/en/stable/fixture.html +- https://docs.pytest.org/en/stable/writing_plugins.html """ # import pytest diff --git a/tests/test_factor.py b/tests/test_factor.py index 9a2f72d..44e6337 100644 --- a/tests/test_factor.py +++ b/tests/test_factor.py @@ -18,30 +18,32 @@ def test_factor_simple(): def test_factor_levels(): - revlev = [5,4,3,2,1] + revlev = [5, 4, 3, 2, 1] lev, ind = factor([1, 3, 5, 5, 3, 1], levels=revlev) assert lev == revlev - assert ind == [4,2,0,0,2,4] + assert ind == [4, 2, 0, 0, 2, 4] # Preserves duplicates. - duplicated = [5,4,5,4,3,4,2,3,1,1,2] + duplicated = [5, 4, 5, 4, 3, 4, 2, 3, 1, 1, 2] lev, ind = factor([1, 3, 5, 5, 3, 1], levels=duplicated) assert lev == duplicated - assert ind == [8,4,0,0,4,8] + assert ind == [8, 4, 0, 0, 4, 8] # Ignores None. - noney = [None,1,2,3,4,5,None] + noney = [None, 1, 2, 3, 4, 5, None] lev, ind = factor([1, 3, 5, 5, 3, 1], levels=noney) assert lev == noney - assert ind == [1,3,5,5,3,1] + assert ind == [1, 3, 5, 5, 3, 1] def test_factor_sorted(): - lev, ind = factor(["C", "D", "A", "B", "C", "A"], sort_levels = True) + lev, ind = factor(["C", "D", "A", "B", "C", "A"], sort_levels=True) assert lev == ["A", "B", "C", "D"] - assert ind == [2,3,0,1,2,0] + assert ind == [2, 3, 0, 1, 2, 0] # Not affected if you supply the levels directly. - lev, ind = factor(["C", "D", "A", "B", "C", "A"], levels = ["D", "C", "B", "A"], sort_levels = True) + lev, ind = factor( + ["C", "D", "A", "B", "C", "A"], levels=["D", "C", "B", "A"], sort_levels=True + ) assert lev == ["D", "C", "B", "A"] - assert ind == [1,0,3,2,1,3] + assert ind == [1, 0, 3, 2, 1, 3] diff --git a/tests/test_intersect.py b/tests/test_intersect.py index def45a8..698f036 100644 --- a/tests/test_intersect.py +++ b/tests/test_intersect.py @@ -17,32 +17,26 @@ def test_intersect_simple(): def test_intersect_duplicates(): # Doesn't report B, D, or F, despite the fact they have multiple counts. - out = intersect( - ["B", "B", "C", "A", "D", "D", "E"], ["A", "A", "C", "E", "F", "F"] - ) + out = intersect(["B", "B", "C", "A", "D", "D", "E"], ["A", "A", "C", "E", "F", "F"]) assert out == ["C", "A", "E"] # Doesn't report A multiple times. - out = intersect( - ["C", "A", "D", "A", "E", "A"], ["A", "C", "E", "F"] - ) + out = intersect(["C", "A", "D", "A", "E", "A"], ["A", "C", "E", "F"]) assert out == ["C", "A", "E"] # Switches the order of A being reported. out = intersect( - ["C", "A", "D", "A", "E", "A"], - ["A", "C", "E", "F"], - duplicate_method = "last" + ["C", "A", "D", "A", "E", "A"], ["A", "C", "E", "F"], duplicate_method="last" ) assert out == ["C", "E", "A"] # Handles the single case correctly. single = ["A", "B", "A", "C", "D", "E", "D", "C"] out = intersect(single) - assert out == [ "A", "B", "C", "D", "E" ] + assert out == ["A", "B", "C", "D", "E"] - out = intersect(single, duplicate_method = "last") - assert out == [ "B", "A", "E", "D", "C" ] + out = intersect(single, duplicate_method="last") + assert out == ["B", "A", "E", "D", "C"] def test_intersect_none(): diff --git a/tests/test_list_type_checks.py b/tests/test_list_type_checks.py index 78a8f6a..6496ca9 100644 --- a/tests/test_list_type_checks.py +++ b/tests/test_list_type_checks.py @@ -16,7 +16,7 @@ def test_simple_list(): xt = (1, 2, None) assert not is_list_of_type(xt, int) - assert is_list_of_type(xt, int, ignore_none = True) + assert is_list_of_type(xt, int, ignore_none=True) def test_should_fail(): diff --git a/tests/test_map_to_index.py b/tests/test_map_to_index.py index 7c307be..9918c78 100644 --- a/tests/test_map_to_index.py +++ b/tests/test_map_to_index.py @@ -3,18 +3,22 @@ def test_map_to_index_simple(): mapping = map_to_index(["A", "B", "C", "D"]) - assert mapping == { "A": 0, "B": 1, "C": 2, "D": 3 } + assert mapping == {"A": 0, "B": 1, "C": 2, "D": 3} + def test_map_to_index_duplicates(): duplicated = ["A", "B", "C", "D", "A", "B", "C", "D"] mapping = map_to_index(duplicated) - assert mapping == { "A": 0, "B": 1, "C": 2, "D": 3 } + assert mapping == {"A": 0, "B": 1, "C": 2, "D": 3} + + mapping = map_to_index( + ["A", "B", "C", "D", "A", "B", "C", "D"], duplicate_method="last" + ) + assert mapping == {"A": 4, "B": 5, "C": 6, "D": 7} - mapping = map_to_index(["A", "B", "C", "D", "A", "B", "C", "D"], duplicate_method="last") - assert mapping == { "A": 4, "B": 5, "C": 6, "D": 7 } def test_map_to_index_none(): - noney = [ None, "A", None, "B", None, "C", None, "D", None ] + noney = [None, "A", None, "B", None, "C", None, "D", None] mapping = map_to_index(noney) - assert mapping == { "A": 1, "B": 3, "C": 5, "D": 7 } + assert mapping == {"A": 1, "B": 3, "C": 5, "D": 7} diff --git a/tests/test_match.py b/tests/test_match.py index 2c1dc2e..23af59b 100644 --- a/tests/test_match.py +++ b/tests/test_match.py @@ -11,6 +11,7 @@ def test_match_simple(): mm2 = match(x, map_to_index(levels)) assert mm == mm2 + def test_match_duplicates(): x = [5, 1, 2, 3, 5, 6, 7, 7, 2, 1] mm = match(x, [1, 2, 3, 3, 5, 6, 1, 7, 6]) @@ -19,6 +20,7 @@ def test_match_duplicates(): mm = match(x, [1, 2, 3, 3, 5, 6, 1, 7, 6], duplicate_method="last") assert mm == [4, 6, 1, 3, 4, 8, 7, 7, 1, 6] + def test_match_none(): mm = match(["A", None, "B", "D", None, "A", "C", None, "B"], ["D", "C", "B", "A"]) assert list(mm) == [3, None, 2, 0, None, 3, 1, None, 2] diff --git a/tests/test_normalize_subscript.py b/tests/test_normalize_subscript.py index 3516ae7..77ea68e 100644 --- a/tests/test_normalize_subscript.py +++ b/tests/test_normalize_subscript.py @@ -8,8 +8,11 @@ def test_normalize_subscript_scalars(): assert normalize_subscript(-1, 100) == ([99], True) assert normalize_subscript(True, 100) == ([0], True) assert normalize_subscript(False, 100) == ([], False) - assert normalize_subscript("C", 5, ["A", "B", "C", "D", "E" ]) == ([2], True) - assert normalize_subscript("B", 5, ["A", "B", "C", "B", "E" ]) == ([1], True) # takes first occurence. + assert normalize_subscript("C", 5, ["A", "B", "C", "D", "E"]) == ([2], True) + assert normalize_subscript("B", 5, ["A", "B", "C", "B", "E"]) == ( + [1], + True, + ) # takes first occurence. with pytest.raises(IndexError) as ex: normalize_subscript(100, 10) @@ -35,8 +38,14 @@ def test_normalize_subscript_slice(): def test_normalize_subscript_range(): assert normalize_subscript(range(5, 2), 100) == ([], False) assert normalize_subscript(range(10, 40), 100) == (range(10, 40), False) - assert normalize_subscript(range(-10, 40), 100) == (list(range(90, 100)) + list(range(40)), False) - assert normalize_subscript(range(50, -10, -1), 100) == (list(range(50, -1, -1)) + list(range(99, 90, -1)), False) + assert normalize_subscript(range(-10, 40), 100) == ( + list(range(90, 100)) + list(range(40)), + False, + ) + assert normalize_subscript(range(50, -10, -1), 100) == ( + list(range(50, -1, -1)) + list(range(99, 90, -1)), + False, + ) assert normalize_subscript(range(-10, -50, -1), 100) == (range(90, 50, -1), False) with pytest.raises(IndexError) as ex: @@ -61,52 +70,79 @@ def test_normalize_subscript_range(): def test_normalize_subscript_chaos(): - assert normalize_subscript([0,2,4,6,8], 50) == ([0,2,4,6,8], False) + assert normalize_subscript([0, 2, 4, 6, 8], 50) == ([0, 2, 4, 6, 8], False) with pytest.raises(IndexError) as ex: - normalize_subscript([0,2,50,6,8], 50) + normalize_subscript([0, 2, 50, 6, 8], 50) assert str(ex.value).find("subscript (50)") >= 0 - assert normalize_subscript([0,-1,2,-3,4,-5,6,-7,8], 50) == ([0,49,2,47,4,45,6,43,8], False) + assert normalize_subscript([0, -1, 2, -3, 4, -5, 6, -7, 8], 50) == ( + [0, 49, 2, 47, 4, 45, 6, 43, 8], + False, + ) with pytest.raises(IndexError) as ex: - normalize_subscript([0,2,-51,6,8], 50) + normalize_subscript([0, 2, -51, 6, 8], 50) assert str(ex.value).find("subscript (-51)") >= 0 - assert normalize_subscript([False,10,True,20,False,30,True], 50) == ([10,2,20,30,6], False) + assert normalize_subscript([False, 10, True, 20, False, 30, True], 50) == ( + [10, 2, 20, 30, 6], + False, + ) names = ["A", "B", "C", "D", "E", "F"] - assert normalize_subscript(["B",1,"D",2,"F",3,"A"], 6, names) == ([1,1,3,2,5,3,0], False) - assert normalize_subscript(["B",1,"A",2,"B",3,"A"], 6, ["A", "B", "A", "B", "A", "B"]) == ([1,1,0,2,1,3,0], False) # Takes the first occurence. + assert normalize_subscript(["B", 1, "D", 2, "F", 3, "A"], 6, names) == ( + [1, 1, 3, 2, 5, 3, 0], + False, + ) + assert normalize_subscript( + ["B", 1, "A", 2, "B", 3, "A"], 6, ["A", "B", "A", "B", "A", "B"] + ) == ( + [1, 1, 0, 2, 1, 3, 0], + False, + ) # Takes the first occurence. with pytest.raises(KeyError) as ex: - normalize_subscript(["B",1,"D",2,"G",3,"A"], 6, names) + normalize_subscript(["B", 1, "D", 2, "G", 3, "A"], 6, names) with pytest.raises(IndexError) as ex: - normalize_subscript(["B",1,"D",2,"F",3,"A"], 6) + normalize_subscript(["B", 1, "D", 2, "F", 3, "A"], 6) assert str(ex.value).find("vector-like object with no names") >= 0 def test_normalize_subscript_numpy(): out, x = normalize_subscript(numpy.array([1, 3, 5]), 6) - assert (out == numpy.array([1,3,5])).all() + assert (out == numpy.array([1, 3, 5])).all() out, x = normalize_subscript(numpy.array([-1, -3, -5]), 6) - assert (out == numpy.array([5,3,1])).all() + assert (out == numpy.array([5, 3, 1])).all() assert normalize_subscript(numpy.int64(5), 6) == ([5], True) assert normalize_subscript(numpy.bool_(True), 6) == ([0], True) # Now the trickiest part - are booleans converted correctly? - assert normalize_subscript(numpy.array([True, False, True, False, True]), 5) == ([0, 2, 4], False) + assert normalize_subscript(numpy.array([True, False, True, False, True]), 5) == ( + [0, 2, 4], + False, + ) def test_normalize_subscript_allow_negative(): assert normalize_subscript(-50, 100, non_negative_only=False) == ([-50], True) - assert normalize_subscript(range(50, -10, -1), 100, non_negative_only=False) == (range(50, -10, -1), False) - assert normalize_subscript(range(-10, -50, -1), 100, non_negative_only=False) == (range(-10, -50, -1), False) - assert normalize_subscript([0,-1,2,-3,4,-5,6,-7,8], 50, non_negative_only=False) == ([0,-1,2,-3,4,-5,6,-7,8], False) + assert normalize_subscript(range(50, -10, -1), 100, non_negative_only=False) == ( + range(50, -10, -1), + False, + ) + assert normalize_subscript(range(-10, -50, -1), 100, non_negative_only=False) == ( + range(-10, -50, -1), + False, + ) + assert normalize_subscript( + [0, -1, 2, -3, 4, -5, 6, -7, 8], 50, non_negative_only=False + ) == ([0, -1, 2, -3, 4, -5, 6, -7, 8], False) with pytest.raises(IndexError) as ex: - normalize_subscript([0,-1,2,-3,4,-51,6,-7,8], 50, non_negative_only=False) + normalize_subscript( + [0, -1, 2, -3, 4, -51, 6, -7, 8], 50, non_negative_only=False + ) assert str(ex.value).find("subscript (-51) out of range") >= 0 diff --git a/tests/test_package_utils.py b/tests/test_package_utils.py index 5373269..46a571c 100644 --- a/tests/test_package_utils.py +++ b/tests/test_package_utils.py @@ -16,7 +16,8 @@ def test_for_scipy(): assert pkg is False + def test_for_numpy(): pkg = is_package_installed("numpy") - assert pkg is True \ No newline at end of file + assert pkg is True diff --git a/tests/test_print_truncated.py b/tests/test_print_truncated.py index df49439..02b1629 100644 --- a/tests/test_print_truncated.py +++ b/tests/test_print_truncated.py @@ -5,19 +5,55 @@ def test_print_truncated_list(): assert print_truncated_list(range(6)) == repr(list(range(6))) assert print_truncated_list(range(10)) == repr(list(range(10))) assert print_truncated_list(range(200)) == "[0, 1, 2, ..., 197, 198, 199]" - assert print_truncated_list(["A", "B", "C", "D", "E", "F"], transform=lambda x : repr("foo_" + x)) == "['foo_A', 'foo_B', 'foo_C', 'foo_D', 'foo_E', 'foo_F']" - assert print_truncated_list(["A", "B", "C", "D", "E", "F"], truncated_to=2, full_threshold=5, transform=lambda x : repr("foo_" + x)) == "['foo_A', 'foo_B', ..., 'foo_E', 'foo_F']" - assert print_truncated_list(range(200), sep=" ", include_brackets=False) == "0 1 2 ... 197 198 199" + assert ( + print_truncated_list( + ["A", "B", "C", "D", "E", "F"], transform=lambda x: repr("foo_" + x) + ) + == "['foo_A', 'foo_B', 'foo_C', 'foo_D', 'foo_E', 'foo_F']" + ) + assert ( + print_truncated_list( + ["A", "B", "C", "D", "E", "F"], + truncated_to=2, + full_threshold=5, + transform=lambda x: repr("foo_" + x), + ) + == "['foo_A', 'foo_B', ..., 'foo_E', 'foo_F']" + ) + assert ( + print_truncated_list(range(200), sep=" ", include_brackets=False) + == "0 1 2 ... 197 198 199" + ) def test_print_truncated_dict(): - assert print_truncated_dict({"A":"B"}) == "{'A': 'B'}" - assert print_truncated_dict({"A":"B", "C": [1,2,3,4,5,6,7,8,9,10,11]}) == "{'A': 'B', 'C': [1, 2, 3, ..., 9, 10, 11]}" - assert print_truncated_dict({"A":-1, "B": 0, "C": 1, "D": 2, "E": True, "F": False }, truncated_to = 2, full_threshold = 5) == "{'A': -1, 'B': 0, ..., 'E': True, 'F': False}" - assert print_truncated_dict({"A":-1, "B": 0, "C": 1, "D": 2, "E": True, "F": False }, sep=" ", include_brackets=False) == "'A': -1 'B': 0 'C': 1 'D': 2 'E': True 'F': False" + assert print_truncated_dict({"A": "B"}) == "{'A': 'B'}" + assert ( + print_truncated_dict({"A": "B", "C": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]}) + == "{'A': 'B', 'C': [1, 2, 3, ..., 9, 10, 11]}" + ) + assert ( + print_truncated_dict( + {"A": -1, "B": 0, "C": 1, "D": 2, "E": True, "F": False}, + truncated_to=2, + full_threshold=5, + ) + == "{'A': -1, 'B': 0, ..., 'E': True, 'F': False}" + ) + assert ( + print_truncated_dict( + {"A": -1, "B": 0, "C": 1, "D": 2, "E": True, "F": False}, + sep=" ", + include_brackets=False, + ) + == "'A': -1 'B': 0 'C': 1 'D': 2 'E': True 'F': False" + ) def test_print_truncated(): - internal = {"A":-1, "B": 0, "C": 1, "D": 2, "E": True, "F": False } + internal = {"A": -1, "B": 0, "C": 1, "D": 2, "E": True, "F": False} expected = "{'A': -1, 'B': 0, ..., 'E': True, 'F': False}" - assert print_truncated([internal] * 10, truncated_to = 2, full_threshold = 5) == "[" + (expected + ", " ) * 2 + "..." + (", " + expected) * 2 + "]" + assert ( + print_truncated([internal] * 10, truncated_to=2, full_threshold=5) + == "[" + (expected + ", ") * 2 + "..." + (", " + expected) * 2 + "]" + ) diff --git a/tests/test_print_wrapped_table.py b/tests/test_print_wrapped_table.py index 5df943d..f2d9906 100644 --- a/tests/test_print_wrapped_table.py +++ b/tests/test_print_wrapped_table.py @@ -1,4 +1,9 @@ -from biocutils import print_wrapped_table, create_floating_names, truncate_strings, print_type +from biocutils import ( + print_wrapped_table, + create_floating_names, + truncate_strings, + print_type, +) import numpy as np @@ -9,7 +14,11 @@ def test_print_wrapped_table(): ["asyudgausydga", "A", "B", "C", "D"], ] print(print_wrapped_table(contents)) - print(print_wrapped_table(contents, floating_names=["", "aarg", "boo", "ffoo", "stuff"])) + print( + print_wrapped_table( + contents, floating_names=["", "aarg", "boo", "ffoo", "stuff"] + ) + ) print(print_wrapped_table(contents, window=10)) print( print_wrapped_table( @@ -19,16 +28,21 @@ def test_print_wrapped_table(): def test_create_floating_names(): - assert create_floating_names(None, [1,2,3,4]) == [ "[1]", "[2]", "[3]", "[4]" ] - assert create_floating_names(["A", "B", "C", "D", "E", "F"], [1,2,3,4]) == [ "B", "C", "D", "E" ] + assert create_floating_names(None, [1, 2, 3, 4]) == ["[1]", "[2]", "[3]", "[4]"] + assert create_floating_names(["A", "B", "C", "D", "E", "F"], [1, 2, 3, 4]) == [ + "B", + "C", + "D", + "E", + ] def test_truncate_strings(): - ref = ["A"*10, "B"*20, "C"*30] - assert truncate_strings(ref, width=25) == [ "A"*10, "B"*20, "C"*22 + "..." ] + ref = ["A" * 10, "B" * 20, "C" * 30] + assert truncate_strings(ref, width=25) == ["A" * 10, "B" * 20, "C" * 22 + "..."] def test_print_type(): - assert print_type(np.array([1,2,3])) == "ndarray[int64]" - assert print_type(np.array([1,2.5,3.3])) == "ndarray[float64]" - assert print_type([1,2,3]) == "list" + assert print_type(np.array([1, 2, 3])) == "ndarray[int64]" + assert print_type(np.array([1, 2.5, 3.3])) == "ndarray[float64]" + assert print_type([1, 2, 3]) == "list" diff --git a/tests/test_subset.py b/tests/test_subset.py index 869b7c8..afb6efc 100644 --- a/tests/test_subset.py +++ b/tests/test_subset.py @@ -3,14 +3,14 @@ def test_subset_list(): - x = [1,2,3,4,5] - assert subset(x, [0,2,4]) == [1,3,5] + x = [1, 2, 3, 4, 5] + assert subset(x, [0, 2, 4]) == [1, 3, 5] - x = [1,2,3,4,5] + x = [1, 2, 3, 4, 5] assert subset(x, slice(None)) == x - x = [1,2,3,4,5] - assert subset(x, range(4, -1, -1)) == [5,4,3,2,1] + x = [1, 2, 3, 4, 5] + assert subset(x, range(4, -1, -1)) == [5, 4, 3, 2, 1] def test_subset_numpy(): diff --git a/tests/test_union.py b/tests/test_union.py index 3de8e31..3fd5078 100644 --- a/tests/test_union.py +++ b/tests/test_union.py @@ -7,7 +7,7 @@ def test_union_simple(): y = ["B", "C", "A", "D", "E"] assert union(y) == y - out = union(y, [ "A", "C", "E", "F" ]) + out = union(y, ["A", "C", "E", "F"]) assert out == ["B", "C", "A", "D", "E", "F"] out = union(["B", "C", "A", "D", "E"], ["A", "C", "K", "E"], ["G", "K"]) @@ -21,13 +21,10 @@ def test_union_duplicates(): out = union(y1, y2) assert out == ["B", "C", "A", "D", "E", "F"] - out = union(y1, y2, duplicate_method = "last") + out = union(y1, y2, duplicate_method="last") assert out == ["B", "D", "A", "C", "E", "F"] def test_union_none(): - out = union( - ["B", None, "C", "A", None, "D", "E"], - ["A", None, "C", "E", None, "F"] - ) + out = union(["B", None, "C", "A", None, "D", "E"], ["A", None, "C", "E", None, "F"]) assert out == ["B", "C", "A", "D", "E", "F"]