diff --git a/compose_api/api/routers/simulation.py b/compose_api/api/routers/simulation.py index 7a762a8..89ab6c8 100644 --- a/compose_api/api/routers/simulation.py +++ b/compose_api/api/routers/simulation.py @@ -4,15 +4,15 @@ import zipfile from pathlib import Path -from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, UploadFile -from starlette.responses import PlainTextResponse - -from compose_api.btools.bsander.bsandr_utils.input_types import ( +from bsedic.execution import execute_bsedic +from bsedic.utils.input_types import ( ContainerizationEngine, ContainerizationTypes, ProgramArguments, ) -from compose_api.btools.bsander.execution import execute_bsander +from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, UploadFile +from starlette.responses import PlainTextResponse + from compose_api.btools.sedml_compiler.sedml_representation_compiler import SimpleSedmlCompiler, ToolSuites from compose_api.btools.sedml_processor import SimpleSedmlRepresentation from compose_api.common.gateway.models import RouterConfig @@ -101,7 +101,7 @@ async def analyze_simulation(uploaded_file: UploadFile) -> str: uploaded_file_path = f"{tmp_dir}/{uploaded_file.filename}" with open(uploaded_file_path, "wb") as fh: fh.write(contents) - singularity_rep, experiment_dep = execute_bsander( + singularity_rep, experiment_dep = execute_bsedic( ProgramArguments( input_file_path=uploaded_file_path, output_dir=tmp_dir, diff --git a/compose_api/btools/bsander/__init__.py b/compose_api/btools/bsander/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/compose_api/btools/bsander/bsandr_utils/__init__.py b/compose_api/btools/bsander/bsandr_utils/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/compose_api/btools/bsander/bsandr_utils/experiment_archive.py b/compose_api/btools/bsander/bsandr_utils/experiment_archive.py deleted file mode 100644 index 4832cb9..0000000 --- a/compose_api/btools/bsander/bsandr_utils/experiment_archive.py +++ /dev/null @@ -1,34 +0,0 @@ -# This file contains utility functions to deal with parsing input archives for relevant info -import os -import zipfile - - -def _extract_pbif_from_zip(archive_path: str, output_dir: str) -> str: - archive_shortname = os.path.basename(archive_path).split(".")[0] - extraction_destination = os.path.join(output_dir, archive_shortname) - os.makedirs(extraction_destination, exist_ok=True) - target_pbif = None - with zipfile.ZipFile(archive_path) as archive: - for name in archive.namelist(): - current_file = archive.extract(name, extraction_destination) - if (not name.endswith(".pbif") and not name.endswith(".json")) or "/__MACOSX/._" in current_file: - continue - target_pbif = current_file # Note: This scheme does not support multiple pbif! - # TODO: Allow for multi-pbif files? May require omex enforcment... - if target_pbif is None: - raise ValueError(f"Could not locate Process Bigraph Intermediate Format file within archive: {archive_path}") - return target_pbif - - -def _extract_pbif_from_omex(archive_path: str, output_dir: str) -> str: - # At the moment, we're not doing anything complicated... - return _extract_pbif_from_zip(archive_path, output_dir) - - -def extract_archive_returning_pbif_path(archive_path: str, output_dir: str) -> str: - if archive_path.endswith(".omex"): - return _extract_pbif_from_omex(archive_path, output_dir) - elif archive_path.endswith(".zip"): - return _extract_pbif_from_zip(archive_path, output_dir) - else: - raise Exception(f"Unsupported archive: {archive_path}") diff --git a/compose_api/btools/bsander/bsandr_utils/input_types.py b/compose_api/btools/bsander/bsandr_utils/input_types.py deleted file mode 100644 index 8e0c9a3..0000000 --- a/compose_api/btools/bsander/bsandr_utils/input_types.py +++ /dev/null @@ -1,70 +0,0 @@ -from dataclasses import dataclass -from enum import Enum - -from pydantic import BaseModel - - -class ContainerizationTypes(Enum): - NONE = 0 - SINGLE = 1 - MULTIPLE = 2 - - -class ContainerizationEngine(Enum): - NONE = 0 - DOCKER = 1 - APPTAINER = 2 - BOTH = 3 - - -class ContainerizationFileRepr(BaseModel): - representation: str - - -class ExperimentPrimaryDependencies(BaseModel): - pypi_dependencies: list[str] - conda_dependencies: list[str] - _compact_repr: str - - @staticmethod - def from_compact_repr(representation: str) -> "ExperimentPrimaryDependencies": - split_dep_type = representation.split(";") - if len(split_dep_type) != 2: - raise ValueError(f"Invalid primary dependency representation: {representation}") - pypi_dependencies = split_dep_type[0].split(",") - conda_dependencies = split_dep_type[1].split(",") - return ExperimentPrimaryDependencies(pypi_dependencies=pypi_dependencies, conda_dependencies=conda_dependencies) - - def __init__(self, pypi_dependencies: list[str], conda_dependencies: list[str]) -> None: - super().__init__(pypi_dependencies=pypi_dependencies, conda_dependencies=conda_dependencies) - self.pypi_dependencies = pypi_dependencies - self.conda_dependencies = conda_dependencies - self._compact_repr = ",".join(pypi_dependencies) + ";" + ",".join(conda_dependencies) - - def __str__(self) -> str: - pypi_dependencies: str = "PyPi Dependencies:\n\t" + "\n\t".join(self.pypi_dependencies) - conda_dependencies: str = "Conda Dependencies:\n\t" + "\n\t".join(self.conda_dependencies) - return pypi_dependencies + "\n" + ("-" * 25) + "\n" + conda_dependencies - - def __repr__(self) -> str: - pypi_dependencies: str = "pypi:" + ",pypi:".join(self.pypi_dependencies) - conda_dependencies: str = "conda:" + ",conda:".join(self.conda_dependencies) - return pypi_dependencies + "," + conda_dependencies - - def get_compact_repr(self) -> str: - return self._compact_repr - - def get_pypi_dependencies(self) -> list[str]: - return self.pypi_dependencies - - def get_conda_dependencies(self) -> list[str]: - return self.conda_dependencies - - -@dataclass -class ProgramArguments: - input_file_path: str - output_dir: str | None - passlist_entries: list[str] - containerization_type: ContainerizationTypes - containerization_engine: ContainerizationEngine diff --git a/compose_api/btools/bsander/bscram/__init__.py b/compose_api/btools/bsander/bscram/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/compose_api/btools/bsander/execution.py b/compose_api/btools/bsander/execution.py deleted file mode 100644 index cfc7111..0000000 --- a/compose_api/btools/bsander/execution.py +++ /dev/null @@ -1,91 +0,0 @@ -import os -import shutil - -from spython.main.parse.parsers import DockerParser # type: ignore[import-untyped] -from spython.main.parse.writers import SingularityWriter # type: ignore[import-untyped] - -from compose_api.btools.bsander.bsandr_utils.experiment_archive import extract_archive_returning_pbif_path -from compose_api.btools.bsander.bsandr_utils.input_types import ( - ContainerizationEngine, - ContainerizationFileRepr, - ContainerizationTypes, - ExperimentPrimaryDependencies, - ProgramArguments, -) -from compose_api.btools.bsander.pbic3g.containerization.container_constructor import ( - formulate_dockerfile_for_necessary_env, -) -from compose_api.btools.bsander.pbic3g.local_registry import load_local_modules - - -def execute_bsander( - original_program_arguments: ProgramArguments, -) -> tuple[ContainerizationFileRepr, ExperimentPrimaryDependencies]: - new_input_file_path: str - input_is_archive = original_program_arguments.input_file_path.endswith( - ".zip" - ) or original_program_arguments.input_file_path.endswith(".omex") - required_program_arguments: ProgramArguments - if input_is_archive: - new_input_file_path = extract_archive_returning_pbif_path( - original_program_arguments.input_file_path, str(original_program_arguments.output_dir) - ) - else: - new_input_file_path = os.path.join( - str(original_program_arguments.output_dir), os.path.basename(original_program_arguments.input_file_path) - ) - print(f"file copied to `{shutil.copy(original_program_arguments.input_file_path, new_input_file_path)}`") - required_program_arguments = ProgramArguments( - new_input_file_path, - original_program_arguments.output_dir, - original_program_arguments.passlist_entries, - original_program_arguments.containerization_type, - original_program_arguments.containerization_engine, - ) - - load_local_modules() # Collect Abstracts - # TODO: Add feature - resolve abstracts - - # Determine Dependencies - docker_template: ContainerizationFileRepr - returned_template: ContainerizationFileRepr - primary_dependencies: ExperimentPrimaryDependencies - docker_template, primary_dependencies = formulate_dockerfile_for_necessary_env(required_program_arguments) - returned_template = docker_template - if required_program_arguments.containerization_type != ContainerizationTypes.NONE: - if required_program_arguments.containerization_type != ContainerizationTypes.SINGLE: - raise NotImplementedError("Only single containerization is currently supported") - container_file_path: str - container_file_path = os.path.join(str(original_program_arguments.output_dir), "Dockerfile") - with open(container_file_path, "w") as docker_file: - docker_file.write(docker_template.representation) - if ( - required_program_arguments.containerization_engine == ContainerizationEngine.APPTAINER - or required_program_arguments.containerization_engine == ContainerizationEngine.BOTH - ): - dockerfile_path = container_file_path - container_file_path = os.path.join(str(original_program_arguments.output_dir), "singularity.def") - dockerfile_parser = DockerParser(dockerfile_path) - singularity_writer = SingularityWriter(dockerfile_parser.recipe) - results = singularity_writer.convert() - returned_template = ContainerizationFileRepr(representation=results) - with open(container_file_path, "w") as container_file: - container_file.write(results) - if required_program_arguments.containerization_engine != ContainerizationEngine.BOTH: - os.remove(dockerfile_path) - print(f"Container build file located at '{container_file_path}'") - - # Reconstitute if archive - if input_is_archive: - base_name = os.path.basename(original_program_arguments.input_file_path) - output_dir: str = ( - os.path.dirname(original_program_arguments.input_file_path) - if original_program_arguments.output_dir is None - else str(original_program_arguments.output_dir) - ) - new_archive_path = os.path.join(output_dir, base_name) - # Note: If no output dir is provided (dir is `None`), then input file WILL BE OVERWRITTEN - target_dir = os.path.join(str(original_program_arguments.output_dir), base_name.split(".")[0]) - shutil.make_archive(new_archive_path, "zip", target_dir) - shutil.move(new_archive_path + ".zip", new_archive_path) # get rid of extra suffix - return returned_template, primary_dependencies diff --git a/compose_api/btools/bsander/pbic3g/__init__.py b/compose_api/btools/bsander/pbic3g/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/compose_api/btools/bsander/pbic3g/containerization/__init__.py b/compose_api/btools/bsander/pbic3g/containerization/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/compose_api/btools/bsander/pbic3g/containerization/container_constructor.py b/compose_api/btools/bsander/pbic3g/containerization/container_constructor.py deleted file mode 100644 index a840890..0000000 --- a/compose_api/btools/bsander/pbic3g/containerization/container_constructor.py +++ /dev/null @@ -1,133 +0,0 @@ -import re -from typing import Optional - -from compose_api.btools.bsander.bsandr_utils.input_types import ( - ContainerizationFileRepr, - ExperimentPrimaryDependencies, - ProgramArguments, -) -from compose_api.btools.bsander.pbic3g.containerization.container_file import ( - get_generic_dockerfile_template, - pull_substitution_keys_from_document, -) - - -def formulate_dockerfile_for_necessary_env( - program_arguments: ProgramArguments, -) -> tuple[ContainerizationFileRepr, ExperimentPrimaryDependencies]: - docker_template: str = get_generic_dockerfile_template() - pb_document_str: str - with open(program_arguments.input_file_path) as pb_document_file: - pb_document_str = pb_document_file.read() - experiment_deps, updated_document_str = determine_dependencies(pb_document_str, program_arguments.passlist_entries) - if updated_document_str != pb_document_str: # we need to update file - with open(program_arguments.input_file_path, "w") as pb_document_file: - pb_document_file.write(updated_document_str) - for desired_field in generate_necessary_values(): - match_target: str = "$${#" + desired_field + "}" - if desired_field == "PYPI_DEPENDENCIES": - if len(experiment_deps.get_pypi_dependencies()) == 0: - docker_template = docker_template.replace(match_target, "# No PyPI dependencies!") - continue - pypi_section = """ -RUN python3 -m pip install $${#DEPENDENCIES} -""".strip() - dependency_str = convert_dependencies_to_installation_string_representation( - experiment_deps.get_pypi_dependencies() - ) - filled_section = pypi_section.replace("$${#DEPENDENCIES}", dependency_str) - docker_template = docker_template.replace(match_target, filled_section) - elif desired_field == "CONDA_FORGE_DEPENDENCIES": - if len(experiment_deps.get_conda_dependencies()) == 0: - docker_template = docker_template.replace(match_target, "# No conda dependencies!") - continue - conda_section = """ -RUN mkdir /micromamba -RUN curl -Ls https://micro.mamba.pm/api/micromamba/linux-64/latest | tar -xvj bin/micromamba -RUN mv bin/micromamba /usr/local/bin/ -RUN micromamba create -y -p /opt/conda -c conda-forge $${#DEPENDENCIES} python=3.12 -ENV PATH=/opt/conda/bin:$PATH -""".strip() - dependency_str = " ".join(experiment_deps.get_conda_dependencies()) - filled_section = conda_section.replace("$${#DEPENDENCIES}", dependency_str) - docker_template = docker_template.replace(match_target, filled_section) - else: - raise ValueError(f"unknown field in template dockerfile: {desired_field}") - - return ContainerizationFileRepr(representation=docker_template), experiment_deps - - -def generate_necessary_values() -> list[str]: - return pull_substitution_keys_from_document() - - -# Due to an assumption that we can not have all dependencies included -# in the same python environment, we need a solid address protocol to assume. -# going with: `pypi:[]@` -# ex: "pypi:copasi-basico[~0.8]@basico.model_io.load_model" (if this was a class, and not a function) -def determine_dependencies( # noqa: C901 - string_to_search: str, whitelist_entries: Optional[list[str]] = None -) -> tuple[ExperimentPrimaryDependencies, str]: - whitelist_mapping: dict[str, set[str]] | None - if whitelist_entries is not None: - whitelist_mapping = {} - for whitelist_entry in whitelist_entries: - entry = whitelist_entry.split("::") - if len(entry) != 2: - raise ValueError(f"invalid whitelist entry: {whitelist_entry}") - source, package = (entry[0], entry[1]) - if source not in whitelist_mapping: - whitelist_mapping[source] = set() - whitelist_mapping[source].add(package) - else: - whitelist_mapping = None - source_name_legal_syntax = r"[\w\-]+" - package_name_legal_syntax = r"[\w\-._~:/?#[\]@!$&'()*+,;=%]+" # package or git-http repo name - version_string_legal_syntax = ( - r"\[([\w><=~!*\-.]+)]" # hard brackets around alphanumeric plus standard python version constraint characters - ) - # stricter pattern of only legal python module names - # (letters and underscore first character, alphanumeric and underscore for remainder); must be at least 1 char long - import_name_legal_syntax = r"[A-Za-z_]\w*(\.[A-Za-z_]\w*)*" - known_sources = ["pypi", "conda"] - approved_dependencies: dict[str, list[str]] = {source: [] for source in known_sources} - regex_pattern = f"python:({source_name_legal_syntax})<({package_name_legal_syntax})({version_string_legal_syntax})?>@({import_name_legal_syntax})" # noqa: E501 - adjusted_search_string = str(string_to_search) - matches = re.findall(regex_pattern, string_to_search) - if len(matches) == 0: - local_protocol_matches = re.findall(f"local:{import_name_legal_syntax}", string_to_search) - if len(local_protocol_matches) == 0: - raise ValueError("No dependencies found in document; unable to generate environment.") - match_str_list: str = ",".join([str(match) for match in matches]) - if len(match_str_list) != 0: # For some reason, we can get a single "match" that's empty... - raise ValueError( - f"Document is using the following local protocols: `{match_str_list}`; unable to determine needed environment." # noqa: E501 - ) - for match in matches: - source_name = match[0] - package_name = match[1] - package_version = match[3] - if source_name not in known_sources: - raise ValueError(f"Unknown source `{source_name}` used; can not determine dependencies") - dependency_str = f"{package_name}{package_version}".strip() - if dependency_str in approved_dependencies[source_name]: - continue # We've already accounted for this dependency - if whitelist_mapping is not None: - # We need to validate against whitelist! - if source_name not in whitelist_mapping: - raise ValueError(f"Unapproved source `{source_name}` used; can not trust document") - if package_name not in whitelist_mapping[source_name]: - raise ValueError( - f"`{package_name}` from `{source_name}` is not a trusted package; can not trust document" - ) - approved_dependencies[source_name].append(dependency_str) - version_str = match[2] if package_version != "" else "" - complete_match = f"python:{source_name}<{package_name}{version_str}>@{match[4]}" - adjusted_search_string = adjusted_search_string.replace(complete_match, f"local:{match[4]}") - return ExperimentPrimaryDependencies( - approved_dependencies["pypi"], approved_dependencies["conda"] - ), adjusted_search_string.strip() - - -def convert_dependencies_to_installation_string_representation(dependencies: list[str]) -> str: - return "'" + "' '".join(dependencies) + "'" diff --git a/compose_api/btools/bsander/pbic3g/containerization/container_file.py b/compose_api/btools/bsander/pbic3g/containerization/container_file.py deleted file mode 100644 index 66ba3a1..0000000 --- a/compose_api/btools/bsander/pbic3g/containerization/container_file.py +++ /dev/null @@ -1,34 +0,0 @@ -import re - - -def get_generic_dockerfile_template() -> str: - return """ -FROM ghcr.io/astral-sh/uv:python3.12-bookworm - -RUN apt update -RUN apt upgrade -y -RUN apt install -y git curl - -## Dependency Installs -### Conda -$${#CONDA_FORGE_DEPENDENCIES} - -### PyPI -$${#PYPI_DEPENDENCIES} - -## -RUN mkdir /runtime -WORKDIR /runtime -RUN git clone https://github.com/biosimulators/bsew.git /runtime -RUN python3 -m pip install -e /runtime - -ENTRYPOINT ["python3", "/runtime/main.py"] -""".strip() - - -# Note the capture group; that's what re.findall will return! -_sub_keys: set[str] = {match for match in re.findall(r"\$\${#(\w+)}", get_generic_dockerfile_template())} # noqa: C416 - - -def pull_substitution_keys_from_document() -> list[str]: - return list(_sub_keys) diff --git a/compose_api/btools/bsander/pbic3g/dependency_resolution/__init__.py b/compose_api/btools/bsander/pbic3g/dependency_resolution/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/compose_api/btools/bsander/pbic3g/dependency_resolution/whitelist.py b/compose_api/btools/bsander/pbic3g/dependency_resolution/whitelist.py deleted file mode 100644 index 87c6151..0000000 --- a/compose_api/btools/bsander/pbic3g/dependency_resolution/whitelist.py +++ /dev/null @@ -1,2 +0,0 @@ -### This file will hold the complex code of version resolution and package allowances, once -### there is a need for such levels of inspection diff --git a/compose_api/btools/bsander/pbic3g/local_registry.py b/compose_api/btools/bsander/pbic3g/local_registry.py deleted file mode 100644 index 1349564..0000000 --- a/compose_api/btools/bsander/pbic3g/local_registry.py +++ /dev/null @@ -1,49 +0,0 @@ -### File that collects the abstract headers -import importlib.metadata -import pkgutil -import re - - -def load_local_modules() -> None: - print("Loading local registry...") - for package in importlib.metadata.distributions(): - if not does_package_require_bsail(package): - continue - # If a package requires BSail, it probably has abstractions for us; worth importing. - recursive_dynamic_import(package.name) - - -def does_package_require_bsail(package: importlib.metadata.Distribution) -> bool: - for key in package.metadata: - if key != "Requires-Dist": - continue - if not re.match(r"bsail \([=><\d.]+,?[=><\d.]+\)", package.metadata[key]): - continue - return True - return False - - -def recursive_dynamic_import(package_name: str) -> list[str]: - classes_to_import = [] - adjusted_package_name = package_name.replace("-", "_") - try: - module = importlib.import_module(adjusted_package_name) - except ModuleNotFoundError: - # TODO: Add code to try and find correct module name via accessing `top_level.txt`, - # and getting the correct module name - # find top-level.txt - # find correct module name - # return recursive_dynamic_import(correct_module_name) - raise ModuleNotFoundError(f"module {adjusted_package_name} not found") - # class_members = inspect.getmembers(module, inspect.isclass) - # for class_name, clazz in class_members: - # if not (issubclass(clazz, Process) or issubclass(clazz, Step)) or (clazz in [Process, Step, Composite]): - # continue - # classes_to_import.append((class_name, clazz)) - - modules_to_check = pkgutil.iter_modules(module.__path__) if hasattr(module, "__path__") else [] - for _module_loader, subname, _isPkg in modules_to_check: - # if not isPkg: continue - classes_to_import += recursive_dynamic_import(f"{adjusted_package_name}.{subname}") - - return classes_to_import diff --git a/compose_api/btools/bsew/__init__.py b/compose_api/btools/bsew/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/compose_api/btools/bsoil/introspect_package.py b/compose_api/btools/bsoil/introspect_package.py index 740d377..8ff9095 100644 --- a/compose_api/btools/bsoil/introspect_package.py +++ b/compose_api/btools/bsoil/introspect_package.py @@ -3,8 +3,8 @@ from urllib.parse import ParseResult import requests +from bsedic.utils.input_types import ExperimentPrimaryDependencies -from compose_api.btools.bsander.bsandr_utils.input_types import ExperimentPrimaryDependencies from compose_api.simulation.models import PackageOutline, PackageType logger = logging.getLogger(__name__) diff --git a/compose_api/db/services/packages_db.py b/compose_api/db/services/packages_db.py index 09fa19b..31a39cb 100644 --- a/compose_api/db/services/packages_db.py +++ b/compose_api/db/services/packages_db.py @@ -2,11 +2,11 @@ from abc import ABC, abstractmethod from typing import Any +from bsedic.utils.input_types import ExperimentPrimaryDependencies from sqlalchemy import Result, select from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker from typing_extensions import override -from compose_api.btools.bsander.bsandr_utils.input_types import ExperimentPrimaryDependencies from compose_api.db.tables.package_tables import ( BiGraphComputeTypeDB, ORMBiGraphCompute, diff --git a/compose_api/db/services/simulators_db.py b/compose_api/db/services/simulators_db.py index 576f49e..7371b69 100644 --- a/compose_api/db/services/simulators_db.py +++ b/compose_api/db/services/simulators_db.py @@ -1,11 +1,11 @@ import logging from abc import ABC, abstractmethod +from bsedic.utils.input_types import ContainerizationFileRepr from sqlalchemy import Result, and_, select from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker from typing_extensions import override -from compose_api.btools.bsander.bsandr_utils.input_types import ContainerizationFileRepr from compose_api.db.tables.simulator_tables import ( ORMSimulation, ORMSimulator, diff --git a/compose_api/db/tables/simulator_tables.py b/compose_api/db/tables/simulator_tables.py index cef6b1f..3c33a23 100644 --- a/compose_api/db/tables/simulator_tables.py +++ b/compose_api/db/tables/simulator_tables.py @@ -1,10 +1,10 @@ import datetime import logging +from bsedic.utils.input_types import ContainerizationFileRepr from sqlalchemy import ForeignKey, func from sqlalchemy.orm import Mapped, mapped_column -from compose_api.btools.bsander.bsandr_utils.input_types import ContainerizationFileRepr from compose_api.db.db_utils import DeclarativeTableBase, package_table_name from compose_api.simulation.models import ( SimulatorVersion, diff --git a/compose_api/simulation/handlers.py b/compose_api/simulation/handlers.py index 5031f09..d446147 100644 --- a/compose_api/simulation/handlers.py +++ b/compose_api/simulation/handlers.py @@ -8,14 +8,14 @@ import zipfile from pathlib import Path -from fastapi import BackgroundTasks, HTTPException - -from compose_api.btools.bsander.bsandr_utils.input_types import ( +from bsedic.execution import execute_bsedic +from bsedic.utils.input_types import ( ContainerizationEngine, ContainerizationTypes, ProgramArguments, ) -from compose_api.btools.bsander.execution import execute_bsander +from fastapi import BackgroundTasks, HTTPException + from compose_api.common.gateway.utils import allow_list from compose_api.db.database_service import DatabaseService from compose_api.dependencies import ( @@ -68,7 +68,7 @@ async def run_simulation( allow_list = pb_allow_list.allow_list with tempfile.TemporaryDirectory(delete=False) as tmp_dir: - singularity_rep, experiment_dep = execute_bsander( + singularity_rep, experiment_dep = execute_bsedic( ProgramArguments( input_file_path=str(simulation_request.omex_archive), output_dir=tmp_dir, diff --git a/compose_api/simulation/hpc_utils.py b/compose_api/simulation/hpc_utils.py index 8dff08d..b8fe962 100644 --- a/compose_api/simulation/hpc_utils.py +++ b/compose_api/simulation/hpc_utils.py @@ -1,7 +1,8 @@ import hashlib from pathlib import Path -from compose_api.btools.bsander.bsandr_utils.input_types import ContainerizationFileRepr +from bsedic.utils.input_types import ContainerizationFileRepr + from compose_api.common.gateway.models import Namespace from compose_api.config import get_settings from compose_api.simulation.models import ( diff --git a/compose_api/simulation/models.py b/compose_api/simulation/models.py index 0fdae38..768e67a 100644 --- a/compose_api/simulation/models.py +++ b/compose_api/simulation/models.py @@ -7,11 +7,10 @@ from pathlib import Path from typing import Any +from bsedic.utils.input_types import ContainerizationFileRepr from pydantic import BaseModel as _BaseModel from pydantic import Field -from compose_api.btools.bsander.bsandr_utils.input_types import ContainerizationFileRepr - @dataclass class FlexData: diff --git a/pyproject.toml b/pyproject.toml index c4fdc2f..87df2b1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,6 +36,7 @@ dependencies = [ "python-libsedml>=2.0.33", "biosimulators-utils>=0.2.3", "python-libsbml>=5.20.5", + "bsedic", ] [project.urls] @@ -76,6 +77,7 @@ default-groups = [ [tool.uv.sources] bsander = { git = "https://github.com/biosimulators/bsander.git" } +bsedic = { git = "https://github.com/biosimulators/bsedic.git" } [tool.hatch.build.targets.sdist] include = [ diff --git a/tests/fixtures/simulation_fixtures.py b/tests/fixtures/simulation_fixtures.py index c76d4fc..f094eee 100644 --- a/tests/fixtures/simulation_fixtures.py +++ b/tests/fixtures/simulation_fixtures.py @@ -3,14 +3,14 @@ from collections.abc import AsyncGenerator import pytest_asyncio -from nats.aio.client import Client as NATSClient - -from compose_api.btools.bsander.bsandr_utils.input_types import ( +from bsedic.execution import execute_bsedic +from bsedic.utils.input_types import ( ContainerizationEngine, ContainerizationTypes, ProgramArguments, ) -from compose_api.btools.bsander.execution import execute_bsander +from nats.aio.client import Client as NATSClient + from compose_api.btools.bsoil.introspect_package import introspect_package from compose_api.common.gateway.utils import allow_list from compose_api.common.hpc.models import SlurmJob @@ -63,7 +63,7 @@ async def job_monitor( async def simulator(database_service: DatabaseService) -> AsyncGenerator[SimulatorVersion, None]: omex_path = os.path.join(os.path.dirname(__file__), "resources/interesting-test.omex") with tempfile.TemporaryDirectory() as temp_dir: - singularity_def, experiment_dep = execute_bsander( + singularity_def, experiment_dep = execute_bsedic( ProgramArguments( input_file_path=omex_path, output_dir=temp_dir, diff --git a/tests/simulation/test_simulation.py b/tests/simulation/test_simulation.py index d3607f0..dc8e0fa 100644 --- a/tests/simulation/test_simulation.py +++ b/tests/simulation/test_simulation.py @@ -7,13 +7,13 @@ from pathlib import Path import pytest - -from compose_api.btools.bsander.bsandr_utils.input_types import ( +from bsedic.execution import execute_bsedic +from bsedic.utils.input_types import ( ContainerizationEngine, ContainerizationTypes, ProgramArguments, ) -from compose_api.btools.bsander.execution import execute_bsander + from compose_api.btools.bsoil.introspect_package import introspect_package from compose_api.common.gateway.utils import allow_list from compose_api.common.hpc.models import SlurmJob @@ -41,7 +41,7 @@ async def test_build_simulator( job_monitor: JobMonitor, ) -> None: with tempfile.TemporaryDirectory() as temp_dir: - singularity_def, experiment_dep = execute_bsander( + singularity_def, experiment_dep = execute_bsedic( ProgramArguments( input_file_path=str(simulation_request.omex_archive), output_dir=temp_dir, diff --git a/uv.lock b/uv.lock index dbc7ceb..e2f16c5 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 3 +revision = 2 requires-python = ">=3.13.2, <4.0" [[package]] @@ -183,6 +183,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/41/7d/8f937bc34c8d35c9203271ae13223a0c49d7a4bf851ff063b15a31fd1555/biosimulators_utils-0.2.3-py3-none-any.whl", hash = "sha256:d6e712f815d7f2535790d5e3917b3f687045a00301552f8fcdb8ca54b2caaf34", size = 586483, upload-time = "2024-10-10T18:39:11.626Z" }, ] +[[package]] +name = "bsedic" +version = "0.0.1" +source = { git = "https://github.com/biosimulators/bsedic.git#326a47cd45ed6f0bb9eaf6bad0269336a3b38abc" } +dependencies = [ + { name = "pydantic" }, + { name = "spython" }, +] + [[package]] name = "cachetools" version = "6.1.0" @@ -303,6 +312,7 @@ dependencies = [ { name = "asyncpg" }, { name = "asyncssh" }, { name = "biosimulators-utils" }, + { name = "bsedic" }, { name = "dotenv" }, { name = "fastapi" }, { name = "httpx" }, @@ -361,6 +371,7 @@ requires-dist = [ { name = "asyncpg", specifier = ">=0.30.0,<0.31" }, { name = "asyncssh", specifier = ">=2.21.0,<3" }, { name = "biosimulators-utils", specifier = ">=0.2.3" }, + { name = "bsedic", git = "https://github.com/biosimulators/bsedic.git" }, { name = "dotenv", specifier = ">=0.9.9,<0.10" }, { name = "fastapi", specifier = ">=0.116.1,<0.117" }, { name = "httpx", specifier = ">=0.28.1,<0.29" }, @@ -1628,7 +1639,7 @@ wheels = [ [[package]] name = "pydantic" -version = "2.11.7" +version = "2.12.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, @@ -1636,37 +1647,62 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" } +sdist = { url = "https://files.pythonhosted.org/packages/96/ad/a17bc283d7d81837c061c49e3eaa27a45991759a1b7eae1031921c6bd924/pydantic-2.12.4.tar.gz", hash = "sha256:0f8cb9555000a4b5b617f66bfd2566264c4984b27589d3b845685983e8ea85ac", size = 821038, upload-time = "2025-11-05T10:50:08.59Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" }, + { url = "https://files.pythonhosted.org/packages/82/2f/e68750da9b04856e2a7ec56fc6f034a5a79775e9b9a81882252789873798/pydantic-2.12.4-py3-none-any.whl", hash = "sha256:92d3d202a745d46f9be6df459ac5a064fdaa3c1c4cd8adcfa332ccf3c05f871e", size = 463400, upload-time = "2025-11-05T10:50:06.732Z" }, ] [[package]] name = "pydantic-core" -version = "2.33.2" +version = "2.41.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, - { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, - { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, - { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, - { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, - { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, - { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, - { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, - { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, - { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, - { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, - { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, - { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, - { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, - { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, - { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, - { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, + { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, + { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, + { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, + { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, + { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, + { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, + { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, + { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, + { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, + { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, + { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, + { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, + { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, + { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, + { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, + { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, + { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, + { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, + { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, + { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, + { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, + { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, + { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, + { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, ] [[package]] @@ -2589,14 +2625,14 @@ wheels = [ [[package]] name = "typing-inspection" -version = "0.4.1" +version = "0.4.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" } +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, ] [[package]]