Skip to content

Use:feat(python-sdk): add core, context, and public sdk surface - #4

Merged
usmanabbas7 merged 1 commit into
mainfrom
codex/python-sdk-core-context
Mar 30, 2026
Merged

Use:feat(python-sdk): add core, context, and public sdk surface#4
usmanabbas7 merged 1 commit into
mainfrom
codex/python-sdk-core-context

Conversation

@usmanabbas7

Copy link
Copy Markdown
Collaborator

This PR adds the public Python SDK surface by wiring the previously ported managers into ConvertSDK, Core, and Context. With this PR, the Python SDK can now initialize from static config or sdkKey, create visitor contexts, evaluate experiences/features, run custom segments, track conversions, and expose config/visitor helper methods.

What’s Included

  • Added ConvertSDK, Core, and Context
  • Added config/default assembly for Python SDK initialization
  • Added public context methods for:
    • run_experience
    • run_experiences
    • run_feature
    • run_features
    • track_conversion
    • set_default_segments
    • run_custom_segments
    • update_visitor_properties
    • get_config_entity
    • get_config_entity_by_id
    • get_visitor_data
    • release_queues
  • Added JS-style compatibility aliases such as:
    • createContext
    • runExperience
    • runFeatures
    • trackConversion
    • onReady
    • refreshConfig
  • Extended DataManager to support:
    • runtime config data updates
    • bucketing event enqueueing
    • conversion event enqueueing
  • Added EntityType and supporting error/config exports

Behavior Added

This PR now enables:

  • SDK initialization from static config
  • SDK initialization from sdkKey via sync config fetch
  • creation of visitor-specific evaluation contexts
  • bucketing and feature evaluation through the public API
  • conversion tracking through the public API
  • config entity lookup by key and id
  • visitor data access through context methods
  • queue release from the context layer

Testing

Added public-surface tests covering:

  • static-config initialization
  • sdkKey config fetch initialization
  • context creation
  • experience and feature evaluation through context
  • conversion tracking through context
  • config entity lookup helpers
  • visitor data helpers

Verification:

  • .venv/bin/pytest -q
  • 69 passed

Out of Scope

This PR does not yet cover broader production hardening work such as:

  • background config refresh scheduling
  • advanced datastore queue behavior
  • long-running process cache policies
  • packaging/docs/examples polish

Those should come in the next follow-up PR.

@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly advances the Python SDK by integrating core managers into a new public API surface, comprising ConvertSDK, Core, and Context classes. It enables fundamental SDK operations such as initializing the SDK from various sources, managing visitor contexts, evaluating A/B tests and features, tracking user conversions, and accessing configuration entities. The changes lay the groundwork for a fully functional Python SDK, allowing developers to interact with the Convert.com platform programmatically.

Highlights

  • Public Python SDK Surface: Introduced ConvertSDK, Core, and Context classes to establish the public Python SDK surface, wiring previously ported managers.
  • SDK Initialization and Configuration: Enabled SDK initialization using either static configuration or an sdkKey for dynamic config fetching, including a new config.py module for default settings.
  • Visitor Context Management: Implemented comprehensive visitor context management within the Context class, allowing evaluation of experiences and features, custom segment execution, and conversion tracking.
  • API Aliases and Data Management Enhancements: Added JavaScript-style compatibility aliases for key public methods and extended DataManager to handle runtime configuration updates and event enqueueing for bucketing and conversions.
  • New Enums and Error Handling: Introduced EntityType enum for categorizing configuration entities and added new error definitions to support the SDK's functionality.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new, more structured SDK architecture by adding Core, Context, and ConvertSDK classes, along with configuration management, new entity types, and improved event and data handling for bucketing and conversions. The review identified several areas for improvement: the update_visitor_properties method in Context has a potentially confusing visitor_id parameter, there are opportunities to optimize list comprehensions in run_feature and run_features for efficiency and readability, and the lookup for EntityType.VARIATION in get_config_entity could be made more performant. Additionally, the on_ready method in Core should be renamed for clarity, and a typo in the VISITOR_ID_REQUIRED error message needs correction.

Comment on lines +228 to +229
def update_visitor_properties(self, visitor_id: str, visitor_properties: Mapping[str, Any]) -> None:
self._data_manager.put_data(visitor_id, {"segments": dict(visitor_properties)})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The update_visitor_properties method on the Context class accepts a visitor_id. This is confusing and potentially error-prone, as a Context instance is already associated with a specific visitor (self._visitor_id). This design could lead to unintentionally modifying data for a different visitor from within a specific visitor's context.

To improve clarity and prevent misuse, this method should operate on the visitor associated with the current context. I recommend removing the visitor_id parameter and using self._visitor_id instead. The aliased method updateVisitorProperties should also be updated to reflect this change in signature.

Suggested change
def update_visitor_properties(self, visitor_id: str, visitor_properties: Mapping[str, Any]) -> None:
self._data_manager.put_data(visitor_id, {"segments": dict(visitor_properties)})
def update_visitor_properties(self, visitor_properties: Mapping[str, Any]) -> None:
self._data_manager.put_data(self._visitor_id, {"segments": dict(visitor_properties)})

Comment on lines +135 to +146
for item in [feature for feature in items if isinstance(feature, Mapping)]:
self._event_manager.fire(
SystemEvents.BUCKETING,
{
"visitorId": self._visitor_id,
"experienceKey": item.get("experienceKey"),
"featureKey": key,
"status": item.get("status"),
},
None,
True,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The list comprehension [feature for feature in items if isinstance(feature, Mapping)] creates a new temporary list just for iteration. This is slightly inefficient and can be made more readable by using a simple for loop with a check inside.

Suggested change
for item in [feature for feature in items if isinstance(feature, Mapping)]:
self._event_manager.fire(
SystemEvents.BUCKETING,
{
"visitorId": self._visitor_id,
"experienceKey": item.get("experienceKey"),
"featureKey": key,
"status": item.get("status"),
},
None,
True,
)
for item in items:
if isinstance(item, Mapping):
self._event_manager.fire(
SystemEvents.BUCKETING,
{
"visitorId": self._visitor_id,
"experienceKey": item.get("experienceKey"),
"featureKey": key,
"status": item.get("status"),
},
None,
True,
)

Comment on lines +159 to +170
for item in [feature for feature in bucketed_features if isinstance(feature, Mapping)]:
self._event_manager.fire(
SystemEvents.BUCKETING,
{
"visitorId": self._visitor_id,
"experienceKey": item.get("experienceKey"),
"featureKey": item.get("key"),
"status": item.get("status"),
},
None,
True,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Similar to run_feature, the list comprehension here creates an unnecessary temporary list. A simple for loop with a check would be more efficient and readable.

Suggested change
for item in [feature for feature in bucketed_features if isinstance(feature, Mapping)]:
self._event_manager.fire(
SystemEvents.BUCKETING,
{
"visitorId": self._visitor_id,
"experienceKey": item.get("experienceKey"),
"featureKey": item.get("key"),
"status": item.get("status"),
},
None,
True,
)
for item in bucketed_features:
if isinstance(item, Mapping):
self._event_manager.fire(
SystemEvents.BUCKETING,
{
"visitorId": self._visitor_id,
"experienceKey": item.get("experienceKey"),
"featureKey": item.get("key"),
"status": item.get("status"),
},
None,
True,
)

Comment on lines +236 to +248
if entity_type == EntityType.VARIATION:
for experience in self._data_manager.get_entities_list("experiences"):
variation = self._data_manager.get_sub_item(
"experiences",
experience.get("key"),
"variations",
key,
"key",
"key",
)
if variation:
return variation
return None

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

When entity_type is EntityType.VARIATION, this method iterates through all experiences to find a matching variation. This can be inefficient if there are many experiences, leading to O(N) complexity where N is the number of experiences.

For better performance, consider optimizing the data structure within DataManager to allow for a more direct lookup of variations, for example by pre-processing the config data to create a map of variation keys/IDs to their parent experience.

Comment on lines +111 to +114
def on_ready(self) -> None:
if self._data_manager.data:
return None
raise ValueError(DATA_OBJECT_MISSING)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The method on_ready is named in a way that suggests it's for registering a callback, similar to on(). However, its implementation is a synchronous check that raises an exception if the SDK is not ready. This can be misleading for developers, especially given the goal of JS-style compatibility where onReady often implies an asynchronous operation or callback registration.

Consider renaming this method to better reflect its behavior, for example assert_ready() or check_ready().

RULE_DATA_NOT_VALID = "Rule data is not valid"
SDK_OR_DATA_OBJECT_REQUIRED = "SDK key or Data object should be provided"
DATA_OBJECT_MISSING = "Data object is missing"
VISITOR_ID_REQUIRED = "Visitor string string is not present"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

There's a typo in the error message for VISITOR_ID_REQUIRED. It says "Visitor string string is not present".

Suggested change
VISITOR_ID_REQUIRED = "Visitor string string is not present"
VISITOR_ID_REQUIRED = "Visitor ID string is not present"

@usmanabbas7
usmanabbas7 merged commit 6b7e30b into main Mar 30, 2026
@abbaseya
abbaseya deleted the codex/python-sdk-core-context branch June 18, 2026 16:37
abbaseya added a commit that referenced this pull request Aug 8, 2026
All eight open Dependabot alerts on this repo sit in `yarn.lock` — the
dev-only semantic-release tooling. None of them reach the published
wheel/sdist (httpx is still the only runtime dependency).

Three of the four highs were already reachable inside the existing semver
ranges; the fourth (sigstore GHSA-52v5-jr5w-gjxr, `certificateOIDs`
verification constraints silently dropped) needed sigstore >= 4.1.1, which
only arrives through a major bump:

  semantic-release 24.2.9 -> 25.0.9
    @semantic-release/npm 12.0.2 -> 13.1.5
      npm 10.9.8 -> 11.19.0
        libnpmpublish 10 -> 11.2.0  }-> sigstore ^3 -> ^4  (4.1.1)
        pacote        19 -> 21.5.1  }   @sigstore/core 2.0.0 -> 3.2.1
        tar 7.5.16 -> 7.5.22

`@semantic-release/github` moves to ^12 to match what semantic-release 25
depends on — leaving it at ^11 would hoist the older copy to the project
root and shadow the one core resolves.

Resolved (4 high, 4 medium — no criticals were open):

  #14 high    ip-address       10.2.0 -> 10.4.0   (needs >= 10.3.1)
  #9  high    brace-expansion  2.1.1  -> 5.0.9    (needs >= 2.1.2)
  #7  high    js-yaml          4.2.0  -> 4.3.1    (needs >= 4.3.0)
  #2  high    sigstore         3.1.0  -> 4.1.1    (needs >= 4.1.1)
  #13 medium  ip-address       (same bump as #14)
  #12 medium  ip-address       (same bump as #14)
  #4  medium  tar              7.5.16 -> 7.5.22   (needs >= 7.5.18)
  #1  medium  @sigstore/core   2.0.0  -> 3.2.1    (needs >= 3.2.1)

Node: semantic-release 25 requires ^22.14.0 || >= 24.10.0. release.yml
installs `lts/*`, currently Node 24.19.0 — satisfied, and every future LTS
line stays above the floor.

Verified: `yarn install --immutable` (what release.yml runs) passes against
the regenerated lockfile with the lockfile format unchanged (__metadata
version 10), and `yarn npm audit --all --recursive` reports no suggestions.
A `semantic-release --dry-run` against this branch loads all four configured
plugins, passes verifyConditions for both exec and github (GitHub
authentication + push permission), and analyzes commits to "no release" —
correct, since `chore` is a non-releasing type in release.config.mjs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant