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

[ff-2397] Allow attributes to be specified as dicts#55

Merged
schmit merged 3 commits intomainfrom
ff-2397/attributes-in-dict
Jun 12, 2024
Merged

[ff-2397] Allow attributes to be specified as dicts#55
schmit merged 3 commits intomainfrom
ff-2397/attributes-in-dict

Conversation

@schmit
Copy link

@schmit schmit commented Jun 11, 2024

Fixes: #ff-2397

Motivation and Context

Currently, users need to specify numerical and categorical attributes explicitly. This adds an option to supply a dictionary and we do the conversion to numerical and categorical attributes within the SDK based on value types

Description

  • Allow users to give subject and action attributes as dicts
  • If needed, convert these dicts to attributes
  • Changed the types for this pattern to look clean
  • Turn off graceful mode for tests to catch errors explicitly

How has this been tested?

All tests pass

"nonexistent_bandit",
"subject_key",
DEFAULT_SUBJECT_ATTRIBUTES,
[],
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did not catch that this test was broken because is_graceful_mode was set to True

client = get_instance()
result = client.get_bandit_action(
"a_flag", "subject_key", DEFAULT_SUBJECT_ATTRIBUTES, [], "default_variation"
"a_flag", "subject_key", DEFAULT_SUBJECT_ATTRIBUTES, {}, "default_variation"
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did not catch that this test was broken because is_graceful_mode was set to True

AttributeType = Union[str, int, float, bool, None]
ConditionValueType = Union[AttributeType, List[AttributeType]]
SubjectAttributes = Dict[str, AttributeType]
AttributesDict = Dict[str, AttributeType]
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed this to re-use the type

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, I did the same in Java calling it "EppoAttributes"

Copy link
Contributor

@aarsilv aarsilv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WHY NOT BOTH 📈 📈 📈

Comment on lines +307 to +308
subject_attributes = convert_subject_context_to_attributes(subject_context)
action_contexts = convert_actions_to_action_contexts(actions)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for helping reduce friction for developers!

AttributeType = Union[str, int, float, bool, None]
ConditionValueType = Union[AttributeType, List[AttributeType]]
SubjectAttributes = Dict[str, AttributeType]
AttributesDict = Dict[str, AttributeType]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, I did the same in Java calling it "EppoAttributes"

base_url=MOCK_BASE_URL,
api_key="dummy",
assignment_logger=mock_assignment_logger,
is_graceful_mode=False,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh yeah good catch!

@aarsilv aarsilv assigned schmit and unassigned aarsilv and giorgiomartini0 Jun 12, 2024
@@ -1 +1 @@
__version__ = "3.2.0"
__version__ = "3.2.1"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: 3.2.0 seems to be non-released yet so there's no reason to bump? (If it were released, the change here should be a minor bump, not patch.)

Suggested change
__version__ = "3.2.1"
__version__ = "3.2.0"

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, didn't realize I forgot to release 3.2.0 -- will undo the version bump and add this to the 3.2.0 release

Comment on lines +315 to +316
subject_attributes.categorical_attributes
| subject_attributes.numeric_attributes, # type: ignore
Copy link
Collaborator

@rasendubi rasendubi Jun 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor: one thing that still bothers me is that:

subject_attributes.categorical_attributes | subject_attributes.numeric_attributes != subject_context

It's just seems very hacky and very easy to shoot oneself in the foot with this, as it may introduce subtle evaluation differences. (e.g., get_string_assignment() returns a different value from here when called by the user).

As one example, attributes that are not one of [str,int,float,bool] are silently dropped for bandits. (In normal flag evaluation, they are convertible to string.)

  • We can cover this with a lot of tests
  • Or maybe refactor the code so that we perform similar transformation (float-promotion and converting to string) on all flag evaluation before attributes enter rules evaluation—so that flag evaluation engine only deals with floats and strings.
  • Alternatively, we can make get_string_assignment() accept Union[Attributes, AttributesDict] as well—this way we can pass user-passed attributes unmodified to flag evaluation.
  • At the very least, this deserves a caution comment here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this is a great comment!

As one example, attributes that are not one of [str,int,float,bool] are silently dropped for bandits

But the attribute type covers all the accepted cases: AttributeType = Union[str, int, float, bool, None]; Yes None gets dropped but it indicates absence anyway and would evaluate the same way whether we set the value to None or remove the key, value pair altogether.

Perhaps the problem is that we are not dropping none [str,int,float,bool] values when evaluating get_string_assignment?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue is that Python's typing is optional, so users are free to pass any type as attribute. It won't give any error and will be handled by applying json.dumps.

So maybe another solution is to be more strict in what attributes we accept. i.e., add a runtime check for attribute type, error or filter out unexpected types.

The issue is also slightly exaggerated for languages that don't have easy union types (e.g., Go accept any value for attributes). Maybe we should handle that in the respective language's implementation but it would be great for the python to set an example here, so this is not copied blindly

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As another inconsistency: ints may loose precision.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not so worried about losing precision -- the loss in precision is orders of magnitude less than the statistical uncertainty in the bandit model

@typotter
Copy link
Collaborator

lgtm 👍

@schmit schmit force-pushed the ff-2397/attributes-in-dict branch from 51c4aaa to fda0003 Compare June 12, 2024 16:29
@schmit schmit merged commit 00fb6ae into main Jun 12, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants