diff --git a/README.md b/README.md index 6b194b8..8111121 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ -# pydase (Python Data Service) +# pydase [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Documentation Status](https://readthedocs.org/projects/pydase/badge/?version=latest)](https://pydase.readthedocs.io/en/latest/?badge=latest) -`pydase` is a Python library for creating data service servers with integrated web and RPC servers. It's designed to handle the management of data structures, automated tasks, and callbacks, and provides built-in functionality for serving data over different protocols. +`pydase` is a Python library designed to streamline the creation of services that interface with devices and data. It offers a unified API, simplifying the process of data querying and device interaction. Whether you're managing lab sensors, network devices, or any abstract data entity, `pydase` facilitates rapid service development and deployment. - [Features](#features) - [Installation](#installation) @@ -45,11 +45,11 @@ ## Features -- [Simple data service definition through class-based interface](#defining-a-dataService) -- [Integrated web interface for interactive access and control of your data service](#accessing-the-web-interface) +- [Simple service definition through class-based interface](#defining-a-dataService) +- [Integrated web interface for interactive access and control of your service](#accessing-the-web-interface) - [Support for programmatic control and interaction with your service](#connecting-to-the-service-via-python-client) - [Component system bridging Python backend with frontend visual representation](#understanding-the-component-system) -- [Customizable styling for the web interface through user-defined CSS](#customizing-web-interface-style) +- [Customizable styling for the web interface](#customizing-web-interface-style) - [Saving and restoring the service state for service persistence](#understanding-service-persistence) - [Automated task management with built-in start/stop controls and optional autostart](#understanding-tasks-in-pydase) - [Support for units](#understanding-units-in-pydase) @@ -510,96 +510,96 @@ In this example, `MySlider` overrides the `min`, `max`, `step_size`, and `value` - Accessing parent class resources in `NumberSlider` - In scenarios where you need the slider component to interact with or access resources from its parent class, you can achieve this by passing a callback function to it. This method avoids directly passing the entire parent class instance (`self`) and offers a more encapsulated approach. The callback function can be designed to utilize specific attributes or methods of the parent class, allowing the slider to perform actions or retrieve data in response to slider events. + In scenarios where you need the slider component to interact with or access resources from its parent class, you can achieve this by passing a callback function to it. This method avoids directly passing the entire parent class instance (`self`) and offers a more encapsulated approach. The callback function can be designed to utilize specific attributes or methods of the parent class, allowing the slider to perform actions or retrieve data in response to slider events. - Here's an illustrative example: + Here's an illustrative example: - ```python - from collections.abc import Callable + ```python + from collections.abc import Callable - import pydase - import pydase.components + import pydase + import pydase.components - class MySlider(pydase.components.NumberSlider): - def __init__( - self, - value: float, - on_change: Callable[[float], None], - ) -> None: - super().__init__(value=value) - self._on_change = on_change + class MySlider(pydase.components.NumberSlider): + def __init__( + self, + value: float, + on_change: Callable[[float], None], + ) -> None: + super().__init__(value=value) + self._on_change = on_change - # ... other properties ... + # ... other properties ... - @property - def value(self) -> float: - return self._value + @property + def value(self) -> float: + return self._value - @value.setter - def value(self, new_value: float) -> None: - if new_value < self._min or new_value > self._max: - raise ValueError("Value is either below allowed min or above max value.") - self._value = new_value - self._on_change(new_value) + @value.setter + def value(self, new_value: float) -> None: + if new_value < self._min or new_value > self._max: + raise ValueError("Value is either below allowed min or above max value.") + self._value = new_value + self._on_change(new_value) - class MyService(pydase.DataService): - def __init__(self) -> None: - self.voltage = MySlider( - 5, - on_change=self.handle_voltage_change, - ) + class MyService(pydase.DataService): + def __init__(self) -> None: + self.voltage = MySlider( + 5, + on_change=self.handle_voltage_change, + ) - def handle_voltage_change(self, new_voltage: float) -> None: - print(f"Voltage changed to: {new_voltage}") - # Additional logic here + def handle_voltage_change(self, new_voltage: float) -> None: + print(f"Voltage changed to: {new_voltage}") + # Additional logic here - if __name__ == "__main__": - service_instance = MyService() - my_service.voltage.value = 7 # Output: "Voltage changed to: 7" - pydase.Server(service_instance).run() - ``` + if __name__ == "__main__": + service_instance = MyService() + my_service.voltage.value = 7 # Output: "Voltage changed to: 7" + pydase.Server(service_instance).run() + ``` - Incorporating units in `NumberSlider` - The `NumberSlider` is capable of [displaying units](#understanding-units-in-pydase) alongside values, enhancing its usability in contexts where unit representation is crucial. When utilizing `pydase.units`, you can specify units for the slider's value, allowing the component to reflect these units in the frontend. + The `NumberSlider` is capable of [displaying units](#understanding-units-in-pydase) alongside values, enhancing its usability in contexts where unit representation is crucial. When utilizing `pydase.units`, you can specify units for the slider's value, allowing the component to reflect these units in the frontend. - Here's how to implement a `NumberSlider` with unit display: + Here's how to implement a `NumberSlider` with unit display: - ```python - import pydase - import pydase.components - import pydase.units as u + ```python + import pydase + import pydase.components + import pydase.units as u - class MySlider(pydase.components.NumberSlider): - def __init__( - self, - value: u.Quantity = 0.0 * u.units.V, - ) -> None: - super().__init__(value) + class MySlider(pydase.components.NumberSlider): + def __init__( + self, + value: u.Quantity = 0.0 * u.units.V, + ) -> None: + super().__init__(value) - @property - def value(self) -> u.Quantity: - return self._value + @property + def value(self) -> u.Quantity: + return self._value - @value.setter - def value(self, value: u.Quantity) -> None: - if value.m < self._min or value.m > self._max: - raise ValueError("Value is either below allowed min or above max value.") - self._value = value + @value.setter + def value(self, value: u.Quantity) -> None: + if value.m < self._min or value.m > self._max: + raise ValueError("Value is either below allowed min or above max value.") + self._value = value - class MyService(pydase.DataService): - def __init__(self) -> None: - super().__init__() - self.voltage = MySlider() + class MyService(pydase.DataService): + def __init__(self) -> None: + super().__init__() + self.voltage = MySlider() - if __name__ == "__main__": - service_instance = MyService() - service_instance.voltage.value = 5 * u.units.V - print(service_instance.voltage.value) # Output: 5 V - pydase.Server(service_instance).run() - ``` + if __name__ == "__main__": + service_instance = MyService() + service_instance.voltage.value = 5 * u.units.V + print(service_instance.voltage.value) # Output: 5 V + pydase.Server(service_instance).run() + ``` #### `ColouredEnum` @@ -922,8 +922,8 @@ import pydase class Device(pydase.DataService): name = "My Device" - some_float = 1.0 - some_int = 1 + temperature = 1.0 + power = 1 class Service(pydase.DataService): @@ -946,11 +946,13 @@ with the following `web_settings.json` "device.name": { "display": false }, - "device.some_float": { + "device.power": { + "displayName": "Power", "displayOrder": 1 }, - "device.some_int": { - "displayOrder": 0 + "device.temperature": { + "displayName": "Temperature", + "displayOrder": 0 }, "state": { "displayOrder": 0 @@ -960,7 +962,7 @@ with the following `web_settings.json` looks like this: -![Tailoring frontend component layout](./docs/images/Tailoring_frontend_component_layout.png) +![Tailoring frontend component layout](./docs/images/Tailoring frontend component layout.png) ### Specifying a Custom Frontend Source diff --git a/docs/dev-guide/Adding_Components.md b/docs/dev-guide/Adding_Components.md index 54ec087..ab5b137 100644 --- a/docs/dev-guide/Adding_Components.md +++ b/docs/dev-guide/Adding_Components.md @@ -111,7 +111,7 @@ Write the React component code, following the structure and patterns used in exi For example, for the `Image` component, a template could look like this: -```tsx +```ts import React, { useEffect, useRef, useState } from 'react'; import { Card, Collapse, Image } from 'react-bootstrap'; import { DocStringComponent } from './DocStringComponent'; @@ -203,8 +203,7 @@ There are two different events a component might want to trigger: updating an at For illustration, take the `ButtonComponent`. When the button state changes, we want to send this update to the backend: - ```tsx - // file: frontend/src/components/ButtonComponent.tsx + ```ts title="frontend/src/components/ButtonComponent.tsx" // ... (import statements) type ButtonComponentProps = { @@ -249,7 +248,7 @@ There are two different events a component might want to trigger: updating an at To see how to use the `MethodComponent` in your component, have a look at the `DeviceConnection.tsx` file. Here is an example that demonstrates the usage of the `runMethod` function (also, have a look at the `MethodComponent.tsx` file): - ```tsx + ```ts title="frontend/src/components/_YourComponent_.tsx" import { runMethod } from '../socket'; // ... (other imports) @@ -287,9 +286,7 @@ The `GenericComponent` is responsible for rendering different types of component At the beginning of the `GenericComponent` file, import the newly created `ImageComponent`: -```tsx -// file: frontend/src/components/GenericComponent.tsx - +```ts title="frontend/src/components/GenericComponent.tsx" import { ImageComponent } from './ImageComponent'; ``` @@ -299,7 +296,7 @@ Update the `AttributeType` type definition to include the new type for the `Imag For example, if the new attribute type is `'Image'` (which should correspond to the name of the backend component class), you can add it to the union: -```tsx +```ts type AttributeType = | 'str' | 'bool' @@ -318,7 +315,7 @@ type AttributeType = Inside the `GenericComponent` function, add a new conditional branch to render the `ImageComponent` when the attribute type is `'Image'`: -```tsx +```ts } else if (attribute.type === 'Image') { return ( element).join('.'); useEffect(() => { diff --git a/docs/images/Tailoring frontend component layout.png b/docs/images/Tailoring frontend component layout.png new file mode 100644 index 0000000..fa4cf22 Binary files /dev/null and b/docs/images/Tailoring frontend component layout.png differ diff --git a/docs/images/Tailoring_frontend_component_layout.png b/docs/images/Tailoring_frontend_component_layout.png deleted file mode 100644 index 02c34c1..0000000 Binary files a/docs/images/Tailoring_frontend_component_layout.png and /dev/null differ diff --git a/docs/user-guide/Interaction.md b/docs/user-guide/Interaction.md deleted file mode 100644 index fcd9114..0000000 --- a/docs/user-guide/Interaction.md +++ /dev/null @@ -1,9 +0,0 @@ -# Interacting with `pydase` Services - -`pydase` offers multiple ways for users to interact with the services they create, providing flexibility and convenience for different use cases. This section outlines the primary interaction methods available, including RESTful APIs, Python client based on Socket.IO, and the auto-generated frontend. - -{% - include-markdown "./RESTful API.md" - heading-offset=1 -%} - diff --git a/docs/user-guide/RESTful API.md b/docs/user-guide/RESTful API.md deleted file mode 100644 index 47939b7..0000000 --- a/docs/user-guide/RESTful API.md +++ /dev/null @@ -1,15 +0,0 @@ -# RESTful API - -The `pydase` RESTful API provides access to various functionalities through specific routes. Below are the available endpoints for version 1 (`v1`) of the API, including details on request methods, parameters, and example usage. - -## Base URL - -``` -http://:/api/v1/ -``` - - - -## Change Log - -- v0.9.0: Initial release with `get_value`, `update_value`, and `trigger_method` endpoints. diff --git a/docs/user-guide/interaction/Auto-generated Frontend.md b/docs/user-guide/interaction/Auto-generated Frontend.md new file mode 100644 index 0000000..a8b6c29 --- /dev/null +++ b/docs/user-guide/interaction/Auto-generated Frontend.md @@ -0,0 +1,167 @@ +# Auto-generated Frontend + +`pydase` automatically generates a frontend interface based on your service definition, representing the current state and controls of the service. +It simplifies the process of visualization and control of the data and devices managed by your `pydase` service, making it accessible to both developers and end-users. + +Through the integration of Socket.IO, the frontend provides real-time updates, reflecting changes as they occur and allowing for immediate interaction with the backend. + + +## Accessing the Frontend + +You can access the auto-generated frontend by navigating to the hostname of the device the service is hosted on, followed by the exposed port: + +``` +http://:/ +``` + +The frontend uses a component-based approach, representing various data types and control mechanisms as distinct UI components. For more information about this, please refer to [Components Guide](../Components.md). + +## Customization Options + +`pydase` allows you to enhance the user experience by customizing the web interface's appearance through + +1. a custom CSS file, and +2. tailoring the frontend component layout and display style. + +For more advanced customization, you can provide a completely custom frontend source. + +### Custom CSS Styling + +You can apply your own styles globally across the web interface by passing a custom CSS file to the server during initialization. +Here's how you can use this feature: + +1. Prepare your custom CSS file with the desired styles. + +2. When initializing your server, use the `css` parameter of the `Server` class to specify the path to your custom CSS file. + +```python +from pydase import Server, DataService + + +class MyService(DataService): + # ... your service definition ... + + +if __name__ == "__main__": + service = MyService() + server = Server(service, css="path/to/your/custom.css").run() +``` + +This will apply the styles defined in `custom.css` to the web interface, allowing you to maintain branding consistency or improve visual accessibility. + +Please ensure that the CSS file path is accessible from the server's running location. Relative or absolute paths can be used depending on your setup. + +### Tailoring Frontend Component Layout + +You can customize the display names, visibility, and order of components via the `web_settings.json` file. +Each key in the file corresponds to the full access path of public attributes, properties, and methods of the exposed service, using dot-notation. + +- **Custom Display Names**: Modify the `"displayName"` value in the file to change how each component appears in the frontend. +- **Control Component Visibility**: Utilize the `"display"` key-value pair to control whether a component is rendered in the frontend. Set the value to `true` to make the component visible or `false` to hide it. +- **Adjustable Component Order**: The `"displayOrder"` values determine the order of components. Alter these values to rearrange the components as desired. The value defaults to [`Number.MAX_SAFE_INTEGER`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER). + +The `web_settings.json` file will be stored in the directory specified by `SERVICE_CONFIG_DIR`. You can generate a `web_settings.json` file by setting the `GENERATE_WEB_SETTINGS` to `True`. For more information, see the [configuration section](#configuring-pydase-via-environment-variables). + +For example, styling the following service + +```python +import pydase + + +class Device(pydase.DataService): + name = "My Device" + temperature = 1.0 + power = 1 + + +class Service(pydase.DataService): + device = Device() + state = "RUNNING" + + +if __name__ == "__main__": + pydase.Server(Service()).run() +``` + +with the following `web_settings.json` + +```json +{ + "device": { + "displayName": "My Device", + "displayOrder": 1 + }, + "device.name": { + "display": false + }, + "device.power": { + "displayName": "Power", + "displayOrder": 1 + }, + "device.temperature": { + "displayName": "Temperature", + "displayOrder": 0 + }, + "state": { + "displayOrder": 0 + } +} +``` + +looks like this: + +![Tailoring frontend component layout](../../images/Tailoring frontend component layout.png) + +### Specifying a Custom Frontend Source + +To further customize your web interface, you can provide a custom frontend source. +By specifying the `frontend_src` parameter when initializing the server, you can host a tailored frontend application: + +```python +from pathlib import Path + +import pydase + + +class MyService(pydase.DataService): + # Service definition + + +if __name__ == "__main__": + service = MyService() + pydase.Server( + service, + frontend_src=Path("path/to/your/frontend/directory"), + ).run() +``` + +`pydase` expects a directory structured as follows: + +```bash title="Frontend directory structure" + +├── assets +│   └── ... +└── index.html +``` + +Any CSS, js, image or other files need to be put into the assets folder for the web server to be able to provide access to it. + +#### Example: Custom React Frontend + +You can use vite to generate a react app template: + +```bash +npm create vite@latest my-react-app -- --template react +``` + +*TODO: Add some useful information here...* + +To deploy the custom react frontend, build it with + +```bash +npm run build +``` + +and pass the relative path of the output directory to the `frontend_src` parameter of the `pydase.Server`. + +**Note** that you have to make sure that all the generated files (except the `index.html`) are in the `assets` folder. In the react app, you can achieve this by not using the `public` folder, but instead using e.g. `src/assets`. diff --git a/docs/user-guide/interaction/Python Client.md b/docs/user-guide/interaction/Python Client.md new file mode 100644 index 0000000..ea844f2 --- /dev/null +++ b/docs/user-guide/interaction/Python Client.md @@ -0,0 +1,45 @@ +# Python Client + +You can connect to the service using the `pydase.Client`. Below is an example of how to establish a connection to a service and interact with it: + +```python +import pydase + +# Replace the hostname and port with the IP address and the port of the machine +# where the service is running, respectively +client_proxy = pydase.Client(hostname="", port=8001).proxy + +# Interact with the service attributes as if they were local +client_proxy.voltage = 5.0 +print(client_proxy.voltage) # Expected output: 5.0 +``` + +This example demonstrates setting and retrieving the `voltage` attribute through the client proxy. +The proxy acts as a local representative of the remote service, enabling straightforward interaction. + +The proxy class dynamically synchronizes with the server's exposed attributes. This synchronization allows the proxy to be automatically updated with any attributes or methods that the server exposes, essentially mirroring the server's API. This dynamic updating enables users to interact with the remote service as if they were working with a local object. + +## Tab Completion Support + +In interactive environments such as Python interpreters and Jupyter notebooks, the proxy class supports tab completion, which allows users to explore available methods and attributes. + +## Integration within Other Services + +You can also integrate a client proxy within another service. Here's how you can set it up: + +```python +import pydase + +class MyService(pydase.DataService): + # Initialize the client without blocking the constructor + proxy = pydase.Client(hostname="", port=8001, block_until_connected=False).proxy + +if __name__ == "__main__": + service = MyService() + # Create a server that exposes this service; adjust the web_port as needed + server = pydase.Server(service, web_port=8002). run() +``` + +In this setup, the `MyService` class has a `proxy` attribute that connects to a `pydase` service located at `:8001`. +The `block_until_connected=False` argument allows the service to start up even if the initial connection attempt fails. +This configuration is particularly useful in distributed systems where services may start in any order. diff --git a/docs/user-guide/interaction/RESTful API.md b/docs/user-guide/interaction/RESTful API.md new file mode 100644 index 0000000..ecd76de --- /dev/null +++ b/docs/user-guide/interaction/RESTful API.md @@ -0,0 +1,14 @@ +# RESTful API + +The `pydase` RESTful API allows for standard HTTP-based interactions and provides access to various functionalities through specific routes. This is particularly useful for integrating `pydase` services with other applications or for scripting and automation. + +To help developers understand and utilize the API, we provide an OpenAPI specification. This specification describes the available endpoints and corresponding request/response formats. + +## OpenAPI Specification +**Base URL**: + +``` +http://:/api/ +``` + + diff --git a/docs/user-guide/interaction/main.md b/docs/user-guide/interaction/main.md new file mode 100644 index 0000000..8c6b19b --- /dev/null +++ b/docs/user-guide/interaction/main.md @@ -0,0 +1,81 @@ +# Interacting with `pydase` Services + +`pydase` offers multiple ways for users to interact with the services they create, providing flexibility and convenience for different use cases. This section outlines the primary interaction methods available, including an auto-generated frontend, a RESTful API, and a Python client based on Socket.IO. + +{% + include-markdown "./Auto-generated Frontend.md" + heading-offset=1 +%} + +{% + include-markdown "./RESTful API.md" + heading-offset=1 +%} + +{% + include-markdown "./Python Client.md" + heading-offset=1 +%} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/user-guide/openapi.yaml b/docs/user-guide/interaction/openapi.yaml similarity index 99% rename from docs/user-guide/openapi.yaml rename to docs/user-guide/interaction/openapi.yaml index 5073456..b2c2def 100644 --- a/docs/user-guide/openapi.yaml +++ b/docs/user-guide/interaction/openapi.yaml @@ -1,6 +1,7 @@ openapi: 3.1.0 info: - title: Pydase RESTful API + version: 1.0.0 + title: pydase API tags: - name: /api/v1 description: Version 1 diff --git a/mkdocs.yml b/mkdocs.yml index 53450e3..3b257d2 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -6,7 +6,7 @@ nav: - Getting Started: getting-started.md - User Guide: - Components Guide: user-guide/Components.md - - Interacting with Services: user-guide/Interaction.md + - Interacting with pydase Services: user-guide/interaction/main.md - Developer Guide: - Developer Guide: dev-guide/README.md - API Reference: dev-guide/api.md @@ -17,7 +17,10 @@ nav: - Contributing: about/contributing.md - License: about/license.md -theme: readthedocs +theme: + name: material + features: + - content.code.copy extra_css: - css/extra.css @@ -27,11 +30,13 @@ markdown_extensions: - toc: permalink: true - pymdownx.highlight: + use_pygments: true anchor_linenums: true + line_spans: __span + pygments_lang_class: true - pymdownx.snippets - pymdownx.superfences - # - pymdownx.highlight: - # - pymdownx.inlinehilite + - pymdownx.inlinehilite plugins: diff --git a/poetry.lock b/poetry.lock index d1aea50..657dc47 100644 --- a/poetry.lock +++ b/poetry.lock @@ -178,6 +178,20 @@ tests = ["attrs[tests-no-zope]", "zope-interface"] tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] +[[package]] +name = "babel" +version = "2.15.0" +description = "Internationalization utilities" +optional = false +python-versions = ">=3.8" +files = [ + {file = "Babel-2.15.0-py3-none-any.whl", hash = "sha256:08706bdad8d0a3413266ab61bd6c34d0c28d6e1e7badf40a2cebe67644e2e1fb"}, + {file = "babel-2.15.0.tar.gz", hash = "sha256:8daf0e265d05768bc6c7a314cf1321e9a123afc328cc635c18622a2f30a04413"}, +] + +[package.extras] +dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] + [[package]] name = "beautifulsoup4" version = "4.12.3" @@ -1110,6 +1124,46 @@ files = [ dev = ["bump2version (==1.0.1)", "mkdocs (==1.4.0)", "pre-commit", "pytest (==7.1.3)", "pytest-cov (==3.0.0)", "tox"] test = ["mkdocs (==1.4.0)", "pytest (==7.1.3)", "pytest-cov (==3.0.0)"] +[[package]] +name = "mkdocs-material" +version = "9.5.30" +description = "Documentation that simply works" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mkdocs_material-9.5.30-py3-none-any.whl", hash = "sha256:fc070689c5250a180e9b9d79d8491ef9a3a7acb240db0728728d6c31eeb131d4"}, + {file = "mkdocs_material-9.5.30.tar.gz", hash = "sha256:3fd417dd42d679e3ba08b9e2d72cd8b8af142cc4a3969676ad6b00993dd182ec"}, +] + +[package.dependencies] +babel = ">=2.10,<3.0" +colorama = ">=0.4,<1.0" +jinja2 = ">=3.0,<4.0" +markdown = ">=3.2,<4.0" +mkdocs = ">=1.6,<2.0" +mkdocs-material-extensions = ">=1.3,<2.0" +paginate = ">=0.5,<1.0" +pygments = ">=2.16,<3.0" +pymdown-extensions = ">=10.2,<11.0" +regex = ">=2022.4" +requests = ">=2.26,<3.0" + +[package.extras] +git = ["mkdocs-git-committers-plugin-2 (>=1.1,<2.0)", "mkdocs-git-revision-date-localized-plugin (>=1.2.4,<2.0)"] +imaging = ["cairosvg (>=2.6,<3.0)", "pillow (>=10.2,<11.0)"] +recommended = ["mkdocs-minify-plugin (>=0.7,<1.0)", "mkdocs-redirects (>=1.2,<2.0)", "mkdocs-rss-plugin (>=1.6,<2.0)"] + +[[package]] +name = "mkdocs-material-extensions" +version = "1.3.1" +description = "Extension pack for Python Markdown and MkDocs Material." +optional = false +python-versions = ">=3.8" +files = [ + {file = "mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31"}, + {file = "mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443"}, +] + [[package]] name = "mkdocs-swagger-ui-tag" version = "0.6.10" @@ -1381,6 +1435,16 @@ files = [ {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] +[[package]] +name = "paginate" +version = "0.5.6" +description = "Divides large result sets into pages for easier browsing" +optional = false +python-versions = "*" +files = [ + {file = "paginate-0.5.6.tar.gz", hash = "sha256:5e6007b6a9398177a7e1648d04fdd9f8c9766a1a945bceac82f1929e8c78af2d"}, +] + [[package]] name = "pathspec" version = "0.12.1" @@ -1672,15 +1736,29 @@ files = [ [package.dependencies] typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" +[[package]] +name = "pygments" +version = "2.18.0" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, + {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + [[package]] name = "pymdown-extensions" -version = "10.8.1" +version = "10.9" description = "Extension pack for Python Markdown." optional = false python-versions = ">=3.8" files = [ - {file = "pymdown_extensions-10.8.1-py3-none-any.whl", hash = "sha256:f938326115884f48c6059c67377c46cf631c733ef3629b6eed1349989d1b30cb"}, - {file = "pymdown_extensions-10.8.1.tar.gz", hash = "sha256:3ab1db5c9e21728dabf75192d71471f8e50f216627e9a1fa9535ecb0231b9940"}, + {file = "pymdown_extensions-10.9-py3-none-any.whl", hash = "sha256:d323f7e90d83c86113ee78f3fe62fc9dee5f56b54d912660703ea1816fed5626"}, + {file = "pymdown_extensions-10.9.tar.gz", hash = "sha256:6ff740bcd99ec4172a938970d42b96128bdc9d4b9bcad72494f29921dc69b753"}, ] [package.dependencies] @@ -1938,6 +2016,94 @@ files = [ [package.dependencies] pyyaml = "*" +[[package]] +name = "regex" +version = "2024.7.24" +description = "Alternative regular expression module, to replace re." +optional = false +python-versions = ">=3.8" +files = [ + {file = "regex-2024.7.24-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b0d3f567fafa0633aee87f08b9276c7062da9616931382993c03808bb68ce"}, + {file = "regex-2024.7.24-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3426de3b91d1bc73249042742f45c2148803c111d1175b283270177fdf669024"}, + {file = "regex-2024.7.24-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f273674b445bcb6e4409bf8d1be67bc4b58e8b46fd0d560055d515b8830063cd"}, + {file = "regex-2024.7.24-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23acc72f0f4e1a9e6e9843d6328177ae3074b4182167e34119ec7233dfeccf53"}, + {file = "regex-2024.7.24-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65fd3d2e228cae024c411c5ccdffae4c315271eee4a8b839291f84f796b34eca"}, + {file = "regex-2024.7.24-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c414cbda77dbf13c3bc88b073a1a9f375c7b0cb5e115e15d4b73ec3a2fbc6f59"}, + {file = "regex-2024.7.24-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf7a89eef64b5455835f5ed30254ec19bf41f7541cd94f266ab7cbd463f00c41"}, + {file = "regex-2024.7.24-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19c65b00d42804e3fbea9708f0937d157e53429a39b7c61253ff15670ff62cb5"}, + {file = "regex-2024.7.24-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7a5486ca56c8869070a966321d5ab416ff0f83f30e0e2da1ab48815c8d165d46"}, + {file = "regex-2024.7.24-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6f51f9556785e5a203713f5efd9c085b4a45aecd2a42573e2b5041881b588d1f"}, + {file = "regex-2024.7.24-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a4997716674d36a82eab3e86f8fa77080a5d8d96a389a61ea1d0e3a94a582cf7"}, + {file = "regex-2024.7.24-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c0abb5e4e8ce71a61d9446040c1e86d4e6d23f9097275c5bd49ed978755ff0fe"}, + {file = "regex-2024.7.24-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:18300a1d78cf1290fa583cd8b7cde26ecb73e9f5916690cf9d42de569c89b1ce"}, + {file = "regex-2024.7.24-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:416c0e4f56308f34cdb18c3f59849479dde5b19febdcd6e6fa4d04b6c31c9faa"}, + {file = "regex-2024.7.24-cp310-cp310-win32.whl", hash = "sha256:fb168b5924bef397b5ba13aabd8cf5df7d3d93f10218d7b925e360d436863f66"}, + {file = "regex-2024.7.24-cp310-cp310-win_amd64.whl", hash = "sha256:6b9fc7e9cc983e75e2518496ba1afc524227c163e43d706688a6bb9eca41617e"}, + {file = "regex-2024.7.24-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:382281306e3adaaa7b8b9ebbb3ffb43358a7bbf585fa93821300a418bb975281"}, + {file = "regex-2024.7.24-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4fdd1384619f406ad9037fe6b6eaa3de2749e2e12084abc80169e8e075377d3b"}, + {file = "regex-2024.7.24-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3d974d24edb231446f708c455fd08f94c41c1ff4f04bcf06e5f36df5ef50b95a"}, + {file = "regex-2024.7.24-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2ec4419a3fe6cf8a4795752596dfe0adb4aea40d3683a132bae9c30b81e8d73"}, + {file = "regex-2024.7.24-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb563dd3aea54c797adf513eeec819c4213d7dbfc311874eb4fd28d10f2ff0f2"}, + {file = "regex-2024.7.24-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:45104baae8b9f67569f0f1dca5e1f1ed77a54ae1cd8b0b07aba89272710db61e"}, + {file = "regex-2024.7.24-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:994448ee01864501912abf2bad9203bffc34158e80fe8bfb5b031f4f8e16da51"}, + {file = "regex-2024.7.24-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fac296f99283ac232d8125be932c5cd7644084a30748fda013028c815ba3364"}, + {file = "regex-2024.7.24-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7e37e809b9303ec3a179085415cb5f418ecf65ec98cdfe34f6a078b46ef823ee"}, + {file = "regex-2024.7.24-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:01b689e887f612610c869421241e075c02f2e3d1ae93a037cb14f88ab6a8934c"}, + {file = "regex-2024.7.24-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f6442f0f0ff81775eaa5b05af8a0ffa1dda36e9cf6ec1e0d3d245e8564b684ce"}, + {file = "regex-2024.7.24-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:871e3ab2838fbcb4e0865a6e01233975df3a15e6fce93b6f99d75cacbd9862d1"}, + {file = "regex-2024.7.24-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c918b7a1e26b4ab40409820ddccc5d49871a82329640f5005f73572d5eaa9b5e"}, + {file = "regex-2024.7.24-cp311-cp311-win32.whl", hash = "sha256:2dfbb8baf8ba2c2b9aa2807f44ed272f0913eeeba002478c4577b8d29cde215c"}, + {file = "regex-2024.7.24-cp311-cp311-win_amd64.whl", hash = "sha256:538d30cd96ed7d1416d3956f94d54e426a8daf7c14527f6e0d6d425fcb4cca52"}, + {file = "regex-2024.7.24-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:fe4ebef608553aff8deb845c7f4f1d0740ff76fa672c011cc0bacb2a00fbde86"}, + {file = "regex-2024.7.24-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:74007a5b25b7a678459f06559504f1eec2f0f17bca218c9d56f6a0a12bfffdad"}, + {file = "regex-2024.7.24-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7df9ea48641da022c2a3c9c641650cd09f0cd15e8908bf931ad538f5ca7919c9"}, + {file = "regex-2024.7.24-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a1141a1dcc32904c47f6846b040275c6e5de0bf73f17d7a409035d55b76f289"}, + {file = "regex-2024.7.24-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80c811cfcb5c331237d9bad3bea2c391114588cf4131707e84d9493064d267f9"}, + {file = "regex-2024.7.24-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7214477bf9bd195894cf24005b1e7b496f46833337b5dedb7b2a6e33f66d962c"}, + {file = "regex-2024.7.24-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d55588cba7553f0b6ec33130bc3e114b355570b45785cebdc9daed8c637dd440"}, + {file = "regex-2024.7.24-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:558a57cfc32adcf19d3f791f62b5ff564922942e389e3cfdb538a23d65a6b610"}, + {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a512eed9dfd4117110b1881ba9a59b31433caed0c4101b361f768e7bcbaf93c5"}, + {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:86b17ba823ea76256b1885652e3a141a99a5c4422f4a869189db328321b73799"}, + {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5eefee9bfe23f6df09ffb6dfb23809f4d74a78acef004aa904dc7c88b9944b05"}, + {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:731fcd76bbdbf225e2eb85b7c38da9633ad3073822f5ab32379381e8c3c12e94"}, + {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eaef80eac3b4cfbdd6de53c6e108b4c534c21ae055d1dbea2de6b3b8ff3def38"}, + {file = "regex-2024.7.24-cp312-cp312-win32.whl", hash = "sha256:185e029368d6f89f36e526764cf12bf8d6f0e3a2a7737da625a76f594bdfcbfc"}, + {file = "regex-2024.7.24-cp312-cp312-win_amd64.whl", hash = "sha256:2f1baff13cc2521bea83ab2528e7a80cbe0ebb2c6f0bfad15be7da3aed443908"}, + {file = "regex-2024.7.24-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:66b4c0731a5c81921e938dcf1a88e978264e26e6ac4ec96a4d21ae0354581ae0"}, + {file = "regex-2024.7.24-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:88ecc3afd7e776967fa16c80f974cb79399ee8dc6c96423321d6f7d4b881c92b"}, + {file = "regex-2024.7.24-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:64bd50cf16bcc54b274e20235bf8edbb64184a30e1e53873ff8d444e7ac656b2"}, + {file = "regex-2024.7.24-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb462f0e346fcf41a901a126b50f8781e9a474d3927930f3490f38a6e73b6950"}, + {file = "regex-2024.7.24-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a82465ebbc9b1c5c50738536fdfa7cab639a261a99b469c9d4c7dcbb2b3f1e57"}, + {file = "regex-2024.7.24-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:68a8f8c046c6466ac61a36b65bb2395c74451df2ffb8458492ef49900efed293"}, + {file = "regex-2024.7.24-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac8e84fff5d27420f3c1e879ce9929108e873667ec87e0c8eeb413a5311adfe"}, + {file = "regex-2024.7.24-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba2537ef2163db9e6ccdbeb6f6424282ae4dea43177402152c67ef869cf3978b"}, + {file = "regex-2024.7.24-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:43affe33137fcd679bdae93fb25924979517e011f9dea99163f80b82eadc7e53"}, + {file = "regex-2024.7.24-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:c9bb87fdf2ab2370f21e4d5636e5317775e5d51ff32ebff2cf389f71b9b13750"}, + {file = "regex-2024.7.24-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:945352286a541406f99b2655c973852da7911b3f4264e010218bbc1cc73168f2"}, + {file = "regex-2024.7.24-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:8bc593dcce679206b60a538c302d03c29b18e3d862609317cb560e18b66d10cf"}, + {file = "regex-2024.7.24-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:3f3b6ca8eae6d6c75a6cff525c8530c60e909a71a15e1b731723233331de4169"}, + {file = "regex-2024.7.24-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c51edc3541e11fbe83f0c4d9412ef6c79f664a3745fab261457e84465ec9d5a8"}, + {file = "regex-2024.7.24-cp38-cp38-win32.whl", hash = "sha256:d0a07763776188b4db4c9c7fb1b8c494049f84659bb387b71c73bbc07f189e96"}, + {file = "regex-2024.7.24-cp38-cp38-win_amd64.whl", hash = "sha256:8fd5afd101dcf86a270d254364e0e8dddedebe6bd1ab9d5f732f274fa00499a5"}, + {file = "regex-2024.7.24-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0ffe3f9d430cd37d8fa5632ff6fb36d5b24818c5c986893063b4e5bdb84cdf24"}, + {file = "regex-2024.7.24-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:25419b70ba00a16abc90ee5fce061228206173231f004437730b67ac77323f0d"}, + {file = "regex-2024.7.24-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:33e2614a7ce627f0cdf2ad104797d1f68342d967de3695678c0cb84f530709f8"}, + {file = "regex-2024.7.24-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d33a0021893ede5969876052796165bab6006559ab845fd7b515a30abdd990dc"}, + {file = "regex-2024.7.24-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04ce29e2c5fedf296b1a1b0acc1724ba93a36fb14031f3abfb7abda2806c1535"}, + {file = "regex-2024.7.24-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b16582783f44fbca6fcf46f61347340c787d7530d88b4d590a397a47583f31dd"}, + {file = "regex-2024.7.24-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:836d3cc225b3e8a943d0b02633fb2f28a66e281290302a79df0e1eaa984ff7c1"}, + {file = "regex-2024.7.24-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:438d9f0f4bc64e8dea78274caa5af971ceff0f8771e1a2333620969936ba10be"}, + {file = "regex-2024.7.24-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:973335b1624859cb0e52f96062a28aa18f3a5fc77a96e4a3d6d76e29811a0e6e"}, + {file = "regex-2024.7.24-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c5e69fd3eb0b409432b537fe3c6f44ac089c458ab6b78dcec14478422879ec5f"}, + {file = "regex-2024.7.24-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:fbf8c2f00904eaf63ff37718eb13acf8e178cb940520e47b2f05027f5bb34ce3"}, + {file = "regex-2024.7.24-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ae2757ace61bc4061b69af19e4689fa4416e1a04840f33b441034202b5cd02d4"}, + {file = "regex-2024.7.24-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:44fc61b99035fd9b3b9453f1713234e5a7c92a04f3577252b45feefe1b327759"}, + {file = "regex-2024.7.24-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:84c312cdf839e8b579f504afcd7b65f35d60b6285d892b19adea16355e8343c9"}, + {file = "regex-2024.7.24-cp39-cp39-win32.whl", hash = "sha256:ca5b2028c2f7af4e13fb9fc29b28d0ce767c38c7facdf64f6c2cd040413055f1"}, + {file = "regex-2024.7.24-cp39-cp39-win_amd64.whl", hash = "sha256:7c479f5ae937ec9985ecaf42e2e10631551d909f203e31308c12d703922742f9"}, + {file = "regex-2024.7.24.tar.gz", hash = "sha256:9cfd009eed1a46b27c14039ad5bbc5e71b6367c5b2e6d5f5da0ea91600817506"}, +] + [[package]] name = "requests" version = "2.32.3" @@ -2265,4 +2431,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "3f397396c238541d7a07327c9b289843124d8d4dd6b1d96a3393f3be2be38fa4" +content-hash = "80dda5533cf7111fd83407e0cce9228a99af644a6ffab4fa1abe085f4272cabf" diff --git a/pyproject.toml b/pyproject.toml index ec52d97..0a66524 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,7 @@ requests = "^2.32.3" optional = true [tool.poetry.group.docs.dependencies] -mkdocs = "^1.5.2" +mkdocs-material = "^9.5.30" mkdocs-include-markdown-plugin = "^3.9.1" mkdocstrings = "^0.22.0" pymdown-extensions = "^10.1"