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

FF-1677 Python SDK UFC update#28

Merged
schmit merged 40 commits intomainfrom
allocation-revamp
Apr 24, 2024
Merged

FF-1677 Python SDK UFC update#28
schmit merged 40 commits intomainfrom
allocation-revamp

Conversation

@schmit
Copy link

@schmit schmit commented Mar 5, 2024

Fixes: #issue

Motivation and Context

Migrating the Python SDK to the UFC to enable more flexible feature flag evaluation and advanced use cases.

Description

  • Leverage UFC to evaluate flags
  • Create explicit integer and float variation types

How has this been tested?

  • Updated unit tests
  • Updated cross-SDK UFC test cases

sharder: Sharder

def evaluate_flag(
self, flag, subject_key, 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.

Also decided to pass along subject_key so we can include it in rule evaluation if we want (so you don't also have to add id to subject attributes), which has been a pet-peeve. We could just inject "id": subject_key into subject_attributes somewhere

Choose a reason for hiding this comment

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

Nice quality of life improvement!

Copy link

@giorgiomartini0 giorgiomartini0 left a comment

Choose a reason for hiding this comment

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

Love how fast this is coming together! Partial review for now

sharder: Sharder

def evaluate_flag(
self, flag, subject_key, subject_attributes

Choose a reason for hiding this comment

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

Nice quality of life improvement!

Copy link

@giorgiomartini0 giorgiomartini0 left a comment

Choose a reason for hiding this comment

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

A few more comments - haven't started on the tests yet.

allocation_key=None,
variation=None,
extra_logging={},
do_log=False,

Choose a reason for hiding this comment

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

Thinking out loud: the more we see the none/default outcome as an error/abnormal, the more I'd like to log it:

  • when a flag doesn't exist in the RAC, logging the attempted evaluation will help to discover mismatches between the flag key here and what was inputted in the UI; or help with autocomplete at flag creation time
  • when no allocations matched (not even the default one, somehow), it's basically an error and we would want to keep track of it

Copy link
Contributor

Choose a reason for hiding this comment

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

This would be benefit of having complex return type that includes evaluation reason.

You can see us passing around a "variation reason" for when we hacked a version with that for Palta: Eppo-exp/js-sdk-common@v2.1.0...v2.1.1-alpha.0

I think we can include the reason IN the FAC itself for most cases.

Copy link
Author

Choose a reason for hiding this comment

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

I think of the FlagEvaluation object as capturing the evaluation reason (in particular, the allocation key: we returned variation X because we matched allocation Y)

Copy link
Author

Choose a reason for hiding this comment

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

Seems like the main thing we should consider adding is an "error" attribute to flag evaluation that we can set

@schmit schmit requested review from aarsilv and leoromanovsky March 7, 2024 17:21
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.

This is looking good!

allocation_key=None,
variation=None,
extra_logging={},
do_log=False,
Copy link
Contributor

Choose a reason for hiding this comment

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

This would be benefit of having complex return type that includes evaluation reason.

You can see us passing around a "variation reason" for when we hacked a version with that for Palta: Eppo-exp/js-sdk-common@v2.1.0...v2.1.1-alpha.0

I think we can include the reason IN the FAC itself for most cases.

@schmit schmit force-pushed the allocation-revamp branch from 5c98d88 to 0df2809 Compare March 14, 2024 03:51
subject_attributes: Dict[str, Union[str, float, int, bool]]
allocation_key: str
variation: Variation
extra_logging: Dict[str, str]
Copy link
Contributor

Choose a reason for hiding this comment

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

is extra_logging where the evaluation reasoning would work its way in?

Copy link
Author

Choose a reason for hiding this comment

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

No, this is a generic object that allows us to add extra logging fields. The immediate use case is to log holdout assignments

@schmit schmit changed the title United RAC revamp [wip] Python SDK UFC update Mar 15, 2024
raise e

def get_assignment_variation(
def get_assignment_detail(
Copy link
Author

Choose a reason for hiding this comment

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

@aarsilv this function returns the FlagEvaluation object, which I currently consider to be the "detailed view of assignment".

Choose a reason for hiding this comment

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

Is this part of the "public" API then? Developers who want the full return object call this one instead of get__assignment?

Copy link
Author

Choose a reason for hiding this comment

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

Yes that is my thought -- but open to feedback.

For example: should we have typed versions of the detailed version?

"[Eppo SDK] No assigned variation. Subject attributes do not match targeting rules: {0}".format(
subject_attributes
)
if not check_type_match(expected_variation_type, flag.variation_type):
Copy link
Author

Choose a reason for hiding this comment

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

Check this before we check whether the flag is enabled so that we can notify developers even for disabled flags.

configs = cast(dict, self.__http_client.get(RAC_ENDPOINT).get("flags", {}))
for exp_key, exp_config in configs.items():
configs[exp_key] = ExperimentConfigurationDto(**exp_config)
configs = cast(dict, self.__http_client.get(UFC_ENDPOINT).get("flags", {}))
Copy link
Member

Choose a reason for hiding this comment

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

It's official 🥳

Choose a reason for hiding this comment

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

I would think that not finding flags in the response should trigger an exception, not silently return an empty config

Copy link
Author

Choose a reason for hiding this comment

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

I think that's fair, but this is how we have been doing things and I think it may be better to change this in a separate PR

Comment on lines +190 to +191
if subject_attributes is None:
subject_attributes = {}
Copy link
Member

Choose a reason for hiding this comment

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

why?

Copy link
Author

Choose a reason for hiding this comment

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

So that from that point onwards we can assume subject_attributes is a dictionary and can avoid the "null" case

class FlagEvaluation:
flag_key: str
subject_key: str
subject_attributes: Dict[str, Union[str, float, int, bool]]
Copy link
Member

Choose a reason for hiding this comment

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

optional?

Copy link
Author

Choose a reason for hiding this comment

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

We always return a dict here -- it might just be empty. I imagine that's easier to deal with downstream than having either nulls or dictionaries with values. Open to feedback

configs = cast(dict, self.__http_client.get(RAC_ENDPOINT).get("flags", {}))
for exp_key, exp_config in configs.items():
configs[exp_key] = ExperimentConfigurationDto(**exp_config)
configs = cast(dict, self.__http_client.get(UFC_ENDPOINT).get("flags", {}))

Choose a reason for hiding this comment

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

I would think that not finding flags in the response should trigger an exception, not silently return an empty config

from eppo_client.types import AttributeType, ConditionValueType, SubjectAttributes


class OperatorType(Enum):

Choose a reason for hiding this comment

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

Would you be open to adding "IS_NULL", "IS_NOT_NULL" here or would you prefer that to be a minor version bump once we support it in the frontend?

Copy link
Author

Choose a reason for hiding this comment

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

I think we should add it, and we can do that before publishing this version of the SDK, but let's move it to a different PR

assert store.get_configuration("flag") == mock_flag


def test_evicts_old_entries_when_max_size_exceeded():

Choose a reason for hiding this comment

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

What's this? Do we have a max size concept elsewhere?

Copy link
Author

Choose a reason for hiding this comment

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

Not sure -- I did not change the logic here

giorgiomartini0

This comment was marked as duplicate.

Comment on lines +191 to +194
logger.error(
"[Eppo SDK] Variation value does not have the correct type for the flag: "
f"{flag_key} and variation key {result.variation.key}"
)
Copy link
Member

Choose a reason for hiding this comment

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

when does this situation happen?

Copy link
Author

Choose a reason for hiding this comment

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

This should never happen, but can theoretically happen if the backend sends a value that does not have the correct type

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.

6 participants