Skip to content

Commit 7397a7e

Browse files
committed
Add new methods set_config_filename and set_config_content
1 parent 35b75fa commit 7397a7e

4 files changed

Lines changed: 113 additions & 0 deletions

File tree

src/pylsl/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
from .util import library_version as library_version
3535
from .util import library_info as library_info
3636
from .util import local_clock as local_clock
37+
from .util import set_config_filename as set_config_filename
38+
from .util import set_config_content as set_config_content
3739
from .lib import cf_int8 as cf_int8
3840
from .lib import cf_int16 as cf_int16
3941
from .lib import cf_int32 as cf_int32

src/pylsl/lib/__init__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,15 @@ def find_liblsl_libraries(verbose=False):
314314
lib.lsl_create_continuous_resolver_byprop.restype = ctypes.c_void_p
315315
except Exception:
316316
print("pylsl: ContinuousResolver not (fully) available in your liblsl version.")
317+
# noinspection PyBroadException
318+
try:
319+
lib.lsl_set_config_filename.argtypes = [ctypes.c_char_p]
320+
lib.lsl_set_config_filename.restype = None
321+
lib.lsl_set_config_content.argtypes = [ctypes.c_char_p]
322+
lib.lsl_set_config_content.restype = None
323+
except Exception:
324+
# Available in liblsl >= 1.17.7; older versions don't expose these.
325+
pass
317326

318327

319328
# int64 support on windows and 32bit OSes isn't there yet

src/pylsl/util.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,36 @@ def library_info():
6060
return lib.lsl_library_info().decode("utf-8")
6161

6262

63+
def set_config_filename(filename: str) -> None:
64+
"""Set the path of the configuration file to be used by liblsl.
65+
66+
Must be called before any other LSL function; the configuration is loaded
67+
lazily on first use, so calling this afterwards has no effect.
68+
"""
69+
if not hasattr(lib, "lsl_set_config_filename"):
70+
raise NotImplementedError(
71+
"lsl_set_config_filename is not available in your liblsl version "
72+
"(requires liblsl >= 1.17.7)."
73+
)
74+
lib.lsl_set_config_filename(filename.encode("utf-8"))
75+
76+
77+
def set_config_content(content: str) -> None:
78+
"""Set the configuration content (as an INI string) to be used by liblsl.
79+
80+
Must be called before any other LSL function; the configuration is loaded
81+
lazily on first use, so calling this afterwards has no effect. When set,
82+
this content takes precedence over any configuration file. The content is
83+
discarded after liblsl has initialized.
84+
"""
85+
if not hasattr(lib, "lsl_set_config_content"):
86+
raise NotImplementedError(
87+
"lsl_set_config_content is not available in your liblsl version "
88+
"(requires liblsl >= 1.17.7)."
89+
)
90+
lib.lsl_set_config_content(content.encode("utf-8"))
91+
92+
6393
def local_clock():
6494
"""Obtain a local system time stamp in seconds.
6595

test/test_set_config.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
"""Tests for set_config_filename and set_config_content.
2+
3+
Both functions must run before liblsl's config is loaded (on first use), so
4+
the effect-verifying tests run in a fresh Python subprocess. In-process smoke
5+
tests only verify that the call path works — they may be no-ops if liblsl has
6+
already initialized in the current process.
7+
"""
8+
9+
import subprocess
10+
import sys
11+
import textwrap
12+
13+
import pytest
14+
15+
import pylsl
16+
17+
18+
def test_set_config_content_accepts_string():
19+
# Safe to call even after init — content is simply ignored once liblsl has loaded.
20+
pylsl.set_config_content("[lab]\nSessionID = ignored_after_init\n")
21+
22+
23+
def test_set_config_filename_accepts_string():
24+
pylsl.set_config_filename("/nonexistent/path/to/lsl.cfg")
25+
26+
27+
def test_set_config_content_rejects_non_string():
28+
with pytest.raises(AttributeError):
29+
pylsl.set_config_content(b"not a str")
30+
31+
32+
def test_set_config_filename_rejects_non_string():
33+
with pytest.raises(AttributeError):
34+
pylsl.set_config_filename(12345)
35+
36+
37+
def _run_in_subprocess(script: str) -> str:
38+
result = subprocess.run(
39+
[sys.executable, "-c", script],
40+
capture_output=True,
41+
text=True,
42+
check=True,
43+
)
44+
return result.stdout.strip()
45+
46+
47+
def test_set_config_content_applies_session_id():
48+
"""Setting content before any LSL call should update the session id that
49+
outlets inherit from the library config."""
50+
session_id = "pytest_content_session"
51+
script = textwrap.dedent(f"""
52+
import pylsl
53+
pylsl.set_config_content('[lab]\\nSessionID = {session_id}\\n')
54+
info = pylsl.StreamInfo('T', 'EEG', 1, 100, 'float32', 'src')
55+
outlet = pylsl.StreamOutlet(info)
56+
print(outlet.get_info().session_id())
57+
""")
58+
assert _run_in_subprocess(script) == session_id
59+
60+
61+
def test_set_config_filename_applies_session_id(tmp_path):
62+
session_id = "pytest_filename_session"
63+
cfg = tmp_path / "lsl.cfg"
64+
cfg.write_text(f"[lab]\nSessionID = {session_id}\n")
65+
script = textwrap.dedent(f"""
66+
import pylsl
67+
pylsl.set_config_filename({str(cfg)!r})
68+
info = pylsl.StreamInfo('T', 'EEG', 1, 100, 'float32', 'src')
69+
outlet = pylsl.StreamOutlet(info)
70+
print(outlet.get_info().session_id())
71+
""")
72+
assert _run_in_subprocess(script) == session_id

0 commit comments

Comments
 (0)