Skip to content

Commit c2d8d26

Browse files
authored
Use obj.model_dump instead of obj.dict for Pydantic V2 (#14)
* Use `model_dump` instead of `dict` for Pydantic V2 * Improve type hints * Fully type hints
1 parent 70605db commit c2d8d26

File tree

18 files changed

+140
-75
lines changed

18 files changed

+140
-75
lines changed

.github/workflows/build.yml

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,20 @@ jobs:
1818
runs-on: ubuntu-latest
1919
strategy:
2020
matrix:
21-
python-version: [3.8, 3.9, "3.10", "3.11"]
21+
python-version: [3.8, 3.9, "3.10", 3.11, 3.12]
2222

2323
steps:
24-
- uses: actions/checkout@v1
24+
- uses: actions/checkout@v4
2525
with:
2626
fetch-depth: 9
2727
submodules: false
2828

2929
- name: Use Python ${{ matrix.python-version }}
30-
uses: actions/setup-python@v4
30+
uses: actions/setup-python@v5
3131
with:
3232
python-version: ${{ matrix.python-version }}
3333

34-
- uses: actions/cache@v1
34+
- uses: actions/cache@v4
3535
id: depcache
3636
with:
3737
path: deps
@@ -58,6 +58,10 @@ jobs:
5858
isort --check-only . 2>&1
5959
black --check . 2>&1
6060
61+
- name: Check type hints
62+
run: mypy .
63+
if: matrix.python-version == 3.12
64+
6165
- name: Upload pytest test results
6266
uses: actions/upload-artifact@master
6367
with:
@@ -90,7 +94,7 @@ jobs:
9094
if: github.event_name == 'release'
9195
steps:
9296
- name: Download a distribution artifact
93-
uses: actions/download-artifact@v2
97+
uses: actions/download-artifact@v4
9498
with:
9599
name: dist-package-3.10
96100
path: dist

CHANGELOG.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## [1.1.6] - 2022-11-05 :snake:
8+
## [1.1.6] - 2024-11-07 :snake:
9+
- Drop python3.6 and python3.7 support
10+
- Use `obj.model_dump` instead of `obj.dict` for Pydantic V2
911
- Workflow maintenance
10-
- Applies `black`, `flake8`, `isort`
12+
- Applies `black`, `flake8`, `isort`, `mypy`
1113

1214
## [1.1.5] - 2022-03-14 :tulip:
1315
- Adds `py.typed` file

Makefile

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,11 @@ testcov:
2222

2323

2424
test:
25-
pytest -v -W ignore
25+
pytest -v -W ignore
26+
27+
28+
check:
29+
flake8 .
30+
isort --check-only .
31+
black --check .
32+
mypy .

essentials/caching.py

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
11
import functools
22
import time
33
from collections import OrderedDict
4-
from typing import Any, Callable, Generic, Iterable, Iterator, Tuple, TypeVar
4+
from typing import TYPE_CHECKING, Any, Generic, Iterable, Iterator, Tuple, TypeVar
5+
6+
if TYPE_CHECKING:
7+
from typing import Callable, TypeVarTuple, Unpack
8+
9+
PosArgsT = TypeVarTuple("PosArgsT")
10+
T_Retval = TypeVar("T_Retval")
11+
FuncType = Callable[[Unpack[PosArgsT]], T_Retval]
12+
FuncDecoType = Callable[[FuncType], FuncType]
513

614
T = TypeVar("T")
715

816

917
class Cache(Generic[T]):
1018
"""In-memory LRU cache implementation."""
1119

12-
def __init__(self, max_size: int = 500):
20+
def __init__(self, max_size: int = 500) -> None:
1321
self._bag: OrderedDict[Any, Any] = OrderedDict()
1422
self._max_size = -1
1523
self.max_size = max_size
@@ -50,7 +58,7 @@ def get(self, key, default=None) -> T:
5058
def set(self, key, value) -> None:
5159
self[key] = value
5260

53-
def _check_size(self):
61+
def _check_size(self) -> None:
5462
while len(self._bag) > self.max_size:
5563
self._bag.popitem(last=False)
5664

@@ -85,7 +93,7 @@ class CachedItem(Generic[T]):
8593

8694
__slots__ = ("_value", "_time")
8795

88-
def __init__(self, value: T):
96+
def __init__(self, value: T) -> None:
8997
self._value = value
9098
self._time = time.time()
9199

@@ -94,7 +102,7 @@ def value(self) -> T:
94102
return self._value
95103

96104
@value.setter
97-
def value(self, value: T):
105+
def value(self, value: T) -> None:
98106
self._value = value
99107
self._time = time.time()
100108

@@ -107,8 +115,8 @@ class ExpiringCache(Cache[T]):
107115
"""A cache whose items can expire by a given function."""
108116

109117
def __init__(
110-
self, expiration_policy: Callable[[CachedItem[T]], bool], max_size: int = 500
111-
):
118+
self, expiration_policy: "Callable[[CachedItem[T]], bool]", max_size: int = 500
119+
) -> None:
112120
super().__init__(max_size)
113121
assert expiration_policy is not None
114122
self.expiration_policy = expiration_policy
@@ -120,12 +128,12 @@ def full(self) -> bool:
120128
def expired(self, item: CachedItem) -> bool:
121129
return self.expiration_policy(item)
122130

123-
def _remove_expired_items(self):
131+
def _remove_expired_items(self) -> None:
124132
for key, item in list(self._bag.items()):
125133
if self.expired(item):
126134
del self[key]
127135

128-
def _check_size(self):
136+
def _check_size(self) -> None:
129137
if self.full:
130138
self._remove_expired_items()
131139
super()._check_size()
@@ -148,7 +156,7 @@ def __setitem__(self, key, value: T) -> None:
148156
self._check_size()
149157

150158
@classmethod
151-
def with_max_age(cls, max_age: float, max_size: int = 500):
159+
def with_max_age(cls, max_age: float, max_size: int = 500) -> "ExpiringCache":
152160
"""
153161
Returns an instance of ExpiringCache whose items are invalidated
154162
when they were set more than a given number of seconds ago.
@@ -174,7 +182,7 @@ def __iter__(self) -> Iterator[Tuple[Any, T]]:
174182
yield (key, item.value)
175183

176184

177-
def lazy(max_seconds: int = 1, cache=None):
185+
def lazy(max_seconds: int = 1, cache=None) -> "FuncDecoType":
178186
"""
179187
Wraps a function so that it is called up to once
180188
every max_seconds, by input arguments.

essentials/decorators/logs.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,17 @@
22
from functools import wraps
33
from inspect import iscoroutinefunction
44
from logging import Logger
5-
from typing import Callable, Optional
5+
from typing import Callable, Optional, TypeVar
66
from uuid import uuid4
77

88
from essentials.diagnostics import StopWatch
99

10+
T = TypeVar("T")
1011
IdFactory = Callable[[], str]
12+
FuncType = Callable[..., T]
1113

1214

13-
def _default_id_factory():
15+
def _default_id_factory() -> str:
1416
return str(uuid4())
1517

1618

@@ -25,7 +27,7 @@ def log(
2527
completed_msg="%s; completed; call id: %s; elapsed %s ms",
2628
completed_msg_with_output="%s; completed; call id: %s; elapsed %s ms; output: %s",
2729
exc_message="%s; unhandled exception; call id: %s; elapsed %s ms",
28-
):
30+
) -> Callable[[FuncType], FuncType]:
2931

3032
if not id_factory:
3133
id_factory = _default_id_factory

essentials/decorators/retry.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,22 @@
22
import time
33
from functools import wraps
44
from inspect import iscoroutinefunction
5-
from typing import Callable, Optional, Tuple, Type, Union
5+
from typing import Callable, Optional, Tuple, Type, TypeVar, Union
66

7+
T = TypeVar("T")
8+
FuncType = Callable[..., T]
79
CatchException = Union[Tuple[Type[Exception]], Type[Exception], None]
810
OnException = Optional[Callable[[Type[Exception], int], None]]
911

1012

1113
def _get_retry_async_wrapper(
12-
fn,
14+
fn: FuncType,
1315
times: int,
1416
delay: float,
1517
catch_exceptions_types: CatchException,
1618
on_exception: OnException,
1719
loop,
18-
):
20+
) -> FuncType:
1921
@wraps(fn)
2022
async def async_wrapper(*args, **kwargs):
2123
attempt = 0
@@ -46,7 +48,7 @@ def retry(
4648
catch_exceptions_types: CatchException = None,
4749
on_exception: OnException = None,
4850
loop=None,
49-
):
51+
) -> Callable[[FuncType], FuncType]:
5052
if catch_exceptions_types is None:
5153
catch_exceptions_types = Exception
5254

essentials/diagnostics.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,47 +2,48 @@
22
Timer class, edited from: Python Cookbook,
33
3rd Edition by Brian K. Jones, David Beazley
44
"""
5+
56
import time
67

78

89
class StopWatch:
9-
def __init__(self, func=time.perf_counter):
10+
def __init__(self, func=time.perf_counter) -> None:
1011
self._elapsed_s = 0.0
1112
self._func = func
1213
self._start = None
1314

14-
def __repr__(self):
15+
def __repr__(self) -> str:
1516
return f"<StopWatch elapsed s.: {self.elapsed_s}>"
1617

17-
def start(self):
18+
def start(self) -> None:
1819
if self._start is not None:
1920
raise RuntimeError("StopWatch already running")
2021

2122
self._start = self._func()
2223

23-
def stop(self):
24+
def stop(self) -> None:
2425
if self._start is None:
2526
raise RuntimeError("StopWatch not running")
2627

2728
self._elapsed_s += self._func() - self._start
2829

29-
def reset(self):
30+
def reset(self) -> None:
3031
self._start = None
3132
self._elapsed_s = 0.0
3233

3334
@property
34-
def elapsed_s(self):
35+
def elapsed_s(self) -> float:
3536
return self._elapsed_s
3637

3738
@property
38-
def elapsed_ms(self):
39+
def elapsed_ms(self) -> float:
3940
if self.elapsed_s > 0.0:
4041
return self.elapsed_s * 1000
4142
return 0.0
4243

43-
def __enter__(self):
44+
def __enter__(self) -> "StopWatch":
4445
self.start()
4546
return self
4647

47-
def __exit__(self, *args):
48+
def __exit__(self, *args) -> None:
4849
self.stop()

essentials/folders.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
11
import ntpath
22
import os
3+
from typing import Tuple
34

45

5-
def ensure_folder(path):
6+
def ensure_folder(path) -> None:
67
os.makedirs(path, exist_ok=True)
78

89

9-
def split_path(filepath):
10+
def split_path(filepath) -> Tuple[str, str, str]:
1011
"""Splits a file path into folder path, file name and extension"""
1112
head, tail = ntpath.split(filepath)
1213
filename, extension = os.path.splitext(tail)
1314
return head, filename, extension
1415

1516

16-
def get_file_extension(filepath):
17+
def get_file_extension(filepath) -> str:
1718
_, file_extension = os.path.splitext(filepath)
1819
return file_extension
1920

2021

21-
def get_path_leaf(path):
22+
def get_path_leaf(path) -> str:
2223
head, tail = ntpath.split(path)
2324
return tail or ntpath.basename(head)

essentials/json.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,26 @@
22
This module defines a user-friendly json encoder,
33
supporting time objects, UUID and bytes.
44
"""
5+
56
import base64
67
import dataclasses
78
import json
89
from datetime import date, datetime, time
910
from enum import Enum
11+
from typing import Any
1012
from uuid import UUID
1113

1214
__all__ = ["FriendlyEncoder", "dumps"]
1315

1416

1517
class FriendlyEncoder(json.JSONEncoder):
16-
def default(self, obj):
18+
def default(self, obj: Any) -> Any:
1719
try:
1820
return json.JSONEncoder.default(self, obj)
1921
except TypeError:
2022
if hasattr(obj, "dict"):
23+
if hasattr(obj, "model_dump"):
24+
return obj.model_dump()
2125
return obj.dict()
2226
if isinstance(obj, time):
2327
return obj.strftime("%H:%M:%S")
@@ -32,7 +36,7 @@ def default(self, obj):
3236
if isinstance(obj, Enum):
3337
return obj.value
3438
if dataclasses.is_dataclass(obj):
35-
return dataclasses.asdict(obj)
39+
return dataclasses.asdict(obj) # type:ignore[arg-type]
3640
raise
3741

3842

@@ -48,7 +52,7 @@ def dumps(
4852
default=None,
4953
sort_keys=False,
5054
**kw
51-
):
55+
) -> str:
5256
if cls is None:
5357
cls = FriendlyEncoder
5458
return json.dumps(

essentials/meta.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
import warnings
22
from functools import wraps
33
from inspect import iscoroutinefunction
4+
from typing import Callable, Optional, TypeVar
5+
6+
T = TypeVar("T")
7+
FuncType = Callable[..., T]
48

59

610
class DeprecatedException(Exception):
7-
def __init__(self, param_name):
11+
def __init__(self, param_name: str) -> None:
812
super().__init__("The function `%s` is deprecated" % param_name)
913

1014

11-
def deprecated(message=None, raise_exception=False):
15+
def deprecated(
16+
message: Optional[str] = None, raise_exception=False
17+
) -> Callable[[FuncType], FuncType]:
1218
"""
1319
This is a decorator which can be used to mark functions
1420
as deprecated. It will result in a warning being emitted

0 commit comments

Comments
 (0)