diff --git a/README.md b/README.md index ed015ab..0c339de 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ [![PyPI version](https://badge.fury.io/py/vban-cmd.svg)](https://badge.fury.io/py/vban-cmd) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/onyx-and-iris/vban-cmd-python/blob/dev/LICENSE) [![Poetry](https://img.shields.io/endpoint?url=https://python-poetry.org/badge/v0.json)](https://python-poetry.org/) -[![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/) +[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) ![Tests Status](./tests/basic.svg?dummy=8484744) ![Tests Status](./tests/banana.svg?dummy=8484744) ![Tests Status](./tests/potato.svg?dummy=8484744) diff --git a/__main__.py b/__main__.py index 2d5bc2b..5dbf100 100644 --- a/__main__.py +++ b/__main__.py @@ -6,27 +6,27 @@ class ManyThings: self.vban = vban def things(self): - self.vban.strip[0].label = "podmic" + self.vban.strip[0].label = 'podmic' self.vban.strip[0].mute = True print( - f"strip 0 ({self.vban.strip[0].label}) mute has been set to {self.vban.strip[0].mute}" + f'strip 0 ({self.vban.strip[0].label}) mute has been set to {self.vban.strip[0].mute}' ) def other_things(self): self.vban.bus[3].gain = -6.3 self.vban.bus[4].eq = True info = ( - f"bus 3 gain has been set to {self.vban.bus[3].gain}", - f"bus 4 eq has been set to {self.vban.bus[4].eq}", + f'bus 3 gain has been set to {self.vban.bus[3].gain}', + f'bus 4 eq has been set to {self.vban.bus[4].eq}', ) - print("\n".join(info)) + print('\n'.join(info)) def main(): - kind_id = "banana" + kind_id = 'banana' with vban_cmd.api( - kind_id, ip="gamepc.local", port=6980, streamname="Command1" + kind_id, ip='gamepc.local', port=6980, streamname='Command1' ) as vban: do = ManyThings(vban) do.things() @@ -35,12 +35,12 @@ def main(): # set many parameters at once vban.apply( { - "strip-2": {"A1": True, "B1": True, "gain": -6.0}, - "bus-2": {"mute": True}, - "vban-in-0": {"on": True}, + 'strip-2': {'A1': True, 'B1': True, 'gain': -6.0}, + 'bus-2': {'mute': True}, + 'vban-in-0': {'on': True}, } ) -if __name__ == "__main__": +if __name__ == '__main__': main() diff --git a/examples/gui/__main__.py b/examples/gui/__main__.py index df21d47..e14aa88 100644 --- a/examples/gui/__main__.py +++ b/examples/gui/__main__.py @@ -1,10 +1,10 @@ import logging +import tkinter as tk +from tkinter import ttk import vban_cmd logging.basicConfig(level=logging.DEBUG) -import tkinter as tk -from tkinter import ttk class App(tk.Tk): @@ -13,7 +13,7 @@ class App(tk.Tk): def __init__(self, vban): super().__init__() self.vban = vban - self.title(f"{vban} - version {vban.version}") + self.title(f'{vban} - version {vban.version}') self.vban.observer.add(self.on_ldirty) # create widget variables @@ -24,10 +24,10 @@ class App(tk.Tk): # initialize style table self.style = ttk.Style() - self.style.theme_use("clam") + self.style.theme_use('clam') self.style.configure( - "Mute.TButton", - foreground="#cd5c5c" if vban.strip[self.INDEX].mute else "#5a5a5a", + 'Mute.TButton', + foreground='#cd5c5c' if vban.strip[self.INDEX].mute else '#5a5a5a', ) # create labelframe and grid it onto the mainframe @@ -39,7 +39,7 @@ class App(tk.Tk): self.labelframe, from_=12, to_=-60, - orient="vertical", + orient='vertical', variable=self.slider_var, command=lambda arg: self.on_slider_move(arg), ) @@ -47,15 +47,15 @@ class App(tk.Tk): column=0, row=0, ) - slider.bind("", self.on_button_double_click) + slider.bind('', self.on_button_double_click) # create level meter and grid it onto the labelframe level_meter = ttk.Progressbar( self.labelframe, - orient="vertical", + orient='vertical', variable=self.meter_var, maximum=72, - mode="determinate", + mode='determinate', ) level_meter.grid(column=1, row=0) @@ -66,8 +66,8 @@ class App(tk.Tk): # create button and grid it onto the labelframe button = ttk.Button( self.labelframe, - text="Mute", - style="Mute.TButton", + text='Mute', + style='Mute.TButton', command=lambda: self.on_button_press(), ) button.grid(column=0, row=2, columnspan=2, padx=1, pady=2) @@ -83,7 +83,7 @@ class App(tk.Tk): self.button_var.set(not self.button_var.get()) self.vban.strip[self.INDEX].mute = self.button_var.get() self.style.configure( - "Mute.TButton", foreground="#cd5c5c" if self.button_var.get() else "#5a5a5a" + 'Mute.TButton', foreground='#cd5c5c' if self.button_var.get() else '#5a5a5a' ) def on_button_double_click(self, e): @@ -100,10 +100,10 @@ class App(tk.Tk): def main(): - with vban_cmd.api("banana", ldirty=True) as vban: + with vban_cmd.api('banana', ldirty=True) as vban: app = App(vban) app.mainloop() -if __name__ == "__main__": +if __name__ == '__main__': main() diff --git a/examples/obs/__main__.py b/examples/obs/__main__.py index 959dfdb..43a744c 100644 --- a/examples/obs/__main__.py +++ b/examples/obs/__main__.py @@ -1,4 +1,4 @@ -import time +import threading from logging import config import obsws_python as obsws @@ -7,85 +7,98 @@ import vban_cmd config.dictConfig( { - "version": 1, - "formatters": { - "standard": { - "format": "%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s" + 'version': 1, + 'formatters': { + 'standard': { + 'format': '%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s' } }, - "handlers": { - "stream": { - "level": "DEBUG", - "class": "logging.StreamHandler", - "formatter": "standard", + 'handlers': { + 'stream': { + 'level': 'DEBUG', + 'class': 'logging.StreamHandler', + 'formatter': 'standard', } }, - "loggers": {"vban_cmd.iremote": {"handlers": ["stream"], "level": "DEBUG"}}, + 'loggers': { + 'vban_cmd.iremote': { + 'handlers': ['stream'], + 'level': 'DEBUG', + 'propagate': False, + } + }, + 'root': {'handlers': ['stream'], 'level': 'WARNING'}, } ) class Observer: - def __init__(self, vban): - self.vban = vban - self.client = obsws.EventClient() - self.client.callback.register( + def __init__(self, vban, stop_event): + self._vban = vban + self._stop_event = stop_event + self._client = obsws.EventClient() + self._client.callback.register( ( self.on_current_program_scene_changed, self.on_exit_started, ) ) - self.is_running = True + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, exc_traceback): + self._client.disconnect() def on_start(self): - self.vban.strip[0].mute = True - self.vban.strip[1].B1 = True - self.vban.strip[2].B2 = True + self._vban.strip[0].mute = True + self._vban.strip[1].B1 = True + self._vban.strip[2].B2 = True def on_brb(self): - self.vban.strip[7].fadeto(0, 500) - self.vban.bus[0].mute = True + self._vban.strip[7].fadeto(0, 500) + self._vban.bus[0].mute = True def on_end(self): - self.vban.apply( + self._vban.apply( { - "strip-0": {"mute": True}, - "strip-1": {"mute": True, "B1": False}, - "strip-2": {"mute": True, "B1": False}, + 'strip-0': {'mute': True}, + 'strip-1': {'mute': True, 'B1': False}, + 'strip-2': {'mute': True, 'B1': False}, } ) def on_live(self): - self.vban.strip[0].mute = False - self.vban.strip[7].fadeto(-6, 500) - self.vban.strip[7].A3 = True + self._vban.strip[0].mute = False + self._vban.strip[7].fadeto(-6, 500) + self._vban.strip[7].A3 = True def on_current_program_scene_changed(self, data): - def fget(scene): - run = { - "START": self.on_start, - "BRB": self.on_brb, - "END": self.on_end, - "LIVE": self.on_live, - } - return run.get(scene) - scene = data.scene_name - print(f"Switched to scene {scene}") - if fn := fget(scene): - fn() + print(f'Switched to scene {scene}') + match scene: + case 'START': + self.on_start() + case 'BRB': + self.on_brb() + case 'END': + self.on_end() + case 'LIVE': + self.on_live() def on_exit_started(self, _): - self.client.unsubscribe() - self.is_running = False + self._stop_event.set() def main(): - with vban_cmd.api("potato") as vban: - observer = Observer(vban) - while observer.is_running: - time.sleep(0.1) + KIND_ID = 'potato' + + with vban_cmd.api(KIND_ID) as vban: + stop_event = threading.Event() + + with Observer(vban, stop_event): + stop_event.wait() -if __name__ == "__main__": +if __name__ == '__main__': main() diff --git a/examples/obs/setup.py b/examples/obs/setup.py index 4b0cb4a..2b05cae 100644 --- a/examples/obs/setup.py +++ b/examples/obs/setup.py @@ -1,7 +1,7 @@ from setuptools import setup setup( - name="obs", - description="OBS Example", - install_requires=["obsws-python"], + name='obs', + description='OBS Example', + install_requires=['obsws-python'], ) diff --git a/examples/observer/__main__.py b/examples/observer/__main__.py index eb2d0ad..9171160 100644 --- a/examples/observer/__main__.py +++ b/examples/observer/__main__.py @@ -13,23 +13,23 @@ class App: # define an 'on_update' callback function to receive event updates def on_update(self, event): - if event == "pdirty": - print("pdirty!") - elif event == "ldirty": + if event == 'pdirty': + print('pdirty!') + elif event == 'ldirty': for bus in self.vban.bus: if bus.levels.isdirty: print(bus, bus.levels.all) def main(): - KIND_ID = "banana" + KIND_ID = 'banana' with vban_cmd.api(KIND_ID, pdirty=True, ldirty=True) as vban: App(vban) - while cmd := input("Press to exit\n"): + while _ := input('Press to exit\n'): pass -if __name__ == "__main__": +if __name__ == '__main__': main() diff --git a/poetry.lock b/poetry.lock index 217fd5a..cfd583c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,93 +1,36 @@ -# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. - -[[package]] -name = "black" -version = "24.3.0" -description = "The uncompromising code formatter." -optional = false -python-versions = ">=3.8" -files = [ - {file = "black-24.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7d5e026f8da0322b5662fa7a8e752b3fa2dac1c1cbc213c3d7ff9bdd0ab12395"}, - {file = "black-24.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9f50ea1132e2189d8dff0115ab75b65590a3e97de1e143795adb4ce317934995"}, - {file = "black-24.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2af80566f43c85f5797365077fb64a393861a3730bd110971ab7a0c94e873e7"}, - {file = "black-24.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:4be5bb28e090456adfc1255e03967fb67ca846a03be7aadf6249096100ee32d0"}, - {file = "black-24.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4f1373a7808a8f135b774039f61d59e4be7eb56b2513d3d2f02a8b9365b8a8a9"}, - {file = "black-24.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aadf7a02d947936ee418777e0247ea114f78aff0d0959461057cae8a04f20597"}, - {file = "black-24.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c02e4ea2ae09d16314d30912a58ada9a5c4fdfedf9512d23326128ac08ac3d"}, - {file = "black-24.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:bf21b7b230718a5f08bd32d5e4f1db7fc8788345c8aea1d155fc17852b3410f5"}, - {file = "black-24.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2818cf72dfd5d289e48f37ccfa08b460bf469e67fb7c4abb07edc2e9f16fb63f"}, - {file = "black-24.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4acf672def7eb1725f41f38bf6bf425c8237248bb0804faa3965c036f7672d11"}, - {file = "black-24.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7ed6668cbbfcd231fa0dc1b137d3e40c04c7f786e626b405c62bcd5db5857e4"}, - {file = "black-24.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:56f52cfbd3dabe2798d76dbdd299faa046a901041faf2cf33288bc4e6dae57b5"}, - {file = "black-24.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:79dcf34b33e38ed1b17434693763301d7ccbd1c5860674a8f871bd15139e7837"}, - {file = "black-24.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e19cb1c6365fd6dc38a6eae2dcb691d7d83935c10215aef8e6c38edee3f77abd"}, - {file = "black-24.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65b76c275e4c1c5ce6e9870911384bff5ca31ab63d19c76811cb1fb162678213"}, - {file = "black-24.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b5991d523eee14756f3c8d5df5231550ae8993e2286b8014e2fdea7156ed0959"}, - {file = "black-24.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c45f8dff244b3c431b36e3224b6be4a127c6aca780853574c00faf99258041eb"}, - {file = "black-24.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6905238a754ceb7788a73f02b45637d820b2f5478b20fec82ea865e4f5d4d9f7"}, - {file = "black-24.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7de8d330763c66663661a1ffd432274a2f92f07feeddd89ffd085b5744f85e7"}, - {file = "black-24.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:7bb041dca0d784697af4646d3b62ba4a6b028276ae878e53f6b4f74ddd6db99f"}, - {file = "black-24.3.0-py3-none-any.whl", hash = "sha256:41622020d7120e01d377f74249e677039d20e6344ff5851de8a10f11f513bf93"}, - {file = "black-24.3.0.tar.gz", hash = "sha256:a0c9c4a0771afc6919578cec71ce82a3e31e054904e7197deacbc9382671c41f"}, -] - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -packaging = ">=22.0" -pathspec = ">=0.9.0" -platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] +# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. [[package]] name = "cachetools" -version = "5.3.1" +version = "5.5.0" description = "Extensible memoizing collections and decorators" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ - {file = "cachetools-5.3.1-py3-none-any.whl", hash = "sha256:95ef631eeaea14ba2e36f06437f36463aac3a096799e876ee55e5cdccb102590"}, - {file = "cachetools-5.3.1.tar.gz", hash = "sha256:dce83f2d9b4e1f732a8cd44af8e8fab2dbe46201467fc98b3ef8f269092bf62b"}, + {file = "cachetools-5.5.0-py3-none-any.whl", hash = "sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292"}, + {file = "cachetools-5.5.0.tar.gz", hash = "sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a"}, ] [[package]] name = "chardet" -version = "5.1.0" +version = "5.2.0" description = "Universal encoding detector for Python 3" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ - {file = "chardet-5.1.0-py3-none-any.whl", hash = "sha256:362777fb014af596ad31334fde1e8c327dfdb076e1960d1694662d46a6917ab9"}, - {file = "chardet-5.1.0.tar.gz", hash = "sha256:0d62712b956bc154f85fb0a266e2a3c5913c2967e00348701b32411d6def31e5"}, + {file = "chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970"}, + {file = "chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7"}, ] -[[package]] -name = "click" -version = "8.1.3" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.7" -files = [ - {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, - {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - [[package]] name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -99,6 +42,7 @@ version = "0.3.9" description = "Distribution utilities" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, @@ -106,13 +50,15 @@ files = [ [[package]] name = "exceptiongroup" -version = "1.2.0" +version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" +groups = ["dev"] +markers = "python_version < \"3.11\"" files = [ - {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, - {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, ] [package.extras] @@ -120,78 +66,43 @@ test = ["pytest (>=6)"] [[package]] name = "filelock" -version = "3.12.2" +version = "3.16.1" description = "A platform independent file lock." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "filelock-3.12.2-py3-none-any.whl", hash = "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec"}, - {file = "filelock-3.12.2.tar.gz", hash = "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81"}, + {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"}, + {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"}, ] [package.extras] -docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"] +docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"] +typing = ["typing-extensions (>=4.12.2)"] [[package]] name = "iniconfig" -version = "1.1.1" -description = "iniconfig: brain-dead simple config-ini parsing" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" optional = false -python-versions = "*" +python-versions = ">=3.7" +groups = ["dev"] files = [ - {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, - {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, -] - -[[package]] -name = "isort" -version = "5.10.1" -description = "A Python utility / library to sort Python imports." -optional = false -python-versions = ">=3.6.1,<4.0" -files = [ - {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, - {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, -] - -[package.extras] -colors = ["colorama (>=0.4.3,<0.5.0)"] -pipfile-deprecated-finder = ["pipreqs", "requirementslib"] -plugins = ["setuptools"] -requirements-deprecated-finder = ["pip-api", "pipreqs"] - -[[package]] -name = "mypy-extensions" -version = "0.4.3" -description = "Experimental type system extensions for programs checked with the mypy typechecker." -optional = false -python-versions = "*" -files = [ - {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, - {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] [[package]] name = "packaging" -version = "23.1" +version = "24.2" description = "Core utilities for Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, - {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, -] - -[[package]] -name = "pathspec" -version = "0.10.1" -description = "Utility library for gitignore style pattern matching of file paths." -optional = false -python-versions = ">=3.7" -files = [ - {file = "pathspec-0.10.1-py3-none-any.whl", hash = "sha256:46846318467efc4556ccfd27816e004270a9eeeeb4d062ce5e6fc7a87c573f93"}, - {file = "pathspec-0.10.1.tar.gz", hash = "sha256:7ace6161b621d31e7902eb6b5ae148d12cfd23f4a249b9ffb6b9fee12084323d"}, + {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, + {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, ] [[package]] @@ -200,6 +111,7 @@ version = "4.3.6" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, @@ -212,47 +124,62 @@ type = ["mypy (>=1.11.2)"] [[package]] name = "pluggy" -version = "1.0.0" +version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, - {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [package.extras] dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "pyenv-inspect" +version = "0.4.0" +description = "An auxiliary library for the virtualenv-pyenv and tox-pyenv-redux plugins" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pyenv-inspect-0.4.0.tar.gz", hash = "sha256:ec429d1d81b67ab0b08a0408414722a79d24fd1845a5b264267e44e19d8d60f0"}, + {file = "pyenv_inspect-0.4.0-py3-none-any.whl", hash = "sha256:618683ae7d3e6db14778d58aa0fc6b3170180d944669b5d35a8aa4fb7db550d2"}, +] + [[package]] name = "pyproject-api" -version = "1.5.2" +version = "1.8.0" description = "API to interact with the python pyproject.toml based projects" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "pyproject_api-1.5.2-py3-none-any.whl", hash = "sha256:9cffcbfb64190f207444d7579d315f3278f2c04ba46d685fad93197b5326d348"}, - {file = "pyproject_api-1.5.2.tar.gz", hash = "sha256:999f58fa3c92b23ebd31a6bad5d1f87d456744d75e05391be7f5c729015d3d91"}, + {file = "pyproject_api-1.8.0-py3-none-any.whl", hash = "sha256:3d7d347a047afe796fd5d1885b1e391ba29be7169bd2f102fcd378f04273d228"}, + {file = "pyproject_api-1.8.0.tar.gz", hash = "sha256:77b8049f2feb5d33eefcc21b57f1e279636277a8ac8ad6b5871037b243778496"}, ] [package.dependencies] -packaging = ">=23.1" +packaging = ">=24.1" tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} [package.extras] -docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] -testing = ["covdefaults (>=2.3)", "importlib-metadata (>=6.6)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)", "setuptools (>=67.8)", "wheel (>=0.40)"] +docs = ["furo (>=2024.8.6)", "sphinx-autodoc-typehints (>=2.4.1)"] +testing = ["covdefaults (>=2.3)", "pytest (>=8.3.3)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "setuptools (>=75.1)"] [[package]] name = "pytest" -version = "7.4.4" +version = "8.3.4" description = "pytest: simple powerful testing with Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, - {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, + {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, + {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, ] [package.dependencies] @@ -260,98 +187,149 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" -pluggy = ">=0.12,<2.0" -tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} +pluggy = ">=1.5,<2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-randomly" -version = "3.12.0" +version = "3.16.0" description = "Pytest plugin to randomly order tests and control random.seed." optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "pytest-randomly-3.12.0.tar.gz", hash = "sha256:d60c2db71ac319aee0fc6c4110a7597d611a8b94a5590918bfa8583f00caccb2"}, - {file = "pytest_randomly-3.12.0-py3-none-any.whl", hash = "sha256:f4f2e803daf5d1ba036cc22bf4fe9dbbf99389ec56b00e5cba732fb5c1d07fdd"}, + {file = "pytest_randomly-3.16.0-py3-none-any.whl", hash = "sha256:8633d332635a1a0983d3bba19342196807f6afb17c3eef78e02c2f85dade45d6"}, + {file = "pytest_randomly-3.16.0.tar.gz", hash = "sha256:11bf4d23a26484de7860d82f726c0629837cf4064b79157bd18ec9d41d7feb26"}, ] [package.dependencies] pytest = "*" [[package]] -name = "pytest-repeat" -version = "0.9.1" -description = "pytest plugin for repeating tests" +name = "ruff" +version = "0.9.2" +description = "An extremely fast Python linter and code formatter, written in Rust." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.7" +groups = ["dev"] files = [ - {file = "pytest-repeat-0.9.1.tar.gz", hash = "sha256:5cd3289745ab3156d43eb9c8e7f7d00a926f3ae5c9cf425bec649b2fe15bad5b"}, - {file = "pytest_repeat-0.9.1-py2.py3-none-any.whl", hash = "sha256:4474a7d9e9137f6d8cc8ae297f8c4168d33c56dd740aa78cfffe562557e6b96e"}, + {file = "ruff-0.9.2-py3-none-linux_armv6l.whl", hash = "sha256:80605a039ba1454d002b32139e4970becf84b5fee3a3c3bf1c2af6f61a784347"}, + {file = "ruff-0.9.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b9aab82bb20afd5f596527045c01e6ae25a718ff1784cb92947bff1f83068b00"}, + {file = "ruff-0.9.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fbd337bac1cfa96be615f6efcd4bc4d077edbc127ef30e2b8ba2a27e18c054d4"}, + {file = "ruff-0.9.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82b35259b0cbf8daa22a498018e300b9bb0174c2bbb7bcba593935158a78054d"}, + {file = "ruff-0.9.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b6a9701d1e371bf41dca22015c3f89769da7576884d2add7317ec1ec8cb9c3c"}, + {file = "ruff-0.9.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9cc53e68b3c5ae41e8faf83a3b89f4a5d7b2cb666dff4b366bb86ed2a85b481f"}, + {file = "ruff-0.9.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:8efd9da7a1ee314b910da155ca7e8953094a7c10d0c0a39bfde3fcfd2a015684"}, + {file = "ruff-0.9.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3292c5a22ea9a5f9a185e2d131dc7f98f8534a32fb6d2ee7b9944569239c648d"}, + {file = "ruff-0.9.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a605fdcf6e8b2d39f9436d343d1f0ff70c365a1e681546de0104bef81ce88df"}, + {file = "ruff-0.9.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c547f7f256aa366834829a08375c297fa63386cbe5f1459efaf174086b564247"}, + {file = "ruff-0.9.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d18bba3d3353ed916e882521bc3e0af403949dbada344c20c16ea78f47af965e"}, + {file = "ruff-0.9.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b338edc4610142355ccf6b87bd356729b62bf1bc152a2fad5b0c7dc04af77bfe"}, + {file = "ruff-0.9.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:492a5e44ad9b22a0ea98cf72e40305cbdaf27fac0d927f8bc9e1df316dcc96eb"}, + {file = "ruff-0.9.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:af1e9e9fe7b1f767264d26b1075ac4ad831c7db976911fa362d09b2d0356426a"}, + {file = "ruff-0.9.2-py3-none-win32.whl", hash = "sha256:71cbe22e178c5da20e1514e1e01029c73dc09288a8028a5d3446e6bba87a5145"}, + {file = "ruff-0.9.2-py3-none-win_amd64.whl", hash = "sha256:c5e1d6abc798419cf46eed03f54f2e0c3adb1ad4b801119dedf23fcaf69b55b5"}, + {file = "ruff-0.9.2-py3-none-win_arm64.whl", hash = "sha256:a1b63fa24149918f8b37cef2ee6fff81f24f0d74b6f0bdc37bc3e1f2143e41c6"}, + {file = "ruff-0.9.2.tar.gz", hash = "sha256:b5eceb334d55fae5f316f783437392642ae18e16dcf4f1858d55d3c2a0f8f5d0"}, ] -[package.dependencies] -pytest = ">=3.6" - [[package]] name = "tomli" -version = "2.0.1" +version = "2.2.1" description = "A lil' TOML parser" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +groups = ["main", "dev"] +markers = "python_version < \"3.11\"" files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, + {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, + {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, + {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, + {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, + {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, + {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, + {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, + {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] [[package]] name = "tox" -version = "4.6.3" +version = "4.23.2" description = "tox is a generic virtualenv management and test command line tool" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "tox-4.6.3-py3-none-any.whl", hash = "sha256:2946a0bb38924c3a9f9575c7fb4ca1f4c11a7c69c61592f176778892155cb50c"}, - {file = "tox-4.6.3.tar.gz", hash = "sha256:9e2c5091a117d03b583c57c4c40aecd068099c17d40520e7b165e85c19334534"}, + {file = "tox-4.23.2-py3-none-any.whl", hash = "sha256:452bc32bb031f2282881a2118923176445bac783ab97c874b8770ab4c3b76c38"}, + {file = "tox-4.23.2.tar.gz", hash = "sha256:86075e00e555df6e82e74cfc333917f91ecb47ffbc868dcafbd2672e332f4a2c"}, ] [package.dependencies] -cachetools = ">=5.3.1" -chardet = ">=5.1" +cachetools = ">=5.5" +chardet = ">=5.2" colorama = ">=0.4.6" -filelock = ">=3.12.2" -packaging = ">=23.1" -platformdirs = ">=3.5.3" -pluggy = ">=1" -pyproject-api = ">=1.5.2" +filelock = ">=3.16.1" +packaging = ">=24.1" +platformdirs = ">=4.3.6" +pluggy = ">=1.5" +pyproject-api = ">=1.8" tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} -virtualenv = ">=20.23.1" +typing-extensions = {version = ">=4.12.2", markers = "python_version < \"3.11\""} +virtualenv = ">=20.26.6" [package.extras] -docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-argparse-cli (>=1.11.1)", "sphinx-autodoc-typehints (>=1.23.2,!=1.23.4)", "sphinx-copybutton (>=0.5.2)", "sphinx-inline-tabs (>=2023.4.21)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -testing = ["build[virtualenv] (>=0.10)", "covdefaults (>=2.3)", "detect-test-pollution (>=1.1.1)", "devpi-process (>=0.3.1)", "diff-cover (>=7.6)", "distlib (>=0.3.6)", "flaky (>=3.7)", "hatch-vcs (>=0.3)", "hatchling (>=1.17.1)", "psutil (>=5.9.5)", "pytest (>=7.3.2)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "pytest-xdist (>=3.3.1)", "re-assert (>=1.1)", "time-machine (>=2.10)", "wheel (>=0.40)"] +test = ["devpi-process (>=1.0.2)", "pytest (>=8.3.3)", "pytest-mock (>=3.14)"] [[package]] name = "typing-extensions" -version = "4.10.0" +version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version < \"3.11\"" files = [ - {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, - {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] [[package]] name = "virtualenv" -version = "20.26.6" +version = "20.29.0" description = "Virtual Python Environment builder" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "virtualenv-20.26.6-py3-none-any.whl", hash = "sha256:7345cc5b25405607a624d8418154577459c3e0277f5466dd79c49d5e492995f2"}, - {file = "virtualenv-20.26.6.tar.gz", hash = "sha256:280aede09a2a5c317e409a00102e7077c6432c5a38f0ef938e643805a7ad2c48"}, + {file = "virtualenv-20.29.0-py3-none-any.whl", hash = "sha256:c12311863497992dc4b8644f8ea82d3b35bb7ef8ee82e6630d76d0197c39baf9"}, + {file = "virtualenv-20.29.0.tar.gz", hash = "sha256:6345e1ff19d4b1296954cee076baaf58ff2a12a84a338c62b02eda39f20aa982"}, ] [package.dependencies] @@ -363,7 +341,23 @@ platformdirs = ">=3.9.1,<5" docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] +[[package]] +name = "virtualenv-pyenv" +version = "0.5.0" +description = "A virtualenv Python discovery plugin for pyenv-installed interpreters" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "virtualenv-pyenv-0.5.0.tar.gz", hash = "sha256:7b0e5fe3dfbdf484f4cf9b01e1f98111e398db6942237910f666356e6293597f"}, + {file = "virtualenv_pyenv-0.5.0-py3-none-any.whl", hash = "sha256:21750247e36c55b3c547cfdeb08f51a3867fe7129922991a4f9c96980c0a4a5d"}, +] + +[package.dependencies] +pyenv-inspect = ">=0.4,<0.5" +virtualenv = "*" + [metadata] -lock-version = "2.0" -python-versions = "^3.10" -content-hash = "a42b70c2ebaa214c9447f0bfc42a75a09e63066211668202a29c7dc6c40aaab9" +lock-version = "2.1" +python-versions = ">=3.10" +content-hash = "13fc9d0eb15d5fc09b54c1c8cd8f528b260259e97ee6813b50ab4724c35d6677" diff --git a/pyproject.toml b/pyproject.toml index 9852844..daa0c0f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,36 +1,39 @@ -[tool.poetry] +[project] name = "vban-cmd" -version = "2.4.12" +version = "2.5.0" description = "Python interface for the VBAN RT Packet Service (Sendtext)" -authors = ["onyx-and-iris "] -license = "MIT" +authors = [ + {name = "Onyx and Iris",email = "code@onyxandiris.online"} +] +license = {text = "MIT"} readme = "README.md" -repository = "https://github.com/onyx-and-iris/vban-cmd-python" +requires-python = ">=3.10" +dependencies = [ + "tomli (>=2.0.1,<3.0) ; python_version < '3.11'", +] -[tool.poetry.dependencies] -python = "^3.10" -tomli = { version = "^2.0.1", python = "<3.11" } +[tool.poetry.requires-plugins] +poethepoet = "^0.32.1" [tool.poetry.group.dev.dependencies] -pytest = "^7.4.4" -pytest-randomly = "^3.12.0" -pytest-repeat = "^0.9.1" -black = ">=22.3,<25.0" -isort = "^5.10.1" -tox = "^4.6.3" +pytest = "^8.3.4" +pytest-randomly = "^3.16.0" +ruff = "^0.9.2" +tox = "^4.23.2" +virtualenv-pyenv = "^0.5.0" [build-system] -requires = ["poetry-core>=1.0.0"] +requires = ["poetry-core>=2.0.0,<3.0.0"] build-backend = "poetry.core.masonry.api" -[tool.poetry.scripts] -gui = "scripts:ex_gui" -obs = "scripts:ex_obs" -observer = "scripts:ex_observer" -basic = "scripts:test_basic" -banana = "scripts:test_banana" -potato = "scripts:test_potato" -all = "scripts:test_all" +[tool.poe.tasks] +gui.script = "scripts:ex_gui" +obs.script = "scripts:ex_obs" +observer.script = "scripts:ex_observer" +basic.script = "scripts:test_basic" +banana.script = "scripts:test_banana" +potato.script = "scripts:test_potato" +all.script = "scripts:test_all" [tool.tox] legacy_tox_ini = """ @@ -38,8 +41,101 @@ legacy_tox_ini = """ envlist = py310,py311,py312 [testenv] +setenv = VIRTUALENV_DISCOVERY=pyenv allowlist_externals = poetry commands = poetry install -v poetry run pytest tests/ + + +[testenv:obs] +setenv = VIRTUALENV_DISCOVERY=pyenv +allowlist_externals = poetry +deps = obsws-python +commands = + poetry install -v --without dev + poetry run python examples/obs/ """ + +[tool.ruff] +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".mypy_cache", + ".nox", + ".pants.d", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "venv", +] + +# Same as Black. +line-length = 88 +indent-width = 4 + +# Assume Python 3.10 +target-version = "py310" + +[tool.ruff.lint] +# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. +# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or +# McCabe complexity (`C901`) by default. +select = ["E4", "E7", "E9", "F"] +ignore = [] + +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = [] + +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + + +[tool.ruff.format] +# Unlike Black, use single quotes for strings. +quote-style = "single" + +# Like Black, indent with spaces, rather than tabs. +indent-style = "space" + +# Like Black, respect magic trailing commas. +skip-magic-trailing-comma = false + +# Like Black, automatically detect the appropriate line ending. +line-ending = "auto" + +# Enable auto-formatting of code examples in docstrings. Markdown, +# reStructuredText code/literal blocks and doctests are all supported. +# +# This is currently disabled by default, but it is planned for this +# to be opt-out in the future. +docstring-code-format = false + +# Set the line length limit used when formatting code snippets in +# docstrings. +# +# This only has an effect when the `docstring-code-format` setting is +# enabled. +docstring-code-line-length = "dynamic" + +[tool.ruff.lint.mccabe] +max-complexity = 10 + +[tool.ruff.lint.per-file-ignores] +"__init__.py" = [ + "E402", + "F401", +] diff --git a/scripts.py b/scripts.py index 645e4ca..7939804 100644 --- a/scripts.py +++ b/scripts.py @@ -5,33 +5,32 @@ from pathlib import Path def ex_gui(): - scriptpath = Path.cwd() / "examples" / "gui" / "." + scriptpath = Path.cwd() / 'examples' / 'gui' / '.' subprocess.run([sys.executable, str(scriptpath)]) def ex_obs(): - scriptpath = Path.cwd() / "examples" / "obs" / "." - subprocess.run([sys.executable, str(scriptpath)]) + subprocess.run(['tox', 'r', '-e', 'obs']) def ex_observer(): - scriptpath = Path.cwd() / "examples" / "observer" / "." + scriptpath = Path.cwd() / 'examples' / 'observer' / '.' subprocess.run([sys.executable, str(scriptpath)]) def test_basic(): - os.environ["KIND"] = "basic" - subprocess.run(["tox"]) + os.environ['KIND'] = 'basic' + subprocess.run(['tox']) def test_banana(): - os.environ["KIND"] = "banana" - subprocess.run(["tox"]) + os.environ['KIND'] = 'banana' + subprocess.run(['tox']) def test_potato(): - os.environ["KIND"] = "potato" - subprocess.run(["tox"]) + os.environ['KIND'] = 'potato' + subprocess.run(['tox']) def test_all(): diff --git a/tests/__init__.py b/tests/__init__.py index 1a4ba06..8558f69 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -9,14 +9,14 @@ from vban_cmd.kinds import request_kind_map as kindmap # get KIND_ID from env var, otherwise set to random KIND_ID = os.environ.get( - "KIND", random.choice(tuple(kind_id.name.lower() for kind_id in KindId)) + 'KIND', random.choice(tuple(kind_id.name.lower() for kind_id in KindId)) ) opts = { - "ip": "testing.local", - "streamname": "testing", - "port": 6990, - "bps": 0, + 'ip': 'ws.local', + 'streamname': 'workstation', + 'port': 6980, + 'bps': 0, } vban = vban_cmd.api(KIND_ID, **opts) @@ -42,7 +42,7 @@ data = Data() def setup_module(): - print(f"\nRunning tests for kind [{data.name}]\n", file=sys.stdout) + print(f'\nRunning tests for kind [{data.name}]\n', file=sys.stdout) vban.login() vban.command.reset() diff --git a/vban_cmd/__init__.py b/vban_cmd/__init__.py index ed7e87a..13b4754 100644 --- a/vban_cmd/__init__.py +++ b/vban_cmd/__init__.py @@ -1,3 +1,3 @@ from .factory import request_vbancmd_obj as api -__ALL__ = ["api"] +__ALL__ = ['api'] diff --git a/vban_cmd/bus.py b/vban_cmd/bus.py index 3809dae..29b4a43 100644 --- a/vban_cmd/bus.py +++ b/vban_cmd/bus.py @@ -7,8 +7,8 @@ from .iremote import IRemote from .meta import bus_mode_prop, channel_bool_prop, channel_label_prop BusModes = IntEnum( - "BusModes", - "normal amix bmix repeat composite tvmix upmix21 upmix41 upmix61 centeronly lfeonly rearonly", + 'BusModes', + 'normal amix bmix repeat composite tvmix upmix21 upmix41 upmix61 centeronly lfeonly rearonly', start=0, ) @@ -26,7 +26,7 @@ class Bus(IRemote): @property def identifier(self) -> str: - return f"bus[{self.index}]" + return f'bus[{self.index}]' @property def gain(self) -> float: @@ -36,19 +36,19 @@ class Bus(IRemote): return val * 0.01 return (((1 << 16) - 1) - val) * -0.01 - val = self.getter("gain") + val = self.getter('gain') return round(val if val else fget(), 1) @gain.setter def gain(self, val: float): - self.setter("gain", val) + self.setter('gain', val) def fadeto(self, target: float, time_: int): - self.setter("FadeTo", f"({target}, {time_})") + self.setter('FadeTo', f'({target}, {time_})') time.sleep(self._remote.DELAY) def fadeby(self, change: float, time_: int): - self.setter("FadeBy", f"({change}, {time_})") + self.setter('FadeBy', f'({change}, {time_})') time.sleep(self._remote.DELAY) @@ -56,22 +56,22 @@ class BusEQ(IRemote): @classmethod def make(cls, remote, index): BUSEQ_cls = type( - f"BusEQ{remote.kind}", + f'BusEQ{remote.kind}', (cls,), { - **{param: channel_bool_prop(param) for param in ["on", "ab"]}, + **{param: channel_bool_prop(param) for param in ['on', 'ab']}, }, ) return BUSEQ_cls(remote, index) @property def identifier(self) -> str: - return f"bus[{self.index}].eq" + return f'bus[{self.index}].eq' class PhysicalBus(Bus): def __str__(self): - return f"{type(self).__name__}{self.index}" + return f'{type(self).__name__}{self.index}' @property def device(self) -> str: @@ -84,7 +84,7 @@ class PhysicalBus(Bus): class VirtualBus(Bus): def __str__(self): - return f"{type(self).__name__}{self.index}" + return f'{type(self).__name__}{self.index}' class BusLevel(IRemote): @@ -105,7 +105,7 @@ class BusLevel(IRemote): if not self._remote.stopped() and self._remote.event.ldirty: return tuple( fget(i) - for i in self._remote.cache["bus_level"][self.range[0] : self.range[-1]] + for i in self._remote.cache['bus_level'][self.range[0] : self.range[-1]] ) return tuple( fget(i) @@ -116,7 +116,7 @@ class BusLevel(IRemote): @property def identifier(self) -> str: - return f"bus[{self.index}]" + return f'bus[{self.index}]' @property def all(self) -> tuple: @@ -138,26 +138,26 @@ def _make_bus_mode_mixin(): """Creates a mixin of Bus Modes.""" modestates = { - "normal": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - "amix": [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1], - "repeat": [0, 2, 2, 0, 0, 2, 2, 0, 0, 2, 2], - "bmix": [1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3], - "composite": [0, 0, 0, 4, 4, 4, 4, 0, 0, 0, 0], - "tvmix": [1, 0, 1, 4, 5, 4, 5, 0, 1, 0, 1], - "upmix21": [0, 2, 2, 4, 4, 6, 6, 0, 0, 2, 2], - "upmix41": [1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3], - "upmix61": [0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8], - "centeronly": [1, 0, 1, 0, 1, 0, 1, 8, 9, 8, 9], - "lfeonly": [0, 2, 2, 0, 0, 2, 2, 8, 8, 10, 10], - "rearonly": [1, 2, 3, 0, 1, 2, 3, 8, 9, 10, 11], + 'normal': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + 'amix': [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1], + 'repeat': [0, 2, 2, 0, 0, 2, 2, 0, 0, 2, 2], + 'bmix': [1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3], + 'composite': [0, 0, 0, 4, 4, 4, 4, 0, 0, 0, 0], + 'tvmix': [1, 0, 1, 4, 5, 4, 5, 0, 1, 0, 1], + 'upmix21': [0, 2, 2, 4, 4, 6, 6, 0, 0, 2, 2], + 'upmix41': [1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3], + 'upmix61': [0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8], + 'centeronly': [1, 0, 1, 0, 1, 0, 1, 8, 9, 8, 9], + 'lfeonly': [0, 2, 2, 0, 0, 2, 2, 8, 8, 10, 10], + 'rearonly': [1, 2, 3, 0, 1, 2, 3, 8, 9, 10, 11], } def identifier(self) -> str: - return f"bus[{self.index}].mode" + return f'bus[{self.index}].mode' def get(self): states = [ - (int.from_bytes(self.public_packet.busstate[self.index], "little") & val) + (int.from_bytes(self.public_packet.busstate[self.index], 'little') & val) >> 4 for val in self._modes.modevals ] @@ -166,13 +166,13 @@ def _make_bus_mode_mixin(): return k return type( - "BusModeMixin", + 'BusModeMixin', (IRemote,), { - "identifier": property(identifier), - "modestates": modestates, + 'identifier': property(identifier), + 'modestates': modestates, **{mode.name: bus_mode_prop(mode.name) for mode in BusModes}, - "get": get, + 'get': get, }, ) @@ -186,14 +186,14 @@ def bus_factory(phys_bus, remote, i) -> Union[PhysicalBus, VirtualBus]: BUS_cls = PhysicalBus if phys_bus else VirtualBus BUSMODEMIXIN_cls = _make_bus_mode_mixin() return type( - f"{BUS_cls.__name__}{remote.kind}", + f'{BUS_cls.__name__}{remote.kind}', (BUS_cls,), { - "eq": BusEQ.make(remote, i), - "levels": BusLevel(remote, i), - "mode": BUSMODEMIXIN_cls(remote, i), - **{param: channel_bool_prop(param) for param in ["mute", "mono"]}, - "label": channel_label_prop(), + 'eq': BusEQ.make(remote, i), + 'levels': BusLevel(remote, i), + 'mode': BUSMODEMIXIN_cls(remote, i), + **{param: channel_bool_prop(param) for param in ['mute', 'mono']}, + 'label': channel_label_prop(), }, )(remote, i) diff --git a/vban_cmd/command.py b/vban_cmd/command.py index 70bc37c..2883b6a 100644 --- a/vban_cmd/command.py +++ b/vban_cmd/command.py @@ -17,30 +17,30 @@ class Command(IRemote): Returns a Command class of a kind. """ CMD_cls = type( - f"Command{remote.kind}", + f'Command{remote.kind}', (cls,), { **{ - param: action_fn(param) for param in ["show", "shutdown", "restart"] + param: action_fn(param) for param in ['show', 'shutdown', 'restart'] }, - "hide": action_fn("show", val=0), + 'hide': action_fn('show', val=0), }, ) return CMD_cls(remote) @property def identifier(self) -> str: - return "command" + return 'command' def set_showvbanchat(self, val: bool): - self.setter("DialogShow.VBANCHAT", 1 if val else 0) + self.setter('DialogShow.VBANCHAT', 1 if val else 0) showvbanchat = property(fset=set_showvbanchat) def set_lock(self, val: bool): - self.setter("lock", 1 if val else 0) + self.setter('lock', 1 if val else 0) lock = property(fset=set_lock) def reset(self): - self._remote.apply_config("reset") + self._remote.apply_config('reset') diff --git a/vban_cmd/config.py b/vban_cmd/config.py index 0cfe2d0..1ab6bf7 100644 --- a/vban_cmd/config.py +++ b/vban_cmd/config.py @@ -20,73 +20,73 @@ class TOMLStrBuilder: def __init__(self, kind): self.kind = kind self.higher = itertools.chain( - [f"strip-{i}" for i in range(kind.num_strip)], - [f"bus-{i}" for i in range(kind.num_bus)], + [f'strip-{i}' for i in range(kind.num_strip)], + [f'bus-{i}' for i in range(kind.num_bus)], ) def init_config(self, profile=None): self.virt_strip_params = ( [ - "mute = false", - "mono = false", - "solo = false", - "gain = 0.0", + 'mute = false', + 'mono = false', + 'solo = false', + 'gain = 0.0', ] - + [f"A{i} = false" for i in range(1, self.kind.phys_out + 1)] - + [f"B{i} = false" for i in range(1, self.kind.virt_out + 1)] + + [f'A{i} = false' for i in range(1, self.kind.phys_out + 1)] + + [f'B{i} = false' for i in range(1, self.kind.virt_out + 1)] ) self.phys_strip_params = self.virt_strip_params + [ - "comp.knob = 0.0", - "gate.knob = 0.0", - "denoiser.knob = 0.0", - "eq.on = false", + 'comp.knob = 0.0', + 'gate.knob = 0.0', + 'denoiser.knob = 0.0', + 'eq.on = false', ] - self.bus_float = ["gain = 0.0"] + self.bus_float = ['gain = 0.0'] self.bus_params = [ - "mono = false", - "eq.on = false", - "mute = false", - "gain = 0.0", + 'mono = false', + 'eq.on = false', + 'mute = false', + 'gain = 0.0', ] - if profile == "reset": + if profile == 'reset': self.reset_config() def reset_config(self): self.phys_strip_params = list( - map(lambda x: x.replace("B1 = false", "B1 = true"), self.phys_strip_params) + map(lambda x: x.replace('B1 = false', 'B1 = true'), self.phys_strip_params) ) self.virt_strip_params = list( - map(lambda x: x.replace("A1 = false", "A1 = true"), self.virt_strip_params) + map(lambda x: x.replace('A1 = false', 'A1 = true'), self.virt_strip_params) ) - def build(self, profile="reset"): + def build(self, profile='reset'): self.init_config(profile) toml_str = str() for eachclass in self.higher: - toml_str += f"[{eachclass}]\n" + toml_str += f'[{eachclass}]\n' toml_str = self.join(eachclass, toml_str) return toml_str def join(self, eachclass, toml_str): - kls, index = eachclass.split("-") + kls, index = eachclass.split('-') match kls: - case "strip": - toml_str += ("\n").join( + case 'strip': + toml_str += ('\n').join( self.phys_strip_params if int(index) < self.kind.phys_in else self.virt_strip_params ) - case "bus": - toml_str += ("\n").join(self.bus_params) + case 'bus': + toml_str += ('\n').join(self.bus_params) case _: pass - return toml_str + "\n" + return toml_str + '\n' class TOMLDataExtractor: def __init__(self, file): - with open(file, "rb") as f: + with open(file, 'rb') as f: self._data = tomllib.load(f) @property @@ -104,10 +104,10 @@ def dataextraction_factory(file): this opens the possibility for other parsers to be added """ - if file.suffix == ".toml": + if file.suffix == '.toml': extractor = TOMLDataExtractor else: - raise ValueError("Cannot extract data from {}".format(file)) + raise ValueError('Cannot extract data from {}'.format(file)) return extractor(file) @@ -141,25 +141,25 @@ class Loader(metaclass=SingletonType): def defaults(self, kind): self.builder = TOMLStrBuilder(kind) toml_str = self.builder.build() - self.register("reset", tomllib.loads(toml_str)) + self.register('reset', tomllib.loads(toml_str)) def parse(self, identifier, data): if identifier in self._configs: self.logger.info( - f"config file with name {identifier} already in memory, skipping.." + f'config file with name {identifier} already in memory, skipping..' ) return try: self.parser = dataextraction_factory(data) except tomllib.TOMLDecodeError as e: - ERR_MSG = (str(e), f"When attempting to load {identifier}.toml") - self.logger.error(f"{type(e).__name__}: {' '.join(ERR_MSG)}") + ERR_MSG = (str(e), f'When attempting to load {identifier}.toml') + self.logger.error(f'{type(e).__name__}: {" ".join(ERR_MSG)}') return return True def register(self, identifier, data=None): self._configs[identifier] = data if data else self.parser.data - self.logger.info(f"config {self.name}/{identifier} loaded into memory") + self.logger.info(f'config {self.name}/{identifier} loaded into memory') def deregister(self): self._configs.clear() @@ -182,18 +182,18 @@ def loader(kind): returns configs loaded into memory """ - logger_loader = logger.getChild("loader") + logger_loader = logger.getChild('loader') loader = Loader(kind) for path in ( - Path.cwd() / "configs" / kind.name, - Path.home() / ".config" / "vban-cmd" / kind.name, - Path.home() / "Documents" / "Voicemeeter" / "configs" / kind.name, + Path.cwd() / 'configs' / kind.name, + Path.home() / '.config' / 'vban-cmd' / kind.name, + Path.home() / 'Documents' / 'Voicemeeter' / 'configs' / kind.name, ): if path.is_dir(): - logger_loader.info(f"Checking [{path}] for TOML config files:") - for file in path.glob("*.toml"): - identifier = file.with_suffix("").stem + logger_loader.info(f'Checking [{path}] for TOML config files:') + for file in path.glob('*.toml'): + identifier = file.with_suffix('').stem if loader.parse(identifier, file): loader.register(identifier) return loader.configs @@ -208,5 +208,5 @@ def request_config(kind_id: str): try: configs = loader(kindmap(kind_id)) except KeyError: - raise VBANCMDError(f"Unknown Voicemeeter kind {kind_id}") + raise VBANCMDError(f'Unknown Voicemeeter kind {kind_id}') return configs diff --git a/vban_cmd/event.py b/vban_cmd/event.py index 58c38f6..137dac2 100644 --- a/vban_cmd/event.py +++ b/vban_cmd/event.py @@ -12,30 +12,30 @@ class Event: self.logger = logger.getChild(self.__class__.__name__) def info(self, msg=None): - info = (f"{msg} events",) if msg else () + info = (f'{msg} events',) if msg else () if self.any(): - info += (f"now listening for {', '.join(self.get())} events",) + info += (f'now listening for {", ".join(self.get())} events',) else: - info += (f"not listening for any events",) - self.logger.info(", ".join(info)) + info += ('not listening for any events',) + self.logger.info(', '.join(info)) @property def pdirty(self) -> bool: - return self.subs["pdirty"] + return self.subs['pdirty'] @pdirty.setter def pdirty(self, val: bool): - self.subs["pdirty"] = val - self.info(f"pdirty {'added to' if val else 'removed from'}") + self.subs['pdirty'] = val + self.info(f'pdirty {"added to" if val else "removed from"}') @property def ldirty(self) -> bool: - return self.subs["ldirty"] + return self.subs['ldirty'] @ldirty.setter def ldirty(self, val: bool): - self.subs["ldirty"] = val - self.info(f"ldirty {'added to' if val else 'removed from'}") + self.subs['ldirty'] = val + self.info(f'ldirty {"added to" if val else "removed from"}') def get(self) -> list: return [k for k, v in self.subs.items() if v] diff --git a/vban_cmd/factory.py b/vban_cmd/factory.py index 0121881..ad8b809 100644 --- a/vban_cmd/factory.py +++ b/vban_cmd/factory.py @@ -26,24 +26,24 @@ class FactoryBuilder: """ BuilderProgress = IntEnum( - "BuilderProgress", "strip bus command macrobutton vban", start=0 + 'BuilderProgress', 'strip bus command macrobutton vban', start=0 ) def __init__(self, factory, kind: KindMapClass): self._factory = factory self.kind = kind self._info = ( - f"Finished building strips for {self._factory}", - f"Finished building buses for {self._factory}", - f"Finished building commands for {self._factory}", - f"Finished building macrobuttons for {self._factory}", - f"Finished building vban in/out streams for {self._factory}", + f'Finished building strips for {self._factory}', + f'Finished building buses for {self._factory}', + f'Finished building commands for {self._factory}', + f'Finished building macrobuttons for {self._factory}', + f'Finished building vban in/out streams for {self._factory}', ) self.logger = logger.getChild(self.__class__.__name__) def _pinfo(self, name: str) -> None: """prints progress status for each step""" - name = name.split("_")[1] + name = name.split('_')[1] self.logger.info(self._info[int(getattr(self.BuilderProgress, name))]) def make_strip(self): @@ -78,20 +78,20 @@ class FactoryBase(VbanCmd): def __init__(self, kind_id: str, **kwargs): defaultkwargs = { - "ip": None, - "port": 6980, - "streamname": "Command1", - "bps": 0, - "channel": 0, - "ratelimit": 0.01, - "timeout": 5, - "outbound": False, - "sync": False, - "pdirty": False, - "ldirty": False, + 'ip': None, + 'port': 6980, + 'streamname': 'Command1', + 'bps': 0, + 'channel': 0, + 'ratelimit': 0.01, + 'timeout': 5, + 'outbound': False, + 'sync': False, + 'pdirty': False, + 'ldirty': False, } - if "subs" in kwargs: - defaultkwargs |= kwargs.pop("subs") # for backwards compatibility + if 'subs' in kwargs: + defaultkwargs |= kwargs.pop('subs') # for backwards compatibility kwargs = defaultkwargs | kwargs self.kind = kindmap(kind_id) super().__init__(**kwargs) @@ -106,7 +106,7 @@ class FactoryBase(VbanCmd): self._configs = None def __str__(self) -> str: - return f"Voicemeeter {self.kind}" + return f'Voicemeeter {self.kind}' def __repr__(self): return ( @@ -198,15 +198,15 @@ def vbancmd_factory(kind_id: str, **kwargs) -> VbanCmd: Returns a VbanCmd class of a kind """ match kind_id: - case "basic": + case 'basic': _factory = BasicFactory - case "banana": + case 'banana': _factory = BananaFactory - case "potato": + case 'potato': _factory = PotatoFactory case _: raise ValueError(f"Unknown Voicemeeter kind '{kind_id}'") - return type(f"VbanCmd{kind_id.capitalize()}", (_factory,), {})(kind_id, **kwargs) + return type(f'VbanCmd{kind_id.capitalize()}', (_factory,), {})(kind_id, **kwargs) def request_vbancmd_obj(kind_id: str, **kwargs) -> VbanCmd: @@ -215,12 +215,12 @@ def request_vbancmd_obj(kind_id: str, **kwargs) -> VbanCmd: Returns a reference to a VbanCmd class of a kind """ - logger_entry = logger.getChild("factory.request_vbancmd_obj") + logger_entry = logger.getChild('factory.request_vbancmd_obj') VBANCMD_obj = None try: VBANCMD_obj = vbancmd_factory(kind_id, **kwargs) except (ValueError, TypeError) as e: - logger_entry.exception(f"{type(e).__name__}: {e}") + logger_entry.exception(f'{type(e).__name__}: {e}') raise VBANCMDError(str(e)) from e return VBANCMD_obj diff --git a/vban_cmd/iremote.py b/vban_cmd/iremote.py index 2caefb6..0bafa0f 100644 --- a/vban_cmd/iremote.py +++ b/vban_cmd/iremote.py @@ -93,7 +93,7 @@ class IRemote(metaclass=ABCMeta): def getter(self, param): cmd = self._cmd(param) - self.logger.debug(f"getter: {cmd}") + self.logger.debug(f'getter: {cmd}') if cmd in self._remote.cache: return self._remote.cache.pop(cmd) if self._remote.sync: @@ -101,14 +101,14 @@ class IRemote(metaclass=ABCMeta): def setter(self, param, val): """Sends a string request RT packet.""" - self.logger.debug(f"setter: {self._cmd(param)}={val}") + self.logger.debug(f'setter: {self._cmd(param)}={val}') self._remote._set_rt(self._cmd(param), val) def _cmd(self, param): cmd = (self.identifier,) if param: - cmd += (f".{param}",) - return "".join(cmd) + cmd += (f'.{param}',) + return ''.join(cmd) @property @abstractmethod @@ -124,10 +124,10 @@ class IRemote(metaclass=ABCMeta): """Sets all parameters of a dict for the channel.""" def fget(attr, val): - if attr == "mode": - return (f"mode.{val}", 1) - elif attr == "knob": - return ("", val) + if attr == 'mode': + return (f'mode.{val}', 1) + elif attr == 'knob': + return ('', val) return (attr, val) for attr, val in data.items(): @@ -138,7 +138,7 @@ class IRemote(metaclass=ABCMeta): val = 1 if val else 0 self._remote.cache[self._cmd(attr)] = val - self._remote._script += f"{self._cmd(attr)}={val};" + self._remote._script += f'{self._cmd(attr)}={val};' else: target = getattr(self, attr) target.apply(val) diff --git a/vban_cmd/kinds.py b/vban_cmd/kinds.py index 17d13d1..e591fec 100644 --- a/vban_cmd/kinds.py +++ b/vban_cmd/kinds.py @@ -91,14 +91,14 @@ class PotatoMap(KindMapClass): def kind_factory(kind_id): match kind_id: - case "basic": + case 'basic': _kind_map = BasicMap - case "banana": + case 'banana': _kind_map = BananaMap - case "potato": + case 'potato': _kind_map = PotatoMap case _: - raise ValueError(f"Unknown Voicemeeter kind {kind_id}") + raise ValueError(f'Unknown Voicemeeter kind {kind_id}') return _kind_map(name=kind_id) diff --git a/vban_cmd/macrobutton.py b/vban_cmd/macrobutton.py index 3817005..ba2dc12 100644 --- a/vban_cmd/macrobutton.py +++ b/vban_cmd/macrobutton.py @@ -5,32 +5,32 @@ class MacroButton(IRemote): """A placeholder class in case this interface is being used interchangeably with the Remote API""" def __str__(self): - return f"{type(self).__name__}{self._remote.kind}{self.index}" + return f'{type(self).__name__}{self._remote.kind}{self.index}' @property def identifier(self): - return f"command.button[{self.index}]" + return f'command.button[{self.index}]' @property def state(self) -> bool: - self.logger.warning("button.state commands are not supported over VBAN") + self.logger.warning('button.state commands are not supported over VBAN') @state.setter def state(self, _): - self.logger.warning("button.state commands are not supported over VBAN") + self.logger.warning('button.state commands are not supported over VBAN') @property def stateonly(self) -> bool: - self.logger.warning("button.stateonly commands are not supported over VBAN") + self.logger.warning('button.stateonly commands are not supported over VBAN') @stateonly.setter def stateonly(self, v): - self.logger.warning("button.stateonly commands are not supported over VBAN") + self.logger.warning('button.stateonly commands are not supported over VBAN') @property def trigger(self) -> bool: - self.logger.warning("button.trigger commands are not supported over VBAN") + self.logger.warning('button.trigger commands are not supported over VBAN') @trigger.setter def trigger(self, _): - self.logger.warning("button.trigger commands are not supported over VBAN") + self.logger.warning('button.trigger commands are not supported over VBAN') diff --git a/vban_cmd/meta.py b/vban_cmd/meta.py index e99a275..2faf454 100644 --- a/vban_cmd/meta.py +++ b/vban_cmd/meta.py @@ -9,16 +9,16 @@ def channel_bool_prop(param): @partial(cache_bool, param=param) def fget(self): cmd = self._cmd(param) - self.logger.debug(f"getter: {cmd}") + self.logger.debug(f'getter: {cmd}') return ( not int.from_bytes( getattr( self.public_packet, - f"{'strip' if 'strip' in type(self).__name__.lower() else 'bus'}state", + f'{"strip" if "strip" in type(self).__name__.lower() else "bus"}state', )[self.index], - "little", + 'little', ) - & getattr(self._modes, f"_{param.lower()}") + & getattr(self._modes, f'_{param.lower()}') == 0 ) @@ -31,15 +31,15 @@ def channel_bool_prop(param): def channel_label_prop(): """meta function for channel label parameters""" - @partial(cache_string, param="label") + @partial(cache_string, param='label') def fget(self) -> str: return getattr( self.public_packet, - f"{'strip' if 'strip' in type(self).__name__.lower() else 'bus'}labels", + f'{"strip" if "strip" in type(self).__name__.lower() else "bus"}labels', )[self.index] def fset(self, val: str): - self.setter("label", str(val)) + self.setter('label', str(val)) return property(fget, fset) @@ -50,10 +50,10 @@ def strip_output_prop(param): @partial(cache_bool, param=param) def fget(self): cmd = self._cmd(param) - self.logger.debug(f"getter: {cmd}") + self.logger.debug(f'getter: {cmd}') return ( - not int.from_bytes(self.public_packet.stripstate[self.index], "little") - & getattr(self._modes, f"_bus{param.lower()}") + not int.from_bytes(self.public_packet.stripstate[self.index], 'little') + & getattr(self._modes, f'_bus{param.lower()}') == 0 ) @@ -69,9 +69,9 @@ def bus_mode_prop(param): @partial(cache_bool, param=param) def fget(self): cmd = self._cmd(param) - self.logger.debug(f"getter: {cmd}") + self.logger.debug(f'getter: {cmd}') return [ - (int.from_bytes(self.public_packet.busstate[self.index], "little") & val) + (int.from_bytes(self.public_packet.busstate[self.index], 'little') & val) >> 4 for val in self._modes.modevals ] == self.modestates[param] diff --git a/vban_cmd/packet.py b/vban_cmd/packet.py index 0c296db..789f669 100644 --- a/vban_cmd/packet.py +++ b/vban_cmd/packet.py @@ -43,7 +43,7 @@ class VbanRtPacket: def _generate_levels(self, levelarray) -> tuple: return tuple( - int.from_bytes(levelarray[i : i + 2], "little") + int.from_bytes(levelarray[i : i + 2], 'little') for i in range(0, len(levelarray), 2) ) @@ -79,13 +79,13 @@ class VbanRtPacket: tuple(not val for val in comp(strip_cache, self.strip_levels)), tuple(not val for val in comp(bus_cache, self.bus_levels)), ) - return any(any(l) for l in (self._strip_comp, self._bus_comp)) + return any(any(li) for li in (self._strip_comp, self._bus_comp)) @property def voicemeetertype(self) -> str: """returns voicemeeter type as a string""" - type_ = ("basic", "banana", "potato") - return type_[int.from_bytes(self._voicemeeterType, "little") - 1] + type_ = ('basic', 'banana', 'potato') + return type_[int.from_bytes(self._voicemeeterType, 'little') - 1] @property def voicemeeterversion(self) -> tuple: @@ -93,7 +93,7 @@ class VbanRtPacket: return tuple( reversed( tuple( - int.from_bytes(self._voicemeeterVersion[i : i + 1], "little") + int.from_bytes(self._voicemeeterVersion[i : i + 1], 'little') for i in range(4) ) ) @@ -102,7 +102,7 @@ class VbanRtPacket: @property def samplerate(self) -> int: """returns samplerate as an int""" - return int.from_bytes(self._samplerate, "little") + return int.from_bytes(self._samplerate, 'little') @property def inputlevels(self) -> tuple: @@ -132,56 +132,56 @@ class VbanRtPacket: @property def stripgainlayer1(self) -> tuple: return tuple( - int.from_bytes(self._stripGaindB100Layer1[i : i + 2], "little") + int.from_bytes(self._stripGaindB100Layer1[i : i + 2], 'little') for i in range(0, 16, 2) ) @property def stripgainlayer2(self) -> tuple: return tuple( - int.from_bytes(self._stripGaindB100Layer2[i : i + 2], "little") + int.from_bytes(self._stripGaindB100Layer2[i : i + 2], 'little') for i in range(0, 16, 2) ) @property def stripgainlayer3(self) -> tuple: return tuple( - int.from_bytes(self._stripGaindB100Layer3[i : i + 2], "little") + int.from_bytes(self._stripGaindB100Layer3[i : i + 2], 'little') for i in range(0, 16, 2) ) @property def stripgainlayer4(self) -> tuple: return tuple( - int.from_bytes(self._stripGaindB100Layer4[i : i + 2], "little") + int.from_bytes(self._stripGaindB100Layer4[i : i + 2], 'little') for i in range(0, 16, 2) ) @property def stripgainlayer5(self) -> tuple: return tuple( - int.from_bytes(self._stripGaindB100Layer5[i : i + 2], "little") + int.from_bytes(self._stripGaindB100Layer5[i : i + 2], 'little') for i in range(0, 16, 2) ) @property def stripgainlayer6(self) -> tuple: return tuple( - int.from_bytes(self._stripGaindB100Layer6[i : i + 2], "little") + int.from_bytes(self._stripGaindB100Layer6[i : i + 2], 'little') for i in range(0, 16, 2) ) @property def stripgainlayer7(self) -> tuple: return tuple( - int.from_bytes(self._stripGaindB100Layer7[i : i + 2], "little") + int.from_bytes(self._stripGaindB100Layer7[i : i + 2], 'little') for i in range(0, 16, 2) ) @property def stripgainlayer8(self) -> tuple: return tuple( - int.from_bytes(self._stripGaindB100Layer8[i : i + 2], "little") + int.from_bytes(self._stripGaindB100Layer8[i : i + 2], 'little') for i in range(0, 16, 2) ) @@ -189,7 +189,7 @@ class VbanRtPacket: def busgain(self) -> tuple: """returns tuple of bus gains""" return tuple( - int.from_bytes(self._busGaindB100[i : i + 2], "little") + int.from_bytes(self._busGaindB100[i : i + 2], 'little') for i in range(0, 16, 2) ) @@ -197,7 +197,7 @@ class VbanRtPacket: def striplabels(self) -> tuple: """returns tuple of strip labels""" return tuple( - self._stripLabelUTF8c60[i : i + 60].decode().split("\x00")[0] + self._stripLabelUTF8c60[i : i + 60].decode().split('\x00')[0] for i in range(0, 480, 60) ) @@ -205,7 +205,7 @@ class VbanRtPacket: def buslabels(self) -> tuple: """returns tuple of bus labels""" return tuple( - self._busLabelUTF8c60[i : i + 60].decode().split("\x00")[0] + self._busLabelUTF8c60[i : i + 60].decode().split('\x00')[0] for i in range(0, 480, 60) ) @@ -214,15 +214,15 @@ class VbanRtPacket: class SubscribeHeader: """Represents the header an RT Packet Service subscription packet""" - name = "Register RTP" + name = 'Register RTP' timeout = 15 - vban: bytes = "VBAN".encode() - format_sr: bytes = (VBAN_PROTOCOL_SERVICE).to_bytes(1, "little") - format_nbs: bytes = (0).to_bytes(1, "little") - format_nbc: bytes = (VBAN_SERVICE_RTPACKETREGISTER).to_bytes(1, "little") - format_bit: bytes = (timeout & 0x000000FF).to_bytes(1, "little") # timeout - streamname: bytes = name.encode("ascii") + bytes(16 - len(name)) - framecounter: bytes = (0).to_bytes(4, "little") + vban: bytes = 'VBAN'.encode() + format_sr: bytes = (VBAN_PROTOCOL_SERVICE).to_bytes(1, 'little') + format_nbs: bytes = (0).to_bytes(1, 'little') + format_nbc: bytes = (VBAN_SERVICE_RTPACKETREGISTER).to_bytes(1, 'little') + format_bit: bytes = (timeout & 0x000000FF).to_bytes(1, 'little') # timeout + streamname: bytes = name.encode('ascii') + bytes(16 - len(name)) + framecounter: bytes = (0).to_bytes(4, 'little') @property def header(self): @@ -233,9 +233,9 @@ class SubscribeHeader: header += self.format_bit header += self.streamname header += self.framecounter - assert ( - len(header) == HEADER_SIZE + 4 - ), f"expected header size {HEADER_SIZE} bytes + 4 bytes framecounter ({HEADER_SIZE +4} bytes total)" + assert len(header) == HEADER_SIZE + 4, ( + f'expected header size {HEADER_SIZE} bytes + 4 bytes framecounter ({HEADER_SIZE + 4} bytes total)' + ) return header @@ -243,13 +243,13 @@ class SubscribeHeader: class VbanRtPacketHeader: """Represents the header of a VBAN RT response packet""" - name = "Voicemeeter-RTP" - vban: bytes = "VBAN".encode() - format_sr: bytes = (VBAN_PROTOCOL_SERVICE).to_bytes(1, "little") - format_nbs: bytes = (0).to_bytes(1, "little") - format_nbc: bytes = (VBAN_SERVICE_RTPACKET).to_bytes(1, "little") - format_bit: bytes = (0).to_bytes(1, "little") - streamname: bytes = name.encode("ascii") + bytes(16 - len(name)) + name = 'Voicemeeter-RTP' + vban: bytes = 'VBAN'.encode() + format_sr: bytes = (VBAN_PROTOCOL_SERVICE).to_bytes(1, 'little') + format_nbs: bytes = (0).to_bytes(1, 'little') + format_nbc: bytes = (VBAN_SERVICE_RTPACKET).to_bytes(1, 'little') + format_bit: bytes = (0).to_bytes(1, 'little') + streamname: bytes = name.encode('ascii') + bytes(16 - len(name)) @property def header(self): @@ -259,7 +259,7 @@ class VbanRtPacketHeader: header += self.format_nbc header += self.format_bit header += self.streamname - assert len(header) == HEADER_SIZE, f"expected header size {HEADER_SIZE} bytes" + assert len(header) == HEADER_SIZE, f'expected header size {HEADER_SIZE} bytes' return header @@ -270,18 +270,18 @@ class RequestHeader: name: str bps_index: int channel: int - vban: bytes = "VBAN".encode() - nbs: bytes = (0).to_bytes(1, "little") - bit: bytes = (0x10).to_bytes(1, "little") - framecounter: bytes = (0).to_bytes(4, "little") + vban: bytes = 'VBAN'.encode() + nbs: bytes = (0).to_bytes(1, 'little') + bit: bytes = (0x10).to_bytes(1, 'little') + framecounter: bytes = (0).to_bytes(4, 'little') @property def sr(self): - return (VBAN_PROTOCOL_TXT + self.bps_index).to_bytes(1, "little") + return (VBAN_PROTOCOL_TXT + self.bps_index).to_bytes(1, 'little') @property def nbc(self): - return (self.channel).to_bytes(1, "little") + return (self.channel).to_bytes(1, 'little') @property def streamname(self): @@ -296,7 +296,7 @@ class RequestHeader: header += self.bit header += self.streamname header += self.framecounter - assert ( - len(header) == HEADER_SIZE + 4 - ), f"expected header size {HEADER_SIZE} bytes + 4 bytes framecounter ({HEADER_SIZE +4} bytes total)" + assert len(header) == HEADER_SIZE + 4, ( + f'expected header size {HEADER_SIZE} bytes + 4 bytes framecounter ({HEADER_SIZE + 4} bytes total)' + ) return header diff --git a/vban_cmd/strip.py b/vban_cmd/strip.py index bc85b11..c3716e6 100644 --- a/vban_cmd/strip.py +++ b/vban_cmd/strip.py @@ -20,7 +20,7 @@ class Strip(IRemote): @property def identifier(self) -> str: - return f"strip[{self.index}]" + return f'strip[{self.index}]' @property def limit(self) -> int: @@ -28,25 +28,25 @@ class Strip(IRemote): @limit.setter def limit(self, val: int): - self.setter("limit", val) + self.setter('limit', val) @property def gain(self) -> float: - val = self.getter("gain") + val = self.getter('gain') if val is None: val = self.gainlayer[0].gain return round(val, 1) @gain.setter def gain(self, val: float): - self.setter("gain", val) + self.setter('gain', val) def fadeto(self, target: float, time_: int): - self.setter("FadeTo", f"({target}, {time_})") + self.setter('FadeTo', f'({target}, {time_})') time.sleep(self._remote.DELAY) def fadeby(self, change: float, time_: int): - self.setter("FadeBy", f"({change}, {time_})") + self.setter('FadeBy', f'({change}, {time_})') time.sleep(self._remote.DELAY) @@ -54,18 +54,18 @@ class PhysicalStrip(Strip): @classmethod def make(cls, remote, index): return type( - f"PhysicalStrip{remote.kind}", + f'PhysicalStrip{remote.kind}', (cls,), { - "comp": StripComp(remote, index), - "gate": StripGate(remote, index), - "denoiser": StripDenoiser(remote, index), - "eq": StripEQ(remote, index), + 'comp': StripComp(remote, index), + 'gate': StripGate(remote, index), + 'denoiser': StripDenoiser(remote, index), + 'eq': StripEQ(remote, index), }, ) def __str__(self): - return f"{type(self).__name__}{self.index}" + return f'{type(self).__name__}{self.index}' @property def device(self): @@ -79,7 +79,7 @@ class PhysicalStrip(Strip): class StripComp(IRemote): @property def identifier(self) -> str: - return f"strip[{self.index}].comp" + return f'strip[{self.index}].comp' @property def knob(self) -> float: @@ -87,7 +87,7 @@ class StripComp(IRemote): @knob.setter def knob(self, val: float): - self.setter("", val) + self.setter('', val) @property def gainin(self) -> float: @@ -95,7 +95,7 @@ class StripComp(IRemote): @gainin.setter def gainin(self, val: float): - self.setter("GainIn", val) + self.setter('GainIn', val) @property def ratio(self) -> float: @@ -103,7 +103,7 @@ class StripComp(IRemote): @ratio.setter def ratio(self, val: float): - self.setter("Ratio", val) + self.setter('Ratio', val) @property def threshold(self) -> float: @@ -111,7 +111,7 @@ class StripComp(IRemote): @threshold.setter def threshold(self, val: float): - self.setter("Threshold", val) + self.setter('Threshold', val) @property def attack(self) -> float: @@ -119,7 +119,7 @@ class StripComp(IRemote): @attack.setter def attack(self, val: float): - self.setter("Attack", val) + self.setter('Attack', val) @property def release(self) -> float: @@ -127,7 +127,7 @@ class StripComp(IRemote): @release.setter def release(self, val: float): - self.setter("Release", val) + self.setter('Release', val) @property def knee(self) -> float: @@ -135,7 +135,7 @@ class StripComp(IRemote): @knee.setter def knee(self, val: float): - self.setter("Knee", val) + self.setter('Knee', val) @property def gainout(self) -> float: @@ -143,7 +143,7 @@ class StripComp(IRemote): @gainout.setter def gainout(self, val: float): - self.setter("GainOut", val) + self.setter('GainOut', val) @property def makeup(self) -> bool: @@ -151,13 +151,13 @@ class StripComp(IRemote): @makeup.setter def makeup(self, val: bool): - self.setter("makeup", 1 if val else 0) + self.setter('makeup', 1 if val else 0) class StripGate(IRemote): @property def identifier(self) -> str: - return f"strip[{self.index}].gate" + return f'strip[{self.index}].gate' @property def knob(self) -> float: @@ -165,7 +165,7 @@ class StripGate(IRemote): @knob.setter def knob(self, val: float): - self.setter("", val) + self.setter('', val) @property def threshold(self) -> float: @@ -173,7 +173,7 @@ class StripGate(IRemote): @threshold.setter def threshold(self, val: float): - self.setter("Threshold", val) + self.setter('Threshold', val) @property def damping(self) -> float: @@ -181,7 +181,7 @@ class StripGate(IRemote): @damping.setter def damping(self, val: float): - self.setter("Damping", val) + self.setter('Damping', val) @property def bpsidechain(self) -> int: @@ -189,7 +189,7 @@ class StripGate(IRemote): @bpsidechain.setter def bpsidechain(self, val: int): - self.setter("BPSidechain", val) + self.setter('BPSidechain', val) @property def attack(self) -> float: @@ -197,7 +197,7 @@ class StripGate(IRemote): @attack.setter def attack(self, val: float): - self.setter("Attack", val) + self.setter('Attack', val) @property def hold(self) -> float: @@ -205,7 +205,7 @@ class StripGate(IRemote): @hold.setter def hold(self, val: float): - self.setter("Hold", val) + self.setter('Hold', val) @property def release(self) -> float: @@ -213,13 +213,13 @@ class StripGate(IRemote): @release.setter def release(self, val: float): - self.setter("Release", val) + self.setter('Release', val) class StripDenoiser(IRemote): @property def identifier(self) -> str: - return f"strip[{self.index}].denoiser" + return f'strip[{self.index}].denoiser' @property def knob(self) -> float: @@ -227,13 +227,13 @@ class StripDenoiser(IRemote): @knob.setter def knob(self, val: float): - self.setter("", val) + self.setter('', val) class StripEQ(IRemote): @property def identifier(self) -> str: - return f"strip[{self.index}].eq" + return f'strip[{self.index}].eq' @property def on(self): @@ -241,7 +241,7 @@ class StripEQ(IRemote): @on.setter def on(self, val: bool): - self.setter("on", 1 if val else 0) + self.setter('on', 1 if val else 0) @property def ab(self): @@ -249,14 +249,14 @@ class StripEQ(IRemote): @ab.setter def ab(self, val: bool): - self.setter("ab", 1 if val else 0) + self.setter('ab', 1 if val else 0) class VirtualStrip(Strip): def __str__(self): - return f"{type(self).__name__}{self.index}" + return f'{type(self).__name__}{self.index}' - mc = channel_bool_prop("mc") + mc = channel_bool_prop('mc') mono = mc @@ -266,13 +266,13 @@ class VirtualStrip(Strip): @k.setter def k(self, val: int): - self.setter("karaoke", val) + self.setter('karaoke', val) def appgain(self, name: str, gain: float): - self.setter("AppGain", f'("{name}", {gain})') + self.setter('AppGain', f'("{name}", {gain})') def appmute(self, name: str, mute: bool = None): - self.setter("AppMute", f'("{name}", {1 if mute else 0})') + self.setter('AppMute', f'("{name}", {1 if mute else 0})') class StripLevel(IRemote): @@ -299,7 +299,7 @@ class StripLevel(IRemote): if not self._remote.stopped() and self._remote.event.ldirty: return tuple( fget(i) - for i in self._remote.cache["strip_level"][ + for i in self._remote.cache['strip_level'][ self.range[0] : self.range[-1] ] ) @@ -312,7 +312,7 @@ class StripLevel(IRemote): @property def identifier(self) -> str: - return f"strip[{self.index}]" + return f'strip[{self.index}]' @property def prefader(self) -> tuple: @@ -345,31 +345,33 @@ class GainLayer(IRemote): @property def identifier(self) -> str: - return f"strip[{self.index}]" + return f'strip[{self.index}]' @property def gain(self) -> float: def fget(): - val = getattr(self.public_packet, f"stripgainlayer{self._i+1}")[self.index] + val = getattr(self.public_packet, f'stripgainlayer{self._i + 1}')[ + self.index + ] if 0 <= val <= 1200: return val * 0.01 return (((1 << 16) - 1) - val) * -0.01 - val = self.getter(f"GainLayer[{self._i}]") + val = self.getter(f'GainLayer[{self._i}]') return round(val if val else fget(), 1) @gain.setter def gain(self, val: float): - self.setter(f"GainLayer[{self._i}]", val) + self.setter(f'GainLayer[{self._i}]', val) def _make_gainlayer_mixin(remote, index): """Creates a GainLayer mixin""" return type( - f"GainlayerMixin", + 'GainlayerMixin', (), { - "gainlayer": tuple( + 'gainlayer': tuple( GainLayer(remote, index, i) for i in range(remote.kind.num_bus) ) }, @@ -379,14 +381,14 @@ def _make_gainlayer_mixin(remote, index): def _make_channelout_mixin(kind): """Creates a channel out property mixin""" return type( - f"ChannelOutMixin{kind}", + f'ChannelOutMixin{kind}', (), { **{ - f"A{i}": strip_output_prop(f"A{i}") for i in range(1, kind.phys_out + 1) + f'A{i}': strip_output_prop(f'A{i}') for i in range(1, kind.phys_out + 1) }, **{ - f"B{i}": strip_output_prop(f"B{i}") for i in range(1, kind.virt_out + 1) + f'B{i}': strip_output_prop(f'B{i}') for i in range(1, kind.virt_out + 1) }, }, ) @@ -410,12 +412,12 @@ def strip_factory(is_phys_strip, remote, i) -> Union[PhysicalStrip, VirtualStrip GAINLAYERMIXIN_cls = _make_gainlayer_mixin(remote, i) return type( - f"{STRIP_cls.__name__}{remote.kind}", + f'{STRIP_cls.__name__}{remote.kind}', (STRIP_cls, CHANNELOUTMIXIN_cls, GAINLAYERMIXIN_cls), { - "levels": StripLevel(remote, i), - **{param: channel_bool_prop(param) for param in ["mono", "solo", "mute"]}, - "label": channel_label_prop(), + 'levels': StripLevel(remote, i), + **{param: channel_bool_prop(param) for param in ['mono', 'solo', 'mute']}, + 'label': channel_label_prop(), }, )(remote, i) diff --git a/vban_cmd/subject.py b/vban_cmd/subject.py index 4f14071..8e4ba77 100644 --- a/vban_cmd/subject.py +++ b/vban_cmd/subject.py @@ -20,10 +20,10 @@ class Subject: """run callbacks on update""" for o in self._observers: - if hasattr(o, "on_update"): + if hasattr(o, 'on_update'): o.on_update(event) else: - if o.__name__ == f"on_{event}": + if o.__name__ == f'on_{event}': o() def add(self, observer): @@ -34,15 +34,15 @@ class Subject: for o in iterator: if o not in self._observers: self._observers.append(o) - self.logger.info(f"{o} added to event observers") + self.logger.info(f'{o} added to event observers') else: - self.logger.error(f"Failed to add {o} to event observers") + self.logger.error(f'Failed to add {o} to event observers') except TypeError: if observer not in self._observers: self._observers.append(observer) - self.logger.info(f"{observer} added to event observers") + self.logger.info(f'{observer} added to event observers') else: - self.logger.error(f"Failed to add {observer} to event observers") + self.logger.error(f'Failed to add {observer} to event observers') register = add @@ -54,15 +54,15 @@ class Subject: for o in iterator: try: self._observers.remove(o) - self.logger.info(f"{o} removed from event observers") + self.logger.info(f'{o} removed from event observers') except ValueError: - self.logger.error(f"Failed to remove {o} from event observers") + self.logger.error(f'Failed to remove {o} from event observers') except TypeError: try: self._observers.remove(observer) - self.logger.info(f"{observer} removed from event observers") + self.logger.info(f'{observer} removed from event observers') except ValueError: - self.logger.error(f"Failed to remove {observer} from event observers") + self.logger.error(f'Failed to remove {observer} from event observers') deregister = remove diff --git a/vban_cmd/util.py b/vban_cmd/util.py index 0a9f601..260e1d5 100644 --- a/vban_cmd/util.py +++ b/vban_cmd/util.py @@ -1,4 +1,3 @@ -from enum import IntEnum from typing import Iterator @@ -42,15 +41,15 @@ def script(func): def wrapper(*args): remote, script = args if isinstance(script, dict): - params = "" + params = '' for key, val in script.items(): - obj, m2, *rem = key.split("-") + obj, m2, *rem = key.split('-') index = int(m2) if m2.isnumeric() else int(*rem) - params += ";".join( - f"{obj}{f'.{m2}stream' if not m2.isnumeric() else ''}[{index}].{k}={int(v) if isinstance(v, bool) else v}" + params += ';'.join( + f'{obj}{f".{m2}stream" if not m2.isnumeric() else ""}[{index}].{k}={int(v) if isinstance(v, bool) else v}' for k, v in val.items() ) - params += ";" + params += ';' script = params return func(remote, script) @@ -83,6 +82,3 @@ def deep_merge(dict1, dict2): yield k, dict1[k] else: yield k, dict2[k] - - -Socket = IntEnum("Socket", "register request response", start=0) diff --git a/vban_cmd/vban.py b/vban_cmd/vban.py index d97c5ee..ffd0643 100644 --- a/vban_cmd/vban.py +++ b/vban_cmd/vban.py @@ -17,7 +17,7 @@ class VbanStream(IRemote): @property def identifier(self) -> str: - return f"vban.{self.direction}stream[{self.index}]" + return f'vban.{self.direction}stream[{self.index}]' @property def on(self) -> bool: @@ -25,7 +25,7 @@ class VbanStream(IRemote): @on.setter def on(self, val: bool): - self.setter("on", 1 if val else 0) + self.setter('on', 1 if val else 0) @property def name(self) -> str: @@ -33,7 +33,7 @@ class VbanStream(IRemote): @name.setter def name(self, val: str): - self.setter("name", val) + self.setter('name', val) @property def ip(self) -> str: @@ -41,7 +41,7 @@ class VbanStream(IRemote): @ip.setter def ip(self, val: str): - self.setter("ip", val) + self.setter('ip', val) @property def port(self) -> int: @@ -51,9 +51,9 @@ class VbanStream(IRemote): def port(self, val: int): if not 1024 <= val <= 65535: self.logger.warning( - f"port got: {val} but expected a value from 1024 to 65535" + f'port got: {val} but expected a value from 1024 to 65535' ) - self.setter("port", val) + self.setter('port', val) @property def sr(self) -> int: @@ -63,8 +63,8 @@ class VbanStream(IRemote): def sr(self, val: int): opts = (11025, 16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000) if val not in opts: - self.logger.warning(f"sr got: {val} but expected a value in {opts}") - self.setter("sr", val) + self.logger.warning(f'sr got: {val} but expected a value in {opts}') + self.setter('sr', val) @property def channel(self) -> int: @@ -73,8 +73,8 @@ class VbanStream(IRemote): @channel.setter def channel(self, val: int): if not 1 <= val <= 8: - self.logger.warning(f"channel got: {val} but expected a value from 1 to 8") - self.setter("channel", val) + self.logger.warning(f'channel got: {val} but expected a value from 1 to 8') + self.setter('channel', val) @property def bit(self) -> int: @@ -83,8 +83,8 @@ class VbanStream(IRemote): @bit.setter def bit(self, val: int): if val not in (16, 24): - self.logger.warning(f"bit got: {val} but expected value 16 or 24") - self.setter("bit", 1 if (val == 16) else 2) + self.logger.warning(f'bit got: {val} but expected value 16 or 24') + self.setter('bit', 1 if (val == 16) else 2) @property def quality(self) -> int: @@ -93,8 +93,8 @@ class VbanStream(IRemote): @quality.setter def quality(self, val: int): if not 0 <= val <= 4: - self.logger.warning(f"quality got: {val} but expected a value from 0 to 4") - self.setter("quality", val) + self.logger.warning(f'quality got: {val} but expected a value from 0 to 4') + self.setter('quality', val) @property def route(self) -> int: @@ -103,8 +103,8 @@ class VbanStream(IRemote): @route.setter def route(self, val: int): if not 0 <= val <= 8: - self.logger.warning(f"route got: {val} but expected a value from 0 to 8") - self.setter("route", val) + self.logger.warning(f'route got: {val} but expected a value from 0 to 8') + self.setter('route', val) class VbanInstream(VbanStream): @@ -115,11 +115,11 @@ class VbanInstream(VbanStream): """ def __str__(self): - return f"{type(self).__name__}{self._remote.kind}{self.index}" + return f'{type(self).__name__}{self._remote.kind}{self.index}' @property def direction(self) -> str: - return "in" + return 'in' @property def sr(self) -> int: @@ -154,11 +154,11 @@ class VbanOutstream(VbanStream): """ def __str__(self): - return f"{type(self).__name__}{self._remote.kind}{self.index}" + return f'{type(self).__name__}{self._remote.kind}{self.index}' @property def direction(self) -> str: - return "out" + return 'out' class VbanAudioOutstream(VbanOutstream): @@ -174,22 +174,22 @@ def _make_stream_pair(remote, kind): def _make_cls(i, direction): match direction: - case "in": + case 'in': if i < num_instream: return VbanAudioInstream(remote, i) elif i < num_instream + num_midi: return VbanMidiInstream(remote, i) else: return VbanTextInstream(remote, i) - case "out": + case 'out': if i < num_outstream: return VbanAudioOutstream(remote, i) else: return VbanMidiOutstream(remote, i) return ( - tuple(_make_cls(i, "in") for i in range(num_instream + num_midi + num_text)), - tuple(_make_cls(i, "out") for i in range(num_outstream + num_midi)), + tuple(_make_cls(i, 'in') for i in range(num_instream + num_midi + num_text)), + tuple(_make_cls(i, 'out') for i in range(num_outstream + num_midi)), ) @@ -212,7 +212,7 @@ class Vban: """if VBAN disabled there can be no communication with it""" def disable(self): - self.remote._set_rt("vban.Enable", 0) + self.remote._set_rt('vban.Enable', 0) def vban_factory(remote) -> Vban: @@ -222,7 +222,7 @@ def vban_factory(remote) -> Vban: Returns a class that represents the VBAN module. """ VBAN_cls = Vban - return type(f"{VBAN_cls.__name__}", (VBAN_cls,), {})(remote) + return type(f'{VBAN_cls.__name__}', (VBAN_cls,), {})(remote) def request_vban_obj(remote) -> Vban: diff --git a/vban_cmd/vbancmd.py b/vban_cmd/vbancmd.py index e7f35ce..28b2136 100644 --- a/vban_cmd/vbancmd.py +++ b/vban_cmd/vbancmd.py @@ -11,7 +11,7 @@ from .error import VBANCMDError from .event import Event from .packet import RequestHeader from .subject import Subject -from .util import Socket, deep_merge, script +from .util import deep_merge, script from .worker import Producer, Subscriber, Updater logger = logging.getLogger(__name__) @@ -31,8 +31,8 @@ class VbanCmd(metaclass=ABCMeta): def __init__(self, **kwargs): self.logger = logger.getChild(self.__class__.__name__) - self.event = Event({k: kwargs.pop(k) for k in ("pdirty", "ldirty")}) - if not kwargs["ip"]: + self.event = Event({k: kwargs.pop(k) for k in ('pdirty', 'ldirty')}) + if not kwargs['ip']: kwargs |= self._conn_from_toml() for attr, val in kwargs.items(): setattr(self, attr, val) @@ -42,9 +42,7 @@ class VbanCmd(metaclass=ABCMeta): bps_index=self.BPS_OPTS.index(self.bps), channel=self.channel, ) - self.socks = tuple( - socket.socket(socket.AF_INET, socket.SOCK_DGRAM) for _ in Socket - ) + self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.subject = self.observer = Subject() self.cache = {} self._pdirty = False @@ -65,29 +63,30 @@ class VbanCmd(metaclass=ABCMeta): import tomli as tomllib def get_filepath(): - filepaths = [ - Path.cwd() / "vban.toml", - Path.cwd() / "configs" / "vban.toml", - Path.home() / ".config" / "vban-cmd" / "vban.toml", - Path.home() / "Documents" / "Voicemeeter" / "configs" / "vban.toml", - ] - for filepath in filepaths: - if filepath.exists(): - return filepath + for pn in ( + Path.cwd() / 'vban.toml', + Path.cwd() / 'configs' / 'vban.toml', + Path.home() / '.config' / 'vban-cmd' / 'vban.toml', + Path.home() / 'Documents' / 'Voicemeeter' / 'configs' / 'vban.toml', + ): + if pn.exists(): + return pn - if filepath := get_filepath(): - with open(filepath, "rb") as f: - conn = tomllib.load(f) - assert ( - "connection" in conn and "ip" in conn["connection"] - ), "expected [connection][ip] in vban config" - return conn["connection"] - raise VBANCMDError("no ip provided and no vban.toml located.") + if not (filepath := get_filepath()): + raise VBANCMDError('no ip provided and no vban.toml located.') + try: + with open(filepath, 'rb') as f: + return tomllib.load(f)['connection'] + except tomllib.TomlDecodeError as e: + raise VBANCMDError(f'Error decoding {filepath}: {e}') from e def __enter__(self): self.login() return self + def __exit__(self, exc_type, exc_value, exc_traceback) -> None: + self.logout() + def login(self) -> None: """Starts the subscriber and updater threads (unless in outbound mode)""" if not self.outbound: @@ -110,31 +109,41 @@ class VbanCmd(metaclass=ABCMeta): ) ) + def logout(self) -> None: + if not self.stopped(): + self.logger.debug('events thread shutdown started') + self.stop_event.set() + if self.producer is not None: + for t in (self.producer, self.subscriber): + t.join() + self.sock.close() + self.logger.info(f'{type(self).__name__}: Successfully logged out of {self}') + def stopped(self): return self.stop_event is None or self.stop_event.is_set() def _set_rt(self, cmd: str, val: Union[str, float]): """Sends a string request command over a network.""" - self.socks[Socket.request].sendto( - self.packet_request.header + f"{cmd}={val};".encode(), + self.sock.sendto( + self.packet_request.header + f'{cmd}={val};'.encode(), (socket.gethostbyname(self.ip), self.port), ) self.packet_request.framecounter = ( - int.from_bytes(self.packet_request.framecounter, "little") + 1 - ).to_bytes(4, "little") + int.from_bytes(self.packet_request.framecounter, 'little') + 1 + ).to_bytes(4, 'little') self.cache[cmd] = val @script def sendtext(self, script): """Sends a multiple parameter string over a network.""" - self.socks[Socket.request].sendto( + self.sock.sendto( self.packet_request.header + script.encode(), (socket.gethostbyname(self.ip), self.port), ) self.packet_request.framecounter = ( - int.from_bytes(self.packet_request.framecounter, "little") + 1 - ).to_bytes(4, "little") - self.logger.debug(f"sendtext: {script}") + int.from_bytes(self.packet_request.framecounter, 'little') + 1 + ).to_bytes(4, 'little') + self.logger.debug(f'sendtext: {script}') time.sleep(self.DELAY) @property @@ -145,7 +154,7 @@ class VbanCmd(metaclass=ABCMeta): @property def version(self) -> str: """Returns Voicemeeter's version as a string""" - return "{0}.{1}.{2}.{3}".format(*self.public_packet.voicemeeterversion) + return '{0}.{1}.{2}.{3}'.format(*self.public_packet.voicemeeterversion) @property def pdirty(self): @@ -184,16 +193,16 @@ class VbanCmd(metaclass=ABCMeta): """ def target(key): - match key.split("-"): - case ["strip" | "bus" as kls, index] if index.isnumeric(): + match key.split('-'): + case ['strip' | 'bus' as kls, index] if index.isnumeric(): target = getattr(self, kls) case [ - "vban", - "in" | "instream" | "out" | "outstream" as direction, + 'vban', + 'in' | 'instream' | 'out' | 'outstream' as direction, index, ] if index.isnumeric(): target = getattr( - self.vban, f"{direction.removesuffix('stream')}stream" + self.vban, f'{direction.removesuffix("stream")}stream' ) case _: ERR_MSG = f"invalid config key '{key}'" @@ -207,36 +216,23 @@ class VbanCmd(metaclass=ABCMeta): """applies a config from memory""" ERR_MSG = ( f"No config with name '{name}' is loaded into memory", - f"Known configs: {list(self.configs.keys())}", + f'Known configs: {list(self.configs.keys())}', ) try: config = self.configs[name] except KeyError as e: - self.logger.error(("\n").join(ERR_MSG)) - raise VBANCMDError(("\n").join(ERR_MSG)) from e + self.logger.error(('\n').join(ERR_MSG)) + raise VBANCMDError(('\n').join(ERR_MSG)) from e - if "extends" in config: - extended = config["extends"] + if 'extends' in config: + extended = config['extends'] config = { k: v for k, v in deep_merge(self.configs[extended], config) - if k not in ("extends") + if k not in ('extends') } self.logger.debug( f"profile '{name}' extends '{extended}', profiles merged.." ) self.apply(config) self.logger.info(f"Profile '{name}' applied!") - - def logout(self) -> None: - if not self.stopped(): - self.logger.debug("events thread shutdown started") - self.stop_event.set() - if self.producer is not None: - for t in (self.producer, self.subscriber): - t.join() - [sock.close() for sock in self.socks] - self.logger.info(f"{type(self).__name__}: Successfully logged out of {self}") - - def __exit__(self, exc_type, exc_value, exc_traceback) -> None: - self.logout() diff --git a/vban_cmd/worker.py b/vban_cmd/worker.py index 6840222..3a05b8a 100644 --- a/vban_cmd/worker.py +++ b/vban_cmd/worker.py @@ -6,7 +6,6 @@ from typing import Optional from .error import VBANCMDConnectionError from .packet import HEADER_SIZE, SubscribeHeader, VbanRtPacket, VbanRtPacketHeader -from .util import Socket logger = logging.getLogger(__name__) @@ -15,7 +14,7 @@ class Subscriber(threading.Thread): """fire a subscription packet every 10 seconds""" def __init__(self, remote, stop_event): - super().__init__(name="subscriber", daemon=False) + super().__init__(name='subscriber', daemon=False) self._remote = remote self.stop_event = stop_event self.logger = logger.getChild(self.__class__.__name__) @@ -24,20 +23,20 @@ class Subscriber(threading.Thread): def run(self): while not self.stopped(): try: - self._remote.socks[Socket.register].sendto( + self._remote.sock.sendto( self.packet.header, (socket.gethostbyname(self._remote.ip), self._remote.port), ) self.packet.framecounter = ( - int.from_bytes(self.packet.framecounter, "little") + 1 - ).to_bytes(4, "little") + int.from_bytes(self.packet.framecounter, 'little') + 1 + ).to_bytes(4, 'little') self.wait_until_stopped(10) except socket.gaierror as e: - self.logger.exception(f"{type(e).__name__}: {e}") + self.logger.exception(f'{type(e).__name__}: {e}') raise VBANCMDConnectionError( - f"unable to resolve hostname {self._remote.ip}" + f'unable to resolve hostname {self._remote.ip}' ) from e - self.logger.debug(f"terminating {self.name} thread") + self.logger.debug(f'terminating {self.name} thread') def stopped(self): return self.stop_event.is_set() @@ -54,20 +53,17 @@ class Producer(threading.Thread): """Continously send job queue to the Updater thread at a rate of self._remote.ratelimit.""" def __init__(self, remote, queue, stop_event): - super().__init__(name="producer", daemon=False) + super().__init__(name='producer', daemon=False) self._remote = remote self.queue = queue self.stop_event = stop_event self.logger = logger.getChild(self.__class__.__name__) self.packet_expected = VbanRtPacketHeader() - self._remote.socks[Socket.response].settimeout(self._remote.timeout) - self._remote.socks[Socket.response].bind( - (socket.gethostbyname(socket.gethostname()), self._remote.port) - ) + self._remote.sock.settimeout(self._remote.timeout) self._remote._public_packet = self._get_rt() ( - self._remote.cache["strip_level"], - self._remote.cache["bus_level"], + self._remote.cache['strip_level'], + self._remote.cache['bus_level'], ) = self._remote._get_levels(self._remote.public_packet) def _get_rt(self) -> VbanRtPacket: @@ -83,7 +79,7 @@ class Producer(threading.Thread): def _fetch_rt_packet(self) -> Optional[VbanRtPacket]: try: - data, _ = self._remote.socks[Socket.response].recvfrom(2048) + data, _ = self._remote.sock.recvfrom(2048) # do we have packet data? if len(data) > HEADER_SIZE: # is the packet of type VBAN RT response? @@ -114,9 +110,9 @@ class Producer(threading.Thread): _busLabelUTF8c60=data[932:1412], ) except TimeoutError as e: - self.logger.exception(f"{type(e).__name__}: {e}") + self.logger.exception(f'{type(e).__name__}: {e}') raise VBANCMDConnectionError( - f"timeout waiting for RtPacket from {self._remote.ip}" + f'timeout waiting for RtPacket from {self._remote.ip}' ) from e def stopped(self): @@ -127,7 +123,7 @@ class Producer(threading.Thread): _pp = self._get_rt() pdirty = _pp.pdirty(self._remote.public_packet) ldirty = _pp.ldirty( - self._remote.cache["strip_level"], self._remote.cache["bus_level"] + self._remote.cache['strip_level'], self._remote.cache['bus_level'] ) if pdirty or ldirty: @@ -136,11 +132,11 @@ class Producer(threading.Thread): self._remote._ldirty = ldirty if self._remote.event.pdirty: - self.queue.put("pdirty") + self.queue.put('pdirty') if self._remote.event.ldirty: - self.queue.put("ldirty") + self.queue.put('ldirty') time.sleep(self._remote.ratelimit) - self.logger.debug(f"terminating {self.name} thread") + self.logger.debug(f'terminating {self.name} thread') self.queue.put(None) @@ -152,7 +148,7 @@ class Updater(threading.Thread): """ def __init__(self, remote, queue): - super().__init__(name="updater", daemon=True) + super().__init__(name='updater', daemon=True) self._remote = remote self.queue = queue self.logger = logger.getChild(self.__class__.__name__) @@ -166,19 +162,19 @@ class Updater(threading.Thread): Generate _strip_comp, _bus_comp and update level cache if ldirty. """ while event := self.queue.get(): - if event == "pdirty" and self._remote.pdirty: + if event == 'pdirty' and self._remote.pdirty: self._remote.subject.notify(event) - elif event == "ldirty" and self._remote.ldirty: + elif event == 'ldirty' and self._remote.ldirty: self._remote._strip_comp, self._remote._bus_comp = ( self._remote._public_packet._strip_comp, self._remote._public_packet._bus_comp, ) ( - self._remote.cache["strip_level"], - self._remote.cache["bus_level"], + self._remote.cache['strip_level'], + self._remote.cache['bus_level'], ) = ( self._remote._public_packet.inputlevels, self._remote._public_packet.outputlevels, ) self._remote.subject.notify(event) - self.logger.debug(f"terminating {self.name} thread") + self.logger.debug(f'terminating {self.name} thread')