Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
21a8460
Improve HTTP response handling and retry logic
MichaelGHSeg Feb 12, 2026
e4f34d0
Increase max retries from 10 to 1000
MichaelGHSeg Feb 12, 2026
8bd4163
Update segment/analytics/test/test_request.py
MichaelGHSeg Feb 12, 2026
e1ac214
Apply suggestions from code review
MichaelGHSeg Feb 18, 2026
6a10b7c
Address PR review feedback
MichaelGHSeg Feb 19, 2026
6ed8ef6
Implement unified HTTP response handling per SDD
MichaelGHSeg Feb 25, 2026
a28102f
Fix Retry-After: 0 handling and 429 re-queue guard
MichaelGHSeg Feb 25, 2026
2165298
Merge branch 'master' into response-status-updates
MichaelGHSeg Feb 25, 2026
115d124
Address PR review: catch KeyError in response parsing
MichaelGHSeg Feb 25, 2026
2474b46
Enabling retry e2e test set
MichaelGHSeg Feb 25, 2026
98df1bc
Fix Retry-After header never parsed on non-2xx responses
MichaelGHSeg Feb 26, 2026
ec0481f
Wire on_error callback in e2e-cli for failure reporting
MichaelGHSeg Feb 26, 2026
ea257f8
Consolidate backoff parameters: max retries 1000 -> 10
MichaelGHSeg Mar 4, 2026
184fe1f
Merge branch 'master' into response-status-updates
MichaelGHSeg Mar 11, 2026
12d2f08
Omit X-Retry-Count header on first attempt, send only on retries
MichaelGHSeg Mar 20, 2026
a2fd03b
Treat 2xx and 3xx status codes as success, not just 200
MichaelGHSeg May 1, 2026
f0c39a7
Merge remote-tracking branch 'origin/master' into response-status-upd…
MichaelGHSeg May 7, 2026
8c5680a
Improve e2e-cli setup: Python resolution and devbox docs
MichaelGHSeg May 7, 2026
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
30 changes: 24 additions & 6 deletions e2e-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,35 @@

E2E test CLI for the [analytics-python](https://github.com/segmentio/analytics-python) SDK. Accepts a JSON input describing events and SDK configuration, sends them through the real SDK, and outputs results as JSON.

## Setup
## Running E2E tests

### With devbox (recommended)

```bash
# From repo root — activates Python 3.12 and installs deps automatically
devbox shell

# Then from e2e-cli dir:
./run-e2e.sh
```

### Without devbox

Requires Python 3.9+ and Node.js 18+. Using a virtualenv is strongly recommended since macOS system Python is externally managed.

```bash
cd e2e-cli
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
pip install -e .
./run-e2e.sh
```

### Override sdk-e2e-tests location

```bash
E2E_TESTS_DIR=../my-e2e-tests ./run-e2e.sh
```

## Usage
## Manual CLI usage

```bash
e2e-cli --input '{"writeKey":"...", ...}'
Expand All @@ -21,7 +39,7 @@ e2e-cli --input '{"writeKey":"...", ...}'
Or without installing:

```bash
python3 -m src.cli --input '{"writeKey":"...", ...}'
python3 src/cli.py --input '{"writeKey":"...", ...}'
```

## Input Format
Expand Down
2 changes: 1 addition & 1 deletion e2e-cli/e2e-config.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"sdk": "python",
"test_suites": "basic",
"test_suites": "basic,retry",
"auto_settings": false,
"patch": null,
"env": {}
Expand Down
18 changes: 15 additions & 3 deletions e2e-cli/run-e2e.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
#
# Run E2E tests for analytics-python
#
# Prerequisites: Python 3, pip, Node.js 18+
# Prerequisites: Node.js 18+ and one of:
# - devbox (recommended): run `devbox shell` first, then ./run-e2e.sh
# - Python 3.9+ with a virtualenv already activated
#
# Usage:
# ./run-e2e.sh [extra args passed to run-tests.sh]
Expand All @@ -17,15 +19,25 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SDK_ROOT="$SCRIPT_DIR/.."
E2E_DIR="${E2E_TESTS_DIR:-$SDK_ROOT/../sdk-e2e-tests}"

# Resolve python and pip — prefer activated venv/devbox python, fall back to python3
PYTHON="${PYTHON:-$(command -v python || command -v python3)}"
PIP="$PYTHON -m pip"

if [[ -z "$PYTHON" ]]; then
echo "Error: Python not found. Run 'devbox shell' first or activate a virtualenv."
exit 1
fi

echo "=== Building analytics-python e2e-cli ==="
echo "Using Python: $PYTHON"

# Install SDK
cd "$SDK_ROOT"
pip install -e .
$PIP install -e . -q

# Install e2e-cli
cd "$SCRIPT_DIR"
pip install -e .
$PIP install -e . -q

echo ""

Expand Down
15 changes: 11 additions & 4 deletions e2e-cli/src/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ def run(input_json: str, debug: bool):
"""Run the E2E CLI with the given input configuration."""
logger = setup_logging(debug)
output = {"success": False, "sentBatches": 0, "error": None}
delivery_errors = []

def on_error(error, batch):
delivery_errors.append(str(error))

try:
data = json.loads(input_json)
Expand All @@ -96,6 +100,7 @@ def run(input_json: str, debug: bool):
write_key=write_key,
host=api_host,
debug=debug,
on_error=on_error,
upload_size=flush_at,
upload_interval=flush_interval,
max_retries=max_retries,
Expand All @@ -120,10 +125,12 @@ def run(input_json: str, debug: bool):
client.flush()
client.join()

output["success"] = True
# Note: We don't have easy access to batch count from the SDK internals
# This could be enhanced if needed
output["sentBatches"] = 1 # Placeholder
if delivery_errors:
output["success"] = False
output["error"] = delivery_errors[0]
else:
output["success"] = True
output["sentBatches"] = 1

except json.JSONDecodeError as e:
output["error"] = f"Invalid JSON input: {e}"
Expand Down
15 changes: 14 additions & 1 deletion segment/analytics/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ class DefaultConfig(object):
gzip = False
timeout = 15
max_retries = 10
max_total_backoff_duration = 43200
max_rate_limit_duration = 43200
proxies = None
thread = 1
upload_interval = 0.5
Expand Down Expand Up @@ -65,9 +67,16 @@ def __init__(self,
oauth_client_key=DefaultConfig.oauth_client_key,
oauth_key_id=DefaultConfig.oauth_key_id,
oauth_auth_server=DefaultConfig.oauth_auth_server,
oauth_scope=DefaultConfig.oauth_scope,):
oauth_scope=DefaultConfig.oauth_scope,
max_total_backoff_duration=DefaultConfig.max_total_backoff_duration,
max_rate_limit_duration=DefaultConfig.max_rate_limit_duration,):
require('write_key', write_key, str)

if max_total_backoff_duration is not None and max_total_backoff_duration < 0:
raise ValueError('max_total_backoff_duration must be non-negative')
if max_rate_limit_duration is not None and max_rate_limit_duration < 0:
raise ValueError('max_rate_limit_duration must be non-negative')

self.queue = queue.Queue(max_queue_size)
self.write_key = write_key
self.on_error = on_error
Expand All @@ -78,6 +87,8 @@ def __init__(self,
self.gzip = gzip
self.timeout = timeout
self.proxies = proxies
self.max_total_backoff_duration = max_total_backoff_duration
self.max_rate_limit_duration = max_rate_limit_duration
self.oauth_manager = None
if(oauth_client_id and oauth_client_key and oauth_key_id):
self.oauth_manager = OauthManager(oauth_client_id, oauth_client_key, oauth_key_id,
Expand Down Expand Up @@ -110,6 +121,8 @@ def __init__(self,
upload_size=upload_size, upload_interval=upload_interval,
gzip=gzip, retries=max_retries, timeout=timeout,
proxies=proxies, oauth_manager=self.oauth_manager,
max_total_backoff_duration=max_total_backoff_duration,
max_rate_limit_duration=max_rate_limit_duration,
)
self.consumers.append(consumer)

Expand Down
Loading
Loading