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 Python 3.9 support #6

Merged
merged 5 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
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 = "egolge@coqui.ai"}
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
Loading