Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drop support for python 3.7 #98

Merged
merged 18 commits into from
Feb 23, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/pythonpackage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion .readthedocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ version: 2
build:
os: ubuntu-22.04
tools:
python: "3.7"
python: "3.8"
sphinx:
configuration: docs/conf.py
python:
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@

`pip install fgpyo`

**Requires python 3.7+**
**Requires python 3.8+**

See documentation on [fgpyo.readthedocs.org][rtd-link].

Expand All @@ -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
```
Expand Down
47 changes: 22 additions & 25 deletions fgpyo/util/inspect.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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!"
Expand All @@ -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, ...]:
Expand All @@ -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!"
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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)
)


Expand Down
8 changes: 1 addition & 7 deletions fgpyo/util/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
11 changes: 11 additions & 0 deletions fgpyo/util/tests/test_types.py
Original file line number Diff line number Diff line change
@@ -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])
45 changes: 5 additions & 40 deletions fgpyo/util/types.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -150,15 +115,15 @@ 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
except ValueError:
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))))
)
)

Expand All @@ -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]:
Expand Down
8 changes: 5 additions & 3 deletions fgpyo/vcf/tests/test_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,11 @@ def _get_random_variant_inputs(
key: (
random_generator.randint(0, 100)
if value_type == VcfFieldType.INTEGER
else random_generator.uniform(0, 1)
if value_type == VcfFieldType.FLOAT
else random_generator.choice(["Up", "Down"])
else (
random_generator.uniform(0, 1)
if value_type == VcfFieldType.FLOAT
else random_generator.choice(["Up", "Down"])
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand the need for this change. The original seems more pythonic to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change was made by black after updating to a newer version.

FWIW I find the original more difficult to parse. I'm a fan of using parens to group statements (even when not strictly necessary) to improve readability.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can try to revert the change and see if black complains.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like a series of v1 if c1 else v2 if c2 else c3 is pretty typical for python. But the version black made isn't some unreadable abomination. If you don't care either way I'd say try to revert it. If black doesn't let you, that's that. I approved.

Copy link
Contributor Author

@jdidion jdidion Feb 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the CI checks pass after reverting.

)
for key, value_type in _INFO_FIELD_TYPES.items()
}
Expand Down
Loading
Loading