-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
FF-2945 feat: Python SDK #29
Conversation
readme = "README.md" | ||
authors = [{ name = "Eppo", email = "[email protected]" }] | ||
license = { file = "LICENSE" } | ||
requires-python = ">=3.8" |
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.
☝️ bumping required python version from 3.6 to 3.8
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.
Sounds good, given 3.6 hit end of life two years ago 😬
@@ -0,0 +1,3 @@ | |||
[pytest] | |||
markers = | |||
rust_only: mark a test as only passing on Python-on-Rust SDK. |
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.
☝️ All tests without this marker pass on both python-on-rust and native python implementations
To run tests against old SDK: 1. Create and activate a virtual for the old SDK. 2. Install old SDK with `pip install -e .` 3. Install pytest 4. Run pytest in this directory with `-k 'not rust_only'`
The one file was growing too big. Split it into multiple files for easier maintenance.
b135f45
to
ce8428b
Compare
…_initialization()
- add a common util module - move tests outside of python source (this is recommended by pytest to avoid confusion)
This is to free up space for Configuration type that holds UFC flags and bandits configuration.
4d098a2
to
61d7ebe
Compare
88f4b6d
to
7f5f777
Compare
7f5f777
to
9ad5e54
Compare
- allow setting initial configuration - allow disabling polling
Negative values are already forbidden.
36d6332
to
4510639
Compare
Just a copy from the native SDK.
4510639
to
92941b4
Compare
Breaking changes: - ConfigurationStore::set_configuration() now accepts Arc<Configuration> - Fixed default poll interval to 30 (from 5 minutes)
This helps to avoid breakage in benchmarks and examples.
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.
💪 💪 💪 Awesome stuff! 📈
This is really exciting. I'm not approving because I don't know Python well and Rust at all. Although this makes me want to learn the latter 🤓
Thanks to helpful comments and method/variable names, I think I was able to decently follow. 🙏 Left a bunch of minor comments you can take or leave as you see fit!
@@ -0,0 +1,280 @@ | |||
# This file is autogenerated by maturin v1.7.0 |
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.
👀 maturin
looks pretty sweet!
if let Ok(s) = value.downcast::<PyString>() { | ||
return Ok(AttributeValue::String(s.extract()?)); | ||
} | ||
// In Python, Bool inherits from Int, so it must be checked first here. |
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.
Appreciate the helpful comments
@@ -28,7 +28,7 @@ pub struct PollerThreadConfig { | |||
|
|||
impl PollerThreadConfig { | |||
/// Default value for [`PollerThreadConfig::interval`]. | |||
pub const DEFAULT_POLL_INTERVAL: Duration = Duration::from_secs(5 * 60); | |||
pub const DEFAULT_POLL_INTERVAL: Duration = Duration::from_secs(30); |
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.
👍 Thanks for conforming to our SDK standard of default 30 second polling
@@ -0,0 +1,48 @@ | |||
// This is a prepare script that is automatically run before "start" command. |
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 the change to JS and requirement of node?
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 to make it cross-platform and easier to run in CI. Alpine linux doesn't have bash
by default (it has sh
) and Windows doesn't have any posix shell by default
docs/_build/ | ||
|
||
# PyCharm | ||
.idea/ |
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.
💪
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.
hehe, I don't actually use intellij products. this was auto-generated
), | ||
default: str, | ||
) -> EvaluationResult: ... | ||
def get_bandit_action_details( |
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.
🤯
Dict[str, ContextAttributes] | ||
| Dict[str, Dict[str, Union[str, int, float, bool, None]]] |
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.
For later, we should also support contextless actions. I know we try to steer customers away from this, but I've seen this use case come up on many calls.
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.
Do you mean supporting actions: Dict[str, None]
or list[str]
? We should be able to add this in the future in a backward-compatible way
subject_attributes: Attributes, | ||
default: Py<PyString>, | ||
) -> PyResult<EvaluationResult> { | ||
slf.get().get_assignment_details( |
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 it make sense for the underlying rust function to have details passed in as boolean like pass in type? Then one less "public" function upstream SDKs need to worry about.
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.
Do you mean a type-level parameter like this?
get_assignment::<WithDetails>(...)
It's possible but is a bit convoluted to implement (requires setting up traits and proxying implementation through trait), so isn't worth it for internal function
PySet::new_bound( | ||
py, | ||
self.configuration | ||
.bandits |
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.
minor but we're deprecating the array-valued bandits
in favor of object-valued banditReferences
. Both have a top-level object though whose keys are the bandit keys, so should be an easy swap.
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.
Sounds good. I shall update the core later. (.bandits
here is the whole /bandits
response. "bandits" field in config response is handled deeper in the core.)
assert result.to_string() == "action" | ||
|
||
|
||
# Native python implementation does not convert variation to string. |
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.
Interesting.
But yeah in the Eppo application UI, we only allow creating string-valued bandit 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.
4.0 extends BanditResult
to EvaluationResult
so different-typed variations are allowed. BanditResult
is now an alias to new EvaluationResult
Something like this:
# old BanditResult:
class BanditResult:
variation: str
action: str | None
# new EvaluationResult:
class EvaluationResult:
variation: Any
action: str | None
evaluation_details: Any | None
I'm able to install with
❌
The existing sample program I have is:
Are adjustments needed to it or to |
@leoromanovsky that's interesting. The import should work without any changes. Could you give me the output of the following:
also, does it work with non-editable install ( |
✅ got it working! |
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.
Ran it in a test python server and it operated as expected.
Really appreciate all the comments you added and explaining your rationale for decisions.
Going to merge this, so next PRs take python into account. Will coordinate with you to release it later |
This PR is ready for review.
It's probably best to start review with the announcement to understand main changes: Eppo-exp/eppo-docs#454