Skip to content

Commit fbac21c

Browse files
statsig-python-core: Add version 0.23.1 (#2489)
1 parent 8e31c03 commit fbac21c

3 files changed

Lines changed: 185 additions & 0 deletions

File tree

‎docs/packages/statsig-python-core.yaml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ versions:
1616
- filename: statsig_python_core-0.23.0-cp310-abi3-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl
1717
sha256: 09bb4668d9429786c631c5e63d1fe4de27f9a85edead4caad0539af5a4b9a151
1818
requires-python: '>=3.10'
19+
- version: 0.23.1
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
From cb9bddf61d05acff335ad691dcbae5e3aeea6e52 Mon Sep 17 00:00:00 2001
2+
From: Ludovic Henry <git@ludovic.dev>
3+
Date: Fri, 28 Aug 2026 08:00:00 +0200
4+
Subject: [PATCH] tests: wait for background specs syncs instead of assuming a
5+
1ms interval
6+
7+
`StatsigOptions.specs_sync_interval_ms` is now rejected below 1000 ("Invalid
8+
'specs_sync_interval_ms', value must be greater than 1000"), so the five tests
9+
that set it to 1 no longer get the near-instant background sync they were
10+
written against. They fail at the 0.22.0 tag on every platform, against the
11+
wheels published on PyPI -- x86_64, aarch64 and macOS alike.
12+
13+
Use the smallest accepted interval and wait for the condition each test is
14+
really about rather than for a fixed duration. The waits return as soon as the
15+
sync lands, so fast hardware pays nothing and the whole file gets quicker.
16+
17+
Upstream-Status: To upstream [not yet submitted; the release tag is not covered by upstream's test workflow, which runs on branches only, so this needs a maintainer discussion rather than a drive-by PR]
18+
---
19+
statsig-pyo3/tests/test_data_store.py | 26 ++++++++++++------
20+
.../tests/test_observability_client.py | 27 ++++++++++++-------
21+
2 files changed, 35 insertions(+), 18 deletions(-)
22+
23+
diff --git a/statsig-pyo3/tests/test_data_store.py b/statsig-pyo3/tests/test_data_store.py
24+
index 7959adfb..d9d615d1 100644
25+
--- a/statsig-pyo3/tests/test_data_store.py
26+
+++ b/statsig-pyo3/tests/test_data_store.py
27+
@@ -1,4 +1,5 @@
28+
import json
29+
+import time
30+
from time import sleep
31+
from typing import Optional
32+
import pytest
33+
@@ -16,6 +17,13 @@ from utils import get_test_data_resource, get_test_data_resource_bytes
34+
35+
known_lcut = 1767981029384
36+
37+
+
38+
+def wait_for(predicate, timeout=30.0, interval=0.05):
39+
+ deadline = time.monotonic() + timeout
40+
+ while not predicate() and time.monotonic() < deadline:
41+
+ sleep(interval)
42+
+
43+
+
44+
dcs_content = get_test_data_resource("eval_proj_dcs.json")
45+
json_data = json.loads(dcs_content)
46+
eval_proj_protobuf = get_test_data_resource_bytes("eval_proj_dcs.pb.br")
47+
@@ -142,7 +150,7 @@ def statsig_setup(httpserver: HTTPServer):
48+
specs_url=httpserver.url_for("/v2/download_config_specs"),
49+
log_event_url=httpserver.url_for("/v1/log_event"),
50+
data_store=data_store,
51+
- specs_sync_interval_ms=1,
52+
+ specs_sync_interval_ms=1001,
53+
)
54+
55+
statsig = Statsig("secret-key", options)
56+
@@ -166,7 +174,7 @@ def statsig_bytes_setup(httpserver: HTTPServer):
57+
specs_url=httpserver.url_for("/v2/download_config_specs"),
58+
log_event_url=httpserver.url_for("/v1/log_event"),
59+
data_store=data_store,
60+
- specs_sync_interval_ms=1,
61+
+ specs_sync_interval_ms=1001,
62+
)
63+
64+
statsig = Statsig("secret-key", options)
65+
@@ -191,6 +199,8 @@ def test_data_store_usage_get(statsig_setup):
66+
67+
statsig.flush_events().wait()
68+
69+
+ wait_for(lambda: data_store.get_called_count > 1)
70+
+
71+
assert data_store.init_called
72+
assert gate.details.reason == "Adapter(DataStore):Recognized"
73+
assert gate.value == True
74+
@@ -207,7 +217,7 @@ def test_data_store_usage_set(statsig_setup):
75+
76+
assert data_store.init_called
77+
assert gate.details.reason == "Adapter(DataStore):Recognized"
78+
- sleep(1)
79+
+ wait_for(lambda: data_store.content_set is not None)
80+
81+
gate_after = statsig.get_feature_gate(user, "test_public")
82+
statsig.flush_events().wait()
83+
@@ -232,10 +242,7 @@ def test_data_store_usage_get_bytes(statsig_bytes_setup):
84+
assert gate.value == True
85+
assert gate.details.lcut == known_lcut
86+
87+
- for _ in range(5):
88+
- if data_store.set_bytes_called_count > 0:
89+
- break
90+
- sleep(0.05)
91+
+ wait_for(lambda: data_store.set_bytes_called_count > 0)
92+
93+
assert data_store.get_bytes_called_count >= 1
94+
assert data_store.get_called_count == 0
95+
@@ -251,7 +258,10 @@ def test_data_store_usage_set_bytes(statsig_bytes_setup):
96+
97+
assert data_store.init_called
98+
assert gate.details.reason == "Adapter(DataStore):Recognized"
99+
- sleep(1)
100+
+ wait_for(
101+
+ lambda: statsig.get_feature_gate(user, "test_public").details.lcut
102+
+ == known_lcut + 10
103+
+ )
104+
105+
gate_after = statsig.get_feature_gate(user, "test_public")
106+
statsig.flush_events().wait()
107+
diff --git a/statsig-pyo3/tests/test_observability_client.py b/statsig-pyo3/tests/test_observability_client.py
108+
index 1459a601..b4d92438 100644
109+
--- a/statsig-pyo3/tests/test_observability_client.py
110+
+++ b/statsig-pyo3/tests/test_observability_client.py
111+
@@ -85,7 +85,7 @@ def statsig_setup(httpserver: HTTPServer):
112+
options.specs_url = httpserver.url_for("/v2/download_config_specs")
113+
options.log_event_url = httpserver.url_for("/v1/log_event")
114+
options.observability_client = observability_client
115+
- options.specs_sync_interval_ms = 1
116+
+ options.specs_sync_interval_ms = 1001
117+
options.output_log_level = "error"
118+
statsig = Statsig("secret-key", options)
119+
120+
@@ -165,16 +165,23 @@ def test_metric_with_high_card(statsig_setup):
121+
statsig.flush_events().wait()
122+
123+
assert observability_client.init_called, "init() should have been called"
124+
- time.sleep(3)
125+
126+
- dist_event = next(
127+
- (
128+
- m
129+
- for m in observability_client.metrics
130+
- if m[0] == "distribution" and m[1] == "statsig.sdk.config_propagation_diff"
131+
- ),
132+
- None,
133+
- )
134+
+ def find_dist_event():
135+
+ return next(
136+
+ (
137+
+ m
138+
+ for m in observability_client.metrics
139+
+ if m[0] == "distribution"
140+
+ and m[1] == "statsig.sdk.config_propagation_diff"
141+
+ ),
142+
+ None,
143+
+ )
144+
+
145+
+ deadline = time.monotonic() + 30.0
146+
+ while find_dist_event() is None and time.monotonic() < deadline:
147+
+ time.sleep(0.05)
148+
+
149+
+ dist_event = find_dist_event()
150+
assert dist_event is not None, "distribution() should have been called"
151+
assert isinstance(dist_event[2], float)
152+
assert isinstance(int(dist_event[3].get("lcut")), (int, float))
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
From 9764058ca54e6b66362c05667b5c22fffd38dce0 Mon Sep 17 00:00:00 2001
2+
From: Ludovic Henry <git@ludovic.dev>
3+
Date: Fri, 28 Aug 2026 08:10:00 +0200
4+
Subject: [PATCH] tests: give fork_runner.py a timeout a slow machine can meet
5+
6+
`fork_runner.py` performs 50 full SDK initialisations -- ten iterations, each
7+
followed by four nested forks that initialise, evaluate and shut down -- against
8+
a local mock server. Ten seconds is enough on upstream's x86_64 CI and not on a
9+
riscv64 runner, where the subprocess is SIGTERMed part way through and the test
10+
fails with `assert -15 == 0`.
11+
12+
`communicate()` returns as soon as the subprocess exits, so a larger budget
13+
costs nothing where the old one was already sufficient.
14+
15+
Upstream-Status: To upstream [not yet submitted; the release tag is not covered by upstream's test workflow, which runs on branches only, so this needs a maintainer discussion rather than a drive-by PR]
16+
---
17+
statsig-pyo3/tests/test_forking.py | 2 +-
18+
1 file changed, 1 insertion(+), 1 deletion(-)
19+
20+
diff --git a/statsig-pyo3/tests/test_forking.py b/statsig-pyo3/tests/test_forking.py
21+
index a5f27c40..8d2bd7db 100644
22+
--- a/statsig-pyo3/tests/test_forking.py
23+
+++ b/statsig-pyo3/tests/test_forking.py
24+
@@ -45,7 +45,7 @@ def test_forking(httpserver: HTTPServer):
25+
env={**os.environ, "RUST_BACKTRACE": "full"},
26+
)
27+
try:
28+
- proc.communicate(timeout=10)
29+
+ proc.communicate(timeout=180)
30+
except subprocess.TimeoutExpired:
31+
proc.terminate()
32+
proc.wait()

0 commit comments

Comments
 (0)