Skip to content

Commit

Permalink
Merge pull request #6 from idiap/drop-py39
Browse files Browse the repository at this point in the history
Drop Python 3.9 support
  • Loading branch information
eginhard authored Dec 18, 2024
2 parents 65a58f7 + 064c8e1 commit b41b0e1
Show file tree
Hide file tree
Showing 14 changed files with 77 additions and 102 deletions.
5 changes: 3 additions & 2 deletions .github/actions/setup-uv/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ runs:
using: 'composite'
steps:
- name: Install uv
uses: astral-sh/setup-uv@v3
uses: astral-sh/setup-uv@v4
with:
version: "0.5.1"
version: "0.5.10"
enable-cache: true
cache-dependency-glob: "**/pyproject.toml"
python-version: ${{ matrix.python-version }}
5 changes: 2 additions & 3 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,13 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: [3.9, "3.10", "3.11", "3.12", "3.13"]
python-version: ["3.10", "3.11", "3.12", "3.13"]

steps:
- uses: actions/checkout@v4
- name: Setup uv
# Installs Python based on ${{ matrix.python-version }}
uses: ./.github/actions/setup-uv
- name: Set up Python ${{ matrix.python-version }}
run: uv python install ${{ matrix.python-version }}
- name: Lint check
run: make lint
- name: Unit tests
Expand Down
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.9
rev: v0.8.3
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
Expand All @@ -13,7 +13,7 @@ repos:
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.12.0
rev: v1.13.0
hooks:
- id: mypy
args: [--strict]
Expand Down
30 changes: 10 additions & 20 deletions coqpit/coqpit.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,16 @@
import contextlib
import json
import operator
import sys
import typing
from collections.abc import ItemsView, Iterable, Iterator, MutableMapping
from collections.abc import Callable, ItemsView, Iterable, Iterator, MutableMapping
from dataclasses import MISSING as _MISSING
from dataclasses import Field, asdict, dataclass, fields, is_dataclass, replace
from pathlib import Path
from pprint import pprint
from types import GenericAlias
from typing import TYPE_CHECKING, Any, Callable, Generic, Literal, TypeVar, Union, overload
from types import GenericAlias, UnionType
from typing import TYPE_CHECKING, Any, Generic, Literal, TypeAlias, TypeGuard, TypeVar, Union, overload

from typing_extensions import Self, TypeAlias, TypeGuard, TypeIs

# TODO: Available from Python 3.10
if sys.version_info >= (3, 10):
from types import UnionType
else:
UnionType: TypeAlias = Union
from typing_extensions import Self, TypeIs

if TYPE_CHECKING: # pragma: no cover
import os
Expand All @@ -38,7 +31,7 @@ class _NoDefault(Generic[_T]):
pass


NoDefaultVar: TypeAlias = Union[_NoDefault[_T], _T]
NoDefaultVar: TypeAlias = _NoDefault[_T] | _T
no_default: NoDefaultVar[Any] = _NoDefault()

FieldType: TypeAlias = Union[str, type, "UnionType"]
Expand Down Expand Up @@ -90,10 +83,7 @@ def _is_union(field_type: FieldType) -> TypeIs[UnionType]:
bool: True if input type is `Union`
"""
origin = typing.get_origin(field_type)
is_union = origin is Union
if sys.version_info >= (3, 10):
is_union = is_union or origin is UnionType
return is_union
return origin is Union or origin is UnionType


def _is_union_and_not_simple_optional(field_type: FieldType) -> TypeGuard[UnionType]:
Expand Down Expand Up @@ -262,13 +252,13 @@ def _deserialize_primitive_types(
Returns:
Union[int, float, str, bool]: deserialized value.
"""
if isinstance(x, (str, bool)):
if isinstance(x, str | bool):
return x
if isinstance(x, (int, float)):
if isinstance(x, int | float):
base_type = _drop_none_type(field_type)
if base_type is not float and base_type is not int and base_type is not str and base_type is not bool:
raise TypeError
base_type = typing.cast(type[Union[int, float, str, bool]], base_type)
base_type = typing.cast(type[int | float | str | bool], base_type)
if x == float("inf") or x == float("-inf"):
# if value type is inf return regardless.
return x
Expand Down Expand Up @@ -315,7 +305,7 @@ def _deserialize(x: Any, field_type: FieldType) -> Any:

CoqpitType: TypeAlias = MutableMapping[str, "CoqpitNestedValue"]
CoqpitNestedValue: TypeAlias = Union["CoqpitValue", CoqpitType]
CoqpitValue: TypeAlias = Union[str, int, float, bool, None]
CoqpitValue: TypeAlias = str | int | float | bool | None


# TODO: It should be possible to get rid of the next 3 `type: ignore`. At
Expand Down
12 changes: 5 additions & 7 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ build-backend = "hatchling.build"

[project]
name = "coqpit-config"
version = "0.1.1"
version = "0.1.2"
description = "Simple (maybe too simple), light-weight config management through python data-classes."
readme = "README.md"
requires-python = ">=3.9"
requires-python = ">=3.10"
license = {text = "MIT"}
authors = [
{name = "Eren Gölge", email = "[email protected]"}
Expand All @@ -18,7 +18,6 @@ maintainers = [
classifiers = [
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
Expand All @@ -36,10 +35,10 @@ dependencies = [
[dependency-groups]
dev = [
"coverage>=7",
"mypy>=1.12.0",
"pre-commit>=3",
"mypy>=1.13.0",
"pre-commit>=4",
"pytest>=8",
"ruff==0.6.9",
"ruff==0.8.3",
]

[project.urls]
Expand All @@ -59,7 +58,6 @@ exclude = [
packages = ["coqpit"]

[tool.ruff]
target-version = "py39"
line-length = 120
lint.select = ["ALL"]
lint.ignore = [
Expand Down
5 changes: 2 additions & 3 deletions tests/test_init_from_dict.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from dataclasses import dataclass, field
from typing import Optional

import pytest

Expand All @@ -8,8 +7,8 @@

@dataclass
class Person(Coqpit):
name: Optional[str] = None
age: Optional[int] = None
name: str | None = None
age: int | None = None


@dataclass
Expand Down
5 changes: 2 additions & 3 deletions tests/test_merge_configs.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
from dataclasses import dataclass
from typing import Optional

from coqpit.coqpit import Coqpit


@dataclass
class CoqpitA(Coqpit):
val_a: int = 10
val_b: Optional[int] = None
val_b: int | None = None
val_c: str = "Coqpit is great!"
val_same: float = 10.21

Expand All @@ -23,7 +22,7 @@ class CoqpitB(Coqpit):
@dataclass
class Reference(Coqpit):
val_a: int = 10
val_b: Optional[int] = None
val_b: int | None = None
val_c: str = "Coqpit is great!"
val_e: int = 257
val_f: float = -10.21
Expand Down
9 changes: 4 additions & 5 deletions tests/test_nested_configs.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
from dataclasses import asdict, dataclass, field
from pathlib import Path
from typing import Optional, Union

from coqpit import Coqpit, check_argument


@dataclass
class SimpleConfig(Coqpit):
val_a: int = 10
val_b: Optional[int] = None
val_b: int | None = None
val_c: str = "Coqpit is great!"

def check_values(self) -> None:
Expand All @@ -22,11 +21,11 @@ def check_values(self) -> None:
@dataclass
class NestedConfig(Coqpit):
val_d: int = 10
val_e: Optional[int] = None
val_e: int | None = None
val_f: str = "Coqpit is great!"
sc_list: Optional[list[SimpleConfig]] = None
sc_list: list[SimpleConfig] | None = None
sc: SimpleConfig = field(default_factory=lambda: SimpleConfig())
union_var: Union[list[SimpleConfig], SimpleConfig] = field(default_factory=lambda: [SimpleConfig(), SimpleConfig()])
union_var: list[SimpleConfig] | SimpleConfig = field(default_factory=lambda: [SimpleConfig(), SimpleConfig()])

def check_values(self) -> None:
"""Check config fields"""
Expand Down
13 changes: 6 additions & 7 deletions tests/test_parse_argparse.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
from dataclasses import asdict, dataclass, field
from typing import Optional

from coqpit.coqpit import Coqpit, check_argument


@dataclass
class SimplerConfig(Coqpit):
val_a: Optional[int] = field(default=None, metadata={"help": "this is val_a"})
val_a: int | None = field(default=None, metadata={"help": "this is val_a"})


@dataclass
class SimpleConfig(Coqpit):
val_a: int = field(default=10, metadata={"help": "this is val_a of SimpleConfig"})
val_b: Optional[int] = field(default=None, metadata={"help": "this is val_b"})
val_b: int | None = field(default=None, metadata={"help": "this is val_b"})
val_c: str = "Coqpit is great!"
val_dict: dict[str, int] = field(default_factory=lambda: {"val_a": 100, "val_b": 200, "val_c": 300})
mylist_with_default: list[SimplerConfig] = field(
Expand All @@ -21,8 +20,8 @@ class SimpleConfig(Coqpit):
)
int_list: list[int] = field(default_factory=lambda: [1, 2, 3], metadata={"help": "int"})
str_list: list[str] = field(default_factory=lambda: ["veni", "vidi", "vici"], metadata={"help": "str"})
empty_int_list: Optional[list[int]] = field(default=None, metadata={"help": "int list without default value"})
empty_str_list: Optional[list[str]] = field(default=None, metadata={"help": "str list without default value"})
empty_int_list: list[int] | None = field(default=None, metadata={"help": "int list without default value"})
empty_str_list: list[str] | None = field(default=None, metadata={"help": "str list without default value"})
list_with_default_factory: list[str] = field(
default_factory=list,
metadata={"help": "str list with default factory"},
Expand Down Expand Up @@ -140,13 +139,13 @@ def test_argparse_with_required_field() -> None:
def test_init_argparse_list_and_nested() -> None:
@dataclass
class SimplerConfig2(Coqpit):
val_a: Optional[int] = field(default=None, metadata={"help": "this is val_a"})
val_a: int | None = field(default=None, metadata={"help": "this is val_a"})

@dataclass
class SimpleConfig2(Coqpit):
val_req: str # required field
val_a: int = field(default=10, metadata={"help": "this is val_a of SimpleConfig2"})
val_b: Optional[int] = field(default=None, metadata={"help": "this is val_b"})
val_b: int | None = field(default=None, metadata={"help": "this is val_b"})
nested_config: SimplerConfig2 = field(default_factory=lambda: SimplerConfig2())
mylist_with_default: list[SimplerConfig2] = field(
default_factory=lambda: [SimplerConfig2(val_a=100), SimplerConfig2(val_a=999)],
Expand Down
5 changes: 2 additions & 3 deletions tests/test_parse_known_argparse.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
from dataclasses import asdict, dataclass, field
from typing import Optional

from coqpit.coqpit import Coqpit, check_argument


@dataclass
class SimplerConfig(Coqpit):
val_a: Optional[int] = field(default=None, metadata={"help": "this is val_a"})
val_a: int | None = field(default=None, metadata={"help": "this is val_a"})


@dataclass
class SimpleConfig(Coqpit):
val_a: int = field(default=10, metadata={"help": "this is val_a of SimpleConfig"})
val_b: Optional[int] = field(default=None, metadata={"help": "this is val_b"})
val_b: int | None = field(default=None, metadata={"help": "this is val_b"})
val_c: str = "Coqpit is great!"
mylist_with_default: list[SimplerConfig] = field(
default_factory=lambda: [SimplerConfig(val_a=100), SimplerConfig(val_a=999)],
Expand Down
8 changes: 4 additions & 4 deletions tests/test_relaxed_parse_known_argparse.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
from dataclasses import asdict, dataclass, field
from typing import Any, Optional, Union
from typing import Any

from coqpit.coqpit import Coqpit, check_argument


@dataclass
class SimpleConfig(Coqpit):
val_a: int = field(default=10, metadata={"help": "this is val_a of SimpleConfig"})
val_b: Optional[int] = field(default=None, metadata={"help": "this is val_b"})
val_c: Optional[Union[int, str]] = None
val_d: Optional[list[list[Any]]] = None
val_b: int | None = field(default=None, metadata={"help": "this is val_b"})
val_c: int | str | None = None
val_d: list[list[Any]] | None = None

def check_values(self) -> None:
"""Check config fields"""
Expand Down
Loading

0 comments on commit b41b0e1

Please sign in to comment.