From a24ae9cc8d7e039a16b07202c40b355831c59235 Mon Sep 17 00:00:00 2001 From: Sameer Rahmani Date: Sat, 20 May 2023 00:15:50 +0100 Subject: [PATCH] Add the worker for the text to speech --- meissa/server.py | 139 ++++++++-------------- meissa/worker.py | 103 ++++++++++++++++ poetry.lock | 298 ++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 1 + 4 files changed, 449 insertions(+), 92 deletions(-) create mode 100644 meissa/worker.py diff --git a/meissa/server.py b/meissa/server.py index 222cb37..da1f3ff 100644 --- a/meissa/server.py +++ b/meissa/server.py @@ -13,94 +13,11 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . - import sys import asyncio -from pathlib import Path - import msgpack -# pylint: disable=redefined-outer-name, unused-argument - - -import numpy as np -import simpleaudio as sa - -from TTS.utils.manage import ModelManager -from TTS.utils.synthesizer import Synthesizer - -from meissa import utils - - -def to_wav_data(wav): - wav_norm = np.array(wav) * (32767 / max(0.01, np.max(np.abs(wav)))) - return wav_norm.astype(np.int16) - - -def play(wav): - try: - play = sa.play_buffer(to_wav_data(wav), 1, 2, 22050) - # Wait for audio playback to finish before exiting - play.wait_done() - - finally: - play.stop() - - -def synth(ctx): - path = Path(__file__).parent / "speaker.models.json" - manager = ModelManager(path) - - language_ids_file_path = None - vocoder_path = None - vocoder_config_path = None - encoder_path = None - encoder_config_path = None - - model_path, config_path, model_item = manager.download_model( - ctx.get("MODEL_NAME", "tts_models/en/ljspeech/tacotron2-DDC") - ) - vocoder_name = model_item["default_vocoder"] - vocoder_path, vocoder_config_path, _ = manager.download_model(vocoder_name) - speaker_idx = ctx.get("SPEAKER_IDX") - - # load models - synthesizer = Synthesizer( - model_path, - config_path, - None, - language_ids_file_path, - vocoder_path, - vocoder_config_path, - encoder_path, - encoder_config_path, - False, - ) - - async def tcp_handler(reader, writer): - while True: - # Read till EOF - data = await reader.read(4) - msg_len = int.from_bytes(data, "big") - - if msg_len != 0: - data = await reader.read(msg_len) - message = data.decode() - - print(f"Received {message!r}") - - if message == "//close": - break - - wav = synthesizer.tts(message, speaker_idx, "None", None) - synthesizer.save_wav(wav, "/tmp/blah.wav") - play(wav) - - writer.write(b"Ok") - await writer.drain() - writer.close() - - return tcp_handler +from meissa import utils, worker class Server: @@ -110,27 +27,65 @@ class Server: def __init__(self, ctx): self.queue = asyncio.Queue() - self.current_job = None - self.running = True + self.stop_event = asyncio.Event() + self.worker_stop = asyncio.Event() self.ctx = ctx + self.worker = None + + def create_worker(self): + self.worker = asyncio.create_task( + worker.worker( + self.ctx, + self.queue, + self.worker_stop, + self.stop_event, + ) + ) + + def stop_worker(self): + self.worker_stop.set() + self.stop_event.set() + self.empty_queue() + + def empty_queue(self): + while not self.queue.empty(): + job = self.queue.get_nowait() + job.task_done() + + def verify_speech_payload(self, payload): + if not payload.get("text"): + return self.err( + "'text' field is mandatory for 'enqueue', 'clear_n_enqueue'" + ) + return None async def command_stop(self): - self.running = False utils.info("Stopping all jobs") + self.stop_worker() + self.create_worker() return self.ok() - async def command_stop_and_play(self, job): - self.running = False + async def command_clear_n_enqueue(self, job): + err = self.verify_speech_payload(job) + if err: + return err + + self.stop_worker() + self.create_worker() + utils.info(f"Stopping all jobs and starting: {job}") - self.current_job = job + self.queue.put_nowait(job) return self.ok() async def command_enqueue(self, job): + err = self.verify_speech_payload(job) + if err: + return err self.queue.put_nowait(job) utils.info(f"Enqueued job: {job}") return self.ok() - async def command_status(self, payload): + async def command_status(self, _): return self.ok({"queue": self.queue.qsize()}) def err(self, msg): @@ -158,6 +113,8 @@ class Server: return self.err(f"No command '{command}'!") async def handle_client(self, reader, writer): + self.create_worker() + while True: data = await reader.read(1024) if not data: diff --git a/meissa/worker.py b/meissa/worker.py new file mode 100644 index 0000000..e640353 --- /dev/null +++ b/meissa/worker.py @@ -0,0 +1,103 @@ +# Meissa - A trainable and simple text to speech server +# +# Copyright (c) 2023 Sameer Rahmani +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 2. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +import asyncio +from pathlib import Path + +# pylint: disable=redefined-outer-name, unused-argument +import numpy as np +import simpleaudio as sa + +from TTS.utils.manage import ModelManager +from TTS.utils.synthesizer import Synthesizer + +from meissa import utils + + +def to_wav_data(wav): + wav_norm = np.array(wav) * (32767 / max(0.01, np.max(np.abs(wav)))) + return wav_norm.astype(np.int16) + + +def play(wav): + return sa.play_buffer(to_wav_data(wav), 1, 2, 22050) + + +def create_synth(ctx): + path = Path(__file__).parent / "speaker.models.json" + manager = ModelManager(path) + + language_ids_file_path = None + vocoder_path = None + vocoder_config_path = None + encoder_path = None + encoder_config_path = None + + model_path, config_path, model_item = manager.download_model( + utils.config(ctx).get("model_name", "tts_models/en/ljspeech/tacotron2-DDC") + ) + vocoder_name = model_item["default_vocoder"] + vocoder_path, vocoder_config_path, _ = manager.download_model(vocoder_name) + + # load models + return Synthesizer( + model_path, + config_path, + None, + language_ids_file_path, + vocoder_path, + vocoder_config_path, + encoder_path, + encoder_config_path, + False, + ) + + +async def worker(ctx, job_queue, worker_stop, stop_event): + utils.info("Spawning a worker...") + synthesizer = create_synth(ctx) + speaker_idx = utils.config(ctx).get("SPEAKER_IDX") + + while not worker_stop.is_set(): + try: + job = await job_queue.get() + utils.info(f"Running job: {job}") + + txt = job["text"] + speaker = job.get("speaker", speaker_idx) + wav = synthesizer.tts(txt, speaker, "None", None) + + # uncomment for debugging + synthesizer.save_wav(wav, "/tmp/blah.wav") + + utils.info("Playing...") + + try: + # wait until the audio finish playing or the stop + # event is set by the sever. E.g. via a command + # from client + player = play(wav) + + while not stop_event.is_set() and player.is_playing: + await asyncio.sleep(0.05) + finally: + player.stop() + stop_event.clear() + + job_queue.task_done() + except asyncio.QueueEmpty: + await asyncio.sleep(0.1) + + utils.info("Worker stopped") diff --git a/poetry.lock b/poetry.lock index b9fae65..41923c3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -148,6 +148,18 @@ files = [ {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, ] +[[package]] +name = "appnope" +version = "0.1.3" +description = "Disable App Nap on macOS >= 10.9" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"}, + {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, +] + [[package]] name = "astroid" version = "2.15.3" @@ -165,6 +177,24 @@ lazy-object-proxy = ">=1.4.0" typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} wrapt = {version = ">=1.11,<2", markers = "python_version < \"3.11\""} +[[package]] +name = "asttokens" +version = "2.2.1" +description = "Annotate AST trees with source code positions" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "asttokens-2.2.1-py2.py3-none-any.whl", hash = "sha256:6b0ac9e93fb0335014d382b8fa9b3afa7df546984258005da0b9e7095b3deb1c"}, + {file = "asttokens-2.2.1.tar.gz", hash = "sha256:4622110b2a6f30b77e1473affaa97e711bc2f07d3f10848420ff1898edbe94f3"}, +] + +[package.dependencies] +six = "*" + +[package.extras] +test = ["astroid", "pytest"] + [[package]] name = "async-timeout" version = "4.0.2" @@ -219,6 +249,18 @@ files = [ {file = "Babel-2.12.1.tar.gz", hash = "sha256:cc2d99999cd01d44420ae725a21c9e3711b3aadc7976d6147f622d8581963455"}, ] +[[package]] +name = "backcall" +version = "0.2.0" +description = "Specifications for callback functions passed in to an API" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, + {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, +] + [[package]] name = "bangla" version = "0.0.2" @@ -714,6 +756,21 @@ files = [ {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, ] +[[package]] +name = "executing" +version = "1.2.0" +description = "Get the currently executing AST node of a frame, and other information" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "executing-1.2.0-py2.py3-none-any.whl", hash = "sha256:0314a69e37426e3608aada02473b4161d4caf5a4b244d1d0c48072b8fee7bacc"}, + {file = "executing-1.2.0.tar.gz", hash = "sha256:19da64c18d2d851112f09c287f8d3dbbdf725ab0e569077efb6cdcbd3497c107"}, +] + +[package.extras] +tests = ["asttokens", "littleutils", "pytest", "rich"] + [[package]] name = "filelock" version = "3.12.0" @@ -1032,6 +1089,62 @@ files = [ docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx"] testing = ["pygments", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] +[[package]] +name = "ipdb" +version = "0.13.13" +description = "IPython-enabled pdb" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "ipdb-0.13.13-py3-none-any.whl", hash = "sha256:45529994741c4ab6d2388bfa5d7b725c2cf7fe9deffabdb8a6113aa5ed449ed4"}, + {file = "ipdb-0.13.13.tar.gz", hash = "sha256:e3ac6018ef05126d442af680aad863006ec19d02290561ac88b8b1c0b0cfc726"}, +] + +[package.dependencies] +decorator = {version = "*", markers = "python_version > \"3.6\" and python_version < \"3.11\""} +ipython = {version = ">=7.31.1", markers = "python_version > \"3.6\" and python_version < \"3.11\""} +tomli = {version = "*", markers = "python_version > \"3.6\" and python_version < \"3.11\""} + +[[package]] +name = "ipython" +version = "8.13.2" +description = "IPython: Productive Interactive Computing" +category = "dev" +optional = false +python-versions = ">=3.9" +files = [ + {file = "ipython-8.13.2-py3-none-any.whl", hash = "sha256:ffca270240fbd21b06b2974e14a86494d6d29290184e788275f55e0b55914926"}, + {file = "ipython-8.13.2.tar.gz", hash = "sha256:7dff3fad32b97f6488e02f87b970f309d082f758d7b7fc252e3b19ee0e432dbb"}, +] + +[package.dependencies] +appnope = {version = "*", markers = "sys_platform == \"darwin\""} +backcall = "*" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +decorator = "*" +jedi = ">=0.16" +matplotlib-inline = "*" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} +pickleshare = "*" +prompt-toolkit = ">=3.0.30,<3.0.37 || >3.0.37,<3.1.0" +pygments = ">=2.4.0" +stack-data = "*" +traitlets = ">=5" + +[package.extras] +all = ["black", "curio", "docrepr", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.21)", "pandas", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"] +black = ["black"] +doc = ["docrepr", "ipykernel", "matplotlib", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"] +kernel = ["ipykernel"] +nbconvert = ["nbconvert"] +nbformat = ["nbformat"] +notebook = ["ipywidgets", "notebook"] +parallel = ["ipyparallel"] +qtconsole = ["qtconsole"] +test = ["pytest (<7.1)", "pytest-asyncio", "testpath"] +test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.21)", "pandas", "pytest (<7.1)", "pytest-asyncio", "testpath", "trio"] + [[package]] name = "isort" version = "5.12.0" @@ -1074,6 +1187,26 @@ files = [ {file = "jamo-0.4.1.tar.gz", hash = "sha256:ea65cf9d35338d0e0af48d75ff426d8a369b0ebde6f07051c3ac37256f56d025"}, ] +[[package]] +name = "jedi" +version = "0.18.2" +description = "An autocompletion tool for Python that can be used for text editors." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "jedi-0.18.2-py2.py3-none-any.whl", hash = "sha256:203c1fd9d969ab8f2119ec0a3342e0b49910045abe6af0a3ae83a5764d54639e"}, + {file = "jedi-0.18.2.tar.gz", hash = "sha256:bae794c30d07f6d910d32a7048af09b5a39ed740918da923c6b780790ebac612"}, +] + +[package.dependencies] +parso = ">=0.8.0,<0.9.0" + +[package.extras] +docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +testing = ["Django (<3.1)", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] + [[package]] name = "jieba" version = "0.42.1" @@ -1473,6 +1606,21 @@ pillow = ">=6.2.0" pyparsing = ">=2.3.1" python-dateutil = ">=2.7" +[[package]] +name = "matplotlib-inline" +version = "0.1.6" +description = "Inline Matplotlib backend for Jupyter" +category = "dev" +optional = false +python-versions = ">=3.5" +files = [ + {file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"}, + {file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"}, +] + +[package.dependencies] +traitlets = "*" + [[package]] name = "mccabe" version = "0.7.0" @@ -2096,6 +2244,49 @@ sql-other = ["SQLAlchemy (>=1.4.16)"] test = ["hypothesis (>=6.34.2)", "pytest (>=7.0.0)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)"] xml = ["lxml (>=4.6.3)"] +[[package]] +name = "parso" +version = "0.8.3" +description = "A Python Parser" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, + {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, +] + +[package.extras] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +testing = ["docopt", "pytest (<6.0.0)"] + +[[package]] +name = "pexpect" +version = "4.8.0" +description = "Pexpect allows easy control of interactive console applications." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, + {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, +] + +[package.dependencies] +ptyprocess = ">=0.5" + +[[package]] +name = "pickleshare" +version = "0.7.5" +description = "Tiny 'shelve'-like database with concurrency support" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, + {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, +] + [[package]] name = "pillow" version = "9.5.0" @@ -2233,6 +2424,21 @@ nodeenv = ">=0.11.1" pyyaml = ">=5.1" virtualenv = ">=20.10.0" +[[package]] +name = "prompt-toolkit" +version = "3.0.38" +description = "Library for building powerful interactive command lines in Python" +category = "dev" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "prompt_toolkit-3.0.38-py3-none-any.whl", hash = "sha256:45ea77a2f7c60418850331366c81cf6b5b9cf4c7fd34616f733c5427e6abbb1f"}, + {file = "prompt_toolkit-3.0.38.tar.gz", hash = "sha256:23ac5d50538a9a38c8bde05fecb47d0b403ecd0662857a86f886f798563d5b9b"}, +] + +[package.dependencies] +wcwidth = "*" + [[package]] name = "protobuf" version = "3.19.6" @@ -2295,6 +2501,33 @@ files = [ [package.extras] test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] +[[package]] +name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] + +[[package]] +name = "pure-eval" +version = "0.2.2" +description = "Safely evaluate AST nodes without side effects" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"}, + {file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"}, +] + +[package.extras] +tests = ["pytest"] + [[package]] name = "pycparser" version = "2.21" @@ -2307,6 +2540,21 @@ files = [ {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, ] +[[package]] +name = "pygments" +version = "2.15.1" +description = "Pygments is a syntax highlighting package written in Python." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Pygments-2.15.1-py3-none-any.whl", hash = "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"}, + {file = "Pygments-2.15.1.tar.gz", hash = "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c"}, +] + +[package.extras] +plugins = ["importlib-metadata"] + [[package]] name = "pylint" version = "2.17.2" @@ -2821,6 +3069,26 @@ numpy = "*" docs = ["linkify-it-py", "myst-parser", "sphinx", "sphinx-book-theme"] test = ["pytest"] +[[package]] +name = "stack-data" +version = "0.6.2" +description = "Extract data from python stack frames and tracebacks for informative displays" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "stack_data-0.6.2-py3-none-any.whl", hash = "sha256:cbb2a53eb64e5785878201a97ed7c7b94883f48b87bfb0bbe8b623c74679e4a8"}, + {file = "stack_data-0.6.2.tar.gz", hash = "sha256:32d2dd0376772d01b6cb9fc996f3c8b57a357089dec328ed4b6553d037eaf815"}, +] + +[package.dependencies] +asttokens = ">=2.1.0" +executing = ">=1.2.0" +pure-eval = "*" + +[package.extras] +tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] + [[package]] name = "sympy" version = "1.11.1" @@ -3025,6 +3293,22 @@ all = ["black", "coqpit", "coverage", "fsspec", "isort", "protobuf (>=3.9.2,<3.2 dev = ["black", "coverage", "isort", "pylint (==2.10.2)", "pytest"] test = ["torchvision"] +[[package]] +name = "traitlets" +version = "5.9.0" +description = "Traitlets Python configuration system" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "traitlets-5.9.0-py3-none-any.whl", hash = "sha256:9e6ec080259b9a5940c797d58b613b5e31441c2257b87c2e795c5228ae80d2d8"}, + {file = "traitlets-5.9.0.tar.gz", hash = "sha256:f6cde21a9c68cf756af02035f72d5a723bf607e862e7be33ece505abf4a3bad9"}, +] + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["argcomplete (>=2.0)", "pre-commit", "pytest", "pytest-mock"] + [[package]] name = "triton" version = "2.0.0" @@ -3232,6 +3516,18 @@ platformdirs = ">=3.2,<4" docs = ["furo (>=2023.3.27)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=22.12)"] test = ["covdefaults (>=2.3)", "coverage (>=7.2.3)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.3.1)", "pytest-env (>=0.8.1)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.10)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)"] +[[package]] +name = "wcwidth" +version = "0.2.6" +description = "Measures the displayed width of unicode strings in a terminal" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "wcwidth-0.2.6-py2.py3-none-any.whl", hash = "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e"}, + {file = "wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"}, +] + [[package]] name = "werkzeug" version = "2.2.3" @@ -3441,4 +3737,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = ">=3.10,<3.11" -content-hash = "8d72955bdf3e95e400eb660e7f877c3e2abd29e209a8864979cc0981ad0fda3a" +content-hash = "48f29c37a983f8be8625c486851c41800f35f60b6b2c772863bf48760ed99bf0" diff --git a/pyproject.toml b/pyproject.toml index 5a46fa4..44c5f93 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,7 @@ simpleaudio = "^1.0.4" [tool.poetry.group.dev.dependencies] pre-commit = "^3.2.2" pylint = "^2.17.2" +ipdb = "^0.13.13" [build-system] requires = ["poetry-core"]