From e951aacf353b4166811680ae2dca4551e5c22818 Mon Sep 17 00:00:00 2001 From: Stefan Bethke Date: Sat, 31 May 2025 22:47:03 +0200 Subject: [PATCH] First draft of SVG font for emulator --- buba/AppConfig.py | 24 ++ buba/__main__.py | 146 ++----- buba/create_font.py | 99 +++++ .../geascript-proportional-labelled.png | Bin 0 -> 14106 bytes .../geascript-proportional-labelled.svg | 395 ++++++++++++++++++ buba/design/geascript-proportional.svg | 146 +++++++ buba/experiments.py | 124 ++++++ buba/static/font.svg | 127 ++++++ buba/static/fonttest.html | 21 + buba/static/main.css | 7 + poetry.lock | 367 +++++++++++++++- pyproject.toml | 4 +- 12 files changed, 1342 insertions(+), 118 deletions(-) create mode 100644 buba/AppConfig.py create mode 100644 buba/create_font.py create mode 100644 buba/design/geascript-proportional-labelled.png create mode 100644 buba/design/geascript-proportional-labelled.svg create mode 100644 buba/design/geascript-proportional.svg create mode 100644 buba/experiments.py create mode 100644 buba/static/font.svg create mode 100644 buba/static/fonttest.html create mode 100644 buba/static/main.css diff --git a/buba/AppConfig.py b/buba/AppConfig.py new file mode 100644 index 0000000..5f20386 --- /dev/null +++ b/buba/AppConfig.py @@ -0,0 +1,24 @@ +import json +import logging +from os import getenv, path +from pathlib import Path + + +class AppConfig: + def __init__(self): + """ + Gets the config from environment variables + """ + self.log = logging.getLogger(__name__) + + self.basepath = path.dirname(__file__) + self.debug = getenv("DEBUG", None) + self.staticpath = path.join(self.basepath, "static") + self.templatepath = path.join(self.basepath, "templates") + self.url = getenv('BUBA_URL', 'http://localhost:3000') + (self.listen_host, self.listen_port) = getenv('BUBA_LISTEN', '127.0.0.1:3000').split(':') + + if self.debug is not None and self.debug.lower not in ('0', 'f', 'false'): + self.debug = True + else: + self.debug = False diff --git a/buba/__main__.py b/buba/__main__.py index bb19e34..ee2d1d5 100644 --- a/buba/__main__.py +++ b/buba/__main__.py @@ -1,125 +1,41 @@ +import logging -from time import sleep +from buba.AppConfig import AppConfig -from pyfis.aegmis import MIS1TextDisplay -from pyfis.utils import debug_hex +config = AppConfig() +if config.debug: + logging.basicConfig(level=logging.DEBUG) -def receive(display): - if display.use_rts: - display.port.setRTS(1) - display.port.write([0x04, 0x81, 0x05]) - if display.use_rts: - display.port.setRTS(0) - return display.read_response(True) +app = Bottle() +if config.debug: + app.config.update({"logging.level": "DEBUG"}) +app.install(LoggingPlugin(app.config)) +TEMPLATE_PATH.insert(0, config.templatepath) +app.install(BottleSessions()) +auth = BottleOIDC(app, config={ + "discovery_url": config.discovery_url, + "client_id": config.client_id, + "client_secret": config.client_secret, + "client_scope": config.oidc_scope, + "user_attr": config.oidc_user_attr, +}) -def decode(display): - r = receive(display) - if r[0] != 0x02: - print("Invalid byte {r[0]:02x}, expected EOT") - d = { - 'code': r[1], - 'subcode': r[2], - } - m = [] - esc = False - for i in range(3, len(r)-1): - if esc: - m.append(r[i]) - erc = False - elif r[i] == 0x10: - esc = True - else: - if r[i] == 0x03: - # compute checksum - next - m.append(r[i]) - m = m[0:-1] - print(f"m({len(m)}): {debug_hex(m)}") - return m +websocket_clients = WebSocketClients() +bottle_helpers = BottleHelpers(auth, group=config.requires_group, allowed=config.allowed) +update_poller = UpdatePoller(websocket_clients, ccujack, 1 if config.debug else 0.1) -def reset_and_get_config(display): - display.send_command(0x31, 0x00, []) - r = decode(display) - return { - 'type': r[0], - 'control-units': r[1], - 'digits-a': r[2] * 256 + r[3], - 'lines-a': r[4], - 'digits-b': r[5] * 256 + r[6], - 'lines-b': r[7], - 'timeout': r[8] * 128.0 + r[9] * 0.5, - } - -def get_config(display): - display.send_command(0x38, 0x00, []) - r = decode(display) - return r - -def text_raw(display, page, row, text): - data = [display.ALIGN_LEFT, page, row, 0] + text - return display.send_command(0x11, 0x00, data, expect_response=False) +@app.route("/static/") +def server_static(filepath): + return static_file(filepath, root=config.staticpath) -def charset(): - lines = [] - for i in range(0, 256, 16): - label = f"{i:03d}: " - b = list(label.encode("CP437")) - for j in range(i, i+16): - if j == 0: - b.append(0x20) - else: - b.append(j) - lines.append(b) +@app.get("/") +@jinja2_view("home.html.j2") +def root(): + return {} - display.set_page(1) - - while True: - for p in range(1,16): - for l in range(0,4): - text_raw(display, 1, l, lines[(p+l-1) % len(lines)]) - sleep(5) - -print("Opening serial port") -display = MIS1TextDisplay("/dev/ttyUSB0") - -print("Sending text") -display.debug = True -#r = display.send_command(0x31, 0x00, []) # reset -#r = display.send_command(0x32, 0x01, []) # test -#r = display.send_command(0x38, 0x01, []) # read config -#r = display.read_response() -#print(f"Response: {r}") -#r = display.send_command(0x52, 0x01, []) # read config -#display.send_raw_telegram([0xff]) -#display.send_raw_data([0x04, 0x81, 0x02, 0x03, 0x00]) -#print(f"response: {display.read_response()}") -#r = reset_and_get_config(display) -r = get_config(display) -print(f"reply {len(r)}: {r}") - -#display.simple_text(page=1, row=0, col=0, text="MEOW MEOW MEOW MEOW MEOW MEOW MEOW MEOW", align=display.ALIGN_CENTER) -#display.simple_text(page=1, row=1, col=0, text="MEOW MEOW MEOW MEOW MEOW MEOW MEOW MEOW", align=display.ALIGN_CENTER) -#display.simple_text(page=1, row=2, col=0, text="MEOW MEOW MEOW MEOW MEOW MEOW MEOW MEOW", align=display.ALIGN_CENTER) -#display.simple_text(page=1, row=3, col=0, text="MEOW MEOW MEOW MEOW MEOW MEOW MEOW MEOW", align=display.ALIGN_CENTER) - -#display.simple_text(page=0, row=3, col=0, text="MEOW MEOW MEOW MEOW MEOW MEOW", align=display.ALIGN_CENTER) - -#print("page 1") -#display.set_page(1) -#sleep(10) - -#for l in range(0,4): -# display.simple_text(page=0, row=l, col=0, text="") -#print("page 0") -#display.set_page(0) -#sleep(10) - -#print("page 1") -#display.set_page(1) -#sleep(10) - -charset() -print("Thats all, folks!") +if __name__ == '__main__': + app.run(host=config.listen_host, port=config.listen_port, server=GeventWebSocketServer, debug=config.debug, + quiet=not config.debug) diff --git a/buba/create_font.py b/buba/create_font.py new file mode 100644 index 0000000..166efd4 --- /dev/null +++ b/buba/create_font.py @@ -0,0 +1,99 @@ +""" +Create an SVG "font" by using the base segments from geascript-proportional.svg and creating a SVG with 256 symbols in it. +""" +import os +from xml.dom import minidom, Node +from xml.dom.minidom import getDOMImplementation, Element + +fontspec = { + '41': [ + '.##### ###### ###### ######', + '###... ...### #..... ......', + '#.#### ###### ###### ######', + ], + '42': [ + '###### ###### ###### ######', + '###... ...### #..... ...###', + '#.#### #####. ###### #####.', + ], +} + + +class Design: + def __init__(self, filename): + self.dom = minidom.parse(filename) + (_, _, self.width, self.height) = [float(x) for x in + self.dom.documentElement.getAttribute("viewBox").split(' ')] + + r = {} + for n in self.dom.getElementsByTagName("g")[0].childNodes: + if n.nodeType == Node.ELEMENT_NODE: + self.remove_namespaced_attributes(n) + self.remove_style_attributes(n) + id = n.getAttribute("id") + r[id] = n + self.segments = r + + self.symbols = getDOMImplementation().createDocument(None, "svg", None) + self.svg = self.symbols.documentElement + self.svg.setAttribute("xmlns", "http://www.w3.org/2000/svg") + self.svg.setAttribute("style", "height: 0; width: 0; position: absolute;") + + def remove_namespaced_attributes(self, node:Element): + if node.nodeType != Node.ELEMENT_NODE: + return + for a in list(node._attrsNS.values()): + if a.prefix is not None: + node.removeAttributeNode(a) + for child in node.childNodes: + self.remove_namespaced_attributes(child) + + def remove_style_attributes(self, node:Element): + if node.nodeType != Node.ELEMENT_NODE: + return + if node.hasAttribute("style"): + node.removeAttribute("style") + for child in node.childNodes: + self.remove_namespaced_attributes(child) + + def make_symbol(self, codepoint): + """ + Use the spec to copy all segments into a new symbol. + :param codepoint: + :return: + """ + spec = fontspec[f"{codepoint:02x}"] + symbol = self.symbols.createElement("symbol") + self.svg.appendChild(symbol) + symbol.setAttribute("id", f"x{codepoint:02x}") + symbol.setAttribute("viewBox", f"0 0 {self.width * len(spec):.3f} {self.height}") + for strip_index, strip in enumerate(spec): + i = 1 + g = self.symbols.createElement("g") + symbol.appendChild(g) + g.setAttribute("id", f"x{codepoint:02x}-{strip_index}") + g.setAttribute("transform", f"translate({self.width * strip_index:.3f} 0)") + for segment in list(strip): + if segment == '#': + clone = self.segments[f"seg{i:02d}"].cloneNode(True) + clone.removeAttribute("id") + g.appendChild(clone) + # clone + pass + elif segment == '.': + # no not clone this one + pass + else: + continue + i += 1 + + +if __name__ == '__main__': + here = os.path.abspath(os.path.dirname(__file__)) + design = Design(os.path.join(here, 'design/geascript-proportional.svg')) + + design.make_symbol(0x41) + design.make_symbol(0x42) + + with open("static/font.svg", "w") as symbolsfile: + design.symbols.writexml(symbolsfile, addindent=' ', newl='\n') diff --git a/buba/design/geascript-proportional-labelled.png b/buba/design/geascript-proportional-labelled.png new file mode 100644 index 0000000000000000000000000000000000000000..52d2e27e749ebe2394bc004f6afaa6d75e2b1d31 GIT binary patch literal 14106 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 literal 0 HcmV?d00001 diff --git a/buba/design/geascript-proportional-labelled.svg b/buba/design/geascript-proportional-labelled.svg new file mode 100644 index 0000000..d0379fe --- /dev/null +++ b/buba/design/geascript-proportional-labelled.svg @@ -0,0 +1,395 @@ + + + + + + + + + 3 + + + + + + + + + + + + + + + + + + + + + 12456789101112131415161718192021222324 + diff --git a/buba/design/geascript-proportional.svg b/buba/design/geascript-proportional.svg new file mode 100644 index 0000000..a8e578d --- /dev/null +++ b/buba/design/geascript-proportional.svg @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/buba/experiments.py b/buba/experiments.py new file mode 100644 index 0000000..690878b --- /dev/null +++ b/buba/experiments.py @@ -0,0 +1,124 @@ +from time import sleep + +from pyfis.aegmis import MIS1TextDisplay +from pyfis.utils import debug_hex + + +def receive(display): + if display.use_rts: + display.port.setRTS(1) + display.port.write([0x04, 0x81, 0x05]) + if display.use_rts: + display.port.setRTS(0) + return display.read_response(True) + +def decode(display): + r = receive(display) + if r[0] != 0x02: + print("Invalid byte {r[0]:02x}, expected EOT") + d = { + 'code': r[1], + 'subcode': r[2], + } + m = [] + esc = False + for i in range(3, len(r)-1): + if esc: + m.append(r[i]) + erc = False + elif r[i] == 0x10: + esc = True + else: + if r[i] == 0x03: + # compute checksum + next + m.append(r[i]) + m = m[0:-1] + print(f"m({len(m)}): {debug_hex(m)}") + return m + + +def reset_and_get_config(display): + display.send_command(0x31, 0x00, []) + r = decode(display) + return { + 'type': r[0], + 'control-units': r[1], + 'digits-a': r[2] * 256 + r[3], + 'lines-a': r[4], + 'digits-b': r[5] * 256 + r[6], + 'lines-b': r[7], + 'timeout': r[8] * 128.0 + r[9] * 0.5, + } + +def get_config(display): + display.send_command(0x38, 0x00, []) + r = decode(display) + return r + +def text_raw(display, page, row, text): + data = [display.ALIGN_LEFT, page, row, 0] + text + return display.send_command(0x11, 0x00, data, expect_response=False) + + +def charset(): + lines = [] + for i in range(0, 256, 16): + label = f"{i:03d}: " + b = list(label.encode("CP437")) + for j in range(i, i+16): + if j == 0: + b.append(0x20) + else: + b.append(j) + lines.append(b) + + display.set_page(1) + + while True: + for p in range(1,16): + for l in range(0,4): + text_raw(display, 1, l, lines[(p+l-1) % len(lines)]) + sleep(5) + +print("Opening serial port") +display = MIS1TextDisplay("/dev/ttyUSB0") + +print("Sending text") +display.debug = True +#r = display.send_command(0x31, 0x00, []) # reset +#r = display.send_command(0x32, 0x01, []) # test +#r = display.send_command(0x38, 0x01, []) # read config +#r = display.read_response() +#print(f"Response: {r}") +#r = display.send_command(0x52, 0x01, []) # read config +#display.send_raw_telegram([0xff]) +#display.send_raw_data([0x04, 0x81, 0x02, 0x03, 0x00]) +#print(f"response: {display.read_response()}") +#r = reset_and_get_config(display) +r = get_config(display) +print(f"reply {len(r)}: {r}") + +#display.simple_text(page=1, row=0, col=0, text="MEOW MEOW MEOW MEOW MEOW MEOW MEOW MEOW", align=display.ALIGN_CENTER) +#display.simple_text(page=1, row=1, col=0, text="MEOW MEOW MEOW MEOW MEOW MEOW MEOW MEOW", align=display.ALIGN_CENTER) +#display.simple_text(page=1, row=2, col=0, text="MEOW MEOW MEOW MEOW MEOW MEOW MEOW MEOW", align=display.ALIGN_CENTER) +#display.simple_text(page=1, row=3, col=0, text="MEOW MEOW MEOW MEOW MEOW MEOW MEOW MEOW", align=display.ALIGN_CENTER) + +#display.simple_text(page=0, row=3, col=0, text="MEOW MEOW MEOW MEOW MEOW MEOW", align=display.ALIGN_CENTER) + +#print("page 1") +#display.set_page(1) +#sleep(10) + +#for l in range(0,4): +# display.simple_text(page=0, row=l, col=0, text="") +#print("page 0") +#display.set_page(0) +#sleep(10) + +#print("page 1") +#display.set_page(1) +#sleep(10) + +charset() +print("Thats all, folks!") diff --git a/buba/static/font.svg b/buba/static/font.svg new file mode 100644 index 0000000..45a6cf7 --- /dev/null +++ b/buba/static/font.svg @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/buba/static/fonttest.html b/buba/static/fonttest.html new file mode 100644 index 0000000..c699fbd --- /dev/null +++ b/buba/static/fonttest.html @@ -0,0 +1,21 @@ + + + + Font Test + + + +

Font Test

+

+ + + + + + + + + +

+ + \ No newline at end of file diff --git a/buba/static/main.css b/buba/static/main.css new file mode 100644 index 0000000..0b20ee9 --- /dev/null +++ b/buba/static/main.css @@ -0,0 +1,7 @@ +svg.char { + width: 2em; + /*height: 10em;*/ + stroke: #ccc; + stroke-width: .1; + fill: black; +} \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index fd8e5c3..4f9051d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,259 @@ -# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. + +[[package]] +name = "bottle" +version = "0.13.3" +description = "Fast and simple WSGI-framework for small web-applications." +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "bottle-0.13.3-py2.py3-none-any.whl", hash = "sha256:551709073d750d2e88ade05915f0d5dbbbcb8e1d82573fbe85a0635eb842ab07"}, + {file = "bottle-0.13.3.tar.gz", hash = "sha256:1c23aeb30aa8a13f39c60c0da494530ddd5de3da235bc431b818a50d999de49f"}, +] + +[[package]] +name = "bottle-websocket" +version = "0.2.9" +description = "WebSockets for bottle" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "bottle-websocket-0.2.9.tar.gz", hash = "sha256:9887f70dc0c7592ed8d0d11a14aa95dede6cd08d50d83d5b81fd963e5fec738b"}, +] + +[package.dependencies] +bottle = "*" +gevent-websocket = "*" + +[[package]] +name = "cffi" +version = "1.17.1" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\"" +files = [ + {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, + {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, + {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, + {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, + {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, + {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, + {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, + {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, + {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, + {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, + {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, + {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, + {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, + {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, + {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "gevent" +version = "25.5.1" +description = "Coroutine-based network library" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "gevent-25.5.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8e5a0fab5e245b15ec1005b3666b0a2e867c26f411c8fe66ae1afe07174a30e9"}, + {file = "gevent-25.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7b80a37f2fb45ee4a8f7e64b77dd8a842d364384046e394227b974a4e9c9a52"}, + {file = "gevent-25.5.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29ab729d50ae85077a68e0385f129f5b01052d01a0ae6d7fdc1824f5337905e4"}, + {file = "gevent-25.5.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80d20592aeabcc4e294fd441fd43d45cb537437fd642c374ea9d964622fad229"}, + {file = "gevent-25.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8ba0257542ccbb72a8229dc34d00844ccdfba110417e4b7b34599548d0e20e9"}, + {file = "gevent-25.5.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cad0821dff998c7c60dd238f92cd61380342c47fb9e92e1a8705d9b5ac7c16e8"}, + {file = "gevent-25.5.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:017a7384c0cd1a5907751c991535a0699596e89725468a7fc39228312e10efa1"}, + {file = "gevent-25.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:469c86d02fccad7e2a3d82fe22237e47ecb376fbf4710bc18747b49c50716817"}, + {file = "gevent-25.5.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:12380aba5c316e9ff53cc21d8ab80f4a91c0df3ada58f65d4f5eb2cf693db00e"}, + {file = "gevent-25.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f0694daab1a041b69a53f53c2141c12994892b2503870515cabe6a5dbd2a928"}, + {file = "gevent-25.5.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2797885e9aeffdc98e1846723e5aa212e7ce53007dbef40d6fd2add264235c41"}, + {file = "gevent-25.5.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cde6aaac36b54332e10ea2a5bc0de6a8aba6c205c92603fe4396e3777c88e05d"}, + {file = "gevent-25.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24484f80f14befb8822bf29554cfb3a26a26cb69cd1e5a8be9e23b4bd7a96e25"}, + {file = "gevent-25.5.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc7446895fa184890d8ca5ea61e502691114f9db55c9b76adc33f3086c4368"}, + {file = "gevent-25.5.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5b6106e2414b1797133786258fa1962a5e836480e4d5e861577f9fc63b673a5a"}, + {file = "gevent-25.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:bc899212d90f311784c58938a9c09c59802fb6dc287a35fabdc36d180f57f575"}, + {file = "gevent-25.5.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:d87c0a1bd809d8f70f96b9b229779ec6647339830b8888a192beed33ac8d129f"}, + {file = "gevent-25.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b87a4b66edb3808d4d07bbdb0deed5a710cf3d3c531e082759afd283758bb649"}, + {file = "gevent-25.5.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f076779050029a82feb0cb1462021d3404d22f80fa76a181b1a7889cd4d6b519"}, + {file = "gevent-25.5.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bb673eb291c19370f69295f7a881a536451408481e2e3deec3f41dedb7c281ec"}, + {file = "gevent-25.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1325ed44225c8309c0dd188bdbbbee79e1df8c11ceccac226b861c7d52e4837"}, + {file = "gevent-25.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:fcd5bcad3102bde686d0adcc341fade6245186050ce14386d547ccab4bd54310"}, + {file = "gevent-25.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1a93062609e8fa67ec97cd5fb9206886774b2a09b24887f40148c9c37e6fb71c"}, + {file = "gevent-25.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:2534c23dc32bed62b659ed4fd9e198906179e68b26c9276a897e04163bdde806"}, + {file = "gevent-25.5.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:a022a9de9275ce0b390b7315595454258c525dc8287a03f1a6cacc5878ab7cbc"}, + {file = "gevent-25.5.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fae8533f9d0ef3348a1f503edcfb531ef7a0236b57da1e24339aceb0ce52922"}, + {file = "gevent-25.5.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c7b32d9c3b5294b39ea9060e20c582e49e1ec81edbfeae6cf05f8ad0829cb13d"}, + {file = "gevent-25.5.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b95815fe44f318ebbfd733b6428b4cb18cc5e68f1c40e8501dd69cc1f42a83d"}, + {file = "gevent-25.5.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d316529b70d325b183b2f3f5cde958911ff7be12eb2b532b5c301f915dbbf1e"}, + {file = "gevent-25.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f6ba33c13db91ffdbb489a4f3d177a261ea1843923e1d68a5636c53fe98fa5ce"}, + {file = "gevent-25.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:37ee34b77c7553777c0b8379915f75934c3f9c8cd32f7cd098ea43c9323c2276"}, + {file = "gevent-25.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:9fa6aa0da224ed807d3b76cdb4ee8b54d4d4d5e018aed2478098e685baae7896"}, + {file = "gevent-25.5.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:0bacf89a65489d26c7087669af89938d5bfd9f7afb12a07b57855b9fad6ccbd0"}, + {file = "gevent-25.5.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e30169ef9cc0a57930bfd8fe14d86bc9d39fb96d278e3891e85cbe7b46058a97"}, + {file = "gevent-25.5.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e72ad5f8d9c92df017fb91a1f6a438cfb63b0eff4b40904ff81b40cb8150078c"}, + {file = "gevent-25.5.1-cp39-cp39-win32.whl", hash = "sha256:e5f358e81e27b1a7f2fb2f5219794e13ab5f59ce05571aa3877cfac63adb97db"}, + {file = "gevent-25.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:b83aff2441c7d4ee93e519989713b7c2607d4510abe990cd1d04f641bc6c03af"}, + {file = "gevent-25.5.1-pp310-pypy310_pp73-macosx_11_0_universal2.whl", hash = "sha256:60ad4ca9ca2c4cc8201b607c229cd17af749831e371d006d8a91303bb5568eb1"}, + {file = "gevent-25.5.1.tar.gz", hash = "sha256:582c948fa9a23188b890d0bc130734a506d039a2e5ad87dae276a456cc683e61"}, +] + +[package.dependencies] +cffi = {version = ">=1.17.1", markers = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\""} +greenlet = {version = ">=3.2.2", markers = "platform_python_implementation == \"CPython\""} +"zope.event" = "*" +"zope.interface" = "*" + +[package.extras] +dnspython = ["dnspython (>=1.16.0,<2.0) ; python_version < \"3.10\"", "idna ; python_version < \"3.10\""] +docs = ["furo", "repoze.sphinx.autointerface", "sphinx", "sphinxcontrib-programoutput", "zope.schema"] +monitor = ["psutil (>=5.7.0) ; sys_platform != \"win32\" or platform_python_implementation == \"CPython\""] +recommended = ["cffi (>=1.17.1) ; platform_python_implementation == \"CPython\"", "dnspython (>=1.16.0,<2.0) ; python_version < \"3.10\"", "idna ; python_version < \"3.10\"", "psutil (>=5.7.0) ; sys_platform != \"win32\" or platform_python_implementation == \"CPython\""] +test = ["cffi (>=1.17.1) ; platform_python_implementation == \"CPython\"", "coverage (>=5.0) ; sys_platform != \"win32\"", "dnspython (>=1.16.0,<2.0) ; python_version < \"3.10\"", "idna ; python_version < \"3.10\"", "objgraph", "psutil (>=5.7.0) ; sys_platform != \"win32\" or platform_python_implementation == \"CPython\"", "requests"] + +[[package]] +name = "gevent-websocket" +version = "0.10.1" +description = "Websocket handler for the gevent pywsgi server, a Python network library" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "gevent-websocket-0.10.1.tar.gz", hash = "sha256:7eaef32968290c9121f7c35b973e2cc302ffb076d018c9068d2f5ca8b2d85fb0"}, + {file = "gevent_websocket-0.10.1-py3-none-any.whl", hash = "sha256:17b67d91282f8f4c973eba0551183fc84f56f1c90c8f6b6b30256f31f66f5242"}, +] + +[package.dependencies] +gevent = "*" + +[[package]] +name = "greenlet" +version = "3.2.2" +description = "Lightweight in-process concurrent programming" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "platform_python_implementation == \"CPython\"" +files = [ + {file = "greenlet-3.2.2-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:c49e9f7c6f625507ed83a7485366b46cbe325717c60837f7244fc99ba16ba9d6"}, + {file = "greenlet-3.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3cc1a3ed00ecfea8932477f729a9f616ad7347a5e55d50929efa50a86cb7be7"}, + {file = "greenlet-3.2.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c9896249fbef2c615853b890ee854f22c671560226c9221cfd27c995db97e5c"}, + {file = "greenlet-3.2.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7409796591d879425997a518138889d8d17e63ada7c99edc0d7a1c22007d4907"}, + {file = "greenlet-3.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7791dcb496ec53d60c7f1c78eaa156c21f402dda38542a00afc3e20cae0f480f"}, + {file = "greenlet-3.2.2-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d8009ae46259e31bc73dc183e402f548e980c96f33a6ef58cc2e7865db012e13"}, + {file = "greenlet-3.2.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fd9fb7c941280e2c837b603850efc93c999ae58aae2b40765ed682a6907ebbc5"}, + {file = "greenlet-3.2.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:00cd814b8959b95a546e47e8d589610534cfb71f19802ea8a2ad99d95d702057"}, + {file = "greenlet-3.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:d0cb7d47199001de7658c213419358aa8937df767936506db0db7ce1a71f4a2f"}, + {file = "greenlet-3.2.2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:dcb9cebbf3f62cb1e5afacae90761ccce0effb3adaa32339a0670fe7805d8068"}, + {file = "greenlet-3.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf3fc9145141250907730886b031681dfcc0de1c158f3cc51c092223c0f381ce"}, + {file = "greenlet-3.2.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:efcdfb9df109e8a3b475c016f60438fcd4be68cd13a365d42b35914cdab4bb2b"}, + {file = "greenlet-3.2.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4bd139e4943547ce3a56ef4b8b1b9479f9e40bb47e72cc906f0f66b9d0d5cab3"}, + {file = "greenlet-3.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71566302219b17ca354eb274dfd29b8da3c268e41b646f330e324e3967546a74"}, + {file = "greenlet-3.2.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3091bc45e6b0c73f225374fefa1536cd91b1e987377b12ef5b19129b07d93ebe"}, + {file = "greenlet-3.2.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:44671c29da26539a5f142257eaba5110f71887c24d40df3ac87f1117df589e0e"}, + {file = "greenlet-3.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c23ea227847c9dbe0b3910f5c0dd95658b607137614eb821e6cbaecd60d81cc6"}, + {file = "greenlet-3.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:0a16fb934fcabfdfacf21d79e6fed81809d8cd97bc1be9d9c89f0e4567143d7b"}, + {file = "greenlet-3.2.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:df4d1509efd4977e6a844ac96d8be0b9e5aa5d5c77aa27ca9f4d3f92d3fcf330"}, + {file = "greenlet-3.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da956d534a6d1b9841f95ad0f18ace637668f680b1339ca4dcfb2c1837880a0b"}, + {file = "greenlet-3.2.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c7b15fb9b88d9ee07e076f5a683027bc3befd5bb5d25954bb633c385d8b737e"}, + {file = "greenlet-3.2.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:752f0e79785e11180ebd2e726c8a88109ded3e2301d40abced2543aa5d164275"}, + {file = "greenlet-3.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ae572c996ae4b5e122331e12bbb971ea49c08cc7c232d1bd43150800a2d6c65"}, + {file = "greenlet-3.2.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02f5972ff02c9cf615357c17ab713737cccfd0eaf69b951084a9fd43f39833d3"}, + {file = "greenlet-3.2.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4fefc7aa68b34b9224490dfda2e70ccf2131368493add64b4ef2d372955c207e"}, + {file = "greenlet-3.2.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a31ead8411a027c2c4759113cf2bd473690517494f3d6e4bf67064589afcd3c5"}, + {file = "greenlet-3.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:b24c7844c0a0afc3ccbeb0b807adeefb7eff2b5599229ecedddcfeb0ef333bec"}, + {file = "greenlet-3.2.2-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:3ab7194ee290302ca15449f601036007873028712e92ca15fc76597a0aeb4c59"}, + {file = "greenlet-3.2.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dc5c43bb65ec3669452af0ab10729e8fdc17f87a1f2ad7ec65d4aaaefabf6bf"}, + {file = "greenlet-3.2.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:decb0658ec19e5c1f519faa9a160c0fc85a41a7e6654b3ce1b44b939f8bf1325"}, + {file = "greenlet-3.2.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6fadd183186db360b61cb34e81117a096bff91c072929cd1b529eb20dd46e6c5"}, + {file = "greenlet-3.2.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1919cbdc1c53ef739c94cf2985056bcc0838c1f217b57647cbf4578576c63825"}, + {file = "greenlet-3.2.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3885f85b61798f4192d544aac7b25a04ece5fe2704670b4ab73c2d2c14ab740d"}, + {file = "greenlet-3.2.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:85f3e248507125bf4af607a26fd6cb8578776197bd4b66e35229cdf5acf1dfbf"}, + {file = "greenlet-3.2.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:1e76106b6fc55fa3d6fe1c527f95ee65e324a13b62e243f77b48317346559708"}, + {file = "greenlet-3.2.2-cp313-cp313-win_amd64.whl", hash = "sha256:fe46d4f8e94e637634d54477b0cfabcf93c53f29eedcbdeecaf2af32029b4421"}, + {file = "greenlet-3.2.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba30e88607fb6990544d84caf3c706c4b48f629e18853fc6a646f82db9629418"}, + {file = "greenlet-3.2.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:055916fafad3e3388d27dd68517478933a97edc2fc54ae79d3bec827de2c64c4"}, + {file = "greenlet-3.2.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2593283bf81ca37d27d110956b79e8723f9aa50c4bcdc29d3c0543d4743d2763"}, + {file = "greenlet-3.2.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89c69e9a10670eb7a66b8cef6354c24671ba241f46152dd3eed447f79c29fb5b"}, + {file = "greenlet-3.2.2-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02a98600899ca1ca5d3a2590974c9e3ec259503b2d6ba6527605fcd74e08e207"}, + {file = "greenlet-3.2.2-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:b50a8c5c162469c3209e5ec92ee4f95c8231b11db6a04db09bbe338176723bb8"}, + {file = "greenlet-3.2.2-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:45f9f4853fb4cc46783085261c9ec4706628f3b57de3e68bae03e8f8b3c0de51"}, + {file = "greenlet-3.2.2-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:9ea5231428af34226c05f927e16fc7f6fa5e39e3ad3cd24ffa48ba53a47f4240"}, + {file = "greenlet-3.2.2-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:1e4747712c4365ef6765708f948acc9c10350719ca0545e362c24ab973017370"}, + {file = "greenlet-3.2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:782743700ab75716650b5238a4759f840bb2dcf7bff56917e9ffdf9f1f23ec59"}, + {file = "greenlet-3.2.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:354f67445f5bed6604e493a06a9a49ad65675d3d03477d38a4db4a427e9aad0e"}, + {file = "greenlet-3.2.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3aeca9848d08ce5eb653cf16e15bb25beeab36e53eb71cc32569f5f3afb2a3aa"}, + {file = "greenlet-3.2.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cb8553ee954536500d88a1a2f58fcb867e45125e600e80f586ade399b3f8819"}, + {file = "greenlet-3.2.2-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1592a615b598643dbfd566bac8467f06c8c8ab6e56f069e573832ed1d5d528cc"}, + {file = "greenlet-3.2.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1f72667cc341c95184f1c68f957cb2d4fc31eef81646e8e59358a10ce6689457"}, + {file = "greenlet-3.2.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a8fa80665b1a29faf76800173ff5325095f3e66a78e62999929809907aca5659"}, + {file = "greenlet-3.2.2-cp39-cp39-win32.whl", hash = "sha256:6629311595e3fe7304039c67f00d145cd1d38cf723bb5b99cc987b23c1433d61"}, + {file = "greenlet-3.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:eeb27bece45c0c2a5842ac4c5a1b5c2ceaefe5711078eed4e8043159fa05c834"}, + {file = "greenlet-3.2.2.tar.gz", hash = "sha256:ad053d34421a2debba45aa3cc39acf454acbcd025b3fc1a9f8a0dee237abd485"}, +] + +[package.extras] +docs = ["Sphinx", "furo"] +test = ["objgraph", "psutil"] [[package]] name = "pillow" @@ -100,6 +355,19 @@ tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "ole typing = ["typing-extensions ; python_version < \"3.10\""] xmp = ["defusedxml"] +[[package]] +name = "pycparser" +version = "2.22" +description = "C parser in Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\"" +files = [ + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, +] + [[package]] name = "pyfis" version = "1.13.0" @@ -130,7 +398,102 @@ files = [ [package.extras] cp2110 = ["hidapi"] +[[package]] +name = "setuptools" +version = "80.9.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922"}, + {file = "setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] +core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] + +[[package]] +name = "zope-event" +version = "5.0" +description = "Very basic event publishing system" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "zope.event-5.0-py3-none-any.whl", hash = "sha256:2832e95014f4db26c47a13fdaef84cef2f4df37e66b59d8f1f4a8f319a632c26"}, + {file = "zope.event-5.0.tar.gz", hash = "sha256:bac440d8d9891b4068e2b5a2c5e2c9765a9df762944bda6955f96bb9b91e67cd"}, +] + +[package.dependencies] +setuptools = "*" + +[package.extras] +docs = ["Sphinx"] +test = ["zope.testrunner"] + +[[package]] +name = "zope-interface" +version = "7.2" +description = "Interfaces for Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "zope.interface-7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ce290e62229964715f1011c3dbeab7a4a1e4971fd6f31324c4519464473ef9f2"}, + {file = "zope.interface-7.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:05b910a5afe03256b58ab2ba6288960a2892dfeef01336dc4be6f1b9ed02ab0a"}, + {file = "zope.interface-7.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:550f1c6588ecc368c9ce13c44a49b8d6b6f3ca7588873c679bd8fd88a1b557b6"}, + {file = "zope.interface-7.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ef9e2f865721553c6f22a9ff97da0f0216c074bd02b25cf0d3af60ea4d6931d"}, + {file = "zope.interface-7.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27f926f0dcb058211a3bb3e0e501c69759613b17a553788b2caeb991bed3b61d"}, + {file = "zope.interface-7.2-cp310-cp310-win_amd64.whl", hash = "sha256:144964649eba4c5e4410bb0ee290d338e78f179cdbfd15813de1a664e7649b3b"}, + {file = "zope.interface-7.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1909f52a00c8c3dcab6c4fad5d13de2285a4b3c7be063b239b8dc15ddfb73bd2"}, + {file = "zope.interface-7.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:80ecf2451596f19fd607bb09953f426588fc1e79e93f5968ecf3367550396b22"}, + {file = "zope.interface-7.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:033b3923b63474800b04cba480b70f6e6243a62208071fc148354f3f89cc01b7"}, + {file = "zope.interface-7.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a102424e28c6b47c67923a1f337ede4a4c2bba3965b01cf707978a801fc7442c"}, + {file = "zope.interface-7.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25e6a61dcb184453bb00eafa733169ab6d903e46f5c2ace4ad275386f9ab327a"}, + {file = "zope.interface-7.2-cp311-cp311-win_amd64.whl", hash = "sha256:3f6771d1647b1fc543d37640b45c06b34832a943c80d1db214a37c31161a93f1"}, + {file = "zope.interface-7.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:086ee2f51eaef1e4a52bd7d3111a0404081dadae87f84c0ad4ce2649d4f708b7"}, + {file = "zope.interface-7.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:21328fcc9d5b80768bf051faa35ab98fb979080c18e6f84ab3f27ce703bce465"}, + {file = "zope.interface-7.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6dd02ec01f4468da0f234da9d9c8545c5412fef80bc590cc51d8dd084138a89"}, + {file = "zope.interface-7.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e7da17f53e25d1a3bde5da4601e026adc9e8071f9f6f936d0fe3fe84ace6d54"}, + {file = "zope.interface-7.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cab15ff4832580aa440dc9790b8a6128abd0b88b7ee4dd56abacbc52f212209d"}, + {file = "zope.interface-7.2-cp312-cp312-win_amd64.whl", hash = "sha256:29caad142a2355ce7cfea48725aa8bcf0067e2b5cc63fcf5cd9f97ad12d6afb5"}, + {file = "zope.interface-7.2-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:3e0350b51e88658d5ad126c6a57502b19d5f559f6cb0a628e3dc90442b53dd98"}, + {file = "zope.interface-7.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15398c000c094b8855d7d74f4fdc9e73aa02d4d0d5c775acdef98cdb1119768d"}, + {file = "zope.interface-7.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:802176a9f99bd8cc276dcd3b8512808716492f6f557c11196d42e26c01a69a4c"}, + {file = "zope.interface-7.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb23f58a446a7f09db85eda09521a498e109f137b85fb278edb2e34841055398"}, + {file = "zope.interface-7.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a71a5b541078d0ebe373a81a3b7e71432c61d12e660f1d67896ca62d9628045b"}, + {file = "zope.interface-7.2-cp313-cp313-win_amd64.whl", hash = "sha256:4893395d5dd2ba655c38ceb13014fd65667740f09fa5bb01caa1e6284e48c0cd"}, + {file = "zope.interface-7.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d3a8ffec2a50d8ec470143ea3d15c0c52d73df882eef92de7537e8ce13475e8a"}, + {file = "zope.interface-7.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:31d06db13a30303c08d61d5fb32154be51dfcbdb8438d2374ae27b4e069aac40"}, + {file = "zope.interface-7.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e204937f67b28d2dca73ca936d3039a144a081fc47a07598d44854ea2a106239"}, + {file = "zope.interface-7.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:224b7b0314f919e751f2bca17d15aad00ddbb1eadf1cb0190fa8175edb7ede62"}, + {file = "zope.interface-7.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baf95683cde5bc7d0e12d8e7588a3eb754d7c4fa714548adcd96bdf90169f021"}, + {file = "zope.interface-7.2-cp38-cp38-win_amd64.whl", hash = "sha256:7dc5016e0133c1a1ec212fc87a4f7e7e562054549a99c73c8896fa3a9e80cbc7"}, + {file = "zope.interface-7.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7bd449c306ba006c65799ea7912adbbfed071089461a19091a228998b82b1fdb"}, + {file = "zope.interface-7.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a19a6cc9c6ce4b1e7e3d319a473cf0ee989cbbe2b39201d7c19e214d2dfb80c7"}, + {file = "zope.interface-7.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:72cd1790b48c16db85d51fbbd12d20949d7339ad84fd971427cf00d990c1f137"}, + {file = "zope.interface-7.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52e446f9955195440e787596dccd1411f543743c359eeb26e9b2c02b077b0519"}, + {file = "zope.interface-7.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ad9913fd858274db8dd867012ebe544ef18d218f6f7d1e3c3e6d98000f14b75"}, + {file = "zope.interface-7.2-cp39-cp39-win_amd64.whl", hash = "sha256:1090c60116b3da3bfdd0c03406e2f14a1ff53e5771aebe33fec1edc0a350175d"}, + {file = "zope.interface-7.2.tar.gz", hash = "sha256:8b49f1a3d1ee4cdaf5b32d2e738362c7f5e40ac8b46dd7d1a65e82a4872728fe"}, +] + +[package.dependencies] +setuptools = "*" + +[package.extras] +docs = ["Sphinx", "furo", "repoze.sphinx.autointerface"] +test = ["coverage[toml]", "zope.event", "zope.testing"] +testing = ["coverage[toml]", "zope.event", "zope.testing"] + [metadata] lock-version = "2.1" python-versions = ">=3.10,<4.0" -content-hash = "53c2964c42e4dbe13b63a609c594b86e75c4255c918eb01afd4849165711afa2" +content-hash = "8fb2435ac6f9cfd75d27dbf3aeb8826077cdfe8a792ba03c25e07fadc6f2c22f" diff --git a/pyproject.toml b/pyproject.toml index 5f6116f..888308f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,9 @@ readme = "README.md" requires-python = ">=3.10,<4.0" dependencies = [ "pyfis (>=1.13.0,<2.0.0)", - "pillow (>=11.2.1,<12.0.0)" + "pillow (>=11.2.1,<12.0.0)", + "bottle (>=0.13.3,<0.14.0)", + "bottle-websocket (>=0.2.9,<0.3.0)" ]