Skip to content
This repository has been archived by the owner on Nov 8, 2024. It is now read-only.

FF-3150 feat: allow passing initial_configuration #70

Merged
merged 8 commits into from
Sep 5, 2024
6 changes: 6 additions & 0 deletions eppo_client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ def init(config: Config) -> EppoClient:
http_client = HttpClient(base_url=config.base_url, sdk_params=sdk_params)
flag_config_store: ConfigurationStore[Flag] = ConfigurationStore()
bandit_config_store: ConfigurationStore[BanditData] = ConfigurationStore()

if config.initial_configuration:
flag_config_store.set_configurations(
config.initial_configuration._flags_configuration.flags
)

config_requestor = ExperimentConfigurationRequestor(
http_client=http_client,
flag_config_store=flag_config_store,
Expand Down
3 changes: 3 additions & 0 deletions eppo_client/config.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from pydantic import Field, ConfigDict
from typing import Optional

from eppo_client.assignment_logger import AssignmentLogger
from eppo_client.base_model import SdkBaseModel
from eppo_client.configuration import Configuration
from eppo_client.validation import validate_not_blank
from eppo_client.constants import (
POLL_INTERVAL_SECONDS_DEFAULT,
Expand All @@ -21,6 +23,7 @@ class Config(SdkBaseModel):
is_graceful_mode: bool = True
poll_interval_seconds: int = POLL_INTERVAL_SECONDS_DEFAULT
poll_jitter_seconds: int = POLL_JITTER_SECONDS_DEFAULT
initial_configuration: Optional[Configuration] = None

def _validate(self):
validate_not_blank("api_key", self.api_key)
11 changes: 11 additions & 0 deletions eppo_client/configuration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from eppo_client.models import UfcResponse


class Configuration:
"""
Client configuration fetched from the backend that dictates how to
interpret feature flags.
"""

def __init__(self, flags_configuration: str):
self._flags_configuration = UfcResponse.model_validate_json(flags_configuration)
4 changes: 4 additions & 0 deletions eppo_client/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ class Flag(SdkBaseModel):
total_shards: int = 10_000


class UfcResponse(SdkBaseModel):
flags: Dict[str, Flag]


class BanditVariation(SdkBaseModel):
key: str
flag_key: str
Expand Down
18 changes: 18 additions & 0 deletions test/configuration_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import pytest
import pydantic

from eppo_client.configuration import Configuration


def test_init_valid():
Configuration(flags_configuration='{"flags": {}}')


def test_init_invalid_json():
with pytest.raises(pydantic.ValidationError):
Configuration(flags_configuration="")


def test_init_invalid_format():
with pytest.raises(pydantic.ValidationError):
Configuration(flags_configuration='{"flags": []}')
27 changes: 27 additions & 0 deletions test/initial_configuration_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import eppo_client
from eppo_client.config import Config
from eppo_client.configuration import Configuration
from eppo_client.assignment_logger import AssignmentLogger


def test_without_initial_configuration():
client = eppo_client.init(
Config(
api_key="test",
base_url="http://localhost:8378/api",
assignment_logger=AssignmentLogger(),
)
)
assert not client.is_initialized()


def test_with_initial_configuration():
client = eppo_client.init(
Config(
api_key="test",
base_url="http://localhost:8378/api",
assignment_logger=AssignmentLogger(),
initial_configuration=Configuration(flags_configuration='{"flags":{}}'),
)
)
assert client.is_initialized()
Copy link
Member

Choose a reason for hiding this comment

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

nice - elegant.

Loading