Skip to content

feat(python-sdk): add api manager and event queue layer - #3

Merged
usmanabbas7 merged 1 commit into
mainfrom
codex/python-sdk-api-layer
Mar 26, 2026
Merged

feat(python-sdk): add api manager and event queue layer#3
usmanabbas7 merged 1 commit into
mainfrom
codex/python-sdk-api-layer

Conversation

@usmanabbas7

Copy link
Copy Markdown
Collaborator

Summary

This PR adds the API and event layer for the Python SDK on top of the deterministic core and in-memory evaluation managers built in earlier PRs. It ports the JS SDK’s sync config fetch and tracking queue behavior so the Python implementation can request project config from the CDN and prepare/send grouped visitor tracking payloads.

What’s Included

  • Added ApiManager for:

    • sync HTTP request handling
    • config fetch from /config/{sdkKey}
    • grouped visitor tracking payloads for /track/{sdkKey}
    • queue release on batch size
    • queue release on timeout
    • tracking enable/disable controls
    • updating account/project data after config load
  • Added EventManager for:

    • event subscription
    • deferred event delivery
    • listener cleanup
  • Added HTTP utility helpers for:

    • JSON request/response handling
    • structured HTTP response objects
    • structured HTTP errors
  • Added SystemEvents enum values needed by the queue/event flow

  • Exported the new API/event modules through the package entrypoint

Behavior Covered

This PR now supports:

  • fetching config data using an sdkKey
  • shaping request payloads consistently for config and tracking endpoints
  • accumulating tracking events per visitor
  • releasing queued events either:
    • when batch size is reached
    • when the release timeout expires
  • firing queue release events on both success and failure

Testing

Added unit coverage for:

  • raw API request payload handling
  • config fetch request shape
  • queue release by size
  • queue release by timeout
  • queue release failure event behavior
  • deferred event delivery in the event manager

Verification:

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

Out of Scope

This PR intentionally does not include:

  • ConvertSDK
  • Context
  • automatic SDK initialization flow
  • config refresh orchestration in the public SDK layer
  • conversion/bucketing event production from visitor context methods

Those should come in the next PR, where the public Python SDK surface is wired on top of the managers introduced so far.

@usmanabbas7
usmanabbas7 merged commit 3c90759 into main Mar 26, 2026
@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 introduces the API and event layers for the Python SDK, building upon the existing deterministic core and in-memory evaluation managers. It integrates functionalities such as synchronous configuration fetching from the CDN and managing grouped visitor tracking payloads, mirroring the behavior found in the JavaScript SDK. This enhancement significantly advances the SDK's capability to interact with external services and handle internal events.

Highlights

  • API Manager Implementation: Added ApiManager for handling synchronous HTTP requests, fetching configuration from the CDN, and managing grouped visitor tracking payloads with queue release mechanisms based on batch size or timeout.
  • Event Manager Introduction: Introduced EventManager to facilitate event subscription, deferred event delivery, and efficient cleanup of event listeners.
  • HTTP Utility Helpers: Implemented new HTTP utility functions for standardized JSON request/response handling, structured HTTP response objects, and robust HTTP error handling.
  • System Events Extension: Extended the SystemEvents enum with new values crucial for the proper functioning of the newly introduced queue and event flow.
  • Module Export: Exported the new ApiManager and EventManager modules through the main package entrypoint, making them accessible within the SDK.

🧠 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 new ApiManager and EventManager components to the SDK, along with a new http_client utility and SystemEvents enum. The ApiManager handles queuing and sending tracking events, and fetching configuration, while the EventManager provides a mechanism for event handling. Several improvements were suggested, including addressing potential runtime errors due to empty default API endpoints, ensuring the _sdk_key remains consistent after data updates, adhering to Python's snake_case convention for visitorId, refining type hints for event_manager, preventing a KeyError when accessing sdkKeySecret, and narrowing the generic Exception catch in release_queue to HttpError for more specific error handling.

Comment on lines +16 to +17
DEFAULT_CONFIG_ENDPOINT = os.getenv("CONFIG_ENDPOINT", "")
DEFAULT_TRACK_ENDPOINT = os.getenv("TRACK_ENDPOINT", "")

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 default values for DEFAULT_CONFIG_ENDPOINT and DEFAULT_TRACK_ENDPOINT are empty strings if the respective environment variables are not set. This can lead to request calls with an invalid base_url, which will likely result in runtime errors when attempting to make HTTP requests. It's safer to either raise an error during initialization if these critical endpoints are not configured or provide a more robust default that prevents silent failures.

Comment on lines +219 to +220
if not self._sdk_key:
self._sdk_key = self._build_default_sdk_key()

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 logic for rebuilding _sdk_key in set_data only triggers if _sdk_key is falsy. If _sdk_key was initially provided (and thus truthy) but _account_id or _project_id are subsequently changed via set_data, the _sdk_key might become stale if it was originally derived from these IDs. To ensure consistency, _sdk_key should be rebuilt unconditionally if _account_id or _project_id are updated, or if sdkKey is not explicitly provided in the initial config.

        self._sdk_key = config.get("sdkKey") or self._build_default_sdk_key()


@dataclass
class VisitorQueueItem:
visitorId: str

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 field visitorId uses camelCase which is inconsistent with Python's snake_case convention as recommended by PEP 8 for variable names. Please consider renaming it to visitor_id for better readability and adherence to style guidelines.

Suggested change
visitorId: str
visitor_id: str

self,
config: Mapping[str, Any] | None = None,
*,
event_manager: Any | None = 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

The type hint for event_manager is Any | None. For better type safety and clarity, it would be more precise to use EventManager | None since EventManager is a defined class within the SDK.

Suggested change
event_manager: Any | None = None,
event_manager: EventManager | None = None,

Comment on lines +87 to +89
self._default_headers["Authorization"] = (
f"Bearer {config['sdkKeySecret']}"
)

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

Accessing config['sdkKeySecret'] directly after checking config.get("sdkKeySecret") can lead to a KeyError if config.get("sdkKeySecret") returns a falsy value (e.g., 0 or False) but the key is not actually present. It's safer to store the result of config.get("sdkKeySecret") in a variable and use that variable.

Suggested change
self._default_headers["Authorization"] = (
f"Bearer {config['sdkKeySecret']}"
)
sdk_key_secret = config.get("sdkKeySecret")
if sdk_key_secret:
self._default_headers["Authorization"] = f"Bearer {sdk_key_secret}"

},
self._mapper(payload),
)
except Exception as error:

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

Catching a generic Exception can mask other programming errors and make debugging more difficult. It's generally better to catch more specific exceptions. In this context, HttpError (which is raised by _request_sender) would be a more appropriate exception to catch for network-related issues.

Suggested change
except Exception as error:
except HttpError as error:

usmanabbas7 added a commit that referenced this pull request Jun 14, 2026
…n round-trip — tests (verification)

Beads: ai-driven-product-dev-xuci
Verification task (Task 4): no production code change; proves FR25 determinism,
AC#5 overlay/persisted distinction, AC#2/#3 round-trip via qs-06 fixture.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
usmanabbas7 added a commit that referenced this pull request Jun 14, 2026
Extend the existing diagnostic surface (4-1 log_safe / 4-2 _Diagnostic.details)
so each diagnostic carries the partial cross-SDK-comparable field set: reason,
environment, bucket_value, variation_key, hashed visitor_ref (fingerprint_visitor).
Additive: optional environment kwarg on Context.__init__ wired from
Core.create_context. Frozen ExperienceResult shape and None-returning callers
preserved. Deferred AC-1 fields (config_version/bucketing_inputs/experience_key)
and AC#2/#3 honored (4.5/5.1).

Beads: ai-driven-product-dev-klm5

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
usmanabbas7 added a commit that referenced this pull request Jun 15, 2026
Extend the existing diagnostic surface (4-1 log_safe / 4-2 _Diagnostic.details)
so each diagnostic carries the partial cross-SDK-comparable field set: reason,
environment, bucket_value, variation_key, hashed visitor_ref (fingerprint_visitor).
Additive: optional environment kwarg on Context.__init__ wired from
Core.create_context. Frozen ExperienceResult shape and None-returning callers
preserved. Deferred AC-1 fields (config_version/bucketing_inputs/experience_key)
and AC#2/#3 honored (4.5/5.1).

Beads: ai-driven-product-dev-klm5

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@abbaseya
abbaseya deleted the codex/python-sdk-api-layer branch June 18, 2026 16:37
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