Conversation
eppo_client/eval.py
Outdated
| sharder: Sharder | ||
|
|
||
| def evaluate_flag( | ||
| self, flag, subject_key, subject_attributes |
There was a problem hiding this comment.
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.
Nice quality of life improvement!
giorgiomartini0
left a comment
There was a problem hiding this comment.
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.
Nice quality of life improvement!
giorgiomartini0
left a comment
There was a problem hiding this comment.
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.
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.
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.
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.
Seems like the main thing we should consider adding is an "error" attribute to flag evaluation that we can set
| allocation_key=None, | ||
| variation=None, | ||
| extra_logging={}, | ||
| do_log=False, |
There was a problem hiding this comment.
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.
is extra_logging where the evaluation reasoning would work its way in?
There was a problem hiding this comment.
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.
@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.
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.
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.
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.
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.
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.
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.
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.
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): |
There was a problem hiding this comment.
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.
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(): |
There was a problem hiding this comment.
What's this? Do we have a max size concept elsewhere?
There was a problem hiding this comment.
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.
when does this situation happen?
There was a problem hiding this comment.
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?