From 041a8a8336655639cdc42a69acd59f25a4a813c0 Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Mon, 16 Dec 2024 19:50:54 -0800 Subject: [PATCH 01/13] add build-and-run.sh to python relay server --- package-testing/python-sdk-relay/README.md | 6 +++ .../python-sdk-relay/build-and-run.sh | 43 +++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100755 package-testing/python-sdk-relay/build-and-run.sh diff --git a/package-testing/python-sdk-relay/README.md b/package-testing/python-sdk-relay/README.md index 5c56e290..536d7e6f 100644 --- a/package-testing/python-sdk-relay/README.md +++ b/package-testing/python-sdk-relay/README.md @@ -2,6 +2,12 @@ Post test case files to this server and check the results against what's expected. +## Build and run + +```shell +./build-and-run.sh +``` + ## Running locally with Docker Build the docker image: diff --git a/package-testing/python-sdk-relay/build-and-run.sh b/package-testing/python-sdk-relay/build-and-run.sh new file mode 100755 index 00000000..e387a1b6 --- /dev/null +++ b/package-testing/python-sdk-relay/build-and-run.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash + +# Set default values for vars + +: "${SDK_REF:=main}" +: "${SDK_RELAY_HOST:=localhost}" +: "${SDK_RELAY_PORT:=4000}" +SDK="https://github.com/Eppo-exp/eppo-multiplatform.git" + +# checkout the specified ref of the SDK repo, build it, and then insert it into vendors here. +rm -rf tmp +mkdir -p tmp + +echo "Cloning ${SDK}@${SDK_REF}" +git clone -b ${SDK_REF} --depth 1 --single-branch ${SDK} tmp || ( echo "Cloning repo failed"; exit 1 ) + +# Run the poller +python3 -m venv tmp/.venv +source tmp/.venv/bin/activate +pip install maturin + +# Build the wheel file in tmp directory +maturin build --release --out tmp/dist --find-interpreter --manifest-path ./tmp/python-sdk/Cargo.toml + +# Get Python version and find matching wheel +PYTHON_VERSION=$(python3 -c 'import sys; print(f"cp{sys.version_info.major}{sys.version_info.minor}")') +echo "Looking for wheel for Python version: ${PYTHON_VERSION}" + +WHEEL_FILE=$(find tmp/dist -name "eppo_server_sdk-*-${PYTHON_VERSION}-*.whl" | head -n 1) +echo "Found wheel file: ${WHEEL_FILE}" + +if [ -z "$WHEEL_FILE" ]; then + echo "Error: Wheel file not found for Python version ${PYTHON_VERSION}" + echo "Available wheels:" + ls tmp/dist + exit 1 +fi + +pip install "${WHEEL_FILE}" + +echo "Listening on port ${SDK_RELAY_PORT}" + +python3 -m http.server ${SDK_RELAY_PORT} From f1537473971c765eb7c6b38bf1867be9d60eba25 Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Mon, 16 Dec 2024 20:03:07 -0800 Subject: [PATCH 02/13] refactor dockerfile to build wheel --- package-testing/python-sdk-relay/Dockerfile | 12 +++++++++++- package-testing/python-sdk-relay/requirements.txt | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/package-testing/python-sdk-relay/Dockerfile b/package-testing/python-sdk-relay/Dockerfile index 183d3ab7..2256da48 100644 --- a/package-testing/python-sdk-relay/Dockerfile +++ b/package-testing/python-sdk-relay/Dockerfile @@ -1,5 +1,9 @@ FROM python:3.12 +# Install Cargo and Rust dependencies +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y +ENV PATH="/root/.cargo/bin:${PATH}" + WORKDIR /app COPY requirements.txt . @@ -8,4 +12,10 @@ RUN pip install -r requirements.txt # Copy the source code COPY src/ ./src/ -CMD ["python", "/app/src/server.py"] +COPY --chmod=755 build-and-run.sh / + +ENV SDK_RELAY_HOST=0.0.0.0 + +EXPOSE $SDK_RELAY_PORT + +CMD ["/build-and-run.sh"] diff --git a/package-testing/python-sdk-relay/requirements.txt b/package-testing/python-sdk-relay/requirements.txt index 98476c97..9c2583ce 100644 --- a/package-testing/python-sdk-relay/requirements.txt +++ b/package-testing/python-sdk-relay/requirements.txt @@ -1,2 +1,2 @@ flask -eppo-server-sdk==4.1.0 +# eppo-server-sdk installed from local wheel From 8c0016a1a671d2e0eeed31c30ad3d365cc71f577 Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Mon, 16 Dec 2024 20:15:38 -0800 Subject: [PATCH 03/13] requirements --- package-testing/python-sdk-relay/Dockerfile | 2 ++ package-testing/python-sdk-relay/build-and-run.sh | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/package-testing/python-sdk-relay/Dockerfile b/package-testing/python-sdk-relay/Dockerfile index 2256da48..4c900f11 100644 --- a/package-testing/python-sdk-relay/Dockerfile +++ b/package-testing/python-sdk-relay/Dockerfile @@ -18,4 +18,6 @@ ENV SDK_RELAY_HOST=0.0.0.0 EXPOSE $SDK_RELAY_PORT +HEALTHCHECK CMD curl --fail http://localhost:${SDK_RELAY_PORT} || exit 1 + CMD ["/build-and-run.sh"] diff --git a/package-testing/python-sdk-relay/build-and-run.sh b/package-testing/python-sdk-relay/build-and-run.sh index e387a1b6..7c4d816a 100755 --- a/package-testing/python-sdk-relay/build-and-run.sh +++ b/package-testing/python-sdk-relay/build-and-run.sh @@ -19,6 +19,8 @@ python3 -m venv tmp/.venv source tmp/.venv/bin/activate pip install maturin +pip install -r requirements.txt + # Build the wheel file in tmp directory maturin build --release --out tmp/dist --find-interpreter --manifest-path ./tmp/python-sdk/Cargo.toml @@ -40,4 +42,4 @@ pip install "${WHEEL_FILE}" echo "Listening on port ${SDK_RELAY_PORT}" -python3 -m http.server ${SDK_RELAY_PORT} +python3 src/server.py From b1a6b9d08656b3e351c0c78f12b8066ffa19e595 Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Mon, 16 Dec 2024 20:21:53 -0800 Subject: [PATCH 04/13] SDK_RELAY_HOST --- package-testing/python-sdk-relay/build-and-run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package-testing/python-sdk-relay/build-and-run.sh b/package-testing/python-sdk-relay/build-and-run.sh index 7c4d816a..f9a7e645 100755 --- a/package-testing/python-sdk-relay/build-and-run.sh +++ b/package-testing/python-sdk-relay/build-and-run.sh @@ -40,6 +40,6 @@ fi pip install "${WHEEL_FILE}" -echo "Listening on port ${SDK_RELAY_PORT}" +echo "Listening on port ${SDK_RELAY_HOST}:${SDK_RELAY_PORT}" python3 src/server.py From 0b49c0ba23cd02d966b54512cc2e1e8bdd47fec1 Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Mon, 16 Dec 2024 20:28:17 -0800 Subject: [PATCH 05/13] hardcode to 0.0.0.0 --- package-testing/python-sdk-relay/src/server.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package-testing/python-sdk-relay/src/server.py b/package-testing/python-sdk-relay/src/server.py index 4f1aa887..b383cb7d 100644 --- a/package-testing/python-sdk-relay/src/server.py +++ b/package-testing/python-sdk-relay/src/server.py @@ -160,7 +160,8 @@ def initialize_client_and_wait(): initialize_client_and_wait() port = int(environ.get('SDK_RELAY_PORT', 7001)) - host = environ.get('SDK_RELAY_HOST', '0.0.0.0') + #host = environ.get('SDK_RELAY_HOST', '0.0.0.0') + host = '0.0.0.0' print(f"Starting server on {host}:{port}") app.run( host=host, From 4b2cae33f73f4be5074a8df0383a471226bce8e7 Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Mon, 16 Dec 2024 20:45:04 -0800 Subject: [PATCH 06/13] enable bandit test in python --- .../python-sdk-relay/src/server.py | 56 +++++++++++++++---- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/package-testing/python-sdk-relay/src/server.py b/package-testing/python-sdk-relay/src/server.py index b383cb7d..3ceb0ec1 100644 --- a/package-testing/python-sdk-relay/src/server.py +++ b/package-testing/python-sdk-relay/src/server.py @@ -38,7 +38,7 @@ def get_sdk_details(): return jsonify({ "sdkName": "python-sdk", "sdkVersion": "4.1.0", - "supportsBandits": False, + "supportsBandits": True, "supportsDynamicTyping": False }) @@ -116,8 +116,8 @@ def handle_assignment(): class BanditActionRequest: flag: str subject_key: str - subject_attributes: dict - actions: list + subject_attributes: dict # Will contain numericAttributes and categoricalAttributes + actions: list # List of dicts with actionKey, numericAttributes, categoricalAttributes default_value: any @@ -133,13 +133,49 @@ def handle_bandit(): ) print(f"Request object: {request_obj}") - # TODO: Implement bandit logic - return jsonify({ - "result": "action", - "assignmentLog": [], - "banditLog": [], - "error": None - }) + client = eppo_client.get_instance() + + try: + # Convert actions to the format expected by the SDK + actions = {} + for action in request_obj.actions: + actions[action['actionKey']] = eppo_client.bandit.AttributeSet( + numeric_attributes=action.get('numericAttributes', {}), + categorical_attributes=action.get('categoricalAttributes', {}) + ) + + # Convert subject attributes + subject_attributes = eppo_client.bandit.AttributeSet( + numeric_attributes=request_obj.subject_attributes.get('numericAttributes', {}), + categorical_attributes=request_obj.subject_attributes.get('categoricalAttributes', {}) + ) + + result = client.get_bandit_action( + request_obj.flag, + request_obj.subject_key, + subject_attributes, + actions, + request_obj.default_value + ) + + response = { + "result": result, + "assignmentLog": [], # You might want to implement assignment logging + "banditLog": [], # You might want to implement bandit logging + "error": None + } + print(f"response: {response}") + return jsonify(response) + + except Exception as e: + print(f"Error processing bandit action: {str(e)}") + response = { + "result": None, + "assignmentLog": [], + "banditLog": [], + "error": str(e) + } + return jsonify(response) def initialize_client_and_wait(): print("Initializing client") From 1fa655baf1c5c6d90b0c949fc60a8bd13a3325af Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Mon, 16 Dec 2024 20:51:24 -0800 Subject: [PATCH 07/13] fix bandit impl --- .../python-sdk-relay/src/server.py | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/package-testing/python-sdk-relay/src/server.py b/package-testing/python-sdk-relay/src/server.py index 3ceb0ec1..ac87bc49 100644 --- a/package-testing/python-sdk-relay/src/server.py +++ b/package-testing/python-sdk-relay/src/server.py @@ -124,6 +124,7 @@ class BanditActionRequest: @app.route('/bandits/v1/action', methods=['POST']) def handle_bandit(): data = request.json + print(f"Request data: {data}") request_obj = BanditActionRequest( flag=data['flag'], subject_key=data['subjectKey'], @@ -133,42 +134,44 @@ def handle_bandit(): ) print(f"Request object: {request_obj}") - client = eppo_client.get_instance() - try: - # Convert actions to the format expected by the SDK + # Create subject context using ContextAttributes constructor + subject_context = eppo_client.bandit.ContextAttributes( + numeric_attributes=request_obj.subject_attributes.get('numericAttributes', {}), + categorical_attributes=request_obj.subject_attributes.get('categoricalAttributes', {}) + ) + + # Create actions dictionary using ContextAttributes constructor actions = {} for action in request_obj.actions: - actions[action['actionKey']] = eppo_client.bandit.AttributeSet( + actions[action['actionKey']] = eppo_client.bandit.ContextAttributes( numeric_attributes=action.get('numericAttributes', {}), categorical_attributes=action.get('categoricalAttributes', {}) ) - # Convert subject attributes - subject_attributes = eppo_client.bandit.AttributeSet( - numeric_attributes=request_obj.subject_attributes.get('numericAttributes', {}), - categorical_attributes=request_obj.subject_attributes.get('categoricalAttributes', {}) - ) - + client = eppo_client.get_instance() result = client.get_bandit_action( request_obj.flag, request_obj.subject_key, - subject_attributes, + subject_context, actions, request_obj.default_value ) response = { - "result": result, - "assignmentLog": [], # You might want to implement assignment logging - "banditLog": [], # You might want to implement bandit logging + "result": { + "variation": result.variation, + "action": result.action + }, + "assignmentLog": [], + "banditLog": [], "error": None } print(f"response: {response}") return jsonify(response) except Exception as e: - print(f"Error processing bandit action: {str(e)}") + print(f"Error processing bandit: {str(e)}") response = { "result": None, "assignmentLog": [], From d785d7a6d8467e357ce53601ee28bf76356fd381 Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Mon, 16 Dec 2024 21:19:25 -0800 Subject: [PATCH 08/13] more logging --- package-testing/python-sdk-relay/src/server.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/package-testing/python-sdk-relay/src/server.py b/package-testing/python-sdk-relay/src/server.py index ac87bc49..111b8834 100644 --- a/package-testing/python-sdk-relay/src/server.py +++ b/package-testing/python-sdk-relay/src/server.py @@ -140,6 +140,7 @@ def handle_bandit(): numeric_attributes=request_obj.subject_attributes.get('numericAttributes', {}), categorical_attributes=request_obj.subject_attributes.get('categoricalAttributes', {}) ) + print(f"Subject context: {subject_context}") # Create actions dictionary using ContextAttributes constructor actions = {} @@ -148,8 +149,10 @@ def handle_bandit(): numeric_attributes=action.get('numericAttributes', {}), categorical_attributes=action.get('categoricalAttributes', {}) ) + print(f"Actions: {actions}") client = eppo_client.get_instance() + print(f"Calling get_bandit_action with flag={request_obj.flag}, subject_key={request_obj.subject_key}, default_value={request_obj.default_value}") result = client.get_bandit_action( request_obj.flag, request_obj.subject_key, @@ -157,6 +160,7 @@ def handle_bandit(): actions, request_obj.default_value ) + print(f"Raw result from get_bandit_action: {result}") response = { "result": { @@ -167,7 +171,7 @@ def handle_bandit(): "banditLog": [], "error": None } - print(f"response: {response}") + print(f"Final response: {response}") return jsonify(response) except Exception as e: From 8b13ad4c8b29e6ba8006bbcddadff290d1ad5980 Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Mon, 16 Dec 2024 21:29:56 -0800 Subject: [PATCH 09/13] see config --- package-testing/python-sdk-relay/src/server.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/package-testing/python-sdk-relay/src/server.py b/package-testing/python-sdk-relay/src/server.py index 111b8834..fb5b2080 100644 --- a/package-testing/python-sdk-relay/src/server.py +++ b/package-testing/python-sdk-relay/src/server.py @@ -189,6 +189,9 @@ def initialize_client_and_wait(): api_key = environ.get('EPPO_API_KEY', 'NOKEYSPECIFIED') base_url = environ.get('EPPO_BASE_URL', 'http://localhost:5000/api') + # Add debug logging for initialization + print(f"Initializing with API key: {api_key}, base URL: {base_url}") + client_config = Config( api_key=api_key, base_url=base_url, @@ -196,8 +199,18 @@ def initialize_client_and_wait(): ) eppo_client.init(client_config) client = eppo_client.get_instance() + + # Add debug logging for initialization status + print("Waiting for initialization...") client.wait_for_initialization() - print("Client initialized") + print("Client initialization complete") + + # Try to fetch a configuration to verify it's working + try: + config = client.get_configuration() + print(f"Test configuration: {config}") + except Exception as e: + print(f"Error fetching test configuration: {e}") if __name__ == "__main__": initialize_client_and_wait() From 36aca396ac24836f116a55933e115d7bf46ca83a Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Mon, 16 Dec 2024 21:48:57 -0800 Subject: [PATCH 10/13] ok --- .../python-sdk-relay/src/server.py | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/package-testing/python-sdk-relay/src/server.py b/package-testing/python-sdk-relay/src/server.py index fb5b2080..9e2b2a6d 100644 --- a/package-testing/python-sdk-relay/src/server.py +++ b/package-testing/python-sdk-relay/src/server.py @@ -137,22 +137,28 @@ def handle_bandit(): try: # Create subject context using ContextAttributes constructor subject_context = eppo_client.bandit.ContextAttributes( - numeric_attributes=request_obj.subject_attributes.get('numericAttributes', {}), - categorical_attributes=request_obj.subject_attributes.get('categoricalAttributes', {}) + numeric_attributes=request_obj.subject_attributes['numericAttributes'], + categorical_attributes=request_obj.subject_attributes['categoricalAttributes'] ) print(f"Subject context: {subject_context}") # Create actions dictionary using ContextAttributes constructor actions = {} for action in request_obj.actions: - actions[action['actionKey']] = eppo_client.bandit.ContextAttributes( - numeric_attributes=action.get('numericAttributes', {}), - categorical_attributes=action.get('categoricalAttributes', {}) + action_key = action['actionKey'] + action_context = eppo_client.bandit.ContextAttributes( + numeric_attributes=action['numericAttributes'], + categorical_attributes=action['categoricalAttributes'] ) - print(f"Actions: {actions}") + actions[action_key] = action_context + + print(f"\nExecuting bandit action:") + print(f"Flag: {request_obj.flag}") + print(f"Subject: {request_obj.subject_key}") + print(f"Default: {request_obj.default_value}") + print(f"Available actions: {list(actions.keys())}") client = eppo_client.get_instance() - print(f"Calling get_bandit_action with flag={request_obj.flag}, subject_key={request_obj.subject_key}, default_value={request_obj.default_value}") result = client.get_bandit_action( request_obj.flag, request_obj.subject_key, @@ -171,7 +177,6 @@ def handle_bandit(): "banditLog": [], "error": None } - print(f"Final response: {response}") return jsonify(response) except Exception as e: From 6dac28670008f02e9ece86ab97cb42bf7b9a0300 Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Mon, 16 Dec 2024 21:53:05 -0800 Subject: [PATCH 11/13] different parsing --- .../python-sdk-relay/src/server.py | 46 +++++++------------ 1 file changed, 16 insertions(+), 30 deletions(-) diff --git a/package-testing/python-sdk-relay/src/server.py b/package-testing/python-sdk-relay/src/server.py index 9e2b2a6d..5ae139b2 100644 --- a/package-testing/python-sdk-relay/src/server.py +++ b/package-testing/python-sdk-relay/src/server.py @@ -112,39 +112,28 @@ def handle_assignment(): } return jsonify(response) -@dataclass -class BanditActionRequest: - flag: str - subject_key: str - subject_attributes: dict # Will contain numericAttributes and categoricalAttributes - actions: list # List of dicts with actionKey, numericAttributes, categoricalAttributes - default_value: any - @app.route('/bandits/v1/action', methods=['POST']) def handle_bandit(): data = request.json print(f"Request data: {data}") - request_obj = BanditActionRequest( - flag=data['flag'], - subject_key=data['subjectKey'], - subject_attributes=data['subjectAttributes'], - default_value=data['defaultValue'], - actions=data['actions'] - ) - print(f"Request object: {request_obj}") + + flag = data['flag'] + subject_key = data['subjectKey'] + subject_attributes = data['subjectAttributes'] + default_value = data['defaultValue'] + actions = data['actions'] try: # Create subject context using ContextAttributes constructor subject_context = eppo_client.bandit.ContextAttributes( - numeric_attributes=request_obj.subject_attributes['numericAttributes'], - categorical_attributes=request_obj.subject_attributes['categoricalAttributes'] + numeric_attributes=subject_attributes['numericAttributes'], + categorical_attributes=subject_attributes['categoricalAttributes'] ) - print(f"Subject context: {subject_context}") # Create actions dictionary using ContextAttributes constructor actions = {} - for action in request_obj.actions: + for action in actions: action_key = action['actionKey'] action_context = eppo_client.bandit.ContextAttributes( numeric_attributes=action['numericAttributes'], @@ -153,26 +142,23 @@ def handle_bandit(): actions[action_key] = action_context print(f"\nExecuting bandit action:") - print(f"Flag: {request_obj.flag}") - print(f"Subject: {request_obj.subject_key}") - print(f"Default: {request_obj.default_value}") + print(f"Flag: {flag}") + print(f"Subject: {subject_key}") + print(f"Default: {default_value}") print(f"Available actions: {list(actions.keys())}") client = eppo_client.get_instance() result = client.get_bandit_action( - request_obj.flag, - request_obj.subject_key, + flag, + subject_key, subject_context, actions, - request_obj.default_value + default_value ) print(f"Raw result from get_bandit_action: {result}") response = { - "result": { - "variation": result.variation, - "action": result.action - }, + "result": result, "assignmentLog": [], "banditLog": [], "error": None From 28c38590b4187e63fd6db8c24d312403928c7973 Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Mon, 16 Dec 2024 21:59:06 -0800 Subject: [PATCH 12/13] split --- package-testing/python-sdk-relay/src/server.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/package-testing/python-sdk-relay/src/server.py b/package-testing/python-sdk-relay/src/server.py index 5ae139b2..16576826 100644 --- a/package-testing/python-sdk-relay/src/server.py +++ b/package-testing/python-sdk-relay/src/server.py @@ -52,7 +52,6 @@ def handle_assignment(): assignment_type=data['assignmentType'], default_value=data['defaultValue'] ) - print(f"Request object: {request_obj}") client = eppo_client.get_instance() @@ -95,7 +94,10 @@ def handle_assignment(): ) response = { - "result": result, + "result": { + "variation": result.variation, + "action": result.action + }, "assignmentLog": [], "banditLog": [], "error": None From 018fea3c7c676e145ecc01c0ddf50f1adf7ef084 Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Mon, 16 Dec 2024 22:03:12 -0800 Subject: [PATCH 13/13] opps --- package-testing/python-sdk-relay/src/server.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package-testing/python-sdk-relay/src/server.py b/package-testing/python-sdk-relay/src/server.py index 16576826..c0849ca1 100644 --- a/package-testing/python-sdk-relay/src/server.py +++ b/package-testing/python-sdk-relay/src/server.py @@ -94,10 +94,7 @@ def handle_assignment(): ) response = { - "result": { - "variation": result.variation, - "action": result.action - }, + "result": result, "assignmentLog": [], "banditLog": [], "error": None @@ -160,7 +157,10 @@ def handle_bandit(): print(f"Raw result from get_bandit_action: {result}") response = { - "result": result, + "result": { + "variation": result.variation, + "action": result.action + }, "assignmentLog": [], "banditLog": [], "error": None