-
Notifications
You must be signed in to change notification settings - Fork 15
[ffe] add pyo3 conversion methods #1289
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
Conversation
| /// Accepts either a dict with `"targeting_key"` and `"attributes"` items, or any object with | ||
| /// `targeting_key` and `attributes` attributes. | ||
| /// | ||
| /// # Examples | ||
| /// | ||
| /// ```python | ||
| /// {"targeting_key": "user1", "attributes": {"attr1": 42}} | ||
| /// ``` | ||
| /// | ||
| /// ```python | ||
| /// @dataclass | ||
| /// class EvaluationContext: | ||
| /// targeting_key: Optional[str] | ||
| /// attributes: dict[str, Any] | ||
| /// | ||
| /// EvaluationContext(targeting_key="user1", attributes={"attr1": 42}) | ||
| /// ``` | ||
| impl<'py> FromPyObject<'py> for EvaluationContext { |
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.
@avara1986 for evaluation context, either a dict or an object with targeting_key/attributes is accepted. (We can technically pass OF's EvaluationContext if we filter out unsupported attributes.)
BenchmarksComparisonBenchmark execution time: 2025-11-04 05:29:50 Comparing candidate commit 6b94369 in PR branch Found 13 performance improvements and 4 performance regressions! Performance is the same for 38 metrics, 2 unstable metrics. scenario:benching serializing traces from their internal representation to msgpack
scenario:credit_card/is_card_number/37828224631000521389798
scenario:credit_card/is_card_number/x371413321323331
scenario:credit_card/is_card_number_no_luhn/ 378282246310005
scenario:credit_card/is_card_number_no_luhn/378282246310005
scenario:credit_card/is_card_number_no_luhn/37828224631000521389798
scenario:credit_card/is_card_number_no_luhn/x371413321323331
scenario:normalization/normalize_service/normalize_service/[empty string]
scenario:redis/obfuscate_redis_string
scenario:single_flag_killswitch/rules-based
CandidateCandidate benchmark detailsGroup 1
Group 2
Group 3
Group 4
Group 5
Group 6
Group 7
Group 8
Group 9
Group 10
Group 11
Group 12
Group 13
Group 14
Group 15
Group 16
Group 17
BaselineOmitted due to size. |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #1289 +/- ##
==========================================
- Coverage 71.61% 71.52% -0.09%
==========================================
Files 370 369 -1
Lines 58454 58530 +76
==========================================
+ Hits 41861 41866 +5
- Misses 16593 16664 +71
🚀 New features to boost your workflow:
|
Artifact Size Benchmark Reportaarch64-alpine-linux-musl
aarch64-unknown-linux-gnu
libdatadog-x64-windows
libdatadog-x86-windows
x86_64-alpine-linux-musl
x86_64-unknown-linux-gnu
|
|
Correct me if I’m wrong, but libdatadog is language-agnostic and wouldn’t require pyo3, right?the bindings could be added directly in ddtrace-py like: https://github.com/DataDog/dd-trace-py/blob/main/src/native/ffande.rs |
Yes, somewhat. The code here is not bindings per se but helper functions that can convert from Python to Rust and back. The reason for adding it here is to satisfy Rust's orphan rule—we technically cannot The code here is added under "pyo3" feature, so other consumers of the library don't need to pull python-related dependencies. The way to think about it is that pyo3 is not a required dependency but "datadog-ffe can play nicely with pyo3 if you enable this feature." |
leoromanovsky
left a comment
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.
considering add a sanity test round trip data
| /// - `bool` | ||
| /// - `NoneType` | ||
| /// | ||
| /// Note that nesting is not currently supported and will throw an error. |
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.
👍
| return Ok(Attribute(AttributeImpl::Number(s.value()))); | ||
| } | ||
| if let Ok(s) = value.downcast::<PyInt>() { | ||
| return Ok(Attribute(AttributeImpl::Number(s.extract::<f64>()?))); |
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.
i64 ?
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 really. We only support float attributes, so ints are squashed to floats here
| targeting_key: targeting_key | ||
| .map(|it| it.extract()) | ||
| .transpose()? | ||
| .unwrap_or_else(|| Str::from_static_str("").into()), |
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.
confirming empty strings are acceptable input
These will be defined in dd-trace-py to better match what Python/OF need.
This reverts commit b9c68ca. Test generation in build.rs made tests hard to edit.
a29bb79 to
8816cf4
Compare
This is required to allow tracers return more precise error codes.
8816cf4 to
572804b
Compare
leoromanovsky
left a comment
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.
API improvement makes sense
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.
The UniversalFlagConfigWire structure is not correct compared to the JSON object coming from Remote Config. Consider merging this branch (and please have an expert review it, it’s my first Rust commit):
https://github.com/DataDog/libdatadog/compare/ffe-pyo3-methods...avara1986/ffe-pyo3-methods?expand=1
|
@avara1986 thanks for finding this! it's not quite related to pyo3 changes here but I fixed that by dropping support for JSON:API |
| /// ffe-specific [`Error`] enum. | ||
| pub type Result<T> = std::result::Result<T, Error>; | ||
|
|
||
| /// Enum representing possible errors that can occur in the feature flagging 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.
Just an FYI: removing this shouldn't impact anything from the FFI's perspective. The FFI converts all errors to a generic ddog_Error type using a wrap_with_ffi_result method
Within wrap_with_ffi_result, there's a anyhow::Error → ddcommon_ffi::Error conversion that flattens all error details into a string message
libdatadog/ddcommon-ffi/src/error.rs
Lines 62 to 68 in 4828b75
| impl From<anyhow::Error> for Error { | |
| fn from(value: anyhow::Error) -> Self { | |
| // {:#} is the "alternate" format, see: | |
| // https://docs.rs/anyhow/latest/anyhow/struct.Error.html#display-representations | |
| Self::from(format!("{value:#}")) | |
| } | |
| } |
And so there's no distinction of Rust-specific error types once they get to C
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.
Well... you'll probably want to change that once you get to reporting reason, error_core, and error_message to OF. It's going to painful to figure these out from error strings (which are not guaranteed to be stable).
You may want to take a look at what we do in Python here: https://github.com/DataDog/dd-trace-py/pull/15098/files#diff-d2ca810369a5f55bf62d0810211bf0d40df1f74ede215ffc430486bb1cce7545. It's basically converting Result<Assignment, EvaluationFailure> to ResolutionDetails which is very similar to what OF expects + a couple of extra fields that we need.
It might even be a good idea to move ResolutionDetails into datadog-ffe, so we can share that code between python, ruby and other SDKs
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.
Thank you for referring me to that PR, particularly the methods you have to map the native errors to OF errors are helpful: _map_reason_to_openfeature and _map_error_code_to_openfeature. We'll update Ruby to do the same mapping.
Moving ResolutionDetails into datadog-ffe doesn't sound like a bad idea. If the OF specs for python, ruby, and other SDKs never drift, that would be ideal. But even if one of them diverges, I imagine we could just augment the code for that specific SDK
|
Unrelated failure from |
|
/merge |
|
View all feedbacks in Devflow UI.
The expected merge time in
This PR is rejected because it was updated |
|
/merge |
|
View all feedbacks in Devflow UI.
This pull request is not mergeable according to GitHub. Common reasons include pending required checks, missing approvals, or merge conflicts — but it could also be blocked by other repository rules or settings.
The expected merge time in
|
[ffe] add pyo3 conversion methods [ffe] Remove unused errors [ffe] step back and remove IntoPyObject types These will be defined in dd-trace-py to better match what Python/OF need. Revert "test(ffe): run sdk tests individually (#1273)" This reverts commit b9c68ca. Test generation in build.rs made tests hard to edit. [ffe] Expose EvaluationFailure This is required to allow tracers return more precise error codes. [ffe] remove support for JSON:API Merge remote-tracking branch 'origin/main' into HEAD [ffe] fix clippy Merge remote-tracking branch 'origin/main' into HEAD refactor(ffe): merge EvaluationError and EvalutionFailure into one type The distinction served us well in Eppo but it's less useful as we're migrating to OpenFeature and it handles these various errors rather similarly. Merge branch 'main' into ffe-pyo3-methods Co-authored-by: oleksii.shmalko <[email protected]>
## Description <!-- Provide an overview of the change and motivation for the change --> Integrate datadog-ffe from libdatadog: DataDog/libdatadog#1289 ## Testing <!-- Describe your testing strategy or note what tests are included --> Tested manually that native bindings work: ``` >>> from ddtrace.internal.native._native import ffe >>> with open("/Users/oleksii.shmalko/dd/libdatadog/datadog-ffe/tests/data/flags-v1.json", 'rb') as f: ... json = f.read() ... >>> config = ffe.Configuration(json) >>> details = config.resolve_value("numeric_flag", ffe.FlagType.Float, {}) >>> details.variant 'pi' >>> details.value 3.1415926 ``` More testing will be done as part of FFE feature. ## Risks <!-- Note any risks associated with this change, or "None" if no risks --> ## Additional Notes <!-- Any other information that would be helpful for reviewers --> This PR is stacked against #15219 --------- Co-authored-by: Alberto Vara <[email protected]>
Reopening #15098 as it was accidentally merged too early. ## Description <!-- Provide an overview of the change and motivation for the change --> Integrate datadog-ffe from libdatadog: DataDog/libdatadog#1289 ## Testing <!-- Describe your testing strategy or note what tests are included --> Tested manually that native bindings work: ``` >>> from ddtrace.internal.native._native import ffe >>> with open("/Users/oleksii.shmalko/dd/libdatadog/datadog-ffe/tests/data/flags-v1.json", 'rb') as f: ... json = f.read() ... >>> config = ffe.Configuration(json) >>> details = config.resolve_value("numeric_flag", ffe.FlagType.Float, {}) >>> details.variant 'pi' >>> details.value 3.1415926 ``` More testing will be done as part of FFE feature. ## Risks <!-- Note any risks associated with this change, or "None" if no risks --> ## Additional Notes <!-- Any other information that would be helpful for reviewers --> This PR is stacked against #15219 --------- Co-authored-by: Alberto Vara <[email protected]>
What does this PR do?
Add pyo3 conversion methods for
datadog-ffe.Motivation
This will allow our Python tracer to easily call into
datadog-ffewith Rust automatically doing conversion from Python to Rust types (and vice versa)How to test the change?
Will be tested as part of ddtrace.