-
Notifications
You must be signed in to change notification settings - Fork 1
Conversation
eppo_client/eval.py
Outdated
sharder: Sharder | ||
|
||
def evaluate_flag( | ||
self, flag, subject_key, subject_attributes |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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!
There was a problem hiding this 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
eppo_client/eval.py
Outdated
sharder: Sharder | ||
|
||
def evaluate_flag( | ||
self, flag, subject_key, subject_attributes |
There was a problem hiding this comment.
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!
There was a problem hiding this 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, |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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
There was a problem hiding this 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, |
There was a problem hiding this comment.
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.
5c98d88
to
0df2809
Compare
subject_attributes: Dict[str, Union[str, float, int, bool]] | ||
allocation_key: str | ||
variation: Variation | ||
extra_logging: Dict[str, str] |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
raise e | ||
|
||
def get_assignment_variation( | ||
def get_assignment_detail( |
There was a problem hiding this comment.
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".
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
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", {})) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's official 🥳
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
if subject_attributes is None: | ||
subject_attributes = {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why?
There was a problem hiding this comment.
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
eppo_client/eval.py
Outdated
class FlagEvaluation: | ||
flag_key: str | ||
subject_key: str | ||
subject_attributes: Dict[str, Union[str, float, int, bool]] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
optional?
There was a problem hiding this comment.
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", {})) |
There was a problem hiding this comment.
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
@@ -4,7 +4,8 @@ | |||
from enum import Enum | |||
from typing import Any, List | |||
|
|||
from eppo_client.base_model import SdkBaseModel | |||
from eppo_client.models import SdkBaseModel | |||
from eppo_client.types import AttributeType, ConditionValueType, SubjectAttributes | |||
|
|||
|
|||
class OperatorType(Enum): |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
store.set_configurations({"randomization_algo": test_exp}) | ||
assert store.get_configuration("randomization_algo") == test_exp | ||
store.set_configurations({"flag": mock_flag}) | ||
assert store.get_configuration("flag") == mock_flag | ||
|
||
|
||
def test_evicts_old_entries_when_max_size_exceeded(): |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
* Pass new tests * mypy + flake
logger.error( | ||
"[Eppo SDK] Variation value does not have the correct type for the flag: " | ||
f"{flag_key} and variation key {result.variation.key}" | ||
) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
Fixes: #issue
Motivation and Context
Migrating the Python SDK to the UFC to enable more flexible feature flag evaluation and advanced use cases.
Description
How has this been tested?