Changeset - acf1b68d031a
[Not reviewed]
default
0 4 0
drewp@bigasterisk.com - 20 months ago 2023-05-23 21:43:12
drewp@bigasterisk.com
fancier background_loop reporting for faders
4 files changed with 56 insertions and 31 deletions:
0 comments (0 inline, 0 general)
lib/background_loop.py
Show inline comments
 
# dev copy
 
import asyncio
 
import logging
 
import time
 
import traceback
 
from typing import Any, Awaitable, Callable, Union
 

	
 
from prometheus_client import Gauge, Summary
 
from light9.recentfps import RecentFps
 
from braillegraph import horizontal_graph
 

	
 
log = logging.getLogger()
 
log = logging.getLogger('loop')
 

	
 

	
 
# throw this away (when net_routes is rewritten)
 
def loop_forever_sync(func, sleep_period, up_metric):
 
    first_run = True
 
    while True:
 
        try:
 
            func(first_run)
 
            up_metric.set(1)
 
            first_run = False
 
        except Exception as ex:
 
            log.error(ex)
 
            traceback.print_exc()
 
            up_metric.set(0)
 
        time.sleep(sleep_period)
 

	
 

	
 
_created = []
 

	
 
# todo: some tricky typing
 
F_RET = Any
 
F_KWARGS = Any
 

	
 
UserAsyncFunc = Callable[...,  # always called with at least kwarg first_run=bool
 
                         Awaitable[F_RET]]
 
UserSyncFunc = Callable[...,  # see above
 
                        F_RET]
 
UserFunc = Union[UserAsyncFunc, UserSyncFunc]
 

	
 

	
 
class Loop:
 

	
 
    def __init__(
 
        self,
 
        func: UserFunc,
 
        sleep_period: float,
 
        metric_prefix: str,
 
        extra_sleep_on_error: float = 2,
 
        log_fps=False,
 
    ):
 
        self.func = func
 
        self.sleep_period = sleep_period
 
        self.extra_sleep_on_error = extra_sleep_on_error
 
        self.metric_prefix = metric_prefix
 

	
 
        self.up_metric = Gauge(f'{metric_prefix}_up', 'not erroring')
 
        self.call_metric = Summary(f'{metric_prefix}_calls', 'calls')
 
        self.lastSuccessRun = 0
 
        self.everSucceeded = False
 
        self.succeeding = False
 
        self.fps = RecentFps() if log_fps else None
 
        self.lastFpsLog = 0
 

	
 
    async def call_func(self, first_run: bool, call_kwargs={}) -> F_RET:
 
        with self.call_metric.time():
 
            if asyncio.iscoroutinefunction(self.func):
 
                ret = await self.func(first_run=first_run, **call_kwargs)
 
            else:
 
                ret = self.func(first_run=first_run, **call_kwargs)
 
        return ret
 

	
 
    async def _run(self):
 
        self.first_run = True
 
        self.newlyFailing = True
 
        while True:
 
            await self._runOne()
 
            await asyncio.sleep(self.sleep_period)
 

	
 
    async def runNow(self, **kwargs: F_KWARGS) -> F_RET:
 
        """unlike background runs, you get to pass more args and get your func's
 
        return value.
 
        """
 
        return await self._runOne(call_kwargs=kwargs)
 

	
 
    async def _runOne(self, call_kwargs={}):
 
        now = time.time()
 
        self._updateFps(now)
 
        try:
 
            result = await self.call_func(self.first_run, call_kwargs)
 
            self.lastSuccessRun = time.time()
 
            if self.fps:
 
                self.fps.mark()
 
            self.up_metric.set(1)
 
            self.everSucceeded = True
 
            self.newlyFailing = True
 
            self.first_run = False
 
        except Exception as ex:
 
            log.error(ex)
 
            traceback.print_exc()
 
            if self.newlyFailing:
 
                traceback.print_exc()
 
                self.newlyFailing = False
 
            else:
 
                log.error(ex)
 
            self.up_metric.set(0)
 
            result = None
 
            self.succeeding = False
 

	
 
            await asyncio.sleep(self.extra_sleep_on_error)
 
            # todo: something that reveals error ratio
 
        return result
 

	
 
    def _updateFps(self, now: float):
 
        if self.fps is None:
 
            return
 
        if now < self.lastFpsLog + 5:
 
            return
 
        d = self.fps()
 
        y_hi = 1 / self.sleep_period
 
        if not d:
 
            return
 
        pts = [int(min(4, y / y_hi * 4)) for y in d['recents']]
 
        log.info(f'{self.metric_prefix} fps={d["average"]:3.1f} (req={y_hi:3.1f}) {horizontal_graph(pts)}')
 
        self.lastFpsLog = now
 

	
 

	
 
def loop_forever(
 
    func: UserFunc,
 
    sleep_period: float,
 
    metric_prefix='background_loop',
 
    up_metric=None,
 
    call_metric=None,
 
    extra_sleep_on_error=2,
 
    log_fps=False,
 
):
 
    """
 
    sleep_period is the sleep time after however long func takes to run
 
    """
 
    if up_metric is not None or call_metric is not None:
 
        raise NotImplementedError('remove old-style metrics')
 

	
 
    loop = Loop(func, sleep_period, metric_prefix)
 
    loop = Loop(func, sleep_period, metric_prefix, extra_sleep_on_error, log_fps)
 
    _created.append(asyncio.create_task(loop._run()))
 
    return loop
light9/effect/sequencer/service.py
Show inline comments
 
@@ -2,108 +2,88 @@
 
plays back effect notes from the timeline (and an untimed note from the faders)
 
"""
 

	
 
import asyncio
 
import json
 
import logging
 
import time
 

	
 
import aiohttp
 
from louie import dispatcher
 
from rdfdb.syncedgraph.syncedgraph import SyncedGraph
 
from sse_starlette.sse import EventSourceResponse
 
from starlette.applications import Starlette
 
from starlette.routing import Route
 
from starlette_exporter import PrometheusMiddleware, handle_metrics
 

	
 
from light9 import networking
 
from light9.collector.collector_client_asyncio import sendToCollector
 
from light9.effect.sequencer.eval_faders import FaderEval
 
from light9.effect.sequencer.sequencer import Sequencer, StateUpdate
 
from light9.effect.settings import DeviceSettings
 
from light9.metrics import metrics
 
from light9.run_local import log
 

	
 
from lib.background_loop import loop_forever
 

	
 
async def changes():
 
    state = {}
 
    q = asyncio.Queue()
 

	
 
    def onBroadcast(update):
 
        state.update(update)
 
        q.put_nowait(None)
 

	
 
    dispatcher.connect(onBroadcast, StateUpdate)
 

	
 
    lastSend = 0
 
    while True:
 
        await q.get()
 
        now = time.time()
 
        if now > lastSend + .2:
 
            lastSend = now
 
            yield json.dumps(state)
 

	
 

	
 
async def send_page_updates(request):
 
    return EventSourceResponse(changes())
 

	
 

	
 
###################################################################
 

	
 

	
 
async def _send_one(faders: FaderEval):
 
    ds = faders.computeOutput()
 
    await sendToCollector('effectSequencer', session='0', settings=ds)
 

	
 

	
 
async def _forever(faders):
 
    prevFail = True
 
    while True:
 
        try:
 
            await _send_one(faders)
 
            if prevFail:
 
                log.info('connected')
 
            prevFail = False
 
        except (aiohttp.ClientConnectorError, aiohttp.ClientOSError) as e:
 
            log.warn(f'{e!r} - retrying')
 
            prevFail = True
 
            await asyncio.sleep(2)
 
        await asyncio.sleep(0.1)
 

	
 

	
 
def send_updates_forever(faders):
 
    asyncio.create_task(_forever(faders))
 

	
 

	
 
####################################################################
 

	
 

	
 
def main():
 
    session = 'effectSequencer'
 
    graph = SyncedGraph(networking.rdfdb.url, "effectSequencer")
 
    logging.getLogger('autodepgraphapi').setLevel(logging.INFO)
 
    logging.getLogger('syncedgraph').setLevel(logging.INFO)
 
    logging.getLogger('sse_starlette.sse').setLevel(logging.INFO)
 

	
 
    async def send(settings: DeviceSettings):
 
        await sendToCollector('effectSequencer', session, settings)
 
    # seq = Sequencer(graph, send)  # per-song timed notes
 
    faders = FaderEval(graph)  # bin/fade's untimed notes
 

	
 
    # seq = Sequencer(graph, send)  # per-song timed notes
 
    faders = FaderEval(graph, send)  # bin/fade's untimed notes
 
    # asyncio.create_task(faders.startUpdating())
 

	
 
    send_updates_forever(faders)
 
    async def so(first_run):
 
        await _send_one(faders)
 
    faders_loop = loop_forever(so, metric_prefix='faders', sleep_period=.05, log_fps=True)
 

	
 
    app = Starlette(
 
        debug=True,
 
        routes=[
 
            Route('/updates', endpoint=send_page_updates),
 
        ],
 
    )
 

	
 
    app.add_middleware(PrometheusMiddleware)
 
    app.add_route("/metrics", handle_metrics)
 

	
 
    return app
 

	
 

	
 
app = main()
pdm.lock
Show inline comments
 
@@ -72,48 +72,53 @@ version = "23.1.2"
 
requires_python = ">=3.7"
 
summary = "WebSocket client & server library, WAMP real-time framework"
 
dependencies = [
 
    "cryptography>=3.4.6",
 
    "hyperlink>=21.0.0",
 
    "setuptools",
 
    "txaio>=21.2.1",
 
]
 

	
 
[[package]]
 
name = "automat"
 
version = "22.10.0"
 
summary = "Self-service finite-state machines for the programmer on the go."
 
dependencies = [
 
    "attrs>=19.2.0",
 
    "six",
 
]
 

	
 
[[package]]
 
name = "backcall"
 
version = "0.2.0"
 
summary = "Specifications for callback functions passed in to an API"
 

	
 
[[package]]
 
name = "braillegraph"
 
version = "0.6"
 
summary = "A library for creating graphs using Unicode braille characters"
 

	
 
[[package]]
 
name = "certifi"
 
version = "2023.5.7"
 
requires_python = ">=3.6"
 
summary = "Python package for providing Mozilla's CA Bundle."
 

	
 
[[package]]
 
name = "cffi"
 
version = "1.15.1"
 
summary = "Foreign Function Interface for Python calling C code."
 
dependencies = [
 
    "pycparser",
 
]
 

	
 
[[package]]
 
name = "charset-normalizer"
 
version = "3.1.0"
 
requires_python = ">=3.7.0"
 
summary = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
 

	
 
[[package]]
 
name = "click"
 
version = "8.1.3"
 
requires_python = ">=3.7"
 
summary = "Composable command line interface toolkit"
 
@@ -999,49 +1004,49 @@ dependencies = [
 
    "tomli>=2.0.1",
 
]
 

	
 
[[package]]
 
name = "yarl"
 
version = "1.9.2"
 
requires_python = ">=3.7"
 
summary = "Yet another URL library"
 
dependencies = [
 
    "idna>=2.0",
 
    "multidict>=4.0",
 
]
 

	
 
[[package]]
 
name = "zope-interface"
 
version = "6.0"
 
requires_python = ">=3.7"
 
summary = "Interfaces for Python"
 
dependencies = [
 
    "setuptools",
 
]
 

	
 
[metadata]
 
lock_version = "4.1"
 
content_hash = "sha256:f6edf9705a4cc5011c8ebdaf29dc2fafe9d069072a34cd16fdc79d8c3786bb57"
 
content_hash = "sha256:87020d26eb24328ebb0d89abe7e4343533e05a6cdd5286e57d1183d3f957ecdd"
 

	
 
[metadata.files]
 
"aiohttp 3.8.4" = [
 
    {url = "https://files.pythonhosted.org/packages/03/e7/84b65e341b1f45753fea51158d8a9522e57a5ae804acbc6dc34edf07cea0/aiohttp-3.8.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bca5f24726e2919de94f047739d0a4fc01372801a3672708260546aa2601bf57"},
 
    {url = "https://files.pythonhosted.org/packages/04/03/3ce412b191aba5961b4ada3ee7a93498623e218fb4d50ac6d357da61dc26/aiohttp-3.8.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8189c56eb0ddbb95bfadb8f60ea1b22fcfa659396ea36f6adcc521213cd7b44d"},
 
    {url = "https://files.pythonhosted.org/packages/05/ee/77b3dc08f41a1bce842e30134233c58b3bbe8c0fa7be121295aa2fad885d/aiohttp-3.8.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:17b79c2963db82086229012cff93ea55196ed31f6493bb1ccd2c62f1724324e4"},
 
    {url = "https://files.pythonhosted.org/packages/07/3c/04c65b5873524a415509cbcf21787be32c31f4e729840fab9866dd197030/aiohttp-3.8.4-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:7c7837fe8037e96b6dd5cfcf47263c1620a9d332a87ec06a6ca4564e56bd0f36"},
 
    {url = "https://files.pythonhosted.org/packages/08/30/3dafa445e7f6358aa1c5ffde987ca4eba6bd7b9038e07ec01732933312fb/aiohttp-3.8.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a80464982d41b1fbfe3154e440ba4904b71c1a53e9cd584098cd41efdb188ef"},
 
    {url = "https://files.pythonhosted.org/packages/0f/30/f00e6c3dd65087ad402e1d5e94ddd54758803b88fc3902a8ad14ac970efa/aiohttp-3.8.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:91f6d540163f90bbaef9387e65f18f73ffd7c79f5225ac3d3f61df7b0d01ad15"},
 
    {url = "https://files.pythonhosted.org/packages/10/a6/bbd9881658cf821fe36144cce4fd05e1fb84f92c67c6222920317b2a7133/aiohttp-3.8.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7a00a9ed8d6e725b55ef98b1b35c88013245f35f68b1b12c5cd4100dddac333"},
 
    {url = "https://files.pythonhosted.org/packages/12/55/2836961a617ce2eec38554bef8093f7f4359ae1b3f90ae7eeea59e447159/aiohttp-3.8.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:22f6eab15b6db242499a16de87939a342f5a950ad0abaf1532038e2ce7d31567"},
 
    {url = "https://files.pythonhosted.org/packages/13/30/4905769f98953e4c1c02190d348001ee683ebf8af1e3ac5106ce7c952d95/aiohttp-3.8.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea9eb976ffdd79d0e893869cfe179a8f60f152d42cb64622fca418cd9b18dc2a"},
 
    {url = "https://files.pythonhosted.org/packages/19/85/e9ed63f5a31e16c2f323993d4cefbd34538eb436c186ab531a9265a1cc1f/aiohttp-3.8.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d89efa095ca7d442a6d0cbc755f9e08190ba40069b235c9886a8763b03785da"},
 
    {url = "https://files.pythonhosted.org/packages/1a/9e/db7ceb5b9609dc60c8e8d94dd362e6e3d713328813a80a4ae0a98a961db4/aiohttp-3.8.4-cp39-cp39-win32.whl", hash = "sha256:34ce9f93a4a68d1272d26030655dd1b58ff727b3ed2a33d80ec433561b03d67a"},
 
    {url = "https://files.pythonhosted.org/packages/21/1e/32b08ea6c14d2b18db7c2d859ca658a7c64f0240c008beb4a9aac3c0c477/aiohttp-3.8.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:8c29c77cc57e40f84acef9bfb904373a4e89a4e8b74e71aa8075c021ec9078c2"},
 
    {url = "https://files.pythonhosted.org/packages/24/9e/9efb855fd4fe4332ac962d447fb87116550c7e317aee490bda1f7857b07b/aiohttp-3.8.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d265f09a75a79a788237d7f9054f929ced2e69eb0bb79de3798c468d8a90f945"},
 
    {url = "https://files.pythonhosted.org/packages/25/b4/4373590c8bd438ccca4c916bb6a65dc3366e4f030a17359f4adb0ff605ce/aiohttp-3.8.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:147ae376f14b55f4f3c2b118b95be50a369b89b38a971e80a17c3fd623f280c9"},
 
    {url = "https://files.pythonhosted.org/packages/26/3d/78f2ae20db769b7f91b823583a349b5756325a96241b5bb5ab14ad0a1986/aiohttp-3.8.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e75b89ac3bd27d2d043b234aa7b734c38ba1b0e43f07787130a0ecac1e12228a"},
 
    {url = "https://files.pythonhosted.org/packages/26/a8/8a7e53fcf159eb838d96810de2b4d05e27b38bf9804275b13ddc952a32f9/aiohttp-3.8.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:aede4df4eeb926c8fa70de46c340a1bc2c6079e1c40ccf7b0eae1313ffd33519"},
 
    {url = "https://files.pythonhosted.org/packages/29/6e/a15e39b2ae4305336bfc00540bde991fff73c60e7e8390b9e82a6ec485f2/aiohttp-3.8.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c6cc15d58053c76eacac5fa9152d7d84b8d67b3fde92709195cb984cfb3475ea"},
 
    {url = "https://files.pythonhosted.org/packages/2a/a9/4183ba5557c40aa5e3e94360ae945c159234b968572bcf3f96ef97ac1bc7/aiohttp-3.8.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7235604476a76ef249bd64cb8274ed24ccf6995c4a8b51a237005ee7a57e8643"},
 
    {url = "https://files.pythonhosted.org/packages/33/a7/c2304859f11531f6be5c464e3d8cdd85cbc196f0754e0c450ef76c297c7c/aiohttp-3.8.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4d347a172f866cd1d93126d9b239fcbe682acb39b48ee0873c73c933dd23bd0f"},
 
    {url = "https://files.pythonhosted.org/packages/35/45/2119fb3958a7e5a745e521ea005f8ffd1bbe0ef63e74d4adcee1f3a47cb2/aiohttp-3.8.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:493f5bc2f8307286b7799c6d899d388bbaa7dfa6c4caf4f97ef7521b9cb13719"},
 
    {url = "https://files.pythonhosted.org/packages/37/4c/0110001ca7950a7c4e672b6f3644299d3dabef669689691f5f1d4142e3e8/aiohttp-3.8.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4dac314662f4e2aa5009977b652d9b8db7121b46c38f2073bfeed9f4049732cd"},
 
@@ -1130,48 +1135,51 @@ content_hash = "sha256:f6edf9705a4cc5011
 
]
 
"async-timeout 4.0.2" = [
 
    {url = "https://files.pythonhosted.org/packages/54/6e/9678f7b2993537452710ffb1750c62d2c26df438aa621ad5fa9d1507a43a/async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"},
 
    {url = "https://files.pythonhosted.org/packages/d6/c1/8991e7c5385b897b8c020cdaad718c5b087a6626d1d11a23e1ea87e325a7/async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"},
 
]
 
"asyncinotify 4.0.2" = [
 
    {url = "https://files.pythonhosted.org/packages/5f/0a/7e83ce699393564b198541dbbd86d52f6d78410292e5d1cad84a5cf5ff5e/asyncinotify-4.0.2.tar.gz", hash = "sha256:bcac19425b1b418bbbc4d31193ea3c39e24343cd7ddff2074ae7b599f1a04829"},
 
    {url = "https://files.pythonhosted.org/packages/93/f5/a072b8b64c750b9c9aa8a4007c49499739a8f86273f87cacd45425d17b71/asyncinotify-4.0.2-py3-none-any.whl", hash = "sha256:1a7cbbb1a87cfb24e6950aa8f8e5ed867f94358ef324f7ebb6129b5cbcedf81d"},
 
]
 
"attrs 23.1.0" = [
 
    {url = "https://files.pythonhosted.org/packages/97/90/81f95d5f705be17872843536b1868f351805acf6971251ff07c1b8334dbb/attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"},
 
    {url = "https://files.pythonhosted.org/packages/f0/eb/fcb708c7bf5056045e9e98f62b93bd7467eb718b0202e7698eb11d66416c/attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"},
 
]
 
"autobahn 23.1.2" = [
 
    {url = "https://files.pythonhosted.org/packages/53/99/b6e0ffa0e8bafe9dfae1c9ab46d44d07317cbf297fbf8f07aff8a80e5bd8/autobahn-23.1.2.tar.gz", hash = "sha256:c5ef8ca7422015a1af774a883b8aef73d4954c9fcd182c9b5244e08e973f7c3a"},
 
]
 
"automat 22.10.0" = [
 
    {url = "https://files.pythonhosted.org/packages/29/90/64aabce6c1b820395452cc5472b8f11cd98320f40941795b8069aef4e0e0/Automat-22.10.0-py2.py3-none-any.whl", hash = "sha256:c3164f8742b9dc440f3682482d32aaff7bb53f71740dd018533f9de286b64180"},
 
    {url = "https://files.pythonhosted.org/packages/7a/7b/9c3d26d8a0416eefbc0428f168241b32657ca260fb7ef507596ff5c2f6c4/Automat-22.10.0.tar.gz", hash = "sha256:e56beb84edad19dcc11d30e8d9b895f75deeb5ef5e96b84a467066b3b84bb04e"},
 
]
 
"backcall 0.2.0" = [
 
    {url = "https://files.pythonhosted.org/packages/4c/1c/ff6546b6c12603d8dd1070aa3c3d273ad4c07f5771689a7b69a550e8c951/backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"},
 
    {url = "https://files.pythonhosted.org/packages/a2/40/764a663805d84deee23043e1426a9175567db89c8b3287b5c2ad9f71aa93/backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"},
 
]
 
"braillegraph 0.6" = [
 
    {url = "https://files.pythonhosted.org/packages/74/cf/f70fb67f740a12754c93039bf7977a2ca79737c8a5900c900c77a7afc13e/braillegraph-0.6.tar.gz", hash = "sha256:dd5656c371c4a60734013222b9ff9dbf4c27105090e66e5ef64cde1f6708d636"},
 
]
 
"certifi 2023.5.7" = [
 
    {url = "https://files.pythonhosted.org/packages/93/71/752f7a4dd4c20d6b12341ed1732368546bc0ca9866139fe812f6009d9ac7/certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"},
 
    {url = "https://files.pythonhosted.org/packages/9d/19/59961b522e6757f0c9097e4493fa906031b95b3ebe9360b2c3083561a6b4/certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"},
 
]
 
"cffi 1.15.1" = [
 
    {url = "https://files.pythonhosted.org/packages/00/05/23a265a3db411b0bfb721bf7a116c7cecaf3eb37ebd48a6ea4dfb0a3244d/cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"},
 
    {url = "https://files.pythonhosted.org/packages/03/7b/259d6e01a6083acef9d3c8c88990c97d313632bb28fa84d6ab2bb201140a/cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"},
 
    {url = "https://files.pythonhosted.org/packages/0e/65/0d7b5dad821ced4dcd43f96a362905a68ce71e6b5f5cfd2fada867840582/cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"},
 
    {url = "https://files.pythonhosted.org/packages/0e/e2/a23af3d81838c577571da4ff01b799b0c2bbde24bd924d97e228febae810/cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"},
 
    {url = "https://files.pythonhosted.org/packages/10/72/617ee266192223a38b67149c830bd9376b69cf3551e1477abc72ff23ef8e/cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"},
 
    {url = "https://files.pythonhosted.org/packages/18/8f/5ff70c7458d61fa8a9752e5ee9c9984c601b0060aae0c619316a1e1f1ee5/cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"},
 
    {url = "https://files.pythonhosted.org/packages/1d/76/bcebbbab689f5f6fc8a91e361038a3001ee2e48c5f9dbad0a3b64a64cc9e/cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"},
 
    {url = "https://files.pythonhosted.org/packages/22/c6/df826563f55f7e9dd9a1d3617866282afa969fe0d57decffa1911f416ed8/cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"},
 
    {url = "https://files.pythonhosted.org/packages/23/8b/2e8c2469eaf89f7273ac685164949a7e644cdfe5daf1c036564208c3d26b/cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"},
 
    {url = "https://files.pythonhosted.org/packages/2b/a8/050ab4f0c3d4c1b8aaa805f70e26e84d0e27004907c5b8ecc1d31815f92a/cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"},
 
    {url = "https://files.pythonhosted.org/packages/2d/86/3ca57cddfa0419f6a95d1c8478f8f622ba597e3581fd501bbb915b20eb75/cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"},
 
    {url = "https://files.pythonhosted.org/packages/2e/7a/68c35c151e5b7a12650ecc12fdfb85211aa1da43e9924598451c4a0a3839/cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"},
 
    {url = "https://files.pythonhosted.org/packages/32/2a/63cb8c07d151de92ff9d897b2eb27ba6a0e78dda8e4c5f70d7b8c16cd6a2/cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"},
 
    {url = "https://files.pythonhosted.org/packages/32/bd/d0809593f7976828f06a492716fbcbbfb62798bbf60ea1f65200b8d49901/cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"},
 
    {url = "https://files.pythonhosted.org/packages/37/5a/c37631a86be838bdd84cc0259130942bf7e6e32f70f4cab95f479847fb91/cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"},
 
    {url = "https://files.pythonhosted.org/packages/3a/12/d6066828014b9ccb2bbb8e1d9dc28872d20669b65aeb4a86806a0757813f/cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"},
 
    {url = "https://files.pythonhosted.org/packages/3a/75/a162315adeaf47e94a3b7f886a8e31d77b9e525a387eef2d6f0efc96a7c8/cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"},
 
    {url = "https://files.pythonhosted.org/packages/3f/fa/dfc242febbff049509e5a35a065bdc10f90d8c8585361c2c66b9c2f97a01/cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"},
 
    {url = "https://files.pythonhosted.org/packages/43/a0/cc7370ef72b6ee586369bacd3961089ab3d94ae712febf07a244f1448ffd/cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"},
pyproject.toml
Show inline comments
 
@@ -16,48 +16,49 @@ dependencies = [
 
    "colormath>=3.0.0",
 
    "flask==2.2.4", # workaround for pydmxcontrol
 
    "ipython>=8.13.2",
 
    "louie>=2.0",
 
    "moviepy>=1.0.3",
 
    "noise>=1.2.2",
 
    "prometheus-client>=0.14.1",
 
    "pydmxcontrol>=2.0.0",
 
    "PyGObject>=3.42.1",
 
    "python-dateutil>=2.8.2",
 
    "rdflib>=6.3.2",
 
    "requests>=2.30.0",
 
    "rx>=3.2.0",
 
    "sse-starlette>=0.10.3",
 
    "starlette-exporter>=0.12.0",
 
    "starlette>=0.27.0",
 
    "statprof>=0.1.2",
 
    "toposort>=1.10",
 
    "udmx-pyusb>=2.0.0",
 
    "uvicorn[standard]>=0.17.6",
 
    "watchdog>=2.1.7",
 
    "webcolors>=1.11.1",
 
    "rdfdb @ https://projects.bigasterisk.com/rdfdb/rdfdb-0.24.0.tar.gz",
 
    "scipy>=1.9.3",
 
    "braillegraph>=0.6",
 
]
 
requires-python = ">=3.10"
 

	
 
[project.urls]
 
Homepage = ""
 

	
 
[project.optional-dependencies]
 
[tool.pdm]
 

	
 
[tool.pdm.dev-dependencies]
 
dev = [
 
    "coverage>=7.2.5",
 
    "flake8>=6.0.0",
 
    "freezegun>=1.2.2",
 
    "hunter>=3.6.1",
 
    "ipdb>=0.13.13",
 
    "mock>=5.0.2",
 
    "yapf>=0.33.0",
 
    "pydeps>=1.12.5",
 
    "nose2>=0.13.0",
 
    "pytest-watch>=4.2.0",
 
]
 

	
 
[[tool.pdm.source]]
0 comments (0 inline, 0 general)