sentry-options 1.2.0__tar.gz → 1.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sentry_options-1.2.0 → sentry_options-1.2.2}/Cargo.lock +5 -5
- {sentry_options-1.2.0 → sentry_options-1.2.2}/Cargo.toml +2 -2
- {sentry_options-1.2.0 → sentry_options-1.2.2}/PKG-INFO +1 -1
- {sentry_options-1.2.0 → sentry_options-1.2.2}/clients/python/src/lib.rs +45 -10
- sentry_options-1.2.2/clients/python/tests/conftest.py +70 -0
- {sentry_options-1.2.0 → sentry_options-1.2.2}/clients/python/tests/propagation_test.py +12 -61
- sentry_options-1.2.2/clients/python/tests/refresh_test.py +73 -0
- {sentry_options-1.2.0 → sentry_options-1.2.2}/clients/python/uv.lock +1 -1
- {sentry_options-1.2.0 → sentry_options-1.2.2}/clients/rust/src/lib.rs +147 -26
- {sentry_options-1.2.0 → sentry_options-1.2.2}/pyproject.toml +1 -1
- {sentry_options-1.2.0 → sentry_options-1.2.2}/python/sentry_options/__init__.py +2 -0
- {sentry_options-1.2.0 → sentry_options-1.2.2}/python/sentry_options/_core.pyi +20 -1
- {sentry_options-1.2.0 → sentry_options-1.2.2}/sentry-options-validation/src/lib.rs +325 -112
- sentry_options-1.2.0/clients/python/tests/conftest.py +0 -10
- {sentry_options-1.2.0 → sentry_options-1.2.2}/clients/python/Cargo.toml +0 -0
- {sentry_options-1.2.0 → sentry_options-1.2.2}/clients/python/benches/bench_get.py +0 -0
- {sentry_options-1.2.0 → sentry_options-1.2.2}/clients/python/benches/bench_has.py +0 -0
- {sentry_options-1.2.0 → sentry_options-1.2.2}/clients/python/benches/bench_isset.py +0 -0
- {sentry_options-1.2.0 → sentry_options-1.2.2}/clients/python/tests/features_test.py +0 -0
- {sentry_options-1.2.0 → sentry_options-1.2.2}/clients/python/tests/options_override_test.py +0 -0
- {sentry_options-1.2.0 → sentry_options-1.2.2}/clients/python/tests/options_test.py +0 -0
- {sentry_options-1.2.0 → sentry_options-1.2.2}/clients/rust/Cargo.toml +0 -0
- {sentry_options-1.2.0 → sentry_options-1.2.2}/clients/rust/benches/features_bench.rs +0 -0
- {sentry_options-1.2.0 → sentry_options-1.2.2}/clients/rust/benches/isset_bench.rs +0 -0
- {sentry_options-1.2.0 → sentry_options-1.2.2}/clients/rust/benches/options_bench.rs +0 -0
- {sentry_options-1.2.0 → sentry_options-1.2.2}/clients/rust/benches/reload_trace.rs +0 -0
- {sentry_options-1.2.0 → sentry_options-1.2.2}/clients/rust/src/features.rs +0 -0
- {sentry_options-1.2.0 → sentry_options-1.2.2}/clients/rust/src/testing.rs +0 -0
- {sentry_options-1.2.0 → sentry_options-1.2.2}/clients/rust/tests/override_validation.rs +0 -0
- {sentry_options-1.2.0 → sentry_options-1.2.2}/python/sentry_options/py.typed +0 -0
- {sentry_options-1.2.0 → sentry_options-1.2.2}/python/sentry_options/testing.py +0 -0
- {sentry_options-1.2.0 → sentry_options-1.2.2}/sentry-options-validation/Cargo.toml +0 -0
- {sentry_options-1.2.0 → sentry_options-1.2.2}/sentry-options-validation/src/feature-schema-defs.json +0 -0
- {sentry_options-1.2.0 → sentry_options-1.2.2}/sentry-options-validation/src/namespace-schema.json +0 -0
|
@@ -435,7 +435,7 @@ dependencies = [
|
|
|
435
435
|
|
|
436
436
|
[[package]]
|
|
437
437
|
name = "example"
|
|
438
|
-
version = "1.2.
|
|
438
|
+
version = "1.2.2"
|
|
439
439
|
dependencies = [
|
|
440
440
|
"anyhow",
|
|
441
441
|
"sentry-options",
|
|
@@ -1437,7 +1437,7 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
|
|
1437
1437
|
|
|
1438
1438
|
[[package]]
|
|
1439
1439
|
name = "sentry-options"
|
|
1440
|
-
version = "1.2.
|
|
1440
|
+
version = "1.2.2"
|
|
1441
1441
|
dependencies = [
|
|
1442
1442
|
"arc-swap",
|
|
1443
1443
|
"criterion",
|
|
@@ -1451,7 +1451,7 @@ dependencies = [
|
|
|
1451
1451
|
|
|
1452
1452
|
[[package]]
|
|
1453
1453
|
name = "sentry-options-cli"
|
|
1454
|
-
version = "1.2.
|
|
1454
|
+
version = "1.2.2"
|
|
1455
1455
|
dependencies = [
|
|
1456
1456
|
"chrono",
|
|
1457
1457
|
"clap",
|
|
@@ -1466,7 +1466,7 @@ dependencies = [
|
|
|
1466
1466
|
|
|
1467
1467
|
[[package]]
|
|
1468
1468
|
name = "sentry-options-python"
|
|
1469
|
-
version = "1.2.
|
|
1469
|
+
version = "1.2.2"
|
|
1470
1470
|
dependencies = [
|
|
1471
1471
|
"pyo3",
|
|
1472
1472
|
"sentry-options",
|
|
@@ -1475,7 +1475,7 @@ dependencies = [
|
|
|
1475
1475
|
|
|
1476
1476
|
[[package]]
|
|
1477
1477
|
name = "sentry-options-validation"
|
|
1478
|
-
version = "1.2.
|
|
1478
|
+
version = "1.2.2"
|
|
1479
1479
|
dependencies = [
|
|
1480
1480
|
"anyhow",
|
|
1481
1481
|
"arc-swap",
|
|
@@ -3,13 +3,13 @@ resolver = "3"
|
|
|
3
3
|
members = ["sentry-options-validation", "clients/rust", "clients/python"]
|
|
4
4
|
|
|
5
5
|
[workspace.package]
|
|
6
|
-
version = "1.2.
|
|
6
|
+
version = "1.2.2"
|
|
7
7
|
edition = "2024"
|
|
8
8
|
repository = "https://github.com/getsentry/sentry-options"
|
|
9
9
|
license = "Apache-2.0"
|
|
10
10
|
|
|
11
11
|
[workspace.dependencies]
|
|
12
|
-
sentry-options-validation = { path = "sentry-options-validation", version = "1.2.
|
|
12
|
+
sentry-options-validation = { path = "sentry-options-validation", version = "1.2.2" }
|
|
13
13
|
serde = { version = "1.0", features = ["derive"] }
|
|
14
14
|
serde_json = "1.0.145"
|
|
15
15
|
anyhow = "1.0.100"
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
use std::collections::HashMap;
|
|
4
4
|
use std::sync::OnceLock;
|
|
5
|
+
use std::time::Duration;
|
|
5
6
|
|
|
6
7
|
use ::sentry_options::{
|
|
7
|
-
FeatureChecker as RustFeatureChecker,
|
|
8
|
-
Options as RustOptions, OptionsError as RustOptionsError,
|
|
8
|
+
DEFAULT_REFRESH_THRESHOLD, FeatureChecker as RustFeatureChecker,
|
|
9
|
+
FeatureContext as RustFeatureContext, Options as RustOptions, OptionsError as RustOptionsError,
|
|
9
10
|
};
|
|
10
11
|
use pyo3::exceptions::{PyException, PyRuntimeError, PyValueError};
|
|
11
12
|
use pyo3::prelude::*;
|
|
@@ -193,29 +194,62 @@ impl PyFeatureChecker {
|
|
|
193
194
|
/// Optionally accepts an `on_propagation` callback that fires whenever values
|
|
194
195
|
/// are refreshed with a new `generated_at` timestamp. The callback receives
|
|
195
196
|
/// `(namespace: str, delay_secs: float)`.
|
|
197
|
+
///
|
|
198
|
+
/// `refresh_threshold` is the staleness threshold in seconds for
|
|
199
|
+
/// refresh-on-read (default: 5.0). Pass `None` to disable refresh-on-read
|
|
200
|
+
/// entirely; values then only change via `refresh()`.
|
|
196
201
|
#[pyfunction]
|
|
197
|
-
#[pyo3(signature = (on_propagation=None))]
|
|
198
|
-
fn init(on_propagation: Option<Py<PyAny
|
|
202
|
+
#[pyo3(signature = (on_propagation=None, refresh_threshold=Some(DEFAULT_REFRESH_THRESHOLD.as_secs_f64())))]
|
|
203
|
+
fn init(on_propagation: Option<Py<PyAny>>, refresh_threshold: Option<f64>) -> PyResult<()> {
|
|
199
204
|
if GLOBAL_OPTIONS.get().is_some() {
|
|
200
205
|
return Ok(());
|
|
201
206
|
}
|
|
202
|
-
|
|
207
|
+
|
|
208
|
+
let refresh_threshold = match refresh_threshold {
|
|
209
|
+
Some(secs) if !secs.is_finite() || secs < 0.0 => {
|
|
210
|
+
return Err(PyValueError::new_err(
|
|
211
|
+
"refresh_threshold must be a non-negative number or None",
|
|
212
|
+
));
|
|
213
|
+
}
|
|
214
|
+
Some(secs) => Some(Duration::from_secs_f64(secs)),
|
|
215
|
+
None => None,
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
let mut builder = RustOptions::builder().with_refresh_threshold(refresh_threshold);
|
|
219
|
+
if let Some(cb) = on_propagation {
|
|
203
220
|
// `Py<PyAny>` is `Send + Sync`; the closure re-acquires the GIL for the
|
|
204
221
|
// call, so the callback can run on whichever thread triggers a refresh.
|
|
205
|
-
|
|
222
|
+
builder = builder.with_callback(move |ns, delay| {
|
|
206
223
|
Python::attach(|py| {
|
|
207
224
|
if let Err(e) = cb.call1(py, (ns, delay)) {
|
|
208
225
|
e.print(py);
|
|
209
226
|
}
|
|
210
227
|
});
|
|
211
|
-
})
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
};
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
let opts = builder.build().map_err(options_err)?;
|
|
215
231
|
let _ = GLOBAL_OPTIONS.set(opts);
|
|
216
232
|
Ok(())
|
|
217
233
|
}
|
|
218
234
|
|
|
235
|
+
/// Refresh values from disk, ignoring the staleness threshold.
|
|
236
|
+
///
|
|
237
|
+
/// Returns whether a new snapshot was published, i.e. the files changed on
|
|
238
|
+
/// disk and reloaded successfully. On error the previous snapshot is retained
|
|
239
|
+
/// and served. Calling this more often than the threshold guarantees reads
|
|
240
|
+
/// never refresh inline.
|
|
241
|
+
///
|
|
242
|
+
/// Raises NotInitializedError if init() has not been called.
|
|
243
|
+
#[pyfunction]
|
|
244
|
+
fn refresh(py: Python<'_>) -> PyResult<bool> {
|
|
245
|
+
let opts = GLOBAL_OPTIONS.get().ok_or_else(|| {
|
|
246
|
+
NotInitializedError::new_err("Options not initialized - call init() first")
|
|
247
|
+
})?;
|
|
248
|
+
// Release the GIL: the reload does file IO, and the propagation callback
|
|
249
|
+
// re-acquires the GIL itself.
|
|
250
|
+
py.detach(|| opts.refresh()).map_err(options_err)
|
|
251
|
+
}
|
|
252
|
+
|
|
219
253
|
/// Get a namespace handle for accessing options.
|
|
220
254
|
///
|
|
221
255
|
/// Raises RuntimeError if init() has not been called.
|
|
@@ -327,6 +361,7 @@ fn _core(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
|
327
361
|
m.add_function(wrap_pyfunction!(init, m)?)?;
|
|
328
362
|
m.add_function(wrap_pyfunction!(options, m)?)?;
|
|
329
363
|
m.add_function(wrap_pyfunction!(features, m)?)?;
|
|
364
|
+
m.add_function(wrap_pyfunction!(refresh, m)?)?;
|
|
330
365
|
// Classes
|
|
331
366
|
m.add_class::<NamespaceOptions>()?;
|
|
332
367
|
m.add_class::<PyFeatureContext>()?;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from textwrap import dedent
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
from sentry_options import init
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.fixture(scope='session', autouse=True)
|
|
15
|
+
def init_options(tmp_path_factory: pytest.TempPathFactory):
|
|
16
|
+
"""Initialize options once for the test session."""
|
|
17
|
+
init()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def make_options_dir(root: Path, *, generated_at: str | None = None) -> Path:
|
|
21
|
+
"""Create an options directory with a ``test-ns`` schema and values file."""
|
|
22
|
+
options_dir = root / 'opts'
|
|
23
|
+
schemas_dir = options_dir / 'schemas' / 'test-ns'
|
|
24
|
+
values_dir = options_dir / 'values' / 'test-ns'
|
|
25
|
+
schemas_dir.mkdir(parents=True)
|
|
26
|
+
values_dir.mkdir(parents=True)
|
|
27
|
+
|
|
28
|
+
schemas_dir.joinpath('schema.json').write_text(
|
|
29
|
+
json.dumps(
|
|
30
|
+
{
|
|
31
|
+
'version': '1.0',
|
|
32
|
+
'type': 'object',
|
|
33
|
+
'properties': {
|
|
34
|
+
'enabled': {
|
|
35
|
+
'type': 'boolean',
|
|
36
|
+
'default': False,
|
|
37
|
+
'description': 'Enabled',
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
values: dict = {'options': {'enabled': True}}
|
|
45
|
+
if generated_at is not None:
|
|
46
|
+
values['generated_at'] = generated_at
|
|
47
|
+
values_dir.joinpath('values.json').write_text(json.dumps(values))
|
|
48
|
+
|
|
49
|
+
return options_dir
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def run_isolated(script: str, options_dir: Path, timeout: int = 30, **env_extra: str) -> None:
|
|
53
|
+
"""Run a Python script in a subprocess with its own ``SENTRY_OPTIONS_DIR``.
|
|
54
|
+
|
|
55
|
+
Each subprocess gets a fresh Rust ``OnceLock``, so scripts can call
|
|
56
|
+
``init(...)`` with their own settings.
|
|
57
|
+
"""
|
|
58
|
+
env = os.environ.copy()
|
|
59
|
+
env['SENTRY_OPTIONS_DIR'] = str(options_dir)
|
|
60
|
+
env.update(env_extra)
|
|
61
|
+
result = subprocess.run(
|
|
62
|
+
[sys.executable, '-c', dedent(script)],
|
|
63
|
+
env=env,
|
|
64
|
+
capture_output=True,
|
|
65
|
+
text=True,
|
|
66
|
+
timeout=timeout,
|
|
67
|
+
)
|
|
68
|
+
assert result.returncode == 0, (
|
|
69
|
+
f"Script failed.\nstdout: {result.stdout}\nstderr: {result.stderr}"
|
|
70
|
+
)
|
|
@@ -8,65 +8,16 @@ to wait it out.
|
|
|
8
8
|
"""
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
|
-
import json
|
|
12
|
-
import os
|
|
13
|
-
import subprocess
|
|
14
|
-
import sys
|
|
15
11
|
from pathlib import Path
|
|
16
|
-
from textwrap import dedent
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def _make_options_dir(root: Path, *, generated_at: str | None = None) -> Path:
|
|
20
|
-
options_dir = root / 'opts'
|
|
21
|
-
schemas_dir = options_dir / 'schemas' / 'test-ns'
|
|
22
|
-
values_dir = options_dir / 'values' / 'test-ns'
|
|
23
|
-
schemas_dir.mkdir(parents=True)
|
|
24
|
-
values_dir.mkdir(parents=True)
|
|
25
|
-
|
|
26
|
-
schemas_dir.joinpath('schema.json').write_text(
|
|
27
|
-
json.dumps(
|
|
28
|
-
{
|
|
29
|
-
'version': '1.0',
|
|
30
|
-
'type': 'object',
|
|
31
|
-
'properties': {
|
|
32
|
-
'enabled': {
|
|
33
|
-
'type': 'boolean',
|
|
34
|
-
'default': False,
|
|
35
|
-
'description': 'Enabled',
|
|
36
|
-
},
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
),
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
values: dict = {'options': {'enabled': True}}
|
|
43
|
-
if generated_at is not None:
|
|
44
|
-
values['generated_at'] = generated_at
|
|
45
|
-
values_dir.joinpath('values.json').write_text(json.dumps(values))
|
|
46
|
-
|
|
47
|
-
return options_dir
|
|
48
12
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
env = os.environ.copy()
|
|
52
|
-
env['SENTRY_OPTIONS_DIR'] = str(options_dir)
|
|
53
|
-
env.update(env_extra)
|
|
54
|
-
result = subprocess.run(
|
|
55
|
-
[sys.executable, '-c', dedent(script)],
|
|
56
|
-
env=env,
|
|
57
|
-
capture_output=True,
|
|
58
|
-
text=True,
|
|
59
|
-
timeout=timeout,
|
|
60
|
-
)
|
|
61
|
-
assert result.returncode == 0, (
|
|
62
|
-
f"Script failed.\nstdout: {result.stdout}\nstderr: {result.stderr}"
|
|
63
|
-
)
|
|
13
|
+
from conftest import make_options_dir
|
|
14
|
+
from conftest import run_isolated
|
|
64
15
|
|
|
65
16
|
|
|
66
17
|
def test_propagation_callback_fires_on_generated_at_change(tmp_path: Path) -> None:
|
|
67
|
-
options_dir =
|
|
18
|
+
options_dir = make_options_dir(tmp_path, generated_at='2024-01-21T18:30:00+00:00')
|
|
68
19
|
values_file = options_dir / 'values' / 'test-ns' / 'values.json'
|
|
69
|
-
|
|
20
|
+
run_isolated(
|
|
70
21
|
f"""\
|
|
71
22
|
import json
|
|
72
23
|
from sentry_options import init, options
|
|
@@ -90,9 +41,9 @@ def test_propagation_callback_fires_on_generated_at_change(tmp_path: Path) -> No
|
|
|
90
41
|
|
|
91
42
|
|
|
92
43
|
def test_get_forced_sees_change_without_waiting(tmp_path: Path) -> None:
|
|
93
|
-
options_dir =
|
|
44
|
+
options_dir = make_options_dir(tmp_path)
|
|
94
45
|
values_file = options_dir / 'values' / 'test-ns' / 'values.json'
|
|
95
|
-
|
|
46
|
+
run_isolated(
|
|
96
47
|
f"""\
|
|
97
48
|
import json
|
|
98
49
|
from sentry_options import init, options
|
|
@@ -114,9 +65,9 @@ def test_get_forced_sees_change_without_waiting(tmp_path: Path) -> None:
|
|
|
114
65
|
|
|
115
66
|
|
|
116
67
|
def test_propagation_callback_not_called_without_generated_at(tmp_path: Path) -> None:
|
|
117
|
-
options_dir =
|
|
68
|
+
options_dir = make_options_dir(tmp_path)
|
|
118
69
|
values_file = options_dir / 'values' / 'test-ns' / 'values.json'
|
|
119
|
-
|
|
70
|
+
run_isolated(
|
|
120
71
|
f"""\
|
|
121
72
|
import json
|
|
122
73
|
from sentry_options import init, options
|
|
@@ -137,9 +88,9 @@ def test_propagation_callback_not_called_without_generated_at(tmp_path: Path) ->
|
|
|
137
88
|
|
|
138
89
|
|
|
139
90
|
def test_propagation_callback_exception_does_not_crash(tmp_path: Path) -> None:
|
|
140
|
-
options_dir =
|
|
91
|
+
options_dir = make_options_dir(tmp_path, generated_at='2024-01-21T18:30:00+00:00')
|
|
141
92
|
values_file = options_dir / 'values' / 'test-ns' / 'values.json'
|
|
142
|
-
|
|
93
|
+
run_isolated(
|
|
143
94
|
f"""\
|
|
144
95
|
import json
|
|
145
96
|
from sentry_options import init, options
|
|
@@ -161,9 +112,9 @@ def test_propagation_callback_exception_does_not_crash(tmp_path: Path) -> None:
|
|
|
161
112
|
|
|
162
113
|
|
|
163
114
|
def test_propagation_callback_receives_multiple_updates(tmp_path: Path) -> None:
|
|
164
|
-
options_dir =
|
|
115
|
+
options_dir = make_options_dir(tmp_path, generated_at='2024-01-21T18:00:00+00:00')
|
|
165
116
|
values_file = options_dir / 'values' / 'test-ns' / 'values.json'
|
|
166
|
-
|
|
117
|
+
run_isolated(
|
|
167
118
|
f"""\
|
|
168
119
|
import json
|
|
169
120
|
from sentry_options import init, options
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""E2E tests for manual refresh and the configurable staleness threshold.
|
|
2
|
+
|
|
3
|
+
Each test spins up a subprocess with its own ``SENTRY_OPTIONS_DIR`` so the
|
|
4
|
+
Rust ``OnceLock`` starts fresh and ``init(refresh_threshold=...)`` takes
|
|
5
|
+
effect.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from conftest import make_options_dir
|
|
12
|
+
from conftest import run_isolated
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_manual_refresh_with_disabled_threshold(tmp_path: Path) -> None:
|
|
16
|
+
options_dir = make_options_dir(tmp_path)
|
|
17
|
+
values_file = options_dir / 'values' / 'test-ns' / 'values.json'
|
|
18
|
+
run_isolated(
|
|
19
|
+
f"""\
|
|
20
|
+
import json
|
|
21
|
+
from sentry_options import init, options, refresh
|
|
22
|
+
|
|
23
|
+
init(refresh_threshold=None)
|
|
24
|
+
opts = options("test-ns")
|
|
25
|
+
assert opts.get("enabled") is True
|
|
26
|
+
|
|
27
|
+
with open("{values_file}", "w") as f:
|
|
28
|
+
json.dump({{"options": {{"enabled": False}}}}, f)
|
|
29
|
+
|
|
30
|
+
# With refresh-on-read disabled, reads never pick up the change.
|
|
31
|
+
assert opts.get("enabled") is True
|
|
32
|
+
# A manual refresh does.
|
|
33
|
+
assert refresh() is True
|
|
34
|
+
assert opts.get("enabled") is False
|
|
35
|
+
# Nothing changed since the last refresh.
|
|
36
|
+
assert refresh() is False
|
|
37
|
+
""",
|
|
38
|
+
options_dir,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_refresh_before_init_raises(tmp_path: Path) -> None:
|
|
43
|
+
options_dir = make_options_dir(tmp_path)
|
|
44
|
+
run_isolated(
|
|
45
|
+
"""\
|
|
46
|
+
from sentry_options import NotInitializedError, refresh
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
refresh()
|
|
50
|
+
except NotInitializedError:
|
|
51
|
+
pass
|
|
52
|
+
else:
|
|
53
|
+
raise AssertionError("Expected NotInitializedError")
|
|
54
|
+
""",
|
|
55
|
+
options_dir,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_init_rejects_negative_refresh_threshold(tmp_path: Path) -> None:
|
|
60
|
+
options_dir = make_options_dir(tmp_path)
|
|
61
|
+
run_isolated(
|
|
62
|
+
"""\
|
|
63
|
+
from sentry_options import init
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
init(refresh_threshold=-1.0)
|
|
67
|
+
except ValueError:
|
|
68
|
+
pass
|
|
69
|
+
else:
|
|
70
|
+
raise AssertionError("Expected ValueError")
|
|
71
|
+
""",
|
|
72
|
+
options_dir,
|
|
73
|
+
)
|