Skip to content

Commit de37144

Browse files
authored
fix: fix type hints of EvaluationContext and HookHints (#535)
fix type hints of EvaluationContext and HookHints Signed-off-by: gruebel <[email protected]>
1 parent b418cb0 commit de37144

File tree

8 files changed

+213
-23
lines changed

8 files changed

+213
-23
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,4 @@ repos:
1919
rev: v1.18.1
2020
hooks:
2121
- id: mypy
22-
files: openfeature
22+
files: openfeature|tests/typechecking

openfeature/evaluation_context/__init__.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,23 @@
1010
__all__ = ["EvaluationContext", "get_evaluation_context", "set_evaluation_context"]
1111

1212
# https://openfeature.dev/specification/sections/evaluation-context#requirement-312
13-
EvaluationContextAttributes = typing.Mapping[
13+
EvaluationContextAttribute = typing.Union[
14+
bool,
15+
int,
16+
float,
1417
str,
15-
typing.Union[
16-
bool,
17-
int,
18-
float,
19-
str,
20-
datetime,
21-
Sequence["EvaluationContextAttributes"],
22-
"EvaluationContextAttributes",
23-
],
18+
datetime,
19+
Sequence["EvaluationContextAttribute"],
20+
typing.Mapping[str, "EvaluationContextAttribute"],
2421
]
2522

2623

2724
@dataclass
2825
class EvaluationContext:
2926
targeting_key: typing.Optional[str] = None
30-
attributes: EvaluationContextAttributes = field(default_factory=dict)
27+
attributes: typing.Mapping[str, EvaluationContextAttribute] = field(
28+
default_factory=dict
29+
)
3130

3231
def merge(self, ctx2: EvaluationContext) -> EvaluationContext:
3332
if not (self and ctx2):

openfeature/hook/__init__.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -70,19 +70,18 @@ def __setattr__(self, key: str, value: typing.Any) -> None:
7070

7171

7272
# https://openfeature.dev/specification/sections/hooks/#requirement-421
73-
HookHints = typing.Mapping[
73+
HookHintValue = typing.Union[
74+
bool,
75+
int,
76+
float,
7477
str,
75-
typing.Union[
76-
bool,
77-
int,
78-
float,
79-
str,
80-
datetime,
81-
Sequence["HookHints"],
82-
typing.Mapping[str, "HookHints"],
83-
],
78+
datetime,
79+
Sequence["HookHintValue"],
80+
typing.Mapping[str, "HookHintValue"],
8481
]
8582

83+
HookHints = typing.Mapping[str, HookHintValue]
84+
8685

8786
class Hook:
8887
def before(

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ module-root = ""
4545
namespace = true
4646

4747
[tool.mypy]
48-
files = "openfeature"
48+
files = "openfeature,tests/typechecking"
4949

5050
python_version = "3.9" # should be identical to the minimum supported version
5151
namespace_packages = true

tests/typechecking/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Type Checking Tests
2+
3+
This directory contains type checking validation files that are designed to be checked by **type checkers only**, not executed by pytest.
4+
5+
## Purpose
6+
7+
These files validate that the type annotations work correctly through static analysis.
8+
They ensure type safety for complex type definitions like `EvaluationContextAttribute` and other typed interfaces.
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
"""Requirement 3.1.2"""
2+
3+
from datetime import datetime
4+
5+
from openfeature.evaluation_context import EvaluationContext
6+
7+
# positive
8+
EvaluationContext(
9+
targeting_key="key",
10+
attributes={"bool": True},
11+
)
12+
13+
EvaluationContext(
14+
targeting_key="key",
15+
attributes={"int": 42},
16+
)
17+
18+
EvaluationContext(
19+
targeting_key="key",
20+
attributes={"float": 3.14},
21+
)
22+
23+
EvaluationContext(
24+
targeting_key="key",
25+
attributes={"str": "value"},
26+
)
27+
28+
EvaluationContext(
29+
targeting_key="key",
30+
attributes={"date": datetime.now()},
31+
)
32+
33+
EvaluationContext(
34+
targeting_key="key",
35+
attributes={
36+
"bool_list": [True, False],
37+
"int_list": [1, 2],
38+
"float_list": [1.1, 2.2],
39+
"date_list": [datetime.now(), datetime.now()],
40+
"str_list": ["a", "b"],
41+
"list_list": [
42+
["a", "b"],
43+
["c", "d"],
44+
],
45+
"dict_list": [
46+
{"int": 42},
47+
{"str": "value"},
48+
],
49+
},
50+
)
51+
52+
EvaluationContext(
53+
targeting_key="key",
54+
attributes={
55+
"user": {
56+
"id": 12345,
57+
"name": "John Doe",
58+
"active": True,
59+
"last_login": datetime.now(),
60+
"permissions": ["read", "write", "admin"],
61+
"metadata": {
62+
"source": "api",
63+
"version": 2.1,
64+
"tags": ["premium", "beta"],
65+
"config": {
66+
"nested_deeply": [
67+
{"item": 1, "enabled": True},
68+
{"item": 2, "enabled": False},
69+
]
70+
},
71+
},
72+
},
73+
},
74+
)
75+
76+
# negative
77+
EvaluationContext(
78+
targeting_key="key",
79+
attributes={"null": None}, # type: ignore[dict-item]
80+
)
81+
82+
EvaluationContext(
83+
targeting_key="key",
84+
attributes={"complex": -4.5j}, # type: ignore[dict-item]
85+
)
86+
87+
EvaluationContext(
88+
targeting_key="key",
89+
attributes={42: 42}, # type: ignore[dict-item]
90+
)

tests/typechecking/hook_data.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""Requirement 4.6.1"""
2+
3+
from openfeature.hook import HookData
4+
5+
# positive
6+
any_hook_data: HookData = {
7+
"user": {
8+
"id": 12345,
9+
"name": "John Doe",
10+
"active": True,
11+
"permissions": ["read", "write", "admin"],
12+
"metadata": {
13+
"source": "api",
14+
"version": 2.1,
15+
"tags": ["premium", "beta"],
16+
"config": {
17+
"nested_deeply": [
18+
{"item": 1, "enabled": True},
19+
{"item": 2, "enabled": False},
20+
]
21+
},
22+
},
23+
},
24+
}
25+
26+
27+
class ExampleClass:
28+
pass
29+
30+
31+
class_hook_data: HookData = {"example": ExampleClass}
32+
33+
# negative
34+
int_key_hook_data: HookData = {42: 42} # type: ignore[dict-item]

tests/typechecking/hook_hints.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
"""Requirement 4.1.2"""
2+
3+
from datetime import datetime
4+
5+
from openfeature.hook import HookHints
6+
7+
# positive
8+
bool_hook_hint: HookHints = {"bool": True}
9+
10+
int_hook_hint: HookHints = {"int": 42}
11+
12+
float_hook_hint: HookHints = {"float": 3.14}
13+
14+
str_hook_hint: HookHints = {"str": "value"}
15+
16+
date_hook_hint: HookHints = {"date": datetime.now()}
17+
18+
list_hook_hint: HookHints = {
19+
"bool_list": [True, False],
20+
"int_list": [1, 2],
21+
"float_list": [1.1, 2.2],
22+
"date_list": [datetime.now(), datetime.now()],
23+
"str_list": ["a", "b"],
24+
"list_list": [
25+
["a", "b"],
26+
["c", "d"],
27+
],
28+
"dict_list": [
29+
{"int": 42},
30+
{"str": "value"},
31+
],
32+
}
33+
34+
dict_hook_hint: HookHints = {
35+
"user": {
36+
"id": 12345,
37+
"name": "John Doe",
38+
"active": True,
39+
"last_login": datetime.now(),
40+
"permissions": ["read", "write", "admin"],
41+
"metadata": {
42+
"source": "api",
43+
"version": 2.1,
44+
"tags": ["premium", "beta"],
45+
"config": {
46+
"nested_deeply": [
47+
{"item": 1, "enabled": True},
48+
{"item": 2, "enabled": False},
49+
]
50+
},
51+
},
52+
},
53+
}
54+
55+
# negative
56+
null_hook_hint: HookHints = {"null": None} # type: ignore[dict-item]
57+
58+
complex_hook_hint: HookHints = {"complex": -4.5j} # type: ignore[dict-item]
59+
60+
int_key_hook_hint: HookHints = {42: 42} # type: ignore[dict-item]

0 commit comments

Comments
 (0)