diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 3101ca59..9f813d65 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - PYTHON_VERSION: ["3.7", "3.8", "3.9", "3.10"] + PYTHON_VERSION: ["3.8", "3.9", "3.10"] steps: - uses: actions/checkout@v2 diff --git a/.readthedocs.yml b/.readthedocs.yml index 501da53e..fe8dd089 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -2,7 +2,7 @@ version: 2 build: os: ubuntu-22.04 tools: - python: "3.7" + python: "3.8" sphinx: configuration: docs/conf.py python: diff --git a/README.md b/README.md index 18806561..87eaf420 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ `pip install fgpyo` -**Requires python 3.7+** +**Requires python 3.8+** See documentation on [fgpyo.readthedocs.org][rtd-link]. @@ -48,7 +48,7 @@ See documentation on [fgpyo.readthedocs.org][rtd-link]. A simple way to create an environment with the desired version of python and poetry is to use [conda][conda-link]. E.g.: ```bash -conda create -n fgpyo -c conda-forge "python>=3.6" poetry +conda create -n fgpyo -c conda-forge "python>=3.8" poetry conda activate fgpyo poetry install ``` diff --git a/fgpyo/util/inspect.py b/fgpyo/util/inspect.py index b35f307e..700d8bb2 100644 --- a/fgpyo/util/inspect.py +++ b/fgpyo/util/inspect.py @@ -1,28 +1,25 @@ +import functools import sys +import typing +from enum import Enum +from functools import partial +from pathlib import PurePath from typing import Any +from typing import Callable from typing import Dict from typing import Iterable from typing import List +from typing import Literal +from typing import Optional from typing import Tuple from typing import Type from typing import Union -if sys.version_info >= (3, 8): - from typing import Literal -else: - from typing_extensions import Literal if sys.version_info >= (3, 12): from typing import TypeAlias else: from typing_extensions import TypeAlias -import functools -from enum import Enum -from functools import partial -from pathlib import PurePath -from typing import Callable -from typing import Optional - import attr import fgpyo.util.types as types @@ -112,8 +109,8 @@ def get_parser() -> partial: raise ValueError("Unable to parse set (try typing.Set[type])") elif type_ == dict: raise ValueError("Unable to parse dict (try typing.Mapping[type])") - elif types.get_origin_type(type_) == list: - subtypes = types.get_arg_types(type_) + elif typing.get_origin(type_) == list: + subtypes = typing.get_args(type_) assert ( len(subtypes) == 1 @@ -133,8 +130,8 @@ def get_parser() -> partial: ] ) ) - elif types.get_origin_type(type_) == set: - subtypes = types.get_arg_types(type_) + elif typing.get_origin(type_) == set: + subtypes = typing.get_args(type_) assert ( len(subtypes) == 1 ), "Sets are allowed only one subtype per PEP specification!" @@ -153,14 +150,14 @@ def get_parser() -> partial: ] ) ) - elif types.get_origin_type(type_) == tuple: + elif typing.get_origin(type_) == tuple: subtype_parsers = [ _get_parser( cls, subtype, parsers, ) - for subtype in types.get_arg_types(type_) + for subtype in typing.get_args(type_) ] def tuple_parse(tuple_string: str) -> Tuple[Any, ...]: @@ -183,8 +180,8 @@ def tuple_parse(tuple_string: str) -> Tuple[Any, ...]: return functools.partial(tuple_parse) - elif types.get_origin_type(type_) == dict: - subtypes = types.get_arg_types(type_) + elif typing.get_origin(type_) == dict: + subtypes = typing.get_args(type_) assert ( len(subtypes) == 2 ), "Dict object must have exactly 2 subtypes per PEP specification!" @@ -231,15 +228,15 @@ def dict_parse(dict_string: str) -> Dict[Any, Any]: return functools.partial(type_) elif type_ == NoneType: return functools.partial(types.none_parser) - elif types.get_origin_type(type_) is Union: + elif typing.get_origin(type_) is Union: return types.make_union_parser( union=type_, - parsers=[_get_parser(cls, arg, parsers) for arg in types.get_arg_types(type_)], + parsers=[_get_parser(cls, arg, parsers) for arg in typing.get_args(type_)], ) - elif types.get_origin_type(type_) is Literal: # Py>=3.7. + elif typing.get_origin(type_) is Literal: return types.make_literal_parser( type_, - [_get_parser(cls, type(arg), parsers) for arg in types.get_arg_types(type_)], + [_get_parser(cls, type(arg), parsers) for arg in typing.get_args(type_)], ) else: raise ParserNotFoundException( @@ -319,8 +316,8 @@ def attr_from( def attribute_is_optional(attribute: attr.Attribute) -> bool: """Returns True if the attribute is optional, False otherwise""" - return types.get_origin_type(attribute.type) is Union and isinstance( - None, types.get_arg_types(attribute.type) + return typing.get_origin(attribute.type) is Union and isinstance( + None, typing.get_args(attribute.type) ) diff --git a/fgpyo/util/logging.py b/fgpyo/util/logging.py index 97174fe6..5c1210b5 100644 --- a/fgpyo/util/logging.py +++ b/fgpyo/util/logging.py @@ -34,19 +34,13 @@ """ import logging -import sys - -if sys.version_info >= (3, 8): - from typing import Literal -else: - from typing_extensions import Literal - import socket from contextlib import AbstractContextManager from logging import Logger from threading import RLock from typing import Any from typing import Callable +from typing import Literal from typing import Optional from typing import Union diff --git a/fgpyo/util/tests/test_types.py b/fgpyo/util/tests/test_types.py new file mode 100644 index 00000000..c49d2d78 --- /dev/null +++ b/fgpyo/util/tests/test_types.py @@ -0,0 +1,11 @@ +from typing import Iterable +from typing import List +from typing import Sequence + +from fgpyo.util import types + + +def test_is_listlike() -> None: + assert types.is_list_like(List[str]) + assert types.is_list_like(Iterable[str]) + assert types.is_list_like(Sequence[str]) diff --git a/fgpyo/util/types.py b/fgpyo/util/types.py index 822c4dc2..e378574d 100644 --- a/fgpyo/util/types.py +++ b/fgpyo/util/types.py @@ -1,50 +1,15 @@ import collections import inspect -import sys import typing from enum import Enum from functools import partial from typing import Callable from typing import Iterable +from typing import Literal from typing import Type from typing import TypeVar from typing import Union -# `get_origin_type` is a method that gets the outer type (ex list in a List[str]) -# `get_arg_types` is a method that gets the inner type (ex str in a List[str]) -if sys.version_info >= (3, 8): - from typing import Literal - - get_origin_type = typing.get_origin - get_arg_types = typing.get_args -else: - import typing_inspect - from typing_extensions import Literal - - def get_origin_type(tp: Type) -> Type: - """Returns the outer type of a Typing object (ex list in a List[T])""" - - if type(tp) is type(Literal): # Py<=3.6. - return Literal - origin = typing_inspect.get_origin(tp) - return { - typing.List: list, - typing.Iterable: collections.abc.Iterable, - typing.Sequence: collections.abc.Sequence, - typing.Tuple: tuple, - typing.Set: set, - typing.Mapping: dict, - typing.Dict: dict, - }.get(origin, origin) - - def get_arg_types(tp: Type) -> Type: - """Gets the inner types of a Typing object (ex T in a List[T])""" - - if type(tp) is type(Literal): # Py<=3.6. - return tp.__values__ - return typing_inspect.get_args(tp, evaluate=True) # evaluate=True default on Py>=3.7. - - UnionType = TypeVar("UnionType", bound="Union") EnumType = TypeVar("EnumType", bound="Enum") # conceptually bound to "Literal" but that's not valid in the spec @@ -109,7 +74,7 @@ def is_constructible_from_str(type_: type) -> bool: def _is_optional(type_: type) -> bool: """Returns true if type_ is optional""" - return get_origin_type(type_) is Union and type(None) in get_arg_types(type_) + return typing.get_origin(type_) is Union and type(None) in typing.get_args(type_) def _make_union_parser_worker( @@ -150,7 +115,7 @@ def _make_literal_parser_worker( """Worker function behind literal parsing. Iterates through possible literals and returns the value produced by the first literal that matches expectation. Otherwise raises an error if none work""" - for arg, p in zip(get_arg_types(literal), parsers): + for arg, p in zip(typing.get_args(literal), parsers): try: if p(value) == arg: return arg @@ -158,7 +123,7 @@ def _make_literal_parser_worker( pass raise InspectException( "invalid choice: {!r} (choose from {})".format( - value, ", ".join(map(repr, map(str, get_arg_types(literal)))) + value, ", ".join(map(repr, map(str, typing.get_args(literal)))) ) ) @@ -174,7 +139,7 @@ def make_literal_parser( def is_list_like(type_: type) -> bool: """Returns true if the value is a list or list like object""" - return get_origin_type(type_) in [list, collections.abc.Iterable, collections.abc.Sequence] + return typing.get_origin(type_) in [list, collections.abc.Iterable, collections.abc.Sequence] def none_parser(value: str) -> Literal[None]: diff --git a/poetry.lock b/poetry.lock index b4360a92..58cb3b4c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "alabaster" @@ -115,7 +115,6 @@ files = [ [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} [[package]] name = "colorama" @@ -227,7 +226,6 @@ files = [ ] [package.dependencies] -importlib-metadata = {version = ">=1.1.0,<4.3", markers = "python_version < \"3.8\""} mccabe = ">=0.7.0,<0.8.0" pycodestyle = ">=2.9.0,<2.10.0" pyflakes = ">=2.5.0,<2.6.0" @@ -270,25 +268,6 @@ files = [ {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, ] -[[package]] -name = "importlib-metadata" -version = "4.2.0" -description = "Read metadata from Python packages" -optional = false -python-versions = ">=3.6" -files = [ - {file = "importlib_metadata-4.2.0-py3-none-any.whl", hash = "sha256:057e92c15bc8d9e8109738a48db0ccb31b4d9d5cfbee5a8670879a30be66304b"}, - {file = "importlib_metadata-4.2.0.tar.gz", hash = "sha256:b7e52a1f8dec14a75ea73e0891f3060099ca1d8e6a462a4dff11c3e119ea1b31"}, -] - -[package.dependencies] -typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} -zipp = ">=0.5" - -[package.extras] -docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pep517", "pyfakefs", "pytest (>=4.6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy"] - [[package]] name = "iniconfig" version = "1.1.1" @@ -423,49 +402,6 @@ files = [ {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, ] -[[package]] -name = "mypy" -version = "0.971" -description = "Optional static typing for Python" -optional = false -python-versions = ">=3.6" -files = [ - {file = "mypy-0.971-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f2899a3cbd394da157194f913a931edfd4be5f274a88041c9dc2d9cdcb1c315c"}, - {file = "mypy-0.971-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:98e02d56ebe93981c41211c05adb630d1d26c14195d04d95e49cd97dbc046dc5"}, - {file = "mypy-0.971-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:19830b7dba7d5356d3e26e2427a2ec91c994cd92d983142cbd025ebe81d69cf3"}, - {file = "mypy-0.971-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:02ef476f6dcb86e6f502ae39a16b93285fef97e7f1ff22932b657d1ef1f28655"}, - {file = "mypy-0.971-cp310-cp310-win_amd64.whl", hash = "sha256:25c5750ba5609a0c7550b73a33deb314ecfb559c350bb050b655505e8aed4103"}, - {file = "mypy-0.971-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d3348e7eb2eea2472db611486846742d5d52d1290576de99d59edeb7cd4a42ca"}, - {file = "mypy-0.971-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3fa7a477b9900be9b7dd4bab30a12759e5abe9586574ceb944bc29cddf8f0417"}, - {file = "mypy-0.971-cp36-cp36m-win_amd64.whl", hash = "sha256:2ad53cf9c3adc43cf3bea0a7d01a2f2e86db9fe7596dfecb4496a5dda63cbb09"}, - {file = "mypy-0.971-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:855048b6feb6dfe09d3353466004490b1872887150c5bb5caad7838b57328cc8"}, - {file = "mypy-0.971-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:23488a14a83bca6e54402c2e6435467a4138785df93ec85aeff64c6170077fb0"}, - {file = "mypy-0.971-cp37-cp37m-win_amd64.whl", hash = "sha256:4b21e5b1a70dfb972490035128f305c39bc4bc253f34e96a4adf9127cf943eb2"}, - {file = "mypy-0.971-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9796a2ba7b4b538649caa5cecd398d873f4022ed2333ffde58eaf604c4d2cb27"}, - {file = "mypy-0.971-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5a361d92635ad4ada1b1b2d3630fc2f53f2127d51cf2def9db83cba32e47c856"}, - {file = "mypy-0.971-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b793b899f7cf563b1e7044a5c97361196b938e92f0a4343a5d27966a53d2ec71"}, - {file = "mypy-0.971-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d1ea5d12c8e2d266b5fb8c7a5d2e9c0219fedfeb493b7ed60cd350322384ac27"}, - {file = "mypy-0.971-cp38-cp38-win_amd64.whl", hash = "sha256:23c7ff43fff4b0df93a186581885c8512bc50fc4d4910e0f838e35d6bb6b5e58"}, - {file = "mypy-0.971-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1f7656b69974a6933e987ee8ffb951d836272d6c0f81d727f1d0e2696074d9e6"}, - {file = "mypy-0.971-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d2022bfadb7a5c2ef410d6a7c9763188afdb7f3533f22a0a32be10d571ee4bbe"}, - {file = "mypy-0.971-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef943c72a786b0f8d90fd76e9b39ce81fb7171172daf84bf43eaf937e9f220a9"}, - {file = "mypy-0.971-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d744f72eb39f69312bc6c2abf8ff6656973120e2eb3f3ec4f758ed47e414a4bf"}, - {file = "mypy-0.971-cp39-cp39-win_amd64.whl", hash = "sha256:77a514ea15d3007d33a9e2157b0ba9c267496acf12a7f2b9b9f8446337aac5b0"}, - {file = "mypy-0.971-py3-none-any.whl", hash = "sha256:0d054ef16b071149917085f51f89555a576e2618d5d9dd70bd6eea6410af3ac9"}, - {file = "mypy-0.971.tar.gz", hash = "sha256:40b0f21484238269ae6a57200c807d80debc6459d444c0489a102d7c6a75fa56"}, -] - -[package.dependencies] -mypy-extensions = ">=0.4.3" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""} -typing-extensions = ">=3.10" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -python2 = ["typed-ast (>=1.4.0,<2)"] -reports = ["lxml"] - [[package]] name = "mypy" version = "1.7.0" @@ -560,9 +496,6 @@ files = [ {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] -[package.dependencies] -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} - [package.extras] dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] @@ -689,7 +622,6 @@ files = [ [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} iniconfig = "*" packaging = "*" pluggy = ">=0.12,<2.0" @@ -1095,21 +1027,6 @@ files = [ {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"}, ] -[[package]] -name = "typing-inspect" -version = "0.8.0" -description = "Runtime inspection utilities for typing module." -optional = false -python-versions = "*" -files = [ - {file = "typing_inspect-0.8.0-py3-none-any.whl", hash = "sha256:5fbf9c1e65d4fa01e701fe12a5bca6c6e08a4ffd5bc60bfac028253a447c5188"}, - {file = "typing_inspect-0.8.0.tar.gz", hash = "sha256:8b1ff0c400943b6145df8119c41c244ca8207f1f10c9c057aeed1560e4806e3d"}, -] - -[package.dependencies] -mypy-extensions = ">=0.3.0" -typing-extensions = ">=3.7.4" - [[package]] name = "urllib3" version = "1.26.13" @@ -1126,25 +1043,10 @@ brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] -[[package]] -name = "zipp" -version = "3.6.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -optional = false -python-versions = ">=3.6" -files = [ - {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"}, - {file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"}, -] - -[package.extras] -docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"] -testing = ["func-timeout", "jaraco.itertools", "pytest (>=4.6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy"] - [extras] docs = ["sphinx", "sphinx_rtd_theme"] [metadata] lock-version = "2.0" -python-versions = ">=3.7.0,<4.0" -content-hash = "51b3d80eacacfbee5a830d83dc79fd63c82fe66f411d456ad79983babd93fe7d" +python-versions = ">=3.8.0,<4.0" +content-hash = "fff1e475a8e2befd38f19f455b8888221109ca56a49d3ef0eab1478533080371" diff --git a/pyproject.toml b/pyproject.toml index f7398ca8..50456cee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "fgpyo" -version = "0.1.4-dev" +version = "0.2.0-dev" description = "Python bioinformatics and genomics library" authors = ["Nils Homer", "Tim Fennell", "Nathan Roach"] license = "MIT" @@ -10,48 +10,42 @@ repository = "https://github.com/fulcrumgenomics/fgpyo" keywords = ["bioinformatics"] classifiers = [ "Development Status :: 3 - Alpha", - "Environment :: Console", + "Environment :: Console", "Intended Audience :: Developers", "Intended Audience :: Science/Research", "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", + "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Topic :: Scientific/Engineering :: Bio-Informatics", - "Topic :: Software Development :: Documentation", - "Topic :: Software Development :: Libraries :: Python Modules", -] -include = [ - "LICENSE", + "Topic :: Software Development :: Documentation", + "Topic :: Software Development :: Libraries :: Python Modules", ] +include = ["LICENSE"] [tool.poetry.dependencies] -python = ">=3.7.0,<4.0" +python = ">=3.8.0,<4.0" attrs = ">=19.3.0" -typing_extensions = { version = ">=3.7.4", python = "<3.8" } # Literal support -typing_inspect = { version = ">=0.3.1", python = "<3.8" } # inspecting types -sphinx = {version = "4.3.1", optional = true} -sphinx_rtd_theme = {version = "^1.3.0", optional = true} pysam = ">=0.22.0" pytest = ">=7.4.0" +sphinx = { version = "4.3.1", optional = true } +sphinx_rtd_theme = { version = "^1.3.0", optional = true } +typing_extensions = { version = ">=3.7.4", python = "<3.12" } [tool.poetry.extras] docs = ["sphinx", "sphinx_rtd_theme"] [tool.poetry.dev-dependencies] -setuptools = ">=68.0.0" -pytest = ">=5.4.2" -mypy = [ - { version = ">=0.770", python = "<3.8" }, - { version = ">=1.7.0", python = ">=3.8"} -] +black = ">=19.10b0" flake8 = [ { version = ">=3.8.1", python = "<3.12.0" }, { version = ">=6.1.0", python = ">=3.12.0" }, ] -black = ">=19.10b0" -pytest-cov = ">=2.8.1" isort = ">=5.10.1" +mypy = ">=1.7.0" +pytest = ">=5.4.2" +pytest-cov = ">=2.8.1" +setuptools = ">=68.0.0" [build-system] -requires = ["poetry>=1.5"] -build-backend = "poetry.masonry.api" +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api"