Skip to content

Commit 11456d4

Browse files
Tatshfabiocaccamo
andauthored
Add type annotations. (#491)
* Add type hints * Add `fix-future-annotations` pre-commit hook. * Run `fix-future-annotations` pre-commit hook. * Use `mypy` default cache dir. * Add `mypy` test requirement. * Add `mypy` pre-commit hook. * Update `pre-commit run` command options. * pre-commit: add deps for Mypy * Add bandit config (for Codacy) * Bump `pre-commit` hooks and `mypy` additional dependencies. * Update `mypy` options. * Add type stubs for mypy to test requirements. * Bump `typing_extensions`. * Delete requirements-dev.txt * Improve typing annotations and consistency. - Fix serializer type parameter order (JSON/Pickle: [Any,str] → [str,Any]). - Simplify XMLSerializer to return dict[str,Any] instead of OrderedDict. - Remove redundant type casts and unnecessary type ignores. - Add proper generic typing to core functions (filter, invert, merge, subset). - Improve type annotations for dict operations and return types. - Remove unused import statements and clean up type imports. - Add missing __future__ annotations imports where needed. - Fix type ignore comments to be more specific and necessary only. --------- Co-authored-by: Fabio Caccamo <[email protected]>
1 parent de5ea2d commit 11456d4

File tree

165 files changed

+2337
-1336
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

165 files changed

+2337
-1336
lines changed

.pre-commit-config.yaml

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,35 @@ repos:
88
- id: pyupgrade
99
args: ["--py310-plus"]
1010

11+
- repo: https://github.com/frostming/fix-future-annotations
12+
rev: 0.5.0
13+
hooks:
14+
- id: fix-future-annotations
15+
1116
- repo: https://github.com/astral-sh/ruff-pre-commit
12-
rev: v0.12.12
17+
rev: v0.13.2
1318
hooks:
1419
- id: ruff
1520
args: [--fix, --exit-non-zero-on-fix]
1621
- id: ruff-format
1722

23+
- repo: https://github.com/pre-commit/mirrors-mypy
24+
rev: v1.18.2
25+
hooks:
26+
- id: mypy
27+
args: [--config-file=pyproject.toml]
28+
additional_dependencies:
29+
- boto3-stubs[essential,lambda]==1.40.41
30+
- decouple-types==1.0.2
31+
- types-PyYAML==6.0.12.20250915
32+
- types-beautifulsoup4==4.12.0.20250516
33+
- types-html5lib==1.1.11.20250917
34+
- types-openpyxl==3.1.5.20250919
35+
- types-python-dateutil==2.9.0.20250822
36+
- types-toml==0.10.8.20240310
37+
- types-xmltodict==1.0.1.20250920
38+
exclude: "tests"
39+
1840
- repo: https://github.com/pre-commit/pre-commit-hooks
1941
rev: v6.0.0
2042
hooks:

bandit.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
skips: ['B101']

benedict/core/clean.py

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,16 @@
1-
def _clean_dict(d, strings, collections):
1+
from __future__ import annotations
2+
3+
from collections.abc import MutableMapping, MutableSequence
4+
from typing import Any, TypeVar
5+
6+
_K = TypeVar("_K")
7+
_V = TypeVar("_V")
8+
_T = TypeVar("_T")
9+
10+
11+
def _clean_dict(
12+
d: MutableMapping[_K, _V], strings: bool, collections: bool
13+
) -> MutableMapping[_K, _V]:
214
keys = list(d.keys())
315
for key in keys:
416
d[key] = _clean_value(d[key], strings=strings, collections=collections)
@@ -7,41 +19,47 @@ def _clean_dict(d, strings, collections):
719
return d
820

921

10-
def _clean_list(ls, strings, collections):
22+
def _clean_list(
23+
ls: MutableSequence[_T], strings: bool, collections: bool
24+
) -> MutableSequence[_T]:
1125
for i in range(len(ls) - 1, -1, -1):
1226
ls[i] = _clean_value(ls[i], strings=strings, collections=collections)
1327
if ls[i] is None:
1428
ls.pop(i)
1529
return ls
1630

1731

18-
def _clean_set(values, strings, collections):
32+
def _clean_set(values: set[_T], strings: bool, collections: bool) -> set[_T]:
1933
return {
2034
value
2135
for value in values
2236
if _clean_value(value, strings=strings, collections=collections) is not None
2337
}
2438

2539

26-
def _clean_str(s, strings, collections):
40+
def _clean_str(s: str, strings: bool, collections: bool) -> str | None:
2741
return s if s and s.strip() else None
2842

2943

30-
def _clean_tuple(values, strings, collections):
44+
def _clean_tuple(
45+
values: tuple[_T, ...], strings: bool, collections: bool
46+
) -> tuple[_T, ...]:
3147
return tuple(
3248
value
3349
for value in values
3450
if _clean_value(value, strings=strings, collections=collections) is not None
3551
)
3652

3753

38-
def _clean_value(value, strings, collections):
54+
def _clean_value(value: Any, strings: bool, collections: bool) -> Any:
3955
if value is None:
4056
return value
41-
elif isinstance(value, list) and collections:
57+
elif isinstance(value, MutableSequence) and collections:
4258
value = _clean_list(value, strings=strings, collections=collections) or None
43-
elif isinstance(value, dict) and collections:
44-
value = _clean_dict(value, strings=strings, collections=collections) or None
59+
elif isinstance(value, MutableMapping) and collections:
60+
value = (
61+
_clean_dict(dict(value), strings=strings, collections=collections) or None
62+
)
4563
elif isinstance(value, set) and collections:
4664
value = _clean_set(value, strings=strings, collections=collections) or None
4765
elif isinstance(value, str) and strings:
@@ -51,5 +69,5 @@ def _clean_value(value, strings, collections):
5169
return value
5270

5371

54-
def clean(d, strings=True, collections=True):
72+
def clean(d: Any, strings: bool = True, collections: bool = True) -> Any:
5573
return _clean_dict(d, strings=strings, collections=collections)

benedict/core/clone.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
1+
from __future__ import annotations
2+
13
import copy
4+
from collections.abc import MutableMapping
5+
from typing import Any, TypeVar
6+
7+
_T = TypeVar("_T")
28

39

4-
def clone(obj, empty=False, memo=None):
10+
def clone(
11+
obj: _T,
12+
empty: bool = False,
13+
memo: dict[int, Any] | None = None,
14+
) -> _T:
515
d = copy.deepcopy(obj, memo)
6-
if empty:
16+
if empty and isinstance(d, MutableMapping):
717
d.clear()
818
return d

benedict/core/dump.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
from typing import Any
2+
13
from benedict.serializers import JSONSerializer
24

35

4-
def dump(obj, **kwargs):
6+
def dump(obj: Any, **kwargs: Any) -> str:
57
serializer = JSONSerializer()
68
options = {"indent": 4, "sort_keys": True}
79
options.update(**kwargs)

benedict/core/filter.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
1-
from benedict.core import clone
1+
from collections.abc import Callable, MutableMapping
2+
from typing import TypeVar
23

4+
from benedict.core.clone import clone
35

4-
def filter(d, predicate):
6+
_K = TypeVar("_K")
7+
_V = TypeVar("_V")
8+
9+
10+
def filter(
11+
d: MutableMapping[_K, _V], predicate: Callable[[_K, _V], bool]
12+
) -> MutableMapping[_K, _V]:
513
if not callable(predicate):
614
raise ValueError("predicate argument must be a callable.")
715
new_dict = clone(d, empty=True)
816
keys = list(d.keys())
917
for key in keys:
10-
value = d.get(key, None)
18+
value = d[key]
1119
if predicate(key, value):
1220
new_dict[key] = value
1321
return new_dict

benedict/core/find.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
1-
def find(d, keys, default=None):
1+
from __future__ import annotations
2+
3+
from collections.abc import Iterable, Mapping
4+
from typing import TypeVar
5+
6+
_K = TypeVar("_K")
7+
_V = TypeVar("_V")
8+
9+
10+
def find(
11+
d: Mapping[_K, _V], keys: Iterable[_K], default: _V | None = None
12+
) -> _V | None:
213
for key in keys:
314
if key in d:
4-
return d.get(key, default)
15+
return d[key]
516
return default

benedict/core/flatten.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,27 @@
1-
from benedict.core import clone
2-
from benedict.utils import type_util
1+
from collections.abc import Mapping
2+
from typing import Any
33

4+
from benedict.core.clone import clone
45

5-
def _flatten_key(base_key, key, separator):
6+
7+
def _flatten_key(base_key: str, key: str, separator: str) -> str:
68
if base_key and separator:
79
return f"{base_key}{separator}{key}"
810
return key
911

1012

11-
def _flatten_item(d, base_dict, base_key, separator):
13+
def _flatten_item(
14+
d: Any,
15+
base_dict: Any,
16+
base_key: str,
17+
separator: str,
18+
) -> Any:
1219
new_dict = base_dict
1320
keys = list(d.keys())
1421
for key in keys:
1522
new_key = _flatten_key(base_key, key, separator)
1623
value = d.get(key, None)
17-
if type_util.is_dict(value):
24+
if isinstance(value, Mapping):
1825
new_value = _flatten_item(
1926
value, base_dict=new_dict, base_key=new_key, separator=separator
2027
)
@@ -26,6 +33,6 @@ def _flatten_item(d, base_dict, base_key, separator):
2633
return new_dict
2734

2835

29-
def flatten(d, separator="_"):
36+
def flatten(d: Any, separator: str = "_") -> Any:
3037
new_dict = clone(d, empty=True)
3138
return _flatten_item(d, base_dict=new_dict, base_key="", separator=separator)

benedict/core/groupby.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
1+
from __future__ import annotations
2+
3+
from collections.abc import Mapping, MutableSequence, Sequence
4+
from typing import Any, TypeVar
5+
16
from benedict.utils import type_util
27

8+
_K = TypeVar("_K")
9+
_V = TypeVar("_V", bound=MutableSequence[Any])
10+
311

4-
def groupby(items, key):
12+
def groupby(items: Sequence[Mapping[_K, Any]], key: _K) -> dict[Any, Any]:
513
if not type_util.is_list(items):
614
raise ValueError("items should be a list of dicts.")
7-
items_grouped = {}
15+
items_grouped: dict[Any, Any] = {}
816
for item in items:
917
if not type_util.is_dict(item):
1018
raise ValueError("item should be a dict.")

benedict/core/invert.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,28 @@
1-
from benedict.core import clone
1+
from collections.abc import MutableMapping, Sequence
2+
from typing import Any, TypeVar
3+
4+
from benedict.core.clone import clone
25
from benedict.utils import type_util
36

7+
_K = TypeVar("_K")
8+
_V = TypeVar("_V")
9+
410

5-
def _invert_item(d, key, value, flat):
11+
def _invert_item(d: MutableMapping[Any, Any], key: _K, value: _V, flat: bool) -> None:
612
if flat:
713
d.setdefault(value, key)
814
else:
915
d.setdefault(value, []).append(key)
1016

1117

12-
def _invert_list(d, key, value, flat):
18+
def _invert_list(
19+
d: MutableMapping[Any, Any], key: _K, value: Sequence[Any], flat: bool
20+
) -> None:
1321
for value_item in value:
1422
_invert_item(d, key, value_item, flat)
1523

1624

17-
def invert(d, flat=False):
25+
def invert(d: MutableMapping[_K, _V], flat: bool = False) -> MutableMapping[Any, Any]:
1826
new_dict = clone(d, empty=True)
1927
for key, value in d.items():
2028
if type_util.is_list_or_tuple(value):

0 commit comments

Comments
 (0)