More request tests added.

development dependencies added to setup.py

fix error in __init__

kind parameter for get_input_list in reqclient now optional.

request tests create/destroy test scenes on setup/teardown.

license, isort, black badges added to readme.
This commit is contained in:
onyx-and-iris 2022-07-30 16:37:07 +01:00
parent 87c4e3cdcd
commit 064a4aa11d
7 changed files with 199 additions and 82 deletions

View File

@ -1,3 +1,7 @@
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://github.com/aatikturk/obsstudio_sdk/blob/main/LICENSE)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![Imports: isort](https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336)](https://pycqa.github.io/isort/)
# A Python SDK for OBS Studio WebSocket v5.0 # A Python SDK for OBS Studio WebSocket v5.0
This is a wrapper around OBS Websocket. This is a wrapper around OBS Websocket.
@ -36,7 +40,7 @@ Import and start using, keyword arguments are as follows:
- `port`: port to access server - `port`: port to access server
- `password`: obs websocket server password - `password`: obs websocket server password
Example `__main__.py` Example `__main__.py`:
```python ```python
import obsstudio_sdk as obs import obsstudio_sdk as obs
@ -48,6 +52,87 @@ cl = obs.ReqClient(host='localhost', port=4455, password='mystrongpass')
cl.toggle_input_mute('Mic/Aux') cl.toggle_input_mute('Mic/Aux')
``` ```
### Requests
Method names for requests match the API calls but snake cased.
example:
```python
cl = ReqClient()
# GetVersion
resp = cl.get_version()
# SetCurrentProgramScene
cl.set_current_program_scene()
```
For a full list of requests refer to [Requests](https://github.com/obsproject/obs-websocket/blob/master/docs/generated/protocol.md#requests)
### Events
When registering a function callback use the name of the expected API event in snake case form.
example:
```python
cl = EventClient()
def scene_created(data):
...
# SceneCreated
cl.callback.register(scene_created)
def input_mute_state_changed(data):
...
# InputMuteStateChanged
cl.callback.register(input_mute_state_changed)
# returns a list of currently registered events
print(cl.callback.get())
```
cl.callback accepts both a single or list of functions.
For a full list of events refer to [Events](https://github.com/obsproject/obs-websocket/blob/master/docs/generated/protocol.md#events)
### Attributes
For both request responses and event data you may inspect the available attributes using `attrs()`.
example:
```python
resp = cl.get_version()
print(resp.attrs())
def scene_created(data):
print(data.attrs())
```
### Errors
If a request fails an `OBSSDKError` will be raised with a status code.
For a full list of status codes refer to [Codes](https://github.com/obsproject/obs-websocket/blob/master/docs/generated/protocol.md#requeststatus)
### Tests
First install development dependencies:
`pip install -e .['dev']`
To run all tests:
```
pytest -v
```
### Official Documentation ### Official Documentation
For the full documentation:
- [OBS Websocket SDK](https://github.com/obsproject/obs-websocket/blob/master/docs/generated/protocol.md#obs-websocket-501-protocol) - [OBS Websocket SDK](https://github.com/obsproject/obs-websocket/blob/master/docs/generated/protocol.md#obs-websocket-501-protocol)

View File

@ -1,4 +1,4 @@
from .events import EventClient from .events import EventClient
from .reqs import ReqClient from .reqs import ReqClient
__ALL__ = ["ReqClient", "EventsClient"] __ALL__ = ["ReqClient", "EventClient"]

View File

@ -17,7 +17,7 @@ class Callback:
return [to_camel_case(fn.__name__[2:]) for fn in self._callbacks] return [to_camel_case(fn.__name__[2:]) for fn in self._callbacks]
def trigger(self, event, data): def trigger(self, event, data):
"""trigger callback on update""" """trigger callback on event"""
for fn in self._callbacks: for fn in self._callbacks:
if fn.__name__ == f"on_{to_snake_case(event)}": if fn.__name__ == f"on_{to_snake_case(event)}":
@ -36,7 +36,7 @@ class Callback:
self._callbacks.append(fns) self._callbacks.append(fns)
def deregister(self, fns: Union[Iterable, Callable]): def deregister(self, fns: Union[Iterable, Callable]):
"""deregisters a callback from _callbacks""" """deregisters callback functions"""
try: try:
iterator = iter(fns) iterator = iter(fns)

View File

@ -592,7 +592,7 @@ class ReqClient:
} }
self.send("SetSceneSceneTransitionOverride", payload) self.send("SetSceneSceneTransitionOverride", payload)
def get_input_list(self, kind): def get_input_list(self, kind=None):
""" """
Gets a list of all inputs in OBS. Gets a list of all inputs in OBS.

View File

@ -1,39 +1,50 @@
import pathlib import pathlib
from setuptools import setup, find_packages
from setuptools import find_packages, setup
HERE = pathlib.Path(__file__).parent HERE = pathlib.Path(__file__).parent
VERSION = '1.0.1' VERSION = "1.0.1"
PACKAGE_NAME = 'obsstudio_sdk' PACKAGE_NAME = "obsstudio_sdk"
AUTHOR = 'Adem Atikturk' AUTHOR = "Adem Atikturk"
AUTHOR_EMAIL = 'aatikturk@gmail.com' AUTHOR_EMAIL = "aatikturk@gmail.com"
URL = 'https://github.com/aatikturk/obsstudio_sdk' URL = "https://github.com/aatikturk/obsstudio_sdk"
LICENSE = 'GNU General Public License v3.0' LICENSE = "GNU General Public License v3.0"
DESCRIPTION = 'A Python SDK for OBS Studio WebSocket v5.0' DESCRIPTION = "A Python SDK for OBS Studio WebSocket v5.0"
LONG_DESCRIPTION = (HERE / "README.md").read_text() LONG_DESCRIPTION = (HERE / "README.md").read_text()
LONG_DESC_TYPE = "text/markdown" LONG_DESC_TYPE = "text/markdown"
# Dependencies for the package # Dependencies for the package
INSTALL_REQUIRES = [ INSTALL_REQUIRES = ["websocket-client"]
'websocket-client'
] # Development dependencies
EXTRAS_REQUIRES = {
"dev": [
"pytest",
"pytest-randomly",
"black",
"isort",
]
}
# Python version requirement # Python version requirement
PYTHON_REQUIRES = '>=3.11' PYTHON_REQUIRES = ">=3.11"
setup(name=PACKAGE_NAME, setup(
version=VERSION, name=PACKAGE_NAME,
description=DESCRIPTION, version=VERSION,
long_description=LONG_DESCRIPTION, description=DESCRIPTION,
long_description_content_type=LONG_DESC_TYPE, long_description=LONG_DESCRIPTION,
author=AUTHOR, long_description_content_type=LONG_DESC_TYPE,
license=LICENSE, author=AUTHOR,
author_email=AUTHOR_EMAIL, license=LICENSE,
url=URL, author_email=AUTHOR_EMAIL,
install_requires=INSTALL_REQUIRES, url=URL,
python_requires=PYTHON_REQUIRES, install_requires=INSTALL_REQUIRES,
packages=find_packages() extras_require=EXTRAS_REQUIRES,
) python_requires=PYTHON_REQUIRES,
packages=find_packages(),
)

View File

@ -1,11 +1,18 @@
import time
import obsstudio_sdk as obs import obsstudio_sdk as obs
req_cl = obs.ReqClient() req_cl = obs.ReqClient()
def setup_module(): def setup_module():
pass req_cl.create_scene("START_TEST")
req_cl.create_scene("BRB_TEST")
req_cl.create_scene("END_TEST")
def teardown_module(): def teardown_module():
req_cl.remove_scene("START_TEST")
req_cl.remove_scene("BRB_TEST")
req_cl.remove_scene("END_TEST")
req_cl.base_client.ws.close() req_cl.base_client.ws.close()

View File

@ -11,34 +11,13 @@ class TestRequests:
assert hasattr(resp, "obs_version") assert hasattr(resp, "obs_version")
assert hasattr(resp, "obs_web_socket_version") assert hasattr(resp, "obs_web_socket_version")
@pytest.mark.parametrize(
"scene",
[
("START"),
("BRB"),
("END"),
],
)
def test_current_program_scene(self, scene):
req_cl.set_current_program_scene(scene)
resp = req_cl.get_current_program_scene()
assert resp.current_program_scene_name == scene
@pytest.mark.parametrize(
"state",
[
(False),
(True),
],
)
def test_studio_mode_enabled(self, state):
req_cl.set_studio_mode_enabled(state)
resp = req_cl.get_studio_mode_enabled()
assert resp.studio_mode_enabled == state
def test_get_hot_key_list(self): def test_get_hot_key_list(self):
resp = req_cl.get_hot_key_list() resp = req_cl.get_hot_key_list()
hotkey_list = [ obsbasic_hotkey_list = [
"OBSBasic.SelectScene",
"OBSBasic.SelectScene",
"OBSBasic.SelectScene",
"OBSBasic.SelectScene",
"OBSBasic.StartStreaming", "OBSBasic.StartStreaming",
"OBSBasic.StopStreaming", "OBSBasic.StopStreaming",
"OBSBasic.ForceStopStreaming", "OBSBasic.ForceStopStreaming",
@ -59,29 +38,8 @@ class TestRequests:
"OBSBasic.ResetStats", "OBSBasic.ResetStats",
"OBSBasic.Screenshot", "OBSBasic.Screenshot",
"OBSBasic.SelectedSourceScreenshot", "OBSBasic.SelectedSourceScreenshot",
"libobs.mute",
"libobs.unmute",
"libobs.push-to-mute",
"libobs.push-to-talk",
"libobs.mute",
"libobs.unmute",
"libobs.push-to-mute",
"libobs.push-to-talk",
"OBSBasic.SelectScene",
"OBSBasic.SelectScene",
"OBSBasic.SelectScene",
"OBSBasic.SelectScene",
"libobs.show_scene_item.Colour Source 2",
"libobs.hide_scene_item.Colour Source 2",
"libobs.show_scene_item.Colour Source 3",
"libobs.hide_scene_item.Colour Source 3",
"libobs.show_scene_item.Colour Source",
"libobs.hide_scene_item.Colour Source",
"OBSBasic.QuickTransition.1",
"OBSBasic.QuickTransition.2",
"OBSBasic.QuickTransition.3",
] ]
assert all(x in resp.hotkeys for x in hotkey_list) assert all(x in resp.hotkeys for x in obsbasic_hotkey_list)
@pytest.mark.parametrize( @pytest.mark.parametrize(
"name,data", "name,data",
@ -103,9 +61,53 @@ class TestRequests:
resp = req_cl.get_profile_list() resp = req_cl.get_profile_list()
assert "test" not in resp.profiles assert "test" not in resp.profiles
def test_stream_service_settings(self):
settings = {
"server": "rtmp://addressofrtmpserver",
"key": "live_myvery_secretkey",
}
req_cl.set_stream_service_settings(
"rtmp_common",
settings,
)
resp = req_cl.get_stream_service_settings()
assert resp.stream_service_type == "rtmp_common"
assert resp.stream_service_settings == {
"server": "rtmp://addressofrtmpserver",
"key": "live_myvery_secretkey",
}
@pytest.mark.parametrize(
"scene",
[
("START_TEST"),
("BRB_TEST"),
("END_TEST"),
],
)
def test_current_program_scene(self, scene):
req_cl.set_current_program_scene(scene)
resp = req_cl.get_current_program_scene()
assert resp.current_program_scene_name == scene
def test_input_list(self):
req_cl.create_input(
"START_TEST", "test", "color_source_v3", {"color": 4294945535}, True
)
resp = req_cl.get_input_list()
assert {
"inputKind": "color_source_v3",
"inputName": "test",
"unversionedInputKind": "color_source",
} in resp.inputs
resp = req_cl.get_input_settings("test")
assert resp.input_kind == "color_source_v3"
assert resp.input_settings == {"color": 4294945535}
req_cl.remove_input("test")
def test_source_filter(self): def test_source_filter(self):
req_cl.create_source_filter("START", "test", "color_key_filter_v2") req_cl.create_source_filter("START_TEST", "test", "color_key_filter_v2")
resp = req_cl.get_source_filter_list("START") resp = req_cl.get_source_filter_list("START_TEST")
assert resp.filters == [ assert resp.filters == [
{ {
"filterEnabled": True, "filterEnabled": True,
@ -115,4 +117,16 @@ class TestRequests:
"filterSettings": {}, "filterSettings": {},
} }
] ]
req_cl.remove_source_filter("START", "test") req_cl.remove_source_filter("START_TEST", "test")
@pytest.mark.parametrize(
"state",
[
(False),
(True),
],
)
def test_studio_mode_enabled(self, state):
req_cl.set_studio_mode_enabled(state)
resp = req_cl.get_studio_mode_enabled()
assert resp.studio_mode_enabled == state