diff --git a/.devcontainer/DevContainer.Dockerfile b/.devcontainer/DevContainer.Dockerfile new file mode 100644 index 0000000..e1b06a5 --- /dev/null +++ b/.devcontainer/DevContainer.Dockerfile @@ -0,0 +1,6 @@ +# Note: You can use any Debian/Ubuntu based image you want. +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 diff --git a/.devcontainer/devcontainer.compose.yaml b/.devcontainer/devcontainer.compose.yaml new file mode 100644 index 0000000..d6a8427 --- /dev/null +++ b/.devcontainer/devcontainer.compose.yaml @@ -0,0 +1,24 @@ +services: + dev: + build: + context: . + dockerfile: DevContainer.Dockerfile + + volumes: + # Forwards the local Docker socket to the container. + - /var/run/docker.sock:/var/run/docker-host.sock + # Update this to wherever you want VS Code to mount the folder of your project + - ../..:/workspaces:cached + + # Overrides default command so things don't shut down after the process ends. + entrypoint: /usr/local/share/docker-init.sh + command: sleep infinity + + # Uncomment the next four lines if you will use a ptrace-based debuggers like C++, Go, and Rust. + # cap_add: + # - SYS_PTRACE + # security_opt: + # - seccomp:unconfined + + # Use "forwardPorts" in **devcontainer.json** to forward an app port locally. + # (Adding the "ports" property to this file will not forward from a Codespace.) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..d669254 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,52 @@ +{ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/docker-outside-of-docker-compose + "name": "MADSci Dev Container", + "dockerComposeFile": "devcontainer.compose.yaml", + "service": "dev", + "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", + + // Use this environment variable if you need to bind mount your local source code into a new container. + "remoteEnv": { + "LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}" + }, + + "customizations": { + "vscode": { + "extensions": [ + "ms-python.python", + "donjayamanne.python-environment-manager", + "charliermarsh.ruff", + "VisualStudioExptTeam.vscodeintellicode", + "aaron-bond.better-comments", + "vuetifyjs.vuetify-vscode", + "christian-kohler.path-intellisense", + "nefrob.vscode-just", + "Vue.volar", + "redhat.vscode-yaml", + "KevinRose.vsc-python-indent", + "ms-python.vscode-pylance" + ] + } + }, + + "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": {} + }, + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + "postCreateCommand": "just init" + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..cf70988 --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +**/node_modules diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..f33a02c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for more information: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates +# https://containers.dev/guide/dependabot + +version: 2 +updates: + - package-ecosystem: "devcontainers" + directory: "/" + schedule: + interval: weekly diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..10ce98a --- /dev/null +++ b/.gitignore @@ -0,0 +1,306 @@ +# Created by https://www.toptal.com/developers/gitignore/api/python,jupyternotebooks,visualstudiocode,macos,linux,windows +# Edit at https://www.toptal.com/developers/gitignore?templates=python,jupyternotebooks,visualstudiocode,macos,linux,windows + +### JupyterNotebooks ### +# gitignore template for Jupyter Notebooks +# website: http://jupyter.org/ + +.ipynb_checkpoints +*/.ipynb_checkpoints/* + +# IPython +profile_default/ +ipython_config.py + +# Remove previous ipynb_checkpoints +# git rm -r .ipynb_checkpoints/ + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ +docs/build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook + +# IPython + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +### VisualStudioCode ### +.vscode/ +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Vim +*.swp + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +# Support for Project snippet scope +.vscode/*.code-snippets + +# Ignore code-workspaces +*.code-workspace + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/python,jupyternotebooks,visualstudiocode,macos,linux,windows + +# Ignore logs folder that is created if user does not specify +logs/ + +# Environment file for Docker Compose +.env + +# ignore redis dump file +*.rdb + +# Image Artifacts +**/*.jpg + +# PDM +.pdm-python +.pdm-build/* + +# Ruff +.ruff_cache/* + +test_experiment/ + +# Output data +.wei/ + +#node modules +**/node_modules diff --git a/.justfile b/.justfile new file mode 100644 index 0000000..3661cdc --- /dev/null +++ b/.justfile @@ -0,0 +1,40 @@ +# List available commands +default: + @just --list --justfile {{justfile()}} + +# initialize the project +init: + @which pdm || echo "pdm not found, you'll need to install it: https://github.com/pdm-project/pdm" + @pdm install + @#test -e .env || cp .env.example .env + +# Install the pre-commit hooks +hooks: + @pre-commit install + +# Run the pre-commit checks +checks: + @pre-commit run --all-files || { echo "Checking fixes\n" ; pre-commit run --all-files; } + + +# Python tasks + +# Update the pdm version +pdm-update: + @pdm self update + +# Install the default dependencies +pdm-install: + @pdm install + +# Install a specific group of dependencies +pdm-install-group group: + @pdm install --group {{group}} + +# Install all dependencies +pdm-install-all: + @just pdm-install-group :all + +# Build the python package +pdm-build: + @pdm build diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..203f4bd --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,29 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: check-yaml + - id: check-toml + - id: check-ast + - id: check-merge-conflict + - id: check-added-large-files + - id: mixed-line-ending + - id: end-of-file-fixer + - id: trailing-whitespace + - repo: https://github.com/kynan/nbstripout + rev: 0.7.1 + hooks: + - id: nbstripout + - repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.7.1 + hooks: + # Run the linter. + - id: ruff + args: [--fix] + # Run the formatter. + - id: ruff-format + - repo: https://gitlab.com/bmares/check-json5 + rev: v1.0.0 + hooks: + - id: check-json5 diff --git a/README.md b/README.md new file mode 100644 index 0000000..c57b87b --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# Modular Autonomous Discovery for Science (MADSci) + +## 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/madsci/madsci_client/README.md b/madsci/madsci_client/README.md new file mode 100644 index 0000000..e69de29 diff --git a/madsci/madsci_client/madsci/client/__init__.py b/madsci/madsci_client/madsci/client/__init__.py new file mode 100644 index 0000000..7c0673d --- /dev/null +++ b/madsci/madsci_client/madsci/client/__init__.py @@ -0,0 +1 @@ +"""The Modular Autonomous Discovery for Science (MADSci) Python Client and CLI.""" diff --git a/madsci/madsci_client/madsci/client/cli/__init__.py b/madsci/madsci_client/madsci/client/cli/__init__.py new file mode 100644 index 0000000..25c755f --- /dev/null +++ b/madsci/madsci_client/madsci/client/cli/__init__.py @@ -0,0 +1,30 @@ +"""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.workcell_cli import workcell + +console = Console() + + +@tui() +@click.group() +def root_cli(): + """MADSci command line interface.""" + pass + + +@root_cli.command() +def version(): + """Display the MADSci client version.""" + console.print("MADSci Client v0.1.0") + + +root_cli.add_command(lab) +root_cli.add_command(workcell) + +if __name__ == "__main__": + root_cli() diff --git a/madsci/madsci_client/madsci/client/cli/lab_cli.py b/madsci/madsci_client/madsci/client/cli/lab_cli.py new file mode 100644 index 0000000..1ad9354 --- /dev/null +++ b/madsci/madsci_client/madsci/client/cli/lab_cli.py @@ -0,0 +1,241 @@ +"""Command Line Interface for managing MADSci Squid labs.""" + +import os +from pathlib import Path +from typing import Optional + +import click +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 search_up_and_down_for_pattern, to_snake_case + +console = Console() + + +@click.group() +def lab(): + """Manage labs.""" + pass + + +@lab.command() +@click.option( + "--name", "-n", type=str, help="The name of the lab to create. (Optional)" +) +@click.option( + "--description", "-d", type=str, help="The description of the lab. (Optional)" +) +@click.option( + "--path", + "-p", + type=str, + help="The location to create the lab definition in. (Optional)", +) +def create(name: Optional[str], description: Optional[str], path: Optional[str]): + """Create a new lab.""" + if not name: + name = console.input("Name: ") + if not description: + description = console.input("Description (optional): ") + lab_definition = LabDefinition(name=name, description=description) + console.print(lab_definition) + if not path: + path = Path.cwd() / f"{to_snake_case(name)}.lab.yaml" + new_path = console.input(f"Path (default: {path}): ") + if new_path: + path = Path(new_path) + if not path.exists(): + lab_definition.to_yaml(path) + else: + console.print(f"Lab definition file already exists: [bold]{path}[/]") + if console.input(r"Overwrite? \[y/n] ") == "y": + lab_definition.to_yaml(path) + + +@lab.command() +def list(): + """List all labs. Will list all labs in the current directory, subdirectories, and parent directories.""" + + # Search for .lab.yaml files in current dir and subdirs + lab_files = search_up_and_down_for_pattern("*.lab.yaml") + + if lab_files: + for lab_file in sorted(set(lab_files)): + lab_definition = LabDefinition.from_yaml(lab_file) + console.print( + f"[bold]{lab_definition.name}[/]: {lab_definition.description} ({lab_file})" + ) + else: + print("No lab definitions found") + + +@lab.command() +@click.argument("name", type=str, required=False) +@click.option( + "--path", + "-p", + type=str, + help="The path to the lab definition (Required if name is not provided).", +) +def info(name: Optional[str], path: Optional[str]): + """Get information about a lab. Either provide a name or a path to the lab definition. If both are provided, the name will be ignored. If there are multiple labs with the same name, all will be listed.""" + if path: + lab_definition = LabDefinition.from_yaml(path) + pprint(lab_definition) + return + + if name: + # Search for .lab.yaml files in current dir and subdirs + lab_files = search_up_and_down_for_pattern("*.lab.yaml") + for lab_file in lab_files: + lab_definition = LabDefinition.from_yaml(lab_file) + if lab_definition.name == name: + pprint(lab_definition) + + if not name and not path: + ctx = click.get_current_context() + click.echo(ctx.get_help()) + ctx.exit() + + +@lab.command() +@click.argument("name", type=str, required=False) +@click.option( + "--path", + "-p", + type=str, + help="The path to the lab definition (Required if name is not provided).", +) +def delete(name: Optional[str], path: Optional[str]): + """Delete a lab. Either provide a name or a path to the lab definition. If both are provided, the name will be ignored. If there are multiple labs with the same name, you will be prompted to confirm deletion of each of them.""" + if path: + lab_definition = LabDefinition.from_yaml(path) + console.print(f"Deleting lab: {lab_definition.name} ({path})") + if console.input(r"Are you sure? \[y/n] ") == "y": + Path(path).unlink() + console.print(f"Deleted {path}") + return + + if name: + # Search for .lab.yaml files in current dir and subdirs + lab_files = search_up_and_down_for_pattern("*.lab.yaml") + found = False + for lab_file in lab_files: + lab_definition = LabDefinition.from_yaml(lab_file) + if lab_definition.name == name: + found = True + console.print(f"Deleting lab: {lab_definition.name} ({lab_file})") + if console.input(r"Are you sure? \[y/n] ") == "y": + Path(lab_file).unlink() + console.print(f"Deleted {lab_file}") + if not found: + console.print(f"No lab definition found for [bold]{name}[/]") + + if not name and not path: + ctx = click.get_current_context() + click.echo(ctx.get_help()) + ctx.exit() + + +@lab.command() +@click.argument("name", type=str, required=False) +@click.option("--path", "-p", type=str, help="The path to the lab definition.") +def validate(name: Optional[str], path: Optional[str]): + """Validate a lab definition file by name or path. If no name or path is provided, will search for .lab.yaml files in the current file tree.""" + + if path: + lab_definition = LabDefinition.from_yaml(path) + console.print(lab_definition) + return + + if name: + # *Search for .lab.yaml files in current dir and subdirs + lab_files = search_up_and_down_for_pattern("*.lab.yaml") + for lab_file in lab_files: + lab_definition = LabDefinition.from_yaml(lab_file) + if lab_definition.name == name: + console.print(lab_definition) + return + + console.print(f"No lab definition found for [bold]{name}[/]") + return + + if not name and not path: + console.print( + "No lab file specified, searching for .lab.yaml files in current file tree..." + ) + + lab_files = search_up_and_down_for_pattern("*.lab.yaml") + for lab_file in lab_files: + console.print(f"Validating {lab_file}...") + lab_definition = LabDefinition.from_yaml(lab_file) + console.print(lab_definition) + return + + +def run_command(command: str, lab_definition: LabDefinition, path: Optional[str]): + """Run a command in a lab.""" + console.print( + f"Running command: [bold]{command}[/] ({lab_definition.commands[command]}) in lab: [bold]{lab_definition.name}[/] ({path})" + ) + print(os.popen(lab_definition.commands[command]).read()) + + +@lab.command() +@click.argument("command", type=str) +@click.option("--path", "-p", type=str, help="The path to the lab definition.") +@click.option("--name", "-n", type=str, help="The name of the lab.") +def run(command: str, path: Optional[str], name: Optional[str]): + """Run a command in a lab. Either provide a name or a path to the lab definition. If both are provided, the name will be ignored. If no name or path is provided, will search for .lab.yaml files in the current file tree and run the command in the first one found.""" + + if path: + lab_definition = LabDefinition.from_yaml(path) + if lab_definition.commands.get(command): + run_command(command, lab_definition, path) + return + else: + console.print( + f"Command [bold]{command}[/] not found in lab definition: [bold]{lab_definition.name}[/] ({path})" + ) + return + + if name: + # Search for .lab.yaml files in current dir and subdirs + lab_files = search_up_and_down_for_pattern("*.lab.yaml") + for lab_file in lab_files: + lab_definition = LabDefinition.from_yaml(lab_file) + if lab_definition.name == name: + if lab_definition.commands.get(command): + run_command(command, lab_definition, lab_file) + else: + console.print( + f"Command [bold]{command}[/] not found in lab definition: [bold]{lab_definition.name}[/] ({lab_file})" + ) + + console.print(f"No lab definition found for [bold]{name}[/]") + return + + if not name and not path: + console.print( + "No lab file specified, searching for .lab.yaml files in current file tree..." + ) + + lab_files = search_up_and_down_for_pattern("*.lab.yaml") + for lab_file in lab_files: + lab_definition = LabDefinition.from_yaml(lab_file) + if lab_definition.commands.get(command): + run_command(command, lab_definition, lab_file) + return + else: + console.print( + f"Command [bold]{command}[/] not found in lab definition: [bold]{lab_definition.name}[/] ({lab_file})" + ) + return + + console.print( + f"No lab definition file found to run command: [bold]{command}[/]" + ) + return diff --git a/madsci/madsci_client/madsci/client/cli/workcell_cli.py b/madsci/madsci_client/madsci/client/cli/workcell_cli.py new file mode 100644 index 0000000..5643cd1 --- /dev/null +++ b/madsci/madsci_client/madsci/client/cli/workcell_cli.py @@ -0,0 +1,174 @@ +"""Command Line Interface for managing MADSci Squid workcells.""" + +from pathlib import Path +from typing import Optional + +import click +from rich import print +from rich.console import Console +from rich.pretty import pprint + +from madsci.common.types.workcell_types import Workcell +from madsci.common.utils import search_up_and_down_for_pattern, to_snake_case + +console = Console() + + +@click.group() +def workcell(): + """Manage workcells.""" + pass + + +@workcell.command() +@click.option( + "--name", "-n", type=str, help="The name of the workcell to create. (Optional)" +) +@click.option( + "--description", "-d", type=str, help="The description of the workcell. (Optional)" +) +@click.option( + "--path", + "-p", + type=str, + help="The location to create the workcell definition in. (Optional)", +) +def create(name: Optional[str], description: Optional[str], path: Optional[str]): + """Create a new workcell.""" + if not name: + name = console.input("Name: ") + if not description: + description = console.input("Description (optional): ") + workcell = Workcell(name=name, description=description) + console.print(workcell) + if not path: + current_path = Path.cwd() + if current_path.name == "workcells": + path = current_path / f"{to_snake_case(name)}.workcell.yaml" + else: + path = current_path / "workcells" / f"{to_snake_case(name)}.workcell.yaml" + new_path = console.input(f"Path (default: {path}): ") + if new_path: + path = Path(new_path) + if not path.exists(): + path.parent.mkdir(parents=True, exist_ok=True) + workcell.to_yaml(path) + else: + console.print(f"Workcell definition file already exists: [bold]{path}[/]") + if console.input(r"Overwrite? \[y/n] ") == "y": + workcell.to_yaml(path) + + +@workcell.command() +def list(): + """List all workcells. Will list all workcells in the current directory, subdirectories, and parent directories.""" + workcell_files = search_up_and_down_for_pattern("*.workcell.yaml") + + if workcell_files: + for workcell_file in sorted(set(workcell_files)): + workcell = Workcell.from_yaml(workcell_file) + console.print( + f"[bold]{workcell.name}[/]: {workcell.description} ({workcell_file})" + ) + else: + print("No workcell definitions found") + + +@workcell.command() +@click.argument("name", type=str, required=False) +@click.option( + "--path", + "-p", + type=str, + help="The path to the workcell definition (Required if name is not provided).", +) +def info(name: Optional[str], path: Optional[str]): + """Get information about a workcell. Either provide a name or a path to the workcell definition.""" + if path: + workcell = Workcell.from_yaml(path) + pprint(workcell) + return + + if name: + workcell_files = search_up_and_down_for_pattern("*.workcell.yaml") + for workcell_file in workcell_files: + workcell = Workcell.from_yaml(workcell_file) + if workcell.name == name: + pprint(workcell) + + if not name and not path: + ctx = click.get_current_context() + click.echo(ctx.get_help()) + ctx.exit() + + +@workcell.command() +@click.argument("name", type=str, required=False) +@click.option( + "--path", + "-p", + type=str, + help="The path to the workcell definition (Required if name is not provided).", +) +def delete(name: Optional[str], path: Optional[str]): + """Delete a workcell. Either provide a name or a path to the workcell definition.""" + if path: + workcell = Workcell.from_yaml(path) + console.print(f"Deleting workcell: {workcell.name} ({path})") + if console.input(r"Are you sure? \[y/n] ") == "y": + Path(path).unlink() + console.print(f"Deleted {path}") + return + + if name: + workcell_files = search_up_and_down_for_pattern("*.workcell.yaml") + found = False + for workcell_file in workcell_files: + workcell = Workcell.from_yaml(workcell_file) + if workcell.name == name: + console.print(f"Deleting workcell: {workcell.name} ({workcell_file})") + if console.input(r"Are you sure? \[y/n] ") == "y": + Path(workcell_file).unlink() + console.print(f"Deleted {workcell_file}") + found = True + if not found: + console.print(f"No workcell definition found for [bold]{name}[/]") + + if not name and not path: + ctx = click.get_current_context() + click.echo(ctx.get_help()) + ctx.exit() + + +@workcell.command() +@click.argument("name", type=str, required=False) +@click.option("--path", "-p", type=str, help="The path to the workcell definition.") +def validate(name: Optional[str], path: Optional[str]): + """Validate a workcell definition file by name or path.""" + if path: + workcell = Workcell.from_yaml(path) + console.print(workcell) + return + + if name: + workcell_files = search_up_and_down_for_pattern("*.workcell.yaml") + for workcell_file in workcell_files: + workcell = Workcell.from_yaml(workcell_file) + if workcell.name == name: + console.print(workcell) + return + + console.print(f"No workcell definition found for [bold]{name}[/]") + return + + if not name and not path: + console.print( + "No workcell file specified, searching for .workcell.yaml files in current file tree..." + ) + + workcell_files = search_up_and_down_for_pattern("*.workcell.yaml") + for workcell_file in workcell_files: + console.print(f"Validating {workcell_file}...") + workcell = Workcell.from_yaml(workcell_file) + console.print(workcell) + return diff --git a/madsci/madsci_client/pyproject.toml b/madsci/madsci_client/pyproject.toml new file mode 100644 index 0000000..5118ea5 --- /dev/null +++ b/madsci/madsci_client/pyproject.toml @@ -0,0 +1,137 @@ +[project] +name = "madsci.client" +dynamic = ["version"] +description = "The Modular Autonomous Discovery for Science (MADSci) Python Client and CLI." +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", + "click>=8.1.7", + "trogon>=0.6.0" +] + +[project.urls] +Homepage = "https://github.com/AD-SDL/MADSci" + +[project.scripts] +madsci = "madsci.client.cli:root_cli" + +###################### +# Build Info + Tools # +###################### + +[build-system] +requires = ["pdm-backend"] +build-backend = "pdm.backend" + +#[dependency-groups] +#dev = ["-e madsci-common @ file:///${PROJECT_ROOT}/../madsci_common"] + +##################### +# Development Tools # +##################### + +[tool.ruff] +# https://docs.astral.sh/ruff/configuration/ + +# Exclude a variety of commonly ignored directories. +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".mypy_cache", + ".nox", + ".pants.d", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "venv", + ".venv", + "docs", +] + +# Same as Black. +line-length = 88 +indent-width = 4 + +# Assume Python 3.9 +target-version = "py39" + +[tool.ruff.lint] +# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. +select = [ + # pycodestyle + "E", + # Pyflakes + "F", + # pyupgrade + # "UP", + # flake8-bugbear + "B", + # flake8-simplify + # "SIM", + # isort + "I", + # Warning + "W", + # pydocstyle + "D100", "D101", "D102", "D103", "D104", "D105", "D106", "D107", + # ruff + # "RUF" +] +ignore = [ + "E501", # Line too long + "B006", # Do not use mutable data structures for argument defaults +] + +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = [] + +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +[tool.ruff.format] +# Like Black, use double quotes for strings. +quote-style = "double" + +# Like Black, indent with spaces, rather than tabs. +indent-style = "space" + +# Like Black, respect magic trailing commas. +skip-magic-trailing-comma = false + +# Like Black, automatically detect the appropriate line ending. +line-ending = "auto" + +[tool.pytest.ini_options] +# https://docs.pytest.org/en/stable/customize.html +addopts = "-x --ignore-glob=**/test_module/*" +junit_family="xunit1" +filterwarnings = [ + "ignore::DeprecationWarning", + "ignore::pottery.exceptions.InefficientAccessWarning", +] + + +[tool.pdm.version] +source = "scm" +fallback_version = "0.0.0" diff --git a/madsci/madsci_common/README.md b/madsci/madsci_common/README.md new file mode 100644 index 0000000..e69de29 diff --git a/madsci/madsci_common/madsci/common/__init__.py b/madsci/madsci_common/madsci/common/__init__.py new file mode 100644 index 0000000..c1d2e45 --- /dev/null +++ b/madsci/madsci_common/madsci/common/__init__.py @@ -0,0 +1,3 @@ +"""Common code for the MADSci project.""" + +pass diff --git a/madsci/madsci_common/madsci/common/types/__init__.py b/madsci/madsci_common/madsci/common/types/__init__.py new file mode 100644 index 0000000..ed4cdec --- /dev/null +++ b/madsci/madsci_common/madsci/common/types/__init__.py @@ -0,0 +1,44 @@ +""" +Base types for MADSci. +""" + +import json +from pathlib import Path +from typing import Type, TypeVar, Union + +import yaml +from sqlmodel import SQLModel +from ulid import ULID + +_T = TypeVar("_T") + +PathLike = Union[str, Path] + + +def new_ulid_str() -> str: + """ + Generate a new ULID string. + """ + return str(ULID()) + + +class BaseModel(SQLModel, use_enum_values=True): + """ + Parent class for all MADSci data models. + """ + + def to_yaml(self, path: PathLike) -> None: + """ + Allows all derived data models to be exported into yaml. + """ + with open(path, mode="w") as fp: + yaml.dump(json.loads(self.json()), fp, indent=4, sort_keys=False) + + @classmethod + def from_yaml(cls: Type[_T], path: PathLike) -> _T: + """ + Allows all derived data models to be loaded from yaml. + """ + with open(path) as fp: + raw_data = yaml.safe_load(fp) + return cls(**raw_data) diff --git a/madsci/madsci_common/madsci/common/types/squid_types.py b/madsci/madsci_common/madsci/common/types/squid_types.py new file mode 100644 index 0000000..9c857dc --- /dev/null +++ b/madsci/madsci_common/madsci/common/types/squid_types.py @@ -0,0 +1,67 @@ +"""Types for MADSci Squid configuration.""" + +from typing import Dict, List, Optional, Union + +from pydantic.functional_validators import field_validator +from sqlmodel.main import Field + +from madsci.common.types import BaseModel, PathLike, new_ulid_str +from madsci.common.types.validators import ulid_validator +from madsci.common.types.workcell_types import WorkcellConfig + + +class LabDefinition(BaseModel): + """Definition for a MADSci Lab.""" + + name: str = Field(title="Name", description="The name of the lab.") + lab_id: str = Field( + title="Lab ID", + description="The ID of the lab.", + default_factory=new_ulid_str, + ) + description: Optional[str] = Field( + default=None, + title="Description", + description="A description of the lab.", + ) + lab_server: "LabServerConfig" = Field( + title="Lab Server Configuration", + default_factory=lambda: LabServerConfig(), + description="The configuration for the lab server.", + ) + workcells: List[Union["WorkcellConfig", PathLike]] = Field( + default_factory=list, + title="Workcells", + description="The workcells in the lab.", + ) + commands: Dict[str, str] = Field( + default_factory=dict, + title="Commands", + description="Commands for operating the lab.", + ) + + @field_validator("commands") + def validate_commands(cls, v: Dict[str, str]) -> Dict[str, str]: + """Validate the commands.""" + if v: + for command in v: + if not str.isalnum(command): + raise ValueError(f"Command '{command}' must be alphanumeric") + return v + + is_ulid = field_validator("lab_id")(ulid_validator) + + +class LabServerConfig(BaseModel): + """Configuration for a MADSci Lab Server.""" + + host: str = Field( + default="localhost", + title="Server Host", + description="The hostname or IP address of the Squid Lab Server.", + ) + port: int = Field( + default=8000, + title="Server Port", + description="The port number of the Squid Lab Server.", + ) diff --git a/madsci/madsci_common/madsci/common/types/validators.py b/madsci/madsci_common/madsci/common/types/validators.py new file mode 100644 index 0000000..31e0503 --- /dev/null +++ b/madsci/madsci_common/madsci/common/types/validators.py @@ -0,0 +1,13 @@ +"""Common validators for MADSci-derived types.""" + +from pydantic import ValidationError, ValidationInfo +from ulid import ULID + + +def ulid_validator(id: str, info: ValidationInfo) -> str: + """Validates that a string field is a valid ULID.""" + try: + ULID.from_str(id) + return id + except ValueError as e: + raise ValidationError(f"Invalid ULID {id} for field {info.field_name}") from e diff --git a/madsci/madsci_common/madsci/common/types/workcell_types.py b/madsci/madsci_common/madsci/common/types/workcell_types.py new file mode 100644 index 0000000..ddcb648 --- /dev/null +++ b/madsci/madsci_common/madsci/common/types/workcell_types.py @@ -0,0 +1,50 @@ +"""Types for MADSci Workcell configuration.""" + +from typing import Optional + +from pydantic.functional_validators import field_validator +from sqlmodel.main import Field + +from madsci.common.types import BaseModel, new_ulid_str +from madsci.common.types.validators import ulid_validator + + +class Workcell(BaseModel): + """Configuration for a MADSci Workcell.""" + + name: str = Field( + title="Workcell Name", + description="The name of the workcell.", + ) + workcell_id: str = Field( + title="Workcell ID", + description="The ID of the workcell.", + default_factory=new_ulid_str, + ) + description: Optional[str] = Field( + default=None, + title="Workcell Description", + description="A description of the workcell.", + ) + config: "WorkcellConfig" = Field( + title="Workcell Configuration", + description="The configuration for the workcell.", + default_factory=lambda: WorkcellConfig(), + ) + + is_ulid = field_validator("workcell_id")(ulid_validator) + + +class WorkcellConfig(BaseModel): + """Configuration for a MADSci Workcell.""" + + scheduler_update_interval: float = Field( + default=0.1, + title="Scheduler Update Interval", + description="The interval at which the scheduler runs, in seconds.", + ) + module_update_interval: float = Field( + default=1.0, + title="Module Update Interval", + description="The interval at which the workcell queries its modules' states, in seconds.", + ) diff --git a/madsci/madsci_common/madsci/common/utils.py b/madsci/madsci_common/madsci/common/utils.py new file mode 100644 index 0000000..b70f63c --- /dev/null +++ b/madsci/madsci_common/madsci/common/utils.py @@ -0,0 +1,28 @@ +"""Utilities for the MADSci project.""" + +from pathlib import Path +from typing import List + + +def to_snake_case(name: str) -> str: + """Convert a string to snake case. + + Handles conversion from camelCase and PascalCase to snake_case. + """ + import re + + name = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name) + name = re.sub("([a-z0-9])([A-Z])", r"\1_\2", name) + return name.lower().replace(" ", "_") + + +def search_up_and_down_for_pattern(pattern: str) -> List[str]: + """Search up and down for a pattern.""" + import glob + import pathlib + + results = [] + results.extend(glob.glob(str(Path("./**") / pattern), recursive=True)) + for parent in pathlib.Path.cwd().parents: + results.extend(glob.glob(str(parent / pattern), recursive=True)) + return results diff --git a/madsci/madsci_common/pyproject.toml b/madsci/madsci_common/pyproject.toml new file mode 100644 index 0000000..4a17772 --- /dev/null +++ b/madsci/madsci_common/pyproject.toml @@ -0,0 +1,131 @@ +[project] +name = "madsci.common" +dynamic = ["version"] +description = "The Modular Autonomous Discovery for Science (MADSci) Common Definitions and Utilities." +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 = [ + "pydantic>=2.9.2", + "PyYAML>=6.0.2", + "sqlmodel>=0.0.22", + "python-ulid[pydantic]>=3.0.0", +] + +[project.urls] +Homepage = "https://github.com/AD-SDL/MADSci" + +###################### +# Build Info + Tools # +###################### + +[build-system] +requires = ["pdm-backend"] +build-backend = "pdm.backend" + +[tool.pdm.version] +source = "scm" +fallback_version = "0.0.0" + +##################### +# Development Tools # +##################### + +[tool.ruff] +# https://docs.astral.sh/ruff/configuration/ + +# Exclude a variety of commonly ignored directories. +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".mypy_cache", + ".nox", + ".pants.d", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "venv", + ".venv", + "docs", +] + +# Same as Black. +line-length = 88 +indent-width = 4 + +# Assume Python 3.9 +target-version = "py39" + +[tool.ruff.lint] +# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. +select = [ + # pycodestyle + "E", + # Pyflakes + "F", + # pyupgrade + # "UP", + # flake8-bugbear + "B", + # flake8-simplify + # "SIM", + # isort + "I", + # Warning + "W", + # pydocstyle + "D100", "D101", "D102", "D103", "D104", "D105", "D106", "D107", + # ruff + # "RUF" +] +ignore = [ + "E501", # Line too long + "B006", # Do not use mutable data structures for argument defaults +] + +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = [] + +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +[tool.ruff.format] +# Like Black, use double quotes for strings. +quote-style = "double" + +# Like Black, indent with spaces, rather than tabs. +indent-style = "space" + +# Like Black, respect magic trailing commas. +skip-magic-trailing-comma = false + +# Like Black, automatically detect the appropriate line ending. +line-ending = "auto" + +[tool.pytest.ini_options] +# https://docs.pytest.org/en/stable/customize.html +addopts = "-x --ignore-glob=**/test_module/*" +junit_family="xunit1" +filterwarnings = [ + "ignore::DeprecationWarning", + "ignore::pottery.exceptions.InefficientAccessWarning", +] diff --git a/madsci/madsci_squid/README.md b/madsci/madsci_squid/README.md new file mode 100644 index 0000000..e69de29 diff --git a/madsci/madsci_squid/madsci/squid/__init__.py b/madsci/madsci_squid/madsci/squid/__init__.py new file mode 100644 index 0000000..2a769ab --- /dev/null +++ b/madsci/madsci_squid/madsci/squid/__init__.py @@ -0,0 +1,3 @@ +"""The MADSci Squid server and workcell engine.""" + +pass diff --git a/madsci/madsci_squid/pyproject.toml b/madsci/madsci_squid/pyproject.toml new file mode 100644 index 0000000..6b4516d --- /dev/null +++ b/madsci/madsci_squid/pyproject.toml @@ -0,0 +1,132 @@ +[project] +name = "madsci.squid" +dynamic = ["version"] +description = "The Modular Autonomous Discovery for Science (MADSci) Control Server and Scheduler, aka Squid." +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" +] + +[project.urls] +Homepage = "https://github.com/AD-SDL/MADSci" + + +###################### +# Build Info + Tools # +###################### + +[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"] + +##################### +# Development Tools # +##################### + +[tool.ruff] +# https://docs.astral.sh/ruff/configuration/ + +# Exclude a variety of commonly ignored directories. +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".mypy_cache", + ".nox", + ".pants.d", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "venv", + ".venv", + "docs", +] + +# Same as Black. +line-length = 88 +indent-width = 4 + +# Assume Python 3.9 +target-version = "py39" + +[tool.ruff.lint] +# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. +select = [ + # pycodestyle + "E", + # Pyflakes + "F", + # pyupgrade + # "UP", + # flake8-bugbear + "B", + # flake8-simplify + # "SIM", + # isort + "I", + # Warning + "W", + # pydocstyle + "D100", "D101", "D102", "D103", "D104", "D105", "D106", "D107", + # ruff + # "RUF" +] +ignore = [ + "E501", # Line too long + "B006", # Do not use mutable data structures for argument defaults +] + +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = [] + +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +[tool.ruff.format] +# Like Black, use double quotes for strings. +quote-style = "double" + +# Like Black, indent with spaces, rather than tabs. +indent-style = "space" + +# Like Black, respect magic trailing commas. +skip-magic-trailing-comma = false + +# Like Black, automatically detect the appropriate line ending. +line-ending = "auto" + +[tool.pytest.ini_options] +# https://docs.pytest.org/en/stable/customize.html +addopts = "-x --ignore-glob=**/test_module/*" +junit_family="xunit1" +filterwarnings = [ + "ignore::DeprecationWarning", + "ignore::pottery.exceptions.InefficientAccessWarning", +] diff --git a/pdm.lock b/pdm.lock new file mode 100644 index 0000000..c5d64ee --- /dev/null +++ b/pdm.lock @@ -0,0 +1,578 @@ +# 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:9fd3aeb77c2ee7181367d5f17c4e01af89f508eed28b776ac9a4d377364a543e" + +[[metadata.targets]] +requires_python = ">=3.9.1" + +[[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 = "click" +version = "8.1.7" +requires_python = ">=3.7" +summary = "Composable command line interface toolkit" +groups = ["dev"] +dependencies = [ + "colorama; platform_system == \"Windows\"", + "importlib-metadata; python_version < \"3.8\"", +] +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[[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 = ["dev"] +marker = "platform_system == \"Windows\"" +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 = "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 = "linkify-it-py" +version = "2.0.3" +requires_python = ">=3.7" +summary = "Links recognition library with FULL unicode support." +groups = ["dev"] +dependencies = [ + "uc-micro-py", +] +files = [ + {file = "linkify-it-py-2.0.3.tar.gz", hash = "sha256:68cda27e162e9215c17d786649d1da0021a451bdc436ef9e0fa0ba5234b9b048"}, + {file = "linkify_it_py-2.0.3-py3-none-any.whl", hash = "sha256:6bcbc417b0ac14323382aef5c5192c0075bf8a9d6b41820a2b66371eac6b6d79"}, +] + +[[package]] +name = "madsci-client" +version = "0.1.dev0" +requires_python = ">=3.9.1" +editable = true +path = "./madsci/madsci_client" +summary = "The Modular Autonomous Discovery for Science (MADSci) Python Client and CLI." +groups = ["dev"] +dependencies = [ + "click>=8.1.7", + "madsci-common", + "trogon>=0.6.0", +] + +[[package]] +name = "madsci-common" +version = "0.1.dev0" +requires_python = ">=3.9.1" +editable = true +path = "./madsci/madsci_common" +summary = "The Modular Autonomous Discovery for Science (MADSci) Common Definitions and Utilities." +groups = ["dev"] +dependencies = [ + "PyYAML>=6.0.2", + "pydantic>=2.9.2", + "python-ulid[pydantic]>=3.0.0", + "sqlmodel>=0.0.22", +] + +[[package]] +name = "madsci-squid" +version = "0.1.dev0" +requires_python = ">=3.9.1" +editable = true +path = "./madsci/madsci_squid" +summary = "The Modular Autonomous Discovery for Science (MADSci) Control Server and Scheduler, aka Squid." +groups = ["dev"] +dependencies = [ + "madsci-common", +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +requires_python = ">=3.8" +summary = "Python port of markdown-it. Markdown parsing, done right!" +groups = ["dev"] +dependencies = [ + "mdurl~=0.1", +] +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +extras = ["linkify", "plugins"] +requires_python = ">=3.8" +summary = "Python port of markdown-it. Markdown parsing, done right!" +groups = ["dev"] +dependencies = [ + "linkify-it-py<3,>=1", + "markdown-it-py==3.0.0", + "mdit-py-plugins", +] +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[[package]] +name = "mdit-py-plugins" +version = "0.4.2" +requires_python = ">=3.8" +summary = "Collection of plugins for markdown-it-py" +groups = ["dev"] +dependencies = [ + "markdown-it-py<4.0.0,>=1.0.0", +] +files = [ + {file = "mdit_py_plugins-0.4.2-py3-none-any.whl", hash = "sha256:0c673c3f889399a33b95e88d2f0d111b4447bdfea7f237dab2d488f459835636"}, + {file = "mdit_py_plugins-0.4.2.tar.gz", hash = "sha256:5f2cd1fdb606ddf152d37ec30e46101a60512bc0e5fa1a7002c36647b09e26b5"}, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +requires_python = ">=3.7" +summary = "Markdown URL utilities" +groups = ["dev"] +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + +[[package]] +name = "platformdirs" +version = "4.3.6" +requires_python = ">=3.8" +summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +groups = ["dev"] +files = [ + {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, + {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, +] + +[[package]] +name = "pydantic" +version = "2.9.2" +requires_python = ">=3.8" +summary = "Data validation using Python type hints" +groups = ["dev"] +dependencies = [ + "annotated-types>=0.6.0", + "pydantic-core==2.23.4", + "typing-extensions>=4.12.2; python_version >= \"3.13\"", + "typing-extensions>=4.6.1; python_version < \"3.13\"", +] +files = [ + {file = "pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12"}, + {file = "pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f"}, +] + +[[package]] +name = "pydantic-core" +version = "2.23.4" +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.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b"}, + {file = "pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2"}, + {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f"}, + {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3"}, + {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071"}, + {file = "pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119"}, + {file = "pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f"}, + {file = "pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8"}, + {file = "pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e"}, + {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b"}, + {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0"}, + {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64"}, + {file = "pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f"}, + {file = "pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3"}, + {file = "pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231"}, + {file = "pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36"}, + {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126"}, + {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e"}, + {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24"}, + {file = "pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84"}, + {file = "pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9"}, + {file = "pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc"}, + {file = "pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b"}, + {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327"}, + {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6"}, + {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f"}, + {file = "pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769"}, + {file = "pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5"}, + {file = "pydantic_core-2.23.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a4fa4fc04dff799089689f4fd502ce7d59de529fc2f40a2c8836886c03e0175a"}, + {file = "pydantic_core-2.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7df63886be5e270da67e0966cf4afbae86069501d35c8c1b3b6c168f42cb36"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcedcd19a557e182628afa1d553c3895a9f825b936415d0dbd3cd0bbcfd29b4b"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f54b118ce5de9ac21c363d9b3caa6c800341e8c47a508787e5868c6b79c9323"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d2f57d3e1379a9525c5ab067b27dbb8a0642fb5d454e17a9ac434f9ce523e3"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de6d1d1b9e5101508cb37ab0d972357cac5235f5c6533d1071964c47139257df"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1278e0d324f6908e872730c9102b0112477a7f7cf88b308e4fc36ce1bdb6d58c"}, + {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a6b5099eeec78827553827f4c6b8615978bb4b6a88e5d9b93eddf8bb6790f55"}, + {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e55541f756f9b3ee346b840103f32779c695a19826a4c442b7954550a0972040"}, + {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c7ba8ffb6d6f8f2ab08743be203654bb1aaa8c9dcb09f82ddd34eadb695605"}, + {file = "pydantic_core-2.23.4-cp39-none-win32.whl", hash = "sha256:37b0fe330e4a58d3c58b24d91d1eb102aeec675a3db4c292ec3928ecd892a9a6"}, + {file = "pydantic_core-2.23.4-cp39-none-win_amd64.whl", hash = "sha256:1498bec4c05c9c787bde9125cfdcc63a41004ff167f495063191b863399b1a29"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433"}, + {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:78ddaaa81421a29574a682b3179d4cf9e6d405a09b99d93ddcf7e5239c742e21"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:883a91b5dd7d26492ff2f04f40fbb652de40fcc0afe07e8129e8ae779c2110eb"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88ad334a15b32a791ea935af224b9de1bf99bcd62fabf745d5f3442199d86d59"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233710f069d251feb12a56da21e14cca67994eab08362207785cf8c598e74577"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19442362866a753485ba5e4be408964644dd6a09123d9416c54cd49171f50744"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:624e278a7d29b6445e4e813af92af37820fafb6dcc55c012c834f9e26f9aaaef"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5ef8f42bec47f21d07668a043f077d507e5bf4e668d5c6dfe6aaba89de1a5b8"}, + {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:aea443fffa9fbe3af1a9ba721a87f926fe548d32cab71d188a6ede77d0ff244e"}, + {file = "pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863"}, +] + +[[package]] +name = "pygments" +version = "2.18.0" +requires_python = ">=3.8" +summary = "Pygments is a syntax highlighting package written in Python." +groups = ["dev"] +files = [ + {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, + {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, +] + +[[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 = "rich" +version = "13.9.4" +requires_python = ">=3.8.0" +summary = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +groups = ["dev"] +dependencies = [ + "markdown-it-py>=2.2.0", + "pygments<3.0.0,>=2.13.0", + "typing-extensions<5.0,>=4.0.0; python_version < \"3.11\"", +] +files = [ + {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"}, + {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"}, +] + +[[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 = "textual" +version = "0.85.1" +requires_python = "<4.0.0,>=3.8.1" +summary = "Modern Text User Interface framework" +groups = ["dev"] +dependencies = [ + "markdown-it-py[linkify,plugins]>=2.1.0", + "platformdirs<5,>=3.6.0", + "rich>=13.3.3", + "typing-extensions<5.0.0,>=4.4.0", +] +files = [ + {file = "textual-0.85.1-py3-none-any.whl", hash = "sha256:a1a064c67b9b81cfa0c1b14298aa52221855aa4a56ad17a9b89a5594c73657a8"}, + {file = "textual-0.85.1.tar.gz", hash = "sha256:9966214390fad9a84c3f69d49398487897577f5fa788838106dd77bd7babc9cd"}, +] + +[[package]] +name = "trogon" +version = "0.6.0" +requires_python = "<4.0.0,>=3.8.1" +summary = "Automatically generate a Textual TUI for your Click CLI" +groups = ["dev"] +dependencies = [ + "click>=8.0.0", + "textual>=0.61.0", +] +files = [ + {file = "trogon-0.6.0-py3-none-any.whl", hash = "sha256:fb5b6c25acd7a0eaba8d2cd32a57f1d80c26413cea737dad7a4eebcda56060e0"}, + {file = "trogon-0.6.0.tar.gz", hash = "sha256:fd1abfeb7b15d79d6e6cfc9e724aad2a2728812e4713a744d975f133e7ec73a4"}, +] + +[[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 = "uc-micro-py" +version = "1.0.3" +requires_python = ">=3.7" +summary = "Micro subset of unicode data files for linkify-it-py projects." +groups = ["dev"] +files = [ + {file = "uc-micro-py-1.0.3.tar.gz", hash = "sha256:d321b92cff673ec58027c04015fcaa8bb1e005478643ff4a500882eaab88c48a"}, + {file = "uc_micro_py-1.0.3-py3-none-any.whl", hash = "sha256:db1dffff340817673d7b466ec86114a9dc0e9d4d9b5ba229d9d60e5c12600cd5"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..eb04c9e --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,103 @@ +[project] +name = "madsci" +dependencies = [] +requires-python = ">=3.9.1" +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", +] +[tool.pdm.version] +source = "scm" +fallback_version = "0.0.0" + +##################### +# Development Tools # +##################### + +[tool.ruff] +# https://docs.astral.sh/ruff/configuration/ + +# Exclude a variety of commonly ignored directories. +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".mypy_cache", + ".nox", + ".pants.d", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "venv", + ".venv", + "docs", +] + +# Same as Black. +line-length = 88 +indent-width = 4 + +# Assume Python 3.9 +target-version = "py39" + +[tool.ruff.lint] +# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. +select = [ + # pycodestyle + "E", + # Pyflakes + "F", + # pyupgrade + # "UP", + # flake8-bugbear + "B", + # flake8-simplify + # "SIM", + # isort + "I", + # Warning + "W", + # pydocstyle + "D100", "D101", "D102", "D103", "D104", "D105", "D106", "D107", + # ruff + # "RUF" +] +ignore = [ + "E501", # Line too long + "B006", # Do not use mutable data structures for argument defaults +] + +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = [] + +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +[tool.ruff.format] +# Like Black, use double quotes for strings. +quote-style = "double" + +# Like Black, indent with spaces, rather than tabs. +indent-style = "space" + +# Like Black, respect magic trailing commas. +skip-magic-trailing-comma = false + +# Like Black, automatically detect the appropriate line ending. +line-ending = "auto"