Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c24ad879c | ||
|
|
6f6cf8dd0d | ||
|
|
e602319f22 | ||
|
|
5e16eb9321 | ||
|
|
8ad7ea511c | ||
|
|
9fa4333196 | ||
|
|
84d4c9c712 | ||
|
|
b89644864c | ||
|
|
70bfad6b0a | ||
|
|
86024be77e | ||
|
|
efac1e790f | ||
|
|
da28b6c82c | ||
|
|
0e8970f0c5 | ||
|
|
55ab705542 | ||
|
|
c970ae89d0 | ||
|
|
23ef229eb1 | ||
|
|
cb94068faf | ||
|
|
2ce8ace227 | ||
|
|
ee124ead89 | ||
|
|
bbee77e231 | ||
|
|
27520864c4 | ||
|
|
e743d89f2e | ||
|
|
050a718e44 | ||
|
|
d2b9dd832f | ||
|
|
d5cb1a1478 | ||
|
|
82f8e1f90c | ||
|
|
c12bb87b2b | ||
|
|
6eafe07ac7 | ||
|
|
76fb674fcd | ||
|
|
e86fd1ffbe | ||
|
|
0626500cdd | ||
|
|
fab526a679 | ||
|
|
ea621c7c4b | ||
|
|
60984b6e13 | ||
|
|
08e0c59ad7 | ||
|
|
f132d71f60 | ||
|
|
fb6fac5f9a | ||
|
|
a2ce2e1116 | ||
|
|
29ebc566bb | ||
|
|
b275446960 | ||
|
|
24f35245bb |
@@ -1,4 +1,6 @@
|
||||
[report]
|
||||
exclude_lines =
|
||||
pragma: no cover
|
||||
if TYPE_CHECKING:
|
||||
if TYPE_CHECKING:
|
||||
omit =
|
||||
src/pydase/utils/logging.py
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
111
.github/workflows/publish-to-pypi.yaml
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
name: Publish Python 🐍 distribution 📦 to PyPI and TestPyPI
|
||||
|
||||
on: push
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build distribution 📦
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.11"
|
||||
- name: Install pypa/build
|
||||
run: >-
|
||||
python3 -m
|
||||
pip install
|
||||
build
|
||||
--user
|
||||
- name: Build a binary wheel and a source tarball
|
||||
run: python3 -m build
|
||||
- name: Store the distribution packages
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: python-package-distributions
|
||||
path: dist/
|
||||
|
||||
publish-to-pypi:
|
||||
name: >-
|
||||
Publish Python 🐍 distribution 📦 to PyPI
|
||||
if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes
|
||||
needs:
|
||||
- build
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
environment:
|
||||
name: pypi
|
||||
url: https://pypi.org/p/pydase
|
||||
|
||||
permissions:
|
||||
id-token: write # IMPORTANT: mandatory for trusted publishing
|
||||
|
||||
steps:
|
||||
- name: Download all the dists
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: python-package-distributions
|
||||
path: dist/
|
||||
- name: Publish distribution 📦 to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
|
||||
github-release:
|
||||
name: >-
|
||||
Sign the Python 🐍 distribution 📦 with Sigstore
|
||||
and upload them to GitHub Release
|
||||
needs:
|
||||
- publish-to-pypi
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: write # IMPORTANT: mandatory for making GitHub Releases
|
||||
id-token: write # IMPORTANT: mandatory for sigstore
|
||||
|
||||
steps:
|
||||
- name: Download all the dists
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: python-package-distributions
|
||||
path: dist/
|
||||
- name: Sign the dists with Sigstore
|
||||
uses: sigstore/gh-action-sigstore-python@v1.2.3
|
||||
with:
|
||||
inputs: >-
|
||||
./dist/*.tar.gz
|
||||
./dist/*.whl
|
||||
- name: Upload artifact signatures to GitHub Release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
# Upload to GitHub Release using the `gh` CLI.
|
||||
# `dist/` contains the built packages, and the
|
||||
# sigstore-produced signatures and certificates.
|
||||
run: >-
|
||||
gh release upload
|
||||
'${{ github.ref_name }}' dist/**
|
||||
--repo '${{ github.repository }}'
|
||||
|
||||
publish-to-testpypi:
|
||||
name: Publish Python 🐍 distribution 📦 to TestPyPI
|
||||
needs:
|
||||
- build
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
environment:
|
||||
name: testpypi
|
||||
url: https://test.pypi.org/p/pydase
|
||||
|
||||
permissions:
|
||||
id-token: write # IMPORTANT: mandatory for trusted publishing
|
||||
|
||||
steps:
|
||||
- name: Download all the dists
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: python-package-distributions
|
||||
path: dist/
|
||||
- name: Publish distribution 📦 to TestPyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
repository-url: https://test.pypi.org/legacy/
|
||||
3
.github/workflows/python-package.yml
vendored
@@ -36,3 +36,6 @@ jobs:
|
||||
- name: Test with pytest
|
||||
run: |
|
||||
poetry run pytest
|
||||
- name: Test with pyright
|
||||
run: |
|
||||
poetry run pyright src/pydase
|
||||
21
.readthedocs.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
# Read the Docs configuration file for MkDocs projects
|
||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
||||
|
||||
# Required
|
||||
version: 2
|
||||
|
||||
# Set the version of Python and other tools you might need
|
||||
build:
|
||||
os: ubuntu-22.04
|
||||
tools:
|
||||
python: "3.11"
|
||||
|
||||
mkdocs:
|
||||
configuration: mkdocs.yml
|
||||
|
||||
# Optionally declare the Python requirements required to build your docs
|
||||
python:
|
||||
install:
|
||||
- method: pip
|
||||
path: .
|
||||
- requirements: docs/requirements.txt
|
||||
10
.vscode/launch.json
vendored
@@ -9,14 +9,20 @@
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"module": "foo",
|
||||
"justMyCode": true
|
||||
"justMyCode": true,
|
||||
"env": {
|
||||
"ENVIRONMENT": "development"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bar",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"module": "bar",
|
||||
"justMyCode": true
|
||||
"justMyCode": true,
|
||||
"env": {
|
||||
"ENVIRONMENT": "development"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "firefox",
|
||||
|
||||
195
README.md
@@ -1,5 +1,8 @@
|
||||
# pydase (Python Data Service) <!-- omit from toc -->
|
||||
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](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.
|
||||
|
||||
- [Features](#features)
|
||||
@@ -9,9 +12,16 @@
|
||||
- [Running the Server](#running-the-server)
|
||||
- [Accessing the Web Interface](#accessing-the-web-interface)
|
||||
- [Connecting to the Service using rpyc](#connecting-to-the-service-using-rpyc)
|
||||
- [Understanding the Component System](#understanding-the-component-system)
|
||||
- [Built-in Type and Enum Components](#built-in-type-and-enum-components)
|
||||
- [Method Components](#method-components)
|
||||
- [DataService Instances (Nested Classes)](#dataservice-instances-nested-classes)
|
||||
- [Custom Components (`pydase.components`)](#custom-components-pydasecomponents)
|
||||
- [Extending with New Components](#extending-with-new-components)
|
||||
- [Understanding Service Persistence](#understanding-service-persistence)
|
||||
- [Understanding Tasks in pydase](#understanding-tasks-in-pydase)
|
||||
- [Understanding Units in pydase](#understanding-units-in-pydase)
|
||||
- [Changing the Log Level](#changing-the-log-level)
|
||||
- [Documentation](#documentation)
|
||||
- [Contributing](#contributing)
|
||||
- [License](#license)
|
||||
@@ -19,16 +29,18 @@
|
||||
## Features
|
||||
|
||||
<!-- no toc -->
|
||||
* [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)
|
||||
* [Support for `rpyc` connections, allowing for programmatic control and interaction with your service](#connecting-to-the-service-using-rpyc)
|
||||
* [Component system bridging Python backend with frontend visual representation](#understanding-the-component-system)
|
||||
* [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)
|
||||
* Event-based callback functionality for real-time updates
|
||||
* Support for additional servers for specific use-cases
|
||||
<!-- * Event-based callback functionality for real-time updates
|
||||
* Support for additional servers for specific use-cases -->
|
||||
|
||||
## Installation
|
||||
|
||||
<!--installation-start-->
|
||||
Install pydase using [`poetry`](https://python-poetry.org/):
|
||||
|
||||
```bash
|
||||
@@ -40,9 +52,10 @@ or `pip`:
|
||||
```bash
|
||||
pip install git+https://github.com/tiqi-group/pydase.git
|
||||
```
|
||||
<!--installation-end-->
|
||||
|
||||
## Usage
|
||||
|
||||
<!--usage-start-->
|
||||
Using `pydase` involves three main steps: defining a `DataService` subclass, running the server, and then connecting to the service either programmatically using `rpyc` or through the web interface.
|
||||
|
||||
### Defining a DataService
|
||||
@@ -52,48 +65,54 @@ To use pydase, you'll first need to create a class that inherits from `DataServi
|
||||
Here's an example:
|
||||
|
||||
```python
|
||||
from pydase import DataService
|
||||
from pydase import DataService, Server
|
||||
|
||||
|
||||
class Device(DataService):
|
||||
|
||||
_current = 0.0
|
||||
_voltage = 0.0
|
||||
_power = False
|
||||
|
||||
@property
|
||||
def current(self):
|
||||
def current(self) -> float:
|
||||
# run code to get current
|
||||
return self._current
|
||||
|
||||
@current.setter
|
||||
def current(self, value):
|
||||
def current(self, value: float) -> None:
|
||||
# run code to set current
|
||||
self._current = value
|
||||
|
||||
@property
|
||||
def voltage(self):
|
||||
def voltage(self) -> float:
|
||||
# run code to get voltage
|
||||
return self._voltage
|
||||
|
||||
@voltage.setter
|
||||
def voltage(self, value):
|
||||
def voltage(self, value: float) -> None:
|
||||
# run code to set voltage
|
||||
self._voltage = value
|
||||
|
||||
@property
|
||||
def power(self):
|
||||
def power(self) -> bool:
|
||||
# run code to get power state
|
||||
return self._power
|
||||
|
||||
@power.setter
|
||||
def power(self, value):
|
||||
def power(self, value: bool) -> None:
|
||||
# run code to set power state
|
||||
self._power = value
|
||||
|
||||
def reset(self):
|
||||
def reset(self) -> None:
|
||||
self.current = 0.0
|
||||
self.voltage = 0.0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
service = Device()
|
||||
Server(service).run()
|
||||
```
|
||||
|
||||
In the above example, we define a Device class that extends DataService. We define a few properties (current, voltage, power) and their getter and setter methods.
|
||||
|
||||
### Running the Server
|
||||
@@ -137,6 +156,133 @@ print(client.voltage) # prints 5.0
|
||||
```
|
||||
|
||||
In this example, replace `<ip_addr>` with the IP address of the machine where the service is running. After establishing a connection, you can interact with the service attributes as if they were local attributes.
|
||||
<!--usage-end-->
|
||||
|
||||
## Understanding the Component System
|
||||
In `pydase`, components are fundamental building blocks that bridge the Python backend logic with frontend visual representation and interactions. This system can be understood based on the following categories:
|
||||
|
||||
### Built-in Type and Enum Components
|
||||
|
||||
`pydase` automatically maps standard Python data types to their corresponding frontend components:
|
||||
- `str`: Translated into a `StringComponent` on the frontend.
|
||||
- `int` and `float`: Manifested as the `NumberComponent`.
|
||||
- `bool`: Rendered as a `ButtonComponent`.
|
||||
- `list`: Each item displayed individually, named after the list attribute and its index.
|
||||
- `enum.Enum`: Presented as an `EnumComponent`, facilitating dropdown selection.
|
||||
|
||||
### Method Components
|
||||
|
||||
Methods within the `DataService` class have frontend representations:
|
||||
- Regular Methods: These are rendered as a `MethodComponent` in the frontend, allowing users to execute the method via an "execute" button.
|
||||
- Asynchronous Methods: These are manifested as the `AsyncMethodComponent` with "start"/"stop" buttons to manage the execution of [tasks](#understanding-tasks-in-pydase).
|
||||
|
||||
### DataService Instances (Nested Classes)
|
||||
|
||||
Nested `DataService` instances offer an organized hierarchy for components, enabling richer applications. Each nested class might have its own attributes and methods, each mapped to a frontend component.
|
||||
|
||||
Here is an example:
|
||||
|
||||
```python
|
||||
from pydase import DataService, Server
|
||||
|
||||
|
||||
class Channel(DataService):
|
||||
def __init__(self, channel_id: int) -> None:
|
||||
self._channel_id = channel_id
|
||||
self._current = 0.0
|
||||
super().__init__()
|
||||
|
||||
@property
|
||||
def current(self) -> float:
|
||||
# run code to get current
|
||||
result = self._current
|
||||
return result
|
||||
|
||||
@current.setter
|
||||
def current(self, value: float) -> None:
|
||||
# run code to set current
|
||||
self._current = value
|
||||
|
||||
|
||||
class Device(DataService):
|
||||
def __init__(self) -> None:
|
||||
self.channels = [Channel(i) for i in range(2)]
|
||||
|
||||
super().__init__()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
service = Device()
|
||||
Server(service).run()
|
||||
```
|
||||
|
||||

|
||||
|
||||
**Note** that defining classes within `DataService` classes is not supported (see [this issue](https://github.com/tiqi-group/pydase/issues/16)).
|
||||
|
||||
### Custom Components (`pydase.components`)
|
||||
The custom components in `pydase` have two main parts:
|
||||
|
||||
- A **Python Component Class** in the backend, implementing the logic needed to set, update, and manage the component's state and data.
|
||||
- A **Frontend React Component** that renders and manages user interaction in the browser.
|
||||
|
||||
Below are the components available in the `pydase.components` module, accompanied by their Python usage:
|
||||
|
||||
- `Image`: This component allows users to display and update images within the application.
|
||||
|
||||
```python
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
import pydase
|
||||
from pydase.components.image import Image
|
||||
|
||||
|
||||
class MyDataService(pydase.DataService):
|
||||
my_image = Image()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
service = MyDataService()
|
||||
# loading from local path
|
||||
service.my_image.load_from_path("/your/image/path/")
|
||||
|
||||
# loading from a URL
|
||||
service.my_image.load_from_url("https://cataas.com/cat")
|
||||
|
||||
# loading a matplotlib figure
|
||||
fig = plt.figure()
|
||||
x = np.linspace(0, 2 * np.pi)
|
||||
plt.plot(x, np.sin(x))
|
||||
plt.grid()
|
||||
service.my_image.load_from_matplotlib_figure(fig)
|
||||
|
||||
pydase.Server(service).run()
|
||||
```
|
||||
|
||||

|
||||
|
||||
- `NumberSlider`: An interactive slider component to adjust numerical values, including floats and integers, on the frontend while synchronizing the data with the backend in real-time.
|
||||
|
||||
```python
|
||||
import pydase
|
||||
from pydase.components import NumberSlider
|
||||
|
||||
|
||||
class MyService(pydase.DataService):
|
||||
slider = NumberSlider(value=3.5, min=0, max=10, step_size=0.1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
service = MyService()
|
||||
pydase.Server(service).run()
|
||||
```
|
||||
|
||||

|
||||
|
||||
#### Extending with New Components
|
||||
|
||||
Users can also extend the library by creating custom components. This involves defining the behavior on the Python backend and the visual representation on the frontend. For those looking to introduce new components, the [guide on adding components](./docs/dev-guide/Adding_Components.md) provides detailed steps on achieving this.
|
||||
|
||||
## Understanding Service Persistence
|
||||
|
||||
@@ -273,13 +419,34 @@ if __name__ == "__main__":
|
||||
|
||||
For more information about what you can do with the units, please consult the documentation of [`pint`](https://pint.readthedocs.io/en/stable/).
|
||||
|
||||
## Changing the Log Level
|
||||
|
||||
You can change the log level of loguru by either
|
||||
1. (RECOMMENDED) setting the `ENVIRONMENT` environment variable to "production" or "development"
|
||||
|
||||
```bash
|
||||
ENVIRONMENT="production" python -m <module_using_pydase>
|
||||
```
|
||||
|
||||
The production environment will only log messages above "INFO", the development environment (default) logs everything above "DEBUG".
|
||||
|
||||
2. calling the `pydase.utils.logging.setup_logging` function with the desired log level
|
||||
|
||||
```python
|
||||
# <your_script.py>
|
||||
|
||||
from pydase.utils.logging import setup_logging
|
||||
|
||||
setup_logging("INFO")
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
The full documentation provides more detailed information about `pydase`, including advanced usage examples, API references, and tips for troubleshooting common issues. See the [full documentation](URL_TO_YOUR_DOCUMENTATION) for more information.
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome contributions! Please see [CONTRIBUTING.md](URL_TO_YOUR_CONTRIBUTING_GUIDELINES) for details on how to contribute.
|
||||
We welcome contributions! Please see [contributing.md](./docs/about/contributing.md) for details on how to contribute.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
0
docs/about/contributing.md
Normal file
10
docs/about/license.md
Normal file
@@ -0,0 +1,10 @@
|
||||
License
|
||||
=======
|
||||
`pydase` is released under the *MIT license*:
|
||||
|
||||
```{.license}
|
||||
{%
|
||||
include "../../LICENSE"
|
||||
comments=false
|
||||
%}
|
||||
```
|
||||
0
docs/about/release-notes.md
Normal file
3
docs/css/extra.css
Normal file
@@ -0,0 +1,3 @@
|
||||
.language-license{
|
||||
background-color: #eeffcc !important;
|
||||
}
|
||||
299
docs/dev-guide/Adding_Components.md
Normal file
@@ -0,0 +1,299 @@
|
||||
# Adding Components to `pydase`
|
||||
|
||||
This guide provides a step-by-step process for adding new components to the `pydase` package. Components in `pydase` consist of both backend (Python) and frontend (React) parts. They work together to create interactive and dynamic data services.
|
||||
|
||||
## Overview
|
||||
|
||||
A component in `pydase` is a unique combination of a backend class (e.g., `Image`) and its corresponding frontend React component. The backend class stores the attributes needed for the component, and possibly methods for setting those in the backend, while the frontend part is responsible for rendering and interacting with the component.
|
||||
|
||||
## Adding a Backend Component to `pydase`
|
||||
|
||||
Backend components belong in the `src/pydase/components` directory.
|
||||
|
||||
### Step 1: Create a New Python File in the Components Directory
|
||||
|
||||
Navigate to the `src/pydase/components` directory and create a new Python file for your component. The name of the file should be descriptive of the component's functionality.
|
||||
|
||||
For example, for a `Image` component, create a file named `image.py`.
|
||||
|
||||
### Step 2: Define the Backend Class
|
||||
|
||||
Within the newly created file, define a Python class representing the component. This class should inherit from `DataService` and contains the attributes that the frontend needs to render the component. Every public attribute defined in this class will synchronise across the clients. It can also contain methods which can be used to interact with the component from the backend.
|
||||
|
||||
For the `Image` component, the class may look like this:
|
||||
|
||||
```python
|
||||
# file: pydase/components/image.py
|
||||
|
||||
from pydase.data_service.data_service import DataService
|
||||
|
||||
|
||||
class Image(DataService):
|
||||
def __init__(
|
||||
self,
|
||||
image_representation: bytes = b"",
|
||||
) -> None:
|
||||
self.image_representation = image_representation
|
||||
super().__init__()
|
||||
|
||||
# need to decode the bytes
|
||||
def __setattr__(self, __name: str, __value: Any) -> None:
|
||||
if __name == "value":
|
||||
if isinstance(__value, bytes):
|
||||
__value = __value.decode()
|
||||
return super().__setattr__(__name, __value)
|
||||
|
||||
```
|
||||
|
||||
So, changing the `image_representation` will push the updated value to the browsers connected to the service.
|
||||
|
||||
### Step 3: Register the Backend Class
|
||||
|
||||
The component should be added to the `__init__.py` file to ensure `pydase` handles them properly:
|
||||
|
||||
```python
|
||||
# file: pydase/components/__init__.py
|
||||
|
||||
from pydase.components.image import Image
|
||||
from pydase.components.number_slider import NumberSlider
|
||||
|
||||
__all__ = [
|
||||
"NumberSlider",
|
||||
"Image", # add the new components here
|
||||
]
|
||||
|
||||
```
|
||||
|
||||
### Step 4: Implement Necessary Methods (Optional)
|
||||
|
||||
If your component requires specific logic or methods, implement them within the class. Document any public methods or attributes to ensure that other developers understand their purpose and usage.
|
||||
|
||||
### Step 5: Write Tests for the Component (Recommended)
|
||||
|
||||
Consider writing unit tests for the component to verify its behavior. Place the tests in the appropriate directory within the `tests` folder.
|
||||
|
||||
For example, a test for the `Image` component could look like this:
|
||||
|
||||
```python
|
||||
from pytest import CaptureFixture
|
||||
|
||||
from pydase.components.image import Image
|
||||
from pydase.data_service.data_service import DataService
|
||||
|
||||
|
||||
def test_Image(capsys: CaptureFixture) -> None:
|
||||
class ServiceClass(DataService):
|
||||
image = Image()
|
||||
|
||||
service = ServiceClass()
|
||||
# ...
|
||||
```
|
||||
|
||||
|
||||
## Adding a Frontend Component to `pydase`
|
||||
|
||||
Frontend components in `pydase` live in the `frontend/src/components/` directory. Follow these steps to create and add a new frontend component:
|
||||
|
||||
### Step 1: Create a New React Component File in the Components Directory
|
||||
|
||||
Navigate to the `frontend/src/components/` directory and create a new React component file for your component. The name of the file should be descriptive of the component's functionality and reflect the naming conventions used in your project.
|
||||
|
||||
For example, for an `Image` component, create a file named `ImageComponent.tsx`.
|
||||
|
||||
### Step 2: Write the React Component Code
|
||||
|
||||
Write the React component code, following the structure and patterns used in existing components. Make sure to import necessary libraries and dependencies.
|
||||
|
||||
For example, for the `Image` component, a template could look like this:
|
||||
|
||||
```tsx
|
||||
import { emit_update } from '../socket'; // use this when your component should update values in the backend
|
||||
import { DocStringComponent } from './DocStringComponent';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { Card, Collapse, Image } from 'react-bootstrap';
|
||||
import { DocStringComponent } from './DocStringComponent';
|
||||
import { ChevronDown, ChevronRight } from 'react-bootstrap-icons';
|
||||
|
||||
interface ImageComponentProps {
|
||||
name: string;
|
||||
parentPath: string;
|
||||
readOnly: boolean;
|
||||
docString: string;
|
||||
addNotification: (string) => void;
|
||||
// Define your component specific props here
|
||||
value: string;
|
||||
format: string;
|
||||
}
|
||||
|
||||
export const ImageComponent = React.memo((props: ImageComponentProps) => {
|
||||
const { name, parentPath, value, docString, format, addNotification } = props;
|
||||
|
||||
const renderCount = useRef(0);
|
||||
const [open, setOpen] = useState(true); // add this if you want to expand/collapse your component
|
||||
|
||||
useEffect(() => {
|
||||
renderCount.current++;
|
||||
});
|
||||
|
||||
// This will trigger a notification if notifications are enabled.
|
||||
useEffect(() => {
|
||||
addNotification(`${parentPath}.${name} changed to ${value}.`);
|
||||
}, [props.value]);
|
||||
|
||||
// Your component logic here
|
||||
|
||||
return (
|
||||
<div className={'imageComponent'} id={parentPath.concat('.' + name)}>
|
||||
{/* Add the Card and Collapse components here if you want to be able to expand and
|
||||
collapse your component. */}
|
||||
<Card>
|
||||
<Card.Header
|
||||
onClick={() => setOpen(!open)}
|
||||
style={{ cursor: 'pointer' }} // Change cursor style on hover
|
||||
>
|
||||
{name} {open ? <ChevronDown /> : <ChevronRight />}
|
||||
</Card.Header>
|
||||
<Collapse in={open}>
|
||||
<Card.Body>
|
||||
{process.env.NODE_ENV === 'development' && (
|
||||
<p>Render count: {renderCount.current}</p>
|
||||
)}
|
||||
<DocStringComponent docString={docString} />
|
||||
{/* Your component TSX here */}
|
||||
</Card.Body>
|
||||
</Collapse>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
```
|
||||
|
||||
### Step 3: Emitting Updates to the Backend
|
||||
|
||||
Often, React components in the frontend will need to send updates to the backend, especially when user interactions result in a change of state or data. In `pydase`, we use `socketio` to seamlessly communicate these changes. Here's a detailed guide on how to emit update events from your frontend component:
|
||||
|
||||
1. **Setting Up Emission**: Ensure you've imported the required functions and methods for emission. The main function we'll use for this is `emit_update` from the `socket` module:
|
||||
|
||||
```tsx
|
||||
import { emit_update } from '../socket';
|
||||
```
|
||||
|
||||
2. **Understanding the Emission Parameters**:
|
||||
|
||||
When emitting an update, we send three main pieces of data:
|
||||
|
||||
- `parentPath`: This is the access path for the parent object of the attribute to be updated. This forms the basis to create the full access path for the attribute. For instance, for the attribute access path `attr1.list_attr[0].attr2`, `attr1.list_attr[0]` would be the `parentPath`.
|
||||
|
||||
- `name`: This represents the name of the attribute to be updated within the `DataService` instance. If the attribute is part of a nested structure, this would be the name of the attribute in the last nested object. So, for `attr1.list_attr[0].attr2`, `attr2` would be the name.
|
||||
|
||||
- `value`: This is the new value intended for the attribute. Ensure that the type of this value matches the type of the attribute in the backend.
|
||||
|
||||
3. **Implementing the Emission**:
|
||||
|
||||
To illustrate the emission process, let's consider the `ButtonComponent`. When the button state changes, we want to send this update to the backend:
|
||||
|
||||
```tsx
|
||||
// ... (other imports)
|
||||
|
||||
export const ButtonComponent = React.memo((props: ButtonComponentProps) => {
|
||||
// ...
|
||||
const { name, parentPath, value } = props;
|
||||
|
||||
const setChecked = (checked: boolean) => {
|
||||
emit_update(name, parentPath, checked);
|
||||
};
|
||||
|
||||
return (
|
||||
<ToggleButton
|
||||
checked={value}
|
||||
value={parentPath}
|
||||
// ... other props
|
||||
onChange={(e) => setChecked(e.currentTarget.checked)}>
|
||||
<p>{name}</p>
|
||||
</ToggleButton>
|
||||
);
|
||||
});
|
||||
```
|
||||
|
||||
In this example, whenever the button's checked state changes (`onChange` event), we invoke the `setChecked` method, which in turn emits the new state to the backend using `emit_update`.
|
||||
|
||||
### Step 4: Add the New Component to the GenericComponent
|
||||
|
||||
The `GenericComponent` is responsible for rendering different types of components based on the attribute type. You can add the new `ImageComponent` to the `GenericComponent` by following these sub-steps:
|
||||
|
||||
#### 1. Import the New Component
|
||||
|
||||
At the beginning of the `GenericComponent` file, import the newly created `ImageComponent`:
|
||||
|
||||
```tsx
|
||||
// file: frontend/src/components/GenericComponent.tsx
|
||||
|
||||
import { ImageComponent } from './ImageComponent';
|
||||
```
|
||||
|
||||
#### 2. Update the AttributeType
|
||||
|
||||
Update the `AttributeType` type definition to include the new type for the `ImageComponent`.
|
||||
|
||||
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
|
||||
type AttributeType =
|
||||
| 'str'
|
||||
| 'bool'
|
||||
| 'float'
|
||||
| 'int'
|
||||
| 'Quantity'
|
||||
| 'list'
|
||||
| 'method'
|
||||
| 'DataService'
|
||||
| 'Enum'
|
||||
| 'NumberSlider'
|
||||
| 'Image'; // Add the name of the backend component class here
|
||||
```
|
||||
|
||||
#### 3. Add a Conditional Branch for the New Component
|
||||
|
||||
Inside the `GenericComponent` function, add a new conditional branch to render the `ImageComponent` when the attribute type is `'Image'`:
|
||||
|
||||
```tsx
|
||||
} else if (attribute.type === 'Image') {
|
||||
return (
|
||||
<ImageComponent
|
||||
name={name}
|
||||
parentPath={parentPath}
|
||||
readOnly={attribute.readonly}
|
||||
docString={attribute.doc}
|
||||
addNotification={addNotification}
|
||||
// Add any other specific props for the ImageComponent here
|
||||
value={attribute.value['value']['value'] as string}
|
||||
format={attribute.value['format']['value'] as string}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
// other code
|
||||
```
|
||||
|
||||
Make sure to update the props passed to the `ImageComponent` based on its specific requirements.
|
||||
|
||||
### Step 5: Adding Custom Notification Message (Optional)
|
||||
|
||||
In some cases, you may want to provide a custom notification message to the user when an attribute of a specific type is updated. This can be useful for enhancing user experience and providing contextual information about the changes.
|
||||
|
||||
For example, updating an `Image` component corresponds to setting a very long string. We don't want to display the whole string in the notification but just notify the user that the image was updated (and maybe also the format).
|
||||
|
||||
To create a custom notification message, you can update the message passed to the `addNotification` method in the `useEffect` hook in the component file file. For the `ImageComponent`, this could look like this:
|
||||
|
||||
```tsx
|
||||
useEffect(() => {
|
||||
addNotification(`${parentPath}.${name} changed.`);
|
||||
}, [props.value]);
|
||||
```
|
||||
|
||||
However, you might want to use the `addNotification` at different places. For an example, see the [MethodComponent](../../frontend/src/components/MethodComponent.tsx).
|
||||
|
||||
### Step 6: Write Tests for the Component (TODO)
|
||||
|
||||
Test the frontend component to ensure that it renders correctly and interacts seamlessly
|
||||
with the backend. Consider writing unit tests using a testing library like Jest or React
|
||||
Testing Library, and manually test the component in the browser.
|
||||
8
docs/dev-guide/README.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# Developer Guide
|
||||
|
||||
Extending `pydase`.
|
||||
|
||||
---
|
||||
|
||||
- [API Reference](api.md)
|
||||
- [Adding Components](Adding_Components.md)
|
||||
0
docs/dev-guide/api.md
Normal file
14
docs/getting-started.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# Getting Started
|
||||
## Installation
|
||||
{%
|
||||
include-markdown "../README.md"
|
||||
start="<!--installation-start-->"
|
||||
end="<!--installation-end-->"
|
||||
%}
|
||||
|
||||
## Usage
|
||||
{%
|
||||
include-markdown "../README.md"
|
||||
start="<!--usage-start-->"
|
||||
end="<!--usage-end-->"
|
||||
%}
|
||||
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 33 KiB |
BIN
docs/images/Image_component.png
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
docs/images/Nested_Class_App.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
docs/images/Slider_component.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 24 KiB |
1
docs/index.md
Normal file
@@ -0,0 +1 @@
|
||||
{% include-markdown "../README.md" %}
|
||||
20
docs/requirements.txt
Normal file
@@ -0,0 +1,20 @@
|
||||
click==8.1.7 ; python_version >= "3.10" and python_version < "4.0"
|
||||
colorama==0.4.6 ; python_version >= "3.10" and python_version < "4.0" and platform_system == "Windows"
|
||||
ghp-import==2.1.0 ; python_version >= "3.10" and python_version < "4.0"
|
||||
jinja2==3.1.2 ; python_version >= "3.10" and python_version < "4.0"
|
||||
markdown==3.4.4 ; python_version >= "3.10" and python_version < "4.0"
|
||||
markupsafe==2.1.3 ; python_version >= "3.10" and python_version < "4.0"
|
||||
mergedeep==1.3.4 ; python_version >= "3.10" and python_version < "4.0"
|
||||
mkdocs-autorefs==0.5.0 ; python_version >= "3.10" and python_version < "4.0"
|
||||
mkdocs-include-markdown-plugin==3.9.1 ; python_version >= "3.10" and python_version < "4.0"
|
||||
mkdocs==1.5.3 ; python_version >= "3.10" and python_version < "4.0"
|
||||
mkdocstrings==0.22.0 ; python_version >= "3.10" and python_version < "4.0"
|
||||
packaging==23.1 ; python_version >= "3.10" and python_version < "4.0"
|
||||
pathspec==0.11.2 ; python_version >= "3.10" and python_version < "4.0"
|
||||
platformdirs==3.10.0 ; python_version >= "3.10" and python_version < "4.0"
|
||||
pymdown-extensions==10.3 ; python_version >= "3.10" and python_version < "4.0"
|
||||
python-dateutil==2.8.2 ; python_version >= "3.10" and python_version < "4.0"
|
||||
pyyaml-env-tag==0.1 ; python_version >= "3.10" and python_version < "4.0"
|
||||
pyyaml==6.0.1 ; python_version >= "3.10" and python_version < "4.0"
|
||||
six==1.16.0 ; python_version >= "3.10" and python_version < "4.0"
|
||||
watchdog==3.0.0 ; python_version >= "3.10" and python_version < "4.0"
|
||||
@@ -112,7 +112,7 @@ const reducer = (state: State, action: Action): State => {
|
||||
const App = () => {
|
||||
const [state, dispatch] = useReducer(reducer, null);
|
||||
const stateRef = useRef(state); // Declare a reference to hold the current state
|
||||
const [isInstantUpdate, setIsInstantUpdate] = useState(true);
|
||||
const [isInstantUpdate, setIsInstantUpdate] = useState(false);
|
||||
const [showSettings, setShowSettings] = useState(false);
|
||||
const [showNotification, setShowNotification] = useState(true);
|
||||
const [notifications, setNotifications] = useState([]);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { Form, InputGroup, Button } from 'react-bootstrap';
|
||||
import { Form, InputGroup } from 'react-bootstrap';
|
||||
import { emit_update } from '../socket';
|
||||
import { DocStringComponent } from './DocStringComponent';
|
||||
import '../App.css';
|
||||
@@ -163,7 +163,12 @@ export const NumberComponent = React.memo((props: NumberComponentProps) => {
|
||||
addNotification(notificationMsg);
|
||||
}, [props.value]);
|
||||
|
||||
const handleNumericKey = (key: string, value: string, selectionStart: number) => {
|
||||
const handleNumericKey = (
|
||||
key: string,
|
||||
value: string,
|
||||
selectionStart: number,
|
||||
selectionEnd: number
|
||||
) => {
|
||||
// Check if a number key or a decimal point key is pressed
|
||||
if (key === '.' && (value.includes('.') || props.type === 'int')) {
|
||||
// Check if value already contains a decimal. If so, ignore input.
|
||||
@@ -171,8 +176,18 @@ export const NumberComponent = React.memo((props: NumberComponentProps) => {
|
||||
console.warn('Invalid input! Ignoring...');
|
||||
return { value, selectionStart };
|
||||
}
|
||||
|
||||
let newValue = value;
|
||||
|
||||
// Add the new key at the cursor's position
|
||||
const newValue = value.slice(0, selectionStart) + key + value.slice(selectionStart);
|
||||
if (selectionEnd > selectionStart) {
|
||||
// If there is a selection, replace it with the key
|
||||
newValue = value.slice(0, selectionStart) + key + value.slice(selectionEnd);
|
||||
} else {
|
||||
// otherwise, append the key after the selection start
|
||||
newValue = value.slice(0, selectionStart) + key + value.slice(selectionStart);
|
||||
}
|
||||
|
||||
return { value: newValue, selectionStart: selectionStart + 1 };
|
||||
};
|
||||
const handleKeyDown = (event) => {
|
||||
@@ -204,13 +219,15 @@ export const NumberComponent = React.memo((props: NumberComponentProps) => {
|
||||
({ value: newValue, selectionStart } = handleNumericKey(
|
||||
key,
|
||||
value,
|
||||
selectionStart
|
||||
selectionStart,
|
||||
selectionEnd
|
||||
));
|
||||
} else if (key === '.') {
|
||||
({ value: newValue, selectionStart } = handleNumericKey(
|
||||
key,
|
||||
value,
|
||||
selectionStart
|
||||
selectionStart,
|
||||
selectionEnd
|
||||
));
|
||||
} else if (key === 'ArrowUp' || key === 'ArrowDown') {
|
||||
({ value: newValue, selectionStart } = handleArrowKey(
|
||||
@@ -277,16 +294,6 @@ export const NumberComponent = React.memo((props: NumberComponentProps) => {
|
||||
/>
|
||||
{unit && <InputGroup.Text>{unit}</InputGroup.Text>}
|
||||
</InputGroup>
|
||||
{!readOnly && (
|
||||
<div className="d-flex flex-column">
|
||||
<Button className="numberComponentButton" variant="outline-secondary">
|
||||
+
|
||||
</Button>
|
||||
<Button className="numberComponentButton" variant="outline-secondary">
|
||||
-
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { InputGroup, Form, Row, Col, Button, Collapse } from 'react-bootstrap';
|
||||
import { InputGroup, Form, Row, Col, Collapse, ToggleButton } from 'react-bootstrap';
|
||||
import { emit_update } from '../socket';
|
||||
import { DocStringComponent } from './DocStringComponent';
|
||||
import { Slider } from '@mui/material';
|
||||
@@ -144,10 +144,13 @@ export const SliderComponent = React.memo((props: SliderComponentProps) => {
|
||||
/>
|
||||
</Col>
|
||||
<Col xs="auto">
|
||||
<Button
|
||||
<ToggleButton
|
||||
onClick={() => setOpen(!open)}
|
||||
type="checkbox"
|
||||
checked={open}
|
||||
value=""
|
||||
className="btn"
|
||||
variant="white"
|
||||
variant="light"
|
||||
aria-controls="slider-settings"
|
||||
aria-expanded={open}>
|
||||
<svg
|
||||
@@ -159,7 +162,7 @@ export const SliderComponent = React.memo((props: SliderComponentProps) => {
|
||||
<path d="M8 4.754a3.246 3.246 0 1 0 0 6.492 3.246 3.246 0 0 0 0-6.492zM5.754 8a2.246 2.246 0 1 1 4.492 0 2.246 2.246 0 0 1-4.492 0z" />
|
||||
<path d="M9.796 1.343c-.527-1.79-3.065-1.79-3.592 0l-.094.319a.873.873 0 0 1-1.255.52l-.292-.16c-1.64-.892-3.433.902-2.54 2.541l.159.292a.873.873 0 0 1-.52 1.255l-.319.094c-1.79.527-1.79 3.065 0 3.592l.319.094a.873.873 0 0 1 .52 1.255l-.16.292c-.892 1.64.901 3.434 2.541 2.54l.292-.159a.873.873 0 0 1 1.255.52l.094.319c.527 1.79 3.065 1.79 3.592 0l.094-.319a.873.873 0 0 1 1.255-.52l.292.16c1.64.893 3.434-.902 2.54-2.541l-.159-.292a.873.873 0 0 1 .52-1.255l.319-.094c1.79-.527 1.79-3.065 0-3.592l-.319-.094a.873.873 0 0 1-.52-1.255l.16-.292c.893-1.64-.902-3.433-2.541-2.54l-.292.159a.873.873 0 0 1-1.255-.52l-.094-.319zm-2.633.283c.246-.835 1.428-.835 1.674 0l.094.319a1.873 1.873 0 0 0 2.693 1.115l.291-.16c.764-.415 1.6.42 1.184 1.185l-.159.292a1.873 1.873 0 0 0 1.116 2.692l.318.094c.835.246.835 1.428 0 1.674l-.319.094a1.873 1.873 0 0 0-1.115 2.693l.16.291c.415.764-.42 1.6-1.185 1.184l-.291-.159a1.873 1.873 0 0 0-2.693 1.116l-.094.318c-.246.835-1.428.835-1.674 0l-.094-.319a1.873 1.873 0 0 0-2.692-1.115l-.292.16c-.764.415-1.6-.42-1.184-1.185l.159-.291A1.873 1.873 0 0 0 1.945 8.93l-.319-.094c-.835-.246-.835-1.428 0-1.674l.319-.094A1.873 1.873 0 0 0 3.06 4.377l-.16-.292c-.415-.764.42-1.6 1.185-1.184l.292.159a1.873 1.873 0 0 0 2.692-1.115l.094-.319z" />
|
||||
</svg>
|
||||
</Button>
|
||||
</ToggleButton>
|
||||
</Col>
|
||||
</Row>
|
||||
<Collapse in={open}>
|
||||
|
||||
42
mkdocs.yml
Normal file
@@ -0,0 +1,42 @@
|
||||
site_name: pydase Documentation
|
||||
repo_url: https://github.com/tiqi-group/pydase
|
||||
edit_uri: blob/docs/docs/
|
||||
nav:
|
||||
- Home: index.md
|
||||
- Getting Started: getting-started.md
|
||||
- Developer Guide:
|
||||
- Developer Guide: dev-guide/README.md
|
||||
- API Reference: dev-guide/api.md
|
||||
- Adding Components: dev-guide/Adding_Components.md
|
||||
- About:
|
||||
- Release Notes: about/release-notes.md
|
||||
- Contributing: about/contributing.md
|
||||
- License: about/license.md
|
||||
|
||||
theme: readthedocs
|
||||
|
||||
extra_css:
|
||||
- css/extra.css
|
||||
|
||||
markdown_extensions:
|
||||
- smarty
|
||||
- toc:
|
||||
permalink: true
|
||||
baselevel: 4
|
||||
- pymdownx.highlight:
|
||||
anchor_linenums: true
|
||||
- pymdownx.snippets
|
||||
- pymdownx.superfences
|
||||
# - pymdownx.highlight:
|
||||
# - pymdownx.inlinehilite
|
||||
|
||||
|
||||
plugins:
|
||||
- include-markdown
|
||||
- search
|
||||
- mkdocstrings
|
||||
|
||||
watch:
|
||||
- src/pydase
|
||||
|
||||
|
||||
573
poetry.lock
generated
@@ -494,6 +494,23 @@ ufo = ["fs (>=2.2.0,<3)"]
|
||||
unicode = ["unicodedata2 (>=15.0.0)"]
|
||||
woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"]
|
||||
|
||||
[[package]]
|
||||
name = "ghp-import"
|
||||
version = "2.1.0"
|
||||
description = "Copy your docs directly to the gh-pages branch."
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"},
|
||||
{file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
python-dateutil = ">=2.8.1"
|
||||
|
||||
[package.extras]
|
||||
dev = ["flake8", "markdown", "twine", "wheel"]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.14.0"
|
||||
@@ -544,6 +561,23 @@ pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"
|
||||
plugins = ["setuptools"]
|
||||
requirements-deprecated-finder = ["pip-api", "pipreqs"]
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.1.2"
|
||||
description = "A very fast and expressive template engine."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
|
||||
{file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
MarkupSafe = ">=2.0"
|
||||
|
||||
[package.extras]
|
||||
i18n = ["Babel (>=2.7)"]
|
||||
|
||||
[[package]]
|
||||
name = "kiwisolver"
|
||||
version = "1.4.4"
|
||||
@@ -639,6 +673,80 @@ win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""}
|
||||
[package.extras]
|
||||
dev = ["Sphinx (==5.3.0)", "colorama (==0.4.5)", "colorama (==0.4.6)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v0.990)", "pre-commit (==3.2.1)", "pytest (==6.1.2)", "pytest (==7.2.1)", "pytest-cov (==2.12.1)", "pytest-cov (==4.0.0)", "pytest-mypy-plugins (==1.10.1)", "pytest-mypy-plugins (==1.9.3)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.2.0)", "tox (==3.27.1)", "tox (==4.4.6)"]
|
||||
|
||||
[[package]]
|
||||
name = "markdown"
|
||||
version = "3.4.4"
|
||||
description = "Python implementation of John Gruber's Markdown."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "Markdown-3.4.4-py3-none-any.whl", hash = "sha256:a4c1b65c0957b4bd9e7d86ddc7b3c9868fb9670660f6f99f6d1bca8954d5a941"},
|
||||
{file = "Markdown-3.4.4.tar.gz", hash = "sha256:225c6123522495d4119a90b3a3ba31a1e87a70369e03f14799ea9c0d7183a3d6"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.0)", "mkdocs-nature (>=0.4)"]
|
||||
testing = ["coverage", "pyyaml"]
|
||||
|
||||
[[package]]
|
||||
name = "markupsafe"
|
||||
version = "2.1.3"
|
||||
description = "Safely add untrusted strings to HTML/XML markup."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"},
|
||||
{file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"},
|
||||
{file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"},
|
||||
{file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"},
|
||||
{file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"},
|
||||
{file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"},
|
||||
{file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"},
|
||||
{file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"},
|
||||
{file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"},
|
||||
{file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"},
|
||||
{file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"},
|
||||
{file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"},
|
||||
{file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"},
|
||||
{file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"},
|
||||
{file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"},
|
||||
{file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"},
|
||||
{file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"},
|
||||
{file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"},
|
||||
{file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"},
|
||||
{file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"},
|
||||
{file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"},
|
||||
{file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"},
|
||||
{file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"},
|
||||
{file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"},
|
||||
{file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"},
|
||||
{file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"},
|
||||
{file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"},
|
||||
{file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"},
|
||||
{file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"},
|
||||
{file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"},
|
||||
{file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"},
|
||||
{file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"},
|
||||
{file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"},
|
||||
{file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"},
|
||||
{file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"},
|
||||
{file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"},
|
||||
{file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"},
|
||||
{file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"},
|
||||
{file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"},
|
||||
{file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"},
|
||||
{file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"},
|
||||
{file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"},
|
||||
{file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"},
|
||||
{file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"},
|
||||
{file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"},
|
||||
{file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"},
|
||||
{file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"},
|
||||
{file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"},
|
||||
{file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"},
|
||||
{file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matplotlib"
|
||||
version = "3.7.2"
|
||||
@@ -711,6 +819,101 @@ files = [
|
||||
{file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mergedeep"
|
||||
version = "1.3.4"
|
||||
description = "A deep merge function for 🐍."
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"},
|
||||
{file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mkdocs"
|
||||
version = "1.5.2"
|
||||
description = "Project documentation with Markdown."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "mkdocs-1.5.2-py3-none-any.whl", hash = "sha256:60a62538519c2e96fe8426654a67ee177350451616118a41596ae7c876bb7eac"},
|
||||
{file = "mkdocs-1.5.2.tar.gz", hash = "sha256:70d0da09c26cff288852471be03c23f0f521fc15cf16ac89c7a3bfb9ae8d24f9"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=7.0"
|
||||
colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""}
|
||||
ghp-import = ">=1.0"
|
||||
jinja2 = ">=2.11.1"
|
||||
markdown = ">=3.2.1"
|
||||
markupsafe = ">=2.0.1"
|
||||
mergedeep = ">=1.3.4"
|
||||
packaging = ">=20.5"
|
||||
pathspec = ">=0.11.1"
|
||||
platformdirs = ">=2.2.0"
|
||||
pyyaml = ">=5.1"
|
||||
pyyaml-env-tag = ">=0.1"
|
||||
watchdog = ">=2.0"
|
||||
|
||||
[package.extras]
|
||||
i18n = ["babel (>=2.9.0)"]
|
||||
min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.3)", "jinja2 (==2.11.1)", "markdown (==3.2.1)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "packaging (==20.5)", "pathspec (==0.11.1)", "platformdirs (==2.2.0)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "typing-extensions (==3.10)", "watchdog (==2.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "mkdocs-autorefs"
|
||||
version = "0.5.0"
|
||||
description = "Automatically link across pages in MkDocs."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "mkdocs_autorefs-0.5.0-py3-none-any.whl", hash = "sha256:7930fcb8ac1249f10e683967aeaddc0af49d90702af111a5e390e8b20b3d97ff"},
|
||||
{file = "mkdocs_autorefs-0.5.0.tar.gz", hash = "sha256:9a5054a94c08d28855cfab967ada10ed5be76e2bfad642302a610b252c3274c0"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
Markdown = ">=3.3"
|
||||
mkdocs = ">=1.1"
|
||||
|
||||
[[package]]
|
||||
name = "mkdocs-include-markdown-plugin"
|
||||
version = "3.9.1"
|
||||
description = "Mkdocs Markdown includer plugin."
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "mkdocs_include_markdown_plugin-3.9.1-py3-none-any.whl", hash = "sha256:f33687e29ac66d045ba181ea50f054646b0090b42b0a4318f08e7f1d1235e6f6"},
|
||||
{file = "mkdocs_include_markdown_plugin-3.9.1.tar.gz", hash = "sha256:5e5698e78d7fea111be9873a456089daa333497988405acaac8eba2924a19152"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
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 = "mkdocstrings"
|
||||
version = "0.22.0"
|
||||
description = "Automatic documentation from sources, for MkDocs."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "mkdocstrings-0.22.0-py3-none-any.whl", hash = "sha256:2d4095d461554ff6a778fdabdca3c00c468c2f1459d469f7a7f622a2b23212ba"},
|
||||
{file = "mkdocstrings-0.22.0.tar.gz", hash = "sha256:82a33b94150ebb3d4b5c73bab4598c3e21468c79ec072eff6931c8f3bfc38256"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
Jinja2 = ">=2.11.1"
|
||||
Markdown = ">=3.3"
|
||||
MarkupSafe = ">=1.1"
|
||||
mkdocs = ">=1.2"
|
||||
mkdocs-autorefs = ">=0.3.1"
|
||||
pymdown-extensions = ">=6.3"
|
||||
|
||||
[package.extras]
|
||||
crystal = ["mkdocstrings-crystal (>=0.3.4)"]
|
||||
python = ["mkdocstrings-python (>=0.5.2)"]
|
||||
python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "mr-proper"
|
||||
version = "0.0.7"
|
||||
@@ -729,33 +932,38 @@ stdlib-list = ">=0.5.0"
|
||||
|
||||
[[package]]
|
||||
name = "mypy"
|
||||
version = "1.5.0"
|
||||
version = "1.5.1"
|
||||
description = "Optional static typing for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "mypy-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ad3109bec37cc33654de8db30fe8ff3a1bb57ea65144167d68185e6dced9868d"},
|
||||
{file = "mypy-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b4ea3a0241cb005b0ccdbd318fb99619b21ae51bcf1660b95fc22e0e7d3ba4a1"},
|
||||
{file = "mypy-1.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1fe816e26e676c1311b9e04fd576543b873576d39439f7c24c8e5c7728391ecf"},
|
||||
{file = "mypy-1.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:42170e68adb1603ccdc55a30068f72bcfcde2ce650188e4c1b2a93018b826735"},
|
||||
{file = "mypy-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:d145b81a8214687cfc1f85c03663a5bbe736777410e5580e54d526e7e904f564"},
|
||||
{file = "mypy-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c36011320e452eb30bec38b9fd3ba20569dc9545d7d4540d967f3ea1fab9c374"},
|
||||
{file = "mypy-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f3940cf5845b2512b3ab95463198b0cdf87975dfd17fdcc6ce9709a9abe09e69"},
|
||||
{file = "mypy-1.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9166186c498170e1ff478a7f540846b2169243feb95bc228d39a67a1a450cdc6"},
|
||||
{file = "mypy-1.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:725b57a19b7408ef66a0fd9db59b5d3e528922250fb56e50bded27fea9ff28f0"},
|
||||
{file = "mypy-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:eec5c927aa4b3e8b4781840f1550079969926d0a22ce38075f6cfcf4b13e3eb4"},
|
||||
{file = "mypy-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:79c520aa24f21852206b5ff2cf746dc13020113aa73fa55af504635a96e62718"},
|
||||
{file = "mypy-1.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:769ddb6bfe55c2bd9c7d6d7020885a5ea14289619db7ee650e06b1ef0852c6f4"},
|
||||
{file = "mypy-1.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbf18f8db7e5f060d61c91e334d3b96d6bb624ddc9ee8a1cde407b737acbca2c"},
|
||||
{file = "mypy-1.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a2500ad063413bc873ae102cf655bf49889e0763b260a3a7cf544a0cbbf7e70a"},
|
||||
{file = "mypy-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:84cf9f7d8a8a22bb6a36444480f4cbf089c917a4179fbf7eea003ea931944a7f"},
|
||||
{file = "mypy-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a551ed0fc02455fe2c1fb0145160df8336b90ab80224739627b15ebe2b45e9dc"},
|
||||
{file = "mypy-1.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:372fd97293ed0076d52695849f59acbbb8461c4ab447858cdaeaf734a396d823"},
|
||||
{file = "mypy-1.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8a7444d6fcac7e2585b10abb91ad900a576da7af8f5cffffbff6065d9115813"},
|
||||
{file = "mypy-1.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:35b13335c6c46a386577a51f3d38b2b5d14aa619e9633bb756bd77205e4bd09f"},
|
||||
{file = "mypy-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:2c9d570f53908cbea326ad8f96028a673b814d9dca7515bf71d95fa662c3eb6f"},
|
||||
{file = "mypy-1.5.0-py3-none-any.whl", hash = "sha256:69b32d0dedd211b80f1b7435644e1ef83033a2af2ac65adcdc87c38db68a86be"},
|
||||
{file = "mypy-1.5.0.tar.gz", hash = "sha256:f3460f34b3839b9bc84ee3ed65076eb827cd99ed13ed08d723f9083cada4a212"},
|
||||
{file = "mypy-1.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f33592ddf9655a4894aef22d134de7393e95fcbdc2d15c1ab65828eee5c66c70"},
|
||||
{file = "mypy-1.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:258b22210a4a258ccd077426c7a181d789d1121aca6db73a83f79372f5569ae0"},
|
||||
{file = "mypy-1.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9ec1f695f0c25986e6f7f8778e5ce61659063268836a38c951200c57479cc12"},
|
||||
{file = "mypy-1.5.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:abed92d9c8f08643c7d831300b739562b0a6c9fcb028d211134fc9ab20ccad5d"},
|
||||
{file = "mypy-1.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:a156e6390944c265eb56afa67c74c0636f10283429171018446b732f1a05af25"},
|
||||
{file = "mypy-1.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6ac9c21bfe7bc9f7f1b6fae441746e6a106e48fc9de530dea29e8cd37a2c0cc4"},
|
||||
{file = "mypy-1.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:51cb1323064b1099e177098cb939eab2da42fea5d818d40113957ec954fc85f4"},
|
||||
{file = "mypy-1.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:596fae69f2bfcb7305808c75c00f81fe2829b6236eadda536f00610ac5ec2243"},
|
||||
{file = "mypy-1.5.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:32cb59609b0534f0bd67faebb6e022fe534bdb0e2ecab4290d683d248be1b275"},
|
||||
{file = "mypy-1.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:159aa9acb16086b79bbb0016145034a1a05360626046a929f84579ce1666b315"},
|
||||
{file = "mypy-1.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f6b0e77db9ff4fda74de7df13f30016a0a663928d669c9f2c057048ba44f09bb"},
|
||||
{file = "mypy-1.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:26f71b535dfc158a71264e6dc805a9f8d2e60b67215ca0bfa26e2e1aa4d4d373"},
|
||||
{file = "mypy-1.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fc3a600f749b1008cc75e02b6fb3d4db8dbcca2d733030fe7a3b3502902f161"},
|
||||
{file = "mypy-1.5.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:26fb32e4d4afa205b24bf645eddfbb36a1e17e995c5c99d6d00edb24b693406a"},
|
||||
{file = "mypy-1.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:82cb6193de9bbb3844bab4c7cf80e6227d5225cc7625b068a06d005d861ad5f1"},
|
||||
{file = "mypy-1.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4a465ea2ca12804d5b34bb056be3a29dc47aea5973b892d0417c6a10a40b2d65"},
|
||||
{file = "mypy-1.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9fece120dbb041771a63eb95e4896791386fe287fefb2837258925b8326d6160"},
|
||||
{file = "mypy-1.5.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d28ddc3e3dfeab553e743e532fb95b4e6afad51d4706dd22f28e1e5e664828d2"},
|
||||
{file = "mypy-1.5.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:57b10c56016adce71fba6bc6e9fd45d8083f74361f629390c556738565af8eeb"},
|
||||
{file = "mypy-1.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:ff0cedc84184115202475bbb46dd99f8dcb87fe24d5d0ddfc0fe6b8575c88d2f"},
|
||||
{file = "mypy-1.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8f772942d372c8cbac575be99f9cc9d9fb3bd95c8bc2de6c01411e2c84ebca8a"},
|
||||
{file = "mypy-1.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5d627124700b92b6bbaa99f27cbe615c8ea7b3402960f6372ea7d65faf376c14"},
|
||||
{file = "mypy-1.5.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:361da43c4f5a96173220eb53340ace68cda81845cd88218f8862dfb0adc8cddb"},
|
||||
{file = "mypy-1.5.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:330857f9507c24de5c5724235e66858f8364a0693894342485e543f5b07c8693"},
|
||||
{file = "mypy-1.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:c543214ffdd422623e9fedd0869166c2f16affe4ba37463975043ef7d2ea8770"},
|
||||
{file = "mypy-1.5.1-py3-none-any.whl", hash = "sha256:f757063a83970d67c444f6e01d9550a7402322af3557ce7630d3c957386fa8f5"},
|
||||
{file = "mypy-1.5.1.tar.gz", hash = "sha256:b031b9601f1060bf1281feab89697324726ba0c0bae9d7cd7ab4b690940f0b92"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -779,6 +987,20 @@ files = [
|
||||
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nodeenv"
|
||||
version = "1.8.0"
|
||||
description = "Node.js virtual environment builder"
|
||||
optional = false
|
||||
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*"
|
||||
files = [
|
||||
{file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"},
|
||||
{file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
setuptools = "*"
|
||||
|
||||
[[package]]
|
||||
name = "numpy"
|
||||
version = "1.25.2"
|
||||
@@ -990,18 +1212,18 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.1.1"
|
||||
version = "2.2.0"
|
||||
description = "Data validation using Python type hints"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "pydantic-2.1.1-py3-none-any.whl", hash = "sha256:43bdbf359d6304c57afda15c2b95797295b702948082d4c23851ce752f21da70"},
|
||||
{file = "pydantic-2.1.1.tar.gz", hash = "sha256:22d63db5ce4831afd16e7c58b3192d3faf8f79154980d9397d9867254310ba4b"},
|
||||
{file = "pydantic-2.2.0-py3-none-any.whl", hash = "sha256:a479d511d1337ad091bd1ef90a7aaf707326968cff78a579108bc289b7f5ecdd"},
|
||||
{file = "pydantic-2.2.0.tar.gz", hash = "sha256:5f30f37a75bca15930e256da6a401f1cb953eb3bc578d454c0876f4e1459a7fa"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
annotated-types = ">=0.4.0"
|
||||
pydantic-core = "2.4.0"
|
||||
pydantic-core = "2.6.0"
|
||||
typing-extensions = ">=4.6.1"
|
||||
|
||||
[package.extras]
|
||||
@@ -1009,112 +1231,117 @@ email = ["email-validator (>=2.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-core"
|
||||
version = "2.4.0"
|
||||
version = "2.6.0"
|
||||
description = ""
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "pydantic_core-2.4.0-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:2ca4687dd996bde7f3c420def450797feeb20dcee2b9687023e3323c73fc14a2"},
|
||||
{file = "pydantic_core-2.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:782fced7d61469fd1231b184a80e4f2fa7ad54cd7173834651a453f96f29d673"},
|
||||
{file = "pydantic_core-2.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6213b471b68146af97b8551294e59e7392c2117e28ffad9c557c65087f4baee3"},
|
||||
{file = "pydantic_core-2.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63797499a219d8e81eb4e0c42222d0a4c8ec896f5c76751d4258af95de41fdf1"},
|
||||
{file = "pydantic_core-2.4.0-cp310-cp310-manylinux_2_24_armv7l.whl", hash = "sha256:0455876d575a35defc4da7e0a199596d6c773e20d3d42fa1fc29f6aa640369ed"},
|
||||
{file = "pydantic_core-2.4.0-cp310-cp310-manylinux_2_24_ppc64le.whl", hash = "sha256:8c938c96294d983dcf419b54dba2d21056959c22911d41788efbf949a29ae30d"},
|
||||
{file = "pydantic_core-2.4.0-cp310-cp310-manylinux_2_24_s390x.whl", hash = "sha256:878a5017d93e776c379af4e7b20f173c82594d94fa073059bcc546789ad50bf8"},
|
||||
{file = "pydantic_core-2.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:69159afc2f2dc43285725f16143bc5df3c853bc1cb7df6021fce7ef1c69e8171"},
|
||||
{file = "pydantic_core-2.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:54df7df399b777c1fd144f541c95d351b3aa110535a6810a6a569905d106b6f3"},
|
||||
{file = "pydantic_core-2.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e412607ca89a0ced10758dfb8f9adcc365ce4c1c377e637c01989a75e9a9ec8a"},
|
||||
{file = "pydantic_core-2.4.0-cp310-none-win32.whl", hash = "sha256:853f103e2b9a58832fdd08a587a51de8b552ae90e1a5d167f316b7eabf8d7dde"},
|
||||
{file = "pydantic_core-2.4.0-cp310-none-win_amd64.whl", hash = "sha256:3ba2c9c94a9176f6321a879c8b864d7c5b12d34f549a4c216c72ce213d7d953c"},
|
||||
{file = "pydantic_core-2.4.0-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:a8b7acd04896e8f161e1500dc5f218017db05c1d322f054e89cbd089ce5d0071"},
|
||||
{file = "pydantic_core-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:16468bd074fa4567592d3255bf25528ed41e6b616d69bf07096bdb5b66f947d1"},
|
||||
{file = "pydantic_core-2.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cba5ad5eef02c86a1f3da00544cbc59a510d596b27566479a7cd4d91c6187a11"},
|
||||
{file = "pydantic_core-2.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7206e41e04b443016e930e01685bab7a308113c0b251b3f906942c8d4b48fcb"},
|
||||
{file = "pydantic_core-2.4.0-cp311-cp311-manylinux_2_24_armv7l.whl", hash = "sha256:c1375025f0bfc9155286ebae8eecc65e33e494c90025cda69e247c3ccd2bab00"},
|
||||
{file = "pydantic_core-2.4.0-cp311-cp311-manylinux_2_24_ppc64le.whl", hash = "sha256:3534118289e33130ed3f1cc487002e8d09b9f359be48b02e9cd3de58ce58fba9"},
|
||||
{file = "pydantic_core-2.4.0-cp311-cp311-manylinux_2_24_s390x.whl", hash = "sha256:94d2b36a74623caab262bf95f0e365c2c058396082bd9d6a9e825657d0c1e7fa"},
|
||||
{file = "pydantic_core-2.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:af24ad4fbaa5e4a2000beae0c3b7fd1c78d7819ab90f9370a1cfd8998e3f8a3c"},
|
||||
{file = "pydantic_core-2.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bf10963d8aed8bbe0165b41797c9463d4c5c8788ae6a77c68427569be6bead41"},
|
||||
{file = "pydantic_core-2.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:68199ada7c310ddb8c76efbb606a0de656b40899388a7498954f423e03fc38be"},
|
||||
{file = "pydantic_core-2.4.0-cp311-none-win32.whl", hash = "sha256:6f855bcc96ed3dd56da7373cfcc9dcbabbc2073cac7f65c185772d08884790ce"},
|
||||
{file = "pydantic_core-2.4.0-cp311-none-win_amd64.whl", hash = "sha256:de39eb3bab93a99ddda1ac1b9aa331b944d8bcc4aa9141148f7fd8ee0299dafc"},
|
||||
{file = "pydantic_core-2.4.0-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:f773b39780323a0499b53ebd91a28ad11cde6705605d98d999dfa08624caf064"},
|
||||
{file = "pydantic_core-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a297c0d6c61963c5c3726840677b798ca5b7dfc71bc9c02b9a4af11d23236008"},
|
||||
{file = "pydantic_core-2.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:546064c55264156b973b5e65e5fafbe5e62390902ce3cf6b4005765505e8ff56"},
|
||||
{file = "pydantic_core-2.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36ba9e728588588f0196deaf6751b9222492331b5552f865a8ff120869d372e0"},
|
||||
{file = "pydantic_core-2.4.0-cp312-cp312-manylinux_2_24_armv7l.whl", hash = "sha256:57a53a75010c635b3ad6499e7721eaa3b450e03f6862afe2dbef9c8f66e46ec8"},
|
||||
{file = "pydantic_core-2.4.0-cp312-cp312-manylinux_2_24_ppc64le.whl", hash = "sha256:4b262bbc13022f2097c48a21adcc360a81d83dc1d854c11b94953cd46d7d3c07"},
|
||||
{file = "pydantic_core-2.4.0-cp312-cp312-manylinux_2_24_s390x.whl", hash = "sha256:01947ad728f426fa07fcb26457ebf90ce29320259938414bc0edd1476e75addb"},
|
||||
{file = "pydantic_core-2.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b2799c2eaf182769889761d4fb4d78b82bc47dae833799fedbf69fc7de306faa"},
|
||||
{file = "pydantic_core-2.4.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a08fd490ba36d1fbb2cd5dcdcfb9f3892deb93bd53456724389135712b5fc735"},
|
||||
{file = "pydantic_core-2.4.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1e8a7c62d15a5c4b307271e4252d76ebb981d6251c6ecea4daf203ef0179ea4f"},
|
||||
{file = "pydantic_core-2.4.0-cp312-none-win32.whl", hash = "sha256:9206c14a67c38de7b916e486ae280017cf394fa4b1aa95cfe88621a4e1d79725"},
|
||||
{file = "pydantic_core-2.4.0-cp312-none-win_amd64.whl", hash = "sha256:884235507549a6b2d3c4113fb1877ae263109e787d9e0eb25c35982ab28d0399"},
|
||||
{file = "pydantic_core-2.4.0-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:4cbe929efa77a806e8f1a97793f2dc3ea3475ae21a9ed0f37c21320fe93f6f50"},
|
||||
{file = "pydantic_core-2.4.0-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:9137289de8fe845c246a8c3482dd0cb40338846ba683756d8f489a4bd8fddcae"},
|
||||
{file = "pydantic_core-2.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5d8e764b5646623e57575f624f8ebb8f7a9f7fd1fae682ef87869ca5fec8dcf"},
|
||||
{file = "pydantic_core-2.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fba0aff4c407d0274e43697e785bcac155ad962be57518d1c711f45e72da70f"},
|
||||
{file = "pydantic_core-2.4.0-cp37-cp37m-manylinux_2_24_armv7l.whl", hash = "sha256:30527d173e826f2f7651f91c821e337073df1555e3b5a0b7b1e2c39e26e50678"},
|
||||
{file = "pydantic_core-2.4.0-cp37-cp37m-manylinux_2_24_ppc64le.whl", hash = "sha256:bd7d1dde70ff3e09e4bc7a1cbb91a7a538add291bfd5b3e70ef1e7b45192440f"},
|
||||
{file = "pydantic_core-2.4.0-cp37-cp37m-manylinux_2_24_s390x.whl", hash = "sha256:72f1216ca8cef7b8adacd4c4c6b89c3b0c4f97503197f5284c80f36d6e4edd30"},
|
||||
{file = "pydantic_core-2.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b013c7861a7c7bfcec48fd709513fea6f9f31727e7a0a93ca0dd12e056740717"},
|
||||
{file = "pydantic_core-2.4.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:478f5f6d7e32bd4a04d102160efb2d389432ecf095fe87c555c0a6fc4adfc1a4"},
|
||||
{file = "pydantic_core-2.4.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d9610b47b5fe4aacbbba6a9cb5f12cbe864eec99dbfed5710bd32ef5dd8a5d5b"},
|
||||
{file = "pydantic_core-2.4.0-cp37-none-win32.whl", hash = "sha256:ff246c0111076c8022f9ba325c294f2cb5983403506989253e04dbae565e019b"},
|
||||
{file = "pydantic_core-2.4.0-cp37-none-win_amd64.whl", hash = "sha256:d0c2b713464a8e263a243ae7980d81ce2de5ac59a9f798a282e44350b42dc516"},
|
||||
{file = "pydantic_core-2.4.0-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:12ef6838245569fd60a179fade81ca4b90ae2fa0ef355d616f519f7bb27582db"},
|
||||
{file = "pydantic_core-2.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:49db206eb8fdc4b4f30e6e3e410584146d813c151928f94ec0db06c4f2595538"},
|
||||
{file = "pydantic_core-2.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a507d7fa44688bbac76af6521e488b3da93de155b9cba6f2c9b7833ce243d59"},
|
||||
{file = "pydantic_core-2.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffe18407a4d000c568182ce5388bbbedeb099896904e43fc14eee76cfae6dec5"},
|
||||
{file = "pydantic_core-2.4.0-cp38-cp38-manylinux_2_24_armv7l.whl", hash = "sha256:fa8e48001b39d54d97d7b380a0669fa99fc0feeb972e35a2d677ba59164a9a22"},
|
||||
{file = "pydantic_core-2.4.0-cp38-cp38-manylinux_2_24_ppc64le.whl", hash = "sha256:394f12a2671ff8c4dfa2e85be6c08be0651ad85bc1e6aa9c77c21671baaf28cd"},
|
||||
{file = "pydantic_core-2.4.0-cp38-cp38-manylinux_2_24_s390x.whl", hash = "sha256:2f9ea0355f90db2a76af530245fa42f04d98f752a1236ed7c6809ec484560d5b"},
|
||||
{file = "pydantic_core-2.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:61d4e713f467abcdd59b47665d488bb898ad3dd47ce7446522a50e0cbd8e8279"},
|
||||
{file = "pydantic_core-2.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:453862ab268f6326b01f067ed89cb3a527d34dc46f6f4eeec46a15bbc706d0da"},
|
||||
{file = "pydantic_core-2.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:56a85fa0dab1567bd0cac10f0c3837b03e8a0d939e6a8061a3a420acd97e9421"},
|
||||
{file = "pydantic_core-2.4.0-cp38-none-win32.whl", hash = "sha256:0d726108c1c0380b88b6dd4db559f0280e0ceda9e077f46ff90bc85cd4d03e77"},
|
||||
{file = "pydantic_core-2.4.0-cp38-none-win_amd64.whl", hash = "sha256:047580388644c473b934d27849f8ed8dbe45df0adb72104e78b543e13bf69762"},
|
||||
{file = "pydantic_core-2.4.0-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:867d3eea954bea807cabba83cfc939c889a18576d66d197c60025b15269d7cc0"},
|
||||
{file = "pydantic_core-2.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:664402ef0c238a7f8a46efb101789d5f2275600fb18114446efec83cfadb5b66"},
|
||||
{file = "pydantic_core-2.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64e8012ad60a5f0da09ed48725e6e923d1be25f2f091a640af6079f874663813"},
|
||||
{file = "pydantic_core-2.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac2b680de398f293b68183317432b3d67ab3faeba216aec18de0c395cb5e3060"},
|
||||
{file = "pydantic_core-2.4.0-cp39-cp39-manylinux_2_24_armv7l.whl", hash = "sha256:8efc1be43b036c2b6bcfb1451df24ee0ddcf69c31351003daf2699ed93f5687b"},
|
||||
{file = "pydantic_core-2.4.0-cp39-cp39-manylinux_2_24_ppc64le.whl", hash = "sha256:d93aedbc4614cc21b9ab0d0c4ccd7143354c1f7cffbbe96ae5216ad21d1b21b5"},
|
||||
{file = "pydantic_core-2.4.0-cp39-cp39-manylinux_2_24_s390x.whl", hash = "sha256:af788b64e13d52fc3600a68b16d31fa8d8573e3ff2fc9a38f8a60b8d94d1f012"},
|
||||
{file = "pydantic_core-2.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:97c6349c81cee2e69ef59eba6e6c08c5936e6b01c2d50b9e4ac152217845ae09"},
|
||||
{file = "pydantic_core-2.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cc086ddb6dc654a15deeed1d1f2bcb1cb924ebd70df9dca738af19f64229b06c"},
|
||||
{file = "pydantic_core-2.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e953353180bec330c3b830891d260b6f8e576e2d18db3c78d314e56bb2276066"},
|
||||
{file = "pydantic_core-2.4.0-cp39-none-win32.whl", hash = "sha256:6feb4b64d11d5420e517910d60a907d08d846cacaf4e029668725cd21d16743c"},
|
||||
{file = "pydantic_core-2.4.0-cp39-none-win_amd64.whl", hash = "sha256:153a61ac4030fa019b70b31fb7986461119230d3ba0ab661c757cfea652f4332"},
|
||||
{file = "pydantic_core-2.4.0-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:3fcf529382b282a30b466bd7af05be28e22aa620e016135ac414f14e1ee6b9e1"},
|
||||
{file = "pydantic_core-2.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2edef05b63d82568b877002dc4cb5cc18f8929b59077120192df1e03e0c633f8"},
|
||||
{file = "pydantic_core-2.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da055a1b0bfa8041bb2ff586b2cb0353ed03944a3472186a02cc44a557a0e661"},
|
||||
{file = "pydantic_core-2.4.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:77dadc764cf7c5405e04866181c5bd94a447372a9763e473abb63d1dfe9b7387"},
|
||||
{file = "pydantic_core-2.4.0-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:a4ea23b07f29487a7bef2a869f68c7ee0e05424d81375ce3d3de829314c6b5ec"},
|
||||
{file = "pydantic_core-2.4.0-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:382f0baa044d674ad59455a5eff83d7965572b745cc72df35c52c2ce8c731d37"},
|
||||
{file = "pydantic_core-2.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:08f89697625e453421401c7f661b9d1eb4c9e4c0a12fd256eeb55b06994ac6af"},
|
||||
{file = "pydantic_core-2.4.0-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:43a405ce520b45941df9ff55d0cd09762017756a7b413bbad3a6e8178e64a2c2"},
|
||||
{file = "pydantic_core-2.4.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:584a7a818c84767af16ce8bda5d4f7fedb37d3d231fc89928a192f567e4ef685"},
|
||||
{file = "pydantic_core-2.4.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04922fea7b13cd480586fa106345fe06e43220b8327358873c22d8dfa7a711c7"},
|
||||
{file = "pydantic_core-2.4.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:17156abac20a9feed10feec867fddd91a80819a485b0107fe61f09f2117fe5f3"},
|
||||
{file = "pydantic_core-2.4.0-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4e562cc63b04636cde361fd47569162f1daa94c759220ff202a8129902229114"},
|
||||
{file = "pydantic_core-2.4.0-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:90f3785146f701e053bb6b9e8f53acce2c919aca91df88bd4975be0cb926eb41"},
|
||||
{file = "pydantic_core-2.4.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:e40b1e97edd3dc127aa53d8a5e539a3d0c227d71574d3f9ac1af02d58218a122"},
|
||||
{file = "pydantic_core-2.4.0-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:b27f3e67f6e031f6620655741b7d0d6bebea8b25d415924b3e8bfef2dd7bd841"},
|
||||
{file = "pydantic_core-2.4.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be86c2eb12fb0f846262ace9d8f032dc6978b8cb26a058920ecb723dbcb87d05"},
|
||||
{file = "pydantic_core-2.4.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4665f7ed345012a8d2eddf4203ef145f5f56a291d010382d235b94e91813f88a"},
|
||||
{file = "pydantic_core-2.4.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:79262be5a292d1df060f29b9a7cdd66934801f987a817632d7552534a172709a"},
|
||||
{file = "pydantic_core-2.4.0-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:5fd905a69ac74eaba5041e21a1e8b1a479dab2b41c93bdcc4c1cede3c12a8d86"},
|
||||
{file = "pydantic_core-2.4.0-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:2ad538b7e07343001934417cdc8584623b4d8823c5b8b258e75ec8d327cec969"},
|
||||
{file = "pydantic_core-2.4.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:dd2429f7635ad4857b5881503f9c310be7761dc681c467a9d27787b674d1250a"},
|
||||
{file = "pydantic_core-2.4.0-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:efff8b6761a1f6e45cebd1b7a6406eb2723d2d5710ff0d1b624fe11313693989"},
|
||||
{file = "pydantic_core-2.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32a1e0352558cd7ccc014ffe818c7d87b15ec6145875e2cc5fa4bb7351a1033d"},
|
||||
{file = "pydantic_core-2.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a027f41c5008571314861744d83aff75a34cf3a07022e0be32b214a5bc93f7f1"},
|
||||
{file = "pydantic_core-2.4.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1927f0e15d190f11f0b8344373731e28fd774c6d676d8a6cfadc95c77214a48b"},
|
||||
{file = "pydantic_core-2.4.0-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:7aa82d483d5fb867d4fb10a138ffd57b0f1644e99f2f4f336e48790ada9ada5e"},
|
||||
{file = "pydantic_core-2.4.0-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b85778308bf945e9b33ac604e6793df9b07933108d20bdf53811bc7c2798a4af"},
|
||||
{file = "pydantic_core-2.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3ded19dcaefe2f6706d81e0db787b59095f4ad0fbadce1edffdf092294c8a23f"},
|
||||
{file = "pydantic_core-2.4.0.tar.gz", hash = "sha256:ec3473c9789cc00c7260d840c3db2c16dbfc816ca70ec87a00cddfa3e1a1cdd5"},
|
||||
{file = "pydantic_core-2.6.0-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:2ae2d2aa91f442427d5d607f5bc07a6601aea7e9812c158b11dfac4fca28b24a"},
|
||||
{file = "pydantic_core-2.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cddecc97d923c3fae698820a788d6e7fda61538244dd2a0808d6263115fe5870"},
|
||||
{file = "pydantic_core-2.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:482a20567991170d0b55aa4d73084858ab8d54804ffef8061f254c0f8b9cf668"},
|
||||
{file = "pydantic_core-2.6.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:775710d825f2b2ffe8a0bfd8d17cb8de6a9e562e78f50171c5afa9c508faa45c"},
|
||||
{file = "pydantic_core-2.6.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4fd9661a30a370faae9303dfde31d09d5b6f28113f8dace9a63f51d205703a8d"},
|
||||
{file = "pydantic_core-2.6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e9430096484696a1837f55728c804917ad694f8e965ad0317ff896db21c3a7b"},
|
||||
{file = "pydantic_core-2.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:253d769ce88f6d5b8ae8965f08c486114e30b5e5478f327348b77615a2a543cb"},
|
||||
{file = "pydantic_core-2.6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:959aa63057738b426137d3de99d8da118f5c8ba19a238fdb5e5f0717297e9da4"},
|
||||
{file = "pydantic_core-2.6.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c2c54ce857d0adb549fc735ffe84b9d1e77d1b460656fb2d3faa9050a85d8d37"},
|
||||
{file = "pydantic_core-2.6.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fe66139cfdd02ec4a0aad0cecf53bf9933c748097a97beb9042c03f236dd68b9"},
|
||||
{file = "pydantic_core-2.6.0-cp310-none-win32.whl", hash = "sha256:1781e985a9493f3fdca4c010fc6a009ab4fd40a61ab78e5cc9820eb8010c1c4c"},
|
||||
{file = "pydantic_core-2.6.0-cp310-none-win_amd64.whl", hash = "sha256:1d1b6c14c1116e797758bf1ff93ff18ab493279609aec6a60e6dee9de9065255"},
|
||||
{file = "pydantic_core-2.6.0-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:2f80f6790e87ec29ba28aab9a66b07ee789ec8fa6ea94aeac47e27f0019a061c"},
|
||||
{file = "pydantic_core-2.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:04d6de74f91ff1a88975bc5e3c7103b676106af380ce8d9b56649116e0855dc9"},
|
||||
{file = "pydantic_core-2.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c7507d40bd5d055dadba8ae9b6008356f380ce102942e0740228d97e8bd4152"},
|
||||
{file = "pydantic_core-2.6.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:82e34dc040001f50bec1a7a5c09fb6566635078ce91943cd10445a560cb3fe23"},
|
||||
{file = "pydantic_core-2.6.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:49848c89575d7768ea8762cc029f573a3d611452c41d05ae75bdcea8f77a9e5c"},
|
||||
{file = "pydantic_core-2.6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04b0e91c338536e3e3f77c3ed5354d14c46163f1c6b0706037b0b4be409eb943"},
|
||||
{file = "pydantic_core-2.6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6265372636a78bc6b8ba92f7d2dafca353c549edc8082a602d00a28f71a9155a"},
|
||||
{file = "pydantic_core-2.6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:42caa59248750573efbce6a74f3e121f9def86dc2087772d51f0907c2ed6dc61"},
|
||||
{file = "pydantic_core-2.6.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fd29541fb6f7647f535c0067cabb50ec014f13fa599ac4e34152abb5cb046988"},
|
||||
{file = "pydantic_core-2.6.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e4d46cd802c163914f07124735371812e9bed8a39acbebcee5bd06d43f454e1a"},
|
||||
{file = "pydantic_core-2.6.0-cp311-none-win32.whl", hash = "sha256:75850d211015ae46e28b3e05ee0cc8687316505cad385170aff70ad60f143011"},
|
||||
{file = "pydantic_core-2.6.0-cp311-none-win_amd64.whl", hash = "sha256:ff462b08951adaf55dbcc623d9b57823e888ffa4886f902dfc2c69d6ddc1ce4b"},
|
||||
{file = "pydantic_core-2.6.0-cp311-none-win_arm64.whl", hash = "sha256:658f4e8afe60d8400526d6db28d4e88e76027cf6111716fc090de87d14b5c311"},
|
||||
{file = "pydantic_core-2.6.0-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:a7d7b5af9ee32517280228629daca013ecc9a7834075af3d928287539ccd54ec"},
|
||||
{file = "pydantic_core-2.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d03de66eda2c3a6eab2c2cf43eeece37e4cf811e891361b1fb8d8d3cd109f3a"},
|
||||
{file = "pydantic_core-2.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:693df3a922d94ba1c42ea732df7ab2f0492d6081b0170e86753a45e8822342a6"},
|
||||
{file = "pydantic_core-2.6.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf6cb8b9c23dbb074f2d97b02af4d9d5401bd8015daad3e92fc35f88c5c07ba6"},
|
||||
{file = "pydantic_core-2.6.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8aee2623180f736fc426925489e84bd244e45de4175dec76f10d4fda775721b"},
|
||||
{file = "pydantic_core-2.6.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:582bfd6e7d09f5883f607b7171fcd2010d226497d9dfc9703c8aa8d58431fa84"},
|
||||
{file = "pydantic_core-2.6.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b52ce001eacd9906955576c71ee3fad9a442117b86dd84e5ea18e6ce287078d"},
|
||||
{file = "pydantic_core-2.6.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:179da6a5264d11cf2defba17c0003f6e27922d95f37b4818905115e2c9b8f7ed"},
|
||||
{file = "pydantic_core-2.6.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4118471f4ba0f92fbe84bb6c0f645b423eaa5453e0dc4b6c0a6759da818352ba"},
|
||||
{file = "pydantic_core-2.6.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:afd9d1ef2805729898f580ccde0e76a3edd39cf16778c2139222047c8d25893b"},
|
||||
{file = "pydantic_core-2.6.0-cp312-none-win32.whl", hash = "sha256:c029084413a8eeb7d7b179d647d1e1a5cbfd5e5a817862a0ba8c5024fc9febf2"},
|
||||
{file = "pydantic_core-2.6.0-cp312-none-win_amd64.whl", hash = "sha256:86a74d426ca995deb3c847a2b382775b93a306fce13ae7b66cdc5fb8090a3ac5"},
|
||||
{file = "pydantic_core-2.6.0-cp312-none-win_arm64.whl", hash = "sha256:5a878f37a144c5641ead8b0771164dd22237ed4013b9899f250f0992447114e0"},
|
||||
{file = "pydantic_core-2.6.0-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:f9ebd8c45c8729bb23bb902a5cff573996fe5d86c3fc8c17cde3443345533889"},
|
||||
{file = "pydantic_core-2.6.0-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:0faddd509ca1811d7e595cb48dc9b63d080a95f8434c5dc6660f268694f3c20f"},
|
||||
{file = "pydantic_core-2.6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:859e11c4543bfd16b8033d50a2d7e4190fc5c6e182a6419b0d7c41109e3841b9"},
|
||||
{file = "pydantic_core-2.6.0-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9f1a5fafbbadca467f426eb796bec61a908a670dfdcb984d300b9dd4d8b82433"},
|
||||
{file = "pydantic_core-2.6.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e3247b6d304226b12e240ff3fb0eb56b45520cd609d382fde6338a5556d44783"},
|
||||
{file = "pydantic_core-2.6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:67f7658ac47a88ea3859389c4a67713edce77ade653812e0a574bc8f0cb0d951"},
|
||||
{file = "pydantic_core-2.6.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:310a47d94895628d3563859cb970cad1b3ee7a5f2282d9bd5512b3c5a09d4379"},
|
||||
{file = "pydantic_core-2.6.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2ee383e256a0e4b8bff1832fb31c530380a1421a714276ffd32609ce58a4c77a"},
|
||||
{file = "pydantic_core-2.6.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:148162967ede812466915bee671403dd2ded9822332df6c52866348129d4e58e"},
|
||||
{file = "pydantic_core-2.6.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d0d672be281d2e297f95ca301710aed9ad7e10c56a691337c2f22375feb60f29"},
|
||||
{file = "pydantic_core-2.6.0-cp37-none-win32.whl", hash = "sha256:ed683ff1663fd596ce84cf4d132f7ce7b94f0b60686ee06ca2c8e151ccb918e7"},
|
||||
{file = "pydantic_core-2.6.0-cp37-none-win_amd64.whl", hash = "sha256:301e47c7cabc1c435773fcf0c7278181add0f211ddaf4c683bbfb62e09457c33"},
|
||||
{file = "pydantic_core-2.6.0-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:14ec281d30dd1a7fbf62e0afe4bc7bfac4b5edcf8da7affef1a79e874f3899cb"},
|
||||
{file = "pydantic_core-2.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d13fc39e2d2957f32d8fa9d013bd7165d00c43890bdaea1e20a726873c50531b"},
|
||||
{file = "pydantic_core-2.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:905048671ef08f2a504cdf7e26ffbe88efd74494ba821f2cdb1e4b1506236047"},
|
||||
{file = "pydantic_core-2.6.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:99b6004cd989bbcaf32e0794e6f42460b6f5ac047b2eb443a661cfdba29704e5"},
|
||||
{file = "pydantic_core-2.6.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4bac3e606b7f8fffd5e3a0d7c5d6ab110075c9dc16b9f8932cb077b6d985f8de"},
|
||||
{file = "pydantic_core-2.6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:77865eb89c646673bedc7de4acd0a076dd6bada2f01d010675031cd855b052cf"},
|
||||
{file = "pydantic_core-2.6.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdbc528c7c6fef0e9aa1b4ba620d707c9735cfc92e6b666b83862ee55faa9605"},
|
||||
{file = "pydantic_core-2.6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7291e0e36c1bc5c3b20d3f3cf77ba9ac7a26423ec50781d4f0435c45ddfe18c2"},
|
||||
{file = "pydantic_core-2.6.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f693255ffec26a090c76adfb8f6286b76f5f3c9aa245f4bbe03aede102d815ef"},
|
||||
{file = "pydantic_core-2.6.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9e8d12016b2fdbf885e3c7580fa8f6d2e90838586faee511656f2022ebf71a2d"},
|
||||
{file = "pydantic_core-2.6.0-cp38-none-win32.whl", hash = "sha256:59420b2fe9edfdc640e79aac09461400862d2e699ca59e5b96e5595cc7554736"},
|
||||
{file = "pydantic_core-2.6.0-cp38-none-win_amd64.whl", hash = "sha256:757372e9b5c81cec72a077237d5d026ccd5ad9bf4931bebee4c92177d52b4eba"},
|
||||
{file = "pydantic_core-2.6.0-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:6798756a5bee9991af64763ee2f3580505932a3f432af9a73bc9fdaca460261f"},
|
||||
{file = "pydantic_core-2.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3170a13c8cdb564f544ce03a7f26962828cce3456413b325fca49d32ef47ed1f"},
|
||||
{file = "pydantic_core-2.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51eb518682898df170d3d2ddd9c1f2a9496d79e5bd611b508d1a698e50b13fc6"},
|
||||
{file = "pydantic_core-2.6.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fe01f83bea0e4715c49449039b3c60a59408f0ceee61bb8c9a64699545e5b786"},
|
||||
{file = "pydantic_core-2.6.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11854f12f09d2a108d130645edbca7aecf24756455599b0b19dacd47499ccadc"},
|
||||
{file = "pydantic_core-2.6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16c572a839eb584115185146a04b15986e19e3cbf00e3788f8296b16ec7b3fd5"},
|
||||
{file = "pydantic_core-2.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e89b0a3f05416a7e67ec7257cddcf44263a10cea618cfc89855d46997c13742"},
|
||||
{file = "pydantic_core-2.6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5464f3d27376739c7fa0af47096ac3696db1d8996d086167b3643f0443a1a976"},
|
||||
{file = "pydantic_core-2.6.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0ff7df99df6ae485e33afafc7adbfae2e133501b5debea4f0c20cd1f679fa321"},
|
||||
{file = "pydantic_core-2.6.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ac4148c3bede4269f286c7d094e98c717f1101025145e89baacbafc7c5f7f74b"},
|
||||
{file = "pydantic_core-2.6.0-cp39-none-win32.whl", hash = "sha256:36d6211421a4dd6d11ccb08e9ac92d143132402403ab791688cfc01973ad3de1"},
|
||||
{file = "pydantic_core-2.6.0-cp39-none-win_amd64.whl", hash = "sha256:83f5a3e201fe16684c12e654423a0c293733a57a1f9a9f284dbfb1b59f0e79bb"},
|
||||
{file = "pydantic_core-2.6.0-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:eeb7b4e1dd925db174a410680c846cb7ab7eb1923f556b44cf53cea774dc42fa"},
|
||||
{file = "pydantic_core-2.6.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0b4bcc57b12980b67e5eed09732102b19380f79dcba09444faa7a5c1826a432"},
|
||||
{file = "pydantic_core-2.6.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5534040341ac6ad4d133023cd45da3654fff77795481c8e4d4508cafd248ba5"},
|
||||
{file = "pydantic_core-2.6.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:358f5d48aa850054ef1b148f4c3000b2ea216db4ab611039080bea294002349c"},
|
||||
{file = "pydantic_core-2.6.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f5ca934143857626de2423d65d487687c87931a62044ed5ee0deee55018569f4"},
|
||||
{file = "pydantic_core-2.6.0-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:5b3159c893a1d6dc93080b882d7c4fa8651abbb228a4d920066f3f48b7a200ac"},
|
||||
{file = "pydantic_core-2.6.0-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:5d9510004c4db5c683e349905c9700217da10b35d4447c7a1dfff1b6dd26192a"},
|
||||
{file = "pydantic_core-2.6.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:fc54501cdffbc8a7dbe300d6e4745c910d767a1aa273febca965f5fa561036b1"},
|
||||
{file = "pydantic_core-2.6.0-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:6cc456fc3c7156b23866ab953d3ff57010ab6a4b79ba686109ef93581467f6c3"},
|
||||
{file = "pydantic_core-2.6.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aedc8671291d6ff17b9fc587fed982c4feeffdd28351c577695a5f07945c4625"},
|
||||
{file = "pydantic_core-2.6.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997fa26fd71c5d8676fa6dfefc06be1fac65fd578934d40e7546c047b7bdd019"},
|
||||
{file = "pydantic_core-2.6.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4f346c8fbd953f47711c388d9b856cad87cf72a714302bc04056f89d6ac55388"},
|
||||
{file = "pydantic_core-2.6.0-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d61acd378c38bdcc1c109605831695eb27bd755d1fc5c765e40878601bd0c66b"},
|
||||
{file = "pydantic_core-2.6.0-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:3427ca73cffe42297bbb0ed712642d8484c42671b329441a2e51ce139f7e2f93"},
|
||||
{file = "pydantic_core-2.6.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:dabfdc82432499ceb33bb204fa0e5c0004a7dc1d85ba0250c5849ddfddd94819"},
|
||||
{file = "pydantic_core-2.6.0-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:10da8e685fe25be11089a666346461e01e23164688a224e33fee25d2a86da4e0"},
|
||||
{file = "pydantic_core-2.6.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:0dbd7262932b213b34f6c1bdd33b53b6ffc07e3cee21d63486d68e433020f452"},
|
||||
{file = "pydantic_core-2.6.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9d6f16d771334c49a173403805ef874aff9800ea7c44f94ebf3817ae9c5631e"},
|
||||
{file = "pydantic_core-2.6.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:646324855acd153632bb86cbbd222771df7859b43d2891ace57c5b8c818ba8a7"},
|
||||
{file = "pydantic_core-2.6.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f52e9edca854804b780bba5c82f7a1aafebb7a7c496879a45423cf991c361f9e"},
|
||||
{file = "pydantic_core-2.6.0-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:95e569d8f702851ab48e349c5eb2f8ea673657b7ed5f2ac335d540ebc8519385"},
|
||||
{file = "pydantic_core-2.6.0-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b2ddc15cf29dc4b385c667064d7efb96431006dcf523527c3d749494b73e73a6"},
|
||||
{file = "pydantic_core-2.6.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:0b52468b09ccee65bc96572345ec73dc89b42528874b626f7757000a6544d285"},
|
||||
{file = "pydantic_core-2.6.0-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:c020c5047b25e64c39006fa11f15d93adf4ae85154387f8e10232871ba78e7b2"},
|
||||
{file = "pydantic_core-2.6.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:892e7de51b8191929bf1246a04c13674a4d4b8dced8a4f86def85a1b0cb1a1e4"},
|
||||
{file = "pydantic_core-2.6.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d179b77fff4387a46ed0b63eb87ad6be58bb2a3a3415e69a44e918e8abcbd8c6"},
|
||||
{file = "pydantic_core-2.6.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b35585d14028c1afa41c1183906ce4128128d1114be9958b5ad0fb3721b50a4"},
|
||||
{file = "pydantic_core-2.6.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ffe8b2c8e30a87f6d7c1a78e23b8270a1acde9140cde425fa94688d302c8b2c9"},
|
||||
{file = "pydantic_core-2.6.0-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:6cc57fb772b48e5fd3691ca82d3756b6e64b885676d27d66bff551d951a18e5c"},
|
||||
{file = "pydantic_core-2.6.0-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:3d72ebaa6451490ae05d3da858120b395b3bf1bebc8a5238ef803ff0f4f16f38"},
|
||||
{file = "pydantic_core-2.6.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:535f47fe0d2db647fdb0376cbbadd34fd00b3a5f56f772b0b0ef26928e8afa22"},
|
||||
{file = "pydantic_core-2.6.0.tar.gz", hash = "sha256:e50513d8dd8ea67259d45986e314f545f219ebb2496eea52269e457cdc7419f4"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1131,6 +1358,21 @@ files = [
|
||||
{file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pymdown-extensions"
|
||||
version = "10.1"
|
||||
description = "Extension pack for Python Markdown."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "pymdown_extensions-10.1-py3-none-any.whl", hash = "sha256:ef25dbbae530e8f67575d222b75ff0649b1e841e22c2ae9a20bad9472c2207dc"},
|
||||
{file = "pymdown_extensions-10.1.tar.gz", hash = "sha256:508009b211373058debb8247e168de4cbcb91b1bff7b5e961b2c3e864e00b195"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
markdown = ">=3.2"
|
||||
pyyaml = "*"
|
||||
|
||||
[[package]]
|
||||
name = "pyparsing"
|
||||
version = "3.0.9"
|
||||
@@ -1145,6 +1387,24 @@ files = [
|
||||
[package.extras]
|
||||
diagrams = ["jinja2", "railroad-diagrams"]
|
||||
|
||||
[[package]]
|
||||
name = "pyright"
|
||||
version = "1.1.323"
|
||||
description = "Command line wrapper for pyright"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "pyright-1.1.323-py3-none-any.whl", hash = "sha256:23ce9eca401fda311be273784ebf128850d43a17f9e87dc299ffcdc0ffe91f75"},
|
||||
{file = "pyright-1.1.323.tar.gz", hash = "sha256:f3029bfe96a3436a505464d28e3433fafe23ac5f86f52edab9a26cd66685825e"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
nodeenv = ">=1.6.0"
|
||||
|
||||
[package.extras]
|
||||
all = ["twine (>=3.4.1)"]
|
||||
dev = ["twine (>=3.4.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "7.4.0"
|
||||
@@ -1320,6 +1580,20 @@ files = [
|
||||
{file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyyaml-env-tag"
|
||||
version = "0.1"
|
||||
description = "A custom YAML tag for referencing environment variables in YAML files. "
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"},
|
||||
{file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pyyaml = "*"
|
||||
|
||||
[[package]]
|
||||
name = "rpyc"
|
||||
version = "5.3.1"
|
||||
@@ -1469,6 +1743,45 @@ h11 = ">=0.8"
|
||||
[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 = "watchdog"
|
||||
version = "3.0.0"
|
||||
description = "Filesystem events monitoring"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "watchdog-3.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:336adfc6f5cc4e037d52db31194f7581ff744b67382eb6021c868322e32eef41"},
|
||||
{file = "watchdog-3.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a70a8dcde91be523c35b2bf96196edc5730edb347e374c7de7cd20c43ed95397"},
|
||||
{file = "watchdog-3.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:adfdeab2da79ea2f76f87eb42a3ab1966a5313e5a69a0213a3cc06ef692b0e96"},
|
||||
{file = "watchdog-3.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2b57a1e730af3156d13b7fdddfc23dea6487fceca29fc75c5a868beed29177ae"},
|
||||
{file = "watchdog-3.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7ade88d0d778b1b222adebcc0927428f883db07017618a5e684fd03b83342bd9"},
|
||||
{file = "watchdog-3.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7e447d172af52ad204d19982739aa2346245cc5ba6f579d16dac4bfec226d2e7"},
|
||||
{file = "watchdog-3.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9fac43a7466eb73e64a9940ac9ed6369baa39b3bf221ae23493a9ec4d0022674"},
|
||||
{file = "watchdog-3.0.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8ae9cda41fa114e28faf86cb137d751a17ffd0316d1c34ccf2235e8a84365c7f"},
|
||||
{file = "watchdog-3.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:25f70b4aa53bd743729c7475d7ec41093a580528b100e9a8c5b5efe8899592fc"},
|
||||
{file = "watchdog-3.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4f94069eb16657d2c6faada4624c39464f65c05606af50bb7902e036e3219be3"},
|
||||
{file = "watchdog-3.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7c5f84b5194c24dd573fa6472685b2a27cc5a17fe5f7b6fd40345378ca6812e3"},
|
||||
{file = "watchdog-3.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3aa7f6a12e831ddfe78cdd4f8996af9cf334fd6346531b16cec61c3b3c0d8da0"},
|
||||
{file = "watchdog-3.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:233b5817932685d39a7896b1090353fc8efc1ef99c9c054e46c8002561252fb8"},
|
||||
{file = "watchdog-3.0.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:13bbbb462ee42ec3c5723e1205be8ced776f05b100e4737518c67c8325cf6100"},
|
||||
{file = "watchdog-3.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8f3ceecd20d71067c7fd4c9e832d4e22584318983cabc013dbf3f70ea95de346"},
|
||||
{file = "watchdog-3.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c9d8c8ec7efb887333cf71e328e39cffbf771d8f8f95d308ea4125bf5f90ba64"},
|
||||
{file = "watchdog-3.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0e06ab8858a76e1219e68c7573dfeba9dd1c0219476c5a44d5333b01d7e1743a"},
|
||||
{file = "watchdog-3.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:d00e6be486affb5781468457b21a6cbe848c33ef43f9ea4a73b4882e5f188a44"},
|
||||
{file = "watchdog-3.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:c07253088265c363d1ddf4b3cdb808d59a0468ecd017770ed716991620b8f77a"},
|
||||
{file = "watchdog-3.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:5113334cf8cf0ac8cd45e1f8309a603291b614191c9add34d33075727a967709"},
|
||||
{file = "watchdog-3.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:51f90f73b4697bac9c9a78394c3acbbd331ccd3655c11be1a15ae6fe289a8c83"},
|
||||
{file = "watchdog-3.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:ba07e92756c97e3aca0912b5cbc4e5ad802f4557212788e72a72a47ff376950d"},
|
||||
{file = "watchdog-3.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:d429c2430c93b7903914e4db9a966c7f2b068dd2ebdd2fa9b9ce094c7d459f33"},
|
||||
{file = "watchdog-3.0.0-py3-none-win32.whl", hash = "sha256:3ed7c71a9dccfe838c2f0b6314ed0d9b22e77d268c67e015450a29036a81f60f"},
|
||||
{file = "watchdog-3.0.0-py3-none-win_amd64.whl", hash = "sha256:4c9956d27be0bb08fc5f30d9d0179a855436e655f046d288e2bcc11adfae893c"},
|
||||
{file = "watchdog-3.0.0-py3-none-win_ia64.whl", hash = "sha256:5d9f3a10e02d7371cd929b5d8f11e87d4bad890212ed3901f9b4d68767bee759"},
|
||||
{file = "watchdog-3.0.0.tar.gz", hash = "sha256:4d98a320595da7a7c5a18fc48cb633c2e73cda78f93cac2ef42d42bf609a33f9"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
watchmedo = ["PyYAML (>=3.10)"]
|
||||
|
||||
[[package]]
|
||||
name = "websockets"
|
||||
version = "11.0.3"
|
||||
@@ -1565,4 +1878,4 @@ dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"]
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.10"
|
||||
content-hash = "40dc80f2d2cd2eb9ec3e03f0d0e23f9692f6face4506032aae022656fcbea426"
|
||||
content-hash = "7410508f069fe5405fb96f8645c0bbe015e23b271ac8de4b1f1a9ecd1fe84d5e"
|
||||
|
||||
@@ -35,6 +35,14 @@ flake8-pep585 = "^0.1.7"
|
||||
flake8-pep604 = "^0.1.0"
|
||||
flake8-eradicate = "^1.4.0"
|
||||
matplotlib = "^3.7.2"
|
||||
pyright = "^1.1.323"
|
||||
|
||||
|
||||
[tool.poetry.group.docs.dependencies]
|
||||
mkdocs = "^1.5.2"
|
||||
mkdocs-include-markdown-plugin = "^3.9.1"
|
||||
mkdocstrings = "^0.22.0"
|
||||
pymdown-extensions = "^10.1"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
from pydase.data_service import DataService
|
||||
from pydase.server import Server
|
||||
from pydase.utils.logging import setup_logging
|
||||
|
||||
setup_logging()
|
||||
|
||||
__all__ = [
|
||||
"DataService",
|
||||
|
||||
@@ -4,6 +4,6 @@ from confz import BaseConfig, EnvSource
|
||||
|
||||
|
||||
class OperationMode(BaseConfig): # type: ignore
|
||||
environment: Literal["development"] | Literal["production"] = "production"
|
||||
environment: Literal["development"] | Literal["production"] = "development"
|
||||
|
||||
CONFIG_SOURCES = EnvSource(allow=["ENVIRONMENT"])
|
||||
|
||||
@@ -19,6 +19,7 @@ from pydase.utils.helpers import (
|
||||
get_component_class_names,
|
||||
get_nested_value_from_DataService_by_path_and_key,
|
||||
get_object_attr_from_path,
|
||||
is_property_attribute,
|
||||
parse_list_attr_and_index,
|
||||
update_value_if_changed,
|
||||
)
|
||||
@@ -58,13 +59,15 @@ class DataService(rpyc.Service, AbstractDataService):
|
||||
self._load_values_from_json()
|
||||
|
||||
def __setattr__(self, __name: str, __value: Any) -> None:
|
||||
current_value = getattr(self, __name, None)
|
||||
# parse ints into floats if current value is a float
|
||||
if isinstance(current_value, float) and isinstance(__value, int):
|
||||
__value = float(__value)
|
||||
# converting attributes that are not properties
|
||||
if not isinstance(getattr(type(self), __name, None), property):
|
||||
current_value = getattr(self, __name, None)
|
||||
# parse ints into floats if current value is a float
|
||||
if isinstance(current_value, float) and isinstance(__value, int):
|
||||
__value = float(__value)
|
||||
|
||||
if isinstance(current_value, u.Quantity):
|
||||
__value = u.convert_to_quantity(__value, str(current_value.u))
|
||||
if isinstance(current_value, u.Quantity):
|
||||
__value = u.convert_to_quantity(__value, str(current_value.u))
|
||||
|
||||
super().__setattr__(__name, __value)
|
||||
|
||||
@@ -84,6 +87,27 @@ class DataService(rpyc.Service, AbstractDataService):
|
||||
if not attr_name.startswith("_DataService__"):
|
||||
warn_if_instance_class_does_not_inherit_from_DataService(attr_value)
|
||||
|
||||
def __set_attribute_based_on_type( # noqa:CFQ002
|
||||
self,
|
||||
target_obj: Any,
|
||||
attr_name: str,
|
||||
attr: Any,
|
||||
value: Any,
|
||||
index: Optional[int],
|
||||
path_list: list[str],
|
||||
) -> None:
|
||||
if isinstance(attr, Enum):
|
||||
update_value_if_changed(target_obj, attr_name, attr.__class__[value])
|
||||
elif isinstance(attr, list) and index is not None:
|
||||
update_value_if_changed(attr, index, value)
|
||||
elif isinstance(attr, DataService) and isinstance(value, dict):
|
||||
for key, v in value.items():
|
||||
self.update_DataService_attribute([*path_list, attr_name], key, v)
|
||||
elif callable(attr):
|
||||
process_callable_attribute(attr, value["args"])
|
||||
else:
|
||||
update_value_if_changed(target_obj, attr_name, value)
|
||||
|
||||
def _rpyc_getattr(self, name: str) -> Any:
|
||||
if name.startswith("_"):
|
||||
# disallow special and private attributes
|
||||
@@ -338,24 +362,19 @@ class DataService(rpyc.Service, AbstractDataService):
|
||||
) -> None:
|
||||
# If attr_name corresponds to a list entry, extract the attr_name and the index
|
||||
attr_name, index = parse_list_attr_and_index(attr_name)
|
||||
|
||||
# Traverse the object according to the path parts
|
||||
target_obj = get_object_attr_from_path(self, path_list)
|
||||
|
||||
attr = get_object_attr_from_path(target_obj, [attr_name])
|
||||
# If the attribute is a property, change it using the setter without getting the
|
||||
# property value (would otherwise be bad for expensive getter methods)
|
||||
if is_property_attribute(target_obj, attr_name):
|
||||
setattr(target_obj, attr_name, value)
|
||||
return
|
||||
|
||||
attr = get_object_attr_from_path(target_obj, [attr_name])
|
||||
if attr is None:
|
||||
return
|
||||
|
||||
# Set the attribute at the terminal point of the path
|
||||
if isinstance(attr, Enum):
|
||||
update_value_if_changed(target_obj, attr_name, attr.__class__[value])
|
||||
elif isinstance(attr, list) and index is not None:
|
||||
update_value_if_changed(attr, index, value)
|
||||
elif isinstance(attr, DataService) and isinstance(value, dict):
|
||||
for key, v in value.items():
|
||||
self.update_DataService_attribute([*path_list, attr_name], key, v)
|
||||
elif callable(attr):
|
||||
return process_callable_attribute(attr, value["args"])
|
||||
else:
|
||||
update_value_if_changed(target_obj, attr_name, value)
|
||||
self.__set_attribute_based_on_type(
|
||||
target_obj, attr_name, attr, value, index, path_list
|
||||
)
|
||||
|
||||
@@ -97,6 +97,28 @@ class TaskManager:
|
||||
|
||||
@wraps(method)
|
||||
def start_task(*args: Any, **kwargs: Any) -> None:
|
||||
def task_done_callback(task: asyncio.Task, name: str) -> None:
|
||||
"""Handles tasks that have finished.
|
||||
|
||||
Removes a task from the tasks dictionary, calls the defined
|
||||
callbacks, and logs and re-raises exceptions."""
|
||||
|
||||
# removing the finished task from the tasks i
|
||||
self.tasks.pop(name, None)
|
||||
|
||||
# emit the notification that the task was stopped
|
||||
for callback in self.task_status_change_callbacks:
|
||||
callback(name, None)
|
||||
|
||||
exception = task.exception()
|
||||
if exception is not None:
|
||||
# Handle the exception, or you can re-raise it.
|
||||
logger.error(
|
||||
f"Task '{name}' encountered an exception: "
|
||||
f"{type(exception).__name__}: {exception}"
|
||||
)
|
||||
raise exception
|
||||
|
||||
async def task(*args: Any, **kwargs: Any) -> None:
|
||||
try:
|
||||
await method(*args, **kwargs)
|
||||
@@ -126,11 +148,18 @@ class TaskManager:
|
||||
**kwargs,
|
||||
}
|
||||
|
||||
# creating the task and adding the task_done_callback which checks
|
||||
# if an exception has occured during the task execution
|
||||
task_object = self._loop.create_task(task(*args, **kwargs))
|
||||
task_object.add_done_callback(
|
||||
lambda task: task_done_callback(task, name)
|
||||
)
|
||||
|
||||
# Store the task and its arguments in the '__tasks' dictionary. The
|
||||
# key is the name of the method, and the value is a dictionary
|
||||
# containing the task object and the updated keyword arguments.
|
||||
self.tasks[name] = {
|
||||
"task": self._loop.create_task(task(*args, **kwargs)),
|
||||
"task": task_object,
|
||||
"kwargs": kwargs_updated,
|
||||
}
|
||||
|
||||
@@ -142,14 +171,10 @@ class TaskManager:
|
||||
|
||||
def stop_task() -> None:
|
||||
# cancel the task
|
||||
task = self.tasks.pop(name, None)
|
||||
task = self.tasks.get(name, None)
|
||||
if task is not None:
|
||||
self._loop.call_soon_threadsafe(task["task"].cancel)
|
||||
|
||||
# emit the notification that the task was stopped
|
||||
for callback in self.task_status_change_callbacks:
|
||||
callback(name, None)
|
||||
|
||||
# create start and stop methods for each coroutine
|
||||
setattr(self.service, f"start_{name}", start_task)
|
||||
setattr(self.service, f"stop_{name}", stop_task)
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"files": {
|
||||
"main.css": "/static/css/main.398bc7f8.css",
|
||||
"main.js": "/static/js/main.b69b2bbf.js",
|
||||
"main.js": "/static/js/main.c348625e.js",
|
||||
"index.html": "/index.html",
|
||||
"main.398bc7f8.css.map": "/static/css/main.398bc7f8.css.map",
|
||||
"main.b69b2bbf.js.map": "/static/js/main.b69b2bbf.js.map"
|
||||
"main.c348625e.js.map": "/static/js/main.c348625e.js.map"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/css/main.398bc7f8.css",
|
||||
"static/js/main.b69b2bbf.js"
|
||||
"static/js/main.c348625e.js"
|
||||
]
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site displaying a pydase UI."/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>pydase App</title><script defer="defer" src="/static/js/main.b69b2bbf.js"></script><link href="/static/css/main.398bc7f8.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site displaying a pydase UI."/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>pydase App</title><script defer="defer" src="/static/js/main.c348625e.js"></script><link href="/static/css/main.398bc7f8.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
||||
1
src/pydase/frontend/static/js/main.c348625e.js.map
Normal file
@@ -8,7 +8,6 @@ from fastapi.staticfiles import StaticFiles
|
||||
from loguru import logger
|
||||
|
||||
from pydase import DataService
|
||||
from pydase.config import OperationMode
|
||||
from pydase.version import __version__
|
||||
|
||||
|
||||
@@ -115,14 +114,13 @@ class WebAPI:
|
||||
def service_properties() -> dict[str, Any]:
|
||||
return self.service.serialize()
|
||||
|
||||
if OperationMode().environment == "production":
|
||||
app.mount(
|
||||
"/",
|
||||
StaticFiles(
|
||||
directory=Path(__file__).parent.parent / "frontend",
|
||||
html=True,
|
||||
),
|
||||
)
|
||||
app.mount(
|
||||
"/",
|
||||
StaticFiles(
|
||||
directory=Path(__file__).parent.parent / "frontend",
|
||||
html=True,
|
||||
),
|
||||
)
|
||||
|
||||
self.__fastapi_app = app
|
||||
|
||||
|
||||
@@ -197,7 +197,7 @@ def extract_dict_or_list_entry(data: dict[str, Any], key: str) -> dict[str, Any]
|
||||
# When the attribute is a class instance, the attributes are nested in the
|
||||
# "value" key
|
||||
if current_data["type"] not in STANDARD_TYPES:
|
||||
current_data = cast(dict[str, Any], current_data.get("value", None))
|
||||
current_data = cast(dict[str, Any], current_data.get("value", None)) # type: ignore
|
||||
assert isinstance(current_data, dict)
|
||||
|
||||
return current_data
|
||||
@@ -394,3 +394,7 @@ def get_component_class_names() -> list[str]:
|
||||
import pydase.components
|
||||
|
||||
return pydase.components.__all__
|
||||
|
||||
|
||||
def is_property_attribute(target_obj: Any, attr_name: str) -> bool:
|
||||
return isinstance(getattr(type(target_obj), attr_name, None), property)
|
||||
|
||||
82
src/pydase/utils/logging.py
Normal file
@@ -0,0 +1,82 @@
|
||||
import logging
|
||||
import sys
|
||||
from types import FrameType
|
||||
from typing import Optional
|
||||
|
||||
import loguru
|
||||
import rpyc
|
||||
from uvicorn.config import LOGGING_CONFIG
|
||||
|
||||
import pydase.config
|
||||
|
||||
ALLOWED_LOG_LEVELS = ["DEBUG", "INFO", "ERROR"]
|
||||
|
||||
|
||||
# from: https://github.com/Delgan/loguru section
|
||||
# "Entirely compatible with standard logging"
|
||||
class InterceptHandler(logging.Handler):
|
||||
def emit(self, record: logging.LogRecord) -> None:
|
||||
# Ignore "asyncio.CancelledError" raised by uvicorn
|
||||
if record.name == "uvicorn.error" and "CancelledError" in record.msg:
|
||||
return
|
||||
|
||||
# Get corresponding Loguru level if it exists.
|
||||
level: int | str
|
||||
try:
|
||||
level = loguru.logger.level(record.levelname).name
|
||||
except ValueError:
|
||||
level = record.levelno
|
||||
|
||||
# Find caller from where originated the logged message.
|
||||
frame: Optional[FrameType] = sys._getframe(6)
|
||||
depth = 6
|
||||
while frame and frame.f_code.co_filename == logging.__file__:
|
||||
frame = frame.f_back
|
||||
depth += 1
|
||||
|
||||
try:
|
||||
msg = record.getMessage()
|
||||
except TypeError:
|
||||
# A `TypeError` is raised when the `msg` string expects more arguments
|
||||
# than are provided by `args`. This can happen when intercepting log
|
||||
# messages with a certain format, like
|
||||
# > logger.debug("call: %s%r", method_name, *args) # in tiqi_rpc
|
||||
# where `*args` unpacks a sequence of values that should replace
|
||||
# placeholders in the string.
|
||||
msg = record.msg % (record.args[0], record.args[2:]) # type: ignore
|
||||
|
||||
loguru.logger.opt(depth=depth, exception=record.exc_info).log(level, msg)
|
||||
|
||||
|
||||
def setup_logging(level: Optional[str] = None) -> None:
|
||||
loguru.logger.debug("Configuring service logging.")
|
||||
|
||||
if pydase.config.OperationMode().environment == "development":
|
||||
log_level = "DEBUG"
|
||||
else:
|
||||
log_level = "INFO"
|
||||
|
||||
if level is not None and level in ALLOWED_LOG_LEVELS:
|
||||
log_level = level
|
||||
|
||||
loguru.logger.remove()
|
||||
loguru.logger.add(sys.stderr, level=log_level)
|
||||
|
||||
# set up the rpyc logger *before* adding the InterceptHandler to the logging module
|
||||
rpyc.setup_logger(quiet=True) # type: ignore
|
||||
|
||||
logging.basicConfig(handlers=[InterceptHandler()], level=0)
|
||||
logging.getLogger("asyncio").setLevel(logging.INFO)
|
||||
logging.getLogger("urllib3").setLevel(logging.INFO)
|
||||
|
||||
# overwriting the uvicorn logging config to use the loguru intercept handler
|
||||
LOGGING_CONFIG["handlers"] = {
|
||||
"default": {
|
||||
"()": InterceptHandler,
|
||||
"formatter": "default",
|
||||
},
|
||||
"access": {
|
||||
"()": InterceptHandler,
|
||||
"formatter": "access",
|
||||
},
|
||||
}
|
||||
@@ -1,2 +1,4 @@
|
||||
__version__ = "0.1.0"
|
||||
from importlib.metadata import distribution
|
||||
|
||||
__version__ = distribution("pydase").version
|
||||
__major__, __minor__, __patch__ = [int(v) for v in __version__.split(".")]
|
||||
|
||||
0
tests/utils/__init__.py
Normal file
93
tests/utils/test_helpers.py
Normal file
@@ -0,0 +1,93 @@
|
||||
import pytest
|
||||
|
||||
from pydase.utils.helpers import (
|
||||
extract_dict_or_list_entry,
|
||||
get_nested_value_from_DataService_by_path_and_key,
|
||||
is_property_attribute,
|
||||
)
|
||||
|
||||
# Sample data for the tests
|
||||
data_sample = {
|
||||
"attr1": {"type": "bool", "value": False, "readonly": False, "doc": None},
|
||||
"class_attr": {
|
||||
"type": "MyClass",
|
||||
"value": {"sub_attr": {"type": "float", "value": 20.5}},
|
||||
},
|
||||
"list_attr": {
|
||||
"type": "list",
|
||||
"value": [
|
||||
{"type": "int", "value": 0, "readonly": False, "doc": None},
|
||||
{"type": "float", "value": 1.0, "readonly": False, "doc": None},
|
||||
],
|
||||
"readonly": False,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
# Tests for extract_dict_or_list_entry
|
||||
def test_extract_dict_with_valid_list_index() -> None:
|
||||
result = extract_dict_or_list_entry(data_sample, "list_attr[1]")
|
||||
assert result == {"type": "float", "value": 1.0, "readonly": False, "doc": None}
|
||||
|
||||
|
||||
def test_extract_dict_without_list_index() -> None:
|
||||
result = extract_dict_or_list_entry(data_sample, "attr1")
|
||||
assert result == {"type": "bool", "value": False, "readonly": False, "doc": None}
|
||||
|
||||
|
||||
def test_extract_dict_with_invalid_key() -> None:
|
||||
result = extract_dict_or_list_entry(data_sample, "attr_not_exist")
|
||||
assert result is None
|
||||
|
||||
|
||||
def test_extract_dict_with_invalid_list_index() -> None:
|
||||
result = extract_dict_or_list_entry(data_sample, "list_attr[5]")
|
||||
assert result is None
|
||||
|
||||
|
||||
# Tests for get_nested_value_from_DataService_by_path_and_key
|
||||
def test_get_nested_value_with_default_key() -> None:
|
||||
result = get_nested_value_from_DataService_by_path_and_key(
|
||||
data_sample, "list_attr[0]"
|
||||
)
|
||||
assert result == 0
|
||||
|
||||
|
||||
def test_get_nested_value_with_custom_key() -> None:
|
||||
result = get_nested_value_from_DataService_by_path_and_key(
|
||||
data_sample, "class_attr.sub_attr", "type"
|
||||
)
|
||||
assert result == "float"
|
||||
|
||||
|
||||
def test_get_nested_value_with_invalid_path() -> None:
|
||||
result = get_nested_value_from_DataService_by_path_and_key(
|
||||
data_sample, "class_attr.nonexistent_attr"
|
||||
)
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"attr_name, expected",
|
||||
[
|
||||
("regular_attribute", False),
|
||||
("my_property", True),
|
||||
("my_method", False),
|
||||
("non_existent_attr", False),
|
||||
],
|
||||
)
|
||||
def test_is_property_attribute(attr_name: str, expected: bool) -> None:
|
||||
# Test Suite
|
||||
class DummyClass:
|
||||
def __init__(self) -> None:
|
||||
self.regular_attribute = "I'm just an attribute"
|
||||
|
||||
@property
|
||||
def my_property(self) -> str:
|
||||
return "I'm a property"
|
||||
|
||||
def my_method(self) -> str:
|
||||
return "I'm a method"
|
||||
|
||||
dummy = DummyClass()
|
||||
assert is_property_attribute(dummy, attr_name) == expected
|
||||