diff --git a/.env.example b/.env.example index ea540461f..3223c6c48 100644 --- a/.env.example +++ b/.env.example @@ -11,3 +11,6 @@ VITE_SENTRY_DSN= # If this env var exists and is true and there is a `save` URL parameter, # clicking the save button POSTS the session.volview.zip file to the specifed URL. VITE_ENABLE_REMOTE_SAVE=true + +# VolView server remote URL +VITE_REMOTE_SERVER_URL=http://localhost:4014 diff --git a/documentation/content/doc/server.md b/documentation/content/doc/server.md new file mode 100644 index 000000000..74c8ad528 --- /dev/null +++ b/documentation/content/doc/server.md @@ -0,0 +1,355 @@ +title: VolView Server Guide +--- + +The VolView server extends the VolView viewer with remote processing +capabilities. It integrates with your Python-based code and exposes that +functionality directly into the viewer. + +## Quick Start + +There are two parts to getting started with this VolView server example: the +server and the viewer. + +### Starting the Server + +The easiest way to get started is to install +[Poetry](https://python-poetry.org/) and create a new Python environment for +running the VolView server. + +``` +cd ./server/ +poetry install +``` + +The VolView codebase comes with a several sample APIs in `server/examples/` that +work with the remote functions sample in the VolView viewer. +- `server/examples/example_api.py`: basic set of example endpoints +- `server/examples/example_class_api.py`: example endpoints using a class + +To run the server with a sample API, run the following command. + +``` +cd ./server/ +poetry run python -m volview_server -P 4014 ./examples/example_api.py +``` + +### Running the Viewer + +We first need to tell the viewer where the server is running. Copy +`.env.example` to `.env` and edit the server URL to be the following value: + +``` +VITE_REMOTE_SERVER_URL=http://localhost:4014/ +``` + +In a separate terminal from the server terminal, we will run the application. + +``` +npm install +npm run build +npm run preview +``` + +The preview server is available at http://localhost:4173/. Navigate to the +"Remote Functions" tab to explore the available remote functionality defined by +the `examples/example_api.py` script. + +- Add numbers: simple API that adds two numbers +- Random number trivia: demonstrates async API support by fetching trivia about + a random number. +- Progress: demonstrates async generators via a simple timed progress counter. +- Median filter: Runs a median filter on the current image. Demonstrates ITK + and VTK image serialization as well as client-side store access, as well as + running ITK filters in a subprocess to avoid thread blocking. + +## In-Depth Guide + +This guide will cover how to install, use, customize, and deploy the VolView +server. + +### Server Installation + +The VolView server is set up with [Poetry](https://python-poetry.org/). To +install dependencies manually, read the `pyproject.toml` file and extract the +dependencies from the `[tool.poetry.dependencies]` entry. + +If you are using Poetry, you can proceed to install the dependencies and set up +a VolView environment like so: + +``` +cd ./server/ +poetry install +``` + +### Writing your own APIs + +To start, the following is a definition of a really simple RPC API that adds two +numbers. + +```python +from volview_server import VolViewApi + +volview = VolViewApi() + +@volview.expose +def add(a: int, b: int): + return a + b +``` + +The `volview.expose` decorator exposes the `add` function with the public name +`add`. A custom public name can be passed in via `volview.expose(name)`. + +```python +# Accessible via the RPC name "my_add" +@volview.expose("my_add") +def add(a: int, b: int): + return a + b +``` + +#### Custom Object Encoding + +If you have encoded objects that have a native Python representation, you can +add custom serializers and deserializers to properly handle those objects. + +The serializer/deserializer functions should either return a transformed result, +or pass through the input if no transformation was applied. + + +```python +from datetime import datetime +from volview_server import VolViewApi + +DATETIME_FORMAT = "%Y%m%dT%H:%M:%S.%f" + +def decode_datetime(obj): + if "__datetime__" in obj: + return datetime.strptime(obj["__datetime__"], DATETIME_FORMAT) + return obj + +def encode_datetime(dt): + if isinstance(dt, datetime): + return {"__datetime__": dt.strftime(DATETIME_FORMAT)} + return dt + +volview = VolViewApi() +volview.serializers.append(encode_datetime) +volview.deserializers.append(decode_datetime) + +@volview.expose +def echo_datetime(dt: datetime): + print(type(dt), dt) + return dt +``` + +#### Async Support + +Async methods are supported via asyncio. + +```python +import asyncio +from volview_server import VolViewApi + +volview = VolViewApi() + +@volview.expose +async def sleep(): + await asyncio.sleep(5) + return "woke up" +``` + +#### Progress via Streaming Async Generators + +If the exposed method is an async generator, the function is automatically +considered to be a streaming method. Streaming methods are invoked via +`client.stream(...)` rather than `client.call(...)`. See the `client.stream` +docs for more details. + +```python +import asyncio +from volview_server import VolViewApi + +volview = VolViewApi() + +@volview.expose +async def progress(): + for i in range(100): + yield { "progress": i } + await asyncio.sleep(0.1) +``` + +#### Accessing Client Stores + +It is possible for RPC methods to access the client application stores using +`get_current_client_store(store_name)`. This feature allows the server to +control the calling client and make modifications, such as adding new images, +updating annotations, switching the viewed image, and more. + +An example that utilizes this feature is the `medianFilter` RPC example in +`examples/example_api.py`. + +```python +import asyncio +from volview_server import VolViewApi + +volview = VolViewApi() + +@volview.expose +async def access_client(): + store = get_current_client_store('images') + image_id_list = await store.idList + + new_image = create_new_itk_image() + await store.addVTKImageData('My image', new_image) +``` + +#### RPC Routers + +RPC routers allow for custom handling of RPC routes. For instance, route methods +can be located in namespaced or scoped scenarios, such as classes. Routers can +also be extended for further customization if desired. + +See the `examples/example_class_api.py` for how to use the `RpcRouter` class and +how to add routers to the `VolViewApi`. + +### Invoking RPCs from the Client + +VolView keeps a global client object in the server store, accessible via `const +{ client } = useServerStore()`. + +Use `result = await client.call(endpoint, [arg1, arg2, ...])` to invoke a +server-side RPC endpoint. + +```js +const result = await client.call('add', [1, 2]) +``` + +Use `await client.stream(endpoint, onStreamData)` to invoke a server-side RPC +stream. + +```typescript +const onStreamData = (data: StreamData) => { + const { progress } = data + console.log('current progress: ', progress) +} + +let done = false +await client.stream('progress', onStreamData) +let done = true +``` + +### Deployment + +The VolView server comes with its own aiohttp-based server, which can be run via +the `volview_server` module. + +``` +python -m volview_server [...options] api_script.py +``` + +By default, `volview_server` expects the `api_script.py` module to contain a +`volview` symbol. If the VolView API is under a different name, add it to the +end of the module filename with a colon. + +```python +from volview_server import VolViewApi + +my_volview_api = VolViewApi() +... +``` + +``` +python -m volview_server [...options] api_script.py:my_volview_api +``` + +The server supports any +[deployment strategy supported by python-socketio](https://python-socketio.readthedocs.io/en/latest/server.html#deployment-strategies) +as well as exposing ASGI-compatible middleware. + +#### ASGI + +The `VolViewApi` object can act as middleware for any ASGI-compatible framework +or server. + +``` +from volview_server import VolViewApi + +app = SomeASGIApp() + +# adds VolView middleware +volview = VolViewApi() +app = VolViewApi(app) +``` + +The VolView API's path can be customized, as well as a host of other properties. +These are exposed as keyword arguments to `VolViewApi(app, server_kwargs={}, asgi_kwargs={})`. +- `server_kwargs`: see +- `asgi_kwargs`: see + +##### FastAPI middleware example + +FastAPI is an ASGI-compatible web framework. This guide will go through the +FastAPI example found in `examples/example_fastapi.py`. + +First install `FastAPI` and `uvicorn[standard]`. + +``` +python -m pip install FastAPI 'uvicorn[standard]' +``` + +To start the FastAPI server, use `uvicorn` as follows. + +``` +uvicorn examples.example_fastapi:app +``` + +Edit the VolView `.env` file to point to the FastAPI server: + +``` +VITE_REMOTE_SERVER_URL=http://localhost:8000/ +``` + +Rebuild the VolView viewer app and navigate to the "Remote Functions" tab to +verify that the server works. + +###### Changing the socket.io path + +If the default `https://your-host/socket.io/` path conflicts with an existing +route, VolView can be configured to use a different path. In this guide, we will +rename the default `/socket.io/` path to `/my-custom-path/`. + +On the server-side, the VolView middleware must be configured with the new path, +as shown. + +```python +app.add_middlware(volview, asgi_kwargs={"socketio_path": "/my-custom-path"}) +``` + +Then, the VolView client server URL must be updated to match. The following sets +the server URL in the `.env` file. + +``` +VITE_REMOTE_SERVER_URL=http://localhost:8000/my-custom-path +``` + +Restart both the server and the client to verify that a successful connection is +achieved. + +#### Python-socketio supported deployment strategies + +You can follow the [deployment strategies](https://python-socketio.readthedocs.io/en/latest/server.html#deployment-strategies) +supported by the python-socketio project, which powers the VolView server. In +order to do so, you will need to get access to the underlying socket.io +instance. + +```python +from volview_server import VolViewApi +from volview_server.rpc_server import RpcServer + +volview = VolViewApi() +... + +server = RpcServer(volview, ...) +# retrieve the socket.io instance +sio = server.sio +sio.attach(...) +``` \ No newline at end of file diff --git a/documentation/tpl/__en__ b/documentation/tpl/__en__ index 04ae899c6..9bf65bbeb 100644 --- a/documentation/tpl/__en__ +++ b/documentation/tpl/__en__ @@ -29,3 +29,4 @@ sidebar: mouse_controls: Mouse/Keyboard Controls rendering: Cinematic Rendering state_files: State Files + server: Remote Server Capabilities \ No newline at end of file diff --git a/documentation/tpl/__sidebar__ b/documentation/tpl/__sidebar__ index bee2ac502..56f52dfad 100644 --- a/documentation/tpl/__sidebar__ +++ b/documentation/tpl/__sidebar__ @@ -9,3 +9,4 @@ doc: mouse_controls: mouse_controls.html rendering: rendering.html state_files: state_files.html + server: server.html diff --git a/package-lock.json b/package-lock.json index 6ec1d43ba..cb6482f24 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,12 +25,14 @@ "itk-wasm": "1.0.0-b.130", "jszip": "3.10.0", "mitt": "^3.0.0", + "nanoid": "^4.0.1", "pinia": "^2.0.34", + "socket.io-client": "^4.7.1", + "socket.io-parser": "^4.2.4", "vue": "^3.3.4", "vue-toastification": "^2.0.0-rc.5", "vuetify": "^3.1.14", "whatwg-url": "^12.0.1", - "wslink": "^1.6.4", "zod": "^3.18.0" }, "devDependencies": { @@ -4122,6 +4124,11 @@ "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", "dev": true }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" + }, "node_modules/@szmarczak/http-timer": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", @@ -8512,6 +8519,46 @@ "once": "^1.4.0" } }, + "node_modules/engine.io-client": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.2.tgz", + "integrity": "sha512-CQZqbrpEYnrpGqC07a9dJDz4gePZUgTPMU3NKJPSeQOyw27Tst4Pl3FemKoFGAlHzgZmKjoRmiJvbWfhCXUlIg==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.11.0", + "xmlhttprequest-ssl": "~2.0.0" + } + }, + "node_modules/engine.io-client/node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz", + "integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/enhanced-resolve": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-0.9.1.tgz", @@ -14281,6 +14328,18 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, + "node_modules/mocha/node_modules/nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/mocha/node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -14440,15 +14499,20 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", - "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", - "dev": true, + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-4.0.2.tgz", + "integrity": "sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "bin": { - "nanoid": "bin/nanoid.cjs" + "nanoid": "bin/nanoid.js" }, "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "node": "^14 || ^16 || >=18" } }, "node_modules/natural-compare": { @@ -17590,6 +17654,32 @@ "npm": ">= 3.0.0" } }, + "node_modules/socket.io-client": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.2.tgz", + "integrity": "sha512-vtA0uD4ibrYD793SOIAwlo8cj6haOeMHrGvwPxJsxH7CeIksqJ+3Zc06RvWTIFgiSqx4A3sOnTXpfAEE2Zyz6w==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/socks": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", @@ -20563,6 +20653,7 @@ "version": "1.10.0", "resolved": "https://registry.npmjs.org/wslink/-/wslink-1.10.0.tgz", "integrity": "sha512-xwSGJc+j5eUwQ8CjdLfp+9WdyqLJ53qa2XCJKTkaAXqSetRfqERL4bFMG1+llfqq9nd+Hc54MJCYFRq/e5GGmQ==", + "peer": true, "dependencies": { "json5": "2.2.0" } @@ -20571,6 +20662,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", + "peer": true, "dependencies": { "minimist": "^1.2.5" }, @@ -20627,6 +20719,14 @@ "optional": true, "peer": true }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", + "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -24136,6 +24236,11 @@ "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", "dev": true }, + "@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" + }, "@szmarczak/http-timer": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", @@ -27519,6 +27624,31 @@ "once": "^1.4.0" } }, + "engine.io-client": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.2.tgz", + "integrity": "sha512-CQZqbrpEYnrpGqC07a9dJDz4gePZUgTPMU3NKJPSeQOyw27Tst4Pl3FemKoFGAlHzgZmKjoRmiJvbWfhCXUlIg==", + "requires": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.11.0", + "xmlhttprequest-ssl": "~2.0.0" + }, + "dependencies": { + "ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "requires": {} + } + } + }, + "engine.io-parser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz", + "integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==" + }, "enhanced-resolve": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-0.9.1.tgz", @@ -31863,6 +31993,12 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, + "nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "dev": true + }, "supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -32000,10 +32136,9 @@ "dev": true }, "nanoid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", - "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", - "dev": true + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-4.0.2.tgz", + "integrity": "sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==" }, "natural-compare": { "version": "1.4.0", @@ -34364,6 +34499,26 @@ "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", "dev": true }, + "socket.io-client": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.2.tgz", + "integrity": "sha512-vtA0uD4ibrYD793SOIAwlo8cj6haOeMHrGvwPxJsxH7CeIksqJ+3Zc06RvWTIFgiSqx4A3sOnTXpfAEE2Zyz6w==", + "requires": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.5.2", + "socket.io-parser": "~4.2.4" + } + }, + "socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "requires": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + } + }, "socks": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", @@ -36524,6 +36679,7 @@ "version": "1.10.0", "resolved": "https://registry.npmjs.org/wslink/-/wslink-1.10.0.tgz", "integrity": "sha512-xwSGJc+j5eUwQ8CjdLfp+9WdyqLJ53qa2XCJKTkaAXqSetRfqERL4bFMG1+llfqq9nd+Hc54MJCYFRq/e5GGmQ==", + "peer": true, "requires": { "json5": "2.2.0" }, @@ -36532,6 +36688,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", + "peer": true, "requires": { "minimist": "^1.2.5" } @@ -36577,6 +36734,11 @@ "optional": true, "peer": true }, + "xmlhttprequest-ssl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", + "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==" + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index a99bda0b1..fd04fedee 100644 --- a/package.json +++ b/package.json @@ -40,12 +40,14 @@ "itk-wasm": "1.0.0-b.130", "jszip": "3.10.0", "mitt": "^3.0.0", + "nanoid": "^4.0.1", "pinia": "^2.0.34", + "socket.io-client": "^4.7.1", + "socket.io-parser": "^4.2.4", "vue": "^3.3.4", "vue-toastification": "^2.0.0-rc.5", "vuetify": "^3.1.14", "whatwg-url": "^12.0.1", - "wslink": "^1.6.4", "zod": "^3.18.0" }, "devDependencies": { @@ -122,4 +124,4 @@ "eslint" ] } -} \ No newline at end of file +} diff --git a/server/.flake8 b/server/.flake8 new file mode 100644 index 000000000..e0ea542fd --- /dev/null +++ b/server/.flake8 @@ -0,0 +1,3 @@ +[flake8] +max-line-length = 88 +extend-ignore = E203 \ No newline at end of file diff --git a/server/README.md b/server/README.md new file mode 100644 index 000000000..5e8d73d14 --- /dev/null +++ b/server/README.md @@ -0,0 +1,4 @@ +# VolView Server + +Visit the [VolView server documentation](../documentation/content/doc/server.md) +for more info on how to use the server. \ No newline at end of file diff --git a/server/examples/example_api.py b/server/examples/example_api.py new file mode 100644 index 000000000..14215e155 --- /dev/null +++ b/server/examples/example_api.py @@ -0,0 +1,113 @@ +import asyncio +from dataclasses import dataclass, field +from concurrent.futures import ProcessPoolExecutor + +import aiohttp +import itk + +from volview_server import VolViewApi, get_current_client_store, get_current_session +from volview_server.transformers import ( + convert_itk_to_vtkjs_image, + convert_vtkjs_to_itk_image, +) + +volview = VolViewApi() + +## basic examples ## + + +@volview.expose +def add(a: int, b: int): + return a + b + + +@volview.expose # exposes as "number_trivia" +@volview.expose("get_number_trivia") # exposes as "get_number_trivia" +async def number_trivia(): + async with aiohttp.ClientSession() as session: + url = "http://numbersapi.com/random/" + async with session.get(url) as resp: + return await resp.text() + + +@volview.expose("progress") +async def number_stream(): + for i in range(1, 101): + yield {"progress": i} + await asyncio.sleep(0.1) + + +## median filter example ## + +process_pool = ProcessPoolExecutor(4) + + +@dataclass +class ClientState: + image_id_map: dict = field(init=False, default_factory=dict) + blurred_ids: set = field(init=False, default_factory=set) + + +def do_median_filter(serialized_img, radius): + img = convert_vtkjs_to_itk_image(serialized_img) + ImageType = type(img) + + median_filter = itk.MedianImageFilter[ImageType, ImageType].New() + median_filter.SetInput(img) + median_filter.SetRadius(radius) + median_filter.Update() + + output = median_filter.GetOutput() + return convert_itk_to_vtkjs_image(output) + + +async def run_median_filter_process(img, radius: int): + serialized_img = convert_itk_to_vtkjs_image(img) + loop = asyncio.get_event_loop() + serialized_output = await loop.run_in_executor( + process_pool, do_median_filter, serialized_img, radius + ) + return convert_vtkjs_to_itk_image(serialized_output) + + +def associate_images(state, image_id, blurred_id): + state.blurred_ids.add(blurred_id) + state.image_id_map[image_id] = blurred_id + state.image_id_map[blurred_id] = image_id + + +def get_base_image(state: ClientState, img_id: str) -> str: + if img_id in state.blurred_ids: + return state.image_id_map[img_id] + return img_id + + +async def show_image(img_id: str): + store = get_current_client_store("dataset") + await store.setPrimarySelection({"type": "image", "dataID": img_id}) + + +@volview.expose("medianFilter") +async def median_filter(img_id, radius): + store = get_current_client_store("images") + state = get_current_session(default_factory=ClientState) + + # Behavior: when a median filter request occurs on a + # blurred image, we instead assume we are re-running + # the blur operation on the original image. + base_image_id = get_base_image(state, img_id) + img = await store.dataIndex[base_image_id] + + # we need to run the median filter in a subprocess, + # since itk blocks the GIL. + output = await run_median_filter_process(img, radius) + + blurred_id = state.image_id_map.get(base_image_id) + if not blurred_id: + blurred_id = await store.addVTKImageData("Blurred image", output) + # Associate the blurred image ID with the base image ID. + associate_images(state, base_image_id, blurred_id) + else: + await store.updateData(blurred_id, output) + + await show_image(blurred_id) diff --git a/server/examples/example_class_api.py b/server/examples/example_class_api.py new file mode 100644 index 000000000..12e9603f3 --- /dev/null +++ b/server/examples/example_class_api.py @@ -0,0 +1,129 @@ +class ExampleApi: + def __init__(self) -> None: + self.rpc = RpcRouter() + self.rpc.expose("add", self.add) + + def add(self, a: int, b: int): + return a + b + + +import asyncio +from dataclasses import dataclass, field +from concurrent.futures import ProcessPoolExecutor + +import aiohttp +import itk + +from volview_server import ( + VolViewApi, + RpcRouter, + get_current_client_store, + get_current_session, +) +from volview_server.transformers import ( + convert_itk_to_vtkjs_image, + convert_vtkjs_to_itk_image, +) + + +@dataclass +class ClientState: + image_id_map: dict = field(init=False, default_factory=dict) + blurred_ids: set = field(init=False, default_factory=set) + + +def do_median_filter(serialized_img, radius): + img = convert_vtkjs_to_itk_image(serialized_img) + ImageType = type(img) + + median_filter = itk.MedianImageFilter[ImageType, ImageType].New() + median_filter.SetInput(img) + median_filter.SetRadius(radius) + median_filter.Update() + + output = median_filter.GetOutput() + return convert_itk_to_vtkjs_image(output) + + +class ExampleApi: + def __init__(self): + self.rpc_router = RpcRouter() + self.rpc_router.add_endpoint("add", self.add) + self.rpc_router.add_endpoint("number_trivia", self.number_trivia) + self.rpc_router.add_endpoint("get_number_trivia", self.number_trivia) + self.rpc_router.add_endpoint("progress", self.number_stream) + self.rpc_router.add_endpoint("medianFilter", self.median_filter) + + self.process_pool = ProcessPoolExecutor(4) + + ## add example ## + + def add(self, a, b): + return a + b + + ## number trivia example ## + + async def number_trivia(self): + async with aiohttp.ClientSession() as session: + url = "http://numbersapi.com/random/" + async with session.get(url) as resp: + return await resp.text() + + ## progress bar example ## + + async def number_stream(self): + for i in range(1, 101): + yield {"progress": i} + await asyncio.sleep(0.1) + + ## median filter example ## + + async def median_filter(self, img_id, radius): + store = get_current_client_store("images") + state = get_current_session(default_factory=ClientState) + + # Behavior: when a median filter request occurs on a + # blurred image, we instead assume we are re-running + # the blur operation on the original image. + base_image_id = self._get_base_image(img_id) + img = await store.dataIndex[base_image_id] + + output = await self._run_median_filter_process(img, radius) + + blurred_id = state.image_id_map.get(base_image_id) + if not blurred_id: + blurred_id = await store.addVTKImageData("Blurred image", output) + # Associate the blurred image ID with the base image ID. + self._associate_images(base_image_id, blurred_id) + else: + await store.updateData(blurred_id, output) + + await self._show_image(blurred_id) + + async def _run_median_filter_process(self, img, radius): + serialized_img = convert_itk_to_vtkjs_image(img) + loop = asyncio.get_event_loop() + serialized_output = await loop.run_in_executor( + self.process_pool, do_median_filter, serialized_img, radius + ) + return convert_vtkjs_to_itk_image(serialized_output) + + def _associate_images(self, image_id, blurred_id): + state = get_current_session(default_factory=ClientState) + state.blurred_ids.add(blurred_id) + state.image_id_map[image_id] = blurred_id + state.image_id_map[blurred_id] = image_id + + def _get_base_image(self, img_id): + state = get_current_session(default_factory=ClientState) + if img_id in state.blurred_ids: + return state.image_id_map[img_id] + return img_id + + async def _show_image(self, img_id): + store = get_current_client_store("dataset") + await store.setPrimarySelection({"type": "image", "dataID": img_id}) + + +volview = VolViewApi() +volview.add_router(ExampleApi().rpc_router) diff --git a/server/examples/example_fastapi.py b/server/examples/example_fastapi.py new file mode 100644 index 000000000..cb9038aae --- /dev/null +++ b/server/examples/example_fastapi.py @@ -0,0 +1,28 @@ +import sys +import os + +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware + +from volview_server import VolViewApi + +# Import the VolView example API +sys.path.append(os.path.dirname(os.path.abspath(__file__))) +from example_api import volview + + +app = FastAPI() + +# Adds volview middlware +app.add_middleware(volview) + +# Set CORS configuration +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], +) + + +@app.get("/") +def index(): + return {"hello": "world"} diff --git a/server/helper.py b/server/helper.py deleted file mode 100644 index 1b6673a31..000000000 --- a/server/helper.py +++ /dev/null @@ -1,147 +0,0 @@ -import traceback -import wslink -from serialize import RpcDecoder, RpcEncoder, DEFAULT_DECODERS, DEFAULT_ENCODERS -from wslink.websocket import LinkProtocol - - -RPC_DEFERRED_TYPE = 'rpc.deferred' -RPC_RESULT_TYPE = 'rpc.result' -RPC_ERROR_TYPE = 'rpc.error' -DEFERRED_RESPONSE_TYPE = 'deferred.response' - - -class FutureResult(object): - def __init__(self, id): - self._id = id - self._done = False - self._result = None - self._exception = None - self._callbacks = [] - - def id(self): - return self._id - - def done(self): - return self._done - - def result(self): - if not self._done: - raise Exception('Future is not done') - return self._result - - def exception(self): - if not self._done: - raise Exception('Future is not done') - return self._exception - - def has_result(self): - return self._done and bool(self._result) - - def has_exception(self): - return self._done and bool(self._exception) - - def add_done_callback(self, cb): - self._callbacks.append(cb) - - def remove_done_callback(self, cb): - self._callbacks.remove(cb) - - def set_result(self, result): - if not self._done: - self._done = True - self._result = result - self._trigger_callbacks() - - def set_exception(self, exception): - if not self._done: - self._done = True - self._exception = exception - self._trigger_callbacks() - - def _trigger_callbacks(self): - for cb in self._callbacks: - try: - cb(self) - except: - pass - - -def make_deferred_response(future): - return { - 'type': RPC_DEFERRED_TYPE, - 'id': future.id(), - } - - -def make_result_response(result): - return { - 'type': RPC_RESULT_TYPE, - 'result': result, - } - - -def make_error_response(exc): - return { - 'type': RPC_ERROR_TYPE, - 'error': str(exc), - } - - -def deserialize(args, kwargs): - decoder = RpcDecoder(hooks=DEFAULT_DECODERS) - new_args = [decoder.decode(arg) for arg in args] - return new_args, kwargs - - -def rpc(name): - def wrapper(fn): - def handler(self, *args, **kwargs): - - try: - args, kwargs = deserialize(args, kwargs) - result = fn(self, *args, **kwargs) - except Exception as e: - traceback.print_exc() - return make_error_response(e) - else: - if type(result) is FutureResult: - self._futures.append(result) - result.set_done_callback(self._handle_future_result) - return make_deferred_response(result.id()) - - return make_result_response(self.serialize(result)) - - return wslink.register(name)(handler) - return wrapper - - -class RpcApi(LinkProtocol): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self._futures = [] - - def _handle_future_result(self, future): - self._futures.remove(future) - self.publish('deferred.responses', - self._make_deferred_result_response(future)) - - def _make_deferred_result_response(self, future): - if future.has_exception(): - return { - 'type': DEFERRED_RESPONSE_TYPE, - 'id': future.id(), - 'rpcResponse': make_error_response(future.exception()) - } - return { - 'type': DEFERRED_RESPONSE_TYPE, - 'id': future.id(), - 'rpcResponse': make_result_response(self.serialize(future.result())) - } - - def serialize(self, obj): - try: - encoder = RpcEncoder(encoders=DEFAULT_ENCODERS, - extra_args=(self.addAttachment,)) - return encoder.encode(obj) - except: - traceback.print_exc() diff --git a/server/host.js b/server/host.js deleted file mode 100644 index d22f7f254..000000000 --- a/server/host.js +++ /dev/null @@ -1,134 +0,0 @@ -/* eslint-disable-next-line max-classes-per-file */ -import WebsocketConnection from 'wslink/src/WebsocketConnection'; -import { defer } from '../src/utils/common'; - -import { deserialize, serialize } from './serialize'; - -/** - * Matches a response against valid type names - */ -function isValidResponse(response, types) { - const rtype = response?.type; - if (!types.includes(rtype)) { - return false; - } - switch (rtype) { - case 'rpc.result': - return 'result' in response; - case 'rpc.error': - return 'error' in response; - case 'rpc.deferred': - return 'id' in response; - case 'deferred.response': - return 'id' in response && 'rpcResponse' in response; - default: - return false; - } -} - -export class RpcError extends Error {} - -export default class HostConnection { - constructor(wsUrl) { - this.ws = null; - this.wsUrl = wsUrl; - this.connected = false; - this.deferredResponses = new Map(); - } - - async connect() { - return new Promise((resolve, reject) => { - if (!this.connected) { - this.ws = WebsocketConnection.newInstance({ urls: this.wsUrl }); - - this.ws.onConnectionReady(() => { - this.connected = true; - this.session = this.ws.getSession(); - - this.session.subscribe( - 'deferred.responses', - this.handleDeferredResponse - ); - - resolve(); - }); - - this.ws.onConnectionClose(() => { - this.connected = false; - }); - - this.ws.onConnectionError(() => { - reject(new Error('Failed to connect to ws endpoint')); - }); - - this.ws.connect(); - } - }); - } - - async disconnect() { - if (this.connected) { - this.ws.destroy(); - } - } - - async call(endpoint, ...args) { - if (!this.connected) { - throw new Error('Not connected'); - } - - const attach = (obj) => { - if (ArrayBuffer.isView(obj) && !(obj instanceof DataView)) { - return this.session.addAttachment(obj.buffer); - } - return obj; - }; - - const preparedArgs = args.map((arg) => serialize(arg, attach)); - - const response = await this.session.call(endpoint, preparedArgs); - return this.handleRpcResponse(response); - } - - async handleRpcResponse(response) { - if ( - !isValidResponse(response, ['rpc.result', 'rpc.error', 'rpc.deferred']) - ) { - throw new Error('Invalid response from rpc'); - } - - if (response.type === 'rpc.deferred') { - const deferred = defer(); - this.deferredResponses.set(response.id, deferred); - return deferred.promise; - } - - if (response.type === 'rpc.error') { - throw new RpcError(response.error); - } - - const result = await deserialize(response.result); - return result; - } - - async handleDeferredResponse(response) { - if (!isValidResponse(response, ['deferred.result'])) { - throw new Error('Invalid deferred response'); - } - - const { id } = response; - if (!this.deferredResponses.has(id)) { - throw new Error('Received a deferred response for a nonexistent call'); - } - - const deferred = this.deferredResponses.get(id); - this.deferredResponses.delete(id); - - try { - const result = await this.handleRpcResponse(response.rpcResponse); - deferred.resolve(result); - } catch (e) { - deferred.reject(e); - } - } -} diff --git a/server/poetry.lock b/server/poetry.lock new file mode 100644 index 000000000..6c20f24e8 --- /dev/null +++ b/server/poetry.lock @@ -0,0 +1,1474 @@ +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. + +[[package]] +name = "aiohttp" +version = "3.8.4" +description = "Async http client/server framework (asyncio)" +optional = false +python-versions = ">=3.6" +files = [ + {file = "aiohttp-3.8.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5ce45967538fb747370308d3145aa68a074bdecb4f3a300869590f725ced69c1"}, + {file = "aiohttp-3.8.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b744c33b6f14ca26b7544e8d8aadff6b765a80ad6164fb1a430bbadd593dfb1a"}, + {file = "aiohttp-3.8.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1a45865451439eb320784918617ba54b7a377e3501fb70402ab84d38c2cd891b"}, + {file = "aiohttp-3.8.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a86d42d7cba1cec432d47ab13b6637bee393a10f664c425ea7b305d1301ca1a3"}, + {file = "aiohttp-3.8.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee3c36df21b5714d49fc4580247947aa64bcbe2939d1b77b4c8dcb8f6c9faecc"}, + {file = "aiohttp-3.8.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:176a64b24c0935869d5bbc4c96e82f89f643bcdf08ec947701b9dbb3c956b7dd"}, + {file = "aiohttp-3.8.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c844fd628851c0bc309f3c801b3a3d58ce430b2ce5b359cd918a5a76d0b20cb5"}, + {file = "aiohttp-3.8.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5393fb786a9e23e4799fec788e7e735de18052f83682ce2dfcabaf1c00c2c08e"}, + {file = "aiohttp-3.8.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e4b09863aae0dc965c3ef36500d891a3ff495a2ea9ae9171e4519963c12ceefd"}, + {file = "aiohttp-3.8.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:adfbc22e87365a6e564c804c58fc44ff7727deea782d175c33602737b7feadb6"}, + {file = "aiohttp-3.8.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:147ae376f14b55f4f3c2b118b95be50a369b89b38a971e80a17c3fd623f280c9"}, + {file = "aiohttp-3.8.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:eafb3e874816ebe2a92f5e155f17260034c8c341dad1df25672fb710627c6949"}, + {file = "aiohttp-3.8.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c6cc15d58053c76eacac5fa9152d7d84b8d67b3fde92709195cb984cfb3475ea"}, + {file = "aiohttp-3.8.4-cp310-cp310-win32.whl", hash = "sha256:59f029a5f6e2d679296db7bee982bb3d20c088e52a2977e3175faf31d6fb75d1"}, + {file = "aiohttp-3.8.4-cp310-cp310-win_amd64.whl", hash = "sha256:fe7ba4a51f33ab275515f66b0a236bcde4fb5561498fe8f898d4e549b2e4509f"}, + {file = "aiohttp-3.8.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3d8ef1a630519a26d6760bc695842579cb09e373c5f227a21b67dc3eb16cfea4"}, + {file = "aiohttp-3.8.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b3f2e06a512e94722886c0827bee9807c86a9f698fac6b3aee841fab49bbfb4"}, + {file = "aiohttp-3.8.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a80464982d41b1fbfe3154e440ba4904b71c1a53e9cd584098cd41efdb188ef"}, + {file = "aiohttp-3.8.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b631e26df63e52f7cce0cce6507b7a7f1bc9b0c501fcde69742130b32e8782f"}, + {file = "aiohttp-3.8.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f43255086fe25e36fd5ed8f2ee47477408a73ef00e804cb2b5cba4bf2ac7f5e"}, + {file = "aiohttp-3.8.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4d347a172f866cd1d93126d9b239fcbe682acb39b48ee0873c73c933dd23bd0f"}, + {file = "aiohttp-3.8.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3fec6a4cb5551721cdd70473eb009d90935b4063acc5f40905d40ecfea23e05"}, + {file = "aiohttp-3.8.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80a37fe8f7c1e6ce8f2d9c411676e4bc633a8462844e38f46156d07a7d401654"}, + {file = "aiohttp-3.8.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d1e6a862b76f34395a985b3cd39a0d949ca80a70b6ebdea37d3ab39ceea6698a"}, + {file = "aiohttp-3.8.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cd468460eefef601ece4428d3cf4562459157c0f6523db89365202c31b6daebb"}, + {file = "aiohttp-3.8.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:618c901dd3aad4ace71dfa0f5e82e88b46ef57e3239fc7027773cb6d4ed53531"}, + {file = "aiohttp-3.8.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:652b1bff4f15f6287550b4670546a2947f2a4575b6c6dff7760eafb22eacbf0b"}, + {file = "aiohttp-3.8.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80575ba9377c5171407a06d0196b2310b679dc752d02a1fcaa2bc20b235dbf24"}, + {file = "aiohttp-3.8.4-cp311-cp311-win32.whl", hash = "sha256:bbcf1a76cf6f6dacf2c7f4d2ebd411438c275faa1dc0c68e46eb84eebd05dd7d"}, + {file = "aiohttp-3.8.4-cp311-cp311-win_amd64.whl", hash = "sha256:6e74dd54f7239fcffe07913ff8b964e28b712f09846e20de78676ce2a3dc0bfc"}, + {file = "aiohttp-3.8.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:880e15bb6dad90549b43f796b391cfffd7af373f4646784795e20d92606b7a51"}, + {file = "aiohttp-3.8.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb96fa6b56bb536c42d6a4a87dfca570ff8e52de2d63cabebfd6fb67049c34b6"}, + {file = "aiohttp-3.8.4-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a6cadebe132e90cefa77e45f2d2f1a4b2ce5c6b1bfc1656c1ddafcfe4ba8131"}, + {file = "aiohttp-3.8.4-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f352b62b45dff37b55ddd7b9c0c8672c4dd2eb9c0f9c11d395075a84e2c40f75"}, + {file = "aiohttp-3.8.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ab43061a0c81198d88f39aaf90dae9a7744620978f7ef3e3708339b8ed2ef01"}, + {file = "aiohttp-3.8.4-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9cb1565a7ad52e096a6988e2ee0397f72fe056dadf75d17fa6b5aebaea05622"}, + {file = "aiohttp-3.8.4-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:1b3ea7edd2d24538959c1c1abf97c744d879d4e541d38305f9bd7d9b10c9ec41"}, + {file = "aiohttp-3.8.4-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:7c7837fe8037e96b6dd5cfcf47263c1620a9d332a87ec06a6ca4564e56bd0f36"}, + {file = "aiohttp-3.8.4-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:3b90467ebc3d9fa5b0f9b6489dfb2c304a1db7b9946fa92aa76a831b9d587e99"}, + {file = "aiohttp-3.8.4-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:cab9401de3ea52b4b4c6971db5fb5c999bd4260898af972bf23de1c6b5dd9d71"}, + {file = "aiohttp-3.8.4-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d1f9282c5f2b5e241034a009779e7b2a1aa045f667ff521e7948ea9b56e0c5ff"}, + {file = "aiohttp-3.8.4-cp36-cp36m-win32.whl", hash = "sha256:5e14f25765a578a0a634d5f0cd1e2c3f53964553a00347998dfdf96b8137f777"}, + {file = "aiohttp-3.8.4-cp36-cp36m-win_amd64.whl", hash = "sha256:4c745b109057e7e5f1848c689ee4fb3a016c8d4d92da52b312f8a509f83aa05e"}, + {file = "aiohttp-3.8.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:aede4df4eeb926c8fa70de46c340a1bc2c6079e1c40ccf7b0eae1313ffd33519"}, + {file = "aiohttp-3.8.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ddaae3f3d32fc2cb4c53fab020b69a05c8ab1f02e0e59665c6f7a0d3a5be54f"}, + {file = "aiohttp-3.8.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4eb3b82ca349cf6fadcdc7abcc8b3a50ab74a62e9113ab7a8ebc268aad35bb9"}, + {file = "aiohttp-3.8.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bcb89336efa095ea21b30f9e686763f2be4478f1b0a616969551982c4ee4c3b"}, + {file = "aiohttp-3.8.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c08e8ed6fa3d477e501ec9db169bfac8140e830aa372d77e4a43084d8dd91ab"}, + {file = "aiohttp-3.8.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c6cd05ea06daca6ad6a4ca3ba7fe7dc5b5de063ff4daec6170ec0f9979f6c332"}, + {file = "aiohttp-3.8.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7a00a9ed8d6e725b55ef98b1b35c88013245f35f68b1b12c5cd4100dddac333"}, + {file = "aiohttp-3.8.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:de04b491d0e5007ee1b63a309956eaed959a49f5bb4e84b26c8f5d49de140fa9"}, + {file = "aiohttp-3.8.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:40653609b3bf50611356e6b6554e3a331f6879fa7116f3959b20e3528783e699"}, + {file = "aiohttp-3.8.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dbf3a08a06b3f433013c143ebd72c15cac33d2914b8ea4bea7ac2c23578815d6"}, + {file = "aiohttp-3.8.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:854f422ac44af92bfe172d8e73229c270dc09b96535e8a548f99c84f82dde241"}, + {file = "aiohttp-3.8.4-cp37-cp37m-win32.whl", hash = "sha256:aeb29c84bb53a84b1a81c6c09d24cf33bb8432cc5c39979021cc0f98c1292a1a"}, + {file = "aiohttp-3.8.4-cp37-cp37m-win_amd64.whl", hash = "sha256:db3fc6120bce9f446d13b1b834ea5b15341ca9ff3f335e4a951a6ead31105480"}, + {file = "aiohttp-3.8.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fabb87dd8850ef0f7fe2b366d44b77d7e6fa2ea87861ab3844da99291e81e60f"}, + {file = "aiohttp-3.8.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:91f6d540163f90bbaef9387e65f18f73ffd7c79f5225ac3d3f61df7b0d01ad15"}, + {file = "aiohttp-3.8.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d265f09a75a79a788237d7f9054f929ced2e69eb0bb79de3798c468d8a90f945"}, + {file = "aiohttp-3.8.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d89efa095ca7d442a6d0cbc755f9e08190ba40069b235c9886a8763b03785da"}, + {file = "aiohttp-3.8.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4dac314662f4e2aa5009977b652d9b8db7121b46c38f2073bfeed9f4049732cd"}, + {file = "aiohttp-3.8.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe11310ae1e4cd560035598c3f29d86cef39a83d244c7466f95c27ae04850f10"}, + {file = "aiohttp-3.8.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ddb2a2026c3f6a68c3998a6c47ab6795e4127315d2e35a09997da21865757f8"}, + {file = "aiohttp-3.8.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e75b89ac3bd27d2d043b234aa7b734c38ba1b0e43f07787130a0ecac1e12228a"}, + {file = "aiohttp-3.8.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6e601588f2b502c93c30cd5a45bfc665faaf37bbe835b7cfd461753068232074"}, + {file = "aiohttp-3.8.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a5d794d1ae64e7753e405ba58e08fcfa73e3fad93ef9b7e31112ef3c9a0efb52"}, + {file = "aiohttp-3.8.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:a1f4689c9a1462f3df0a1f7e797791cd6b124ddbee2b570d34e7f38ade0e2c71"}, + {file = "aiohttp-3.8.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:3032dcb1c35bc330134a5b8a5d4f68c1a87252dfc6e1262c65a7e30e62298275"}, + {file = "aiohttp-3.8.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8189c56eb0ddbb95bfadb8f60ea1b22fcfa659396ea36f6adcc521213cd7b44d"}, + {file = "aiohttp-3.8.4-cp38-cp38-win32.whl", hash = "sha256:33587f26dcee66efb2fff3c177547bd0449ab7edf1b73a7f5dea1e38609a0c54"}, + {file = "aiohttp-3.8.4-cp38-cp38-win_amd64.whl", hash = "sha256:e595432ac259af2d4630008bf638873d69346372d38255774c0e286951e8b79f"}, + {file = "aiohttp-3.8.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5a7bdf9e57126dc345b683c3632e8ba317c31d2a41acd5800c10640387d193ed"}, + {file = "aiohttp-3.8.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:22f6eab15b6db242499a16de87939a342f5a950ad0abaf1532038e2ce7d31567"}, + {file = "aiohttp-3.8.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7235604476a76ef249bd64cb8274ed24ccf6995c4a8b51a237005ee7a57e8643"}, + {file = "aiohttp-3.8.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea9eb976ffdd79d0e893869cfe179a8f60f152d42cb64622fca418cd9b18dc2a"}, + {file = "aiohttp-3.8.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92c0cea74a2a81c4c76b62ea1cac163ecb20fb3ba3a75c909b9fa71b4ad493cf"}, + {file = "aiohttp-3.8.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:493f5bc2f8307286b7799c6d899d388bbaa7dfa6c4caf4f97ef7521b9cb13719"}, + {file = "aiohttp-3.8.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a63f03189a6fa7c900226e3ef5ba4d3bd047e18f445e69adbd65af433add5a2"}, + {file = "aiohttp-3.8.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10c8cefcff98fd9168cdd86c4da8b84baaa90bf2da2269c6161984e6737bf23e"}, + {file = "aiohttp-3.8.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bca5f24726e2919de94f047739d0a4fc01372801a3672708260546aa2601bf57"}, + {file = "aiohttp-3.8.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:03baa76b730e4e15a45f81dfe29a8d910314143414e528737f8589ec60cf7391"}, + {file = "aiohttp-3.8.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:8c29c77cc57e40f84acef9bfb904373a4e89a4e8b74e71aa8075c021ec9078c2"}, + {file = "aiohttp-3.8.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:03543dcf98a6619254b409be2d22b51f21ec66272be4ebda7b04e6412e4b2e14"}, + {file = "aiohttp-3.8.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:17b79c2963db82086229012cff93ea55196ed31f6493bb1ccd2c62f1724324e4"}, + {file = "aiohttp-3.8.4-cp39-cp39-win32.whl", hash = "sha256:34ce9f93a4a68d1272d26030655dd1b58ff727b3ed2a33d80ec433561b03d67a"}, + {file = "aiohttp-3.8.4-cp39-cp39-win_amd64.whl", hash = "sha256:41a86a69bb63bb2fc3dc9ad5ea9f10f1c9c8e282b471931be0268ddd09430b04"}, + {file = "aiohttp-3.8.4.tar.gz", hash = "sha256:bf2e1a9162c1e441bf805a1fd166e249d574ca04e03b34f97e2928769e91ab5c"}, +] + +[package.dependencies] +aiosignal = ">=1.1.2" +async-timeout = ">=4.0.0a3,<5.0" +attrs = ">=17.3.0" +charset-normalizer = ">=2.0,<4.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +yarl = ">=1.0,<2.0" + +[package.extras] +speedups = ["Brotli", "aiodns", "cchardet"] + +[[package]] +name = "aiosignal" +version = "1.3.1" +description = "aiosignal: a list of registered asynchronous callbacks" +optional = false +python-versions = ">=3.7" +files = [ + {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, + {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, +] + +[package.dependencies] +frozenlist = ">=1.1.0" + +[[package]] +name = "anyio" +version = "3.7.0" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.7" +files = [ + {file = "anyio-3.7.0-py3-none-any.whl", hash = "sha256:eddca883c4175f14df8aedce21054bfca3adb70ffe76a9f607aef9d7fa2ea7f0"}, + {file = "anyio-3.7.0.tar.gz", hash = "sha256:275d9973793619a5374e1c89a4f4ad3f4b0a5510a2b5b939444bee8f4c4d37ce"}, +] + +[package.dependencies] +exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} +idna = ">=2.8" +sniffio = ">=1.1" + +[package.extras] +doc = ["Sphinx (>=6.1.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme", "sphinxcontrib-jquery"] +test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (<0.22)"] + +[[package]] +name = "async-timeout" +version = "4.0.2" +description = "Timeout context manager for asyncio programs" +optional = false +python-versions = ">=3.6" +files = [ + {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, + {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, +] + +[[package]] +name = "attrs" +version = "23.1.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.7" +files = [ + {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, + {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, +] + +[package.extras] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[docs,tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] + +[[package]] +name = "bidict" +version = "0.22.1" +description = "The bidirectional mapping library for Python." +optional = false +python-versions = ">=3.7" +files = [ + {file = "bidict-0.22.1-py3-none-any.whl", hash = "sha256:6ef212238eb884b664f28da76f33f1d28b260f665fc737b413b287d5487d1e7b"}, + {file = "bidict-0.22.1.tar.gz", hash = "sha256:1e0f7f74e4860e6d0943a05d4134c63a2fad86f3d4732fb265bd79e4e856d81d"}, +] + +[package.extras] +docs = ["furo", "sphinx", "sphinx-copybutton"] +lint = ["pre-commit"] +test = ["hypothesis", "pytest", "pytest-benchmark[histogram]", "pytest-cov", "pytest-xdist", "sortedcollections", "sortedcontainers", "sphinx"] + +[[package]] +name = "black" +version = "23.3.0" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.7" +files = [ + {file = "black-23.3.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915"}, + {file = "black-23.3.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9"}, + {file = "black-23.3.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2"}, + {file = "black-23.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c"}, + {file = "black-23.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c"}, + {file = "black-23.3.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6"}, + {file = "black-23.3.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b"}, + {file = "black-23.3.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d"}, + {file = "black-23.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70"}, + {file = "black-23.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326"}, + {file = "black-23.3.0-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b"}, + {file = "black-23.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2"}, + {file = "black-23.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925"}, + {file = "black-23.3.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27"}, + {file = "black-23.3.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331"}, + {file = "black-23.3.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5"}, + {file = "black-23.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961"}, + {file = "black-23.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8"}, + {file = "black-23.3.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30"}, + {file = "black-23.3.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3"}, + {file = "black-23.3.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266"}, + {file = "black-23.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab"}, + {file = "black-23.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb"}, + {file = "black-23.3.0-py3-none-any.whl", hash = "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4"}, + {file = "black-23.3.0.tar.gz", hash = "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "charset-normalizer" +version = "3.1.0" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.1.0.tar.gz", hash = "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-win32.whl", hash = "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-win32.whl", hash = "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-win32.whl", hash = "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-win32.whl", hash = "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-win32.whl", hash = "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b"}, + {file = "charset_normalizer-3.1.0-py3-none-any.whl", hash = "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d"}, +] + +[[package]] +name = "click" +version = "8.1.3" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.1.1" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.1.1-py3-none-any.whl", hash = "sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e"}, + {file = "exceptiongroup-1.1.1.tar.gz", hash = "sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "flake8" +version = "6.0.0" +description = "the modular source code checker: pep8 pyflakes and co" +optional = false +python-versions = ">=3.8.1" +files = [ + {file = "flake8-6.0.0-py2.py3-none-any.whl", hash = "sha256:3833794e27ff64ea4e9cf5d410082a8b97ff1a06c16aa3d2027339cd0f1195c7"}, + {file = "flake8-6.0.0.tar.gz", hash = "sha256:c61007e76655af75e6785a931f452915b371dc48f56efd765247c8fe68f2b181"}, +] + +[package.dependencies] +mccabe = ">=0.7.0,<0.8.0" +pycodestyle = ">=2.10.0,<2.11.0" +pyflakes = ">=3.0.0,<3.1.0" + +[[package]] +name = "frozenlist" +version = "1.3.3" +description = "A list-like structure which implements collections.abc.MutableSequence" +optional = false +python-versions = ">=3.7" +files = [ + {file = "frozenlist-1.3.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff8bf625fe85e119553b5383ba0fb6aa3d0ec2ae980295aaefa552374926b3f4"}, + {file = "frozenlist-1.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dfbac4c2dfcc082fcf8d942d1e49b6aa0766c19d3358bd86e2000bf0fa4a9cf0"}, + {file = "frozenlist-1.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b1c63e8d377d039ac769cd0926558bb7068a1f7abb0f003e3717ee003ad85530"}, + {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fdfc24dcfce5b48109867c13b4cb15e4660e7bd7661741a391f821f23dfdca7"}, + {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c926450857408e42f0bbc295e84395722ce74bae69a3b2aa2a65fe22cb14b99"}, + {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1841e200fdafc3d51f974d9d377c079a0694a8f06de2e67b48150328d66d5483"}, + {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f470c92737afa7d4c3aacc001e335062d582053d4dbe73cda126f2d7031068dd"}, + {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:783263a4eaad7c49983fe4b2e7b53fa9770c136c270d2d4bbb6d2192bf4d9caf"}, + {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:924620eef691990dfb56dc4709f280f40baee568c794b5c1885800c3ecc69816"}, + {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ae4dc05c465a08a866b7a1baf360747078b362e6a6dbeb0c57f234db0ef88ae0"}, + {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:bed331fe18f58d844d39ceb398b77d6ac0b010d571cba8267c2e7165806b00ce"}, + {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:02c9ac843e3390826a265e331105efeab489ffaf4dd86384595ee8ce6d35ae7f"}, + {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9545a33965d0d377b0bc823dcabf26980e77f1b6a7caa368a365a9497fb09420"}, + {file = "frozenlist-1.3.3-cp310-cp310-win32.whl", hash = "sha256:d5cd3ab21acbdb414bb6c31958d7b06b85eeb40f66463c264a9b343a4e238642"}, + {file = "frozenlist-1.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:b756072364347cb6aa5b60f9bc18e94b2f79632de3b0190253ad770c5df17db1"}, + {file = "frozenlist-1.3.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b4395e2f8d83fbe0c627b2b696acce67868793d7d9750e90e39592b3626691b7"}, + {file = "frozenlist-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14143ae966a6229350021384870458e4777d1eae4c28d1a7aa47f24d030e6678"}, + {file = "frozenlist-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5d8860749e813a6f65bad8285a0520607c9500caa23fea6ee407e63debcdbef6"}, + {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23d16d9f477bb55b6154654e0e74557040575d9d19fe78a161bd33d7d76808e8"}, + {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb82dbba47a8318e75f679690190c10a5e1f447fbf9df41cbc4c3afd726d88cb"}, + {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9309869032abb23d196cb4e4db574232abe8b8be1339026f489eeb34a4acfd91"}, + {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a97b4fe50b5890d36300820abd305694cb865ddb7885049587a5678215782a6b"}, + {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c188512b43542b1e91cadc3c6c915a82a5eb95929134faf7fd109f14f9892ce4"}, + {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:303e04d422e9b911a09ad499b0368dc551e8c3cd15293c99160c7f1f07b59a48"}, + {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0771aed7f596c7d73444c847a1c16288937ef988dc04fb9f7be4b2aa91db609d"}, + {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:66080ec69883597e4d026f2f71a231a1ee9887835902dbe6b6467d5a89216cf6"}, + {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:41fe21dc74ad3a779c3d73a2786bdf622ea81234bdd4faf90b8b03cad0c2c0b4"}, + {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f20380df709d91525e4bee04746ba612a4df0972c1b8f8e1e8af997e678c7b81"}, + {file = "frozenlist-1.3.3-cp311-cp311-win32.whl", hash = "sha256:f30f1928162e189091cf4d9da2eac617bfe78ef907a761614ff577ef4edfb3c8"}, + {file = "frozenlist-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:a6394d7dadd3cfe3f4b3b186e54d5d8504d44f2d58dcc89d693698e8b7132b32"}, + {file = "frozenlist-1.3.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8df3de3a9ab8325f94f646609a66cbeeede263910c5c0de0101079ad541af332"}, + {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0693c609e9742c66ba4870bcee1ad5ff35462d5ffec18710b4ac89337ff16e27"}, + {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd4210baef299717db0a600d7a3cac81d46ef0e007f88c9335db79f8979c0d3d"}, + {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:394c9c242113bfb4b9aa36e2b80a05ffa163a30691c7b5a29eba82e937895d5e"}, + {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6327eb8e419f7d9c38f333cde41b9ae348bec26d840927332f17e887a8dcb70d"}, + {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e24900aa13212e75e5b366cb9065e78bbf3893d4baab6052d1aca10d46d944c"}, + {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3843f84a6c465a36559161e6c59dce2f2ac10943040c2fd021cfb70d58c4ad56"}, + {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:84610c1502b2461255b4c9b7d5e9c48052601a8957cd0aea6ec7a7a1e1fb9420"}, + {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:c21b9aa40e08e4f63a2f92ff3748e6b6c84d717d033c7b3438dd3123ee18f70e"}, + {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:efce6ae830831ab6a22b9b4091d411698145cb9b8fc869e1397ccf4b4b6455cb"}, + {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:40de71985e9042ca00b7953c4f41eabc3dc514a2d1ff534027f091bc74416401"}, + {file = "frozenlist-1.3.3-cp37-cp37m-win32.whl", hash = "sha256:180c00c66bde6146a860cbb81b54ee0df350d2daf13ca85b275123bbf85de18a"}, + {file = "frozenlist-1.3.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9bbbcedd75acdfecf2159663b87f1bb5cfc80e7cd99f7ddd9d66eb98b14a8411"}, + {file = "frozenlist-1.3.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:034a5c08d36649591be1cbb10e09da9f531034acfe29275fc5454a3b101ce41a"}, + {file = "frozenlist-1.3.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba64dc2b3b7b158c6660d49cdb1d872d1d0bf4e42043ad8d5006099479a194e5"}, + {file = "frozenlist-1.3.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:47df36a9fe24054b950bbc2db630d508cca3aa27ed0566c0baf661225e52c18e"}, + {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:008a054b75d77c995ea26629ab3a0c0d7281341f2fa7e1e85fa6153ae29ae99c"}, + {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:841ea19b43d438a80b4de62ac6ab21cfe6827bb8a9dc62b896acc88eaf9cecba"}, + {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e235688f42b36be2b6b06fc37ac2126a73b75fb8d6bc66dd632aa35286238703"}, + {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca713d4af15bae6e5d79b15c10c8522859a9a89d3b361a50b817c98c2fb402a2"}, + {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ac5995f2b408017b0be26d4a1d7c61bce106ff3d9e3324374d66b5964325448"}, + {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a4ae8135b11652b08a8baf07631d3ebfe65a4c87909dbef5fa0cdde440444ee4"}, + {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4ea42116ceb6bb16dbb7d526e242cb6747b08b7710d9782aa3d6732bd8d27649"}, + {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:810860bb4bdce7557bc0febb84bbd88198b9dbc2022d8eebe5b3590b2ad6c842"}, + {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ee78feb9d293c323b59a6f2dd441b63339a30edf35abcb51187d2fc26e696d13"}, + {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0af2e7c87d35b38732e810befb9d797a99279cbb85374d42ea61c1e9d23094b3"}, + {file = "frozenlist-1.3.3-cp38-cp38-win32.whl", hash = "sha256:899c5e1928eec13fd6f6d8dc51be23f0d09c5281e40d9cf4273d188d9feeaf9b"}, + {file = "frozenlist-1.3.3-cp38-cp38-win_amd64.whl", hash = "sha256:7f44e24fa70f6fbc74aeec3e971f60a14dde85da364aa87f15d1be94ae75aeef"}, + {file = "frozenlist-1.3.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2b07ae0c1edaa0a36339ec6cce700f51b14a3fc6545fdd32930d2c83917332cf"}, + {file = "frozenlist-1.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ebb86518203e12e96af765ee89034a1dbb0c3c65052d1b0c19bbbd6af8a145e1"}, + {file = "frozenlist-1.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5cf820485f1b4c91e0417ea0afd41ce5cf5965011b3c22c400f6d144296ccbc0"}, + {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c11e43016b9024240212d2a65043b70ed8dfd3b52678a1271972702d990ac6d"}, + {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8fa3c6e3305aa1146b59a09b32b2e04074945ffcfb2f0931836d103a2c38f936"}, + {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:352bd4c8c72d508778cf05ab491f6ef36149f4d0cb3c56b1b4302852255d05d5"}, + {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65a5e4d3aa679610ac6e3569e865425b23b372277f89b5ef06cf2cdaf1ebf22b"}, + {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e2c1185858d7e10ff045c496bbf90ae752c28b365fef2c09cf0fa309291669"}, + {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f163d2fd041c630fed01bc48d28c3ed4a3b003c00acd396900e11ee5316b56bb"}, + {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:05cdb16d09a0832eedf770cb7bd1fe57d8cf4eaf5aced29c4e41e3f20b30a784"}, + {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:8bae29d60768bfa8fb92244b74502b18fae55a80eac13c88eb0b496d4268fd2d"}, + {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:eedab4c310c0299961ac285591acd53dc6723a1ebd90a57207c71f6e0c2153ab"}, + {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3bbdf44855ed8f0fbcd102ef05ec3012d6a4fd7c7562403f76ce6a52aeffb2b1"}, + {file = "frozenlist-1.3.3-cp39-cp39-win32.whl", hash = "sha256:efa568b885bca461f7c7b9e032655c0c143d305bf01c30caf6db2854a4532b38"}, + {file = "frozenlist-1.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:cfe33efc9cb900a4c46f91a5ceba26d6df370ffddd9ca386eb1d4f0ad97b9ea9"}, + {file = "frozenlist-1.3.3.tar.gz", hash = "sha256:58bcc55721e8a90b88332d6cd441261ebb22342e238296bb330968952fbb3a6a"}, +] + +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.7" +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[[package]] +name = "httptools" +version = "0.5.0" +description = "A collection of framework independent HTTP protocol utils." +optional = false +python-versions = ">=3.5.0" +files = [ + {file = "httptools-0.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8f470c79061599a126d74385623ff4744c4e0f4a0997a353a44923c0b561ee51"}, + {file = "httptools-0.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e90491a4d77d0cb82e0e7a9cb35d86284c677402e4ce7ba6b448ccc7325c5421"}, + {file = "httptools-0.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1d2357f791b12d86faced7b5736dea9ef4f5ecdc6c3f253e445ee82da579449"}, + {file = "httptools-0.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f90cd6fd97c9a1b7fe9215e60c3bd97336742a0857f00a4cb31547bc22560c2"}, + {file = "httptools-0.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5230a99e724a1bdbbf236a1b58d6e8504b912b0552721c7c6b8570925ee0ccde"}, + {file = "httptools-0.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3a47a34f6015dd52c9eb629c0f5a8a5193e47bf2a12d9a3194d231eaf1bc451a"}, + {file = "httptools-0.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:24bb4bb8ac3882f90aa95403a1cb48465de877e2d5298ad6ddcfdebec060787d"}, + {file = "httptools-0.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e67d4f8734f8054d2c4858570cc4b233bf753f56e85217de4dfb2495904cf02e"}, + {file = "httptools-0.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7e5eefc58d20e4c2da82c78d91b2906f1a947ef42bd668db05f4ab4201a99f49"}, + {file = "httptools-0.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0297822cea9f90a38df29f48e40b42ac3d48a28637368f3ec6d15eebefd182f9"}, + {file = "httptools-0.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:557be7fbf2bfa4a2ec65192c254e151684545ebab45eca5d50477d562c40f986"}, + {file = "httptools-0.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:54465401dbbec9a6a42cf737627fb0f014d50dc7365a6b6cd57753f151a86ff0"}, + {file = "httptools-0.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4d9ebac23d2de960726ce45f49d70eb5466725c0087a078866043dad115f850f"}, + {file = "httptools-0.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:e8a34e4c0ab7b1ca17b8763613783e2458e77938092c18ac919420ab8655c8c1"}, + {file = "httptools-0.5.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f659d7a48401158c59933904040085c200b4be631cb5f23a7d561fbae593ec1f"}, + {file = "httptools-0.5.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef1616b3ba965cd68e6f759eeb5d34fbf596a79e84215eeceebf34ba3f61fdc7"}, + {file = "httptools-0.5.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3625a55886257755cb15194efbf209584754e31d336e09e2ffe0685a76cb4b60"}, + {file = "httptools-0.5.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:72ad589ba5e4a87e1d404cc1cb1b5780bfcb16e2aec957b88ce15fe879cc08ca"}, + {file = "httptools-0.5.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:850fec36c48df5a790aa735417dca8ce7d4b48d59b3ebd6f83e88a8125cde324"}, + {file = "httptools-0.5.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f222e1e9d3f13b68ff8a835574eda02e67277d51631d69d7cf7f8e07df678c86"}, + {file = "httptools-0.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3cb8acf8f951363b617a8420768a9f249099b92e703c052f9a51b66342eea89b"}, + {file = "httptools-0.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:550059885dc9c19a072ca6d6735739d879be3b5959ec218ba3e013fd2255a11b"}, + {file = "httptools-0.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a04fe458a4597aa559b79c7f48fe3dceabef0f69f562daf5c5e926b153817281"}, + {file = "httptools-0.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d0c1044bce274ec6711f0770fd2d5544fe392591d204c68328e60a46f88843b"}, + {file = "httptools-0.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c6eeefd4435055a8ebb6c5cc36111b8591c192c56a95b45fe2af22d9881eee25"}, + {file = "httptools-0.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:5b65be160adcd9de7a7e6413a4966665756e263f0d5ddeffde277ffeee0576a5"}, + {file = "httptools-0.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fe9c766a0c35b7e3d6b6939393c8dfdd5da3ac5dec7f971ec9134f284c6c36d6"}, + {file = "httptools-0.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:85b392aba273566c3d5596a0a490978c085b79700814fb22bfd537d381dd230c"}, + {file = "httptools-0.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5e3088f4ed33947e16fd865b8200f9cfae1144f41b64a8cf19b599508e096bc"}, + {file = "httptools-0.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c2a56b6aad7cc8f5551d8e04ff5a319d203f9d870398b94702300de50190f63"}, + {file = "httptools-0.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9b571b281a19762adb3f48a7731f6842f920fa71108aff9be49888320ac3e24d"}, + {file = "httptools-0.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa47ffcf70ba6f7848349b8a6f9b481ee0f7637931d91a9860a1838bfc586901"}, + {file = "httptools-0.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:bede7ee075e54b9a5bde695b4fc8f569f30185891796b2e4e09e2226801d09bd"}, + {file = "httptools-0.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:64eba6f168803a7469866a9c9b5263a7463fa8b7a25b35e547492aa7322036b6"}, + {file = "httptools-0.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4b098e4bb1174096a93f48f6193e7d9aa7071506a5877da09a783509ca5fff42"}, + {file = "httptools-0.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9423a2de923820c7e82e18980b937893f4aa8251c43684fa1772e341f6e06887"}, + {file = "httptools-0.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca1b7becf7d9d3ccdbb2f038f665c0f4857e08e1d8481cbcc1a86a0afcfb62b2"}, + {file = "httptools-0.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:50d4613025f15f4b11f1c54bbed4761c0020f7f921b95143ad6d58c151198142"}, + {file = "httptools-0.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8ffce9d81c825ac1deaa13bc9694c0562e2840a48ba21cfc9f3b4c922c16f372"}, + {file = "httptools-0.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:1af91b3650ce518d226466f30bbba5b6376dbd3ddb1b2be8b0658c6799dd450b"}, + {file = "httptools-0.5.0.tar.gz", hash = "sha256:295874861c173f9101960bba332429bb77ed4dcd8cdf5cee9922eb00e4f6bc09"}, +] + +[package.extras] +test = ["Cython (>=0.29.24,<0.30.0)"] + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "itk" +version = "5.3.0" +description = "ITK is an open-source toolkit for multidimensional image analysis" +optional = false +python-versions = "*" +files = [ + {file = "itk-5.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f92ec860173c82eb458764b4b5b771783b690c3aa3a01d15c6f3d008fc2bb493"}, + {file = "itk-5.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:90b21c6f53027302bf74b411a062a4161d7a3d92ebbdac99857d7c23d55a2034"}, + {file = "itk-5.3.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:22005a57bd777246c57590d89a6bb7dc004855e4f656a66eed02d395ad13ad6a"}, + {file = "itk-5.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:da04af4ab07efe3e8235dbc8d72abfd8255888bb17d97088679854abc931e56a"}, + {file = "itk-5.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:272708ee5ed5d09a519b2e98ac9c130f3146630257506ea440c83501c16f9580"}, + {file = "itk-5.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:09f34acac79a5f1ddb3456a74cbe19d04f897ce62450413feb41434e885ce502"}, + {file = "itk-5.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9dcfd9721ff6022e91eb98dc4004d437de2912dfd50d707d1ee72b89c334a3d4"}, + {file = "itk-5.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2ec92b1afbc1375477b80f9ec09aa4e9b005d0a439a9242b1371e00e78471ceb"}, + {file = "itk-5.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fb186a97fe8d80f40d0058fa630be87b0e81b403dea3cfabaa8e809882fe2822"}, + {file = "itk-5.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:42dff624b8e29abe0ab5341ea5c150f4fda99918d1654f06fc722d733eeaad42"}, + {file = "itk-5.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:ba8361a8ed1c5462e690ee893f624c0babb7a1072a15609c26790eea717e3f77"}, + {file = "itk-5.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:8731e867e23a11848dd6e6e2d0a061045bdd94b1a02e38be509b41eaf69cfba7"}, + {file = "itk-5.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:493e28a3c9f38502f82613fa6ab9855fb19bff671095c287100a441830a921d0"}, + {file = "itk-5.3.0-cp37-cp37m-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:73225da2c88884906e701d614a229f81f79d3829179b47bbfd9c251aed652b03"}, + {file = "itk-5.3.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:814b1f2ecf8d3befa5d55ce901b2d2357e0999272dbe0cc3c13afb2db0757c8c"}, + {file = "itk-5.3.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:265c8b28469164a45fd9d94c211b2ed017acc7cda7a9e74bbb20b38c49c1af61"}, + {file = "itk-5.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3046c84bd3cdb9a31b284d153a6e24ee5e1ef9b47dbc72e68d1805fc011ad127"}, + {file = "itk-5.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1fbcde6f6612b13d2934722707fd7194b1d5900a655efa191dfc130bbb94df09"}, + {file = "itk-5.3.0-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:049be9c76d66121064e4f8ddbc4793e24d086d5d5574aa38d9a3cd6e0a4526d5"}, + {file = "itk-5.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:1577cc952a6dfd6c3e39745827d46e06b0933e77fb280fb7214a367a3d787420"}, + {file = "itk-5.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:d83dc2b0f5d673226ef6eacac012d1da6dd36c6126f2b3cffc7ed62231c29bf2"}, + {file = "itk-5.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:5804692e0b0c188104efcc33b92a16f494ddb9752554c403b64ca8e2c29c5395"}, + {file = "itk-5.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:155581581929dfe834af6c6233a8c83e2ca2b1f52d6c7b2c81f04dc249aab1a5"}, + {file = "itk-5.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e25803a186a71515732e5d05291e4a33e49fae617a6b869ba8717699aa6109a0"}, + {file = "itk-5.3.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:41990d514a32bbefd9e1ce897cb7689d1ce568c140a112bce18213570612a433"}, + {file = "itk-5.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:3376b859da3c926f74fc616cbf42e813c5998b210c059cb7f6a2fd665369aacd"}, + {file = "itk-5.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:bcc4449f2df35224cbc26472475d2afeb8a92886a81db950b2305f911bc2a38c"}, + {file = "itk-5.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:f376693f2a5fcc799047012b21509b73d0d41055f4cd5a92521d2c1a3e41a5ac"}, +] + +[package.dependencies] +itk-core = "5.3.0" +itk-filtering = "5.3.0" +itk-io = "5.3.0" +itk-numerics = "5.3.0" +itk-registration = "5.3.0" +itk-segmentation = "5.3.0" +numpy = "*" + +[[package]] +name = "itk-core" +version = "5.3.0" +description = "ITK is an open-source toolkit for multidimensional image analysis" +optional = false +python-versions = "*" +files = [ + {file = "itk_core-5.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:15fd888c5338a0d630ae5ceaa90d0244d4e2040baf08279bb6db284f2774b77c"}, + {file = "itk_core-5.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4b8bb2df8697c3706b12adf4d79dea0be3dab73b8bd200c14a970f1ea6e52bb7"}, + {file = "itk_core-5.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fa2f4b283af6c67707c4ed2f3b064a2126a60ec7658917fa33d5f2f21e1698d"}, + {file = "itk_core-5.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:c8f26fae88f6e0f0eac6f6800c5e6ec62879e2597a861bed36454954db5e377e"}, + {file = "itk_core-5.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:f51cffaeddb86aa650538955a8aa1a4f6cb8a0f5296b6977412b0bdea10fae99"}, + {file = "itk_core-5.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:b7cd730513bbd502e660855f45a9657953eed8c88c666f39a3b622203d14720b"}, + {file = "itk_core-5.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:530ffb9492004cb2eee8b9702e728e62373cf205e01b3612a811cd6b745b1251"}, + {file = "itk_core-5.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5be7b5ebaaf29cb4a1763b09ec0dbbcc6f84580b2dc234f98167cd2e3f7ea97b"}, + {file = "itk_core-5.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a43f1e625409cedf7c9046bfaba0ce02ee86c86263b2615b9e0444acb38a1d8"}, + {file = "itk_core-5.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:2068a7456d22a31ef689099f5a342e591c7a426673c8e84ecaadf92456ddea07"}, + {file = "itk_core-5.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:7cc25d0923deb9219299cd8c5a7e5bc1d8ed6731497a06cf121409b71c72ae59"}, + {file = "itk_core-5.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:cd22229f6945e539141e8829f866bdf0c21567e8f8b9b7621a27032c5f2cfbf8"}, + {file = "itk_core-5.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ec82117e4a9d57705aabd6ed94c3fd73707c090fdcff9c7dfbdb42b1c4ba66bd"}, + {file = "itk_core-5.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9c77357c383236db6d24de312c0d13b4908a347e3f1eb27ab65995e25b54236"}, + {file = "itk_core-5.3.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:03470278569da50a6cc8adf8a8b89a34b01468009f1291efb78e8dcb694b66f6"}, + {file = "itk_core-5.3.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:1cf17bc60a7caa648bbe8643f53187ee4cce2e24eef20ad0904a0dd71b718582"}, + {file = "itk_core-5.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1b7cf723ca9c01ab952ba956bcbb8b368984e6585268c5c0c7d431f28def8190"}, + {file = "itk_core-5.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:dabcce38cf30daa4c21dd500f96f4be250bde4718fa3c28d1c60094a12fa68f4"}, + {file = "itk_core-5.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44b55a36e8746e052ef7638458cb088eb0269c1023d1fe0a575580840d25334e"}, + {file = "itk_core-5.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:34493c3f59ae9ad47202ff41ceea8a81acd36c3702e6b2ce42c56be94a1892fa"}, + {file = "itk_core-5.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:10b2236bced382aeef65aed4f36e9fb39c1bed0ccc2d163417700442a4fe355d"}, + {file = "itk_core-5.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:a01ce1e2456cdb432fc88eab5a0b0585a77d1e73758bfae2d3f0d6fdd7ecb222"}, + {file = "itk_core-5.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:48313b935f9cdee3ff9bed24d7e604a97e6971645c7c346d8a9b03d10d53ba28"}, + {file = "itk_core-5.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fbdf8fb96e929cd2298d1f3c61398265620050b140410516a9283a1894211564"}, + {file = "itk_core-5.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e98aea3787d3bc1f49cf5a4988a1ec4a54232830ef42e50f91234ec1485365a9"}, + {file = "itk_core-5.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:7f913621eaf319f670332de39143f0cd7e27266d0172a1d225ed784bec4587fa"}, + {file = "itk_core-5.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:c3825d3042c208a91d46884b7af80af00c86b83cdf099833d4e61961c9989aa0"}, + {file = "itk_core-5.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:bafc7d3472efc4e9d164d979bdcab88c78da8d37f10cc422285e3b219857a98e"}, +] + +[package.dependencies] +numpy = "*" + +[[package]] +name = "itk-filtering" +version = "5.3.0" +description = "ITK is an open-source toolkit for multidimensional image analysis" +optional = false +python-versions = "*" +files = [ + {file = "itk_filtering-5.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:235add39e26b69c5328fbd130c0767ffc93a2bdef7d7dd95e5d072d9e9596c36"}, + {file = "itk_filtering-5.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f52cdc3ab57f038d05ece473036b422f24d6c3cf1fb5bd29d16485d80ece088f"}, + {file = "itk_filtering-5.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8b52dd48f2f3b9c0cd0430cd6d406a78403432ca4d6780092149e1c68484344"}, + {file = "itk_filtering-5.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:4c3dea1d3e2d2adb53ecaf85a182ba1aa84ff6ed607c1a41516f78c520494250"}, + {file = "itk_filtering-5.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a10f3797e8da19225d4535a3d6241df4761bbd2e662f414aeb820a5b7e9759a2"}, + {file = "itk_filtering-5.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:b447d6b04b5e8f837d6d126f598ecfddc6a053c994a086b5fd15f3b51b4e9099"}, + {file = "itk_filtering-5.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:70c30da55be721439d24c1be9696094e3637d853f7f9ba7265ff0faa92d9976a"}, + {file = "itk_filtering-5.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:221e767f921a1b2f2b768a0aef198b9bd059ae698cf9a65ee5c643df4dea48ad"}, + {file = "itk_filtering-5.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94ecd92c2d6ecf3ee248c579d824e8f19d73651ecf795b60f7fe12a6e8728108"}, + {file = "itk_filtering-5.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:35b21321f4bdfbbc799e4b2d5bbdcc435c02dcd03ea8cf064108d834d1b5c3a0"}, + {file = "itk_filtering-5.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:88810f999017ac032aeefa1bc0ee884c25089a5975eda7deeb68932568677a6b"}, + {file = "itk_filtering-5.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:fe0679a8044bf7336e86c9726620ff959a2b6effcf807813f6139030d3bf152c"}, + {file = "itk_filtering-5.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:56b79cfa4031884d77fdb64b9cd6801c9189f1edd94cc98accdcc06c8a1ff7e9"}, + {file = "itk_filtering-5.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:faa5f09ee154c14de96a802e84e9c12f383118f8d445fc7186b3ffccc03284a6"}, + {file = "itk_filtering-5.3.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:e622b9501a370bd1dea2e74c3f2562160077c18914bd67e02bb92edb40717be0"}, + {file = "itk_filtering-5.3.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:b027846f9204b0282a578aed20ae2b01b08ff55bf98ccf55f603f6d99a6415f7"}, + {file = "itk_filtering-5.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1d14f729cc393c44c39290b19446ca804f2bc2d4353413aebd6575c9c3190c79"}, + {file = "itk_filtering-5.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0a2da4187a43db7c545b82840d7b4fe57f00ad563b5e86de1b182d385f514845"}, + {file = "itk_filtering-5.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9881fd2fbab8e208ddbcd3c2634abe664db5c471bfd9999317486ba54db72b4f"}, + {file = "itk_filtering-5.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:d07c0fa5cce515c7d2f4a3699548fbfc9ac7f9c445d797888f0842fd7ee68be6"}, + {file = "itk_filtering-5.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:cf39d183dbf1120183513c3930cce8d515e4cb4d98b9ad41f92c89de4a14e167"}, + {file = "itk_filtering-5.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b65ca1dcf827bec3ede681e14b0f0b51e457e5ee59946b8834dd15c638f3dd53"}, + {file = "itk_filtering-5.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2f560b20dbc716da26d99ffd713b96bc19f3915590a9b785a9fd91a6e20cc6f7"}, + {file = "itk_filtering-5.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:953b4f5102e6c7e185d8b758b061a703cb6555791a3cb672b7e05aefa39be4c6"}, + {file = "itk_filtering-5.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aac486432bba982aea18243cffc28d97e035b40afba61279a17454cf04c87e37"}, + {file = "itk_filtering-5.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:77f0df5ac6f5c027c31f6ef14ce6b34ae0b0f90ea3703a580e0ce78505bcfb78"}, + {file = "itk_filtering-5.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:c60f83f6504fab0ba42042bc994c6a6bb7198025dcadb98f6a027e29a0445fab"}, + {file = "itk_filtering-5.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:10ee7530b81bde6caef722f099ff202329ff85e3e9b8362e364d3628a7dcbd95"}, +] + +[package.dependencies] +itk-numerics = "5.3.0" + +[[package]] +name = "itk-io" +version = "5.3.0" +description = "ITK is an open-source toolkit for multidimensional image analysis" +optional = false +python-versions = "*" +files = [ + {file = "itk_io-5.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc3fc771ccfc136ce3aed9e80d29c90e83128c150087309a59b9e0a936bd699e"}, + {file = "itk_io-5.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bc279b0f7c87002a9e7ac9d7fbd8fcbf3ecc1378b15b738086bb3fb47c23e2dd"}, + {file = "itk_io-5.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd338c3097237fbdda0e06ee1d523c13392cc3436a4263658df55d3eae32726a"}, + {file = "itk_io-5.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:4aa7e434097a9b3c0a07e18e545652ac15aa66744d001c0a527aee548ad0feee"}, + {file = "itk_io-5.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:fdb812c744ac98b437609f490d163fa69800476ca0a9d811f62307973962aa7c"}, + {file = "itk_io-5.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:1176e67f459cfc053fe1add8c1a13865743459b2c1436892590e3be14a5db303"}, + {file = "itk_io-5.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:44f857cbb44b0f79cbc88f74417f58412a31246e6cd563d8082fe313dd2afa7b"}, + {file = "itk_io-5.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e95c55009cabcce62a0bd5c5fa48aaa2a3d7e7de8926d8da355b02615f83c7e1"}, + {file = "itk_io-5.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82039102e08ab0a9a8cb8b6968130bc2fc26f10785e1f089880f176a285f8b45"}, + {file = "itk_io-5.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:ec820b467f7ca8af10a69fce84bbf7fad41d439c23796cd0547d69b72db25847"}, + {file = "itk_io-5.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8f71c21249c88fecf68b6958e6987d76de802a556cad4c64a0d2f3aea3ad4aa2"}, + {file = "itk_io-5.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:5808a5d9f76780dd6408a6fb12b7c4e344bb147b1fc6645865da195aac134bea"}, + {file = "itk_io-5.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:32445a865a33a0500650826957031493e8c5ebcf54c8b538e104f21e7d345ab0"}, + {file = "itk_io-5.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5031643e7b34e999c635e413b50e4174a5b3e41492b5dca73042db20014fc5fe"}, + {file = "itk_io-5.3.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:c90d8e6d0097515fc6ef320696be6ecefc476b4a2dca1af16700d95f46bebad1"}, + {file = "itk_io-5.3.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:c935079d2e3e039a19ce5053a3020b41d2d6ab8adc6bce20aed2a8741eca8da5"}, + {file = "itk_io-5.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:5e2966a229fcd684051bc7f186d9d74171aa6851f1244926d05f73171e2d15af"}, + {file = "itk_io-5.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e3a0e4ac2c2006bf60e31f8c77c75c35d2fb20a2b09933ef8bd09fc4c73c6386"}, + {file = "itk_io-5.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aee4fccb9aa5ada4190d9dc839f64b7d5e66f4fad4b35d9c33779833aa8ea5ad"}, + {file = "itk_io-5.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:03d562a710053be4484d2e856eb527365e74699618aa102e9a3620817f1acc8e"}, + {file = "itk_io-5.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:39651d78bbba831da6e84ab865cea81a16c0f5f80a25aebe07f98e32f7cab361"}, + {file = "itk_io-5.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:bc15b379ccba1c7d0af1559536ce943d15725e01fa571ccbca319ce56dafaaee"}, + {file = "itk_io-5.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:888c1a587e7f11796d9a680a23a713929928c22daf4af216199ced181f34d8e7"}, + {file = "itk_io-5.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5ca81b07d5358f9ad32b56571e09cd7167f71ebd66240bd581fd9504db8594dd"}, + {file = "itk_io-5.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28baef4b92be1452f5a8a6bba8a006f0c127468ae89b127352a9e7441d640767"}, + {file = "itk_io-5.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:5284b6e59c6b63375cf1e85e574b33a4ecf235ebe7b6ee2ab13846bbdf128a1a"}, + {file = "itk_io-5.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:5a2f429734593f0afb624004445d59dda0e3500ebe6e5e86f8f91c5f01e60e4a"}, + {file = "itk_io-5.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:b82171c335b0f973274f65faaa40b91e80006a464a3427c460f4158fbb2558a4"}, +] + +[package.dependencies] +itk-core = "5.3.0" + +[[package]] +name = "itk-numerics" +version = "5.3.0" +description = "ITK is an open-source toolkit for multidimensional image analysis" +optional = false +python-versions = "*" +files = [ + {file = "itk_numerics-5.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:776a75b0bb67dbf7be6a1281295d942d3835d7fba7e19d13d22de76048712b19"}, + {file = "itk_numerics-5.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fbbff8a4d31d01e33a049bb36fcc64f9919a2549a1ef2ad130c88c33821e434"}, + {file = "itk_numerics-5.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2042fc1e16923c875d46181c2ac4ddbb4cbd616019e932e85feaf4be2692800"}, + {file = "itk_numerics-5.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:56a9433bc67b2e9485bc96c601022310c6352b5927dc52cb21ae188905dbfaed"}, + {file = "itk_numerics-5.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:c857b9ed98c7f88639a2fa6de1872e5ad109804db255e7650c68347a194a7e4f"}, + {file = "itk_numerics-5.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:4d1edd278d155857efe85cca2bd02ab5053c71eeeec604eb8394e48edaa7c04c"}, + {file = "itk_numerics-5.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cf35bbfd561df4901d122324c8cb12f2395bbd705f3e6b1bfc84ebe9e00cef12"}, + {file = "itk_numerics-5.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d69b23e429dd854653ff10bfb9853676305fb8f1e904c8ac6083dbaa7ab2f47c"}, + {file = "itk_numerics-5.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72da89f1c2d0d076a5accd04c07da0a5e45a1091b90da1b178c44dced7a2661b"}, + {file = "itk_numerics-5.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:0225964ec26bbe5d3687a2238ae4f2069dc844c4d6b46723b7937b26402b2c1a"}, + {file = "itk_numerics-5.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:daf06148d7392c5a4d8a31c5e3a3267aeb684fc3aecce5a22c72afaf48f2c4af"}, + {file = "itk_numerics-5.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:ec683a55a6cebb705407c438b686a885b4529414f12d817510cecb898384be51"}, + {file = "itk_numerics-5.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c7253f4c3eea70162154c5070796633da2f75d1b693f5a239884dccc84643f29"}, + {file = "itk_numerics-5.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1223cca8014265edbf1a848f9a5b497a18b1904e3791a24ff8a6c88abe01dd61"}, + {file = "itk_numerics-5.3.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:313ccd4aee3cf797c7e2309ee252301dd706638266e4fb1c707357479aef5080"}, + {file = "itk_numerics-5.3.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:a246d445a6bd5163beada710b1bf73d89d7d6ac7a9072b9f777f5e772f5d7533"}, + {file = "itk_numerics-5.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:10fb0ba19e2a26ff10d95f61eefc2718ae959c6c3615e23cfc7d7dd2bc20793e"}, + {file = "itk_numerics-5.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:55b11cc7a8756994125cca9b6ddd46706d6cdae74ea91df6151edb26e366179e"}, + {file = "itk_numerics-5.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1af9fc0f8d32cf136b2be82feb5b59c4b1d271ea391b17987a79ed5ce1dce25"}, + {file = "itk_numerics-5.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:0aaca8ba6630a9475f232929afcb5ebd6925333bfb311d9bc5a4d9a351b9a9bb"}, + {file = "itk_numerics-5.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:1422f1f92f1b392927cc7aa0c409a153356728688d221b6d422f2d7f5de00ba0"}, + {file = "itk_numerics-5.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:5e04b193b79a0424181e3aad6a35394f16892ce7be692399fc56825055f3422f"}, + {file = "itk_numerics-5.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c35bf9d59ed043d225835d7c01f37366581a4055024742de64124f095e778b62"}, + {file = "itk_numerics-5.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a97de21e0daf2838c9fbbac11b35580510df340bd4d4c83127c2de89908f5b4e"}, + {file = "itk_numerics-5.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da05b2bff8528c92c8b1c795de718e261f5f4fec9a9499fa1789cf99b94e2a2e"}, + {file = "itk_numerics-5.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:026a6f791a73685db507ec810ae0286817e782bf8d292f150aa7c04639db4212"}, + {file = "itk_numerics-5.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:c3a7d36b424dc88ff7b576b79687090187c514423b9418adf1ac6099e1cd70fe"}, + {file = "itk_numerics-5.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:74670ff4030aeb05f8c928e67d28da6991b86361d4777f24d5e907c851ed4ad1"}, +] + +[package.dependencies] +itk-core = "5.3.0" + +[[package]] +name = "itk-registration" +version = "5.3.0" +description = "ITK is an open-source toolkit for multidimensional image analysis" +optional = false +python-versions = "*" +files = [ + {file = "itk_registration-5.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80a9c766d2fb6589e32f735e564be5d74fd2d08bc03338645f4520028d63388a"}, + {file = "itk_registration-5.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d9afc65d6ce05cfb7f10d4ea26544988f2fc29839ecc5daee8c5eaf9ab2e7b86"}, + {file = "itk_registration-5.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d90d7d87790df631a9297e7c51b819a559e846e9b18270300666430734173af"}, + {file = "itk_registration-5.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:81e8e3e9ac56fae16d6dfe9fbbdfc9e8f4e787780abd0550ba3da2fd194e0872"}, + {file = "itk_registration-5.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:46407cb7c1d828f3a093337f026c4a7e55cf58894b4f34ab59a8e800a02c63a8"}, + {file = "itk_registration-5.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:07c75407ef3de77e832d8e7dcb1f6bd39d8ff6873cffe49684fe55a9ce68060c"}, + {file = "itk_registration-5.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a9480adf86b5fcdf7e548adb7a7401ccf85bbb34bfaf5e6bd819e037bcfa910"}, + {file = "itk_registration-5.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bfa2dcc80767203aaca5140cfa57599a683186b0681c9a52e307d15a3cb7c89a"}, + {file = "itk_registration-5.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfae5f2e8d29bf7ca275ea277601e7c47fcdc973d5e1cae7b8da4ab2b72b1c6e"}, + {file = "itk_registration-5.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:2829c7213fe02f0e9cbd6c6a5a3835023427811e52d5ef514453ce894210705e"}, + {file = "itk_registration-5.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:1583a59424f103f6b7c447689706157f64a0adcbfd1336f77ac078bda862987a"}, + {file = "itk_registration-5.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:c63bfd32997249678502de7de53c07f5122ac4e0315a5e6262720c4fd63a9d2b"}, + {file = "itk_registration-5.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1d835591dcb9e3153ddfd67da746ce60f632d895d59f0e1a1f00aa0941238744"}, + {file = "itk_registration-5.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74b3739240796abb101d443bf4c2fbdb4173b5451b642abcfa13d7b1a8f9e02b"}, + {file = "itk_registration-5.3.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:de857071bbd0b7358928c1834424a4188ec8390e5bcc6429c46e906c20e70448"}, + {file = "itk_registration-5.3.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:5f791890c7a390960fec3b812815669c2dca936aad2a4a0087474efb42ddbad9"}, + {file = "itk_registration-5.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:536f61b11cf05c7f470817ece21a6b114d0194e91908384289927a2ac2523c85"}, + {file = "itk_registration-5.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:705427675fd0d5f263335a448ccecd3ca66d8ad41399a53f37ab42f6b4195f7c"}, + {file = "itk_registration-5.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cb947518db9aca873f13eb643a5d40a048f6bf02554d10df4ede9d042213765"}, + {file = "itk_registration-5.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:68645a48b8c89ccb1053846f1c6158d8583bfece1c118f05742850dc413205f9"}, + {file = "itk_registration-5.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:7bda071b420d31e3520dcc8a778ffc84a387d5cbeafabb29695f1294028421a1"}, + {file = "itk_registration-5.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:ee4637f5cff9e321b7ffde0a0eb31cdc6d482491de5cdc26a392f0ca20abc5ce"}, + {file = "itk_registration-5.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:33d7ec9ba26525957cba399ba0e49c10013cc6d3560ecc4511d91b5a299aa176"}, + {file = "itk_registration-5.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:19acf62f7a03bc72943eb7baf6a3f2c3b30a576a8a1bda7c3f56917280c7a640"}, + {file = "itk_registration-5.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00d5ee90f77820d65fb915acce2d448fabecaff431dd0a9bf07199d59515aee4"}, + {file = "itk_registration-5.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:b73c535a0aa70b1663fdb1ffb53608d393070705953c1286360848cd08a2a1e3"}, + {file = "itk_registration-5.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:372f956a6dbb3caf8eafe24a888db3535938257371290268a98e2f0cfe6c8b25"}, + {file = "itk_registration-5.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:30068a6a9c685d95c85b4c962479798ee5affee24a7145e3f0b3efef3a2435cd"}, +] + +[package.dependencies] +itk-filtering = "5.3.0" + +[[package]] +name = "itk-segmentation" +version = "5.3.0" +description = "ITK is an open-source toolkit for multidimensional image analysis" +optional = false +python-versions = "*" +files = [ + {file = "itk_segmentation-5.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:874f420ccce41a1123d2340b3712501cf50c4872eecf67e92fb4783da07be823"}, + {file = "itk_segmentation-5.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:85f763055a33cd1c9e8f7142d5565c46ff4e72e884552cbca9dc74a3411197d6"}, + {file = "itk_segmentation-5.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ea86a9acdebac7acc97560f20ca1cb14fbb64fc13a7e08936484a8f9287088a"}, + {file = "itk_segmentation-5.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6bfac076c01d3b1a7c57f036849d5b0e7279803f6bc49321fb0a5aaae153d49d"}, + {file = "itk_segmentation-5.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:01d68d75fa1911f0424329a32f08333bf73a12538f44af30a431bb00ca4697f5"}, + {file = "itk_segmentation-5.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:c95c133e4d92904697fb237c6f808c56bba0a5eb84fa28df4ed8b89ede0db08a"}, + {file = "itk_segmentation-5.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5aebbb20f05b9d771561e087c069108e353e69b8d761ad9c36f2f228bdcccfb8"}, + {file = "itk_segmentation-5.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93b9ba19b28e29c56149c5ce61f8139acd3bea6fc91ee578262945f27d7a187b"}, + {file = "itk_segmentation-5.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b964e0c58dacfe0d4d19d90743c5c3c8b8de776d2691384967c2c04c9764875e"}, + {file = "itk_segmentation-5.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:ce066460f4c7308f67f2426bc490ee57640dfee59ecd5e94c13025a6fdc8734d"}, + {file = "itk_segmentation-5.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:fd28a807e7b8bce9eef9beb67fa731490b62a91ee26b407a9498e9d0eab87e6c"}, + {file = "itk_segmentation-5.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:a6008e88c13501cc26d6b89e69ecfb1d054613162348849065c7b3b54347580a"}, + {file = "itk_segmentation-5.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7d57429ad8fccc01f18835f2b6306303670021142e27c13ec4e08e208acf419c"}, + {file = "itk_segmentation-5.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c20b58a62c4b5eb3334f9b6f38ccaf9175d006dc53dcfddffc04f837fd40ca30"}, + {file = "itk_segmentation-5.3.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:1998ae56bfb55765efd47223355cef856e707070696626c745f228b2a6191c7f"}, + {file = "itk_segmentation-5.3.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:b97562afc7c5bbe17e9b7be917609373921f3b57e49874e13b20b8b52d2a5ba8"}, + {file = "itk_segmentation-5.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:0d15c2fa2279983f46bc038e458e6a9cd7b92b983477d310e8841dc45c2171e2"}, + {file = "itk_segmentation-5.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d0275a738ae085b844032538e8b6c41517453892ed525545a1a80999c054a250"}, + {file = "itk_segmentation-5.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:817ed241b8d72169c15cc202e5421c097b939ba7b5d0cacfb1c2aa25e2cc708e"}, + {file = "itk_segmentation-5.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:306c7490616a3ea20ba874b73d2459923def6b6027613fde57855fc42a213aa5"}, + {file = "itk_segmentation-5.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:216e6fe75ecebd70a0155d4a85d29124fc0a59dc85a96530adf8456abe272d56"}, + {file = "itk_segmentation-5.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:a075407de9f20fd86c9aceb4a9e9b2e0be9ca605d24a1d62daccd1e64066dd5a"}, + {file = "itk_segmentation-5.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c90fa141ff5f52e62ac4f9419d81c2039216178e0cfcd634fc7bb7fe0974a5c5"}, + {file = "itk_segmentation-5.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:44005395b85c9b6f5bdcf1d41ef8acdbc0ee6f2c6e2aa3f4d57c623b766620b1"}, + {file = "itk_segmentation-5.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:069714a3e582545b0b11e1e5edd2d0ad540a83d6d41cd2cea4cfa12fcbe10b67"}, + {file = "itk_segmentation-5.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:d5b245bacb75c16ec510418f73897e9935b664ddd1dac4d6baf8c1e546fca7c4"}, + {file = "itk_segmentation-5.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:d19873f55feef8d763fcc2579dd5486436de1790d1fb3ee6c9a988e767e985c0"}, + {file = "itk_segmentation-5.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:37b570bd601a73f458010b935cd50b468940c397ad3386481f93b8528bcf72bf"}, +] + +[package.dependencies] +itk-filtering = "5.3.0" + +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +optional = false +python-versions = ">=3.6" +files = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] + +[[package]] +name = "multidict" +version = "6.0.4" +description = "multidict implementation" +optional = false +python-versions = ">=3.7" +files = [ + {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"}, + {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"}, + {file = "multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5"}, + {file = "multidict-6.0.4-cp310-cp310-win32.whl", hash = "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8"}, + {file = "multidict-6.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461"}, + {file = "multidict-6.0.4-cp311-cp311-win32.whl", hash = "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636"}, + {file = "multidict-6.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0"}, + {file = "multidict-6.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d"}, + {file = "multidict-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775"}, + {file = "multidict-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1"}, + {file = "multidict-6.0.4-cp38-cp38-win32.whl", hash = "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779"}, + {file = "multidict-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95"}, + {file = "multidict-6.0.4-cp39-cp39-win32.whl", hash = "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313"}, + {file = "multidict-6.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2"}, + {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "numpy" +version = "1.24.3" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "numpy-1.24.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3c1104d3c036fb81ab923f507536daedc718d0ad5a8707c6061cdfd6d184e570"}, + {file = "numpy-1.24.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:202de8f38fc4a45a3eea4b63e2f376e5f2dc64ef0fa692838e31a808520efaf7"}, + {file = "numpy-1.24.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8535303847b89aa6b0f00aa1dc62867b5a32923e4d1681a35b5eef2d9591a463"}, + {file = "numpy-1.24.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d926b52ba1367f9acb76b0df6ed21f0b16a1ad87c6720a1121674e5cf63e2b6"}, + {file = "numpy-1.24.3-cp310-cp310-win32.whl", hash = "sha256:f21c442fdd2805e91799fbe044a7b999b8571bb0ab0f7850d0cb9641a687092b"}, + {file = "numpy-1.24.3-cp310-cp310-win_amd64.whl", hash = "sha256:ab5f23af8c16022663a652d3b25dcdc272ac3f83c3af4c02eb8b824e6b3ab9d7"}, + {file = "numpy-1.24.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9a7721ec204d3a237225db3e194c25268faf92e19338a35f3a224469cb6039a3"}, + {file = "numpy-1.24.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d6cc757de514c00b24ae8cf5c876af2a7c3df189028d68c0cb4eaa9cd5afc2bf"}, + {file = "numpy-1.24.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76e3f4e85fc5d4fd311f6e9b794d0c00e7002ec122be271f2019d63376f1d385"}, + {file = "numpy-1.24.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1d3c026f57ceaad42f8231305d4653d5f05dc6332a730ae5c0bea3513de0950"}, + {file = "numpy-1.24.3-cp311-cp311-win32.whl", hash = "sha256:c91c4afd8abc3908e00a44b2672718905b8611503f7ff87390cc0ac3423fb096"}, + {file = "numpy-1.24.3-cp311-cp311-win_amd64.whl", hash = "sha256:5342cf6aad47943286afa6f1609cad9b4266a05e7f2ec408e2cf7aea7ff69d80"}, + {file = "numpy-1.24.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7776ea65423ca6a15255ba1872d82d207bd1e09f6d0894ee4a64678dd2204078"}, + {file = "numpy-1.24.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ae8d0be48d1b6ed82588934aaaa179875e7dc4f3d84da18d7eae6eb3f06c242c"}, + {file = "numpy-1.24.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecde0f8adef7dfdec993fd54b0f78183051b6580f606111a6d789cd14c61ea0c"}, + {file = "numpy-1.24.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4749e053a29364d3452c034827102ee100986903263e89884922ef01a0a6fd2f"}, + {file = "numpy-1.24.3-cp38-cp38-win32.whl", hash = "sha256:d933fabd8f6a319e8530d0de4fcc2e6a61917e0b0c271fded460032db42a0fe4"}, + {file = "numpy-1.24.3-cp38-cp38-win_amd64.whl", hash = "sha256:56e48aec79ae238f6e4395886b5eaed058abb7231fb3361ddd7bfdf4eed54289"}, + {file = "numpy-1.24.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4719d5aefb5189f50887773699eaf94e7d1e02bf36c1a9d353d9f46703758ca4"}, + {file = "numpy-1.24.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ec87a7084caa559c36e0a2309e4ecb1baa03b687201d0a847c8b0ed476a7187"}, + {file = "numpy-1.24.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea8282b9bcfe2b5e7d491d0bf7f3e2da29700cec05b49e64d6246923329f2b02"}, + {file = "numpy-1.24.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210461d87fb02a84ef243cac5e814aad2b7f4be953b32cb53327bb49fd77fbb4"}, + {file = "numpy-1.24.3-cp39-cp39-win32.whl", hash = "sha256:784c6da1a07818491b0ffd63c6bbe5a33deaa0e25a20e1b3ea20cf0e43f8046c"}, + {file = "numpy-1.24.3-cp39-cp39-win_amd64.whl", hash = "sha256:d5036197ecae68d7f491fcdb4df90082b0d4960ca6599ba2659957aafced7c17"}, + {file = "numpy-1.24.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:352ee00c7f8387b44d19f4cada524586f07379c0d49270f87233983bc5087ca0"}, + {file = "numpy-1.24.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7d6acc2e7524c9955e5c903160aa4ea083736fde7e91276b0e5d98e6332812"}, + {file = "numpy-1.24.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:35400e6a8d102fd07c71ed7dcadd9eb62ee9a6e84ec159bd48c28235bbb0f8e4"}, + {file = "numpy-1.24.3.tar.gz", hash = "sha256:ab344f1bf21f140adab8e47fdbc7c35a477dc01408791f8ba00d018dd0bc5155"}, +] + +[[package]] +name = "packaging" +version = "23.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, + {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, +] + +[[package]] +name = "pathspec" +version = "0.11.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"}, + {file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"}, +] + +[[package]] +name = "platformdirs" +version = "3.5.3" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = ">=3.7" +files = [ + {file = "platformdirs-3.5.3-py3-none-any.whl", hash = "sha256:0ade98a4895e87dc51d47151f7d2ec290365a585151d97b4d8d6312ed6132fed"}, + {file = "platformdirs-3.5.3.tar.gz", hash = "sha256:e48fabd87db8f3a7df7150a4a5ea22c546ee8bc39bc2473244730d4b56d2cc4e"}, +] + +[package.extras] +docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)"] + +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pycodestyle" +version = "2.10.0" +description = "Python style guide checker" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pycodestyle-2.10.0-py2.py3-none-any.whl", hash = "sha256:8a4eaf0d0495c7395bdab3589ac2db602797d76207242c17d470186815706610"}, + {file = "pycodestyle-2.10.0.tar.gz", hash = "sha256:347187bdb476329d98f695c213d7295a846d1152ff4fe9bacb8a9590b8ee7053"}, +] + +[[package]] +name = "pyflakes" +version = "3.0.1" +description = "passive checker of Python programs" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pyflakes-3.0.1-py2.py3-none-any.whl", hash = "sha256:ec55bf7fe21fff7f1ad2f7da62363d749e2a470500eab1b555334b67aa1ef8cf"}, + {file = "pyflakes-3.0.1.tar.gz", hash = "sha256:ec8b276a6b60bd80defed25add7e439881c19e64850afd9b346283d4165fd0fd"}, +] + +[[package]] +name = "pytest" +version = "7.3.2" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.3.2-py3-none-any.whl", hash = "sha256:cdcbd012c9312258922f8cd3f1b62a6580fdced17db6014896053d47cddf9295"}, + {file = "pytest-7.3.2.tar.gz", hash = "sha256:ee990a3cc55ba808b80795a79944756f315c67c12b56abd3ac993a7b8c17030b"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "python-dotenv" +version = "1.0.0" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python-dotenv-1.0.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba"}, + {file = "python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "python-engineio" +version = "4.4.1" +description = "Engine.IO server and client for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "python-engineio-4.4.1.tar.gz", hash = "sha256:eb3663ecb300195926b526386f712dff84cd092c818fb7b62eeeda9160120c29"}, + {file = "python_engineio-4.4.1-py3-none-any.whl", hash = "sha256:28ab67f94cba2e5f598cbb04428138fd6bb8b06d3478c939412da445f24f0773"}, +] + +[package.extras] +asyncio-client = ["aiohttp (>=3.4)"] +client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] + +[[package]] +name = "python-socketio" +version = "5.8.0" +description = "Socket.IO server and client for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "python-socketio-5.8.0.tar.gz", hash = "sha256:e714f4dddfaaa0cb0e37a1e2deef2bb60590a5b9fea9c343dd8ca5e688416fd9"}, + {file = "python_socketio-5.8.0-py3-none-any.whl", hash = "sha256:7adb8867aac1c2929b9c1429f1c02e12ca4c36b67c807967393e367dfbb01441"}, +] + +[package.dependencies] +bidict = ">=0.21.0" +python-engineio = ">=4.3.0" + +[package.extras] +asyncio-client = ["aiohttp (>=3.4)"] +client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] + +[[package]] +name = "pyyaml" +version = "6.0" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, + {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, + {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, + {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, +] + +[[package]] +name = "sniffio" +version = "1.3.0" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, + {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, +] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "typing-extensions" +version = "4.6.3" +description = "Backported and Experimental Type Hints for Python 3.7+" +optional = false +python-versions = ">=3.7" +files = [ + {file = "typing_extensions-4.6.3-py3-none-any.whl", hash = "sha256:88a4153d8505aabbb4e13aacb7c486c2b4a33ca3b3f807914a9b4c844c471c26"}, + {file = "typing_extensions-4.6.3.tar.gz", hash = "sha256:d91d5919357fe7f681a9f2b5b4cb2a5f1ef0a1e9f59c4d8ff0d3491e05c0ffd5"}, +] + +[[package]] +name = "uvicorn" +version = "0.22.0" +description = "The lightning-fast ASGI server." +optional = false +python-versions = ">=3.7" +files = [ + {file = "uvicorn-0.22.0-py3-none-any.whl", hash = "sha256:e9434d3bbf05f310e762147f769c9f21235ee118ba2d2bf1155a7196448bd996"}, + {file = "uvicorn-0.22.0.tar.gz", hash = "sha256:79277ae03db57ce7d9aa0567830bbb51d7a612f54d6e1e3e92da3ef24c2c8ed8"}, +] + +[package.dependencies] +click = ">=7.0" +colorama = {version = ">=0.4", optional = true, markers = "sys_platform == \"win32\" and extra == \"standard\""} +h11 = ">=0.8" +httptools = {version = ">=0.5.0", optional = true, markers = "extra == \"standard\""} +python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} +pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""} +uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "(sys_platform != \"win32\" and sys_platform != \"cygwin\") and platform_python_implementation != \"PyPy\" and extra == \"standard\""} +watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} +websockets = {version = ">=10.4", optional = true, markers = "extra == \"standard\""} + +[package.extras] +standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] + +[[package]] +name = "uvloop" +version = "0.17.0" +description = "Fast implementation of asyncio event loop on top of libuv" +optional = false +python-versions = ">=3.7" +files = [ + {file = "uvloop-0.17.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ce9f61938d7155f79d3cb2ffa663147d4a76d16e08f65e2c66b77bd41b356718"}, + {file = "uvloop-0.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:68532f4349fd3900b839f588972b3392ee56042e440dd5873dfbbcd2cc67617c"}, + {file = "uvloop-0.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0949caf774b9fcefc7c5756bacbbbd3fc4c05a6b7eebc7c7ad6f825b23998d6d"}, + {file = "uvloop-0.17.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff3d00b70ce95adce264462c930fbaecb29718ba6563db354608f37e49e09024"}, + {file = "uvloop-0.17.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a5abddb3558d3f0a78949c750644a67be31e47936042d4f6c888dd6f3c95f4aa"}, + {file = "uvloop-0.17.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8efcadc5a0003d3a6e887ccc1fb44dec25594f117a94e3127954c05cf144d811"}, + {file = "uvloop-0.17.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3378eb62c63bf336ae2070599e49089005771cc651c8769aaad72d1bd9385a7c"}, + {file = "uvloop-0.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6aafa5a78b9e62493539456f8b646f85abc7093dd997f4976bb105537cf2635e"}, + {file = "uvloop-0.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c686a47d57ca910a2572fddfe9912819880b8765e2f01dc0dd12a9bf8573e539"}, + {file = "uvloop-0.17.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:864e1197139d651a76c81757db5eb199db8866e13acb0dfe96e6fc5d1cf45fc4"}, + {file = "uvloop-0.17.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2a6149e1defac0faf505406259561bc14b034cdf1d4711a3ddcdfbaa8d825a05"}, + {file = "uvloop-0.17.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6708f30db9117f115eadc4f125c2a10c1a50d711461699a0cbfaa45b9a78e376"}, + {file = "uvloop-0.17.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:23609ca361a7fc587031429fa25ad2ed7242941adec948f9d10c045bfecab06b"}, + {file = "uvloop-0.17.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2deae0b0fb00a6af41fe60a675cec079615b01d68beb4cc7b722424406b126a8"}, + {file = "uvloop-0.17.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45cea33b208971e87a31c17622e4b440cac231766ec11e5d22c76fab3bf9df62"}, + {file = "uvloop-0.17.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:9b09e0f0ac29eee0451d71798878eae5a4e6a91aa275e114037b27f7db72702d"}, + {file = "uvloop-0.17.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dbbaf9da2ee98ee2531e0c780455f2841e4675ff580ecf93fe5c48fe733b5667"}, + {file = "uvloop-0.17.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a4aee22ece20958888eedbad20e4dbb03c37533e010fb824161b4f05e641f738"}, + {file = "uvloop-0.17.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:307958f9fc5c8bb01fad752d1345168c0abc5d62c1b72a4a8c6c06f042b45b20"}, + {file = "uvloop-0.17.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ebeeec6a6641d0adb2ea71dcfb76017602ee2bfd8213e3fcc18d8f699c5104f"}, + {file = "uvloop-0.17.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1436c8673c1563422213ac6907789ecb2b070f5939b9cbff9ef7113f2b531595"}, + {file = "uvloop-0.17.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8887d675a64cfc59f4ecd34382e5b4f0ef4ae1da37ed665adba0c2badf0d6578"}, + {file = "uvloop-0.17.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3db8de10ed684995a7f34a001f15b374c230f7655ae840964d51496e2f8a8474"}, + {file = "uvloop-0.17.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7d37dccc7ae63e61f7b96ee2e19c40f153ba6ce730d8ba4d3b4e9738c1dccc1b"}, + {file = "uvloop-0.17.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cbbe908fda687e39afd6ea2a2f14c2c3e43f2ca88e3a11964b297822358d0e6c"}, + {file = "uvloop-0.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d97672dc709fa4447ab83276f344a165075fd9f366a97b712bdd3fee05efae8"}, + {file = "uvloop-0.17.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1e507c9ee39c61bfddd79714e4f85900656db1aec4d40c6de55648e85c2799c"}, + {file = "uvloop-0.17.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c092a2c1e736086d59ac8e41f9c98f26bbf9b9222a76f21af9dfe949b99b2eb9"}, + {file = "uvloop-0.17.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:30babd84706115626ea78ea5dbc7dd8d0d01a2e9f9b306d24ca4ed5796c66ded"}, + {file = "uvloop-0.17.0.tar.gz", hash = "sha256:0ddf6baf9cf11a1a22c71487f39f15b2cf78eb5bde7e5b45fbb99e8a9d91b9e1"}, +] + +[package.extras] +dev = ["Cython (>=0.29.32,<0.30.0)", "Sphinx (>=4.1.2,<4.2.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=22.0.0,<22.1.0)", "pycodestyle (>=2.7.0,<2.8.0)", "pytest (>=3.6.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] +docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] +test = ["Cython (>=0.29.32,<0.30.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=22.0.0,<22.1.0)", "pycodestyle (>=2.7.0,<2.8.0)"] + +[[package]] +name = "watchfiles" +version = "0.19.0" +description = "Simple, modern and high performance file watching and code reload in python." +optional = false +python-versions = ">=3.7" +files = [ + {file = "watchfiles-0.19.0-cp37-abi3-macosx_10_7_x86_64.whl", hash = "sha256:91633e64712df3051ca454ca7d1b976baf842d7a3640b87622b323c55f3345e7"}, + {file = "watchfiles-0.19.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:b6577b8c6c8701ba8642ea9335a129836347894b666dd1ec2226830e263909d3"}, + {file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:18b28f6ad871b82df9542ff958d0c86bb0d8310bb09eb8e87d97318a3b5273af"}, + {file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fac19dc9cbc34052394dbe81e149411a62e71999c0a19e1e09ce537867f95ae0"}, + {file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:09ea3397aecbc81c19ed7f025e051a7387feefdb789cf768ff994c1228182fda"}, + {file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c0376deac92377817e4fb8f347bf559b7d44ff556d9bc6f6208dd3f79f104aaf"}, + {file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c75eff897786ee262c9f17a48886f4e98e6cfd335e011c591c305e5d083c056"}, + {file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb5d45c4143c1dd60f98a16187fd123eda7248f84ef22244818c18d531a249d1"}, + {file = "watchfiles-0.19.0-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:79c533ff593db861ae23436541f481ec896ee3da4e5db8962429b441bbaae16e"}, + {file = "watchfiles-0.19.0-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:3d7d267d27aceeeaa3de0dd161a0d64f0a282264d592e335fff7958cc0cbae7c"}, + {file = "watchfiles-0.19.0-cp37-abi3-win32.whl", hash = "sha256:176a9a7641ec2c97b24455135d58012a5be5c6217fc4d5fef0b2b9f75dbf5154"}, + {file = "watchfiles-0.19.0-cp37-abi3-win_amd64.whl", hash = "sha256:945be0baa3e2440151eb3718fd8846751e8b51d8de7b884c90b17d271d34cae8"}, + {file = "watchfiles-0.19.0-cp37-abi3-win_arm64.whl", hash = "sha256:0089c6dc24d436b373c3c57657bf4f9a453b13767150d17284fc6162b2791911"}, + {file = "watchfiles-0.19.0-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:cae3dde0b4b2078f31527acff6f486e23abed307ba4d3932466ba7cdd5ecec79"}, + {file = "watchfiles-0.19.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f3920b1285a7d3ce898e303d84791b7bf40d57b7695ad549dc04e6a44c9f120"}, + {file = "watchfiles-0.19.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9afd0d69429172c796164fd7fe8e821ade9be983f51c659a38da3faaaaac44dc"}, + {file = "watchfiles-0.19.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68dce92b29575dda0f8d30c11742a8e2b9b8ec768ae414b54f7453f27bdf9545"}, + {file = "watchfiles-0.19.0-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:5569fc7f967429d4bc87e355cdfdcee6aabe4b620801e2cf5805ea245c06097c"}, + {file = "watchfiles-0.19.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5471582658ea56fca122c0f0d0116a36807c63fefd6fdc92c71ca9a4491b6b48"}, + {file = "watchfiles-0.19.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b538014a87f94d92f98f34d3e6d2635478e6be6423a9ea53e4dd96210065e193"}, + {file = "watchfiles-0.19.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20b44221764955b1e703f012c74015306fb7e79a00c15370785f309b1ed9aa8d"}, + {file = "watchfiles-0.19.0.tar.gz", hash = "sha256:d9b073073e048081e502b6c6b0b88714c026a1a4c890569238d04aca5f9ca74b"}, +] + +[package.dependencies] +anyio = ">=3.0.0" + +[[package]] +name = "websockets" +version = "11.0.3" +description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "websockets-11.0.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3ccc8a0c387629aec40f2fc9fdcb4b9d5431954f934da3eaf16cdc94f67dbfac"}, + {file = "websockets-11.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d67ac60a307f760c6e65dad586f556dde58e683fab03323221a4e530ead6f74d"}, + {file = "websockets-11.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84d27a4832cc1a0ee07cdcf2b0629a8a72db73f4cf6de6f0904f6661227f256f"}, + {file = "websockets-11.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffd7dcaf744f25f82190856bc26ed81721508fc5cbf2a330751e135ff1283564"}, + {file = "websockets-11.0.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7622a89d696fc87af8e8d280d9b421db5133ef5b29d3f7a1ce9f1a7bf7fcfa11"}, + {file = "websockets-11.0.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bceab846bac555aff6427d060f2fcfff71042dba6f5fca7dc4f75cac815e57ca"}, + {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:54c6e5b3d3a8936a4ab6870d46bdd6ec500ad62bde9e44462c32d18f1e9a8e54"}, + {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:41f696ba95cd92dc047e46b41b26dd24518384749ed0d99bea0a941ca87404c4"}, + {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:86d2a77fd490ae3ff6fae1c6ceaecad063d3cc2320b44377efdde79880e11526"}, + {file = "websockets-11.0.3-cp310-cp310-win32.whl", hash = "sha256:2d903ad4419f5b472de90cd2d40384573b25da71e33519a67797de17ef849b69"}, + {file = "websockets-11.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:1d2256283fa4b7f4c7d7d3e84dc2ece74d341bce57d5b9bf385df109c2a1a82f"}, + {file = "websockets-11.0.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e848f46a58b9fcf3d06061d17be388caf70ea5b8cc3466251963c8345e13f7eb"}, + {file = "websockets-11.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa5003845cdd21ac0dc6c9bf661c5beddd01116f6eb9eb3c8e272353d45b3288"}, + {file = "websockets-11.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b58cbf0697721120866820b89f93659abc31c1e876bf20d0b3d03cef14faf84d"}, + {file = "websockets-11.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:660e2d9068d2bedc0912af508f30bbeb505bbbf9774d98def45f68278cea20d3"}, + {file = "websockets-11.0.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1f0524f203e3bd35149f12157438f406eff2e4fb30f71221c8a5eceb3617b6b"}, + {file = "websockets-11.0.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:def07915168ac8f7853812cc593c71185a16216e9e4fa886358a17ed0fd9fcf6"}, + {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b30c6590146e53149f04e85a6e4fcae068df4289e31e4aee1fdf56a0dead8f97"}, + {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:619d9f06372b3a42bc29d0cd0354c9bb9fb39c2cbc1a9c5025b4538738dbffaf"}, + {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:01f5567d9cf6f502d655151645d4e8b72b453413d3819d2b6f1185abc23e82dd"}, + {file = "websockets-11.0.3-cp311-cp311-win32.whl", hash = "sha256:e1459677e5d12be8bbc7584c35b992eea142911a6236a3278b9b5ce3326f282c"}, + {file = "websockets-11.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:e7837cb169eca3b3ae94cc5787c4fed99eef74c0ab9506756eea335e0d6f3ed8"}, + {file = "websockets-11.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9f59a3c656fef341a99e3d63189852be7084c0e54b75734cde571182c087b152"}, + {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2529338a6ff0eb0b50c7be33dc3d0e456381157a31eefc561771ee431134a97f"}, + {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34fd59a4ac42dff6d4681d8843217137f6bc85ed29722f2f7222bd619d15e95b"}, + {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:332d126167ddddec94597c2365537baf9ff62dfcc9db4266f263d455f2f031cb"}, + {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6505c1b31274723ccaf5f515c1824a4ad2f0d191cec942666b3d0f3aa4cb4007"}, + {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f467ba0050b7de85016b43f5a22b46383ef004c4f672148a8abf32bc999a87f0"}, + {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9d9acd80072abcc98bd2c86c3c9cd4ac2347b5a5a0cae7ed5c0ee5675f86d9af"}, + {file = "websockets-11.0.3-cp37-cp37m-win32.whl", hash = "sha256:e590228200fcfc7e9109509e4d9125eace2042fd52b595dd22bbc34bb282307f"}, + {file = "websockets-11.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:b16fff62b45eccb9c7abb18e60e7e446998093cdcb50fed33134b9b6878836de"}, + {file = "websockets-11.0.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fb06eea71a00a7af0ae6aefbb932fb8a7df3cb390cc217d51a9ad7343de1b8d0"}, + {file = "websockets-11.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8a34e13a62a59c871064dfd8ffb150867e54291e46d4a7cf11d02c94a5275bae"}, + {file = "websockets-11.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4841ed00f1026dfbced6fca7d963c4e7043aa832648671b5138008dc5a8f6d99"}, + {file = "websockets-11.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a073fc9ab1c8aff37c99f11f1641e16da517770e31a37265d2755282a5d28aa"}, + {file = "websockets-11.0.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68b977f21ce443d6d378dbd5ca38621755f2063d6fdb3335bda981d552cfff86"}, + {file = "websockets-11.0.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1a99a7a71631f0efe727c10edfba09ea6bee4166a6f9c19aafb6c0b5917d09c"}, + {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bee9fcb41db2a23bed96c6b6ead6489702c12334ea20a297aa095ce6d31370d0"}, + {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4b253869ea05a5a073ebfdcb5cb3b0266a57c3764cf6fe114e4cd90f4bfa5f5e"}, + {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1553cb82942b2a74dd9b15a018dce645d4e68674de2ca31ff13ebc2d9f283788"}, + {file = "websockets-11.0.3-cp38-cp38-win32.whl", hash = "sha256:f61bdb1df43dc9c131791fbc2355535f9024b9a04398d3bd0684fc16ab07df74"}, + {file = "websockets-11.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:03aae4edc0b1c68498f41a6772d80ac7c1e33c06c6ffa2ac1c27a07653e79d6f"}, + {file = "websockets-11.0.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:777354ee16f02f643a4c7f2b3eff8027a33c9861edc691a2003531f5da4f6bc8"}, + {file = "websockets-11.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8c82f11964f010053e13daafdc7154ce7385ecc538989a354ccc7067fd7028fd"}, + {file = "websockets-11.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3580dd9c1ad0701169e4d6fc41e878ffe05e6bdcaf3c412f9d559389d0c9e016"}, + {file = "websockets-11.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f1a3f10f836fab6ca6efa97bb952300b20ae56b409414ca85bff2ad241d2a61"}, + {file = "websockets-11.0.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df41b9bc27c2c25b486bae7cf42fccdc52ff181c8c387bfd026624a491c2671b"}, + {file = "websockets-11.0.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:279e5de4671e79a9ac877427f4ac4ce93751b8823f276b681d04b2156713b9dd"}, + {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1fdf26fa8a6a592f8f9235285b8affa72748dc12e964a5518c6c5e8f916716f7"}, + {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:69269f3a0b472e91125b503d3c0b3566bda26da0a3261c49f0027eb6075086d1"}, + {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:97b52894d948d2f6ea480171a27122d77af14ced35f62e5c892ca2fae9344311"}, + {file = "websockets-11.0.3-cp39-cp39-win32.whl", hash = "sha256:c7f3cb904cce8e1be667c7e6fef4516b98d1a6a0635a58a57528d577ac18a128"}, + {file = "websockets-11.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c792ea4eabc0159535608fc5658a74d1a81020eb35195dd63214dcf07556f67e"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f2e58f2c36cc52d41f2659e4c0cbf7353e28c8c9e63e30d8c6d3494dc9fdedcf"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de36fe9c02995c7e6ae6efe2e205816f5f00c22fd1fbf343d4d18c3d5ceac2f5"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ac56b661e60edd453585f4bd68eb6a29ae25b5184fd5ba51e97652580458998"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e052b8467dd07d4943936009f46ae5ce7b908ddcac3fda581656b1b19c083d9b"}, + {file = "websockets-11.0.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:42cc5452a54a8e46a032521d7365da775823e21bfba2895fb7b77633cce031bb"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e6316827e3e79b7b8e7d8e3b08f4e331af91a48e794d5d8b099928b6f0b85f20"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8531fdcad636d82c517b26a448dcfe62f720e1922b33c81ce695d0edb91eb931"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c114e8da9b475739dde229fd3bc6b05a6537a88a578358bc8eb29b4030fac9c9"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e063b1865974611313a3849d43f2c3f5368093691349cf3c7c8f8f75ad7cb280"}, + {file = "websockets-11.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:92b2065d642bf8c0a82d59e59053dd2fdde64d4ed44efe4870fa816c1232647b"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0ee68fe502f9031f19d495dae2c268830df2760c0524cbac5d759921ba8c8e82"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcacf2c7a6c3a84e720d1bb2b543c675bf6c40e460300b628bab1b1efc7c034c"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b67c6f5e5a401fc56394f191f00f9b3811fe843ee93f4a70df3c389d1adf857d"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d5023a4b6a5b183dc838808087033ec5df77580485fc533e7dab2567851b0a4"}, + {file = "websockets-11.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ed058398f55163a79bb9f06a90ef9ccc063b204bb346c4de78efc5d15abfe602"}, + {file = "websockets-11.0.3-py3-none-any.whl", hash = "sha256:6681ba9e7f8f3b19440921e99efbb40fc89f26cd71bf539e45d8c8a25c976dc6"}, + {file = "websockets-11.0.3.tar.gz", hash = "sha256:88fc51d9a26b10fc331be344f1781224a375b78488fc343620184e95a4b27016"}, +] + +[[package]] +name = "yarl" +version = "1.9.2" +description = "Yet another URL library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c2ad583743d16ddbdf6bb14b5cd76bf43b0d0006e918809d5d4ddf7bde8dd82"}, + {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82aa6264b36c50acfb2424ad5ca537a2060ab6de158a5bd2a72a032cc75b9eb8"}, + {file = "yarl-1.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c0c77533b5ed4bcc38e943178ccae29b9bcf48ffd1063f5821192f23a1bd27b9"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee4afac41415d52d53a9833ebae7e32b344be72835bbb589018c9e938045a560"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bf345c3a4f5ba7f766430f97f9cc1320786f19584acc7086491f45524a551ac"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a96c19c52ff442a808c105901d0bdfd2e28575b3d5f82e2f5fd67e20dc5f4ea"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:891c0e3ec5ec881541f6c5113d8df0315ce5440e244a716b95f2525b7b9f3608"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3a53ba34a636a256d767c086ceb111358876e1fb6b50dfc4d3f4951d40133d5"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:566185e8ebc0898b11f8026447eacd02e46226716229cea8db37496c8cdd26e0"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2b0738fb871812722a0ac2154be1f049c6223b9f6f22eec352996b69775b36d4"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:32f1d071b3f362c80f1a7d322bfd7b2d11e33d2adf395cc1dd4df36c9c243095"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e9fdc7ac0d42bc3ea78818557fab03af6181e076a2944f43c38684b4b6bed8e3"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56ff08ab5df8429901ebdc5d15941b59f6253393cb5da07b4170beefcf1b2528"}, + {file = "yarl-1.9.2-cp310-cp310-win32.whl", hash = "sha256:8ea48e0a2f931064469bdabca50c2f578b565fc446f302a79ba6cc0ee7f384d3"}, + {file = "yarl-1.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:50f33040f3836e912ed16d212f6cc1efb3231a8a60526a407aeb66c1c1956dde"}, + {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:646d663eb2232d7909e6601f1a9107e66f9791f290a1b3dc7057818fe44fc2b6"}, + {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aff634b15beff8902d1f918012fc2a42e0dbae6f469fce134c8a0dc51ca423bb"}, + {file = "yarl-1.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a83503934c6273806aed765035716216cc9ab4e0364f7f066227e1aaea90b8d0"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b25322201585c69abc7b0e89e72790469f7dad90d26754717f3310bfe30331c2"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22a94666751778629f1ec4280b08eb11815783c63f52092a5953faf73be24191"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ec53a0ea2a80c5cd1ab397925f94bff59222aa3cf9c6da938ce05c9ec20428d"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:159d81f22d7a43e6eabc36d7194cb53f2f15f498dbbfa8edc8a3239350f59fe7"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:832b7e711027c114d79dffb92576acd1bd2decc467dec60e1cac96912602d0e6"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:95d2ecefbcf4e744ea952d073c6922e72ee650ffc79028eb1e320e732898d7e8"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d4e2c6d555e77b37288eaf45b8f60f0737c9efa3452c6c44626a5455aeb250b9"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:783185c75c12a017cc345015ea359cc801c3b29a2966c2655cd12b233bf5a2be"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:b8cc1863402472f16c600e3e93d542b7e7542a540f95c30afd472e8e549fc3f7"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:822b30a0f22e588b32d3120f6d41e4ed021806418b4c9f0bc3048b8c8cb3f92a"}, + {file = "yarl-1.9.2-cp311-cp311-win32.whl", hash = "sha256:a60347f234c2212a9f0361955007fcf4033a75bf600a33c88a0a8e91af77c0e8"}, + {file = "yarl-1.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:be6b3fdec5c62f2a67cb3f8c6dbf56bbf3f61c0f046f84645cd1ca73532ea051"}, + {file = "yarl-1.9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:38a3928ae37558bc1b559f67410df446d1fbfa87318b124bf5032c31e3447b74"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac9bb4c5ce3975aeac288cfcb5061ce60e0d14d92209e780c93954076c7c4367"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3da8a678ca8b96c8606bbb8bfacd99a12ad5dd288bc6f7979baddd62f71c63ef"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13414591ff516e04fcdee8dc051c13fd3db13b673c7a4cb1350e6b2ad9639ad3"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf74d08542c3a9ea97bb8f343d4fcbd4d8f91bba5ec9d5d7f792dbe727f88938"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e7221580dc1db478464cfeef9b03b95c5852cc22894e418562997df0d074ccc"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:494053246b119b041960ddcd20fd76224149cfea8ed8777b687358727911dd33"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:52a25809fcbecfc63ac9ba0c0fb586f90837f5425edfd1ec9f3372b119585e45"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:e65610c5792870d45d7b68c677681376fcf9cc1c289f23e8e8b39c1485384185"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:1b1bba902cba32cdec51fca038fd53f8beee88b77efc373968d1ed021024cc04"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:662e6016409828ee910f5d9602a2729a8a57d74b163c89a837de3fea050c7582"}, + {file = "yarl-1.9.2-cp37-cp37m-win32.whl", hash = "sha256:f364d3480bffd3aa566e886587eaca7c8c04d74f6e8933f3f2c996b7f09bee1b"}, + {file = "yarl-1.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6a5883464143ab3ae9ba68daae8e7c5c95b969462bbe42e2464d60e7e2698368"}, + {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5610f80cf43b6202e2c33ba3ec2ee0a2884f8f423c8f4f62906731d876ef4fac"}, + {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b9a4e67ad7b646cd6f0938c7ebfd60e481b7410f574c560e455e938d2da8e0f4"}, + {file = "yarl-1.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:83fcc480d7549ccebe9415d96d9263e2d4226798c37ebd18c930fce43dfb9574"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fcd436ea16fee7d4207c045b1e340020e58a2597301cfbcfdbe5abd2356c2fb"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84e0b1599334b1e1478db01b756e55937d4614f8654311eb26012091be109d59"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3458a24e4ea3fd8930e934c129b676c27452e4ebda80fbe47b56d8c6c7a63a9e"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:838162460b3a08987546e881a2bfa573960bb559dfa739e7800ceeec92e64417"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4e2d08f07a3d7d3e12549052eb5ad3eab1c349c53ac51c209a0e5991bbada78"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:de119f56f3c5f0e2fb4dee508531a32b069a5f2c6e827b272d1e0ff5ac040333"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:149ddea5abf329752ea5051b61bd6c1d979e13fbf122d3a1f9f0c8be6cb6f63c"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:674ca19cbee4a82c9f54e0d1eee28116e63bc6fd1e96c43031d11cbab8b2afd5"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:9b3152f2f5677b997ae6c804b73da05a39daa6a9e85a512e0e6823d81cdad7cc"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5415d5a4b080dc9612b1b63cba008db84e908b95848369aa1da3686ae27b6d2b"}, + {file = "yarl-1.9.2-cp38-cp38-win32.whl", hash = "sha256:f7a3d8146575e08c29ed1cd287068e6d02f1c7bdff8970db96683b9591b86ee7"}, + {file = "yarl-1.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:63c48f6cef34e6319a74c727376e95626f84ea091f92c0250a98e53e62c77c72"}, + {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:75df5ef94c3fdc393c6b19d80e6ef1ecc9ae2f4263c09cacb178d871c02a5ba9"}, + {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c027a6e96ef77d401d8d5a5c8d6bc478e8042f1e448272e8d9752cb0aff8b5c8"}, + {file = "yarl-1.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3b078dbe227f79be488ffcfc7a9edb3409d018e0952cf13f15fd6512847f3f7"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59723a029760079b7d991a401386390c4be5bfec1e7dd83e25a6a0881859e716"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b03917871bf859a81ccb180c9a2e6c1e04d2f6a51d953e6a5cdd70c93d4e5a2a"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1012fa63eb6c032f3ce5d2171c267992ae0c00b9e164efe4d73db818465fac3"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a74dcbfe780e62f4b5a062714576f16c2f3493a0394e555ab141bf0d746bb955"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c56986609b057b4839968ba901944af91b8e92f1725d1a2d77cbac6972b9ed1"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2c315df3293cd521033533d242d15eab26583360b58f7ee5d9565f15fee1bef4"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b7232f8dfbd225d57340e441d8caf8652a6acd06b389ea2d3222b8bc89cbfca6"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:53338749febd28935d55b41bf0bcc79d634881195a39f6b2f767870b72514caf"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:066c163aec9d3d073dc9ffe5dd3ad05069bcb03fcaab8d221290ba99f9f69ee3"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8288d7cd28f8119b07dd49b7230d6b4562f9b61ee9a4ab02221060d21136be80"}, + {file = "yarl-1.9.2-cp39-cp39-win32.whl", hash = "sha256:b124e2a6d223b65ba8768d5706d103280914d61f5cae3afbc50fc3dfcc016623"}, + {file = "yarl-1.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:61016e7d582bc46a5378ffdd02cd0314fb8ba52f40f9cf4d9a5e7dbef88dee18"}, + {file = "yarl-1.9.2.tar.gz", hash = "sha256:04ab9d4b9f587c06d801c2abfe9317b77cdf996c65a90d5e84ecc45010823571"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + +[metadata] +lock-version = "2.0" +python-versions = "^3.8.1" +content-hash = "a3af5fde80b9e36741cc2068e93a225d638598eca63a727e9442d6b432b9016c" diff --git a/server/pyproject.toml b/server/pyproject.toml new file mode 100644 index 000000000..b06b76a46 --- /dev/null +++ b/server/pyproject.toml @@ -0,0 +1,26 @@ +[tool.poetry] +name = "volview_server" +version = "0.1.0" +description = "The VolView Python Server" +authors = ["Forrest "] +license = "Apache 2.0" + +[tool.poetry.dependencies] +python = "^3.8.1" +itk = "^5.3.0" +black = "^23.1.0" +flake8 = "^6.0.0" +pytest = "^7.2.1" +numpy = "^1.24.1" +aiohttp = "^3.8.3" +python-socketio = "^5.8.0" +charset-normalizer = "^3.1.0" + +[tool.poetry.dev-dependencies] + +[tool.poetry.group.dev.dependencies] +uvicorn = {extras = ["standard"], version = "^0.22.0"} + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/server/serialize.js b/server/serialize.js deleted file mode 100644 index 54f47c162..000000000 --- a/server/serialize.js +++ /dev/null @@ -1,111 +0,0 @@ -import vtk from '@kitware/vtk.js/vtk'; - -export const BREAK = Symbol('Break'); - -export function vtkObjectReplacer(vo) { - if (vo?.isA?.('vtkObject')) { - return vo.getState(); - } - return vo; -} - -export function vtkDataArrayJSONReplacer(da, attach) { - if (da?.classHierarchy?.includes?.('vtkDataArray')) { - const values = new globalThis[da.dataType](da.values); - return { - ...da, - values: attach(values), - }; - } - return da; -} - -/** - * Serializes typed arrays into regular arrays - */ -export function typedArrayReplacer(ta) { - if (ArrayBuffer.isView(ta) && !(ta instanceof DataView)) { - return Array.from(ta); - } - return ta; -} - -export async function vtkDataArrayValueReviver(da) { - if (da?.vtkClass === 'vtkDataArray' && da?.values instanceof Blob) { - const ab = await da.values.arrayBuffer(); - return { - ...da, - values: new globalThis[da.dataType](ab), - }; - } - return da; -} - -export function vtkObjectReviver(obj) { - if ('vtkClass' in obj) { - return vtk(obj); - } - return obj; -} - -const REPLACERS = [ - vtkObjectReplacer, - vtkDataArrayJSONReplacer, - typedArrayReplacer, -]; - -const REVIVERS = [ - vtkDataArrayValueReviver, - // should be after the vtkDataArrayValueReviver - vtkObjectReviver, -]; - -export function serialize(obj, attach) { - return JSON.parse( - JSON.stringify(obj, (key, value) => { - let transformed = value; - for (let i = 0; i < REPLACERS.length; i += 1) { - const result = REPLACERS[i](transformed, attach); - if (result === BREAK) { - break; - } - transformed = result; - } - return transformed; - }) - ); -} - -export async function deserialize(obj) { - async function revive(o) { - let transformed = o; - for (let i = 0; i < REVIVERS.length; i += 1) { - // eslint-disable-next-line no-await-in-loop - const result = await REVIVERS[i](transformed); - if (result === BREAK) { - break; - } - transformed = result; - } - return transformed; - } - - async function recurseHelper(o) { - if (Array.isArray(o)) { - const newArray = await Promise.all(o.map((item) => recurseHelper(item))); - return revive(newArray); - } - if (o instanceof Object && o.constructor === Object) { - const newO = {}; - await Promise.all( - Object.keys(o).map(async (k) => { - newO[k] = await recurseHelper(o[k]); - }) - ); - return revive(newO); - } - return o; - } - - return recurseHelper(obj); -} diff --git a/server/serialize.py b/server/serialize.py deleted file mode 100644 index 93e2b1ab8..000000000 --- a/server/serialize.py +++ /dev/null @@ -1,203 +0,0 @@ -import json -import struct -import itk -import numpy as np - -JS_TO_NPY_TYPEMAP = { - 'Int8Array': { - 'struct': (1, 'b'), - 'dtype': 'int8', - }, - 'Int16Array': { - 'struct': (2, 'h'), - 'dtype': 'int16', - }, - 'Int32Array': { - 'struct': (4, 'i'), - 'dtype': 'int32', - }, - 'Uint8Array': { - 'struct': (1, 'B'), - 'dtype': 'uint8', - }, - 'Uint16Array': { - 'struct': (2, 'H'), - 'dtype': 'uint16', - }, - 'Uint32Array': { - 'struct': (4, 'I'), - 'dtype': 'uint32', - }, - 'Float32Array': { - 'struct': (4, 'f'), - 'dtype': 'float32', - }, - 'Float64Array': { - 'struct': (8, 'd'), - 'dtype': 'float64', - }, -} - -ITK_COMP_TO_JS_TYPEMAP = { - 'SC': 'Int8Array', - 'UC': 'Uint8Array', - 'SS': 'Int16Array', - 'US': 'Uint16Array', - 'SI': 'Int32Array', - 'UI': 'Uint32Array', - 'F': 'Float32Array', - 'D': 'Float64Array', - 'B': 'Uint8Array' -} - - -def bytebuffer_to_numpy(blob, js_type): - typeinfo = JS_TO_NPY_TYPEMAP[js_type] - size, fmt = typeinfo['struct'] - dtype = np.dtype(typeinfo['dtype']) - - if len(blob) % size != 0: - raise ValueError('given byte buffer is not aligned to the type') - - full_fmt = '<{0}{1}'.format(len(blob) // size, fmt) - return np.array(struct.unpack(full_fmt, blob), dtype=dtype, copy=False) - - -def itk_image_pixel_type_to_js(itk_image): - component_str = repr(itk_image).split( - 'itkImagePython.')[1].split(';')[0][8:] - # TODO handle mangling as per https://github.com/InsightSoftwareConsortium/itk-jupyter-widgets/blob/master/itkwidgets/trait_types.py#L49 - return ITK_COMP_TO_JS_TYPEMAP[component_str[:-1]] - - -class RpcEncoder(object): - def __init__(self, encoders=[], extra_args=[], extra_kwargs={}): - self._encoders = list(encoders) - self._extra_args = extra_args - self._extra_kwargs = extra_kwargs - - def add_encoder(self, encoder): - self._encoders.append(encoder) - - def remove_encoder(self, encoder): - self._encoders.remove(encoder) - - def run_encoders(self, obj): - output = obj - for encoder in self._encoders: - output = encoder(output, *self._extra_args, **self._extra_kwargs) - return output - - def encode(self, obj): - # run on every possible value - if isinstance(obj, list): - return self.run_encoders([self.encode(item) for item in obj]) - if isinstance(obj, dict): - return self.run_encoders({k: self.encode(v) for k, v in obj.items()}) - else: - return self.run_encoders(obj) - - -class RpcDecoder(object): - def __init__(self, hooks=[], extra_args=[], extra_kwargs={}): - self._hooks = list(hooks) - self._extra_args = extra_args - self._extra_kwargs = extra_kwargs - - def add_hook(self, hook): - self._hooks.append(hook) - - def remove_hook(self, hook): - self._hooks.remove(hook) - - def run_hooks(self, val): - output = val - for hook in self._hooks: - output = hook(output, *self._extra_args, **self._extra_kwargs) - return output - - def decode(self, obj): - # only run hooks on dictionaries - if isinstance(obj, list): - return [self.decode(item) for item in obj] - if isinstance(obj, dict): - return self.run_hooks({k: self.decode(v) for k, v in obj.items()}) - else: - return obj - - -def itk_image_encoder(obj, attach): - if type(obj).__name__.startswith('itkImage'): - img = obj - size = list(img.GetLargestPossibleRegion().GetSize()) - values = itk.GetArrayFromImage(img).flatten(order='C') - return { - 'vtkClass': 'vtkImageData', - 'dataDescription': 8, - 'direction': list( - itk.GetArrayFromVnlMatrix( - img.GetDirection().GetVnlMatrix().as_matrix() - ).flatten() - ), - 'extent': [ - 0, size[0] - 1, - 0, size[1] - 1, - 0, size[2] - 1, - ], - 'spacing': list(img.GetSpacing()), - 'origin': list(img.GetOrigin()), - 'pointData': { - 'vtkClass': 'vtkDataSetAttributes', - 'activeScalars': 0, # the index of the only array - 'arrays': [ - { - 'data': { - 'vtkClass': 'vtkDataArray', - 'size': len(values), - 'values': attach(values.tobytes()), - 'dataType': itk_image_pixel_type_to_js(img), - 'numberOfComponents': img.GetNumberOfComponentsPerPixel(), - 'name': 'Scalars', - } - } - ] - } - } - return obj - - -def itk_image_decode_hook(obj): - if isinstance(obj, dict) and obj.get('vtkClass', None) == 'vtkImageData': - data_array = obj['pointData']['arrays'][0]['data'] - pixel_data = bytebuffer_to_numpy( - data_array['values'], data_array['dataType']) - - extent = obj['extent'] - # numpy indexes in ZYX order, where X varies the fastest - dims = [ - extent[5] - extent[4] + 1, - extent[3] - extent[2] + 1, - extent[1] - extent[0] + 1, - ] - direction = np.zeros((3, 3)) - for x in range(3): - for y in range(3): - direction[x][y] = obj['direction'][x*3+y] - - itk_image = itk.GetImageFromArray(np.reshape(pixel_data, dims)) - # https://discourse.itk.org/t/set-image-direction-from-numpy-array/844/10 - vnlmat = itk.GetVnlMatrixFromArray(direction) - itk_image.GetDirection().GetVnlMatrix().copy_in(vnlmat.data_block()) - itk_image.SetOrigin(obj['origin']) - itk_image.SetSpacing(obj['spacing']) - return itk_image - return obj - - -DEFAULT_DECODERS = [ - itk_image_decode_hook, -] - -DEFAULT_ENCODERS = [ - itk_image_encoder, -] diff --git a/server/server.py b/server/server.py deleted file mode 100644 index 9fb7fe336..000000000 --- a/server/server.py +++ /dev/null @@ -1,84 +0,0 @@ -import sys -import os -import webbrowser -import socket -import argparse -import importlib - -from wslink.websocket import ServerProtocol -from wslink import server -from twisted.internet import reactor - - -def get_port(): - # Don't care about race condition here for getting a free port - # if someone binds the port between get_port() and actually binding, - # then the server won't start - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.bind(('localhost', 0)) - _, port = sock.getsockname() - sock.close() - return port - - -def create_protocol(ApiClass): - class ApiProtocol(ServerProtocol): - authKey = 'wslink-secret' - - @staticmethod - def configure(options): - ApiProtocol.authKey = options.authKey - - def initialize(self): - self.registerLinkProtocol(ApiClass()) - self.updateSecret(ApiProtocol.authKey) - - return ApiProtocol - - -if __name__ == '__main__': - # https://stackoverflow.com/questions/7674790/bundling-data-files-with-pyinstaller-onefile - try: - basepath = sys._MEIPASS - except: - basepath = os.path.dirname(os.path.dirname(sys.argv[0])) - - parser = argparse.ArgumentParser() - parser.add_argument('-H', '--host', default='localhost', - help='Hostname for server to listen on') - parser.add_argument('-P', '--port', default=get_port(), - help='Port for server to listen on') - parser.add_argument('-b', '--no-browser', action='store_true', - help='Do not auto-open the browser') - parser.add_argument('api_script', - help='Python file that exposes ServerApi') - args = parser.parse_args() - - static_dir = os.path.join(basepath, 'www') - host = args.host - port = args.port - server_args = [ - '--content', static_dir, - '--host', host, - '--port', str(port) - ] - - wsurl = 'ws://{host}:{port}/ws'.format(host=host, port=port) - full_url = 'http://{host}:{port}/?wsServer={wsurl}'.format( - host=host, port=port, wsurl=wsurl) - - def open_webapp(): - webbrowser.open(full_url) - - # if not args.no_browser: - # print('If the browser doesn\'t open, navigate to:', full_url) - # reactor.callLater(0.1, open_webapp) - - sys.path.append(os.path.dirname(os.path.realpath(__file__))) - - spec = importlib.util.spec_from_file_location('Api', args.api_script) - api_module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(api_module) - - server.start(server_args, create_protocol(api_module.Api)) - server.stop_webserver() diff --git a/server/test.py b/server/test.py deleted file mode 100644 index b534d983b..000000000 --- a/server/test.py +++ /dev/null @@ -1,17 +0,0 @@ -import itk -import json -from helper import RpcApi, rpc - - -class Api(RpcApi): - - @rpc('run') - def test(self, arg1, arg2): - print(type(arg1), type(arg2), arg2) - with open('spleen_10.json', 'r') as fp: - mm = json.load(fp) - return { - 'segmentation': itk.imread('spleen_10-label.nrrd'), - # 'segmentation': itk.imread('/home/forrestli/data/Branch-label.nrrd'), - 'measurements': mm, - } diff --git a/server/volview_server/__init__.py b/server/volview_server/__init__.py new file mode 100644 index 000000000..be005b967 --- /dev/null +++ b/server/volview_server/__init__.py @@ -0,0 +1,8 @@ +__version__ = "0.1.0" +__author__ = "Kitware, Inc." +__all__ = ["VolViewApi", "RpcRouter", "get_current_client_store", "get_current_session"] + +from volview_server.volview_api import VolViewApi +from volview_server.rpc_router import RpcRouter +from volview_server.client_store import get_current_client_store +from volview_server.session import get_current_session diff --git a/server/volview_server/__main__.py b/server/volview_server/__main__.py new file mode 100644 index 000000000..22c2f0005 --- /dev/null +++ b/server/volview_server/__main__.py @@ -0,0 +1,94 @@ +import sys +import re +import os +import argparse +import importlib +import logging + +from aiohttp import web + +from volview_server.volview_api import VolViewApi +from volview_server.rpc_server import RpcServer +from volview_server.chunking import CHUNK_SIZE + + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument( + "-H", "--host", default="localhost", help="Hostname for server to listen on" + ) + parser.add_argument( + "-P", "--port", default=4014, help="Port for server to listen on" + ) + parser.add_argument( + "--verbose", default=False, action="store_true", help="Enable verbose logging." + ) + parser.add_argument("api_script", help="Python file that exposes ServerApi") + return parser.parse_args() + + +def import_api_script(api_script_file: str): + api_script_file = os.path.abspath(api_script_file) + import_target = os.path.basename(api_script_file) + + script_filename, _, instance_name = import_target.partition(":") + instance_name = instance_name or "volview" + module_name = re.sub(r"\.py$", "", script_filename) + + sys.path.append(os.path.dirname(api_script_file)) + module = importlib.import_module(module_name) + + instance = module + for attr_name in instance_name.split("."): + instance = getattr(instance, attr_name) + return instance + + +def run_server( + api: VolViewApi, + *, + host: str, + port: int, + debug: bool = False, + **kwargs, +): + rpc_server = RpcServer(api, async_mode="aiohttp", **kwargs) + + if debug: + logging.basicConfig(level=logging.DEBUG) + + async def stop(app): + await rpc_server.teardown() + + async def start(): + app = web.Application(client_max_size=CHUNK_SIZE) + rpc_server.sio.attach(app) + rpc_server.setup() + app.on_shutdown.append(stop) + return app + + web.run_app(start(), host=host, port=port) + + +def main(args): + volview_api = import_api_script(args.api_script) + + if not isinstance(volview_api, VolViewApi): + raise TypeError("Imported instance is not a VolViewApi") + + run_server( + volview_api, + host=args.host, + port=args.port, + debug=args.verbose, + # socketio.AsyncServer kwargs + async_handlers=True, + cors_allowed_origins="*", + logger=args.verbose, + engineio_logger=args.verbose, + max_http_buffer_size=CHUNK_SIZE, + ) + + +if __name__ == "__main__": + main(parse_args()) diff --git a/server/volview_server/api.py b/server/volview_server/api.py new file mode 100644 index 000000000..6b16172a5 --- /dev/null +++ b/server/volview_server/api.py @@ -0,0 +1,135 @@ +import asyncio +import inspect +from typing import Any, List, Callable, Union +from contextvars import copy_context +from concurrent.futures import ThreadPoolExecutor + +from volview_server.rpc_router import RpcRouter, ExposeType +from volview_server.transformers import ( + pipe, + transform_object, + default_serializers, + default_deserializers, + Transformer, +) + +DEFAULT_NUM_THREADS = 4 + + +class RpcApi: + serializers: List[Transformer] + deserializers: List[Transformer] + + def __init__( + self, + num_threads: int = DEFAULT_NUM_THREADS, + serializers: List[Transformer] = default_serializers, + deserializers: List[Transformer] = default_deserializers, + ): + self.serializers = serializers or [] + self.deserializers = deserializers or [] + self._default_router = RpcRouter() + self._routers = [self._default_router] + self._thread_pool = ThreadPoolExecutor(num_threads) + + def add_router(self, router: RpcRouter): + self._routers.append(router) + + def expose(self, name_or_func: Union[str, Callable], transform_args=True): + """Decorator that exposes a function as an RPC endpoint. + + See RpcRouter.add_endpoint() for more info. + + Both examples below expose the decorated function with the RPC name + "my_rpc". + + @volview.expose + def my_rpc(): + ... + + @volview.expose("my_rpc") + def internal_name(): + ... + + Keyword arguments: + - transform_args(=true): transform input arguments and output + results. Disable this if you do not want transform overhead + or you want to explicitly transform your inputs and outputs. + """ + if callable(name_or_func): + fn = name_or_func + name = fn.__name__ + self._default_router.add_endpoint(name, fn, transform_args=transform_args) + return fn + elif type(name_or_func) is str: + name = name_or_func + + def add_endpoint(fn): + self._default_router.add_endpoint( + name, fn, transform_args=transform_args + ) + return fn + + return add_endpoint + else: + raise TypeError("not given a name or function") + + def _find_endpoint(self, rpc_name: str): + for router in self._routers: + if rpc_name in router.endpoints: + return router.endpoints[rpc_name] + raise KeyError(f"Cannot find RPC endpoint {rpc_name}") + + async def invoke_rpc(self, rpc_name: str, *args, asyncio_loop=None, context=None): + """Invokes an RPC endpoint. + + If the endpoint is a non-async function, then it is run in an asyncio + loop with a given context. + + If no asyncio_loop is given, the default running loop is used. + + If no context is given, the current context is copied. + """ + fn, info = self._find_endpoint(rpc_name) + + if info.type != ExposeType.RPC: + raise TypeError(f"Cannot invoke a non-RPC endpoint") + + if info.transform_args: + args = [self.serialize_object(obj) for obj in args] + + if inspect.iscoroutinefunction(fn): + result = await fn(*args) + else: + loop = asyncio_loop or asyncio.get_running_loop() + ctx = context or copy_context() + result = await loop.run_in_executor(self._thread_pool, ctx.run, fn, *args) + + if info.transform_args: + result = self.serialize_object(result) + + return result + + async def invoke_stream(self, stream_name: str, *args): + """Invokes a stream endpoint. + + This is an async generator that produces result data. + """ + fn, info = self._find_endpoint(stream_name) + + if info.type != ExposeType.STREAM: + raise TypeError(f"Cannot stream from a non-stream endpoint") + + if info.transform_args: + args = [self.serialize_object(obj) for obj in args] + + async for data in fn(*args): + if info.transform_args: + data = self.deserialize_object(data) + yield data + + def serialize_object(self, obj: Any): + return transform_object(obj, lambda o: pipe(o, *self.serializers)) + + def deserialize_object(self, obj: Any): + return transform_object(obj, lambda o: pipe(o, *self.deserializers)) diff --git a/server/volview_server/chunking/__init__.py b/server/volview_server/chunking/__init__.py new file mode 100644 index 000000000..85302c22e --- /dev/null +++ b/server/volview_server/chunking/__init__.py @@ -0,0 +1,4 @@ +__all__ = ["CHUNK_SIZE", "ChunkingAsyncServer"] + +from .chunking_packet import CHUNK_SIZE +from .chunking_server import ChunkingAsyncServer diff --git a/server/volview_server/chunking/chunking_packet.py b/server/volview_server/chunking/chunking_packet.py new file mode 100644 index 000000000..20c52e4ce --- /dev/null +++ b/server/volview_server/chunking/chunking_packet.py @@ -0,0 +1,74 @@ +import json +from typing import List, Union + +from socketio.packet import Packet + +CHUNK_SIZE = 1 * 1024 * 1024 +CHUNKED_PACKET_TYPE = "C" + +EncodedMessage = Union[str, bytes] + + +class ChunkedPacket(Packet): + """A socket.io packet that chunks packets. + + The chunked encoder extends the default socket.io-parser protocol to support + chunked binary attachments. It should work with any protocol version, but has + only been tested with v5. + + This encoding adds an optional first message indicating that all subsequent + messages are chunked. + + [] + + [...] + + The chunking message is a string message that starts with the char 'C' and + has the following format: + + `C` + + The format of is a flat array of integers: + [N1, N2, N3, ...] + + There are a total of M integers, for M messages. The ith integer tells + us how many messages should be concatenated together to re-form the ith + message. + + Chunking works on both string and binary messages. + """ + + def encode(self): + encoded_packet = super().encode() + msgs = encoded_packet if type(encoded_packet) is list else [encoded_packet] + + # skip chunking info if all messages are smaller than chunk size. + if all(len(msg) <= CHUNK_SIZE for msg in msgs): + return msgs + + output: List[EncodedMessage] = [] + chunked_sizes: List[int] = [] + + for msg in msgs: + chunks = self._chunk_message(msg) + chunked_sizes.append(len(chunks)) + output.extend(chunks) + + return [ + f"{CHUNKED_PACKET_TYPE}{json.dumps(chunked_sizes, separators=(',', ':'))}", + *output, + ] + + def _chunk_message(self, msg: EncodedMessage) -> List[Union[str, bytes]]: + if type(msg) is str: + return self._chunk_str(msg) + if type(msg) is bytes: + return self._chunk_bytes(msg) + + def _chunk_str(self, string: str) -> List[str]: + return [string[o : o + CHUNK_SIZE] for o in range(0, len(string), CHUNK_SIZE)] + + def _chunk_bytes(self, binary: bytes) -> List[bytes]: + # TODO can we get memoryview working here? + # at the moment, memoryview doesn't serialize properly. + return [binary[o : o + CHUNK_SIZE] for o in range(0, len(binary), CHUNK_SIZE)] diff --git a/server/volview_server/chunking/chunking_server.py b/server/volview_server/chunking/chunking_server.py new file mode 100644 index 000000000..782bae5b2 --- /dev/null +++ b/server/volview_server/chunking/chunking_server.py @@ -0,0 +1,62 @@ +import json +from typing import List + +from socketio import AsyncServer + +from .chunking_packet import ChunkedPacket, CHUNKED_PACKET_TYPE + + +class ChunkingAsyncServer(AsyncServer): + """A socket.io server that handles chunked messages. + + See ChunkedPacket for more info. + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, serializer=ChunkedPacket, **kwargs) + self._chunks = None + self._chunking_info = None + + async def _handle_eio_message(self, eio_sid, data): + if self._chunking_info is not None and len(self._chunking_info): + self._chunks.append(data) + + if len(self._chunks) == self._chunking_info[0]: + await super()._handle_eio_message( + eio_sid, self._reconstruct_chunks(self._chunks) + ) + self._chunks = [] + self._chunking_info.pop(0) + + if len(self._chunking_info) == 0: + # reset chunking state + self._chunks = None + self._chunking_info = None + elif type(data) is str and data[0] == CHUNKED_PACKET_TYPE: + self._chunks = [] + self._chunking_info = self._try_parse_chunking_info(data[1:]) + else: + await super()._handle_eio_message(eio_sid, data) + + def _try_parse_chunking_info(self, data: str): + info = json.loads(data) + if type(info) is not list: + raise TypeError("chunking info is not a list") + + if not all(type(v) is int for v in info): + raise TypeError("chunking info is not comprised of integers") + + return info + + def _reconstruct_chunks(self, chunks): + if all(type(c) is str for c in chunks): + return self._reconstruct_string(chunks) + if all(type(c) is bytes for c in chunks): + return self._reconstruct_binary(chunks) + raise TypeError("Received a set of unknown chunks") + + def _reconstruct_string(self, chunks: List[str]): + return "".join(chunks) + + def _reconstruct_binary(self, chunks: List[bytes]): + return b"".join(chunks) diff --git a/server/volview_server/client_store.py b/server/volview_server/client_store.py new file mode 100644 index 000000000..50a073475 --- /dev/null +++ b/server/volview_server/client_store.py @@ -0,0 +1,107 @@ +from dataclasses import dataclass +from typing import List, Union, Any + +from volview_server.rpc_server import current_server + +PropKey = Union[int, str] + +RPC_GET_VALUE = "getStoreProperty" +RPC_CALL_METHOD = "callStoreMethod" + + +def get_current_server(): + server = current_server.get() + if not server: + raise RuntimeError("No active VolView RPC server") + return server + + +@dataclass +class StoreOptions: + transform_args: bool = True + + +class PropertyDescriptor: + def __init__(self, store_id: str, prop_chain: List[PropKey], options: StoreOptions): + self.store_id = store_id + self.prop_chain = prop_chain + self.options = options + + def __repr__(self): + return f'<{type(self).__name__} store_id={self.store_id} prop_chain={".".join(self.prop_chain)}>' + + +class ClientStoreMethodCallDescriptor(PropertyDescriptor): + def __init__( + self, + store_id: str, + prop_chain: List[PropKey], + options: StoreOptions, + args: List[Any], + ): + super().__init__(store_id, prop_chain, options) + # we don't support passing kwargs to client + self.args = args + + def __await__(self): + return ( + get_current_server() + .call_client( + RPC_CALL_METHOD, + [self.store_id, self.prop_chain, self.args], + transform_args=self.options.transform_args, + ) + .__await__() + ) + + def __repr__(self): + return f'<{type(self).__name__} store_id={self.store_id} prop_chain={".".join(self.prop_chain)}()>' + + +class ClientStorePropertyDescriptor(PropertyDescriptor): + def __call__(self, *args): + return ClientStoreMethodCallDescriptor( + self.store_id, self.prop_chain, self.options, args + ) + + def __getattr__(self, name: str): + return self.__getitem__(name) + + def __getitem__(self, key: PropKey): + return ClientStorePropertyDescriptor( + self.store_id, self.prop_chain + [key], self.options + ) + + def __await__(self): + return ( + get_current_server() + .call_client( + RPC_GET_VALUE, + [self.store_id, self.prop_chain], + transform_args=self.options.transform_args, + ) + .__await__() + ) + + +class ClientStore: + def __init__(self, name: str, options: StoreOptions): + self.name = name + self.options = options + + def __getattr__(self, name: str): + return self.__getitem__(name) + + def __getitem__(self, key: PropKey): + return ClientStorePropertyDescriptor(self.name, [key], self.options) + + +def get_current_client_store(store_name: str, **kwargs): + """Gets a proxy to a client's store. + + This should only be called from inside an RPC endpoint. + + The methods and properties accessed through this client store proxy are not bound to a client until awaited. + """ + options = StoreOptions(**kwargs) + return ClientStore(store_name, options) diff --git a/server/volview_server/exceptions.py b/server/volview_server/exceptions.py new file mode 100644 index 000000000..e7acbb871 --- /dev/null +++ b/server/volview_server/exceptions.py @@ -0,0 +1,4 @@ +class KeyExistsError(Exception): + """A given key already exists.""" + + ... diff --git a/server/volview_server/rpc_router.py b/server/volview_server/rpc_router.py new file mode 100644 index 000000000..ba1b8b6c8 --- /dev/null +++ b/server/volview_server/rpc_router.py @@ -0,0 +1,51 @@ +from dataclasses import dataclass +import inspect +import enum +from typing import Callable, Tuple, Dict + +from volview_server.exceptions import KeyExistsError + + +class ExposeType(enum.Enum): + RPC = "rpc" + STREAM = "stream" + + +@dataclass +class EndpointInfo: + name: str + type: ExposeType + transform_args: bool = True + + +Endpoint = Tuple[Callable, EndpointInfo] + + +class RpcRouter: + endpoints: Dict[str, Endpoint] + + def __init__(self): + self.endpoints = {} + + def add_endpoint(self, public_name: str, fn: Callable, transform_args=True): + """Adds a public endpoint. + + Arguments: + - public_name: the endpoint name + - fn: the function to call + + Keyword arguments: + - transform_args(=true): transform input arguments and output + results. Disable this if you do not want transform overhead + or you want to explicitly transform your inputs and outputs. + """ + + if public_name in self.endpoints: + raise KeyExistsError(f"{public_name} is already registered") + + expose_type = ExposeType.RPC + if inspect.isasyncgenfunction(fn) or inspect.isgeneratorfunction(fn): + expose_type = ExposeType.STREAM + + info = EndpointInfo(public_name, expose_type, transform_args) + self.endpoints[public_name] = (fn, info) diff --git a/server/volview_server/rpc_server.py b/server/volview_server/rpc_server.py new file mode 100644 index 000000000..bac63c5a1 --- /dev/null +++ b/server/volview_server/rpc_server.py @@ -0,0 +1,284 @@ +from __future__ import annotations + +import time +import asyncio +import uuid +import logging +from typing import Any, Union, List, Generator, Dict, Tuple, Optional +from contextvars import ContextVar +from dataclasses import dataclass, field, asdict +from urllib.parse import parse_qs + +from socketio.exceptions import ConnectionRefusedError + +from volview_server.api import RpcApi +from volview_server.chunking import ChunkingAsyncServer + +RPC_CALL_EVENT = "rpc:call" +RPC_RESULT_EVENT = "rpc:result" +STREAM_CALL_EVENT = "stream:call" +STREAM_RESULT_EVENT = "stream:result" + +CLIENT_ID_QS = "clientId" +FUTURE_TIMEOUT = 5 * 60 # seconds + +current_server: ContextVar[RpcServer] = ContextVar("server") +current_client_id: ContextVar[str] = ContextVar("client_id") + +logger = logging.getLogger("volview_server.rpc_server") + + +@dataclass +class RpcCall: + rpcId: str + name: str + args: List[Any] + + +def validate_rpc_call(data: Any): + if type(data) is not dict: + raise TypeError("data is not a dict") + + rpc_id = data["rpcId"] + if type(rpc_id) is not str: + raise TypeError("rpc ID is not a str") + + name = data["name"] + if type(name) is not str: + raise TypeError("rpc name is not a str") + + args = data.get("args", None) or [] + if type(args) is not list: + raise TypeError("rpc args is not a list") + + return rpc_id, name, args + + +@dataclass +class RpcOkResult: + rpcId: str = field(default="", init=False) + ok: bool = field(default=True, init=False) + data: Any = field(default=None) + + +@dataclass +class RpcErrorResult: + rpcId: str = field(default="", init=False) + ok: bool = field(default=False, init=False) + error: str + + +def validate_rpc_result(result: Any): + if type(result) is not dict: + raise TypeError("Result is not a dict") + return ( + result["rpcId"], + result["ok"], + result.get("data", None), + result.get("error", None), + ) + + +@dataclass +class StreamDataResult(RpcOkResult): + done: bool = field(default=False) + + +RpcResult = Union[RpcOkResult, RpcErrorResult] + + +@dataclass +class FutureMetadata: + transform_args: bool = True + creation_time: int = field(default_factory=lambda: int(time.time())) + + +class RpcServer: + """Implements a bidirectional RPC mechanism. + + When you are done with the server, be sure to invoke server.teardown() in + order to stop all background tasks. + """ + + api: RpcApi + # sid -> client ID + clients: Dict[str, str] + # client ID -> session object + sessions: Dict[str, Any] + future_timeout: int + + def __init__( + self, + api: RpcApi, + future_timeout: int = FUTURE_TIMEOUT, + **kwargs, + ): + """ + Keyword Arguments: + - future_timeout: number of seconds before an inflight RPC is ignored. + """ + self.sio = ChunkingAsyncServer(**kwargs) + self.api = api + self.clients = {} + self.sessions = {} + self.future_timeout = future_timeout + + self._inflight_rpcs: Dict[str, Tuple[asyncio.Future, FutureMetadata]] = {} + self._cleanup_task = None + + @self.sio.event + def connect(sid: str, environ: dict): + self._on_connect(sid, environ) + + @self.sio.event + def disconnect(sid: str): + self._on_disconnect(sid) + + @self.sio.on(RPC_CALL_EVENT) + async def on_rpc_call(sid: str, data: Any): + await self._on_rpc_call(self.clients[sid], data) + + @self.sio.on(STREAM_CALL_EVENT) + async def on_stream_call(sid: str, data: Any): + await self._on_stream_call(self.clients[sid], data) + + @self.sio.on(RPC_RESULT_EVENT) + async def on_rpc_result(sid: str, data: Any): + await self._on_rpc_result(self.clients[sid], data) + + def setup(self): + """Runs setup and starts background tasks. + + Needs to be run from inside an async context. + """ + self._cleanup_task = asyncio.create_task(self.cleanup()) + + async def teardown(self): + """Clean up, including stopping background tasks.""" + if self._cleanup_task: + self._cleanup_task.cancel() + self._inflight_rpcs.clear() + for sid in self.clients.keys(): + await self.sio.disconnect(sid) + + async def cleanup(self): + while True: + await asyncio.sleep(self.future_timeout) + + now = int(time.time()) + for rpc_id, (future, metadata) in self._inflight_rpcs.items(): + if ( + not future.done() + and now - metadata.creation_time >= self.future_timeout + ): + del self._inflight_rpcs[rpc_id] + + async def call_client( + self, + rpc_name: str, + args: List[Any] = None, + client_id: Optional[str] = None, + transform_args: bool = True, + ): + """Calls an RPC method on a given client or current client. + + Does not support invoking client generators. + + args: supplies a list of arguments to be sent to the client. + client_id: targets a specific client. + transform_args: whether to apply transforms to the request args and response result. + """ + rpc_id = uuid.uuid4().hex + client_id = client_id or current_client_id.get() + + future: asyncio.Future = asyncio.Future() + + if transform_args: + args = [self.api.serialize_object(obj) for obj in args] + + info = FutureMetadata(transform_args=transform_args) + self._inflight_rpcs[rpc_id] = (future, info) + + await self.sio.emit( + RPC_CALL_EVENT, + asdict(RpcCall(rpc_id, rpc_name, args)), + room=client_id, + ) + return await future + + async def _on_rpc_result(self, client_id: str, result: Any): + try: + rpc_id, ok, data, error = validate_rpc_result(result) + future, info = self._inflight_rpcs[rpc_id] + except (TypeError, KeyError): + # ignore invalid RPC result + logger.error("Received invalid RPC result") + else: + del self._inflight_rpcs[rpc_id] + if ok: + if info.transform_args: + data = self.api.deserialize_object(data) + future.set_result(data) + else: + future.set_exception(Exception(error)) + + def _on_connect(self, sid: str, environ: dict): + qs = parse_qs(environ.get("QUERY_STRING", "")) + (client_id,) = qs.get(CLIENT_ID_QS, [None]) + if not client_id: + raise ConnectionRefusedError("No clientId provided") + + self.clients[sid] = client_id + # add to room based on client_id + self.sio.enter_room(sid, client_id) + + def _on_disconnect(self, sid: str): + client_id = self.clients[sid] + self.sio.leave_room(sid, client_id) + + async def _on_rpc_call(self, client_id: str, data: Any): + try: + rpc_id, name, args = validate_rpc_call(data) + except TypeError: + logger.error("Received invalid RPC call") + else: + result = await self._try_rpc_call(client_id, name, args) + result.rpcId = rpc_id + await self.sio.emit(RPC_RESULT_EVENT, asdict(result), room=client_id) + + async def _try_rpc_call( + self, client_id: str, name: str, args: List[Any] + ) -> RpcResult: + current_server.set(self) + current_client_id.set(client_id) + + try: + result = await self.api.invoke_rpc(name, *args) + return RpcOkResult(result) + except Exception as exc: + logger.exception(f"RPC {name} raised an exception", stack_info=True) + return RpcErrorResult(str(exc)) + + async def _on_stream_call(self, client_id: str, data: Any): + try: + rpc_id, name, args = validate_rpc_call(data) + except TypeError: + logger.error("Received invalid RPC call") + return + + async for result in self._try_generate_stream(client_id, name, args): + result.rpcId = rpc_id + await self.sio.emit(STREAM_RESULT_EVENT, asdict(result), room=client_id) + + async def _try_generate_stream( + self, client_id: str, name: str, args: List[Any] + ) -> Generator[RpcResult, None, None]: + current_server.set(self) + current_client_id.set(client_id) + + try: + async for data in self.api.invoke_stream(name, *args): + yield StreamDataResult(done=False, data=data) + yield StreamDataResult(done=True) + except Exception as exc: + yield RpcErrorResult(str(exc)) diff --git a/server/volview_server/session.py b/server/volview_server/session.py new file mode 100644 index 000000000..b7f44b55c --- /dev/null +++ b/server/volview_server/session.py @@ -0,0 +1,31 @@ +from typing import Callable, Any, TypeVar + +from volview_server.rpc_server import current_server, current_client_id + +T = TypeVar("T") + + +def get_current_session(default_factory: Callable[[], T] = None) -> T: + """Retrieves the current session object for the current client. + + This should only be called from inside an RPC endpoint. + + If no session exists for the current client and `default_factory` is + provided, then `default_factory` will be invoked and a new session object + will be returned. + + If no `default_factory` is provided, `None` will be returned. + + If there is no current client, then this will raise a RuntimeError. + """ + server = current_server.get() + if not server: + raise RuntimeError("No current server") + + client_id = current_client_id.get() + if not client_id: + raise RuntimeError("No no current client") + + if client_id not in server.sessions and default_factory: + server.sessions[client_id] = default_factory() + return server.sessions.get(client_id, None) diff --git a/server/volview_server/transformers/__init__.py b/server/volview_server/transformers/__init__.py new file mode 100644 index 000000000..b167a487b --- /dev/null +++ b/server/volview_server/transformers/__init__.py @@ -0,0 +1,33 @@ +from typing import Callable, List, Any + +Transformer = Callable[[Any], Any] + +from volview_server.transformers.image_data import ( + convert_itk_to_vtkjs_image, + convert_vtkjs_to_itk_image, +) + + +def pipe(input, *fns: List[Transformer]): + intermediate = input + for fn in fns: + intermediate = fn(intermediate) + return intermediate + + +def transform_object(input: Any, transform: Callable): + output = transform(input) + + if isinstance(output, list) or isinstance(output, tuple): + return [transform_object(item, transform) for item in output] + + if isinstance(output, dict): + return { + key: transform_object(value, transform) for key, value in output.items() + } + + return output + + +default_serializers: List[Transformer] = [convert_itk_to_vtkjs_image] +default_deserializers: List[Transformer] = [convert_vtkjs_to_itk_image] diff --git a/server/volview_server/transformers/exceptions.py b/server/volview_server/transformers/exceptions.py new file mode 100644 index 000000000..c6ed05262 --- /dev/null +++ b/server/volview_server/transformers/exceptions.py @@ -0,0 +1,2 @@ +class ConvertError(Exception): + """An error occurred while converting.""" diff --git a/server/volview_server/transformers/image_data.py b/server/volview_server/transformers/image_data.py new file mode 100644 index 000000000..8abc34206 --- /dev/null +++ b/server/volview_server/transformers/image_data.py @@ -0,0 +1,117 @@ +from typing import Dict + +import itk +import numpy as np + +from volview_server.transformers.itk_helpers import ( + itk_image_pixel_type_to_js, + TYPE_ARRAY_JS_TO_NUMPY, +) +from volview_server.transformers.exceptions import ConvertError + + +def vtk_to_itk_image(vtk_image: Dict): + """Converts a serialized vtkImageData to an ITK image.""" + if not isinstance(vtk_image, dict): + raise ConvertError("Provided vtk_image is not a dict") + if vtk_image.get("vtkClass", None) != "vtkImageData": + raise ConvertError("Provided vtk_image is not a serialized vtkImageData") + + try: + extent = vtk_image["extent"] + # numpy indexes in ZYX order, where X varies the fastest + dims = [ + extent[5] - extent[4] + 1, + extent[3] - extent[2] + 1, + extent[1] - extent[0] + 1, + ] + if type(vtk_image["direction"]) is list: + direction = np.array(vtk_image["direction"], dtype=float) + elif type(vtk_image["direction"] is bytes): + direction = np.frombuffer(vtk_image["direction"], dtype=float) + else: + raise TypeError("Cannot parse image direction") + + # vtk.js direction matrix is column-major + direction = direction.reshape((3, 3)).transpose() + + pixel_data_array = vtk_image["pointData"]["arrays"][0]["data"] + pixel_js_datatype = pixel_data_array["dataType"] + pixel_dtype = TYPE_ARRAY_JS_TO_NUMPY.get(pixel_js_datatype) + if not pixel_dtype: + raise TypeError( + f"Failed to map vtkImageData pixel type {pixel_js_datatype}" + ) + + pixel_data = np.frombuffer(pixel_data_array["values"], dtype=pixel_dtype) + itk_image = itk.GetImageFromArray(np.reshape(pixel_data, dims)) + + # https://discourse.itk.org/t/set-image-direction-from-numpy-array/844/10 + itk_image.SetDirection(itk.matrix_from_array(direction)) + itk_image.SetOrigin(vtk_image["origin"]) + itk_image.SetSpacing(vtk_image["spacing"]) + return itk_image + + except Exception as exc: + raise ConvertError("Cannot convert provided vtk_image to an ITK image") from exc + + +def itk_to_vtk_image(itk_image): + """Converts an ITK image to a serialized vtkImageData for vtk.js.""" + if not type(itk_image).__name__.startswith("itkImage"): + raise ConvertError("Provided data is not an ITK image") + + size = list(itk_image.GetLargestPossibleRegion().GetSize()) + values = itk.GetArrayViewFromImage(itk_image).flatten(order="C") + return { + "vtkClass": "vtkImageData", + "dataDescription": 8, + "direction": list( + itk.GetArrayFromVnlMatrix( + itk_image.GetDirection().GetVnlMatrix().as_matrix() + ) + .transpose() # vtk.js is column-major, ITK is row-major + .flatten() + ), + "extent": [ + 0, + size[0] - 1, + 0, + size[1] - 1, + 0, + size[2] - 1, + ], + "spacing": list(itk_image.GetSpacing()), + "origin": list(itk_image.GetOrigin()), + "pointData": { + "vtkClass": "vtkDataSetAttributes", + # the index of the only array + "activeScalars": 0, + "arrays": [ + { + "data": { + "vtkClass": "vtkDataArray", + "size": len(values), + "values": values.tobytes(), + "dataType": itk_image_pixel_type_to_js(itk_image), + "numberOfComponents": itk_image.GetNumberOfComponentsPerPixel(), + "name": "Scalars", + } + } + ], + }, + } + + +def convert_vtkjs_to_itk_image(obj): + try: + return vtk_to_itk_image(obj) + except ConvertError: + return obj + + +def convert_itk_to_vtkjs_image(obj): + try: + return itk_to_vtk_image(obj) + except ConvertError: + return obj diff --git a/server/volview_server/transformers/itk_helpers.py b/server/volview_server/transformers/itk_helpers.py new file mode 100644 index 000000000..34ad5300d --- /dev/null +++ b/server/volview_server/transformers/itk_helpers.py @@ -0,0 +1,34 @@ +import struct +import numpy as np + +TYPE_ARRAY_JS_TO_NUMPY = { + "Int8Array": np.int8, + "Int8ClampedArray": np.int8, + "Int16Array": np.int16, + "Int32Array": np.int32, + "Uint8Array": np.uint8, + "Uint16Array": np.uint16, + "Uint32Array": np.uint32, + "Float32Array": np.float32, + "Float64Array": np.float64, +} + +TYPE_ARRAY_ITKCOMP_TO_JS = { + "SC": "Int8Array", + "UC": "Uint8Array", + "SS": "Int16Array", + "US": "Uint16Array", + "SI": "Int32Array", + "UI": "Uint32Array", + "F": "Float32Array", + "D": "Float64Array", + "B": "Uint8Array", +} + + +def itk_image_pixel_type_to_js(itk_image): + """Gets the JS pixel type from an ITK image.""" + component_str = repr(itk_image).split("itkImagePython.")[1].split(";")[0][8:] + # TODO handle mangling as per + # https://github.com/InsightSoftwareConsortium/itk-jupyter-widgets/blob/master/itkwidgets/trait_types.py#L49 + return TYPE_ARRAY_ITKCOMP_TO_JS[component_str[:-1]] diff --git a/server/volview_server/volview_api.py b/server/volview_server/volview_api.py new file mode 100644 index 000000000..f60e04f50 --- /dev/null +++ b/server/volview_server/volview_api.py @@ -0,0 +1,34 @@ +import socketio + +from volview_server.rpc_server import RpcServer +from volview_server.chunking import CHUNK_SIZE +from volview_server.api import RpcApi + + +class VolViewApi(RpcApi): + def __call__(self, app, server_kwargs={}, asgi_kwargs={}): + """Adds ASGI middleware for accessing VolView's API. + + Args: + - app: the ASGI app to extend + - server_kwargs: RpcServer options + - asgi_kwargs: socketio.ASGIApp options + + RPCServer options: + https://python-socketio.readthedocs.io/en/latest/api.html#asyncserver-class + + ASGIApp options: + https://python-socketio.readthedocs.io/en/latest/api.html#asgiapp-class + """ + server = RpcServer( + self, + # python-socketio kwargs + async_mode="asgi", + async_handlers=True, + # allow upstream handling of CORS + cors_allowed_origins=[], + # default to chunk size + max_http_buffer_size=CHUNK_SIZE, + **server_kwargs, + ) + return socketio.ASGIApp(server.sio, app, **asgi_kwargs) diff --git a/src/components/App.vue b/src/components/App.vue index 65ffb832d..170094057 100644 --- a/src/components/App.vue +++ b/src/components/App.vue @@ -98,6 +98,13 @@ + { + switch (serverStore.connState) { + case ConnectionState.Connected: + return 'mdi-lan-check'; + case ConnectionState.Disconnected: + return 'mdi-lan-disconnect'; + case ConnectionState.Pending: + return 'mdi-lan-pending'; + default: + throw new Error('Invalid connection state'); + } + }); + + onMounted(() => { + serverStore.connect(); + }); + + // --- --- // const hasData = computed(() => imageStore.idList.length > 0); const messageCount = computed(() => messageStore.importantMessages.length); @@ -498,6 +528,8 @@ export default defineComponent({ openFiles, hasData, saveUrl, + serverConnectionIcon, + serverUrl, }; }, }); diff --git a/src/components/ModulePanel.vue b/src/components/ModulePanel.vue index 47190f862..3ae128be5 100644 --- a/src/components/ModulePanel.vue +++ b/src/components/ModulePanel.vue @@ -34,7 +34,7 @@ + +