diff --git a/.devcontainer/DevContainer.Dockerfile b/.devcontainer/DevContainer.Dockerfile index e1b06a5..9208f90 100644 --- a/.devcontainer/DevContainer.Dockerfile +++ b/.devcontainer/DevContainer.Dockerfile @@ -3,4 +3,5 @@ FROM mcr.microsoft.com/devcontainers/base:bullseye # [Optional] Uncomment this section to install additional OS packages. RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ - && apt-get -y install --no-install-recommends vim + && apt-get -y install --no-install-recommends vim \ + && rm -rf /var/lib/apt/lists/* diff --git a/.devcontainer/devcontainer.compose.yaml b/.devcontainer/devcontainer.compose.yaml index d6a8427..900f852 100644 --- a/.devcontainer/devcontainer.compose.yaml +++ b/.devcontainer/devcontainer.compose.yaml @@ -1,3 +1,4 @@ +name: madsci_dev services: dev: build: @@ -14,6 +15,8 @@ services: entrypoint: /usr/local/share/docker-init.sh command: sleep infinity + network_mode: host + # Uncomment the next four lines if you will use a ptrace-based debuggers like C++, Go, and Rust. # cap_add: # - SYS_PTRACE diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 0f74ba3..a457f39 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -13,6 +13,9 @@ "customizations": { "vscode": { + "settings": { + "python.defaultInterpreterPath": "/workspaces/MADSci/.venv/bin/python" + }, "extensions": [ "ms-python.python", "donjayamanne.python-environment-manager", @@ -26,21 +29,54 @@ "redhat.vscode-yaml", "KevinRose.vsc-python-indent", "ms-python.vscode-pylance", - "ms-toolsai.jupyter" + "ms-toolsai.jupyter", + "github.vscode-github-actions" ] } }, "features": { - "ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}, - "ghcr.io/devcontainers/features/python:1": {}, - "ghcr.io/guiyomh/features/just:0": {}, - "ghcr.io/devcontainers-extra/features/act:1": {}, - "ghcr.io/devcontainers-extra/features/actionlint:1": {}, - "ghcr.io/devcontainers-extra/features/pdm:2": {}, - "ghcr.io/devcontainers-extra/features/vue-cli:2": {}, - "ghcr.io/devcontainers-extra/features/pre-commit:2": {}, - "ghcr.io/devcontainers-extra/features/ruff:1": {} + "ghcr.io/devcontainers/features/docker-outside-of-docker:1": { + "moby": true, + "installDockerBuildx": true, + "installDockerComposeSwitch": true, + "version": "latest", + "dockerDashComposeVersion": "v2.32.3" + }, + "ghcr.io/devcontainers/features/python:1": { + "installTools": true, + "installJupyterlab": true, + "version": "3.9" + }, + "ghcr.io/guiyomh/features/just:0": { + "version": "latest" + }, + "ghcr.io/devcontainers-extra/features/act:1": { + "version": "latest" + }, + "ghcr.io/devcontainers-extra/features/actionlint:1": { + "version": "latest" + }, + "ghcr.io/devcontainers-extra/features/pdm:2": { + "version": "latest" + }, + "ghcr.io/devcontainers-extra/features/vue-cli:2": { + "version": "latest" + }, + "ghcr.io/devcontainers-extra/features/pre-commit:2": { + "version": "latest" + }, + "ghcr.io/devcontainers-extra/features/ruff:1": { + "version": "latest" + }, + "ghcr.io/devcontainers-extra/features/mongosh-homebrew:1": { + "version": "latest" + }, + "ghcr.io/devcontainers/features/nix:1": { + "multiUser": true, + "version": "latest", + "packages": "jless" + } }, // Use 'forwardPorts' to make a list of ports inside the container available locally. // "forwardPorts": [8000], diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..bd19a5d --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,11 @@ +## PR Info + +_List any issues this PR closes or features it implements, and a concise description of the changes._ + +## Developer Checklists + +I have: + +- [ ] Run Pre-commit and Unit Tests, and ensured that they pass +- [ ] Created or updated documentation relevant to your change +- [ ] Created or updated unit tests relevant to your change diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 0000000..481743f --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,17 @@ +name: Pre-Commit Checks + +on: + push: + +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + name: Checkout code + - uses: actions/setup-python@v4 + name: Setup Python + with: + python-version: 3.9 + - uses: pre-commit/action@v3.0.0 + name: Run Pre-Commit Checks diff --git a/.gitignore b/.gitignore index 10ce98a..cd84d5d 100644 --- a/.gitignore +++ b/.gitignore @@ -301,6 +301,7 @@ test_experiment/ # Output data .wei/ +.madsci/ #node modules **/node_modules diff --git a/.justfile b/.justfile index 5c9aa17..6801229 100644 --- a/.justfile +++ b/.justfile @@ -6,11 +6,14 @@ default: init: hooks @which pdm || echo "pdm not found, you'll need to install it: https://github.com/pdm-project/pdm" @pdm install + @cd madsci/madsci_common && pdm install && cd - + @pdm update @#test -e .env || cp .env.example .env # Install the pre-commit hooks hooks: @pre-commit install + @pre-commit autoupdate # Run the pre-commit checks checks: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 056d5f3..cc1990a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: nbstripout - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.8.1 + rev: v0.9.1 hooks: # Run the linter. - id: ruff diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d0e6a14 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,42 @@ +FROM python:3.12 +LABEL org.opencontainers.image.source=https://github.com/AD-SDL/MADSci/ +LABEL org.opencontainers.image.description="The Modular Autonomous Discovery for Science (MADSci) toolkit's base docker image." +LABEL org.opencontainers.image.licenses=MIT + +RUN set -eux; \ + apt-get update; \ + apt-get install -y gosu; \ + rm -rf /var/lib/apt/lists/* + +# User configuration +ARG USER_ID=9999 +ARG GROUP_ID=9999 +ARG CONTAINER_USER=madsci + +RUN groupadd -g ${GROUP_ID} ${CONTAINER_USER} +RUN useradd --create-home -u ${USER_ID} --shell /bin/bash -g ${CONTAINER_USER} ${CONTAINER_USER} + +WORKDIR /home/${CONTAINER_USER} + +# Create working directories +RUN mkdir -p /home/${CONTAINER_USER}/.madsci +RUN mkdir -p /home/${CONTAINER_USER}/MADSci + +# install PDM +RUN pip install -U pdm +# disable update check +ENV PDM_CHECK_UPDATE=false +# copy files +COPY pyproject.toml pdm.lock README.md /home/${CONTAINER_USER}/MADSci/ +COPY src/ /home/${CONTAINER_USER}/MADSci/src + +WORKDIR /home/${CONTAINER_USER}/MADSci +RUN --mount=type=cache,target=/root/.cache \ + pdm install -g -p . --check && \ + pdm install -g -p src/madsci_common --check && \ + pdm sync -g -p . + +COPY madsci-entrypoint.sh /madsci-entrypoint.sh +RUN chmod +x /madsci-entrypoint.sh +ENTRYPOINT ["/madsci-entrypoint.sh"] +WORKDIR /home/${CONTAINER_USER} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..43cbac3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Argonne National Laboratory Rapid Prototyping Lab + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index c57b87b..d4f29b9 100644 --- a/README.md +++ b/README.md @@ -3,15 +3,3 @@ ## Overview MADSci is a modular, autonomous, and scalable framework for scientific discovery and experimentation. - -## Components - -- [Squid](./src/madsci.squid/README.md): The Workcell Management Engine. -- [Types](./src/madsci.types/README.md): The Type Definition Library for MADSci. -- [PyClient](./src/madsci.pyclient/README.md): The Python Client for MADSci. -- [Module](./src/madsci.module/README.md): The Module Library for integrating devices. -- [Server](./src/madsci.server/README.md): The REST API Server. -- [CLI](./src/madsci.cli/README.md): The Command Line Interface. -- [Dashboard](./src/madsci.dashboard/README.md): The web-based Dashboard and management interface. -- [Resources](./src/madsci.resources/README.md): The Resource Library for managing resources. -- [Events](./src/madsci.events/README.md): The Event Library for managing events. diff --git a/example_lab/compose.yaml b/example_lab/compose.yaml new file mode 100644 index 0000000..e19838b --- /dev/null +++ b/example_lab/compose.yaml @@ -0,0 +1,41 @@ +name: madsci_example_lab +services: + mongodb: + container_name: mongodb + image: mongodb/mongodb-community-server:latest + ports: + - 27017:27017 + event_manager: + container_name: event_manager + image: madsci:latest + build: + context: .. + dockerfile: Dockerfile + environment: + - USER_ID=1000 + - GROUP_ID=1000 + ports: + - 8001:8001 + volumes: + - ${LOCAL_WORKSPACE_FOLDER:-../}/example_lab:/home/madsci/example_lab/ + - ${LOCAL_WORKSPACE_FOLDER:-../}/.madsci:/home/madsci/.madsci/ + command: python -m madsci.event_manager.event_server + depends_on: + - mongodb + liquidhandler: + container_name: liquidhandler + image: madsci:latest + build: + context: .. + dockerfile: Dockerfile + environment: + - USER_ID=1000 + - GROUP_ID=1000 + volumes: + - ${LOCAL_WORKSPACE_FOLDER:-../}/example_lab:/home/madsci/example_lab/ + - ${LOCAL_WORKSPACE_FOLDER:-../}/.madsci:/home/madsci/.madsci/ + command: python example_lab/example_modules/liquidhandler.py --definition example_lab/example_modules/nodes/default_liquidhandler.node.yaml + ports: + - 2001:2001 + depends_on: + - event_manager diff --git a/example_lab/example_lab.lab.yaml b/example_lab/example_lab.lab.yaml new file mode 100644 index 0000000..016c981 --- /dev/null +++ b/example_lab/example_lab.lab.yaml @@ -0,0 +1,23 @@ +name: ExampleLab +lab_id: 01JFK5527MDKJ92RE7B8PX71ZM +description: An example of a lab powered by the MADSci framework +server_config: + host: 127.0.0.1 + port: 8000 +workcells: {} +commands: + start: docker compose up -d + logs: docker compose logs + stop: docker compose down + restart: docker compose restart +managers: + event_manager: + name: event_manager + manager_id: 01JHJVMM9KYMNHR8VQQGP4AYY3 + description: An event manager for this example MADSci Lab + manager_type: event_manager + manager_config: + host: 0.0.0.0 + port: 8001 + db_url: mongodb://mongodb:27017 + url: http://killingtime:8001/ diff --git a/example_lab/example_modules/liquidhandler.module.yaml b/example_lab/example_modules/liquidhandler.module.yaml new file mode 100644 index 0000000..ad12f4c --- /dev/null +++ b/example_lab/example_modules/liquidhandler.module.yaml @@ -0,0 +1,53 @@ +module_name: liquidhandler +module_type: device +module_description: null +capabilities: + get_info: false + get_state: false + get_status: false + send_action: false + get_action_result: false + get_action_history: false + action_files: false + send_admin_commands: false + set_config: false + get_resources: false + get_log: false + events: false + resources: false + admin_commands: [] +config: + rest_node: + namespace: rest_node + description: Configuration related to the REST API of a REST node. + parameters: + host: + name: host + description: The host of the REST API. + default: 0.0.0.0 + required: true + port: + name: port + description: The port of the REST API. + default: 2001 + required: true + protocol: + name: protocol + description: The protocol of the REST API, either 'http' or 'https'. + default: http + required: true + madsci_events: + namespace: madsci_events + description: Configuration related to MADSci event logging. + parameters: + event_server: + name: event_server + description: The URL of the event manager. + default: http://event_manager:8001 + required: false + log_level: + name: log_level + description: The default log level. + default: INFO + required: true +commands: {} diff --git a/tests/test_modules/liquidhandler.py b/example_lab/example_modules/liquidhandler.py similarity index 54% rename from tests/test_modules/liquidhandler.py rename to example_lab/example_modules/liquidhandler.py index d23c2d8..6fa08cc 100644 --- a/tests/test_modules/liquidhandler.py +++ b/example_lab/example_modules/liquidhandler.py @@ -1,37 +1,46 @@ """A fake liquid handler module for testing.""" -from typing import Any +from typing import Any, Optional +from madsci.client.event_client import EventClient from madsci.common.types.action_types import ActionResult, ActionSucceeded -from madsci.module.abstract_module import action -from madsci.module.rest_module import RestNode +from madsci.node_module.abstract_node_module import action +from madsci.node_module.rest_node_module import RestNode -class LiquidHandler: - """A fake liquid handler for testing.""" +class LiquidHandlerInterface: + """A fake liquid handler interface for testing.""" status_code: int = 0 + device_number: int = 0 - def __init__(self) -> "LiquidHandler": + def __init__( + self, device_number: int = 0, logger: Optional[EventClient] = None + ) -> "LiquidHandlerInterface": """Initialize the liquid handler.""" + self.logger = logger if logger else EventClient() + self.device_number = device_number def run_command(self, command: str) -> None: """Run a command on the liquid handler.""" - print(f"Running command: {command}") + self.logger.log( + f"Running command {command} on device number {self.device_number}." + ) -class LiquidHandlerModule(RestNode): - """A fake liquid handler module for testing.""" +class LiquidHandlerNode(RestNode): + """A fake liquid handler node module for testing.""" - liquid_handler: LiquidHandler = None + liquid_handler: LiquidHandlerInterface = None def startup_handler(self) -> None: """Called to (re)initialize the node. Should be used to open connections to devices or initialize any other resources.""" - self.liquid_handler = LiquidHandler() + self.liquid_handler = LiquidHandlerInterface(logger=self.logger) + self.logger.log("Liquid handler initialized!") def shutdown_handler(self) -> None: """Called to shutdown the node. Should be used to close connections to devices or release any other resources.""" - print("Shutting down") + self.logger.log("Shutting down") del self.liquid_handler def state_handler(self) -> dict[str, Any]: @@ -49,5 +58,5 @@ def run_command(self, command: str) -> ActionResult: if __name__ == "__main__": - liquid_handler_node = LiquidHandlerModule() + liquid_handler_node = LiquidHandlerNode() liquid_handler_node.start_node() diff --git a/example_lab/example_modules/nodes/default_liquidhandler.node.info.yaml b/example_lab/example_modules/nodes/default_liquidhandler.node.info.yaml new file mode 100644 index 0000000..a49977f --- /dev/null +++ b/example_lab/example_modules/nodes/default_liquidhandler.node.info.yaml @@ -0,0 +1,74 @@ +module_name: liquidhandler +module_type: device +module_description: null +capabilities: + get_info: false + get_state: false + get_status: false + send_action: false + get_action_result: false + get_action_history: false + action_files: false + send_admin_commands: false + set_config: false + get_resources: false + get_log: false + events: false + resources: false + admin_commands: + - reset + - shutdown +config: + rest_node: + namespace: rest_node + description: Configuration related to the REST API of a REST node. + parameters: + host: + name: host + description: The host of the REST API. + default: 0.0.0.0 + required: true + port: + name: port + description: The port of the REST API. + default: 2001 + required: true + protocol: + name: protocol + description: The protocol of the REST API, either 'http' or 'https'. + default: http + required: true + madsci_events: + namespace: madsci_events + description: Configuration related to MADSci event logging. + parameters: + event_server: + name: event_server + description: The URL of the event manager. + default: http://event_manager:8001 + required: false + log_level: + name: log_level + description: The default log level. + default: INFO + required: true +commands: {} +node_name: default_liquidhandler +node_id: 01JD7WCXX8Y1CMER8P3XHA6CTW +node_url: null +node_description: Default liquidhandler +module_definition: ../liquidhandler.module.yaml +actions: + run_command: + name: run_command + description: Run a command on the liquid handler. + args: + command: + name: command + description: '' + type: str + required: true + default: null + files: {} + results: {} + blocking: false diff --git a/example_lab/example_modules/nodes/default_liquidhandler.node.yaml b/example_lab/example_modules/nodes/default_liquidhandler.node.yaml new file mode 100644 index 0000000..d1ed263 --- /dev/null +++ b/example_lab/example_modules/nodes/default_liquidhandler.node.yaml @@ -0,0 +1,7 @@ +node_name: default_liquidhandler +node_id: 01JD7WCXX8Y1CMER8P3XHA6CTW +node_url: null +node_description: Default liquidhandler +module_definition: ../liquidhandler.module.yaml +config: {} +commands: {} diff --git a/madsci-entrypoint.sh b/madsci-entrypoint.sh new file mode 100644 index 0000000..edf9b53 --- /dev/null +++ b/madsci-entrypoint.sh @@ -0,0 +1,60 @@ +#!/bin/bash +#set -e +set -o pipefail + +DEFAULT_CONTAINER_USER=madsci + +# *Change the UID and GID of the CONTAINER_USER user if they are provided as environment variables +if [ -z "${USER_ID}" ]; then + USER_ID=9999 +fi +if [ -z "${GROUP_ID}" ]; then + GROUP_ID=9999 +fi +if [ -z ${CONTAINER_USER} ]; then + CONTAINER_USER=${DEFAULT_CONTAINER_USER} +fi +if [ "$USER_ID" -eq 0 ] && [ "$GROUP_ID" -eq 0 ]; then + echo "Running as root" +else + echo "Running as ${CONTAINER_USER} with UID ${USER_ID} and GID ${GROUP_ID}" +fi + +if [ "$USER_ID" -ne 0 ] && [ "$USER_ID" -ne 9999 ]; then + GROUP_LIST=$(groups ${DEFAULT_CONTAINER_USER}) + userdel ${DEFAULT_CONTAINER_USER} +elif [ "$GROUP_ID" -ne 0 ] && [ "$GROUP_ID" -ne 9999 ]; then + groupdel ${DEFAULT_CONTAINER_USER} +fi +if [ "$GROUP_ID" -ne 0 ] && [ "$GROUP_ID" -ne 9999 ] && ! getent group $GROUP_ID > /dev/null; then + groupadd -g $GROUP_ID ${CONTAINER_USER} +fi +if [ "$USER_ID" -ne 0 ] && [ "$USER_ID" -ne 9999 ]; then + useradd -u $USER_ID --shell /bin/bash -g ${GROUP_ID} ${CONTAINER_USER} + usermod -aG $(echo "$GROUP_LIST" | sed 's/.*: //; s/ /,/g') ${CONTAINER_USER} +fi + +# *Best-effort attempt to align permissions for the default data directory +mkdir -p /home/${CONTAINER_USER}/.madsci +chown $USER_ID:$GROUP_ID /home/${CONTAINER_USER}/.madsci || true +chown $USER_ID:$GROUP_ID /home/${CONTAINER_USER}/.madsci/logs || true + + + +# *Run the container command as the specified user +if [ "$USER_ID" -eq 0 ] && [ "$GROUP_ID" -eq 0 ]; then + # *If we are root, easiest thing to do is to symlink everything from /home/${CONAINER_USER} to /root + shopt -s dotglob + for item in /home/${CONTAINER_USER}/*; do + dest="/root/$(basename "$item")" + + if [ ! -e "$dest" ]; then + ln -s "$item" "$dest" + fi + done + shopt -u dotglob + exec "$@" +else + # *If we are not root, we need to drop privileges + exec gosu ${CONTAINER_USER} "$@" +fi diff --git a/madsci/madsci_common/madsci/common/definition_loaders.py b/madsci/madsci_common/madsci/common/definition_loaders.py deleted file mode 100644 index dbd7e08..0000000 --- a/madsci/madsci_common/madsci/common/definition_loaders.py +++ /dev/null @@ -1,140 +0,0 @@ -"""MADSci Configuration Loaders.""" - -import argparse -import json -from pathlib import Path -from typing import Any - -from dotenv import load_dotenv - -from madsci.common.types.base_types import BaseModel -from madsci.common.types.module_types import NodeModuleDefinition -from madsci.common.types.node_types import ( - NodeDefinition, - get_module_from_node_definition, -) -from madsci.common.types.squid_types import LabDefinition -from madsci.common.utils import search_for_file_pattern - - -def madsci_definition_loader( - model: type[BaseModel] = BaseModel, - definition_file_pattern: str = "*.yaml", - search_for_file: bool = True, -) -> BaseModel: - """MADSci Definition Loader. Supports loading from a definition file, environment variables, and command line arguments, in reverse order of priority (i.e. command line arguments override environment variables, which override definition file values).""" - - # Load environment variables from a .env file - load_dotenv() - - parser = argparse.ArgumentParser(description="MADSci Definition Loader") - parser.add_argument( - "--definition", - type=Path, - help="The path to the MADSci configuration file.", - ) - args, _ = parser.parse_known_args() - definition_file = args.definition - if not definition_file: - if not search_for_file: - raise ValueError( - "Definition file not specified, please specify a definition file using the --definition argument.", - ) - - # *Load from definition file - if search_for_file: - definition_files = search_for_file_pattern( - definition_file_pattern, - parents=True, - children=True, - ) - if not definition_files: - raise ValueError( - f"No definition files found matching pattern: {definition_file_pattern}. Please specify a valid configuration file path using the --definition argument.", - ) - - definition_file = definition_files[0] - - return model.from_yaml(definition_file) - - -def lab_definition_loader( - model: type[BaseModel] = LabDefinition, - definition_file_pattern: str = "*.lab.yaml", - **kwargs: Any, -) -> LabDefinition: - """Lab Definition Loader. Supports loading from a definition file, environment variables, and command line arguments, in reverse order of priority (i.e. command line arguments override environment variables, which override definition file values).""" - definition = madsci_definition_loader( - model=model, - definition_file_pattern=definition_file_pattern, - **kwargs, - ) - for field_name, field in definition.lab_config.model_fields.items(): - parser = argparse.ArgumentParser( - description=f"MADSci Lab Definition Loader for {field_name}", - ) - parser.add_argument( - f"--{field_name}", - type=str, - help=f"[{field.annotation}] {field.description}", - default=None, - ) - args, _ = parser.parse_known_args() - for field_name in definition.lab_config.model_fields: - if getattr(args, field_name) is not None: - setattr( - definition.lab_config, - field_name, - json.loads(getattr(args, field_name)), - ) - definition.model_validate(definition) - return definition - - -def node_definition_loader( - model: type[BaseModel] = NodeDefinition, - definition_file_pattern: str = "*.node.yaml", - **kwargs: Any, -) -> tuple[NodeDefinition, NodeModuleDefinition, dict[str, Any]]: - """Node Definition Loader. Supports loading from a definition file, environment variables, and command line arguments, in reverse order of priority (i.e. command line arguments override environment variables, which override definition file values).""" - - # * Load the node definition file - node_definition = madsci_definition_loader( - model=model, - definition_file_pattern=definition_file_pattern, - **kwargs, - ) - - module_definition = get_module_from_node_definition(node_definition) - - combined_config = node_definition.config.copy() - - # * Import any module config from the module definition - for config_name, config in module_definition.config.items(): - # * Only add the config if it isn't already defined in the node definition - if config_name not in node_definition.config: - combined_config[config_name] = config - - # * Load the node configuration from the command line - parser = argparse.ArgumentParser(description="MADSci Node Definition Loader") - for field_name, field in combined_config.items(): - parser.add_argument( - f"--{field_name}", - type=str, - help=field.description, - default=field.default, - required=False, - ) - args, _ = parser.parse_known_args() - config_values = {} - for arg_name, arg_value in vars(args).items(): - if arg_value is not None: - try: - config_values[arg_name] = json.loads(str(arg_value)) - except json.JSONDecodeError: - config_values[arg_name] = arg_value - else: - config_values[arg_name] = field.default - - # * Return the node and module definitions - return node_definition, module_definition, config_values diff --git a/madsci/madsci_common/madsci/common/events.py b/madsci/madsci_common/madsci/common/events.py deleted file mode 100644 index 6e8b131..0000000 --- a/madsci/madsci_common/madsci/common/events.py +++ /dev/null @@ -1,61 +0,0 @@ -"""MADSci Event Handling.""" - -import logging -from typing import Optional - -from madsci.common.types.event_types import Event - - -class MADSciEventLogger: - """A logger for MADSci events.""" - - def __init__( - self, - name: Optional[str] = None, - log_level: int = logging.INFO, - event_server: Optional[str] = None, - ) -> None: - """Initialize the event logger.""" - if name: - self.logger = logging.getLogger(__name__ + "." + name) - else: - self.logger = logging.getLogger(__name__) - self.logger.setLevel(log_level) - self.event_server = event_server - - def get_log(self) -> list[Event]: - """Read the log""" - # TODO: Read logs - - def log(self, event: Event, level: Optional[int] = None) -> None: - """Log an event.""" - event.log_level = level if level else event.log_level - logging.log(event.log_level, event.event_data) - if self.event_server: - self.send_event(event) - - def log_debug(self, event: Event) -> None: - """Log an event at the debug level.""" - self.log(event, logging.DEBUG) - - def log_info(self, event: Event) -> None: - """Log an event at the info level.""" - self.log(event, logging.INFO) - - def log_warning(self, event: Event) -> None: - """Log an event at the warning level.""" - self.log(event, logging.WARNING) - - def log_error(self, event: Event) -> None: - """Log an event at the error level.""" - self.log(event, logging.ERROR) - - def log_critical(self, event: Event) -> None: - """Log an event at the critical level.""" - self.log(event, logging.CRITICAL) - - def send_event(self, event: Event) -> None: - """Send an event to the event manager.""" - - -default_event_logger = MADSciEventLogger() diff --git a/madsci/madsci_common/madsci/common/types/module_types.py b/madsci/madsci_common/madsci/common/types/module_types.py deleted file mode 100644 index 6882d9f..0000000 --- a/madsci/madsci_common/madsci/common/types/module_types.py +++ /dev/null @@ -1,200 +0,0 @@ -"""Types related to MADSci Modules.""" - -from enum import Enum -from typing import Any, Optional, Union - -from pydantic.functional_validators import field_validator -from sqlmodel.main import Field - -from madsci.common.types.admin_command_types import AdminCommands -from madsci.common.types.base_types import BaseModel - - -class NodeType(str, Enum): - """The type of a MADSci node.""" - - DEVICE = "device" - COMPUTE = "compute" - RESOURCE_MANAGER = "resource_manager" - EVENT_MANAGER = "event_manager" - WORKCELL_MANAGER = "workcell_manager" - DATA_MANAGER = "data_manager" - TRANSFER_MANAGER = "transfer_manager" - - -class NodeModuleDefinition(BaseModel, extra="allow"): - """Definition for a MADSci Node Module.""" - - module_name: str = Field( - title="Node Module Name", - description="The name of the node module.", - ) - module_type: Optional[NodeType] = Field( - title="Module Type", - description="The type of the node module.", - default=None, - ) - module_description: Optional[str] = Field( - default=None, - title="Module Description", - description="A description of the node module.", - ) - capabilities: "NodeCapabilities" = Field( - default_factory=lambda: NodeCapabilities(), - title="Module Capabilities", - description="The capabilities of the node module.", - ) - config: Union[list["ConfigParameter"], dict[str, "ConfigParameter"]] = Field( - title="Module Configuration", - description="The configuration of the node module. These are 'default' configuration parameters inherited by all child nodes.", - default_factory=list, - ) - commands: dict[str, str] = Field( - title="Module Commands", - description="The commands that the node module supports. These are 'default' commands inherited by all child nodes.", - default_factory=dict, - ) - - @field_validator("config", mode="after") - @classmethod - def validate_config( - cls, - v: Union[list["ConfigParameter"], dict[str, "ConfigParameter"]], - ) -> Union[list["ConfigParameter"], dict[str, "ConfigParameter"]]: - """Validate the node module configuration, promoting a list of ConfigParameters to a dictionary for easier access.""" - if isinstance(v, dict): - return v - return {param.name: param for param in v} - - -class ConfigParameter(BaseModel, extra="allow"): - """A parameter for a MADSci Module/Node Configuration.""" - - name: str = Field( - title="Parameter Name", - description="The name of the parameter.", - ) - description: Optional[str] = Field( - title="Parameter Description", - description="A description of the parameter.", - default=None, - ) - default: Optional[Any] = Field( - title="Parameter Default", - description="The default value of the parameter.", - default=None, - ) - required: bool = Field( - title="Parameter Required", - description="Whether the parameter is required.", - default=False, - ) - reset_on_change: bool = Field( - title="Parameter Reset on Change", - description="Whether the node should restart whenever the parameter changes.", - default=True, - ) - - -NODE_MODULE_CONFIG_TEMPLATES: dict[str, list[ConfigParameter]] = { - "REST Module": [ - ConfigParameter( - name="host", - description="The host of the REST API.", - default="127.0.0.1", - required=True, - ), - ConfigParameter( - name="port", - description="The port of the REST API.", - default=8000, - required=True, - ), - ConfigParameter( - name="protocol", - description="The protocol of the REST API, either 'http' or 'https'.", - default="http", - required=True, - ), - ], -} - - -class NodeClientCapabilities(BaseModel): - """Capabilities of a MADSci Node Client.""" - - get_info: bool = Field( - default=False, - title="Module Info", - description="Whether the node supports querying its info.", - ) - get_state: bool = Field( - default=False, - title="Module State", - description="Whether the node supports querying its state.", - ) - get_status: bool = Field( - default=False, - title="Module Status", - description="Whether the node supports querying its status.", - ) - send_action: bool = Field( - default=False, - title="Module Send Action", - description="Whether the node supports sending actions.", - ) - get_action_result: bool = Field( - default=False, - title="Module Get Action", - description="Whether the node supports querying the status of an action.", - ) - get_action_history: bool = Field( - default=False, - title="Module Get Actions", - description="Whether the node supports querying the history of actions.", - ) - action_files: bool = Field( - default=False, - title="Module Action Files", - description="Whether the node supports sending action files.", - ) - send_admin_commands: bool = Field( - default=False, - title="Module Send Admin Commands", - description="Whether the node supports sending admin commands.", - ) - set_config: bool = Field( - default=False, - title="Module Set Config", - description="Whether the node supports setting configuration.", - ) - get_resources: bool = Field( - default=False, - title="Module Get Resources", - description="Whether the node supports querying its resources.", - ) - get_log: bool = Field( - default=False, - title="Module Get Log", - description="Whether the node supports querying its log.", - ) - - -class NodeCapabilities(NodeClientCapabilities): - """Capabilities of a MADSci Node.""" - - events: bool = Field( - default=False, - title="Module Events", - description="Whether the module supports raising MADSci events.", - ) - resources: bool = Field( - default=False, - title="Module Resources", - description="Whether the module supports MADSci-compatible resource management.", - ) - admin_commands: set[AdminCommands] = Field( - default=set(), - title="Module Admin Commands", - description="Which admin commands the module supports, if any.", - ) diff --git a/pdm.lock b/pdm.lock index ce8ea16..757705c 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "dev"] strategy = ["inherit_metadata"] lock_version = "4.5.0" -content_hash = "sha256:dd0d66d317eee266aeb8ee31d1841b1ecd2661adbb474adc9a5f3b304485d3cb" +content_hash = "sha256:60546b5607c7568a9430ad8e38e689f650b84933a5d407701ede4f6f4bd84332" [[metadata.targets]] requires_python = ">=3.9.1" @@ -36,13 +36,13 @@ files = [ [[package]] name = "certifi" -version = "2024.8.30" +version = "2024.12.14" requires_python = ">=3.6" summary = "Python package for providing Mozilla's CA Bundle." groups = ["dev"] files = [ - {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, - {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, + {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"}, + {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"}, ] [[package]] @@ -158,6 +158,17 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "dnspython" +version = "2.7.0" +requires_python = ">=3.9" +summary = "DNS toolkit" +groups = ["dev"] +files = [ + {file = "dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86"}, + {file = "dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1"}, +] + [[package]] name = "greenlet" version = "3.1.1" @@ -249,10 +260,10 @@ files = [ [[package]] name = "madsci-client" -version = "0.1.dev8" +version = "0.0.0" requires_python = ">=3.9.1" editable = true -path = "./madsci/madsci_client" +path = "./src/madsci_client" summary = "The Modular Autonomous Discovery for Science (MADSci) Python Client and CLI." groups = ["dev"] dependencies = [ @@ -263,16 +274,17 @@ dependencies = [ [[package]] name = "madsci-common" -version = "0.1.dev8" +version = "0.1.dev16" requires_python = ">=3.9.1" editable = true -path = "./madsci/madsci_common" +path = "./src/madsci_common" summary = "The Modular Autonomous Discovery for Science (MADSci) Common Definitions and Utilities." groups = ["dev"] dependencies = [ "PyYAML>=6.0.2", "aenum>=3.1.15", "pydantic>=2.9.2", + "pymongo>=4.10.1", "python-dotenv>=1.0.1", "python-ulid[pydantic]>=3.0.0", "requests>=2.32.3", @@ -281,22 +293,34 @@ dependencies = [ [[package]] name = "madsci-common" -version = "0.1.dev8" +version = "0.1.dev16" extras = ["server"] requires_python = ">=3.9.1" summary = "The Modular Autonomous Discovery for Science (MADSci) Common Definitions and Utilities." groups = ["dev"] dependencies = [ - "madsci-common==0.1.dev8", + "madsci-common==0.1.dev16", +] + +[[package]] +name = "madsci-event-manager" +version = "0.1.dev16" +requires_python = ">=3.9.1" +editable = true +path = "./src/madsci_event_manager" +summary = "The Modular Autonomous Discovery for Science (MADSci) Event and Logging Manager." +groups = ["dev"] +dependencies = [ + "madsci-common[server]", ] [[package]] -name = "madsci-module" -version = "0.1.dev8" +name = "madsci-node-module" +version = "0.1.dev16" requires_python = ">=3.9.1" editable = true -path = "./madsci/madsci_module" -summary = "The Modular Autonomous Discovery for Science (MADSci) Module Helper Classes and Interfaces." +path = "./src/madsci_node_module" +summary = "The Modular Autonomous Discovery for Science (MADSci) Node Module Helper Classes." groups = ["dev"] dependencies = [ "madsci-common", @@ -304,10 +328,10 @@ dependencies = [ [[package]] name = "madsci-resource-manager" -version = "0.1.dev8" +version = "0.1.dev16" requires_python = ">=3.9.1" editable = true -path = "./madsci/madsci_resource_manager" +path = "./src/madsci_resource_manager" summary = "The Modular Autonomous Discovery for Science (MADSci) Resource Manager." groups = ["dev"] dependencies = [ @@ -316,10 +340,10 @@ dependencies = [ [[package]] name = "madsci-squid" -version = "0.1.dev8" +version = "0.1.dev16" requires_python = ">=3.9.1" editable = true -path = "./madsci/madsci_squid" +path = "./src/madsci_squid" summary = "The Modular Autonomous Discovery for Science (MADSci) Control Server and Scheduler, aka Squid." groups = ["dev"] dependencies = [ @@ -395,23 +419,23 @@ files = [ [[package]] name = "pydantic" -version = "2.10.2" +version = "2.10.4" requires_python = ">=3.8" summary = "Data validation using Python type hints" groups = ["dev"] dependencies = [ "annotated-types>=0.6.0", - "pydantic-core==2.27.1", + "pydantic-core==2.27.2", "typing-extensions>=4.12.2", ] files = [ - {file = "pydantic-2.10.2-py3-none-any.whl", hash = "sha256:cfb96e45951117c3024e6b67b25cdc33a3cb7b2fa62e239f7af1378358a1d99e"}, - {file = "pydantic-2.10.2.tar.gz", hash = "sha256:2bc2d7f17232e0841cbba4641e65ba1eb6fafb3a08de3a091ff3ce14a197c4fa"}, + {file = "pydantic-2.10.4-py3-none-any.whl", hash = "sha256:597e135ea68be3a37552fb524bc7d0d66dcf93d395acd93a00682f1efcb8ee3d"}, + {file = "pydantic-2.10.4.tar.gz", hash = "sha256:82f12e9723da6de4fe2ba888b5971157b3be7ad914267dea8f05f82b28254f06"}, ] [[package]] name = "pydantic-core" -version = "2.27.1" +version = "2.27.2" requires_python = ">=3.8" summary = "Core functionality for Pydantic validation and serialization" groups = ["dev"] @@ -419,93 +443,93 @@ dependencies = [ "typing-extensions!=4.7.0,>=4.6.0", ] files = [ - {file = "pydantic_core-2.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:71a5e35c75c021aaf400ac048dacc855f000bdfed91614b4a726f7432f1f3d6a"}, - {file = "pydantic_core-2.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f82d068a2d6ecfc6e054726080af69a6764a10015467d7d7b9f66d6ed5afa23b"}, - {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:121ceb0e822f79163dd4699e4c54f5ad38b157084d97b34de8b232bcaad70278"}, - {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4603137322c18eaf2e06a4495f426aa8d8388940f3c457e7548145011bb68e05"}, - {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a33cd6ad9017bbeaa9ed78a2e0752c5e250eafb9534f308e7a5f7849b0b1bfb4"}, - {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15cc53a3179ba0fcefe1e3ae50beb2784dede4003ad2dfd24f81bba4b23a454f"}, - {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45d9c5eb9273aa50999ad6adc6be5e0ecea7e09dbd0d31bd0c65a55a2592ca08"}, - {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8bf7b66ce12a2ac52d16f776b31d16d91033150266eb796967a7e4621707e4f6"}, - {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:655d7dd86f26cb15ce8a431036f66ce0318648f8853d709b4167786ec2fa4807"}, - {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:5556470f1a2157031e676f776c2bc20acd34c1990ca5f7e56f1ebf938b9ab57c"}, - {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f69ed81ab24d5a3bd93861c8c4436f54afdf8e8cc421562b0c7504cf3be58206"}, - {file = "pydantic_core-2.27.1-cp310-none-win32.whl", hash = "sha256:f5a823165e6d04ccea61a9f0576f345f8ce40ed533013580e087bd4d7442b52c"}, - {file = "pydantic_core-2.27.1-cp310-none-win_amd64.whl", hash = "sha256:57866a76e0b3823e0b56692d1a0bf722bffb324839bb5b7226a7dbd6c9a40b17"}, - {file = "pydantic_core-2.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac3b20653bdbe160febbea8aa6c079d3df19310d50ac314911ed8cc4eb7f8cb8"}, - {file = "pydantic_core-2.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a5a8e19d7c707c4cadb8c18f5f60c843052ae83c20fa7d44f41594c644a1d330"}, - {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f7059ca8d64fea7f238994c97d91f75965216bcbe5f695bb44f354893f11d52"}, - {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bed0f8a0eeea9fb72937ba118f9db0cb7e90773462af7962d382445f3005e5a4"}, - {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3cb37038123447cf0f3ea4c74751f6a9d7afef0eb71aa07bf5f652b5e6a132c"}, - {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84286494f6c5d05243456e04223d5a9417d7f443c3b76065e75001beb26f88de"}, - {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acc07b2cfc5b835444b44a9956846b578d27beeacd4b52e45489e93276241025"}, - {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4fefee876e07a6e9aad7a8c8c9f85b0cdbe7df52b8a9552307b09050f7512c7e"}, - {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:258c57abf1188926c774a4c94dd29237e77eda19462e5bb901d88adcab6af919"}, - {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:35c14ac45fcfdf7167ca76cc80b2001205a8d5d16d80524e13508371fb8cdd9c"}, - {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d1b26e1dff225c31897696cab7d4f0a315d4c0d9e8666dbffdb28216f3b17fdc"}, - {file = "pydantic_core-2.27.1-cp311-none-win32.whl", hash = "sha256:2cdf7d86886bc6982354862204ae3b2f7f96f21a3eb0ba5ca0ac42c7b38598b9"}, - {file = "pydantic_core-2.27.1-cp311-none-win_amd64.whl", hash = "sha256:3af385b0cee8df3746c3f406f38bcbfdc9041b5c2d5ce3e5fc6637256e60bbc5"}, - {file = "pydantic_core-2.27.1-cp311-none-win_arm64.whl", hash = "sha256:81f2ec23ddc1b476ff96563f2e8d723830b06dceae348ce02914a37cb4e74b89"}, - {file = "pydantic_core-2.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9cbd94fc661d2bab2bc702cddd2d3370bbdcc4cd0f8f57488a81bcce90c7a54f"}, - {file = "pydantic_core-2.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f8c4718cd44ec1580e180cb739713ecda2bdee1341084c1467802a417fe0f02"}, - {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15aae984e46de8d376df515f00450d1522077254ef6b7ce189b38ecee7c9677c"}, - {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ba5e3963344ff25fc8c40da90f44b0afca8cfd89d12964feb79ac1411a260ac"}, - {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:992cea5f4f3b29d6b4f7f1726ed8ee46c8331c6b4eed6db5b40134c6fe1768bb"}, - {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0325336f348dbee6550d129b1627cb8f5351a9dc91aad141ffb96d4937bd9529"}, - {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7597c07fbd11515f654d6ece3d0e4e5093edc30a436c63142d9a4b8e22f19c35"}, - {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3bbd5d8cc692616d5ef6fbbbd50dbec142c7e6ad9beb66b78a96e9c16729b089"}, - {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:dc61505e73298a84a2f317255fcc72b710b72980f3a1f670447a21efc88f8381"}, - {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:e1f735dc43da318cad19b4173dd1ffce1d84aafd6c9b782b3abc04a0d5a6f5bb"}, - {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f4e5658dbffe8843a0f12366a4c2d1c316dbe09bb4dfbdc9d2d9cd6031de8aae"}, - {file = "pydantic_core-2.27.1-cp312-none-win32.whl", hash = "sha256:672ebbe820bb37988c4d136eca2652ee114992d5d41c7e4858cdd90ea94ffe5c"}, - {file = "pydantic_core-2.27.1-cp312-none-win_amd64.whl", hash = "sha256:66ff044fd0bb1768688aecbe28b6190f6e799349221fb0de0e6f4048eca14c16"}, - {file = "pydantic_core-2.27.1-cp312-none-win_arm64.whl", hash = "sha256:9a3b0793b1bbfd4146304e23d90045f2a9b5fd5823aa682665fbdaf2a6c28f3e"}, - {file = "pydantic_core-2.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f216dbce0e60e4d03e0c4353c7023b202d95cbaeff12e5fd2e82ea0a66905073"}, - {file = "pydantic_core-2.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a2e02889071850bbfd36b56fd6bc98945e23670773bc7a76657e90e6b6603c08"}, - {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42b0e23f119b2b456d07ca91b307ae167cc3f6c846a7b169fca5326e32fdc6cf"}, - {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:764be71193f87d460a03f1f7385a82e226639732214b402f9aa61f0d025f0737"}, - {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c00666a3bd2f84920a4e94434f5974d7bbc57e461318d6bb34ce9cdbbc1f6b2"}, - {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ccaa88b24eebc0f849ce0a4d09e8a408ec5a94afff395eb69baf868f5183107"}, - {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c65af9088ac534313e1963443d0ec360bb2b9cba6c2909478d22c2e363d98a51"}, - {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:206b5cf6f0c513baffaeae7bd817717140770c74528f3e4c3e1cec7871ddd61a"}, - {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:062f60e512fc7fff8b8a9d680ff0ddaaef0193dba9fa83e679c0c5f5fbd018bc"}, - {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:a0697803ed7d4af5e4c1adf1670af078f8fcab7a86350e969f454daf598c4960"}, - {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:58ca98a950171f3151c603aeea9303ef6c235f692fe555e883591103da709b23"}, - {file = "pydantic_core-2.27.1-cp313-none-win32.whl", hash = "sha256:8065914ff79f7eab1599bd80406681f0ad08f8e47c880f17b416c9f8f7a26d05"}, - {file = "pydantic_core-2.27.1-cp313-none-win_amd64.whl", hash = "sha256:ba630d5e3db74c79300d9a5bdaaf6200172b107f263c98a0539eeecb857b2337"}, - {file = "pydantic_core-2.27.1-cp313-none-win_arm64.whl", hash = "sha256:45cf8588c066860b623cd11c4ba687f8d7175d5f7ef65f7129df8a394c502de5"}, - {file = "pydantic_core-2.27.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:e9386266798d64eeb19dd3677051f5705bf873e98e15897ddb7d76f477131967"}, - {file = "pydantic_core-2.27.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4228b5b646caa73f119b1ae756216b59cc6e2267201c27d3912b592c5e323b60"}, - {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b3dfe500de26c52abe0477dde16192ac39c98f05bf2d80e76102d394bd13854"}, - {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aee66be87825cdf72ac64cb03ad4c15ffef4143dbf5c113f64a5ff4f81477bf9"}, - {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b748c44bb9f53031c8cbc99a8a061bc181c1000c60a30f55393b6e9c45cc5bd"}, - {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ca038c7f6a0afd0b2448941b6ef9d5e1949e999f9e5517692eb6da58e9d44be"}, - {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e0bd57539da59a3e4671b90a502da9a28c72322a4f17866ba3ac63a82c4498e"}, - {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ac6c2c45c847bbf8f91930d88716a0fb924b51e0c6dad329b793d670ec5db792"}, - {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b94d4ba43739bbe8b0ce4262bcc3b7b9f31459ad120fb595627eaeb7f9b9ca01"}, - {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:00e6424f4b26fe82d44577b4c842d7df97c20be6439e8e685d0d715feceb9fb9"}, - {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:38de0a70160dd97540335b7ad3a74571b24f1dc3ed33f815f0880682e6880131"}, - {file = "pydantic_core-2.27.1-cp39-none-win32.whl", hash = "sha256:7ccebf51efc61634f6c2344da73e366c75e735960b5654b63d7e6f69a5885fa3"}, - {file = "pydantic_core-2.27.1-cp39-none-win_amd64.whl", hash = "sha256:a57847b090d7892f123726202b7daa20df6694cbd583b67a592e856bff603d6c"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3fa80ac2bd5856580e242dbc202db873c60a01b20309c8319b5c5986fbe53ce6"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d950caa237bb1954f1b8c9227b5065ba6875ac9771bb8ec790d956a699b78676"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e4216e64d203e39c62df627aa882f02a2438d18a5f21d7f721621f7a5d3611d"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02a3d637bd387c41d46b002f0e49c52642281edacd2740e5a42f7017feea3f2c"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:161c27ccce13b6b0c8689418da3885d3220ed2eae2ea5e9b2f7f3d48f1d52c27"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:19910754e4cc9c63bc1c7f6d73aa1cfee82f42007e407c0f413695c2f7ed777f"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:e173486019cc283dc9778315fa29a363579372fe67045e971e89b6365cc035ed"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:af52d26579b308921b73b956153066481f064875140ccd1dfd4e77db89dbb12f"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:981fb88516bd1ae8b0cbbd2034678a39dedc98752f264ac9bc5839d3923fa04c"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5fde892e6c697ce3e30c61b239330fc5d569a71fefd4eb6512fc6caec9dd9e2f"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:816f5aa087094099fff7edabb5e01cc370eb21aa1a1d44fe2d2aefdfb5599b31"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c10c309e18e443ddb108f0ef64e8729363adbfd92d6d57beec680f6261556f3"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98476c98b02c8e9b2eec76ac4156fd006628b1b2d0ef27e548ffa978393fd154"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c3027001c28434e7ca5a6e1e527487051136aa81803ac812be51802150d880dd"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:7699b1df36a48169cdebda7ab5a2bac265204003f153b4bd17276153d997670a"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1c39b07d90be6b48968ddc8c19e7585052088fd7ec8d568bb31ff64c70ae3c97"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:46ccfe3032b3915586e469d4972973f893c0a2bb65669194a5bdea9bacc088c2"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:62ba45e21cf6571d7f716d903b5b7b6d2617e2d5d67c0923dc47b9d41369f840"}, - {file = "pydantic_core-2.27.1.tar.gz", hash = "sha256:62a763352879b84aa31058fc931884055fd75089cccbd9d58bb6afd01141b235"}, + {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"}, + {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236"}, + {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962"}, + {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9"}, + {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af"}, + {file = "pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4"}, + {file = "pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31"}, + {file = "pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc"}, + {file = "pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d"}, + {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b"}, + {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474"}, + {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6"}, + {file = "pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c"}, + {file = "pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc"}, + {file = "pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4"}, + {file = "pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0"}, + {file = "pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4"}, + {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3"}, + {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4"}, + {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57"}, + {file = "pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc"}, + {file = "pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9"}, + {file = "pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b"}, + {file = "pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b"}, + {file = "pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4"}, + {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27"}, + {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee"}, + {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1"}, + {file = "pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130"}, + {file = "pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee"}, + {file = "pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b"}, + {file = "pydantic_core-2.27.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993"}, + {file = "pydantic_core-2.27.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54"}, + {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f"}, + {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362"}, + {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96"}, + {file = "pydantic_core-2.27.2-cp39-cp39-win32.whl", hash = "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e"}, + {file = "pydantic_core-2.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35"}, + {file = "pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39"}, ] [[package]] @@ -519,6 +543,66 @@ files = [ {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, ] +[[package]] +name = "pymongo" +version = "4.10.1" +requires_python = ">=3.8" +summary = "Python driver for MongoDB " +groups = ["dev"] +dependencies = [ + "dnspython<3.0.0,>=1.16.0", +] +files = [ + {file = "pymongo-4.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e699aa68c4a7dea2ab5a27067f7d3e08555f8d2c0dc6a0c8c60cfd9ff2e6a4b1"}, + {file = "pymongo-4.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:70645abc714f06b4ad6b72d5bf73792eaad14e3a2cfe29c62a9c81ada69d9e4b"}, + {file = "pymongo-4.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae2fd94c9fe048c94838badcc6e992d033cb9473eb31e5710b3707cba5e8aee2"}, + {file = "pymongo-4.10.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ded27a4a5374dae03a92e084a60cdbcecd595306555bda553b833baf3fc4868"}, + {file = "pymongo-4.10.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ecc2455e3974a6c429687b395a0bc59636f2d6aedf5785098cf4e1f180f1c71"}, + {file = "pymongo-4.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920fee41f7d0259f5f72c1f1eb331bc26ffbdc952846f9bd8c3b119013bb52c"}, + {file = "pymongo-4.10.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0a15665b2d6cf364f4cd114d62452ce01d71abfbd9c564ba8c74dcd7bbd6822"}, + {file = "pymongo-4.10.1-cp310-cp310-win32.whl", hash = "sha256:29e1c323c28a4584b7095378ff046815e39ff82cdb8dc4cc6dfe3acf6f9ad1f8"}, + {file = "pymongo-4.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:88dc4aa45f8744ccfb45164aedb9a4179c93567bbd98a33109d7dc400b00eb08"}, + {file = "pymongo-4.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:57ee6becae534e6d47848c97f6a6dff69e3cce7c70648d6049bd586764febe59"}, + {file = "pymongo-4.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6f437a612f4d4f7aca1812311b1e84477145e950fdafe3285b687ab8c52541f3"}, + {file = "pymongo-4.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a970fd3117ab40a4001c3dad333bbf3c43687d90f35287a6237149b5ccae61d"}, + {file = "pymongo-4.10.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c4d0e7cd08ef9f8fbf2d15ba281ed55604368a32752e476250724c3ce36c72e"}, + {file = "pymongo-4.10.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca6f700cff6833de4872a4e738f43123db34400173558b558ae079b5535857a4"}, + {file = "pymongo-4.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cec237c305fcbeef75c0bcbe9d223d1e22a6e3ba1b53b2f0b79d3d29c742b45b"}, + {file = "pymongo-4.10.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3337804ea0394a06e916add4e5fac1c89902f1b6f33936074a12505cab4ff05"}, + {file = "pymongo-4.10.1-cp311-cp311-win32.whl", hash = "sha256:778ac646ce6ac1e469664062dfe9ae1f5c9961f7790682809f5ec3b8fda29d65"}, + {file = "pymongo-4.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:9df4ab5594fdd208dcba81be815fa8a8a5d8dedaf3b346cbf8b61c7296246a7a"}, + {file = "pymongo-4.10.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fbedc4617faa0edf423621bb0b3b8707836687161210d470e69a4184be9ca011"}, + {file = "pymongo-4.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7bd26b2aec8ceeb95a5d948d5cc0f62b0eb6d66f3f4230705c1e3d3d2c04ec76"}, + {file = "pymongo-4.10.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb104c3c2a78d9d85571c8ac90ec4f95bca9b297c6eee5ada71fabf1129e1674"}, + {file = "pymongo-4.10.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4924355245a9c79f77b5cda2db36e0f75ece5faf9f84d16014c0a297f6d66786"}, + {file = "pymongo-4.10.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:11280809e5dacaef4971113f0b4ff4696ee94cfdb720019ff4fa4f9635138252"}, + {file = "pymongo-4.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5d55f2a82e5eb23795f724991cac2bffbb1c0f219c0ba3bf73a835f97f1bb2e"}, + {file = "pymongo-4.10.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e974ab16a60be71a8dfad4e5afccf8dd05d41c758060f5d5bda9a758605d9a5d"}, + {file = "pymongo-4.10.1-cp312-cp312-win32.whl", hash = "sha256:544890085d9641f271d4f7a47684450ed4a7344d6b72d5968bfae32203b1bb7c"}, + {file = "pymongo-4.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:dcc07b1277e8b4bf4d7382ca133850e323b7ab048b8353af496d050671c7ac52"}, + {file = "pymongo-4.10.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:90bc6912948dfc8c363f4ead54d54a02a15a7fee6cfafb36dc450fc8962d2cb7"}, + {file = "pymongo-4.10.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:594dd721b81f301f33e843453638e02d92f63c198358e5a0fa8b8d0b1218dabc"}, + {file = "pymongo-4.10.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0783e0c8e95397c84e9cf8ab092ab1e5dd7c769aec0ef3a5838ae7173b98dea0"}, + {file = "pymongo-4.10.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6fb6a72e88df46d1c1040fd32cd2d2c5e58722e5d3e31060a0393f04ad3283de"}, + {file = "pymongo-4.10.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e3a593333e20c87415420a4fb76c00b7aae49b6361d2e2205b6fece0563bf40"}, + {file = "pymongo-4.10.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72e2ace7456167c71cfeca7dcb47bd5dceda7db2231265b80fc625c5e8073186"}, + {file = "pymongo-4.10.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ad05eb9c97e4f589ed9e74a00fcaac0d443ccd14f38d1258eb4c39a35dd722b"}, + {file = "pymongo-4.10.1-cp313-cp313-win32.whl", hash = "sha256:ee4c86d8e6872a61f7888fc96577b0ea165eb3bdb0d841962b444fa36001e2bb"}, + {file = "pymongo-4.10.1-cp313-cp313-win_amd64.whl", hash = "sha256:45ee87a4e12337353242bc758accc7fb47a2f2d9ecc0382a61e64c8f01e86708"}, + {file = "pymongo-4.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15b1492cc5c7cd260229590be7218261e81684b8da6d6de2660cf743445500ce"}, + {file = "pymongo-4.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95207503c41b97e7ecc7e596d84a61f441b4935f11aa8332828a754e7ada8c82"}, + {file = "pymongo-4.10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb99f003c720c6d83be02c8f1a7787c22384a8ca9a4181e406174db47a048619"}, + {file = "pymongo-4.10.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f2bc1ee4b1ca2c4e7e6b7a5e892126335ec8d9215bcd3ac2fe075870fefc3358"}, + {file = "pymongo-4.10.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:93a0833c10a967effcd823b4e7445ec491f0bf6da5de0ca33629c0528f42b748"}, + {file = "pymongo-4.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f56707497323150bd2ed5d63067f4ffce940d0549d4ea2dfae180deec7f9363"}, + {file = "pymongo-4.10.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:409ab7d6c4223e5c85881697f365239dd3ed1b58f28e4124b846d9d488c86880"}, + {file = "pymongo-4.10.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dac78a650dc0637d610905fd06b5fa6419ae9028cf4d04d6a2657bc18a66bbce"}, + {file = "pymongo-4.10.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1ec3fa88b541e0481aff3c35194c9fac96e4d57ec5d1c122376000eb28c01431"}, + {file = "pymongo-4.10.1-cp39-cp39-win32.whl", hash = "sha256:e0e961923a7b8a1c801c43552dcb8153e45afa41749d9efbd3a6d33f45489f7a"}, + {file = "pymongo-4.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:dabe8bf1ad644e6b93f3acf90ff18536d94538ca4d27e583c6db49889e98e48f"}, + {file = "pymongo-4.10.1.tar.gz", hash = "sha256:a9de02be53b6bb98efe0b9eda84ffa1ec027fcb23a2de62c4f941d9a2f2f3330"}, +] + [[package]] name = "python-dotenv" version = "1.0.1" @@ -718,7 +802,7 @@ files = [ [[package]] name = "textual" -version = "0.87.1" +version = "1.0.0" requires_python = "<4.0.0,>=3.8.1" summary = "Modern Text User Interface framework" groups = ["dev"] @@ -729,8 +813,8 @@ dependencies = [ "typing-extensions<5.0.0,>=4.4.0", ] files = [ - {file = "textual-0.87.1-py3-none-any.whl", hash = "sha256:026d1368cd10610a72a9d3de7a56692a17e7e8dffa0468147eb8e186ba0ff0c0"}, - {file = "textual-0.87.1.tar.gz", hash = "sha256:daf4e248ba3d890831ff2617099535eb835863a2e3609c8ce00af0f6d55ed123"}, + {file = "textual-1.0.0-py3-none-any.whl", hash = "sha256:2d4a701781c05104925e463ae370c630567c70c2880e92ab838052e3e23c986f"}, + {file = "textual-1.0.0.tar.gz", hash = "sha256:bec9fe63547c1c552569d1b75d309038b7d456c03f86dfa3706ddb099b151399"}, ] [[package]] diff --git a/pyproject.toml b/pyproject.toml index c99d363..d518d04 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,11 +6,12 @@ dynamic = ["version"] [tool.pdm.dev-dependencies] dev = [ - "-e madsci.common @ file:///${PROJECT_ROOT}/madsci/madsci_common", - "-e madsci.squid @ file:///${PROJECT_ROOT}/madsci/madsci_squid", - "-e madsci.client @ file:///${PROJECT_ROOT}/madsci/madsci_client", - "-e madsci.module @ file:///${PROJECT_ROOT}/madsci/madsci_module", - "-e madsci.resource_manager @ file:///${PROJECT_ROOT}/madsci/madsci_resource_manager", + "-e madsci.common @ file:///${PROJECT_ROOT}/src/madsci_common", + "-e madsci.squid @ file:///${PROJECT_ROOT}/src/madsci_squid", + "-e madsci.client @ file:///${PROJECT_ROOT}/src/madsci_client", + "-e madsci.node_module @ file:///${PROJECT_ROOT}/src/madsci_node_module", + "-e madsci.resource_manager @ file:///${PROJECT_ROOT}/src/madsci_resource_manager", + "-e madsci.event_manager @ file:///${PROJECT_ROOT}/src/madsci_event_manager", ] [tool.pdm.version] source = "scm" diff --git a/ruff.toml b/ruff.toml index 53bf857..b33bc31 100644 --- a/ruff.toml +++ b/ruff.toml @@ -89,6 +89,8 @@ select = [ "FURB", # flake-8-unused-arguments "ARG", + # flake-8-print + "T20", ] ignore = [ "E501", # Line too long @@ -104,6 +106,9 @@ unfixable = [] # Allow unused variables when underscore-prefixed. dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" +[lint.per-file-ignores] +"**/tests/**/*.py" = ["S101"] + [format] # Like Black, use double quotes for strings. quote-style = "double" diff --git a/madsci/madsci_client/README.md b/src/madsci_client/README.md similarity index 100% rename from madsci/madsci_client/README.md rename to src/madsci_client/README.md diff --git a/madsci/madsci_client/madsci/client/__init__.py b/src/madsci_client/madsci/client/__init__.py similarity index 100% rename from madsci/madsci_client/madsci/client/__init__.py rename to src/madsci_client/madsci/client/__init__.py diff --git a/madsci/madsci_client/madsci/client/cli/__init__.py b/src/madsci_client/madsci/client/cli/__init__.py similarity index 92% rename from madsci/madsci_client/madsci/client/cli/__init__.py rename to src/madsci_client/madsci/client/cli/__init__.py index 22c2084..8191387 100644 --- a/madsci/madsci_client/madsci/client/cli/__init__.py +++ b/src/madsci_client/madsci/client/cli/__init__.py @@ -1,14 +1,14 @@ """Command Line Interface for the MADSci client.""" import click -from rich.console import Console -from trogon import tui - from madsci.client.cli.lab_cli import lab +from madsci.client.cli.manager_cli import manager from madsci.client.cli.module_cli import module from madsci.client.cli.node_cli import node from madsci.client.cli.resources_cli import resource from madsci.client.cli.workcell_cli import workcell +from rich.console import Console +from trogon import tui console = Console() @@ -36,6 +36,7 @@ def version() -> None: root_cli.add_command(module) root_cli.add_command(node) root_cli.add_command(resource) +root_cli.add_command(manager) if __name__ == "__main__": tui(root_cli, auto_envvar_prefix="MADSCI_CLI_") diff --git a/madsci/madsci_client/madsci/client/cli/lab_cli.py b/src/madsci_client/madsci/client/cli/lab_cli.py similarity index 98% rename from madsci/madsci_client/madsci/client/cli/lab_cli.py rename to src/madsci_client/madsci/client/cli/lab_cli.py index a9e8bf1..1b52d8a 100644 --- a/madsci/madsci_client/madsci/client/cli/lab_cli.py +++ b/src/madsci_client/madsci/client/cli/lab_cli.py @@ -1,15 +1,12 @@ """Command Line Interface for managing MADSci Squid labs.""" import os +import shlex from pathlib import Path from typing import Optional import click from click.core import Context -from rich import print -from rich.console import Console -from rich.pretty import pprint - from madsci.common.types.squid_types import LabDefinition from madsci.common.utils import ( prompt_for_input, @@ -18,6 +15,9 @@ search_for_file_pattern, to_snake_case, ) +from rich import print +from rich.console import Console +from rich.pretty import pprint console = Console() @@ -177,7 +177,8 @@ def run_command(command: str, lab: LabDefinition, path: Path) -> None: console.print( f"Running command: [bold]{command}[/] ({lab.commands[command]}) in lab: [bold]{lab.name}[/] ({path})", ) - print(os.popen(lab.commands[command]).read()) # noqa: S605 + args = shlex.split(lab.commands[command]) + os.execvp(args[0], args) # noqa: S606 @lab.command() diff --git a/src/madsci_client/madsci/client/cli/log_cli.py b/src/madsci_client/madsci/client/cli/log_cli.py new file mode 100644 index 0000000..c93ced3 --- /dev/null +++ b/src/madsci_client/madsci/client/cli/log_cli.py @@ -0,0 +1 @@ +"""CLI for accessing MADSci logs.""" diff --git a/src/madsci_client/madsci/client/cli/manager_cli.py b/src/madsci_client/madsci/client/cli/manager_cli.py new file mode 100644 index 0000000..a04c6cb --- /dev/null +++ b/src/madsci_client/madsci/client/cli/manager_cli.py @@ -0,0 +1,207 @@ +"""Command Line Interface for managing MADSci Squid managers.""" + +from pathlib import Path +from typing import Optional + +import click +from click.core import Context +from madsci.common.types.squid_types import ( + LabDefinition, + ManagerDefinition, + ManagerType, +) +from madsci.common.utils import ( + prompt_for_input, + prompt_yes_no, + save_model, + search_for_file_pattern, + to_snake_case, +) +from rich import print +from rich.console import Console +from rich.pretty import pprint + +console = Console() + + +class ManagerContext: + """Context object for manager commands.""" + + def __init__(self) -> None: + """Initialize the context object.""" + self.manager_def: Optional[ManagerDefinition] = None + self.lab_def: Optional[LabDefinition] = None + self.path: Optional[Path] = None + self.quiet: bool = False + + +pass_manager = click.make_pass_decorator(ManagerContext) + + +def find_manager(name: Optional[str], path: Optional[str]) -> ManagerContext: + """Find a manager by name or path.""" + manager_context = ManagerContext() + + if path: + manager_context.path = Path(path) + if manager_context.path.exists(): + manager_context.manager_def = ManagerDefinition.from_yaml(path) + return manager_context + + if name: + lab_files = search_for_file_pattern("*.lab.yaml") + for lab_file in lab_files: + lab_def = LabDefinition.from_yaml(lab_file) + for manager_name, manager in lab_def.managers.items(): + if manager_name == name: + manager_context.path = Path(lab_file) + manager_context.lab_def = lab_def + manager_context.manager_def = ( + manager + if isinstance(manager, ManagerDefinition) + else ManagerDefinition.from_yaml(manager) + ) + return manager_context + + # * Search for any manager file + manager_files = search_for_file_pattern("*.manager.yaml") + if manager_files: + manager_context.path = Path(manager_files[0]) + manager_context.manager_def = ManagerDefinition.from_yaml(manager_files[0]) + + return manager_context + + +@click.group() +@click.option("--name", "-n", type=str, help="The name of the manager to operate on.") +@click.option("--path", "-p", type=str, help="The path to the manager definition file.") +@click.pass_context +def manager(ctx: Context, name: Optional[str], path: Optional[str]) -> None: + """Manage managers.""" + ctx.obj = find_manager(name, path) + ctx.obj.quiet = ctx.parent.params.get("quiet") + + +@manager.command(name="add") +@click.option("--name", "-n", type=str, help="The name of the manager.", required=False) +@click.option("--path", "-p", type=str, help="The path to the manager definition file.") +@click.option("--description", "-d", type=str, help="The description of the manager.") +@click.option( + "--type", + "-t", + type=click.Choice([e.value for e in ManagerType]), + help="The type of the manager.", + required=True, +) +@click.pass_context +def add( + ctx: Context, + name: Optional[str], + path: Optional[str], + description: Optional[str], + type: str, +) -> None: + """Add a new manager.""" + if not name: + name = ctx.parent.params.get("name") + if not name: + name = prompt_for_input("Manager Name", required=True, quiet=ctx.obj.quiet) + if not description: + description = prompt_for_input("Manager Description", quiet=ctx.obj.quiet) + + manager_definition = ManagerDefinition( + name=name, description=description, manager_type=type + ) + console.print(manager_definition) + + if ctx.obj.lab_def: + ctx.obj.lab_def.managers[name] = manager_definition + save_model(ctx.obj.path, ctx.obj.lab_def, overwrite_check=not ctx.obj.quiet) + else: + if not path: + path = ctx.parent.params.get("path") + if not path: + default_path = Path.cwd() / f"{to_snake_case(name)}.manager.yaml" + new_path = prompt_for_input( + "Path to save Manager Definition file", + default=str(default_path), + quiet=ctx.obj.quiet, + ) + if new_path: + path = Path(new_path) + print("Path:", path) + save_model( + path=path, model=manager_definition, overwrite_check=not ctx.obj.quiet + ) + + +@manager.command() +def list() -> None: + """List all managers. Will list all managers in the current directory, subdirectories, and parent directories.""" + manager_files = search_for_file_pattern("*.manager.yaml") + + if manager_files: + for manager_file in sorted(set(manager_files)): + manager_definition = ManagerDefinition.from_yaml(manager_file) + console.print( + f"[bold]{manager_definition.name}[/]: {manager_definition.description} ({manager_file})", + ) + else: + lab_files = search_for_file_pattern("*.lab.yaml") + for lab_file in lab_files: + lab_def = LabDefinition.from_yaml(lab_file) + for _, manager in lab_def.managers.items(): + manager_definition = ( + manager + if isinstance(manager, ManagerDefinition) + else ManagerDefinition.from_yaml(manager) + ) + console.print( + f"[bold]{manager_definition.name}[/]: {manager_definition.description} (defined in {lab_file})", + ) + + +@manager.command() +@pass_manager +def info(ctx: ManagerContext) -> None: + """Get information about a manager.""" + if ctx.manager_def: + pprint(ctx.manager_def) + else: + console.print( + "No manager found. Specify manager by name or path. If you don't have a manager file, you can create one with 'madsci manager create'.", + ) + + +@manager.command() +@click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompt.") +@pass_manager +def delete(ctx: ManagerContext, yes: bool) -> None: + """Delete a manager.""" + if ctx.manager_def and ctx.path: + console.print(f"Deleting manager: {ctx.manager_def.name} ({ctx.path})") + if yes or ctx.quiet or prompt_yes_no("Are you sure?"): + if ctx.obj.lab_def: + del ctx.obj.lab_def.managers[ctx.manager_def.name] + save_model( + ctx.obj.path, ctx.obj.lab_def, overwrite_check=not ctx.obj.quiet + ) + else: + ctx.path.unlink() + console.print(f"Deleted {ctx.path}") + else: + console.print( + "No manager found. Specify manager by name or path. If you don't have a manager file, you can create one with 'madsci manager create'.", + ) + + +@manager.command() +@pass_manager +def validate(ctx: ManagerContext) -> None: + """Validate a manager definition file.""" + if ctx.manager_def: + console.print(ctx.manager_def) + else: + console.print( + "No manager found. Specify manager by name or path. If you don't have a manager definition file, you can create one with 'madsci manager create'.", + ) diff --git a/madsci/madsci_client/madsci/client/cli/module_cli.py b/src/madsci_client/madsci/client/cli/module_cli.py similarity index 99% rename from madsci/madsci_client/madsci/client/cli/module_cli.py rename to src/madsci_client/madsci/client/cli/module_cli.py index 295edce..fa94a93 100644 --- a/madsci/madsci_client/madsci/client/cli/module_cli.py +++ b/src/madsci_client/madsci/client/cli/module_cli.py @@ -5,10 +5,7 @@ import click from click.core import Context -from rich.console import Console -from rich.pretty import pprint - -from madsci.common.types.module_types import ( +from madsci.common.types.node_types import ( NODE_MODULE_CONFIG_TEMPLATES, NodeModuleDefinition, NodeType, @@ -21,6 +18,8 @@ search_for_file_pattern, to_snake_case, ) +from rich.console import Console +from rich.pretty import pprint console = Console() diff --git a/madsci/madsci_client/madsci/client/cli/node_cli.py b/src/madsci_client/madsci/client/cli/node_cli.py similarity index 98% rename from madsci/madsci_client/madsci/client/cli/node_cli.py rename to src/madsci_client/madsci/client/cli/node_cli.py index ac0aa08..53368e4 100644 --- a/madsci/madsci_client/madsci/client/cli/node_cli.py +++ b/src/madsci_client/madsci/client/cli/node_cli.py @@ -6,11 +6,7 @@ import click from click.core import Context -from rich.console import Console -from rich.pretty import pprint - -from madsci.common.types.module_types import NodeModuleDefinition -from madsci.common.types.node_types import NodeDefinition +from madsci.common.types.node_types import NodeDefinition, NodeModuleDefinition from madsci.common.types.workcell_types import WorkcellDefinition from madsci.common.utils import ( PathLike, @@ -22,6 +18,8 @@ search_for_file_pattern, to_snake_case, ) +from rich.console import Console +from rich.pretty import pprint console = Console() @@ -396,7 +394,7 @@ def run(ctx: NodeContext, command_name: str) -> None: console.print( f"Running command: [bold]{command_name}[/] ({command}) in node: [bold]{ctx.node_def.node_name}[/] ({ctx.path})", ) - print(os.popen(command).read()) # noqa: S605 + print(os.popen(command).read()) # noqa: S605,T201 else: console.print( f"Command [bold]{command_name}[/] not found in node definition: [bold]{ctx.node_def.node_name}[/] ({ctx.path})", diff --git a/madsci/madsci_client/madsci/client/cli/resources_cli.py b/src/madsci_client/madsci/client/cli/resources_cli.py similarity index 99% rename from madsci/madsci_client/madsci/client/cli/resources_cli.py rename to src/madsci_client/madsci/client/cli/resources_cli.py index 24a1492..ea3ff27 100644 --- a/madsci/madsci_client/madsci/client/cli/resources_cli.py +++ b/src/madsci_client/madsci/client/cli/resources_cli.py @@ -5,9 +5,6 @@ import click from click.core import Context -from rich.console import Console -from rich.pretty import pprint - from madsci.common.types.resource_types import ( RESOURCE_BASE_TYPES, RESOURCE_DEFINITION_MAP, @@ -24,6 +21,8 @@ save_model, search_for_file_pattern, ) +from rich.console import Console +from rich.pretty import pprint console = Console() diff --git a/madsci/madsci_client/madsci/client/cli/workcell_cli.py b/src/madsci_client/madsci/client/cli/workcell_cli.py similarity index 99% rename from madsci/madsci_client/madsci/client/cli/workcell_cli.py rename to src/madsci_client/madsci/client/cli/workcell_cli.py index fd46b6c..88c1f66 100644 --- a/madsci/madsci_client/madsci/client/cli/workcell_cli.py +++ b/src/madsci_client/madsci/client/cli/workcell_cli.py @@ -5,9 +5,6 @@ import click from click import Context -from rich.console import Console -from rich.pretty import pprint - from madsci.client.cli.lab_cli import LabContext, find_lab from madsci.common.types.workcell_types import WorkcellDefinition from madsci.common.utils import ( @@ -17,6 +14,8 @@ search_for_file_pattern, to_snake_case, ) +from rich.console import Console +from rich.pretty import pprint console = Console() @@ -191,7 +190,7 @@ def list(ctx: WorkcellContext) -> None: ) else: lab_context = " in lab directory" if ctx.lab and ctx.lab.path else "" - print( + print( # noqa: T201 f"No workcell definitions found{lab_context}, you can create one with 'madsci workcell create'", ) @@ -203,7 +202,7 @@ def info(ctx: WorkcellContext) -> None: if ctx.workcell: pprint(ctx.workcell) else: - print( + print( # noqa: T201 "No workcell specified/found, please specify a workcell with --name or --path, or create a new workcell with 'madsci workcell create'", ) @@ -243,7 +242,7 @@ def delete( del ctx.lab.lab_def.workcells[ctx.workcell.name] save_model(ctx.lab.path, ctx.lab.lab_def, overwrite_check=False) else: - print( + print( # noqa: T201 "No workcell specified/found, please specify a workcell with --name or --path, or create a new workcell with 'madsci workcell create'", ) diff --git a/src/madsci_client/madsci/client/event_client.py b/src/madsci_client/madsci/client/event_client.py new file mode 100644 index 0000000..2020289 --- /dev/null +++ b/src/madsci_client/madsci/client/event_client.py @@ -0,0 +1,163 @@ +"""MADSci Event Handling.""" + +import contextlib +import inspect +import logging +from collections import OrderedDict +from pathlib import Path +from typing import Any, Optional, Union + +import requests +from madsci.common.types.auth_types import OwnershipInfo +from madsci.common.types.base_types import PathLike +from madsci.common.types.event_types import Event, EventType +from madsci.common.utils import threaded_task +from pydantic import ValidationError +from rich import print + + +class EventClient: + """A logger and event handler for MADSci system components.""" + + def __init__( + self, + name: Optional[str] = None, + log_level: int = logging.INFO, + event_server: Optional[str] = None, + source: Optional[OwnershipInfo] = None, + log_dir: Optional[PathLike] = None, + ) -> None: + """Initialize the event logger.""" + if name: + self.name = name + else: + # * See if there's a calling class we can name after + stack = inspect.stack() + parent = stack[1][0] + if calling_self := parent.f_locals.get("self"): + self.name = calling_self.__class__.__name__ + else: + # * No luck, name after EventClient + self.name = __name__ + self.name = name if name else __name__ + self.logger = logging.getLogger(self.name) + self.log_dir = Path(log_dir) if log_dir else Path.home() / ".madsci" / "logs" + self.log_dir.mkdir(parents=True, exist_ok=True) + self.logfile = self.log_dir / f"{self.name}.log" + self.logger.setLevel(log_level) + if len(self.logger.handlers) == 0: + file_handler = logging.FileHandler(filename=str(self.logfile), mode="a+") + self.logger.addHandler(file_handler) + self.event_server = event_server + self.source = source + + def get_log(self) -> dict[str, Event]: + """Read the log""" + events = {} + with self.logfile.open() as log: + for line in log.readlines(): + try: + event = Event.model_validate_json(line) + events[event.event_id] = event + except ValidationError: + events.append(Event(event_type=EventType.UNKNOWN, event_data=line)) + return events + + def get_events(self, number: int = 100, level: int = -1) -> dict[str, Event]: + """Query the event server for a certain number of recent events. If no event server is configured, query the log file instead.""" + if level == -1: + level = self.logger.getEffectiveLevel() + events = OrderedDict() + if self.event_server: + response = requests.get( + self.event_server + "/events", + timeout=10, + params={"number": number, "level": level}, + ) + if not response.ok: + response.raise_for_status() + print(response.json()) + for key, value in response.json().items(): + events[key] = Event.model_validate(value) + return dict(events) + events = self.get_log() + selected_events = {} + for event in reversed(list(events.values())): + selected_events[event.event_id] = event + if len(selected_events) >= number: + break + return selected_events + + def log(self, event: Union[Event, Any], level: Optional[int] = None) -> None: + """Log an event.""" + + # * If we've got a string or dict, check if it's a serialized event + if isinstance(event, str): + with contextlib.suppress(ValidationError): + event = Event.model_validate_json(event) + if isinstance(event, dict): + with contextlib.suppress(ValidationError): + event = Event.model_validate(**event) + if not isinstance(event, Event): + event = self._new_event_for_log(event, level) + event.log_level = level if level else event.log_level + event.source = event.source if event.source is not None else self.source + self.logger.log(event.log_level, event.model_dump_json()) + # * Log the event to the event server if configured + # * Only log if the event is at the same level or higher than the logger + if self.logger.getEffectiveLevel() <= event.log_level: + print(f"{event.event_timestamp} ({event.event_type}): {event.event_data}") + if self.event_server: + self._send_event_to_event_server(event) + + def log_debug(self, event: Union[Event, str]) -> None: + """Log an event at the debug level.""" + self.log(event, logging.DEBUG) + + def log_info(self, event: Union[Event, str]) -> None: + """Log an event at the info level.""" + self.log(event, logging.INFO) + + def log_warning(self, event: Union[Event, str]) -> None: + """Log an event at the warning level.""" + self.log(event, logging.WARNING) + + def log_error(self, event: Union[Event, str]) -> None: + """Log an event at the error level.""" + self.log(event, logging.ERROR) + + def log_critical(self, event: Union[Event, str]) -> None: + """Log an event at the critical level.""" + self.log(event, logging.CRITICAL) + + @threaded_task + def _send_event_to_event_server(self, event: Event) -> None: + """Send an event to the event manager.""" + response = requests.post( + url=self.event_server + "/event", + json=event.model_dump(mode="json"), + timeout=10, + ) + if not response.ok: + response.raise_for_status() + + def _new_event_for_log(self, event_data: Any, level: int) -> Event: + """Create a new log event from arbitrary data""" + event_type = EventType.LOG + if level == logging.DEBUG: + event_type = EventType.LOG_DEBUG + elif level == logging.INFO: + event_type = EventType.LOG_INFO + elif level == logging.WARNING: + event_type = EventType.LOG_WARNING + elif level == logging.ERROR: + event_type = EventType.LOG_ERROR + elif level == logging.CRITICAL: + event_type = EventType.LOG_CRITICAL + return Event( + event_type=event_type, + event_data=event_data, + ) + + +default_logger = EventClient(name="madsci_default_log", log_level=logging.INFO) diff --git a/madsci/madsci_client/madsci/client/node/__init__.py b/src/madsci_client/madsci/client/node/__init__.py similarity index 100% rename from madsci/madsci_client/madsci/client/node/__init__.py rename to src/madsci_client/madsci/client/node/__init__.py diff --git a/madsci/madsci_client/madsci/client/node/abstract_node_client.py b/src/madsci_client/madsci/client/node/abstract_node_client.py similarity index 98% rename from madsci/madsci_client/madsci/client/node/abstract_node_client.py rename to src/madsci_client/madsci/client/node/abstract_node_client.py index e5880e5..2d2ed17 100644 --- a/madsci/madsci_client/madsci/client/node/abstract_node_client.py +++ b/src/madsci_client/madsci/client/node/abstract_node_client.py @@ -8,12 +8,10 @@ ) from madsci.common.types.admin_command_types import AdminCommandResponse from madsci.common.types.event_types import Event -from madsci.common.types.module_types import ( - AdminCommands, - NodeClientCapabilities, -) from madsci.common.types.node_types import ( + AdminCommands, Node, + NodeClientCapabilities, NodeInfo, NodeSetConfigResponse, NodeStatus, diff --git a/madsci/madsci_client/madsci/client/node/rest_node_client.py b/src/madsci_client/madsci/client/node/rest_node_client.py similarity index 98% rename from madsci/madsci_client/madsci/client/node/rest_node_client.py rename to src/madsci_client/madsci/client/node/rest_node_client.py index cf17754..aa6dfcd 100644 --- a/madsci/madsci_client/madsci/client/node/rest_node_client.py +++ b/src/madsci_client/madsci/client/node/rest_node_client.py @@ -5,19 +5,16 @@ from typing import Any, ClassVar import requests - from madsci.client.node.abstract_node_client import ( AbstractNodeClient, ) from madsci.common.types.action_types import ActionRequest, ActionResult from madsci.common.types.admin_command_types import AdminCommandResponse from madsci.common.types.event_types import Event -from madsci.common.types.module_types import ( - AdminCommands, - NodeClientCapabilities, -) from madsci.common.types.node_types import ( + AdminCommands, Node, + NodeClientCapabilities, NodeInfo, NodeSetConfigResponse, NodeStatus, @@ -56,7 +53,7 @@ def send_action(self, action_request: ActionRequest) -> ActionResult: ("files", (file, Path(path).open("rb"))) # noqa: SIM115 for file, path in action_request.files.items() ] - print(files) + self.logger.log_debug(files) rest_response = requests.post( f"{self.node.node_url}/action", diff --git a/madsci/madsci_client/pyproject.toml b/src/madsci_client/pyproject.toml similarity index 100% rename from madsci/madsci_client/pyproject.toml rename to src/madsci_client/pyproject.toml diff --git a/src/madsci_client/tests/test_lab_cli.py b/src/madsci_client/tests/test_lab_cli.py new file mode 100644 index 0000000..facd82b --- /dev/null +++ b/src/madsci_client/tests/test_lab_cli.py @@ -0,0 +1,126 @@ +"""Automated Tests for the MADSci cli's lab commands.""" + +import os +from pathlib import Path + +import pytest +from click.testing import CliRunner +from madsci.client.cli import root_cli +from madsci.common.types.squid_types import LabDefinition + + +@pytest.fixture +def runner() -> CliRunner: + """Fixture for creating a CliRunner instance.""" + return CliRunner() + + +@pytest.fixture +def temp_lab_file(tmp_path: Path) -> Path: + """Fixture for creating a temporary lab definition file.""" + lab_file = tmp_path / "test_lab.lab.yaml" + lab_definition = LabDefinition( + name="test_lab", + description="A test lab", + ) + lab_definition.to_yaml(lab_file) + return lab_file + + +def test_lab_create(runner: CliRunner, tmp_path: Path) -> None: + """Test creating a new lab definition.""" + result = runner.invoke( + root_cli, + [ + "lab", + "create", + "--name", + "test_lab", + "--description", + "A test lab", + "--path", + str(tmp_path / "test_lab.lab.yaml"), + ], + ) + assert result.exit_code == 0 + assert (tmp_path / "test_lab.lab.yaml").exists() + + +def test_lab_list(runner: CliRunner, temp_lab_file: Path) -> None: + """Test listing all lab definitions.""" + os.chdir(temp_lab_file.parent) + result = runner.invoke(root_cli, ["lab", "list"]) + assert result.exit_code == 0 + assert "test_lab" in result.output + + +def test_lab_info(runner: CliRunner, temp_lab_file: Path) -> None: + """Test getting information about a specific lab definition.""" + os.chdir(temp_lab_file.parent) + result = runner.invoke(root_cli, ["lab", "--path", str(temp_lab_file), "info"]) + assert result.exit_code == 0 + assert "test_lab" in result.output + + +def test_lab_delete(runner: CliRunner, temp_lab_file: Path) -> None: + """Test deleting a lab definition.""" + os.chdir(temp_lab_file.parent) + result = runner.invoke( + root_cli, ["lab", "--path", str(temp_lab_file), "delete", "--yes"] + ) + assert result.exit_code == 0 + assert not temp_lab_file.exists() + + +def test_lab_validate(runner: CliRunner, temp_lab_file: Path) -> None: + """Test validating a lab definition file.""" + os.chdir(temp_lab_file.parent) + result = runner.invoke(root_cli, ["lab", "--path", str(temp_lab_file), "validate"]) + assert result.exit_code == 0 + assert "test_lab" in result.output + + +def test_lab_commands(runner: CliRunner, temp_lab_file: Path) -> None: + """Test adding a command to a lab definition.""" + os.chdir(temp_lab_file.parent) + # * Test creating a new command + result = runner.invoke( + root_cli, + [ + "lab", + "--path", + str(temp_lab_file), + "add-command", + "--name", + "test_command", + "--command", + "echo 'Hello, World!'", + ], + ) + assert result.exit_code == 0 + assert "test_command" in result.output + # * Test running the command + result = runner.invoke( + root_cli, ["lab", "--path", str(temp_lab_file), "run", "test_command"] + ) + assert result.exit_code == 0 + assert "Hello, World!" in result.output + # * Test deleting the command + result = runner.invoke( + root_cli, + [ + "lab", + "--path", + str(temp_lab_file), + "delete-command", + "test_command", + "--yes", + ], + ) + assert result.exit_code == 0 + assert "Deleted command" in result.output + # * Test running the command after deletion + result = runner.invoke( + root_cli, ["lab", "--path", str(temp_lab_file), "run", "test_command"] + ) + assert result.exit_code != 0 diff --git a/madsci/madsci_common/README.md b/src/madsci_common/README.md similarity index 100% rename from madsci/madsci_common/README.md rename to src/madsci_common/README.md diff --git a/madsci/madsci_common/madsci/common/__init__.py b/src/madsci_common/madsci/common/__init__.py similarity index 100% rename from madsci/madsci_common/madsci/common/__init__.py rename to src/madsci_common/madsci/common/__init__.py diff --git a/src/madsci_common/madsci/common/definition_loaders.py b/src/madsci_common/madsci/common/definition_loaders.py new file mode 100644 index 0000000..23d243c --- /dev/null +++ b/src/madsci_common/madsci/common/definition_loaders.py @@ -0,0 +1,207 @@ +"""MADSci Configuration Loaders.""" + +import argparse +from pathlib import Path +from typing import Any, Optional, Union + +from madsci.client.event_client import default_logger +from madsci.common.types.base_types import BaseModel +from madsci.common.types.config_types import ( + ConfigNamespaceDefinition, + ConfigParameterDefinition, +) +from madsci.common.types.node_types import ( + NodeDefinition, + NodeModuleDefinition, + get_module_from_node_definition, +) +from madsci.common.types.squid_types import ( + MANAGER_TYPE_DEFINITION_MAP, + LabDefinition, + ManagerDefinition, +) +from madsci.common.utils import search_for_file_pattern +from pydantic import AnyUrl + + +def madsci_definition_loader( + model: type[BaseModel] = BaseModel, + definition_file_pattern: str = "*.yaml", + search_for_file: bool = True, + return_all: bool = False, + cli_arg: Optional[str] = "definition", +) -> Optional[Union[BaseModel, list[BaseModel]]]: + """MADSci Definition Loader. Supports loading from a definition file, environment variables, and command line arguments, in reverse order of priority (i.e. command line arguments override environment variables, which override definition file values).""" + + definition_files = [] + if cli_arg: + parser = argparse.ArgumentParser(description="MADSci Definition Loader") + parser.add_argument( + f"--{cli_arg}", + type=Path, + help="The path to the MADSci configuration file.", + ) + args, _ = parser.parse_known_args() + if args.definition: + definition_files.append(args.definition) + + # *Load from definition file + if search_for_file: + definition_files.extend( + search_for_file_pattern( + definition_file_pattern, + parents=True, + children=True, + ) + ) + + if return_all: + return [model.from_yaml(file) for file in definition_files] + return model.from_yaml(definition_files[0]) if definition_files else None + + +def lab_definition_loader( + model: type[BaseModel] = LabDefinition, + definition_file_pattern: str = "*.lab.yaml", + **kwargs: Any, +) -> LabDefinition: + """Lab Definition Loader. Supports loading from a definition file, environment variables, and command line arguments, in reverse order of priority (i.e. command line arguments override environment variables, which override definition file values).""" + return madsci_definition_loader( + model=model, + definition_file_pattern=definition_file_pattern, + **kwargs, + ) + + +def node_definition_loader( + model: type[BaseModel] = NodeDefinition, + definition_file_pattern: str = "*.node.yaml", + **kwargs: Any, +) -> tuple[NodeDefinition, NodeModuleDefinition, dict[str, Any]]: + """Node Definition Loader. Supports loading from a definition file, environment variables, and command line arguments, in reverse order of priority (i.e. command line arguments override environment variables, which override definition file values).""" + + # * Load the node definition file + node_definition = madsci_definition_loader( + model=model, + definition_file_pattern=definition_file_pattern, + **kwargs, + ) + default_logger.log_debug(f"Node Definition: {node_definition}") + + module_definition = get_module_from_node_definition(node_definition) + + config_definition = node_definition.config.copy() + + # * Import any module config from the module definition + for config_name, config in module_definition.config.items(): + # * Only add the config if it isn't already defined in the node definition + if config_name not in node_definition.config: + config_definition[config_name] = config + + config_values = load_config(config_definition) + + # * Return the node and module definitions + return node_definition, module_definition, config_values + + +def load_config( + config_definition: dict[ + str, Union[ConfigParameterDefinition, ConfigNamespaceDefinition] + ], +) -> dict[str, Any]: + """Load configuration values from the command line, based on the config parameters definition""" + # * Step 1: Create an argparse parser for the node configuration definition + parser = argparse.ArgumentParser(description="MADSci Node Definition Loader") + + # * Step 2: Parse the command line args + for field_name, field in config_definition.items(): + build_argparser_from_config_definition(parser, field_name, field) + args, _ = parser.parse_known_args() + + # * Step 3: Try to parse the argparser results into a config dictionary + config_values = parse_args_to_config(config_definition, args) + default_logger.log_debug(f"Arg Values: {args}") + default_logger.log_debug(f"Config Values: {config_values}") + return config_values + + +def build_argparser_from_config_definition( + parser: argparse.ArgumentParser, + field_name: str, + field: Union[ConfigParameterDefinition, ConfigNamespaceDefinition], +) -> None: + """Creates argparse args for each config parameter, recursively for nested namespaces""" + if isinstance(field, ConfigNamespaceDefinition): + for sub_field_name, sub_field in field.parameters.items(): + build_argparser_from_config_definition( + parser, f"{field_name}_{sub_field_name}", sub_field + ) + else: + parser.add_argument( + f"--{field_name}", + type=str, + help=field.description, + default=field.default if hasattr(field, "default") else None, + required=False, + ) + + +def parse_args_to_config( + config_definition: dict[ + str, Union[ConfigParameterDefinition, ConfigNamespaceDefinition] + ], + args: argparse.Namespace, + prefix: str = "", +) -> dict[str, Any]: + """Parses argparse args into a config dictionary, recursively for nested namespaces.""" + config_values = {} + for field_name, field in config_definition.items(): + if isinstance(field, ConfigNamespaceDefinition): + config_values[field_name] = parse_args_to_config( + field.parameters, args, prefix=f"{prefix}{field_name}_" + ) + else: + config_values[field_name] = getattr(args, f"{prefix}{field_name}") + return config_values + + +def manager_definition_loader( + model: type[BaseModel] = ManagerDefinition, + definition_file_pattern: str = "*.*manager.yaml", +) -> ManagerDefinition: + """Loads all Manager Definitions available in the current context""" + + # * Load from any standalone manager definition files + manager_definitions = madsci_definition_loader( + model=model, + definition_file_pattern=definition_file_pattern, + cli_arg=None, + search_for_file=True, + return_all=True, + ) + + # * Load from the lab manager's managers section + lab_manager_definition = lab_definition_loader(search_for_file=True) + if lab_manager_definition: + for manager in lab_manager_definition.managers.values(): + if isinstance(manager, ManagerDefinition): + manager_definitions.append(manager) + elif isinstance(manager, AnyUrl): + # TODO: Support querying manager definition from URL, skip for now + pass + elif isinstance(manager, (Path, str)): + manager_definitions.append(ManagerDefinition.from_yaml(manager)) + + # * Upgrade to more specific manager types, where possible + refined_managers = [] + for manager in manager_definitions: + if manager.manager_type in MANAGER_TYPE_DEFINITION_MAP: + refined_managers.append( + MANAGER_TYPE_DEFINITION_MAP[manager.manager_type].model_validate( + manager + ) + ) + else: + refined_managers.append(manager) + + return refined_managers diff --git a/madsci/madsci_common/madsci/common/exceptions.py b/src/madsci_common/madsci/common/exceptions.py similarity index 100% rename from madsci/madsci_common/madsci/common/exceptions.py rename to src/madsci_common/madsci/common/exceptions.py diff --git a/madsci/madsci_common/madsci/common/types/__init__.py b/src/madsci_common/madsci/common/types/__init__.py similarity index 100% rename from madsci/madsci_common/madsci/common/types/__init__.py rename to src/madsci_common/madsci/common/types/__init__.py diff --git a/madsci/madsci_common/madsci/common/types/action_types.py b/src/madsci_common/madsci/common/types/action_types.py similarity index 99% rename from madsci/madsci_common/madsci/common/types/action_types.py rename to src/madsci_common/madsci/common/types/action_types.py index 87f3b31..c5d2a6a 100644 --- a/madsci/madsci_common/madsci/common/types/action_types.py +++ b/src/madsci_common/madsci/common/types/action_types.py @@ -4,11 +4,10 @@ from enum import Enum from typing import Any, Literal, Optional, Union +from madsci.common.types.base_types import BaseModel, Error, PathLike, new_ulid_str from pydantic.functional_validators import field_validator, model_validator from sqlmodel.main import Field -from madsci.common.types.base_types import BaseModel, Error, PathLike, new_ulid_str - class ActionStatus(str, Enum): """Status for a step of a workflow""" diff --git a/madsci/madsci_common/madsci/common/types/admin_command_types.py b/src/madsci_common/madsci/common/types/admin_command_types.py similarity index 99% rename from madsci/madsci_common/madsci/common/types/admin_command_types.py rename to src/madsci_common/madsci/common/types/admin_command_types.py index 56b7296..d586d55 100644 --- a/madsci/madsci_common/madsci/common/types/admin_command_types.py +++ b/src/madsci_common/madsci/common/types/admin_command_types.py @@ -2,9 +2,8 @@ from enum import Enum -from sqlmodel.main import Field - from madsci.common.types.base_types import BaseModel, Error +from sqlmodel.main import Field class AdminCommands(str, Enum): diff --git a/madsci/madsci_common/madsci/common/types/auth_types.py b/src/madsci_common/madsci/common/types/auth_types.py similarity index 82% rename from madsci/madsci_common/madsci/common/types/auth_types.py rename to src/madsci_common/madsci/common/types/auth_types.py index b6d9b04..21a88fe 100644 --- a/madsci/madsci_common/madsci/common/types/auth_types.py +++ b/src/madsci_common/madsci/common/types/auth_types.py @@ -1,13 +1,13 @@ """Types related to authentication and ownership of MADSci objects.""" -from typing import Optional +from typing import Any, Optional +from madsci.common.types.base_types import BaseModel, new_ulid_str +from madsci.common.validators import ulid_validator +from pydantic import SerializationInfo, SerializerFunctionWrapHandler, model_serializer from pydantic.functional_validators import field_validator from sqlmodel.main import Field -from madsci.common.types.base_types import BaseModel -from madsci.common.types.validators import ulid_validator - class OwnershipInfo(BaseModel): """Information about the ownership of a MADSci object.""" @@ -15,6 +15,7 @@ class OwnershipInfo(BaseModel): auth_id: str = Field( title="Auth ID", description="The ID of the auth that owns the object.", + default_factory=new_ulid_str, ) user_id: Optional[str] = Field( @@ -63,6 +64,14 @@ class OwnershipInfo(BaseModel): mode="after", )(ulid_validator) + @model_serializer(mode="wrap") + def exclude_unset_by_default( + self, nxt: SerializerFunctionWrapHandler, info: SerializationInfo + ) -> dict[str, Any]: + """Exclude unset fields by default.""" + serialized = nxt(self, info) + return {k: v for k, v in serialized.items() if v is not None} + class UserInfo(BaseModel): """Information about a user.""" diff --git a/madsci/madsci_common/madsci/common/types/base_types.py b/src/madsci_common/madsci/common/types/base_types.py similarity index 100% rename from madsci/madsci_common/madsci/common/types/base_types.py rename to src/madsci_common/madsci/common/types/base_types.py diff --git a/src/madsci_common/madsci/common/types/config_types.py b/src/madsci_common/madsci/common/types/config_types.py new file mode 100644 index 0000000..d785bbc --- /dev/null +++ b/src/madsci_common/madsci/common/types/config_types.py @@ -0,0 +1,61 @@ +"""Types for configuring components of MADSci""" + +from typing import Any, Optional, Union + +from madsci.common.types.base_types import BaseModel +from pydantic import Field + + +class ConfigParameterDefinition(BaseModel, extra="allow"): + """A configuration parameter definition for a MADSci system component.""" + + name: str = Field( + title="Parameter Name", + description="The name of the parameter.", + ) + description: Optional[str] = Field( + title="Parameter Description", + description="A description of the parameter.", + default=None, + ) + default: Optional[Any] = Field( + title="Parameter Default", + description="The default value of the parameter.", + default=None, + ) + required: bool = Field( + title="Parameter Required", + description="Whether the parameter is required.", + default=False, + ) + + +class ConfigParameterWithValue(ConfigParameterDefinition): + """A configuration parameter definition with value set""" + + value: Optional[Any] = Field( + title="Parameter Value", + description="The value of the parameter, if set", + default=None, + ) + + +class ConfigNamespaceDefinition(BaseModel, extra="allow"): + """A namespace for configuration parameters.""" + + namespace: str = Field( + title="Namespace Name", + description="The name of the namespace.", + ) + description: Optional[str] = Field( + title="Namespace Description", + description="A description of the namespace.", + default=None, + ) + parameters: dict[ + str, Union[ConfigParameterDefinition, "ConfigNamespaceDefinition"] + ] = Field( + title="Namespace Parameters", + description="The config parameters in the namespace.", + default_factory=dict, + ) diff --git a/madsci/madsci_common/madsci/common/types/event_types.py b/src/madsci_common/madsci/common/types/event_types.py similarity index 82% rename from madsci/madsci_common/madsci/common/types/event_types.py rename to src/madsci_common/madsci/common/types/event_types.py index 3f5e4b2..4efe173 100644 --- a/madsci/madsci_common/madsci/common/types/event_types.py +++ b/src/madsci_common/madsci/common/types/event_types.py @@ -7,20 +7,16 @@ from enum import Enum from typing import Any, Optional -from pydantic import ConfigDict -from pydantic.functional_validators import field_validator -from sqlmodel import Field - from madsci.common.types.auth_types import OwnershipInfo from madsci.common.types.base_types import BaseModel, new_ulid_str -from madsci.common.types.validators import ulid_validator +from madsci.common.validators import ulid_validator +from pydantic.functional_validators import field_validator +from sqlmodel import Field class Event(BaseModel): """An event in the MADSci system.""" - model_config = ConfigDict(extra="allow") - event_id: str = Field( title="Event ID", description="The ID of the event.", @@ -29,12 +25,12 @@ class Event(BaseModel): event_type: "EventType" = Field( title="Event Type", description="The type of the event.", - default="unknown", + default_factory=lambda: EventType.UNKNOWN, ) log_level: "EventLogLevel" = Field( title="Event Log Level", description="The log level of the event. Defaults to NOTSET. See https://docs.python.org/3/library/logging.html#logging-levels", - default_factory=lambda: EventLogLevel.NOTSET, + default_factory=lambda: EventLogLevel.INFO, ) event_timestamp: datetime = Field( title="Event Timestamp", @@ -70,6 +66,13 @@ class EventType(str, Enum): """The type of an event.""" UNKNOWN = "unknown" + LOG = "log" + LOG_DEBUG = "log_debug" + LOG_INFO = "log_info" + LOG_WARNING = "log_warning" + LOG_ERROR = "log_error" + LOG_CRITICAL = "log_critical" + TEST = "test" # *Lab Events LAB_CREATE = "lab_create" LAB_START = "lab_start" @@ -80,6 +83,7 @@ class EventType(str, Enum): NODE_STOP = "node_stop" NODE_CONFIG_UPDATE = "node_config_update" NODE_STATUS_UPDATE = "node_status_update" + NODE_ERROR = "node_error" # *Workcell Events WORKCELL_CREATE = "workcell_create" WORKCELL_START = "workcell_start" @@ -104,3 +108,11 @@ class EventType(str, Enum): CAMPAIGN_START = "campaign_start" CAMPAIGN_COMPLETE = "campaign_complete" CAMPAIGN_ABORT = "campaign_abort" + + @classmethod + def _missing_(cls, value: str) -> "EventType": + value = value.lower() + for member in cls: + if member.lower() == value: + return member + raise ValueError(f"Invalid ManagerTypes: {value}") diff --git a/madsci/madsci_common/madsci/common/types/location_types.py b/src/madsci_common/madsci/common/types/location_types.py similarity index 97% rename from madsci/madsci_common/madsci/common/types/location_types.py rename to src/madsci_common/madsci/common/types/location_types.py index d461f50..146fe4e 100644 --- a/madsci/madsci_common/madsci/common/types/location_types.py +++ b/src/madsci_common/madsci/common/types/location_types.py @@ -2,13 +2,12 @@ from typing import Optional +from madsci.common.types.base_types import BaseModel, new_ulid_str +from madsci.common.validators import ulid_validator from pydantic import Field from pydantic.functional_validators import field_validator from pydantic.types import Json -from madsci.common.types.base_types import BaseModel, new_ulid_str -from madsci.common.types.validators import ulid_validator - class Location(BaseModel): """A location in the lab.""" diff --git a/madsci/madsci_common/madsci/common/types/node_types.py b/src/madsci_common/madsci/common/types/node_types.py similarity index 58% rename from madsci/madsci_common/madsci/common/types/node_types.py rename to src/madsci_common/madsci/common/types/node_types.py index f45afd6..4d81530 100644 --- a/madsci/madsci_common/madsci/common/types/node_types.py +++ b/src/madsci_common/madsci/common/types/node_types.py @@ -1,18 +1,193 @@ """MADSci Node Types.""" +from enum import Enum from os import PathLike from pathlib import Path from typing import Any, Optional, Union -from pydantic import Field +from madsci.common.types.action_types import ActionDefinition +from madsci.common.types.admin_command_types import AdminCommands +from madsci.common.types.base_types import BaseModel, Error, new_ulid_str +from madsci.common.types.config_types import ( + ConfigNamespaceDefinition, + ConfigParameterDefinition, +) +from madsci.common.validators import ulid_validator from pydantic.fields import computed_field from pydantic.functional_validators import field_validator from pydantic.networks import AnyUrl +from sqlmodel.main import Field -from madsci.common.types.action_types import ActionDefinition -from madsci.common.types.base_types import BaseModel, Error, new_ulid_str -from madsci.common.types.module_types import ConfigParameter, NodeModuleDefinition -from madsci.common.types.validators import ulid_validator + +class NodeType(str, Enum): + """The type of a MADSci node.""" + + DEVICE = "device" + COMPUTE = "compute" + RESOURCE_MANAGER = "resource_manager" + EVENT_MANAGER = "event_manager" + WORKCELL_MANAGER = "workcell_manager" + DATA_MANAGER = "data_manager" + TRANSFER_MANAGER = "transfer_manager" + + +class NodeModuleDefinition(BaseModel, extra="allow"): + """Definition for a MADSci Node Module.""" + + module_name: str = Field( + title="Node Module Name", + description="The name of the node module.", + ) + module_type: Optional[NodeType] = Field( + title="Module Type", + description="The type of the node module.", + default=None, + ) + module_description: Optional[str] = Field( + default=None, + title="Module Description", + description="A description of the node module.", + ) + capabilities: "NodeCapabilities" = Field( + default_factory=lambda: NodeCapabilities(), + title="Module Capabilities", + description="The capabilities of the node module.", + ) + config: Union[ + list[Union[ConfigParameterDefinition, ConfigNamespaceDefinition]], + dict[str, Union[ConfigParameterDefinition, ConfigNamespaceDefinition]], + ] = Field( + title="Module Configuration", + description="The configuration of the node module. These are 'default' configuration parameters inherited by all child nodes.", + default_factory=list, + ) + commands: dict[str, str] = Field( + title="Module Commands", + description="The commands that the node module supports. These are 'default' commands inherited by all child nodes.", + default_factory=dict, + ) + + @field_validator("config", mode="after") + @classmethod + def validate_config( + cls, + v: Union[ + list["ConfigParameterDefinition"], dict[str, "ConfigParameterDefinition"] + ], + ) -> Union[ + list["ConfigParameterDefinition"], dict[str, "ConfigParameterDefinition"] + ]: + """Validate the node module configuration, promoting a list of ConfigParameters to a dictionary for easier access.""" + if isinstance(v, dict): + return v + return { + param.name if hasattr(param, "name") else param.namespace: param + for param in v + } + + +NODE_MODULE_CONFIG_TEMPLATES: dict[str, list[ConfigParameterDefinition]] = { + "REST Node": [ + ConfigParameterDefinition( + name="host", + description="The host of the REST API.", + default="127.0.0.1", + required=True, + ), + ConfigParameterDefinition( + name="port", + description="The port of the REST API.", + default=8000, + required=True, + ), + ConfigParameterDefinition( + name="protocol", + description="The protocol of the REST API, either 'http' or 'https'.", + default="http", + required=True, + ), + ], +} + + +class NodeClientCapabilities(BaseModel): + """Capabilities of a MADSci Node Client.""" + + get_info: bool = Field( + default=False, + title="Module Info", + description="Whether the node supports querying its info.", + ) + get_state: bool = Field( + default=False, + title="Module State", + description="Whether the node supports querying its state.", + ) + get_status: bool = Field( + default=False, + title="Module Status", + description="Whether the node supports querying its status.", + ) + send_action: bool = Field( + default=False, + title="Module Send Action", + description="Whether the node supports sending actions.", + ) + get_action_result: bool = Field( + default=False, + title="Module Get Action", + description="Whether the node supports querying the status of an action.", + ) + get_action_history: bool = Field( + default=False, + title="Module Get Actions", + description="Whether the node supports querying the history of actions.", + ) + action_files: bool = Field( + default=False, + title="Module Action Files", + description="Whether the node supports sending action files.", + ) + send_admin_commands: bool = Field( + default=False, + title="Module Send Admin Commands", + description="Whether the node supports sending admin commands.", + ) + set_config: bool = Field( + default=False, + title="Module Set Config", + description="Whether the node supports setting configuration.", + ) + get_resources: bool = Field( + default=False, + title="Module Get Resources", + description="Whether the node supports querying its resources.", + ) + get_log: bool = Field( + default=False, + title="Module Get Log", + description="Whether the node supports querying its log.", + ) + + +class NodeCapabilities(NodeClientCapabilities): + """Capabilities of a MADSci Node.""" + + events: bool = Field( + default=False, + title="Module Events", + description="Whether the module supports raising MADSci events.", + ) + resources: bool = Field( + default=False, + title="Module Resources", + description="Whether the module supports MADSci-compatible resource management.", + ) + admin_commands: set[AdminCommands] = Field( + default=set(), + title="Module Admin Commands", + description="Which admin commands the module supports, if any.", + ) def get_module_from_node_definition( @@ -90,7 +265,10 @@ class NodeDefinition(BaseModel): description="Definition of the module that the node is an instance of.", default=None, ) # TODO: Add support for pointing to URL - config: Union[list[ConfigParameter], dict[str, ConfigParameter]] = Field( + config: Union[ + list[Union[ConfigParameterDefinition, ConfigNamespaceDefinition]], + dict[str, Union[ConfigParameterDefinition, ConfigNamespaceDefinition]], + ] = Field( title="Node Configuration", description="The configuration for the node.", default_factory=list, @@ -107,12 +285,15 @@ class NodeDefinition(BaseModel): @classmethod def validate_config( cls, - v: Union[list[ConfigParameter], dict[str, ConfigParameter]], - ) -> Union[list[ConfigParameter], dict[str, ConfigParameter]]: + v: Union[list[ConfigParameterDefinition], dict[str, ConfigParameterDefinition]], + ) -> Union[list[ConfigParameterDefinition], dict[str, ConfigParameterDefinition]]: """Validate the node configuration, promoting a list of ConfigParameters to a dictionary for easier access.""" if isinstance(v, dict): return v - return {param.name: param for param in v} + return { + param.name if hasattr(param, "name") else param.namespace: param + for param in v + } class Node(BaseModel, arbitrary_types_allowed=True): diff --git a/madsci/madsci_common/madsci/common/types/resource_types.py b/src/madsci_common/madsci/common/types/resource_types.py similarity index 99% rename from madsci/madsci_common/madsci/common/types/resource_types.py rename to src/madsci_common/madsci/common/types/resource_types.py index ec02802..7edbb97 100644 --- a/madsci/madsci_common/madsci/common/types/resource_types.py +++ b/src/madsci_common/madsci/common/types/resource_types.py @@ -3,16 +3,15 @@ from enum import Enum from typing import Annotated, Any, Literal, Optional, Union +from madsci.common.types.auth_types import OwnershipInfo +from madsci.common.types.base_types import BaseModel, new_ulid_str +from madsci.common.validators import ulid_validator from pydantic import Json from pydantic.config import ConfigDict from pydantic.functional_validators import field_validator, model_validator from pydantic.types import Discriminator, Tag from sqlmodel.main import Field -from madsci.common.types.auth_types import OwnershipInfo -from madsci.common.types.base_types import BaseModel, new_ulid_str -from madsci.common.types.validators import ulid_validator - class ResourceType(str, Enum): """Type for a MADSci Resource.""" diff --git a/madsci/madsci_common/madsci/common/types/squid_types.py b/src/madsci_common/madsci/common/types/squid_types.py similarity index 70% rename from madsci/madsci_common/madsci/common/types/squid_types.py rename to src/madsci_common/madsci/common/types/squid_types.py index 402c8a7..34f7a38 100644 --- a/madsci/madsci_common/madsci/common/types/squid_types.py +++ b/src/madsci_common/madsci/common/types/squid_types.py @@ -1,18 +1,16 @@ """Types for MADSci Squid configuration.""" from enum import Enum -from typing import Any, Optional, Union - -from pydantic.functional_validators import field_validator -from pydantic.networks import AnyUrl -from sqlmodel.main import Field +from typing import Any, Literal, Optional, Union from madsci.common.types.base_types import BaseModel, PathLike, new_ulid_str -from madsci.common.types.validators import ( - alphanumeric_with_underscores_validator, +from madsci.common.types.workcell_types import WorkcellDefinition +from madsci.common.validators import ( ulid_validator, ) -from madsci.common.types.workcell_types import WorkcellDefinition +from pydantic.functional_validators import field_validator +from pydantic.networks import AnyUrl +from sqlmodel.main import Field class LabDefinition(BaseModel): @@ -47,17 +45,19 @@ class LabDefinition(BaseModel): managers: dict[str, Union["ManagerDefinition", PathLike, AnyUrl]] = Field( default_factory=dict, title="Squid Manager Definitions", - description="Squid Manager definitions used by the lab. Either a path to a manager definition file, a URL to a manager, or a manager definition object. If the manager definition is a URL, the server will attempt to fetch the manager definition from the URL.", + description="Squid Manager definitions used by the lab. Either a path to a manager definition file, a URL to a manager, or a manager definition object. If the manager definition is a URL, the server will attempt to fetch the manager definition from the URL during startup.", ) @field_validator("commands") @classmethod def validate_commands(cls, v: dict[str, str]) -> dict[str, str]: - """Validate the commands.""" + """Validate the command names.""" if v: for command in v: - if not str.isalnum(command): - raise ValueError(f"Command '{command}' must be alphanumeric") + if not str(command).replace("_", "").isalnum(): + raise ValueError( + f"Command '{command}' must consist only of alphanumeric characters or underscores." + ) return v is_ulid = field_validator("lab_id")(ulid_validator) @@ -88,14 +88,14 @@ class ManagerDefinition(BaseModel): manager_id: Optional[str] = Field( title="Manager ID", description="The ID of the manager.", - default=None, + default_factory=new_ulid_str, ) description: Optional[str] = Field( default=None, title="Description", description="A description of the manager.", ) - manager_type: str = Field( + manager_type: "ManagerType" = Field( title="Manager Type", description="The type of the manager, used by other components or managers to find matching managers.", ) @@ -110,18 +110,13 @@ class ManagerDefinition(BaseModel): description="The URL of the manager server.", ) - is_alphanumeric = field_validator("manager_type")( - alphanumeric_with_underscores_validator, - ) - -class ManagerTypes(str, Enum): +class ManagerType(str, Enum): """Types of Squid Managers.""" WORKCELL_MANAGER = "workcell_manager" RESOURCE_MANAGER = "resource_manager" EVENT_MANAGER = "event_manager" - LOG_MANAGER = "log_manager" AUTH_MANAGER = "auth_manager" NOTIFICATION_MANAGER = "notification_manager" DATA_MANAGER = "data_manager" @@ -129,9 +124,47 @@ class ManagerTypes(str, Enum): DASHBOARD_MANAGER = "dashboard_manager" @classmethod - def _missing_(cls, value: str) -> "ManagerTypes": + def _missing_(cls, value: str) -> "ManagerType": value = value.lower() for member in cls: if member.lower() == value: return member raise ValueError(f"Invalid ManagerTypes: {value}") + + +class EventManagerDefinition(ManagerDefinition): + """Definition for a Squid Event Manager""" + + manager_type: Literal[ManagerType.EVENT_MANAGER] = Field( + title="Manager Type", + description="The type of the event manager", + default=ManagerType.EVENT_MANAGER, + ) + manager_config: "EventManagerConfig" = Field( + default_factory=lambda: EventManagerConfig(), + title="Manager Configuration", + description="The configuration for an event manager", + ) + + +class EventManagerConfig(BaseModel): + """Configuration definition for an Event Manager""" + + host: str = Field( + default="127.0.0.1", + title="Server Host", + description="The hostname or IP address of the Event Manager server.", + ) + port: int = Field( + default=8001, + title="Server Port", + description="The port number of the Event Manager server.", + ) + db_url: str = Field( + default="mongodb://localhost:27017", + title="Database URL", + description="The URL of the database used by the Event Manager.", + ) + + +MANAGER_TYPE_DEFINITION_MAP = {"event_manager": EventManagerDefinition} diff --git a/madsci/madsci_common/madsci/common/types/step_types.py b/src/madsci_common/madsci/common/types/step_types.py similarity index 99% rename from madsci/madsci_common/madsci/common/types/step_types.py rename to src/madsci_common/madsci/common/types/step_types.py index 40fd724..fb97377 100644 --- a/madsci/madsci_common/madsci/common/types/step_types.py +++ b/src/madsci_common/madsci/common/types/step_types.py @@ -3,10 +3,9 @@ from datetime import datetime, timedelta from typing import Any, Optional -from sqlmodel.main import Field - from madsci.common.types.action_types import ActionResult, ActionStatus from madsci.common.types.base_types import BaseModel, PathLike, new_ulid_str +from sqlmodel.main import Field class StepDefinition(BaseModel): diff --git a/madsci/madsci_common/madsci/common/types/workcell_types.py b/src/madsci_common/madsci/common/types/workcell_types.py similarity index 91% rename from madsci/madsci_common/madsci/common/types/workcell_types.py rename to src/madsci_common/madsci/common/types/workcell_types.py index 3e96a62..de376c5 100644 --- a/madsci/madsci_common/madsci/common/types/workcell_types.py +++ b/src/madsci_common/madsci/common/types/workcell_types.py @@ -2,14 +2,13 @@ from typing import Optional, Union +from madsci.common.types.base_types import BaseModel, PathLike, new_ulid_str +from madsci.common.types.node_types import NodeDefinition +from madsci.common.validators import ulid_validator from pydantic.functional_validators import field_validator from pydantic.networks import AnyUrl from sqlmodel.main import Field -from madsci.common.types.base_types import BaseModel, PathLike, new_ulid_str -from madsci.common.types.node_types import NodeDefinition -from madsci.common.types.validators import ulid_validator - class WorkcellDefinition(BaseModel, extra="allow"): """Configuration for a MADSci Workcell.""" @@ -40,6 +39,10 @@ class WorkcellDefinition(BaseModel, extra="allow"): ) is_ulid = field_validator("workcell_id")(ulid_validator) + """ + TODO: Do we need this validator? + is_dict = field_validator("nodes", mode="before")(create_dict_promoter("node_name")) + """ class WorkcellConfig(BaseModel): diff --git a/madsci/madsci_common/madsci/common/utils.py b/src/madsci_common/madsci/common/utils.py similarity index 99% rename from madsci/madsci_common/madsci/common/utils.py rename to src/madsci_common/madsci/common/utils.py index 975be12..e790a4e 100644 --- a/madsci/madsci_common/madsci/common/utils.py +++ b/src/madsci_common/madsci/common/utils.py @@ -5,12 +5,11 @@ from pathlib import Path from typing import Any, Optional +from madsci.common.types.base_types import BaseModel, PathLike from pydantic import ValidationError from pydantic_core._pydantic_core import PydanticUndefined from rich.console import Console -from madsci.common.types.base_types import BaseModel, PathLike - console = Console() diff --git a/madsci/madsci_common/madsci/common/types/validators.py b/src/madsci_common/madsci/common/validators.py similarity index 100% rename from madsci/madsci_common/madsci/common/types/validators.py rename to src/madsci_common/madsci/common/validators.py diff --git a/madsci/madsci_common/pdm.lock b/src/madsci_common/pdm.lock similarity index 94% rename from madsci/madsci_common/pdm.lock rename to src/madsci_common/pdm.lock index 3034e76..8510780 100644 --- a/madsci/madsci_common/pdm.lock +++ b/src/madsci_common/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "server", "tests"] strategy = ["inherit_metadata"] lock_version = "4.5.0" -content_hash = "sha256:48620a5f03867071175c529837f308437d8b523a041a4834e63223a3b8bed639" +content_hash = "sha256:dcc795dffd2c1ff4aeeaf751aa3775a33724bdc058c84da3bb4916947149da1a" [[metadata.targets]] requires_python = ">=3.9.1" @@ -329,6 +329,17 @@ files = [ {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, ] +[[package]] +name = "dnspython" +version = "2.7.0" +requires_python = ">=3.9" +summary = "DNS toolkit" +groups = ["default"] +files = [ + {file = "dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86"}, + {file = "dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1"}, +] + [[package]] name = "exceptiongroup" version = "1.2.2" @@ -870,6 +881,66 @@ files = [ {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, ] +[[package]] +name = "pymongo" +version = "4.10.1" +requires_python = ">=3.8" +summary = "Python driver for MongoDB " +groups = ["default"] +dependencies = [ + "dnspython<3.0.0,>=1.16.0", +] +files = [ + {file = "pymongo-4.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e699aa68c4a7dea2ab5a27067f7d3e08555f8d2c0dc6a0c8c60cfd9ff2e6a4b1"}, + {file = "pymongo-4.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:70645abc714f06b4ad6b72d5bf73792eaad14e3a2cfe29c62a9c81ada69d9e4b"}, + {file = "pymongo-4.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae2fd94c9fe048c94838badcc6e992d033cb9473eb31e5710b3707cba5e8aee2"}, + {file = "pymongo-4.10.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ded27a4a5374dae03a92e084a60cdbcecd595306555bda553b833baf3fc4868"}, + {file = "pymongo-4.10.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ecc2455e3974a6c429687b395a0bc59636f2d6aedf5785098cf4e1f180f1c71"}, + {file = "pymongo-4.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920fee41f7d0259f5f72c1f1eb331bc26ffbdc952846f9bd8c3b119013bb52c"}, + {file = "pymongo-4.10.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0a15665b2d6cf364f4cd114d62452ce01d71abfbd9c564ba8c74dcd7bbd6822"}, + {file = "pymongo-4.10.1-cp310-cp310-win32.whl", hash = "sha256:29e1c323c28a4584b7095378ff046815e39ff82cdb8dc4cc6dfe3acf6f9ad1f8"}, + {file = "pymongo-4.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:88dc4aa45f8744ccfb45164aedb9a4179c93567bbd98a33109d7dc400b00eb08"}, + {file = "pymongo-4.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:57ee6becae534e6d47848c97f6a6dff69e3cce7c70648d6049bd586764febe59"}, + {file = "pymongo-4.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6f437a612f4d4f7aca1812311b1e84477145e950fdafe3285b687ab8c52541f3"}, + {file = "pymongo-4.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a970fd3117ab40a4001c3dad333bbf3c43687d90f35287a6237149b5ccae61d"}, + {file = "pymongo-4.10.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c4d0e7cd08ef9f8fbf2d15ba281ed55604368a32752e476250724c3ce36c72e"}, + {file = "pymongo-4.10.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca6f700cff6833de4872a4e738f43123db34400173558b558ae079b5535857a4"}, + {file = "pymongo-4.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cec237c305fcbeef75c0bcbe9d223d1e22a6e3ba1b53b2f0b79d3d29c742b45b"}, + {file = "pymongo-4.10.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3337804ea0394a06e916add4e5fac1c89902f1b6f33936074a12505cab4ff05"}, + {file = "pymongo-4.10.1-cp311-cp311-win32.whl", hash = "sha256:778ac646ce6ac1e469664062dfe9ae1f5c9961f7790682809f5ec3b8fda29d65"}, + {file = "pymongo-4.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:9df4ab5594fdd208dcba81be815fa8a8a5d8dedaf3b346cbf8b61c7296246a7a"}, + {file = "pymongo-4.10.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fbedc4617faa0edf423621bb0b3b8707836687161210d470e69a4184be9ca011"}, + {file = "pymongo-4.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7bd26b2aec8ceeb95a5d948d5cc0f62b0eb6d66f3f4230705c1e3d3d2c04ec76"}, + {file = "pymongo-4.10.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb104c3c2a78d9d85571c8ac90ec4f95bca9b297c6eee5ada71fabf1129e1674"}, + {file = "pymongo-4.10.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4924355245a9c79f77b5cda2db36e0f75ece5faf9f84d16014c0a297f6d66786"}, + {file = "pymongo-4.10.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:11280809e5dacaef4971113f0b4ff4696ee94cfdb720019ff4fa4f9635138252"}, + {file = "pymongo-4.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5d55f2a82e5eb23795f724991cac2bffbb1c0f219c0ba3bf73a835f97f1bb2e"}, + {file = "pymongo-4.10.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e974ab16a60be71a8dfad4e5afccf8dd05d41c758060f5d5bda9a758605d9a5d"}, + {file = "pymongo-4.10.1-cp312-cp312-win32.whl", hash = "sha256:544890085d9641f271d4f7a47684450ed4a7344d6b72d5968bfae32203b1bb7c"}, + {file = "pymongo-4.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:dcc07b1277e8b4bf4d7382ca133850e323b7ab048b8353af496d050671c7ac52"}, + {file = "pymongo-4.10.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:90bc6912948dfc8c363f4ead54d54a02a15a7fee6cfafb36dc450fc8962d2cb7"}, + {file = "pymongo-4.10.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:594dd721b81f301f33e843453638e02d92f63c198358e5a0fa8b8d0b1218dabc"}, + {file = "pymongo-4.10.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0783e0c8e95397c84e9cf8ab092ab1e5dd7c769aec0ef3a5838ae7173b98dea0"}, + {file = "pymongo-4.10.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6fb6a72e88df46d1c1040fd32cd2d2c5e58722e5d3e31060a0393f04ad3283de"}, + {file = "pymongo-4.10.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e3a593333e20c87415420a4fb76c00b7aae49b6361d2e2205b6fece0563bf40"}, + {file = "pymongo-4.10.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72e2ace7456167c71cfeca7dcb47bd5dceda7db2231265b80fc625c5e8073186"}, + {file = "pymongo-4.10.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ad05eb9c97e4f589ed9e74a00fcaac0d443ccd14f38d1258eb4c39a35dd722b"}, + {file = "pymongo-4.10.1-cp313-cp313-win32.whl", hash = "sha256:ee4c86d8e6872a61f7888fc96577b0ea165eb3bdb0d841962b444fa36001e2bb"}, + {file = "pymongo-4.10.1-cp313-cp313-win_amd64.whl", hash = "sha256:45ee87a4e12337353242bc758accc7fb47a2f2d9ecc0382a61e64c8f01e86708"}, + {file = "pymongo-4.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15b1492cc5c7cd260229590be7218261e81684b8da6d6de2660cf743445500ce"}, + {file = "pymongo-4.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95207503c41b97e7ecc7e596d84a61f441b4935f11aa8332828a754e7ada8c82"}, + {file = "pymongo-4.10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb99f003c720c6d83be02c8f1a7787c22384a8ca9a4181e406174db47a048619"}, + {file = "pymongo-4.10.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f2bc1ee4b1ca2c4e7e6b7a5e892126335ec8d9215bcd3ac2fe075870fefc3358"}, + {file = "pymongo-4.10.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:93a0833c10a967effcd823b4e7445ec491f0bf6da5de0ca33629c0528f42b748"}, + {file = "pymongo-4.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f56707497323150bd2ed5d63067f4ffce940d0549d4ea2dfae180deec7f9363"}, + {file = "pymongo-4.10.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:409ab7d6c4223e5c85881697f365239dd3ed1b58f28e4124b846d9d488c86880"}, + {file = "pymongo-4.10.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dac78a650dc0637d610905fd06b5fa6419ae9028cf4d04d6a2657bc18a66bbce"}, + {file = "pymongo-4.10.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1ec3fa88b541e0481aff3c35194c9fac96e4d57ec5d1c122376000eb28c01431"}, + {file = "pymongo-4.10.1-cp39-cp39-win32.whl", hash = "sha256:e0e961923a7b8a1c801c43552dcb8153e45afa41749d9efbd3a6d33f45489f7a"}, + {file = "pymongo-4.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:dabe8bf1ad644e6b93f3acf90ff18536d94538ca4d27e583c6db49889e98e48f"}, + {file = "pymongo-4.10.1.tar.gz", hash = "sha256:a9de02be53b6bb98efe0b9eda84ffa1ec027fcb23a2de62c4f941d9a2f2f3330"}, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0" diff --git a/madsci/madsci_common/pyproject.toml b/src/madsci_common/pyproject.toml similarity index 97% rename from madsci/madsci_common/pyproject.toml rename to src/madsci_common/pyproject.toml index 0993bcc..62ee38a 100644 --- a/madsci/madsci_common/pyproject.toml +++ b/src/madsci_common/pyproject.toml @@ -19,6 +19,7 @@ dependencies = [ "aenum>=3.1.15", "python-dotenv>=1.0.1", "requests>=2.32.3", + "pymongo>=4.10.1", ] [dependency-groups] diff --git a/src/madsci_event_manager/README.md b/src/madsci_event_manager/README.md new file mode 100644 index 0000000..3d329b1 --- /dev/null +++ b/src/madsci_event_manager/README.md @@ -0,0 +1,45 @@ +# MADSci Event Manager + +Handles distributed logging and events throughout a MADSci-powered Lab. + +## Usage + +## Manager + +To create and run a new MADSci Event Manager, do the following in your MADSci lab directory: + +```bash +# Create an Event Manager Definition +madsci manager add --type event_manager +# Start the Event Manager Server +python -m madsci.event_manager.event_server +``` + +You should see a REST server started on the configured host and port. Navigate in your browser to the URL you configured (default: `http://localhost:8001/`) to see if it's working. + +You can see up-to-date documentation on the endpoints provided by your event manager, and try them out, via the swagger page served at `http://your-event-manager-url-here/docs`. + +## Client + +You can use MADSci's `EventClient` (`madsci.client.event_client.EventClient`) in your python code to log events to the event manager. + +```python +from madsci.client.event_client import EventClient +from madsci.common.types.event_types import Event, EventLogLevel, EventType + +event_client = EventClient( + event_server="https://127.0.0.1:8001", # Update with the host/port you configured for your EventManager server +) + +event_client.log_info("This logs a simple string at the INFO level, with event_type LOG_INFO") +event = Event( + event_type="NODE_CREATE", + log_level=EventLogLevel.DEBUG, + event_data="This logs a more complex NODE_CREATE event at the DEBUG level. The event_data field should contain relevant data about the event (in this case, something like the NodeDefinition, for instance)" +) +event_client.log(event) +event_client.log_warning(event) # Log the same event, but override the log level. + +# Query for recent events +event_client.get_events(number=50) +``` diff --git a/src/madsci_event_manager/madsci/event_manager/event_server.py b/src/madsci_event_manager/madsci/event_manager/event_server.py new file mode 100644 index 0000000..6b498ce --- /dev/null +++ b/src/madsci_event_manager/madsci/event_manager/event_server.py @@ -0,0 +1,107 @@ +"""REST Server for the MADSci Event Manager""" + +from typing import Optional + +import uvicorn +from fastapi import FastAPI +from fastapi.routing import APIRouter +from madsci.client.event_client import EventClient +from madsci.common.definition_loaders import ( + manager_definition_loader, +) +from madsci.common.types.event_types import Event +from madsci.common.types.squid_types import EventManagerDefinition, ManagerType +from pymongo import MongoClient +from pymongo.synchronous.collection import Collection +from pymongo.synchronous.database import Database + + +class EventManagerServer: + """A REST server for managing MADSci events across a lab.""" + + event_manager_definition: EventManagerDefinition + db_client: MongoClient + app = FastAPI() + logger = EventClient(name=__name__) + events: Collection + + def __init__( + self, + event_manager_definition: Optional[EventManagerDefinition] = None, + db_connection: Optional[Database] = None, + ) -> None: + """Initialize the Event Manager Server.""" + if event_manager_definition is not None: + self.event_manager_definition = event_manager_definition + else: + manager_definitions = manager_definition_loader() + for manager in manager_definitions: + if manager.manager_type == ManagerType.EVENT_MANAGER: + self.event_manager_definition = manager + if self.event_manager_definition is None: + raise ValueError( + "No event manager definition found, please specify a path with --definition, or add it to your lab definition's 'managers' section" + ) + + # * Logger + self.logger = EventClient(name=event_manager_definition.name) + self.logger.log_info(event_manager_definition) + + # * DB Config + if db_connection is not None: + self.events_db = db_connection + else: + self.db_client = MongoClient( + self.event_manager_definition.manager_config.db_url + ) + self.events_db = self.db_client["madsci_events"] + self.events = self.events_db["events"] + self.events.create_index("event_id", unique=True, background=True) + + # * REST Server Config + self._configure_routes() + + async def root(self) -> EventManagerDefinition: + """Return the Event Manager Definition""" + return self.event_manager_definition + + async def get_event(self, event_id: str) -> Event: + """Look up an event by event_id""" + return self.events.find_one({"event_id": event_id}) + + async def get_events(self, number: int = 100, level: int = 0) -> dict[str, Event]: + """Get the latest events""" + event_list = ( + self.events.find({"log_level": {"$gte": level}}) + .sort("event_timestamp", -1) + .limit(number) + .to_list() + ) + return {event["event_id"]: event for event in event_list} + + async def create_event(self, event: Event) -> Event: + """Create a new event.""" + self.events.insert_one(event.model_dump(mode="json")) + return event + + def start_server(self) -> None: + """Start the server.""" + uvicorn.run( + self.app, + host=self.event_manager_definition.manager_config.host, + port=self.event_manager_definition.manager_config.port, + ) + + def _configure_routes(self) -> None: + self.router = APIRouter() + self.router.add_api_route("/", self.root, methods=["GET"]) + self.router.add_api_route("/event/{event_id}", self.get_event, methods=["GET"]) + self.router.add_api_route("/event", self.get_events, methods=["GET"]) + self.router.add_api_route("/events", self.get_events, methods=["GET"]) + self.router.add_api_route("/event", self.create_event, methods=["POST"]) + self.app.include_router(self.router) + + +if __name__ == "__main__": + server = EventManagerServer() + server.start_server() diff --git a/src/madsci_event_manager/pdm.lock b/src/madsci_event_manager/pdm.lock new file mode 100644 index 0000000..4b7b0ab --- /dev/null +++ b/src/madsci_event_manager/pdm.lock @@ -0,0 +1,868 @@ +# This file is @generated by PDM. +# It is not intended for manual editing. + +[metadata] +groups = ["default", "dev", "test"] +strategy = ["inherit_metadata"] +lock_version = "4.5.0" +content_hash = "sha256:49a08fce5a420f8a29a7e4811cadea6eba588b419f8354e368aca82f75385f8a" + +[[metadata.targets]] +requires_python = ">=3.9.1" + +[[package]] +name = "aenum" +version = "3.1.15" +summary = "Advanced Enumerations (compatible with Python's stdlib Enum), NamedTuples, and NamedConstants" +groups = ["dev"] +files = [ + {file = "aenum-3.1.15-py3-none-any.whl", hash = "sha256:e0dfaeea4c2bd362144b87377e2c61d91958c5ed0b4daf89cb6f45ae23af6288"}, + {file = "aenum-3.1.15.tar.gz", hash = "sha256:8cbd76cd18c4f870ff39b24284d3ea028fbe8731a58df3aa581e434c575b9559"}, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +requires_python = ">=3.8" +summary = "Reusable constraint types to use with typing.Annotated" +groups = ["dev", "test"] +dependencies = [ + "typing-extensions>=4.0.0; python_version < \"3.9\"", +] +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + +[[package]] +name = "anyio" +version = "4.8.0" +requires_python = ">=3.9" +summary = "High level compatibility layer for multiple asynchronous event loop implementations" +groups = ["test"] +dependencies = [ + "exceptiongroup>=1.0.2; python_version < \"3.11\"", + "idna>=2.8", + "sniffio>=1.1", + "typing-extensions>=4.5; python_version < \"3.13\"", +] +files = [ + {file = "anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a"}, + {file = "anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a"}, +] + +[[package]] +name = "certifi" +version = "2024.12.14" +requires_python = ">=3.6" +summary = "Python package for providing Mozilla's CA Bundle." +groups = ["dev", "test"] +files = [ + {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"}, + {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.0" +requires_python = ">=3.7.0" +summary = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +groups = ["dev"] +files = [ + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, + {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, + {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, +] + +[[package]] +name = "colorama" +version = "0.4.6" +requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +summary = "Cross-platform colored terminal text." +groups = ["test"] +marker = "sys_platform == \"win32\"" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "dnspython" +version = "2.7.0" +requires_python = ">=3.9" +summary = "DNS toolkit" +groups = ["dev", "test"] +files = [ + {file = "dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86"}, + {file = "dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +requires_python = ">=3.7" +summary = "Backport of PEP 654 (exception groups)" +groups = ["test"] +marker = "python_version < \"3.11\"" +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, +] + +[[package]] +name = "filelock" +version = "3.16.1" +requires_python = ">=3.8" +summary = "A platform independent file lock." +groups = ["test"] +files = [ + {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"}, + {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"}, +] + +[[package]] +name = "greenlet" +version = "3.1.1" +requires_python = ">=3.7" +summary = "Lightweight in-process concurrent programming" +groups = ["dev", "test"] +marker = "(platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\") and python_version < \"3.13\"" +files = [ + {file = "greenlet-3.1.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36b89d13c49216cadb828db8dfa6ce86bbbc476a82d3a6c397f0efae0525bdd0"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94b6150a85e1b33b40b1464a3f9988dcc5251d6ed06842abff82e42632fac120"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93147c513fac16385d1036b7e5b102c7fbbdb163d556b791f0f11eada7ba65dc"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da7a9bff22ce038e19bf62c4dd1ec8391062878710ded0a845bcf47cc0200617"}, + {file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b2795058c23988728eec1f36a4e5e4ebad22f8320c85f3587b539b9ac84128d7"}, + {file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ed10eac5830befbdd0c32f83e8aa6288361597550ba669b04c48f0f9a2c843c6"}, + {file = "greenlet-3.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:77c386de38a60d1dfb8e55b8c1101d68c79dfdd25c7095d51fec2dd800892b80"}, + {file = "greenlet-3.1.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2813dc3de8c1ee3f924e4d4227999285fd335d1bcc0d2be6dc3f1f6a318ec1"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a"}, + {file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511"}, + {file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1776fd7f989fc6b8d8c8cb8da1f6b82c5814957264d1f6cf818d475ec2bf6395"}, + {file = "greenlet-3.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:48ca08c771c268a768087b408658e216133aecd835c0ded47ce955381105ba39"}, + {file = "greenlet-3.1.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9"}, + {file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0"}, + {file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942"}, + {file = "greenlet-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01"}, + {file = "greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e"}, + {file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1"}, + {file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c"}, + {file = "greenlet-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822"}, + {file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01"}, + {file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6"}, + {file = "greenlet-3.1.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:396979749bd95f018296af156201d6211240e7a23090f50a8d5d18c370084dc3"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca9d0ff5ad43e785350894d97e13633a66e2b50000e8a183a50a88d834752d42"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f6ff3b14f2df4c41660a7dec01045a045653998784bf8cfcb5a525bdffffbc8f"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94ebba31df2aa506d7b14866fed00ac141a867e63143fe5bca82a8e503b36437"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73aaad12ac0ff500f62cebed98d8789198ea0e6f233421059fa68a5aa7220145"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63e4844797b975b9af3a3fb8f7866ff08775f5426925e1e0bbcfe7932059a12c"}, + {file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7939aa3ca7d2a1593596e7ac6d59391ff30281ef280d8632fa03d81f7c5f955e"}, + {file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d0028e725ee18175c6e422797c407874da24381ce0690d6b9396c204c7f7276e"}, + {file = "greenlet-3.1.1-cp39-cp39-win32.whl", hash = "sha256:5e06afd14cbaf9e00899fae69b24a32f2196c19de08fcb9f4779dd4f004e5e7c"}, + {file = "greenlet-3.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:3319aa75e0e0639bc15ff54ca327e8dc7a6fe404003496e3c6925cd3142e0e22"}, + {file = "greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467"}, +] + +[[package]] +name = "h11" +version = "0.14.0" +requires_python = ">=3.7" +summary = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +groups = ["test"] +dependencies = [ + "typing-extensions; python_version < \"3.8\"", +] +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[[package]] +name = "httpcore" +version = "1.0.7" +requires_python = ">=3.8" +summary = "A minimal low-level HTTP client." +groups = ["test"] +dependencies = [ + "certifi", + "h11<0.15,>=0.13", +] +files = [ + {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"}, + {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"}, +] + +[[package]] +name = "httpx" +version = "0.28.1" +requires_python = ">=3.8" +summary = "The next generation HTTP client." +groups = ["test"] +dependencies = [ + "anyio", + "certifi", + "httpcore==1.*", + "idna", +] +files = [ + {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, + {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, +] + +[[package]] +name = "idna" +version = "3.10" +requires_python = ">=3.6" +summary = "Internationalized Domain Names in Applications (IDNA)" +groups = ["dev", "test"] +files = [ + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +requires_python = ">=3.7" +summary = "brain-dead simple config-ini parsing" +groups = ["test"] +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "madsci-common" +version = "0.1.dev14" +requires_python = ">=3.9.1" +editable = true +path = "../madsci_common" +summary = "The Modular Autonomous Discovery for Science (MADSci) Common Definitions and Utilities." +groups = ["dev"] +dependencies = [ + "PyYAML>=6.0.2", + "aenum>=3.1.15", + "pydantic>=2.9.2", + "pymongo>=4.10.1", + "python-dotenv>=1.0.1", + "python-ulid[pydantic]>=3.0.0", + "requests>=2.32.3", + "sqlmodel>=0.0.22", +] + +[[package]] +name = "packaging" +version = "24.2" +requires_python = ">=3.8" +summary = "Core utilities for Python packages" +groups = ["test"] +files = [ + {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, + {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +requires_python = ">=3.8" +summary = "plugin and hook calling mechanisms for python" +groups = ["test"] +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[[package]] +name = "pydantic" +version = "2.10.3" +requires_python = ">=3.8" +summary = "Data validation using Python type hints" +groups = ["dev", "test"] +dependencies = [ + "annotated-types>=0.6.0", + "pydantic-core==2.27.1", + "typing-extensions>=4.12.2", +] +files = [ + {file = "pydantic-2.10.3-py3-none-any.whl", hash = "sha256:be04d85bbc7b65651c5f8e6b9976ed9c6f41782a55524cef079a34a0bb82144d"}, + {file = "pydantic-2.10.3.tar.gz", hash = "sha256:cb5ac360ce894ceacd69c403187900a02c4b20b693a9dd1d643e1effab9eadf9"}, +] + +[[package]] +name = "pydantic-core" +version = "2.27.1" +requires_python = ">=3.8" +summary = "Core functionality for Pydantic validation and serialization" +groups = ["dev", "test"] +dependencies = [ + "typing-extensions!=4.7.0,>=4.6.0", +] +files = [ + {file = "pydantic_core-2.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:71a5e35c75c021aaf400ac048dacc855f000bdfed91614b4a726f7432f1f3d6a"}, + {file = "pydantic_core-2.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f82d068a2d6ecfc6e054726080af69a6764a10015467d7d7b9f66d6ed5afa23b"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:121ceb0e822f79163dd4699e4c54f5ad38b157084d97b34de8b232bcaad70278"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4603137322c18eaf2e06a4495f426aa8d8388940f3c457e7548145011bb68e05"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a33cd6ad9017bbeaa9ed78a2e0752c5e250eafb9534f308e7a5f7849b0b1bfb4"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15cc53a3179ba0fcefe1e3ae50beb2784dede4003ad2dfd24f81bba4b23a454f"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45d9c5eb9273aa50999ad6adc6be5e0ecea7e09dbd0d31bd0c65a55a2592ca08"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8bf7b66ce12a2ac52d16f776b31d16d91033150266eb796967a7e4621707e4f6"}, + {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:655d7dd86f26cb15ce8a431036f66ce0318648f8853d709b4167786ec2fa4807"}, + {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:5556470f1a2157031e676f776c2bc20acd34c1990ca5f7e56f1ebf938b9ab57c"}, + {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f69ed81ab24d5a3bd93861c8c4436f54afdf8e8cc421562b0c7504cf3be58206"}, + {file = "pydantic_core-2.27.1-cp310-none-win32.whl", hash = "sha256:f5a823165e6d04ccea61a9f0576f345f8ce40ed533013580e087bd4d7442b52c"}, + {file = "pydantic_core-2.27.1-cp310-none-win_amd64.whl", hash = "sha256:57866a76e0b3823e0b56692d1a0bf722bffb324839bb5b7226a7dbd6c9a40b17"}, + {file = "pydantic_core-2.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac3b20653bdbe160febbea8aa6c079d3df19310d50ac314911ed8cc4eb7f8cb8"}, + {file = "pydantic_core-2.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a5a8e19d7c707c4cadb8c18f5f60c843052ae83c20fa7d44f41594c644a1d330"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f7059ca8d64fea7f238994c97d91f75965216bcbe5f695bb44f354893f11d52"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bed0f8a0eeea9fb72937ba118f9db0cb7e90773462af7962d382445f3005e5a4"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3cb37038123447cf0f3ea4c74751f6a9d7afef0eb71aa07bf5f652b5e6a132c"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84286494f6c5d05243456e04223d5a9417d7f443c3b76065e75001beb26f88de"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acc07b2cfc5b835444b44a9956846b578d27beeacd4b52e45489e93276241025"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4fefee876e07a6e9aad7a8c8c9f85b0cdbe7df52b8a9552307b09050f7512c7e"}, + {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:258c57abf1188926c774a4c94dd29237e77eda19462e5bb901d88adcab6af919"}, + {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:35c14ac45fcfdf7167ca76cc80b2001205a8d5d16d80524e13508371fb8cdd9c"}, + {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d1b26e1dff225c31897696cab7d4f0a315d4c0d9e8666dbffdb28216f3b17fdc"}, + {file = "pydantic_core-2.27.1-cp311-none-win32.whl", hash = "sha256:2cdf7d86886bc6982354862204ae3b2f7f96f21a3eb0ba5ca0ac42c7b38598b9"}, + {file = "pydantic_core-2.27.1-cp311-none-win_amd64.whl", hash = "sha256:3af385b0cee8df3746c3f406f38bcbfdc9041b5c2d5ce3e5fc6637256e60bbc5"}, + {file = "pydantic_core-2.27.1-cp311-none-win_arm64.whl", hash = "sha256:81f2ec23ddc1b476ff96563f2e8d723830b06dceae348ce02914a37cb4e74b89"}, + {file = "pydantic_core-2.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9cbd94fc661d2bab2bc702cddd2d3370bbdcc4cd0f8f57488a81bcce90c7a54f"}, + {file = "pydantic_core-2.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f8c4718cd44ec1580e180cb739713ecda2bdee1341084c1467802a417fe0f02"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15aae984e46de8d376df515f00450d1522077254ef6b7ce189b38ecee7c9677c"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ba5e3963344ff25fc8c40da90f44b0afca8cfd89d12964feb79ac1411a260ac"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:992cea5f4f3b29d6b4f7f1726ed8ee46c8331c6b4eed6db5b40134c6fe1768bb"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0325336f348dbee6550d129b1627cb8f5351a9dc91aad141ffb96d4937bd9529"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7597c07fbd11515f654d6ece3d0e4e5093edc30a436c63142d9a4b8e22f19c35"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3bbd5d8cc692616d5ef6fbbbd50dbec142c7e6ad9beb66b78a96e9c16729b089"}, + {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:dc61505e73298a84a2f317255fcc72b710b72980f3a1f670447a21efc88f8381"}, + {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:e1f735dc43da318cad19b4173dd1ffce1d84aafd6c9b782b3abc04a0d5a6f5bb"}, + {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f4e5658dbffe8843a0f12366a4c2d1c316dbe09bb4dfbdc9d2d9cd6031de8aae"}, + {file = "pydantic_core-2.27.1-cp312-none-win32.whl", hash = "sha256:672ebbe820bb37988c4d136eca2652ee114992d5d41c7e4858cdd90ea94ffe5c"}, + {file = "pydantic_core-2.27.1-cp312-none-win_amd64.whl", hash = "sha256:66ff044fd0bb1768688aecbe28b6190f6e799349221fb0de0e6f4048eca14c16"}, + {file = "pydantic_core-2.27.1-cp312-none-win_arm64.whl", hash = "sha256:9a3b0793b1bbfd4146304e23d90045f2a9b5fd5823aa682665fbdaf2a6c28f3e"}, + {file = "pydantic_core-2.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f216dbce0e60e4d03e0c4353c7023b202d95cbaeff12e5fd2e82ea0a66905073"}, + {file = "pydantic_core-2.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a2e02889071850bbfd36b56fd6bc98945e23670773bc7a76657e90e6b6603c08"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42b0e23f119b2b456d07ca91b307ae167cc3f6c846a7b169fca5326e32fdc6cf"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:764be71193f87d460a03f1f7385a82e226639732214b402f9aa61f0d025f0737"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c00666a3bd2f84920a4e94434f5974d7bbc57e461318d6bb34ce9cdbbc1f6b2"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ccaa88b24eebc0f849ce0a4d09e8a408ec5a94afff395eb69baf868f5183107"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c65af9088ac534313e1963443d0ec360bb2b9cba6c2909478d22c2e363d98a51"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:206b5cf6f0c513baffaeae7bd817717140770c74528f3e4c3e1cec7871ddd61a"}, + {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:062f60e512fc7fff8b8a9d680ff0ddaaef0193dba9fa83e679c0c5f5fbd018bc"}, + {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:a0697803ed7d4af5e4c1adf1670af078f8fcab7a86350e969f454daf598c4960"}, + {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:58ca98a950171f3151c603aeea9303ef6c235f692fe555e883591103da709b23"}, + {file = "pydantic_core-2.27.1-cp313-none-win32.whl", hash = "sha256:8065914ff79f7eab1599bd80406681f0ad08f8e47c880f17b416c9f8f7a26d05"}, + {file = "pydantic_core-2.27.1-cp313-none-win_amd64.whl", hash = "sha256:ba630d5e3db74c79300d9a5bdaaf6200172b107f263c98a0539eeecb857b2337"}, + {file = "pydantic_core-2.27.1-cp313-none-win_arm64.whl", hash = "sha256:45cf8588c066860b623cd11c4ba687f8d7175d5f7ef65f7129df8a394c502de5"}, + {file = "pydantic_core-2.27.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:e9386266798d64eeb19dd3677051f5705bf873e98e15897ddb7d76f477131967"}, + {file = "pydantic_core-2.27.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4228b5b646caa73f119b1ae756216b59cc6e2267201c27d3912b592c5e323b60"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b3dfe500de26c52abe0477dde16192ac39c98f05bf2d80e76102d394bd13854"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aee66be87825cdf72ac64cb03ad4c15ffef4143dbf5c113f64a5ff4f81477bf9"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b748c44bb9f53031c8cbc99a8a061bc181c1000c60a30f55393b6e9c45cc5bd"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ca038c7f6a0afd0b2448941b6ef9d5e1949e999f9e5517692eb6da58e9d44be"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e0bd57539da59a3e4671b90a502da9a28c72322a4f17866ba3ac63a82c4498e"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ac6c2c45c847bbf8f91930d88716a0fb924b51e0c6dad329b793d670ec5db792"}, + {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b94d4ba43739bbe8b0ce4262bcc3b7b9f31459ad120fb595627eaeb7f9b9ca01"}, + {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:00e6424f4b26fe82d44577b4c842d7df97c20be6439e8e685d0d715feceb9fb9"}, + {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:38de0a70160dd97540335b7ad3a74571b24f1dc3ed33f815f0880682e6880131"}, + {file = "pydantic_core-2.27.1-cp39-none-win32.whl", hash = "sha256:7ccebf51efc61634f6c2344da73e366c75e735960b5654b63d7e6f69a5885fa3"}, + {file = "pydantic_core-2.27.1-cp39-none-win_amd64.whl", hash = "sha256:a57847b090d7892f123726202b7daa20df6694cbd583b67a592e856bff603d6c"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3fa80ac2bd5856580e242dbc202db873c60a01b20309c8319b5c5986fbe53ce6"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d950caa237bb1954f1b8c9227b5065ba6875ac9771bb8ec790d956a699b78676"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e4216e64d203e39c62df627aa882f02a2438d18a5f21d7f721621f7a5d3611d"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02a3d637bd387c41d46b002f0e49c52642281edacd2740e5a42f7017feea3f2c"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:161c27ccce13b6b0c8689418da3885d3220ed2eae2ea5e9b2f7f3d48f1d52c27"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:19910754e4cc9c63bc1c7f6d73aa1cfee82f42007e407c0f413695c2f7ed777f"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:e173486019cc283dc9778315fa29a363579372fe67045e971e89b6365cc035ed"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:af52d26579b308921b73b956153066481f064875140ccd1dfd4e77db89dbb12f"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:981fb88516bd1ae8b0cbbd2034678a39dedc98752f264ac9bc5839d3923fa04c"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5fde892e6c697ce3e30c61b239330fc5d569a71fefd4eb6512fc6caec9dd9e2f"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:816f5aa087094099fff7edabb5e01cc370eb21aa1a1d44fe2d2aefdfb5599b31"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c10c309e18e443ddb108f0ef64e8729363adbfd92d6d57beec680f6261556f3"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98476c98b02c8e9b2eec76ac4156fd006628b1b2d0ef27e548ffa978393fd154"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c3027001c28434e7ca5a6e1e527487051136aa81803ac812be51802150d880dd"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:7699b1df36a48169cdebda7ab5a2bac265204003f153b4bd17276153d997670a"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1c39b07d90be6b48968ddc8c19e7585052088fd7ec8d568bb31ff64c70ae3c97"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:46ccfe3032b3915586e469d4972973f893c0a2bb65669194a5bdea9bacc088c2"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:62ba45e21cf6571d7f716d903b5b7b6d2617e2d5d67c0923dc47b9d41369f840"}, + {file = "pydantic_core-2.27.1.tar.gz", hash = "sha256:62a763352879b84aa31058fc931884055fd75089cccbd9d58bb6afd01141b235"}, +] + +[[package]] +name = "pymongo" +version = "4.10.1" +requires_python = ">=3.8" +summary = "Python driver for MongoDB " +groups = ["dev", "test"] +dependencies = [ + "dnspython<3.0.0,>=1.16.0", +] +files = [ + {file = "pymongo-4.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e699aa68c4a7dea2ab5a27067f7d3e08555f8d2c0dc6a0c8c60cfd9ff2e6a4b1"}, + {file = "pymongo-4.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:70645abc714f06b4ad6b72d5bf73792eaad14e3a2cfe29c62a9c81ada69d9e4b"}, + {file = "pymongo-4.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae2fd94c9fe048c94838badcc6e992d033cb9473eb31e5710b3707cba5e8aee2"}, + {file = "pymongo-4.10.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ded27a4a5374dae03a92e084a60cdbcecd595306555bda553b833baf3fc4868"}, + {file = "pymongo-4.10.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ecc2455e3974a6c429687b395a0bc59636f2d6aedf5785098cf4e1f180f1c71"}, + {file = "pymongo-4.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920fee41f7d0259f5f72c1f1eb331bc26ffbdc952846f9bd8c3b119013bb52c"}, + {file = "pymongo-4.10.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0a15665b2d6cf364f4cd114d62452ce01d71abfbd9c564ba8c74dcd7bbd6822"}, + {file = "pymongo-4.10.1-cp310-cp310-win32.whl", hash = "sha256:29e1c323c28a4584b7095378ff046815e39ff82cdb8dc4cc6dfe3acf6f9ad1f8"}, + {file = "pymongo-4.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:88dc4aa45f8744ccfb45164aedb9a4179c93567bbd98a33109d7dc400b00eb08"}, + {file = "pymongo-4.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:57ee6becae534e6d47848c97f6a6dff69e3cce7c70648d6049bd586764febe59"}, + {file = "pymongo-4.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6f437a612f4d4f7aca1812311b1e84477145e950fdafe3285b687ab8c52541f3"}, + {file = "pymongo-4.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a970fd3117ab40a4001c3dad333bbf3c43687d90f35287a6237149b5ccae61d"}, + {file = "pymongo-4.10.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c4d0e7cd08ef9f8fbf2d15ba281ed55604368a32752e476250724c3ce36c72e"}, + {file = "pymongo-4.10.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca6f700cff6833de4872a4e738f43123db34400173558b558ae079b5535857a4"}, + {file = "pymongo-4.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cec237c305fcbeef75c0bcbe9d223d1e22a6e3ba1b53b2f0b79d3d29c742b45b"}, + {file = "pymongo-4.10.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3337804ea0394a06e916add4e5fac1c89902f1b6f33936074a12505cab4ff05"}, + {file = "pymongo-4.10.1-cp311-cp311-win32.whl", hash = "sha256:778ac646ce6ac1e469664062dfe9ae1f5c9961f7790682809f5ec3b8fda29d65"}, + {file = "pymongo-4.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:9df4ab5594fdd208dcba81be815fa8a8a5d8dedaf3b346cbf8b61c7296246a7a"}, + {file = "pymongo-4.10.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fbedc4617faa0edf423621bb0b3b8707836687161210d470e69a4184be9ca011"}, + {file = "pymongo-4.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7bd26b2aec8ceeb95a5d948d5cc0f62b0eb6d66f3f4230705c1e3d3d2c04ec76"}, + {file = "pymongo-4.10.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb104c3c2a78d9d85571c8ac90ec4f95bca9b297c6eee5ada71fabf1129e1674"}, + {file = "pymongo-4.10.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4924355245a9c79f77b5cda2db36e0f75ece5faf9f84d16014c0a297f6d66786"}, + {file = "pymongo-4.10.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:11280809e5dacaef4971113f0b4ff4696ee94cfdb720019ff4fa4f9635138252"}, + {file = "pymongo-4.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5d55f2a82e5eb23795f724991cac2bffbb1c0f219c0ba3bf73a835f97f1bb2e"}, + {file = "pymongo-4.10.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e974ab16a60be71a8dfad4e5afccf8dd05d41c758060f5d5bda9a758605d9a5d"}, + {file = "pymongo-4.10.1-cp312-cp312-win32.whl", hash = "sha256:544890085d9641f271d4f7a47684450ed4a7344d6b72d5968bfae32203b1bb7c"}, + {file = "pymongo-4.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:dcc07b1277e8b4bf4d7382ca133850e323b7ab048b8353af496d050671c7ac52"}, + {file = "pymongo-4.10.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:90bc6912948dfc8c363f4ead54d54a02a15a7fee6cfafb36dc450fc8962d2cb7"}, + {file = "pymongo-4.10.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:594dd721b81f301f33e843453638e02d92f63c198358e5a0fa8b8d0b1218dabc"}, + {file = "pymongo-4.10.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0783e0c8e95397c84e9cf8ab092ab1e5dd7c769aec0ef3a5838ae7173b98dea0"}, + {file = "pymongo-4.10.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6fb6a72e88df46d1c1040fd32cd2d2c5e58722e5d3e31060a0393f04ad3283de"}, + {file = "pymongo-4.10.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e3a593333e20c87415420a4fb76c00b7aae49b6361d2e2205b6fece0563bf40"}, + {file = "pymongo-4.10.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72e2ace7456167c71cfeca7dcb47bd5dceda7db2231265b80fc625c5e8073186"}, + {file = "pymongo-4.10.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ad05eb9c97e4f589ed9e74a00fcaac0d443ccd14f38d1258eb4c39a35dd722b"}, + {file = "pymongo-4.10.1-cp313-cp313-win32.whl", hash = "sha256:ee4c86d8e6872a61f7888fc96577b0ea165eb3bdb0d841962b444fa36001e2bb"}, + {file = "pymongo-4.10.1-cp313-cp313-win_amd64.whl", hash = "sha256:45ee87a4e12337353242bc758accc7fb47a2f2d9ecc0382a61e64c8f01e86708"}, + {file = "pymongo-4.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15b1492cc5c7cd260229590be7218261e81684b8da6d6de2660cf743445500ce"}, + {file = "pymongo-4.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95207503c41b97e7ecc7e596d84a61f441b4935f11aa8332828a754e7ada8c82"}, + {file = "pymongo-4.10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb99f003c720c6d83be02c8f1a7787c22384a8ca9a4181e406174db47a048619"}, + {file = "pymongo-4.10.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f2bc1ee4b1ca2c4e7e6b7a5e892126335ec8d9215bcd3ac2fe075870fefc3358"}, + {file = "pymongo-4.10.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:93a0833c10a967effcd823b4e7445ec491f0bf6da5de0ca33629c0528f42b748"}, + {file = "pymongo-4.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f56707497323150bd2ed5d63067f4ffce940d0549d4ea2dfae180deec7f9363"}, + {file = "pymongo-4.10.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:409ab7d6c4223e5c85881697f365239dd3ed1b58f28e4124b846d9d488c86880"}, + {file = "pymongo-4.10.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dac78a650dc0637d610905fd06b5fa6419ae9028cf4d04d6a2657bc18a66bbce"}, + {file = "pymongo-4.10.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1ec3fa88b541e0481aff3c35194c9fac96e4d57ec5d1c122376000eb28c01431"}, + {file = "pymongo-4.10.1-cp39-cp39-win32.whl", hash = "sha256:e0e961923a7b8a1c801c43552dcb8153e45afa41749d9efbd3a6d33f45489f7a"}, + {file = "pymongo-4.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:dabe8bf1ad644e6b93f3acf90ff18536d94538ca4d27e583c6db49889e98e48f"}, + {file = "pymongo-4.10.1.tar.gz", hash = "sha256:a9de02be53b6bb98efe0b9eda84ffa1ec027fcb23a2de62c4f941d9a2f2f3330"}, +] + +[[package]] +name = "pytest" +version = "8.3.4" +requires_python = ">=3.8" +summary = "pytest: simple powerful testing with Python" +groups = ["test"] +dependencies = [ + "colorama; sys_platform == \"win32\"", + "exceptiongroup>=1.0.0rc8; python_version < \"3.11\"", + "iniconfig", + "packaging", + "pluggy<2,>=1.5", + "tomli>=1; python_version < \"3.11\"", +] +files = [ + {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, + {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, +] + +[[package]] +name = "pytest-mock-resources" +version = "2.12.1" +requires_python = "<4,>=3.7" +summary = "A pytest plugin for easily instantiating reproducible mock resources." +groups = ["test"] +dependencies = [ + "importlib-metadata; python_version < \"3.8\"", + "pytest>=1.0", + "sqlalchemy!=1.4.0,!=1.4.1,!=1.4.10,!=1.4.11,!=1.4.12,!=1.4.13,!=1.4.14,!=1.4.15,!=1.4.16,!=1.4.17,!=1.4.18,!=1.4.19,!=1.4.2,!=1.4.20,!=1.4.21,!=1.4.22,!=1.4.23,!=1.4.3,!=1.4.4,!=1.4.5,!=1.4.6,!=1.4.7,!=1.4.8,!=1.4.9,>1.0", + "typing-extensions", +] +files = [ + {file = "pytest_mock_resources-2.12.1-py3-none-any.whl", hash = "sha256:788d9b72d9155d3cd49c281da26527e23ca3511136b1594bbc7ee95ef19be236"}, + {file = "pytest_mock_resources-2.12.1.tar.gz", hash = "sha256:f95c231e5dd957b5f48caf60141a43f57bbe7182d6e4d09a806a78e8d060f9b3"}, +] + +[[package]] +name = "pytest-mock-resources" +version = "2.12.1" +extras = ["mongo"] +requires_python = "<4,>=3.7" +summary = "A pytest plugin for easily instantiating reproducible mock resources." +groups = ["test"] +dependencies = [ + "filelock", + "pymongo", + "pytest-mock-resources==2.12.1", + "python-on-whales>=0.22.0", +] +files = [ + {file = "pytest_mock_resources-2.12.1-py3-none-any.whl", hash = "sha256:788d9b72d9155d3cd49c281da26527e23ca3511136b1594bbc7ee95ef19be236"}, + {file = "pytest_mock_resources-2.12.1.tar.gz", hash = "sha256:f95c231e5dd957b5f48caf60141a43f57bbe7182d6e4d09a806a78e8d060f9b3"}, +] + +[[package]] +name = "python-dotenv" +version = "1.0.1" +requires_python = ">=3.8" +summary = "Read key-value pairs from a .env file and set them as environment variables" +groups = ["dev"] +files = [ + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, +] + +[[package]] +name = "python-on-whales" +version = "0.75.1" +requires_python = "<4,>=3.8" +summary = "A Docker client for Python, designed to be fun and intuitive!" +groups = ["test"] +dependencies = [ + "pydantic!=2.0.*,<3,>=2", + "typing-extensions", +] +files = [ + {file = "python_on_whales-0.75.1-py3-none-any.whl", hash = "sha256:67b1779c44200d0f832c5dea43b7c147c03546aaa02eacce6f375c821c0498e9"}, + {file = "python_on_whales-0.75.1.tar.gz", hash = "sha256:ad192482d79edc81fb4ff9dde84e9188d5b8b181a5e46d54d40afcd6c9ad90e6"}, +] + +[[package]] +name = "python-ulid" +version = "3.0.0" +requires_python = ">=3.9" +summary = "Universally unique lexicographically sortable identifier" +groups = ["dev"] +files = [ + {file = "python_ulid-3.0.0-py3-none-any.whl", hash = "sha256:e4c4942ff50dbd79167ad01ac725ec58f924b4018025ce22c858bfcff99a5e31"}, + {file = "python_ulid-3.0.0.tar.gz", hash = "sha256:e50296a47dc8209d28629a22fc81ca26c00982c78934bd7766377ba37ea49a9f"}, +] + +[[package]] +name = "python-ulid" +version = "3.0.0" +extras = ["pydantic"] +requires_python = ">=3.9" +summary = "Universally unique lexicographically sortable identifier" +groups = ["dev"] +dependencies = [ + "pydantic>=2.0", + "python-ulid==3.0.0", +] +files = [ + {file = "python_ulid-3.0.0-py3-none-any.whl", hash = "sha256:e4c4942ff50dbd79167ad01ac725ec58f924b4018025ce22c858bfcff99a5e31"}, + {file = "python_ulid-3.0.0.tar.gz", hash = "sha256:e50296a47dc8209d28629a22fc81ca26c00982c78934bd7766377ba37ea49a9f"}, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +requires_python = ">=3.8" +summary = "YAML parser and emitter for Python" +groups = ["dev"] +files = [ + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, +] + +[[package]] +name = "requests" +version = "2.32.3" +requires_python = ">=3.8" +summary = "Python HTTP for Humans." +groups = ["dev"] +dependencies = [ + "certifi>=2017.4.17", + "charset-normalizer<4,>=2", + "idna<4,>=2.5", + "urllib3<3,>=1.21.1", +] +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +requires_python = ">=3.7" +summary = "Sniff out which async library your code is running under" +groups = ["test"] +files = [ + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.36" +requires_python = ">=3.7" +summary = "Database Abstraction Library" +groups = ["dev", "test"] +dependencies = [ + "greenlet!=0.4.17; (platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\") and python_version < \"3.13\"", + "importlib-metadata; python_version < \"3.8\"", + "typing-extensions>=4.6.0", +] +files = [ + {file = "SQLAlchemy-2.0.36-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:59b8f3adb3971929a3e660337f5dacc5942c2cdb760afcabb2614ffbda9f9f72"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37350015056a553e442ff672c2d20e6f4b6d0b2495691fa239d8aa18bb3bc908"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8318f4776c85abc3f40ab185e388bee7a6ea99e7fa3a30686580b209eaa35c08"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c245b1fbade9c35e5bd3b64270ab49ce990369018289ecfde3f9c318411aaa07"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:69f93723edbca7342624d09f6704e7126b152eaed3cdbb634cb657a54332a3c5"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f9511d8dd4a6e9271d07d150fb2f81874a3c8c95e11ff9af3a2dfc35fe42ee44"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-win32.whl", hash = "sha256:c3f3631693003d8e585d4200730616b78fafd5a01ef8b698f6967da5c605b3fa"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-win_amd64.whl", hash = "sha256:a86bfab2ef46d63300c0f06936bd6e6c0105faa11d509083ba8f2f9d237fb5b5"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fd3a55deef00f689ce931d4d1b23fa9f04c880a48ee97af488fd215cf24e2a6c"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f5e9cd989b45b73bd359f693b935364f7e1f79486e29015813c338450aa5a71"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0ddd9db6e59c44875211bc4c7953a9f6638b937b0a88ae6d09eb46cced54eff"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2519f3a5d0517fc159afab1015e54bb81b4406c278749779be57a569d8d1bb0d"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59b1ee96617135f6e1d6f275bbe988f419c5178016f3d41d3c0abb0c819f75bb"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:39769a115f730d683b0eb7b694db9789267bcd027326cccc3125e862eb03bfd8"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-win32.whl", hash = "sha256:66bffbad8d6271bb1cc2f9a4ea4f86f80fe5e2e3e501a5ae2a3dc6a76e604e6f"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-win_amd64.whl", hash = "sha256:23623166bfefe1487d81b698c423f8678e80df8b54614c2bf4b4cfcd7c711959"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7b64e6ec3f02c35647be6b4851008b26cff592a95ecb13b6788a54ef80bbdd4"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:46331b00096a6db1fdc052d55b101dbbfc99155a548e20a0e4a8e5e4d1362855"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdf3386a801ea5aba17c6410dd1dc8d39cf454ca2565541b5ac42a84e1e28f53"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac9dfa18ff2a67b09b372d5db8743c27966abf0e5344c555d86cc7199f7ad83a"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:90812a8933df713fdf748b355527e3af257a11e415b613dd794512461eb8a686"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1bc330d9d29c7f06f003ab10e1eaced295e87940405afe1b110f2eb93a233588"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-win32.whl", hash = "sha256:79d2e78abc26d871875b419e1fd3c0bca31a1cb0043277d0d850014599626c2e"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-win_amd64.whl", hash = "sha256:b544ad1935a8541d177cb402948b94e871067656b3a0b9e91dbec136b06a2ff5"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b5cc79df7f4bc3d11e4b542596c03826063092611e481fcf1c9dfee3c94355ef"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3c01117dd36800f2ecaa238c65365b7b16497adc1522bf84906e5710ee9ba0e8"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bc633f4ee4b4c46e7adcb3a9b5ec083bf1d9a97c1d3854b92749d935de40b9b"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e46ed38affdfc95d2c958de328d037d87801cfcbea6d421000859e9789e61c2"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b2985c0b06e989c043f1dc09d4fe89e1616aadd35392aea2844f0458a989eacf"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a121d62ebe7d26fec9155f83f8be5189ef1405f5973ea4874a26fab9f1e262c"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-win32.whl", hash = "sha256:0572f4bd6f94752167adfd7c1bed84f4b240ee6203a95e05d1e208d488d0d436"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-win_amd64.whl", hash = "sha256:8c78ac40bde930c60e0f78b3cd184c580f89456dd87fc08f9e3ee3ce8765ce88"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dc022184d3e5cacc9579e41805a681187650e170eb2fd70e28b86192a479dcaa"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b817d41d692bf286abc181f8af476c4fbef3fd05e798777492618378448ee689"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4e46a888b54be23d03a89be510f24a7652fe6ff660787b96cd0e57a4ebcb46d"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4ae3005ed83f5967f961fd091f2f8c5329161f69ce8480aa8168b2d7fe37f06"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:03e08af7a5f9386a43919eda9de33ffda16b44eb11f3b313e6822243770e9763"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3dbb986bad3ed5ceaf090200eba750b5245150bd97d3e67343a3cfed06feecf7"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-win32.whl", hash = "sha256:9fe53b404f24789b5ea9003fc25b9a3988feddebd7e7b369c8fac27ad6f52f28"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-win_amd64.whl", hash = "sha256:af148a33ff0349f53512a049c6406923e4e02bf2f26c5fb285f143faf4f0e46a"}, + {file = "SQLAlchemy-2.0.36-py3-none-any.whl", hash = "sha256:fddbe92b4760c6f5d48162aef14824add991aeda8ddadb3c31d56eb15ca69f8e"}, + {file = "sqlalchemy-2.0.36.tar.gz", hash = "sha256:7f2767680b6d2398aea7082e45a774b2b0767b5c8d8ffb9c8b683088ea9b29c5"}, +] + +[[package]] +name = "sqlmodel" +version = "0.0.22" +requires_python = ">=3.7" +summary = "SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness." +groups = ["dev"] +dependencies = [ + "SQLAlchemy<2.1.0,>=2.0.14", + "pydantic<3.0.0,>=1.10.13", +] +files = [ + {file = "sqlmodel-0.0.22-py3-none-any.whl", hash = "sha256:a1ed13e28a1f4057cbf4ff6cdb4fc09e85702621d3259ba17b3c230bfb2f941b"}, + {file = "sqlmodel-0.0.22.tar.gz", hash = "sha256:7d37c882a30c43464d143e35e9ecaf945d88035e20117bf5ec2834a23cbe505e"}, +] + +[[package]] +name = "tomli" +version = "2.2.1" +requires_python = ">=3.8" +summary = "A lil' TOML parser" +groups = ["test"] +marker = "python_version < \"3.11\"" +files = [ + {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, + {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, + {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, + {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, + {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, + {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, + {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, + {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, + {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +requires_python = ">=3.8" +summary = "Backported and Experimental Type Hints for Python 3.8+" +groups = ["dev", "test"] +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + +[[package]] +name = "urllib3" +version = "2.2.3" +requires_python = ">=3.8" +summary = "HTTP library with thread-safe connection pooling, file post, and more." +groups = ["dev"] +files = [ + {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, + {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, +] diff --git a/src/madsci_event_manager/pyproject.toml b/src/madsci_event_manager/pyproject.toml new file mode 100644 index 0000000..9f35f19 --- /dev/null +++ b/src/madsci_event_manager/pyproject.toml @@ -0,0 +1,41 @@ +[project] +name = "madsci.event_manager" +dynamic = ["version"] +description = "The Modular Autonomous Discovery for Science (MADSci) Event and Logging Manager." +authors = [ + {name = "Tobias Ginsburg", email = "tginsburg@anl.gov"}, + {name = "Ryan D. Lewis", email = "ryan.lewis@anl.gov"}, + {name = "Casey Stone", email = "cstone@anl.gov"}, + {name = "Doga Ozgulbas", email = "dozgulbas@anl.gov"}, +] +requires-python = ">=3.9.1" +readme = "README.md" +license = {text = "MIT"} +dependencies = [ + "madsci.common[server]" +] + +[project.urls] +Homepage = "https://github.com/AD-SDL/MADSci" + + +###################### +# Build Info + Tools # +###################### + +[project.optional-dependencies] +test = [ + "httpx>=0.28.1", + "pytest>=8.3.4", + "pytest-mock-resources[mongo]>=2.12.1", +] +[build-system] +requires = ["pdm-backend"] +build-backend = "pdm.backend" + +[tool.pdm.version] +source = "scm" +fallback_version = "0.0.0" + +#[dependency-groups] +#dev = ["-e madsci-common @ file:///${PROJECT_ROOT}/../madsci_common"] diff --git a/src/madsci_event_manager/tests/test_event_server.py b/src/madsci_event_manager/tests/test_event_server.py new file mode 100644 index 0000000..0e70116 --- /dev/null +++ b/src/madsci_event_manager/tests/test_event_server.py @@ -0,0 +1,80 @@ +""" +Test the Event Manager's REST server. + +Uses pytest-mock-resources to create a MongoDB fixture. Note that this _requires_ +a working docker installation. +""" + +from fastapi.testclient import TestClient +from madsci.common.types.event_types import Event, EventType +from madsci.common.types.squid_types import EventManagerDefinition +from madsci.event_manager.event_server import EventManagerServer +from pymongo.synchronous.database import Database +from pytest_mock_resources import create_mongo_fixture + +db_connection = create_mongo_fixture() + +event_manager_def = EventManagerDefinition( + name="test_event_manager", +) + + +def test_root(db_connection: Database) -> None: + """ + Test the root endpoint for the Event_Manager's server. + Should return an EventManagerDefinition. + """ + event_manager_server = EventManagerServer( + event_manager_definition=event_manager_def, db_connection=db_connection + ) + event_manager_server._configure_routes() + test_client = TestClient(event_manager_server.app) + result = test_client.get("/").json() + EventManagerDefinition.model_validate(result) + + +def test_roundtrip_event(db_connection: Database) -> None: + """ + Test that we can send and then retrieve an event by ID. + """ + event_manager_server = EventManagerServer( + event_manager_definition=event_manager_def, db_connection=db_connection + ) + event_manager_server._configure_routes() + test_client = TestClient(event_manager_server.app) + test_event = Event( + event_type=EventType.TEST, + event_data={"test": "data"}, + ) + result = test_client.post("/event", json=test_event.model_dump(mode="json")).json() + assert Event.model_validate(result) == test_event + result = test_client.get(f"/event/{test_event.event_id}").json() + assert Event.model_validate(result) == test_event + + +def test_get_events(db_connection: Database) -> None: + """ + Test that we can retrieve all events and they are returned as a dictionary in reverse-chronological order, with the correct number of events. + """ + event_manager_server = EventManagerServer( + event_manager_definition=event_manager_def, db_connection=db_connection + ) + event_manager_server._configure_routes() + test_client = TestClient(event_manager_server.app) + for i in range(10): + test_event = Event( + event_type=EventType.TEST, + event_data={"test": i}, + ) + test_client.post("/event", json=test_event.model_dump(mode="json")) + query_number = 5 + result = test_client.get("/events", params={"number": query_number}).json() + # * Check that the number of events returned is correct + assert len(result) == query_number + previous_timestamp = float("inf") + for _, value in result.items(): + event = Event.model_validate(value) + # * Check that the events are in reverse-chronological order + assert event.event_data["test"] in range(5, 10) + assert previous_timestamp >= event.event_timestamp.timestamp() + previous_timestamp = event.event_timestamp.timestamp() diff --git a/madsci/madsci_module/README.md b/src/madsci_node_module/README.md similarity index 100% rename from madsci/madsci_module/README.md rename to src/madsci_node_module/README.md diff --git a/madsci/madsci_module/madsci/module/__init__.py b/src/madsci_node_module/madsci/node_module/__init__.py similarity index 100% rename from madsci/madsci_module/madsci/module/__init__.py rename to src/madsci_node_module/madsci/node_module/__init__.py diff --git a/madsci/madsci_module/madsci/module/abstract_module.py b/src/madsci_node_module/madsci/node_module/abstract_node_module.py similarity index 88% rename from madsci/madsci_module/madsci/module/abstract_module.py rename to src/madsci_node_module/madsci/node_module/abstract_node_module.py index 9f03701..26301cc 100644 --- a/madsci/madsci_module/madsci/module/abstract_module.py +++ b/src/madsci_node_module/madsci/node_module/abstract_node_module.py @@ -7,13 +7,10 @@ from pathlib import Path from typing import Any, Callable, ClassVar, Optional, Union, get_type_hints -from pydantic import ValidationError -from rich import print - +from madsci.client.event_client import EventClient from madsci.common.definition_loaders import ( node_definition_loader, ) -from madsci.common.events import MADSciEventLogger from madsci.common.exceptions import ( ActionMissingArgumentError, ActionMissingFileError, @@ -28,19 +25,23 @@ ActionStatus, ) from madsci.common.types.admin_command_types import AdminCommandResponse +from madsci.common.types.auth_types import OwnershipInfo from madsci.common.types.base_types import Error -from madsci.common.types.event_types import Event -from madsci.common.types.module_types import ( - AdminCommands, - NodeModuleDefinition, +from madsci.common.types.config_types import ( + ConfigNamespaceDefinition, + ConfigParameterDefinition, ) +from madsci.common.types.event_types import Event, EventLogLevel, EventType from madsci.common.types.node_types import ( + AdminCommands, NodeDefinition, NodeInfo, + NodeModuleDefinition, NodeSetConfigResponse, NodeStatus, ) from madsci.common.utils import pretty_type_repr, threaded_daemon, threaded_task +from pydantic import ValidationError def action( @@ -93,7 +94,7 @@ class AbstractNode: """The interval at which the state handler is called. Overridable by config.""" node_info_path: ClassVar[Optional[Path]] = None """The path to the node info file. If unset, defaults to '.info.yaml'""" - logger: ClassVar[MADSciEventLogger] = MADSciEventLogger() + logger: ClassVar[EventClient] = EventClient() """The event logger for this node""" def __init__(self) -> "AbstractNode": @@ -108,6 +109,8 @@ def __init__(self) -> "AbstractNode": "Module definition not found, aborting node initialization", ) + self._configure_events() + # * Synthesize the node info self.node_info = NodeInfo.from_node_and_module( self.node_definition, @@ -156,29 +159,27 @@ def start_node(self, config: dict[str, Any] = {}) -> None: if self.module_definition._definition_path: self.module_definition.to_yaml(self.module_definition._definition_path) else: - print( + self.logger.log_warning( "No definition path set for module, skipping module definition update", ) if self.node_definition._definition_path: self.node_definition.to_yaml(self.node_definition._definition_path) else: - print("No definition path set for node, skipping node definition update") + self.logger.log_warning( + "No definition path set for node, skipping node definition update" + ) # *Check for any required config parameters that weren't set self.config = {**self.config, **config} - for config_value in self.node_definition.config.values(): - if ( - config_value.required - and ( - config_value.name not in self.config - or self.config[config_value.name] is None - ) - and config_value.default is None - ): - print(f"Required config parameter '{config_value.name}' not set") - self.node_status.waiting_for_config.add(config_value.name) - else: - self.node_status.waiting_for_config.discard(config_value.name) + for param in self.node_definition.config.values(): + self._check_required_config_parameter(param) + + # * Update EventClient with logging parameters + self._configure_events() + + # * Log startup info + self.logger.log_info(f"{self.node_definition=}") + self.logger.log_info(f"{self.module_definition=}") def status_handler(self) -> None: """Called periodically to update the node status. Should set `self.node_status`""" @@ -250,27 +251,20 @@ def get_status(self) -> NodeStatus: def set_config(self, new_config: dict[str, Any]) -> NodeSetConfigResponse: """Set configuration values of the module.""" - need_reset = False errors = [] - for config_key, config_value in new_config.items(): - try: - if config_key in self.node_definition.config: - self.config[config_key] = config_value + + def update_recursively(old_dict: dict, new_dict: dict) -> None: + for key, value in new_dict.items(): + if isinstance(value, dict): + update_recursively(old_dict[key], value) else: - raise ValueError(f"Invalid config parameter: {config_key}") - if self.node_definition.config[config_key].reset_on_change: - need_reset = True - except Exception as e: - errors.append(Error.from_exception(e)) + old_dict[key] = value + + update_recursively(self.config, new_config) # *Check if all required parameters are set for param in self.node_definition.config.values(): - if param.required and ( - param.name not in self.config or self.config[param.name] is None - ): - self.node_status.waiting_for_config.add(param.name) - else: - self.node_status.waiting_for_config.discard(param.name) - if need_reset and hasattr(self, "reset"): + self._check_required_config_parameter(param) + if hasattr(self, "reset"): # * Reset after a short delay to allow the response to be returned @threaded_task def schedule_reset() -> None: @@ -340,6 +334,21 @@ def get_log(self) -> list[Event]: """Internal and Private Methods""" """------------------------------------------------------------------------------------------------""" + def _configure_events(self) -> None: + """Configure the event logger.""" + if (madsci_event_config := self.config.get("madsci_events", None)) is not None: + event_server = madsci_event_config.get("event_server", None) + log_level = madsci_event_config.get("log_level", EventLogLevel.INFO) + else: + event_server = None + log_level = EventLogLevel.INFO + self.logger = EventClient( + name=f"node.{self.node_definition.node_name}", + source=OwnershipInfo(node_id=self.node_definition.node_id), + event_server=event_server, + log_level=log_level, + ) + def _add_action( self, func: Callable, @@ -550,8 +559,12 @@ def _run_action( def _exception_handler(self, e: Exception, set_node_error: bool = True) -> None: """Handle an exception.""" self.node_status.errored = set_node_error - self.node_status.errors.append(Error.from_exception(e)) - traceback.print_exc() + madsci_error = Error.from_exception(e) + self.node_status.errors.append(madsci_error) + self.logger.log_error( + Event(event_type=EventType.NODE_ERROR, event_data=madsci_error) + ) + self.logger.log_error(traceback.format_exc()) @threaded_daemon def _loop_handler(self) -> None: @@ -578,3 +591,18 @@ def _loop_handler(self) -> None: except Exception as e: self._exception_handler(e) time.sleep(0.1) + + def _check_required_config_parameter( + self, param: Union[ConfigParameterDefinition, ConfigNamespaceDefinition] + ) -> None: + if isinstance(param, ConfigNamespaceDefinition): + for sub_param in param.parameters.values(): + self._check_required_config_parameter(sub_param) + return + if param.required and ( + param.name not in self.config or self.config[param.name] is None + ): + self.node_status.waiting_for_config.add(param.name) + self.logger.log_info(f"Required config parameter '{param.name}' not set") + else: + self.node_status.waiting_for_config.discard(param.name) diff --git a/madsci/madsci_module/madsci/module/rest_module.py b/src/madsci_node_module/madsci/node_module/rest_node_module.py similarity index 96% rename from madsci/madsci_module/madsci/module/rest_module.py rename to src/madsci_node_module/madsci/node_module/rest_node_module.py index dcb91e5..7e0746a 100644 --- a/madsci/madsci_module/madsci/module/rest_module.py +++ b/src/madsci_node_module/madsci/node_module/rest_node_module.py @@ -14,27 +14,23 @@ from fastapi.applications import FastAPI from fastapi.datastructures import UploadFile from fastapi.routing import APIRouter -from rich import print -from starlette.responses import FileResponse - from madsci.client.node.rest_node_client import RestNodeClient from madsci.common.types.action_types import ActionRequest, ActionResult, ActionStatus from madsci.common.types.admin_command_types import AdminCommandResponse from madsci.common.types.base_types import Error, new_ulid_str from madsci.common.types.event_types import Event -from madsci.common.types.module_types import ( +from madsci.common.types.node_types import ( AdminCommands, NodeCapabilities, -) -from madsci.common.types.node_types import ( NodeInfo, NodeSetConfigResponse, NodeStatus, ) from madsci.common.utils import threaded_task -from madsci.module.abstract_module import ( +from madsci.node_module.abstract_node_module import ( AbstractNode, ) +from starlette.responses import FileResponse def action_response_to_headers(action_response: ActionResult) -> dict[str, str]: @@ -118,7 +114,7 @@ def run_action( self, action_name: str, args: Optional[str] = None, - files: list[UploadFile] = [], + files: Optional[list[UploadFile]] = [], action_id: Optional[str] = None, ) -> Union[ActionResult, ActionResultWithFiles]: """Run an action on the node.""" @@ -244,10 +240,16 @@ def _start_rest_api(self) -> None: self.rest_api = FastAPI(lifespan=self._lifespan) self._configure_routes() + if rest_server_config := self.config.get("rest_node", None): + host = rest_server_config.get("host", "localhost") + port = rest_server_config.get("port", 2000) + else: + host = "localhost" + port = 2000 self.rest_server_process = Process( target=uvicorn.run, args=(self.rest_api,), - kwargs={"host": self.config["host"], "port": self.config["port"]}, + kwargs={"host": host, "port": port}, daemon=True, ) self.rest_server_process.start() @@ -276,7 +278,7 @@ def _startup_thread(self) -> None: self.node_status.errored = True finally: # * Mark the node as no longer initializing - print("Startup complete") + self.logger.log(f"Startup complete for node {self.node_info.node_name}.") self.node_status.initializing = False def _lifespan(self, app: FastAPI) -> Generator[None, None, None]: # noqa: ARG002 diff --git a/src/madsci_node_module/pdm.lock b/src/madsci_node_module/pdm.lock new file mode 100644 index 0000000..4b31dae --- /dev/null +++ b/src/madsci_node_module/pdm.lock @@ -0,0 +1,543 @@ +# This file is @generated by PDM. +# It is not intended for manual editing. + +[metadata] +groups = ["default", "dev"] +strategy = ["inherit_metadata"] +lock_version = "4.5.0" +content_hash = "sha256:6d7b963ebea0f4c1cf7a44744fd71bd78010a1944f50b3fd00ee9174b5e00930" + +[[metadata.targets]] +requires_python = ">=3.9.1" + +[[package]] +name = "aenum" +version = "3.1.15" +summary = "Advanced Enumerations (compatible with Python's stdlib Enum), NamedTuples, and NamedConstants" +groups = ["dev"] +files = [ + {file = "aenum-3.1.15-py3-none-any.whl", hash = "sha256:e0dfaeea4c2bd362144b87377e2c61d91958c5ed0b4daf89cb6f45ae23af6288"}, + {file = "aenum-3.1.15.tar.gz", hash = "sha256:8cbd76cd18c4f870ff39b24284d3ea028fbe8731a58df3aa581e434c575b9559"}, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +requires_python = ">=3.8" +summary = "Reusable constraint types to use with typing.Annotated" +groups = ["dev"] +dependencies = [ + "typing-extensions>=4.0.0; python_version < \"3.9\"", +] +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + +[[package]] +name = "certifi" +version = "2024.8.30" +requires_python = ">=3.6" +summary = "Python package for providing Mozilla's CA Bundle." +groups = ["dev"] +files = [ + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.0" +requires_python = ">=3.7.0" +summary = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +groups = ["dev"] +files = [ + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, + {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, + {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, +] + +[[package]] +name = "greenlet" +version = "3.1.1" +requires_python = ">=3.7" +summary = "Lightweight in-process concurrent programming" +groups = ["dev"] +marker = "(platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\") and python_version < \"3.13\"" +files = [ + {file = "greenlet-3.1.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36b89d13c49216cadb828db8dfa6ce86bbbc476a82d3a6c397f0efae0525bdd0"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94b6150a85e1b33b40b1464a3f9988dcc5251d6ed06842abff82e42632fac120"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93147c513fac16385d1036b7e5b102c7fbbdb163d556b791f0f11eada7ba65dc"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da7a9bff22ce038e19bf62c4dd1ec8391062878710ded0a845bcf47cc0200617"}, + {file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b2795058c23988728eec1f36a4e5e4ebad22f8320c85f3587b539b9ac84128d7"}, + {file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ed10eac5830befbdd0c32f83e8aa6288361597550ba669b04c48f0f9a2c843c6"}, + {file = "greenlet-3.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:77c386de38a60d1dfb8e55b8c1101d68c79dfdd25c7095d51fec2dd800892b80"}, + {file = "greenlet-3.1.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2813dc3de8c1ee3f924e4d4227999285fd335d1bcc0d2be6dc3f1f6a318ec1"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a"}, + {file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511"}, + {file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1776fd7f989fc6b8d8c8cb8da1f6b82c5814957264d1f6cf818d475ec2bf6395"}, + {file = "greenlet-3.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:48ca08c771c268a768087b408658e216133aecd835c0ded47ce955381105ba39"}, + {file = "greenlet-3.1.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9"}, + {file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0"}, + {file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942"}, + {file = "greenlet-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01"}, + {file = "greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e"}, + {file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1"}, + {file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c"}, + {file = "greenlet-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822"}, + {file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01"}, + {file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6"}, + {file = "greenlet-3.1.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:396979749bd95f018296af156201d6211240e7a23090f50a8d5d18c370084dc3"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca9d0ff5ad43e785350894d97e13633a66e2b50000e8a183a50a88d834752d42"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f6ff3b14f2df4c41660a7dec01045a045653998784bf8cfcb5a525bdffffbc8f"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94ebba31df2aa506d7b14866fed00ac141a867e63143fe5bca82a8e503b36437"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73aaad12ac0ff500f62cebed98d8789198ea0e6f233421059fa68a5aa7220145"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63e4844797b975b9af3a3fb8f7866ff08775f5426925e1e0bbcfe7932059a12c"}, + {file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7939aa3ca7d2a1593596e7ac6d59391ff30281ef280d8632fa03d81f7c5f955e"}, + {file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d0028e725ee18175c6e422797c407874da24381ce0690d6b9396c204c7f7276e"}, + {file = "greenlet-3.1.1-cp39-cp39-win32.whl", hash = "sha256:5e06afd14cbaf9e00899fae69b24a32f2196c19de08fcb9f4779dd4f004e5e7c"}, + {file = "greenlet-3.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:3319aa75e0e0639bc15ff54ca327e8dc7a6fe404003496e3c6925cd3142e0e22"}, + {file = "greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467"}, +] + +[[package]] +name = "idna" +version = "3.10" +requires_python = ">=3.6" +summary = "Internationalized Domain Names in Applications (IDNA)" +groups = ["dev"] +files = [ + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +] + +[[package]] +name = "madsci-common" +version = "0.1.dev10" +requires_python = ">=3.9.1" +editable = true +path = "../madsci_common" +summary = "The Modular Autonomous Discovery for Science (MADSci) Common Definitions and Utilities." +groups = ["dev"] +dependencies = [ + "PyYAML>=6.0.2", + "aenum>=3.1.15", + "pydantic>=2.9.2", + "python-dotenv>=1.0.1", + "python-ulid[pydantic]>=3.0.0", + "requests>=2.32.3", + "sqlmodel>=0.0.22", +] + +[[package]] +name = "pydantic" +version = "2.10.3" +requires_python = ">=3.8" +summary = "Data validation using Python type hints" +groups = ["dev"] +dependencies = [ + "annotated-types>=0.6.0", + "pydantic-core==2.27.1", + "typing-extensions>=4.12.2", +] +files = [ + {file = "pydantic-2.10.3-py3-none-any.whl", hash = "sha256:be04d85bbc7b65651c5f8e6b9976ed9c6f41782a55524cef079a34a0bb82144d"}, + {file = "pydantic-2.10.3.tar.gz", hash = "sha256:cb5ac360ce894ceacd69c403187900a02c4b20b693a9dd1d643e1effab9eadf9"}, +] + +[[package]] +name = "pydantic-core" +version = "2.27.1" +requires_python = ">=3.8" +summary = "Core functionality for Pydantic validation and serialization" +groups = ["dev"] +dependencies = [ + "typing-extensions!=4.7.0,>=4.6.0", +] +files = [ + {file = "pydantic_core-2.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:71a5e35c75c021aaf400ac048dacc855f000bdfed91614b4a726f7432f1f3d6a"}, + {file = "pydantic_core-2.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f82d068a2d6ecfc6e054726080af69a6764a10015467d7d7b9f66d6ed5afa23b"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:121ceb0e822f79163dd4699e4c54f5ad38b157084d97b34de8b232bcaad70278"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4603137322c18eaf2e06a4495f426aa8d8388940f3c457e7548145011bb68e05"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a33cd6ad9017bbeaa9ed78a2e0752c5e250eafb9534f308e7a5f7849b0b1bfb4"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15cc53a3179ba0fcefe1e3ae50beb2784dede4003ad2dfd24f81bba4b23a454f"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45d9c5eb9273aa50999ad6adc6be5e0ecea7e09dbd0d31bd0c65a55a2592ca08"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8bf7b66ce12a2ac52d16f776b31d16d91033150266eb796967a7e4621707e4f6"}, + {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:655d7dd86f26cb15ce8a431036f66ce0318648f8853d709b4167786ec2fa4807"}, + {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:5556470f1a2157031e676f776c2bc20acd34c1990ca5f7e56f1ebf938b9ab57c"}, + {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f69ed81ab24d5a3bd93861c8c4436f54afdf8e8cc421562b0c7504cf3be58206"}, + {file = "pydantic_core-2.27.1-cp310-none-win32.whl", hash = "sha256:f5a823165e6d04ccea61a9f0576f345f8ce40ed533013580e087bd4d7442b52c"}, + {file = "pydantic_core-2.27.1-cp310-none-win_amd64.whl", hash = "sha256:57866a76e0b3823e0b56692d1a0bf722bffb324839bb5b7226a7dbd6c9a40b17"}, + {file = "pydantic_core-2.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac3b20653bdbe160febbea8aa6c079d3df19310d50ac314911ed8cc4eb7f8cb8"}, + {file = "pydantic_core-2.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a5a8e19d7c707c4cadb8c18f5f60c843052ae83c20fa7d44f41594c644a1d330"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f7059ca8d64fea7f238994c97d91f75965216bcbe5f695bb44f354893f11d52"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bed0f8a0eeea9fb72937ba118f9db0cb7e90773462af7962d382445f3005e5a4"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3cb37038123447cf0f3ea4c74751f6a9d7afef0eb71aa07bf5f652b5e6a132c"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84286494f6c5d05243456e04223d5a9417d7f443c3b76065e75001beb26f88de"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acc07b2cfc5b835444b44a9956846b578d27beeacd4b52e45489e93276241025"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4fefee876e07a6e9aad7a8c8c9f85b0cdbe7df52b8a9552307b09050f7512c7e"}, + {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:258c57abf1188926c774a4c94dd29237e77eda19462e5bb901d88adcab6af919"}, + {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:35c14ac45fcfdf7167ca76cc80b2001205a8d5d16d80524e13508371fb8cdd9c"}, + {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d1b26e1dff225c31897696cab7d4f0a315d4c0d9e8666dbffdb28216f3b17fdc"}, + {file = "pydantic_core-2.27.1-cp311-none-win32.whl", hash = "sha256:2cdf7d86886bc6982354862204ae3b2f7f96f21a3eb0ba5ca0ac42c7b38598b9"}, + {file = "pydantic_core-2.27.1-cp311-none-win_amd64.whl", hash = "sha256:3af385b0cee8df3746c3f406f38bcbfdc9041b5c2d5ce3e5fc6637256e60bbc5"}, + {file = "pydantic_core-2.27.1-cp311-none-win_arm64.whl", hash = "sha256:81f2ec23ddc1b476ff96563f2e8d723830b06dceae348ce02914a37cb4e74b89"}, + {file = "pydantic_core-2.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9cbd94fc661d2bab2bc702cddd2d3370bbdcc4cd0f8f57488a81bcce90c7a54f"}, + {file = "pydantic_core-2.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f8c4718cd44ec1580e180cb739713ecda2bdee1341084c1467802a417fe0f02"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15aae984e46de8d376df515f00450d1522077254ef6b7ce189b38ecee7c9677c"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ba5e3963344ff25fc8c40da90f44b0afca8cfd89d12964feb79ac1411a260ac"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:992cea5f4f3b29d6b4f7f1726ed8ee46c8331c6b4eed6db5b40134c6fe1768bb"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0325336f348dbee6550d129b1627cb8f5351a9dc91aad141ffb96d4937bd9529"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7597c07fbd11515f654d6ece3d0e4e5093edc30a436c63142d9a4b8e22f19c35"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3bbd5d8cc692616d5ef6fbbbd50dbec142c7e6ad9beb66b78a96e9c16729b089"}, + {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:dc61505e73298a84a2f317255fcc72b710b72980f3a1f670447a21efc88f8381"}, + {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:e1f735dc43da318cad19b4173dd1ffce1d84aafd6c9b782b3abc04a0d5a6f5bb"}, + {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f4e5658dbffe8843a0f12366a4c2d1c316dbe09bb4dfbdc9d2d9cd6031de8aae"}, + {file = "pydantic_core-2.27.1-cp312-none-win32.whl", hash = "sha256:672ebbe820bb37988c4d136eca2652ee114992d5d41c7e4858cdd90ea94ffe5c"}, + {file = "pydantic_core-2.27.1-cp312-none-win_amd64.whl", hash = "sha256:66ff044fd0bb1768688aecbe28b6190f6e799349221fb0de0e6f4048eca14c16"}, + {file = "pydantic_core-2.27.1-cp312-none-win_arm64.whl", hash = "sha256:9a3b0793b1bbfd4146304e23d90045f2a9b5fd5823aa682665fbdaf2a6c28f3e"}, + {file = "pydantic_core-2.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f216dbce0e60e4d03e0c4353c7023b202d95cbaeff12e5fd2e82ea0a66905073"}, + {file = "pydantic_core-2.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a2e02889071850bbfd36b56fd6bc98945e23670773bc7a76657e90e6b6603c08"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42b0e23f119b2b456d07ca91b307ae167cc3f6c846a7b169fca5326e32fdc6cf"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:764be71193f87d460a03f1f7385a82e226639732214b402f9aa61f0d025f0737"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c00666a3bd2f84920a4e94434f5974d7bbc57e461318d6bb34ce9cdbbc1f6b2"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ccaa88b24eebc0f849ce0a4d09e8a408ec5a94afff395eb69baf868f5183107"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c65af9088ac534313e1963443d0ec360bb2b9cba6c2909478d22c2e363d98a51"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:206b5cf6f0c513baffaeae7bd817717140770c74528f3e4c3e1cec7871ddd61a"}, + {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:062f60e512fc7fff8b8a9d680ff0ddaaef0193dba9fa83e679c0c5f5fbd018bc"}, + {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:a0697803ed7d4af5e4c1adf1670af078f8fcab7a86350e969f454daf598c4960"}, + {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:58ca98a950171f3151c603aeea9303ef6c235f692fe555e883591103da709b23"}, + {file = "pydantic_core-2.27.1-cp313-none-win32.whl", hash = "sha256:8065914ff79f7eab1599bd80406681f0ad08f8e47c880f17b416c9f8f7a26d05"}, + {file = "pydantic_core-2.27.1-cp313-none-win_amd64.whl", hash = "sha256:ba630d5e3db74c79300d9a5bdaaf6200172b107f263c98a0539eeecb857b2337"}, + {file = "pydantic_core-2.27.1-cp313-none-win_arm64.whl", hash = "sha256:45cf8588c066860b623cd11c4ba687f8d7175d5f7ef65f7129df8a394c502de5"}, + {file = "pydantic_core-2.27.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:e9386266798d64eeb19dd3677051f5705bf873e98e15897ddb7d76f477131967"}, + {file = "pydantic_core-2.27.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4228b5b646caa73f119b1ae756216b59cc6e2267201c27d3912b592c5e323b60"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b3dfe500de26c52abe0477dde16192ac39c98f05bf2d80e76102d394bd13854"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aee66be87825cdf72ac64cb03ad4c15ffef4143dbf5c113f64a5ff4f81477bf9"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b748c44bb9f53031c8cbc99a8a061bc181c1000c60a30f55393b6e9c45cc5bd"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ca038c7f6a0afd0b2448941b6ef9d5e1949e999f9e5517692eb6da58e9d44be"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e0bd57539da59a3e4671b90a502da9a28c72322a4f17866ba3ac63a82c4498e"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ac6c2c45c847bbf8f91930d88716a0fb924b51e0c6dad329b793d670ec5db792"}, + {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b94d4ba43739bbe8b0ce4262bcc3b7b9f31459ad120fb595627eaeb7f9b9ca01"}, + {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:00e6424f4b26fe82d44577b4c842d7df97c20be6439e8e685d0d715feceb9fb9"}, + {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:38de0a70160dd97540335b7ad3a74571b24f1dc3ed33f815f0880682e6880131"}, + {file = "pydantic_core-2.27.1-cp39-none-win32.whl", hash = "sha256:7ccebf51efc61634f6c2344da73e366c75e735960b5654b63d7e6f69a5885fa3"}, + {file = "pydantic_core-2.27.1-cp39-none-win_amd64.whl", hash = "sha256:a57847b090d7892f123726202b7daa20df6694cbd583b67a592e856bff603d6c"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3fa80ac2bd5856580e242dbc202db873c60a01b20309c8319b5c5986fbe53ce6"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d950caa237bb1954f1b8c9227b5065ba6875ac9771bb8ec790d956a699b78676"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e4216e64d203e39c62df627aa882f02a2438d18a5f21d7f721621f7a5d3611d"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02a3d637bd387c41d46b002f0e49c52642281edacd2740e5a42f7017feea3f2c"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:161c27ccce13b6b0c8689418da3885d3220ed2eae2ea5e9b2f7f3d48f1d52c27"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:19910754e4cc9c63bc1c7f6d73aa1cfee82f42007e407c0f413695c2f7ed777f"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:e173486019cc283dc9778315fa29a363579372fe67045e971e89b6365cc035ed"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:af52d26579b308921b73b956153066481f064875140ccd1dfd4e77db89dbb12f"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:981fb88516bd1ae8b0cbbd2034678a39dedc98752f264ac9bc5839d3923fa04c"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5fde892e6c697ce3e30c61b239330fc5d569a71fefd4eb6512fc6caec9dd9e2f"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:816f5aa087094099fff7edabb5e01cc370eb21aa1a1d44fe2d2aefdfb5599b31"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c10c309e18e443ddb108f0ef64e8729363adbfd92d6d57beec680f6261556f3"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98476c98b02c8e9b2eec76ac4156fd006628b1b2d0ef27e548ffa978393fd154"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c3027001c28434e7ca5a6e1e527487051136aa81803ac812be51802150d880dd"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:7699b1df36a48169cdebda7ab5a2bac265204003f153b4bd17276153d997670a"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1c39b07d90be6b48968ddc8c19e7585052088fd7ec8d568bb31ff64c70ae3c97"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:46ccfe3032b3915586e469d4972973f893c0a2bb65669194a5bdea9bacc088c2"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:62ba45e21cf6571d7f716d903b5b7b6d2617e2d5d67c0923dc47b9d41369f840"}, + {file = "pydantic_core-2.27.1.tar.gz", hash = "sha256:62a763352879b84aa31058fc931884055fd75089cccbd9d58bb6afd01141b235"}, +] + +[[package]] +name = "python-dotenv" +version = "1.0.1" +requires_python = ">=3.8" +summary = "Read key-value pairs from a .env file and set them as environment variables" +groups = ["dev"] +files = [ + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, +] + +[[package]] +name = "python-ulid" +version = "3.0.0" +requires_python = ">=3.9" +summary = "Universally unique lexicographically sortable identifier" +groups = ["dev"] +files = [ + {file = "python_ulid-3.0.0-py3-none-any.whl", hash = "sha256:e4c4942ff50dbd79167ad01ac725ec58f924b4018025ce22c858bfcff99a5e31"}, + {file = "python_ulid-3.0.0.tar.gz", hash = "sha256:e50296a47dc8209d28629a22fc81ca26c00982c78934bd7766377ba37ea49a9f"}, +] + +[[package]] +name = "python-ulid" +version = "3.0.0" +extras = ["pydantic"] +requires_python = ">=3.9" +summary = "Universally unique lexicographically sortable identifier" +groups = ["dev"] +dependencies = [ + "pydantic>=2.0", + "python-ulid==3.0.0", +] +files = [ + {file = "python_ulid-3.0.0-py3-none-any.whl", hash = "sha256:e4c4942ff50dbd79167ad01ac725ec58f924b4018025ce22c858bfcff99a5e31"}, + {file = "python_ulid-3.0.0.tar.gz", hash = "sha256:e50296a47dc8209d28629a22fc81ca26c00982c78934bd7766377ba37ea49a9f"}, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +requires_python = ">=3.8" +summary = "YAML parser and emitter for Python" +groups = ["dev"] +files = [ + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, +] + +[[package]] +name = "requests" +version = "2.32.3" +requires_python = ">=3.8" +summary = "Python HTTP for Humans." +groups = ["dev"] +dependencies = [ + "certifi>=2017.4.17", + "charset-normalizer<4,>=2", + "idna<4,>=2.5", + "urllib3<3,>=1.21.1", +] +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.36" +requires_python = ">=3.7" +summary = "Database Abstraction Library" +groups = ["dev"] +dependencies = [ + "greenlet!=0.4.17; (platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\") and python_version < \"3.13\"", + "importlib-metadata; python_version < \"3.8\"", + "typing-extensions>=4.6.0", +] +files = [ + {file = "SQLAlchemy-2.0.36-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:59b8f3adb3971929a3e660337f5dacc5942c2cdb760afcabb2614ffbda9f9f72"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37350015056a553e442ff672c2d20e6f4b6d0b2495691fa239d8aa18bb3bc908"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8318f4776c85abc3f40ab185e388bee7a6ea99e7fa3a30686580b209eaa35c08"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c245b1fbade9c35e5bd3b64270ab49ce990369018289ecfde3f9c318411aaa07"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:69f93723edbca7342624d09f6704e7126b152eaed3cdbb634cb657a54332a3c5"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f9511d8dd4a6e9271d07d150fb2f81874a3c8c95e11ff9af3a2dfc35fe42ee44"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-win32.whl", hash = "sha256:c3f3631693003d8e585d4200730616b78fafd5a01ef8b698f6967da5c605b3fa"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-win_amd64.whl", hash = "sha256:a86bfab2ef46d63300c0f06936bd6e6c0105faa11d509083ba8f2f9d237fb5b5"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fd3a55deef00f689ce931d4d1b23fa9f04c880a48ee97af488fd215cf24e2a6c"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f5e9cd989b45b73bd359f693b935364f7e1f79486e29015813c338450aa5a71"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0ddd9db6e59c44875211bc4c7953a9f6638b937b0a88ae6d09eb46cced54eff"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2519f3a5d0517fc159afab1015e54bb81b4406c278749779be57a569d8d1bb0d"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59b1ee96617135f6e1d6f275bbe988f419c5178016f3d41d3c0abb0c819f75bb"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:39769a115f730d683b0eb7b694db9789267bcd027326cccc3125e862eb03bfd8"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-win32.whl", hash = "sha256:66bffbad8d6271bb1cc2f9a4ea4f86f80fe5e2e3e501a5ae2a3dc6a76e604e6f"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-win_amd64.whl", hash = "sha256:23623166bfefe1487d81b698c423f8678e80df8b54614c2bf4b4cfcd7c711959"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7b64e6ec3f02c35647be6b4851008b26cff592a95ecb13b6788a54ef80bbdd4"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:46331b00096a6db1fdc052d55b101dbbfc99155a548e20a0e4a8e5e4d1362855"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdf3386a801ea5aba17c6410dd1dc8d39cf454ca2565541b5ac42a84e1e28f53"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac9dfa18ff2a67b09b372d5db8743c27966abf0e5344c555d86cc7199f7ad83a"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:90812a8933df713fdf748b355527e3af257a11e415b613dd794512461eb8a686"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1bc330d9d29c7f06f003ab10e1eaced295e87940405afe1b110f2eb93a233588"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-win32.whl", hash = "sha256:79d2e78abc26d871875b419e1fd3c0bca31a1cb0043277d0d850014599626c2e"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-win_amd64.whl", hash = "sha256:b544ad1935a8541d177cb402948b94e871067656b3a0b9e91dbec136b06a2ff5"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b5cc79df7f4bc3d11e4b542596c03826063092611e481fcf1c9dfee3c94355ef"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3c01117dd36800f2ecaa238c65365b7b16497adc1522bf84906e5710ee9ba0e8"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bc633f4ee4b4c46e7adcb3a9b5ec083bf1d9a97c1d3854b92749d935de40b9b"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e46ed38affdfc95d2c958de328d037d87801cfcbea6d421000859e9789e61c2"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b2985c0b06e989c043f1dc09d4fe89e1616aadd35392aea2844f0458a989eacf"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a121d62ebe7d26fec9155f83f8be5189ef1405f5973ea4874a26fab9f1e262c"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-win32.whl", hash = "sha256:0572f4bd6f94752167adfd7c1bed84f4b240ee6203a95e05d1e208d488d0d436"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-win_amd64.whl", hash = "sha256:8c78ac40bde930c60e0f78b3cd184c580f89456dd87fc08f9e3ee3ce8765ce88"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dc022184d3e5cacc9579e41805a681187650e170eb2fd70e28b86192a479dcaa"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b817d41d692bf286abc181f8af476c4fbef3fd05e798777492618378448ee689"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4e46a888b54be23d03a89be510f24a7652fe6ff660787b96cd0e57a4ebcb46d"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4ae3005ed83f5967f961fd091f2f8c5329161f69ce8480aa8168b2d7fe37f06"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:03e08af7a5f9386a43919eda9de33ffda16b44eb11f3b313e6822243770e9763"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3dbb986bad3ed5ceaf090200eba750b5245150bd97d3e67343a3cfed06feecf7"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-win32.whl", hash = "sha256:9fe53b404f24789b5ea9003fc25b9a3988feddebd7e7b369c8fac27ad6f52f28"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-win_amd64.whl", hash = "sha256:af148a33ff0349f53512a049c6406923e4e02bf2f26c5fb285f143faf4f0e46a"}, + {file = "SQLAlchemy-2.0.36-py3-none-any.whl", hash = "sha256:fddbe92b4760c6f5d48162aef14824add991aeda8ddadb3c31d56eb15ca69f8e"}, + {file = "sqlalchemy-2.0.36.tar.gz", hash = "sha256:7f2767680b6d2398aea7082e45a774b2b0767b5c8d8ffb9c8b683088ea9b29c5"}, +] + +[[package]] +name = "sqlmodel" +version = "0.0.22" +requires_python = ">=3.7" +summary = "SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness." +groups = ["dev"] +dependencies = [ + "SQLAlchemy<2.1.0,>=2.0.14", + "pydantic<3.0.0,>=1.10.13", +] +files = [ + {file = "sqlmodel-0.0.22-py3-none-any.whl", hash = "sha256:a1ed13e28a1f4057cbf4ff6cdb4fc09e85702621d3259ba17b3c230bfb2f941b"}, + {file = "sqlmodel-0.0.22.tar.gz", hash = "sha256:7d37c882a30c43464d143e35e9ecaf945d88035e20117bf5ec2834a23cbe505e"}, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +requires_python = ">=3.8" +summary = "Backported and Experimental Type Hints for Python 3.8+" +groups = ["dev"] +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + +[[package]] +name = "urllib3" +version = "2.2.3" +requires_python = ">=3.8" +summary = "HTTP library with thread-safe connection pooling, file post, and more." +groups = ["dev"] +files = [ + {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, + {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, +] diff --git a/madsci/madsci_module/pyproject.toml b/src/madsci_node_module/pyproject.toml similarity index 96% rename from madsci/madsci_module/pyproject.toml rename to src/madsci_node_module/pyproject.toml index 32469b9..ada63b7 100644 --- a/madsci/madsci_module/pyproject.toml +++ b/src/madsci_node_module/pyproject.toml @@ -1,6 +1,6 @@ [project] -name = "madsci.module" +name = "madsci.node_module" dynamic = ["version"] description = "The Modular Autonomous Discovery for Science (MADSci) Node Module Helper Classes." authors = [ diff --git a/madsci/madsci_resource_manager/README.md b/src/madsci_resource_manager/README.md similarity index 100% rename from madsci/madsci_resource_manager/README.md rename to src/madsci_resource_manager/README.md diff --git a/madsci/madsci_resource_manager/madsci/resource_manager/__init__.py b/src/madsci_resource_manager/madsci/resource_manager/__init__.py similarity index 100% rename from madsci/madsci_resource_manager/madsci/resource_manager/__init__.py rename to src/madsci_resource_manager/madsci/resource_manager/__init__.py diff --git a/madsci/madsci_resource_manager/madsci/resource_manager/resource_server.py b/src/madsci_resource_manager/madsci/resource_manager/resource_server.py similarity index 99% rename from madsci/madsci_resource_manager/madsci/resource_manager/resource_server.py rename to src/madsci_resource_manager/madsci/resource_manager/resource_server.py index dddeaf4..bb25170 100644 --- a/madsci/madsci_resource_manager/madsci/resource_manager/resource_server.py +++ b/src/madsci_resource_manager/madsci/resource_manager/resource_server.py @@ -1,7 +1,6 @@ """MADSci Resource Manager Server.""" from fastapi import FastAPI - from madsci.resource_manager.types import ( ResourceManagerConfig, ResourceManagerDefinition, diff --git a/madsci/madsci_resource_manager/madsci/resource_manager/types.py b/src/madsci_resource_manager/madsci/resource_manager/types.py similarity index 99% rename from madsci/madsci_resource_manager/madsci/resource_manager/types.py rename to src/madsci_resource_manager/madsci/resource_manager/types.py index d58a584..e9d4d74 100644 --- a/madsci/madsci_resource_manager/madsci/resource_manager/types.py +++ b/src/madsci_resource_manager/madsci/resource_manager/types.py @@ -1,9 +1,8 @@ """MADSci Resource Manager Types.""" -from sqlmodel.main import Field - from madsci.common.types.base_types import BaseModel from madsci.common.types.squid_types import ManagerDefinition +from sqlmodel.main import Field class ResourceManagerDefinition(ManagerDefinition): diff --git a/madsci/madsci_resource_manager/pyproject.toml b/src/madsci_resource_manager/pyproject.toml similarity index 100% rename from madsci/madsci_resource_manager/pyproject.toml rename to src/madsci_resource_manager/pyproject.toml diff --git a/madsci/madsci_squid/README.md b/src/madsci_squid/README.md similarity index 100% rename from madsci/madsci_squid/README.md rename to src/madsci_squid/README.md diff --git a/madsci/madsci_squid/madsci/squid/__init__.py b/src/madsci_squid/madsci/squid/__init__.py similarity index 100% rename from madsci/madsci_squid/madsci/squid/__init__.py rename to src/madsci_squid/madsci/squid/__init__.py diff --git a/madsci/madsci_squid/madsci/squid/lab_server.py b/src/madsci_squid/madsci/squid/lab_server.py similarity index 99% rename from madsci/madsci_squid/madsci/squid/lab_server.py rename to src/madsci_squid/madsci/squid/lab_server.py index 6e4c2ce..32a87ad 100644 --- a/madsci/madsci_squid/madsci/squid/lab_server.py +++ b/src/madsci_squid/madsci/squid/lab_server.py @@ -2,10 +2,9 @@ import uvicorn from fastapi import FastAPI -from starlette.responses import JSONResponse - from madsci.common.definition_loaders import lab_definition_loader from madsci.common.types.squid_types import LabDefinition +from starlette.responses import JSONResponse app = FastAPI() diff --git a/madsci/madsci_squid/pyproject.toml b/src/madsci_squid/pyproject.toml similarity index 100% rename from madsci/madsci_squid/pyproject.toml rename to src/madsci_squid/pyproject.toml diff --git a/tests/test_cli.ipynb b/tests/test_cli.ipynb index 1618942..f048429 100644 --- a/tests/test_cli.ipynb +++ b/tests/test_cli.ipynb @@ -9,12 +9,14 @@ "import os\n", "from pathlib import Path\n", "\n", + "from madsci.client.event_client import default_logger\n", + "\n", "if Path.cwd().stem == \"test_lab\":\n", " os.chdir(\"..\")\n", "\n", "path = Path.cwd() / \"test_lab\"\n", "if path.exists():\n", - " print(\"Directory test_lab/ already exists, removing...\")\n", + " default_logger.log(\"Directory test_lab/ already exists, removing...\")\n", "\n", " def remove_children(path: Path) -> None:\n", " \"\"\"Recursively remove all children of a directory.\"\"\"\n", @@ -31,7 +33,7 @@ "\n", "path.mkdir()\n", "os.chdir(path)\n", - "print(Path.cwd())" + "default_logger.log(Path.cwd())" ] }, { diff --git a/tests/test_events.ipynb b/tests/test_events.ipynb new file mode 100644 index 0000000..b6a0beb --- /dev/null +++ b/tests/test_events.ipynb @@ -0,0 +1,161 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from madsci.client.event_client import EventClient\n", + "from madsci.common.types.auth_types import OwnershipInfo\n", + "from madsci.common.types.base_types import new_ulid_str" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from rich import print\n", + "\n", + "events = EventClient(\n", + " \"node.test\",\n", + " source=OwnershipInfo(node_id=new_ulid_str()),\n", + " event_server=\"http://localhost:8001\",\n", + ")\n", + "events.log_info(\"info_event\")\n", + "events.log_debug(\"debug event\")\n", + "events.log_warning(\"warning event\")\n", + "events.log_error(\"error_event\")\n", + "events.log_critical(\"critical_event\")\n", + "\n", + "print(events.get_events())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from madsci.client.event_client import Event\n", + "from madsci.common.types.base_types import Error\n", + "\n", + "try:\n", + " raise Exception(\"blargh\")\n", + "except Exception as e:\n", + " error = Error.from_exception(e)\n", + " events.log_critical(Event(event_data=error))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pymongo import MongoClient" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "db_url = \"mongodb://localhost:27017\"\n", + "client = MongoClient(db_url)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "db = client[\"madsci_events\"]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "collection_name = db[\"events\"]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from madsci.common.types.event_types import Event\n", + "\n", + "event = Event(event_data={\"test\": \"data\"})\n", + "\n", + "collection_name.insert_one(event.model_dump(mode=\"json\"))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "result = collection_name.count_documents({})\n", + "print(result)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "result = collection_name.find({})\n", + "for item in result:\n", + " print(Event.model_validate(item))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "type(collection_name)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.21" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/tests/test_module.ipynb b/tests/test_module.ipynb index 7bc2a63..0ee8bc0 100644 --- a/tests/test_module.ipynb +++ b/tests/test_module.ipynb @@ -6,11 +6,10 @@ "metadata": {}, "outputs": [], "source": [ - "from rich import print\n", - "\n", "from madsci.client.node.rest_node_client import RestNodeClient\n", "from madsci.common.types.action_types import ActionRequest\n", - "from madsci.common.types.node_types import Node" + "from madsci.common.types.node_types import Node\n", + "from rich import print" ] }, { diff --git a/tests/test_modules/liquidhandler.module.yaml b/tests/test_modules/liquidhandler.module.yaml deleted file mode 100644 index f20097a..0000000 --- a/tests/test_modules/liquidhandler.module.yaml +++ /dev/null @@ -1,38 +0,0 @@ -module_name: liquidhandler -module_type: device -module_description: null -capabilities: - get_info: false - get_state: false - get_status: false - send_action: false - get_action_result: false - get_action_history: false - action_files: false - send_admin_commands: false - set_config: false - get_resources: false - get_log: false - events: false - resources: false - admin_commands: [] -config: - host: - name: host - description: The host of the REST API. - default: 127.0.0.1 - required: true - reset_on_change: true - port: - name: port - description: The port of the REST API. - default: 2000 - required: true - reset_on_change: true - protocol: - name: protocol - description: The protocol of the REST API, either 'http' or 'https'. - default: http - required: true - reset_on_change: true -commands: {} diff --git a/tests/test_modules/nodes/default.node.info.yaml b/tests/test_modules/nodes/default.node.info.yaml deleted file mode 100644 index efa7019..0000000 --- a/tests/test_modules/nodes/default.node.info.yaml +++ /dev/null @@ -1,59 +0,0 @@ -module_name: liquidhandler -module_type: device -module_description: null -capabilities: - get_info: false - get_state: false - get_status: false - send_action: false - get_action_result: false - get_action_history: false - action_files: false - send_admin_commands: false - set_config: false - get_resources: false - get_log: false - events: false - resources: false - admin_commands: - - reset - - shutdown -config: - host: - name: host - description: The host of the REST API. - default: 127.0.0.1 - required: true - reset_on_change: true - port: - name: port - description: The port of the REST API. - default: 2000 - required: true - reset_on_change: true - protocol: - name: protocol - description: The protocol of the REST API, either 'http' or 'https'. - default: http - required: true - reset_on_change: true -commands: {} -node_name: default -node_id: 01JD7WCXX8Y1CMER8P3XHA6CTW -node_url: null -node_description: Default liquidhandler -module_definition: ../liquidhandler.module.yaml -actions: - run_command: - name: run_command - description: Run a command on the liquid handler. - args: - command: - name: command - description: '' - type: str - required: true - default: null - files: {} - results: {} - blocking: false diff --git a/tests/test_modules/nodes/default.node.yaml b/tests/test_modules/nodes/default.node.yaml deleted file mode 100644 index 686bb63..0000000 --- a/tests/test_modules/nodes/default.node.yaml +++ /dev/null @@ -1,25 +0,0 @@ -node_name: default -node_id: 01JD7WCXX8Y1CMER8P3XHA6CTW -node_url: null -node_description: Default liquidhandler -module_definition: ../liquidhandler.module.yaml -config: - host: - name: host - description: The host of the REST API. - default: 127.0.0.1 - required: true - reset_on_change: true - port: - name: port - description: The port of the REST API. - default: 2000 - required: true - reset_on_change: true - protocol: - name: protocol - description: The protocol of the REST API, either 'http' or 'https'. - default: http - required: true - reset_on_change: true -commands: {}