Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
481 changes: 481 additions & 0 deletions scripts/generate_parity_fixtures.py

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions src/convert_sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
TrackingError,
)
from .events import LifecycleEvent, LifecycleEventPayload
from .adapters.storage import InMemoryDataStore
from .ports.storage import DataStore
from .version import __version__

__all__ = [
Expand All @@ -30,10 +32,12 @@
"Core",
"ConversionEvent",
"ConversionResult",
"DataStore",
"ExperienceResult",
"FeatureResult",
"FeatureStatus",
"GoalNotFoundError",
"InMemoryDataStore",
"InitializationError",
"LifecycleEvent",
"LifecycleEventPayload",
Expand Down
5 changes: 5 additions & 0 deletions src/convert_sdk/adapters/storage/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""Storage adapter implementations."""

from .in_memory import InMemoryDataStore

__all__ = ["InMemoryDataStore"]
33 changes: 33 additions & 0 deletions src/convert_sdk/adapters/storage/in_memory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""Default in-memory persistence adapter for visitor-linked SDK state."""

from __future__ import annotations

from threading import Lock

from ...domain.context_state import ContextState
from ...ports.storage import DataStore


class InMemoryDataStore(DataStore):
"""Thread-safe process-local visitor state store for MVP behavior."""

def __init__(self) -> None:
self._context_states: dict[str, ContextState] = {}
self._tracked_goals: set[tuple[str, str]] = set()
self._lock = Lock()

def load_context_state(self, visitor_id: str) -> ContextState | None:
with self._lock:
return self._context_states.get(visitor_id)

def save_context_state(self, state: ContextState) -> None:
with self._lock:
self._context_states[state.visitor_id] = state

def has_tracked_goal(self, visitor_id: str, goal_id: str) -> bool:
with self._lock:
return (visitor_id, goal_id) in self._tracked_goals

def mark_tracked_goal(self, visitor_id: str, goal_id: str) -> None:
with self._lock:
self._tracked_goals.add((visitor_id, goal_id))
35 changes: 33 additions & 2 deletions src/convert_sdk/config_loader/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,37 @@

from __future__ import annotations

import logging

from .normalizer import build_snapshot
from .validators import validate_config_data, validate_sdk_config
from ..config import SDKConfig
from ..diagnostics import config_source, log_diagnostic_event, snapshot_entity_counts
from ..domain.config_snapshot import ConfigSnapshot
from ..errors import ConfigLoadError
from ..ports.transport import ConfigRequest, Transport


def load_config_snapshot(config: SDKConfig, transport: Transport) -> ConfigSnapshot:
source = config_source(config.config_data, config.sdk_key)
log_diagnostic_event(
"config.load.started",
source=source,
has_environment=bool(config.environment),
)
validate_sdk_config(config)

if config.config_data is not None:
validate_config_data(config.config_data)
return build_snapshot(config.config_data)
snapshot = build_snapshot(config.config_data)
log_diagnostic_event(
"config.load.succeeded",
source=source,
has_account_id=snapshot.account_id is not None,
has_project_id=snapshot.project_id is not None,
entity_counts=snapshot_entity_counts(snapshot),
)
return snapshot

request = ConfigRequest(
sdk_key=config.sdk_key or "",
Expand All @@ -27,9 +44,23 @@ def load_config_snapshot(config: SDKConfig, transport: Transport) -> ConfigSnaps
try:
config_data = transport.fetch_config(request)
except Exception as exc: # noqa: BLE001
log_diagnostic_event(
"config.load.failed",
level=logging.WARNING,
source=source,
error_type=type(exc).__name__,
)
raise ConfigLoadError(
f"Config fetch failed for sdk_key '{config.sdk_key}'"
) from exc

validate_config_data(config_data)
return build_snapshot(config_data)
snapshot = build_snapshot(config_data)
log_diagnostic_event(
"config.load.succeeded",
source=source,
has_account_id=snapshot.account_id is not None,
has_project_id=snapshot.project_id is not None,
entity_counts=snapshot_entity_counts(snapshot),
)
return snapshot
Loading