8 Commits

Author SHA1 Message Date
a53aab3a33 Merge pull request #13 from onyx-and-iris/dependabot/pip/requests-2.33.0
Bump requests from 2.32.5 to 2.33.0
2026-04-01 12:52:26 +01:00
dependabot[bot]
eee6d18bda Bump requests from 2.32.5 to 2.33.0
Bumps [requests](https://github.com/psf/requests) from 2.32.5 to 2.33.0.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.32.5...v2.33.0)

---
updated-dependencies:
- dependency-name: requests
  dependency-version: 2.33.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-01 11:51:28 +00:00
78f9fc7f53 add entry point docstring 2026-04-01 12:28:05 +01:00
f75cb98d67 add v2.0.0 to CHANGELOG 2026-04-01 12:25:01 +01:00
d6a26fda34 update README 2026-03-25 05:47:02 +00:00
efde969527 upd CHANGELOG with note about {Client}.wait() 2026-03-25 05:46:48 +00:00
336a52d172 add block_forever example
update example READMEs

add script entry point for block_forever example
add poe task block-forever
2026-03-25 05:46:14 +00:00
e1fa0eea79 {Client}.sio is now a private attribute
{Client}.wait() added to expose {Client}._sio.wait() and {Client}._sio.sleep()
2026-03-25 05:40:20 +00:00
10 changed files with 151 additions and 31 deletions

View File

@@ -8,7 +8,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
The following changes are proposals for v2.
- [x]
## [2.0.0] - 2026-04-01
### Added
- wait() method on the Client class, it's a convenience method that exposes {socketio.Client}.sleep() and {socketio.Client}.wait().
### Fixed
@@ -22,8 +28,7 @@ The following changes are proposals for v2.
### Changed
- loguru is now used for logging. See the events example for a demonstration.
- loguru is now used for logging. See the examples for a demonstration.
## [1.1.2] - 2024-11-06

View File

@@ -41,23 +41,28 @@ def main():
client.obs.on('twitch_account', on_twitch_event)
# run for 30 seconds then disconnect client from server
client.sio.sleep(30)
client.wait(30)
if __name__ == '__main__':
main()
```
> note: From the [SocketIO docs](https://python-socketio.readthedocs.io/en/latest/client.html#managing-background-tasks), `client.sio.wait()` may be used if your application has nothing to do in the main thread.
### Client class
*`streamlabsio.connect(*, token: str, raw: bool = False)`*
*`streamlabsio.connect(*, token: str, raw: bool = False) -> Client`*
The following keyword arguments may be passed:
- token: Streamlabs SocketIO api token.
- raw: Receive raw data messages as json objects.
#### methods
*wait(self, seconds: float | None = None) -> None*
- float: Time in seconds to block the main thread
- If None, the method will block indefinitely.
### Event Data Attributes
For event data you may inspect the available attributes using `attrs()`.

View File

@@ -0,0 +1,18 @@
## About
This example demonstrates the following:
- How to register callback functions for different kinds of events.
- How to view the logs emitted by the streamlabsio library.
- How to use the raw data returned by the callback functions.
- How to use {Client}.wait() to block the main thread indefinitely.
## Configure
The script expects the Streamlabs token to be loaded into the environment with key `STREAMLABS_TOKEN`.
If you're running the script with `poetry poe block-forever` then poe is configured to load a `.env` file in the root of the repository.
## Use
Run the script and trigger any of the events with `Test Widgets` in the Streamlabs GUI.

View File

@@ -0,0 +1,55 @@
import os
from loguru import logger
import streamlabsio
logger.enable('streamlabsio')
def on_streamlabs_event(event, data):
match event:
case 'donation':
print('{name} donated {amount}! With message: {message}'.format(**data))
def on_twitch_event(event, data):
event_message = {
'follow': 'Received follow from {name}',
'bits': '{name} donated {amount} bits! With message: {message}',
'subscription': '{name} just subscribed for {months} months!',
'raid': '{name} just raided with {raiders} raiders!',
'host': '{name} just hosted with {viewers} viewers!',
}
if event in event_message:
print(event_message[event].format(**data))
def on_youtube_event(event, data):
event_message = {
'follow': 'Received follow from {name}',
'superchat': '{name} donated {displayString} with a superchat! With comment: {comment}',
'subscription': '{name} just subscribed for {months} months!',
}
if event in event_message:
print(event_message[event].format(**data))
def main():
try:
with streamlabsio.connect(
token=os.getenv('STREAMLABS_TOKEN'), raw=True
) as client:
client.obs.on('streamlabs', on_streamlabs_event)
client.obs.on('twitch_account', on_twitch_event)
client.obs.on('youtube_account', on_youtube_event)
client.wait()
except KeyboardInterrupt:
pass
if __name__ == '__main__':
main()

View File

@@ -1,18 +1,18 @@
## About
To view the logs emitted by the streamlabsio library simply add the following to your code:
This example demonstrates the following:
```python
from loguru import logger
logger.enable('streamlabsio')
```
- How to register callback functions for different kinds of events.
- How to view the logs emitted by the streamlabsio library.
- How to print the attributes of an event data dataclass using the *attrs()* method.
- How to use dataclass methods on the dataclasses, this case *asdict*.
- How to use {Client}.wait() to block the main thread for a period of time.
## Configure
The script expects the Streamlabs token to be loaded into the environment with key `STREAMLABS_TOKEN`.
If you're running the script with `poetry poe` then poe is configured to load a `.env` file in the root of the repository.
If you're running the script with `poetry poe events` then poe is configured to load a `.env` file in the root of the repository.
## Use

View File

@@ -9,6 +9,8 @@ logger.enable('streamlabsio')
def on_streamlabs_event(event, data):
print(data.attrs())
match event:
case 'donation':
print(f'{data.name} donated {data.amount}! With message: {data.message}')
@@ -44,7 +46,7 @@ def main():
client.obs.on('twitch_account', on_twitch_event)
client.obs.on('youtube_account', on_youtube_event)
client.sio.sleep(30)
client.wait(30)
if __name__ == '__main__':

18
poetry.lock generated
View File

@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 2.3.2 and should not be changed by hand.
# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand.
[[package]]
name = "bidict"
@@ -220,7 +220,7 @@ colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""}
win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""}
[package.extras]
dev = ["Sphinx (==8.1.3) ; python_version >= \"3.11\"", "build (==1.2.2) ; python_version >= \"3.11\"", "colorama (==0.4.5) ; python_version < \"3.8\"", "colorama (==0.4.6) ; python_version >= \"3.8\"", "exceptiongroup (==1.1.3) ; python_version >= \"3.7\" and python_version < \"3.11\"", "freezegun (==1.1.0) ; python_version < \"3.8\"", "freezegun (==1.5.0) ; python_version >= \"3.8\"", "mypy (==0.910) ; python_version < \"3.6\"", "mypy (==0.971) ; python_version == \"3.6\"", "mypy (==1.13.0) ; python_version >= \"3.8\"", "mypy (==1.4.1) ; python_version == \"3.7\"", "myst-parser (==4.0.0) ; python_version >= \"3.11\"", "pre-commit (==4.0.1) ; python_version >= \"3.9\"", "pytest (==6.1.2) ; python_version < \"3.8\"", "pytest (==8.3.2) ; python_version >= \"3.8\"", "pytest-cov (==2.12.1) ; python_version < \"3.8\"", "pytest-cov (==5.0.0) ; python_version == \"3.8\"", "pytest-cov (==6.0.0) ; python_version >= \"3.9\"", "pytest-mypy-plugins (==1.9.3) ; python_version >= \"3.6\" and python_version < \"3.8\"", "pytest-mypy-plugins (==3.1.0) ; python_version >= \"3.8\"", "sphinx-rtd-theme (==3.0.2) ; python_version >= \"3.11\"", "tox (==3.27.1) ; python_version < \"3.8\"", "tox (==4.23.2) ; python_version >= \"3.8\"", "twine (==6.0.1) ; python_version >= \"3.11\""]
dev = ["Sphinx (==8.1.3) ; python_version >= \"3.11\"", "build (==1.2.2) ; python_version >= \"3.11\"", "colorama (==0.4.5) ; python_version < \"3.8\"", "colorama (==0.4.6) ; python_version >= \"3.8\"", "exceptiongroup (==1.1.3) ; python_version >= \"3.7\" and python_version < \"3.11\"", "freezegun (==1.1.0) ; python_version < \"3.8\"", "freezegun (==1.5.0) ; python_version >= \"3.8\"", "mypy (==v0.910) ; python_version < \"3.6\"", "mypy (==v0.971) ; python_version == \"3.6\"", "mypy (==v1.13.0) ; python_version >= \"3.8\"", "mypy (==v1.4.1) ; python_version == \"3.7\"", "myst-parser (==4.0.0) ; python_version >= \"3.11\"", "pre-commit (==4.0.1) ; python_version >= \"3.9\"", "pytest (==6.1.2) ; python_version < \"3.8\"", "pytest (==8.3.2) ; python_version >= \"3.8\"", "pytest-cov (==2.12.1) ; python_version < \"3.8\"", "pytest-cov (==5.0.0) ; python_version == \"3.8\"", "pytest-cov (==6.0.0) ; python_version >= \"3.9\"", "pytest-mypy-plugins (==1.9.3) ; python_version >= \"3.6\" and python_version < \"3.8\"", "pytest-mypy-plugins (==3.1.0) ; python_version >= \"3.8\"", "sphinx-rtd-theme (==3.0.2) ; python_version >= \"3.11\"", "tox (==3.27.1) ; python_version < \"3.8\"", "tox (==4.23.2) ; python_version >= \"3.8\"", "twine (==6.0.1) ; python_version >= \"3.11\""]
[[package]]
name = "observable"
@@ -281,25 +281,25 @@ docs = ["furo", "sphinx"]
[[package]]
name = "requests"
version = "2.32.5"
version = "2.33.1"
description = "Python HTTP for Humans."
optional = false
python-versions = ">=3.9"
python-versions = ">=3.10"
groups = ["main"]
files = [
{file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"},
{file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"},
{file = "requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a"},
{file = "requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517"},
]
[package.dependencies]
certifi = ">=2017.4.17"
certifi = ">=2023.5.7"
charset_normalizer = ">=2,<4"
idna = ">=2.5,<4"
urllib3 = ">=1.21.1,<3"
urllib3 = ">=1.26,<3"
[package.extras]
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
use-chardet-on-py3 = ["chardet (>=3.0.2,<8)"]
[[package]]
name = "ruff"

View File

@@ -27,6 +27,7 @@ envfile = ".env"
[tool.poe.tasks]
events.script = "scripts:ex_events"
block-forever.script = "scripts:ex_block_forever"
[tool.ruff]
exclude = [

View File

@@ -6,3 +6,11 @@ from pathlib import Path
def ex_events():
scriptpath = Path.cwd() / 'examples' / 'events' / '.'
subprocess.run([sys.executable, str(scriptpath)])
def ex_block_forever():
scriptpath = Path.cwd() / 'examples' / 'block_forever' / '.'
try:
subprocess.run([sys.executable, str(scriptpath)])
except KeyboardInterrupt:
sys.exit(0)

View File

@@ -17,15 +17,15 @@ class Client:
self._logger = logger.bind(name=self.__class__.__name__)
self._token = token
self._raw = raw
self.sio = socketio.Client()
self.sio.on('connect', self._connect_handler)
self.sio.on('event', self._event_handler)
self.sio.on('disconnect', self._disconnect_handler)
self._sio = socketio.Client()
self._sio.on('connect', self._connect_handler)
self._sio.on('event', self._event_handler)
self._sio.on('disconnect', self._disconnect_handler)
self.obs = Observable()
def __enter__(self) -> 'Client':
try:
self.sio.connect(f'https://sockets.streamlabs.com?token={self._token}')
self._sio.connect(f'https://sockets.streamlabs.com?token={self._token}')
except socketio.exceptions.ConnectionError as e:
self._logger.exception('Connection to Streamlabs Socket API failed')
ERR_MSG = 'Failed to connect to Streamlabs Socket API. Please check your token and network connection.'
@@ -33,14 +33,31 @@ class Client:
return self
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
self.sio.disconnect()
self._sio.disconnect()
def wait(self, seconds: float | None = None) -> None:
"""Exposes the wait and sleep methods of the socketio client to allow the user to keep the connection alive.
Args:
seconds (float | None): If None, the method will block indefinitely.
Returns:
None
"""
if seconds is None:
self._sio.wait()
else:
self._sio.sleep(seconds)
def _connect_handler(self) -> None:
self._logger.info('Connected to Streamlabs Socket API')
def _event_handler(self, data: dict) -> None:
"""
Handles incoming events and triggers corresponding OBS actions.
Handles incoming events and triggers the callback functions.
Args:
data (dict): The event data containing information about the event.
Expected keys:
@@ -77,4 +94,13 @@ class Client:
def request_client_object(*, token: str, raw: bool = False) -> Client:
"""Entry point for users to request a Client object.
Args:
token (str): The authentication token for the Streamlabs Socket API.
raw (bool, optional): If True, the client will return raw event data. Defaults to False.
Returns:
Client: An instance of the Client class.
"""
return Client(token=token, raw=raw)