Skip to content
This repository was archived by the owner on Nov 8, 2024. It is now read-only.

Commit 102bd6f

Browse files
authored
[bandits] rename Attributes and ContextAttributes (#56)
1 parent 00fb6ae commit 102bd6f

File tree

9 files changed

+70
-68
lines changed

9 files changed

+70
-68
lines changed

eppo_client/bandit.py

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
)
1111
from eppo_client.rules import to_string
1212
from eppo_client.sharders import Sharder
13-
from eppo_client.types import AttributesDict
13+
from eppo_client.types import Attributes
1414

1515

1616
logger = logging.getLogger(__name__)
@@ -21,7 +21,7 @@ class BanditEvaluationError(Exception):
2121

2222

2323
@dataclass
24-
class Attributes:
24+
class ContextAttributes:
2525
numeric_attributes: Dict[str, float]
2626
categorical_attributes: Dict[str, str]
2727

@@ -31,22 +31,23 @@ def empty(cls):
3131
Create an empty Attributes instance with no numeric or categorical attributes.
3232
3333
Returns:
34-
Attributes: An instance of the Attributes class with empty dictionaries
34+
ContextAttributes: An instance of the ContextAttributes class with empty dictionaries
3535
for numeric and categorical attributes.
3636
"""
3737
return cls({}, {})
3838

3939
@classmethod
40-
def from_dict(cls, attributes: AttributesDict):
40+
def from_dict(cls, attributes: Attributes):
4141
"""
42-
Create an Attributes instance from a dictionary of attributes.
42+
Create an ContextAttributes instance from a dictionary of attributes.
4343
4444
Args:
4545
attributes (Dict[str, Union[float, int, bool, str]]): A dictionary where keys are attribute names
46-
and values are attribute values which can be of type float, int, bool, or str.
46+
and values are attribute values which can be of type float, int, bool, or str.
4747
4848
Returns:
49-
Attributes: An instance of the Attributes class with numeric and categorical attributes separated.
49+
ContextAttributes: An instance of the ContextAttributes class
50+
with numeric and categorical attributes separated.
5051
"""
5152
numeric_attributes = {
5253
key: float(value)
@@ -61,17 +62,17 @@ def from_dict(cls, attributes: AttributesDict):
6162
return cls(numeric_attributes, categorical_attributes)
6263

6364

64-
ActionContexts = Dict[str, Attributes]
65-
ActionContextsDict = Dict[str, AttributesDict]
65+
ActionContexts = Dict[str, ContextAttributes]
66+
ActionAttributes = Dict[str, Attributes]
6667

6768

6869
@dataclass
6970
class BanditEvaluation:
7071
flag_key: str
7172
subject_key: str
72-
subject_attributes: Attributes
73+
subject_attributes: ContextAttributes
7374
action_key: Optional[str]
74-
action_attributes: Optional[Attributes]
75+
action_attributes: Optional[ContextAttributes]
7576
action_score: float
7677
action_weight: float
7778
gamma: float
@@ -88,7 +89,7 @@ def to_string(self) -> str:
8889

8990

9091
def null_evaluation(
91-
flag_key: str, subject_key: str, subject_attributes: Attributes, gamma: float
92+
flag_key: str, subject_key: str, subject_attributes: ContextAttributes, gamma: float
9293
):
9394
return BanditEvaluation(
9495
flag_key, subject_key, subject_attributes, None, None, 0.0, 0.0, gamma, 0.0
@@ -104,7 +105,7 @@ def evaluate_bandit(
104105
self,
105106
flag_key: str,
106107
subject_key: str,
107-
subject_attributes: Attributes,
108+
subject_attributes: ContextAttributes,
108109
actions: ActionContexts,
109110
bandit_model: BanditModelData,
110111
) -> BanditEvaluation:
@@ -138,7 +139,7 @@ def evaluate_bandit(
138139

139140
def score_actions(
140141
self,
141-
subject_attributes: Attributes,
142+
subject_attributes: ContextAttributes,
142143
actions: ActionContexts,
143144
bandit_model: BanditModelData,
144145
) -> Dict[str, float]:
@@ -209,8 +210,8 @@ def select_action(self, flag_key, subject_key, action_weights) -> str:
209210

210211

211212
def score_action(
212-
subject_attributes: Attributes,
213-
action_attributes: Attributes,
213+
subject_attributes: ContextAttributes,
214+
action_attributes: ContextAttributes,
214215
coefficients: BanditCoefficients,
215216
) -> float:
216217
score = coefficients.intercept

eppo_client/client.py

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
from typing import Any, Dict, Optional, Union
55
from eppo_client.assignment_logger import AssignmentLogger
66
from eppo_client.bandit import (
7-
ActionContextsDict,
7+
ActionAttributes,
88
BanditEvaluator,
99
BanditResult,
10-
Attributes,
10+
ContextAttributes,
1111
ActionContexts,
1212
)
1313
from eppo_client.configuration_requestor import (
@@ -17,7 +17,7 @@
1717
from eppo_client.models import VariationType
1818
from eppo_client.poller import Poller
1919
from eppo_client.sharders import MD5Sharder
20-
from eppo_client.types import AttributesDict, ValueType
20+
from eppo_client.types import Attributes, ValueType
2121
from eppo_client.validation import validate_not_blank
2222
from eppo_client.eval import FlagEvaluation, Evaluator, none_result
2323
from eppo_client.version import __version__
@@ -49,7 +49,7 @@ def get_string_assignment(
4949
self,
5050
flag_key: str,
5151
subject_key: str,
52-
subject_attributes: AttributesDict,
52+
subject_attributes: Attributes,
5353
default: str,
5454
) -> str:
5555
return self.get_assignment_variation(
@@ -64,7 +64,7 @@ def get_integer_assignment(
6464
self,
6565
flag_key: str,
6666
subject_key: str,
67-
subject_attributes: AttributesDict,
67+
subject_attributes: Attributes,
6868
default: int,
6969
) -> int:
7070
return self.get_assignment_variation(
@@ -79,7 +79,7 @@ def get_numeric_assignment(
7979
self,
8080
flag_key: str,
8181
subject_key: str,
82-
subject_attributes: AttributesDict,
82+
subject_attributes: Attributes,
8383
default: float,
8484
) -> float:
8585
# convert to float in case we get an int
@@ -97,7 +97,7 @@ def get_boolean_assignment(
9797
self,
9898
flag_key: str,
9999
subject_key: str,
100-
subject_attributes: AttributesDict,
100+
subject_attributes: Attributes,
101101
default: bool,
102102
) -> bool:
103103
return self.get_assignment_variation(
@@ -112,7 +112,7 @@ def get_json_assignment(
112112
self,
113113
flag_key: str,
114114
subject_key: str,
115-
subject_attributes: AttributesDict,
115+
subject_attributes: Attributes,
116116
default: Dict[Any, Any],
117117
) -> Dict[Any, Any]:
118118
json_value = self.get_assignment_variation(
@@ -131,7 +131,7 @@ def get_assignment_variation(
131131
self,
132132
flag_key: str,
133133
subject_key: str,
134-
subject_attributes: AttributesDict,
134+
subject_attributes: Attributes,
135135
default: Optional[ValueType],
136136
expected_variation_type: VariationType,
137137
):
@@ -155,7 +155,7 @@ def get_assignment_detail(
155155
self,
156156
flag_key: str,
157157
subject_key: str,
158-
subject_attributes: AttributesDict,
158+
subject_attributes: Attributes,
159159
expected_variation_type: VariationType,
160160
) -> FlagEvaluation:
161161
"""Maps a subject to a variation for a given flag
@@ -231,8 +231,8 @@ def get_bandit_action(
231231
self,
232232
flag_key: str,
233233
subject_key: str,
234-
subject_context: Union[Attributes, AttributesDict],
235-
actions: Union[ActionContexts, ActionContextsDict],
234+
subject_context: Union[ContextAttributes, Attributes],
235+
actions: Union[ActionContexts, ActionAttributes],
236236
default: str,
237237
) -> BanditResult:
238238
"""
@@ -250,11 +250,11 @@ def get_bandit_action(
250250
Args:
251251
flag_key (str): The feature flag key that contains the bandit as one of the variations.
252252
subject_key (str): The key identifying the subject.
253-
subject_context (Attributes | AttributesDict): The subject context.
254-
If supplying an AttributesDict, it gets converted to an Attributes instance
255-
actions (ActionContexts | ActionContextsDict): The dictionary that maps action keys
253+
subject_context (ActionContexts | ActionAttributes): The subject context.
254+
If supplying an ActionAttributes, it gets converted to an ActionContexts instance
255+
actions (ActionContexts | ActionAttributes): The dictionary that maps action keys
256256
to their context of actions with their contexts.
257-
If supplying an AttributesDict, it gets converted to an Attributes instance.
257+
If supplying an ActionAttributes, it gets converted to an ActionContexts instance.
258258
default (str): The default variation to use if the subject is not part of the bandit.
259259
260260
Returns:
@@ -267,13 +267,16 @@ def get_bandit_action(
267267
result = client.get_bandit_action(
268268
"flag_key",
269269
"subject_key",
270-
Attributes(
270+
ContextAttributes(
271271
numeric_attributes={"age": 25},
272272
categorical_attributes={"country": "USA"}),
273273
{
274-
"action1": Attributes(numeric_attributes={"price": 10.0}, categorical_attributes={"category": "A"}),
274+
"action1": ContextAttributes(
275+
numeric_attributes={"price": 10.0},
276+
categorical_attributes={"category": "A"}
277+
),
275278
"action2": {"price": 10.0, "category": "B"}
276-
"action3": Attributes.empty(),
279+
"action3": ContextAttributes.empty(),
277280
},
278281
"default"
279282
)
@@ -300,8 +303,8 @@ def get_bandit_action_detail(
300303
self,
301304
flag_key: str,
302305
subject_key: str,
303-
subject_context: Union[Attributes, AttributesDict],
304-
actions: Union[ActionContexts, ActionContextsDict],
306+
subject_context: Union[ContextAttributes, Attributes],
307+
actions: Union[ActionContexts, ActionAttributes],
305308
default: str,
306309
) -> BanditResult:
307310
subject_attributes = convert_subject_context_to_attributes(subject_context)
@@ -428,17 +431,17 @@ def check_value_type_match(
428431

429432

430433
def convert_subject_context_to_attributes(
431-
subject_context: Union[Attributes, AttributesDict]
432-
) -> Attributes:
434+
subject_context: Union[ContextAttributes, Attributes]
435+
) -> ContextAttributes:
433436
if isinstance(subject_context, dict):
434-
return Attributes.from_dict(subject_context)
437+
return ContextAttributes.from_dict(subject_context)
435438
return subject_context
436439

437440

438441
def convert_actions_to_action_contexts(
439-
actions: Union[ActionContexts, ActionContextsDict]
442+
actions: Union[ActionContexts, ActionAttributes]
440443
) -> ActionContexts:
441444
return {
442-
k: Attributes.from_dict(v) if isinstance(v, dict) else v
445+
k: ContextAttributes.from_dict(v) if isinstance(v, dict) else v
443446
for k, v in actions.items()
444447
}

eppo_client/eval.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@
55
from dataclasses import dataclass
66
import datetime
77

8-
from eppo_client.types import AttributesDict
8+
from eppo_client.types import Attributes
99

1010

1111
@dataclass
1212
class FlagEvaluation:
1313
flag_key: str
1414
variation_type: VariationType
1515
subject_key: str
16-
subject_attributes: AttributesDict
16+
subject_attributes: Attributes
1717
allocation_key: Optional[str]
1818
variation: Optional[Variation]
1919
extra_logging: Dict[str, str]
@@ -28,7 +28,7 @@ def evaluate_flag(
2828
self,
2929
flag: Flag,
3030
subject_key: str,
31-
subject_attributes: AttributesDict,
31+
subject_attributes: Attributes,
3232
) -> FlagEvaluation:
3333
if not flag.enabled:
3434
return none_result(
@@ -93,7 +93,7 @@ def none_result(
9393
flag_key: str,
9494
variation_type: VariationType,
9595
subject_key: str,
96-
subject_attributes: AttributesDict,
96+
subject_attributes: Attributes,
9797
) -> FlagEvaluation:
9898
return FlagEvaluation(
9999
flag_key=flag_key,

eppo_client/rules.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import semver
88

99
from eppo_client.models import SdkBaseModel
10-
from eppo_client.types import AttributeType, ConditionValueType, AttributesDict
10+
from eppo_client.types import AttributeType, ConditionValueType, Attributes
1111

1212

1313
class OperatorType(Enum):
@@ -32,16 +32,14 @@ class Rule(SdkBaseModel):
3232
conditions: List[Condition]
3333

3434

35-
def matches_rule(rule: Rule, subject_attributes: AttributesDict) -> bool:
35+
def matches_rule(rule: Rule, subject_attributes: Attributes) -> bool:
3636
return all(
3737
evaluate_condition(condition, subject_attributes)
3838
for condition in rule.conditions
3939
)
4040

4141

42-
def evaluate_condition(
43-
condition: Condition, subject_attributes: AttributesDict
44-
) -> bool:
42+
def evaluate_condition(condition: Condition, subject_attributes: Attributes) -> bool:
4543
subject_value = subject_attributes.get(condition.attribute, None)
4644
if condition.operator == OperatorType.IS_NULL:
4745
if condition.value:

eppo_client/types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
ValueType = Union[str, int, float, bool]
44
AttributeType = Union[str, int, float, bool, None]
55
ConditionValueType = Union[AttributeType, List[AttributeType]]
6-
AttributesDict = Dict[str, AttributeType]
6+
Attributes = Dict[str, AttributeType]
77
Action = str

eppo_client/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "3.2.0"
1+
__version__ = "3.2.1"

example/03_bandit.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,15 @@ async def bandit(name: str, country: str, age: int):
4343
bandit_result = client.get_bandit_action(
4444
"shoe-bandit",
4545
name,
46-
eppo_client.bandit.Attributes(
46+
eppo_client.bandit.ContextAttributes(
4747
numeric_attributes={"age": age}, categorical_attributes={"country": country}
4848
),
4949
{
50-
"nike": eppo_client.bandit.Attributes(
50+
"nike": eppo_client.bandit.ContextAttributes(
5151
numeric_attributes={"brand_affinity": 2.3},
5252
categorical_attributes={"aspect_ratio": "16:9"},
5353
),
54-
"adidas": eppo_client.bandit.Attributes(
54+
"adidas": eppo_client.bandit.ContextAttributes(
5555
numeric_attributes={"brand_affinity": 0.2},
5656
categorical_attributes={"aspect_ratio": "16:9"},
5757
),

0 commit comments

Comments
 (0)