From 27a7606342dd7fe19926c85fde0b9376ff4c59d2 Mon Sep 17 00:00:00 2001 From: SongshGeo Date: Thu, 1 Jan 2026 21:15:13 +0100 Subject: [PATCH 1/4] feat(tracking): :sparkles: Integrate Aim and MLflow tracking backends with configuration updates This commit introduces support for Aim and MLflow as tracking backends in the project. It adds a new `tracker` section in the configuration schema, allowing users to specify tracking options, including backend selection and related parameters. The `MainModel` class is updated to utilize these tracking backends, enhancing data collection capabilities. Additionally, the `normalize_config` function is improved to handle deprecated keys, ensuring backward compatibility. New tests are added to validate configuration normalization and tracking functionality. --- .gitignore | 1 + abses/core/model.py | 19 +- abses/utils/config.py | 270 ++++++++ abses/utils/datacollector.py | 39 +- abses/utils/errors.py | 12 + abses/utils/tracker/__init__.py | 36 + abses/utils/tracker/aim_tracker.py | 162 +++++ abses/utils/tracker/default.py | 36 + abses/utils/tracker/factory.py | 255 +++++++ abses/utils/tracker/mlflow_tracker.py | 81 +++ docs/home/configuration_schema.md | 944 ++++++++++++++++++++++++++ examples/fire_spread/config.yaml | 24 +- examples/fire_spread/model.py | 1 + mkdocs.yml | 1 + pyproject.toml | 8 + tests/core/test_model_config.py | 37 + tests/utils/test_config.py | 77 +++ tests/utils/test_tracker.py | 30 + uv.lock | 861 ++++++++++++++++++++++- 19 files changed, 2885 insertions(+), 9 deletions(-) create mode 100644 abses/utils/config.py create mode 100644 abses/utils/tracker/__init__.py create mode 100644 abses/utils/tracker/aim_tracker.py create mode 100644 abses/utils/tracker/default.py create mode 100644 abses/utils/tracker/factory.py create mode 100644 abses/utils/tracker/mlflow_tracker.py create mode 100644 docs/home/configuration_schema.md create mode 100644 tests/core/test_model_config.py create mode 100644 tests/utils/test_config.py create mode 100644 tests/utils/test_tracker.py diff --git a/.gitignore b/.gitignore index 2a49aae9..26943df6 100644 --- a/.gitignore +++ b/.gitignore @@ -197,3 +197,4 @@ examples/shifting_baseline/ examples/water_quota/ examples/South-China-Livehood-Evolution/ examples/yr-water-quota/ +.aim diff --git a/abses/core/model.py b/abses/core/model.py index 6691e47d..9acb9619 100644 --- a/abses/core/model.py +++ b/abses/core/model.py @@ -41,6 +41,7 @@ from abses.human.human import BaseHuman from abses.space.nature import BaseNature from abses.utils.args import merge_parameters +from abses.utils.config import apply_validation, normalize_config from abses.utils.datacollector import ABSESpyDataCollector from abses.utils.logging import ( log_session, @@ -48,6 +49,10 @@ setup_logger_info, setup_model_logger, ) +from abses.utils.tracker.factory import ( + create_tracker, + prepare_collector_config, +) if TYPE_CHECKING: from mesa.model import RNGLike, SeedLike @@ -113,14 +118,21 @@ def __init__( self._run_id: Optional[int] = run_id # Filter out None values from kwargs for type safety clean_kwargs = {k: v for k, v in kwargs.items() if v is not None} - self._settings = merge_parameters(parameters, **clean_kwargs) + normalized_params = normalize_config(parameters) + apply_validation(normalized_params) + + self._settings = merge_parameters(normalized_params, **clean_kwargs) self._time = TimeDriver(model=self) self._setup_subsystems(human_class, nature_class) self._agents_handler = _ModelAgentsContainer( model=self, max_len=kwargs.get("max_agents", None) ) + + tracker_cfg = self._settings.get("tracker", {}) + tracker_backend = create_tracker(tracker_cfg, model=self) + collector_cfg = prepare_collector_config(tracker_cfg) self.datacollector: ABSESpyDataCollector = ABSESpyDataCollector( - parameters.get("reports", {}) + reports=collector_cfg, tracker=tracker_backend ) # Call initialize on model first @@ -458,6 +470,9 @@ def step(self) -> None: def end(self) -> None: """Users can custom what to do when the model is end.""" + # End tracker run if available + if self.datacollector.tracker is not None: + self.datacollector.tracker.end_run() # def summary(self, verbose: bool = False) -> pd.DataFrame: # """Generates a summary report of the model's current state. diff --git a/abses/utils/config.py b/abses/utils/config.py new file mode 100644 index 00000000..1258e164 --- /dev/null +++ b/abses/utils/config.py @@ -0,0 +1,270 @@ +#!/usr/bin/env python3 +# -*-coding:utf-8 -*- +# @Author : ABSESpy Team +from __future__ import annotations + +from datetime import datetime +from typing import Any, Dict, List + +from omegaconf import DictConfig, OmegaConf + +from abses.utils.errors import ConfigurationError +from abses.utils.logging import logger + + +def normalize_config(config: DictConfig) -> DictConfig: + """Normalize configuration for backward compatibility. + + This function converts deprecated keys to the new schema while + preserving original data. + + Args: + config: Raw configuration. + + Returns: + Normalized configuration. + """ + # Handle empty or None config + if config is None: + config = DictConfig({}) + + if isinstance(config, dict) and not isinstance(config, DictConfig): + config = OmegaConf.create(config) + + if not isinstance(config, DictConfig): + raise ConfigurationError("Configuration must be a DictConfig or dict.") + + OmegaConf.set_struct(config, False) + cfg = config + + # reports -> tracker + if "reports" in cfg and "tracker" not in cfg: + cfg.tracker = cfg.reports + logger.warning( + "Configuration key 'reports' is deprecated. Please use 'tracker' instead." + ) + + # agent -> agents inside tracker/reports + tracker_cfg = cfg.get("tracker", {}) + if tracker_cfg and isinstance(tracker_cfg, (dict, DictConfig)): + # Ensure tracker_cfg is DictConfig for modification + if not isinstance(tracker_cfg, DictConfig): + tracker_cfg = OmegaConf.create(tracker_cfg) + cfg.tracker = tracker_cfg + + if "agent" in tracker_cfg and "agents" not in tracker_cfg: + tracker_cfg.agents = tracker_cfg.agent + logger.warning( + "Configuration key 'tracker.agent' is deprecated. " + "Please use 'tracker.agents' instead." + ) + del tracker_cfg["agent"] + return cfg + + +def validate_config(config: DictConfig, strict: bool = False) -> List[str]: + """Validate configuration sections. + + Args: + config: Normalized configuration. + strict: Whether to raise on validation errors. + + Returns: + List of validation error messages. + """ + errors: List[str] = [] + errors.extend(_validate_time(config.get("time", {}))) + errors.extend(_validate_exp(config.get("exp", {}))) + errors.extend(_validate_model(config.get("model", {}))) + errors.extend(_validate_tracker(config.get("tracker", {}))) + + if errors and strict: + raise ConfigurationError(_format_validation_errors(errors)) + return errors + + +def _validate_time(time_cfg: Any) -> List[str]: + """Validate time configuration.""" + errs: List[str] = [] + if not time_cfg: + return errs + if not isinstance(time_cfg, (dict, DictConfig)): + return ["time: must be a mapping."] + end = time_cfg.get("end", None) + for key in ("start",): + val = time_cfg.get(key, None) + if val is not None and not isinstance(val, (str, datetime)): + errs.append(f"time.{key}: expected str|datetime|null, got {type(val)}") + if end is not None and not isinstance(end, (int, str, datetime)): + errs.append(f"time.end: expected int|str|datetime|null, got {type(end)}") + for key in ("days", "hours", "minutes", "seconds"): + val = time_cfg.get(key, 0) + if val is not None and not isinstance(val, int): + errs.append(f"time.{key}: expected int, got {type(val)}") + if isinstance(val, int) and val < 0: + errs.append(f"time.{key}: must be non-negative, got {val}") + irregular = time_cfg.get("irregular", False) + if irregular not in (True, False): + errs.append("time.irregular: expected bool.") + return errs + + +def _validate_exp(exp_cfg: Any) -> List[str]: + """Validate experiment configuration.""" + errs: List[str] = [] + if not exp_cfg: + return errs + if not isinstance(exp_cfg, (dict, DictConfig)): + return ["exp: must be a mapping."] + if "name" in exp_cfg and not isinstance(exp_cfg.get("name"), str): + errs.append(f"exp.name: expected str, got {type(exp_cfg.get('name'))}") + if "outdir" in exp_cfg and not isinstance(exp_cfg.get("outdir"), str): + errs.append(f"exp.outdir: expected str, got {type(exp_cfg.get('outdir'))}") + repeats = exp_cfg.get("repeats", 1) + if not isinstance(repeats, int) or repeats <= 0: + errs.append("exp.repeats: expected positive int.") + seed = exp_cfg.get("seed", None) + if seed is not None and not isinstance(seed, int): + errs.append("exp.seed: expected int or null.") + logging_mode = exp_cfg.get("logging", "once") + if logging_mode not in ("once", "always", False, True, None): + errs.append("exp.logging: expected 'once' | 'always' | bool.") + return errs + + +def _validate_model(model_cfg: Any) -> List[str]: + """Validate model parameters (basic checks and ranges).""" + errs: List[str] = [] + if not model_cfg: + return errs + if not isinstance(model_cfg, (dict, DictConfig)): + return ["model: must be a mapping."] + for key, val in model_cfg.items(): + if isinstance(val, (dict, DictConfig)): + if _is_range_node(val): + errs.extend(_validate_range_node(key, val)) + else: + errs.extend(_validate_model(val)) + return errs + + +def _is_range_node(val: Dict[str, Any]) -> bool: + """Check if a node looks like a range definition.""" + return {"min", "max", "step"} & set(val.keys()) != set() + + +def _validate_range_node(name: str, node: Dict[str, Any]) -> List[str]: + """Validate a range node with min/max/step.""" + errs: List[str] = [] + min_val = node.get("min", None) + max_val = node.get("max", None) + step = node.get("step", None) + for field_name, field_val in (("min", min_val), ("max", max_val), ("step", step)): + if field_val is not None and not isinstance(field_val, (int, float)): + errs.append( + f"model.{name}.{field_name}: expected number, got {type(field_val)}" + ) + if ( + isinstance(min_val, (int, float)) + and isinstance(max_val, (int, float)) + and min_val >= max_val + ): + errs.append(f"model.{name}: min should be < max (min={min_val}, max={max_val})") + if isinstance(step, (int, float)) and step <= 0: + errs.append(f"model.{name}.step: must be > 0, got {step}") + return errs + + +def _validate_tracker(tracker_cfg: Any) -> List[str]: + """Validate tracker configuration.""" + errs: List[str] = [] + if tracker_cfg is None: + return errs + if not isinstance(tracker_cfg, (dict, DictConfig)): + return ["tracker: must be a mapping."] + if "backend" in tracker_cfg: + backend = tracker_cfg.get("backend") + if backend not in ("default", "aim", "mlflow", None): + errs.append( + "tracker.backend: expected 'default' | 'aim' | 'mlflow' | null." + ) + for section in ("model", "agents", "final"): + sub = tracker_cfg.get(section, {}) + if not sub: + continue + if not isinstance(sub, (dict, DictConfig)): + errs.append(f"tracker.{section}: must be a mapping.") + continue + if section == "agents": + for breed, reporters in sub.items(): + if not isinstance(reporters, (dict, DictConfig)): + errs.append(f"tracker.agents.{breed}: must be a mapping.") + continue + for name, reporter in reporters.items(): + errs.extend( + _validate_tracker_entry(f"agents.{breed}.{name}", reporter) + ) + else: + for name, reporter in sub.items(): + errs.extend(_validate_tracker_entry(f"{section}.{name}", reporter)) + return errs + + +def _validate_tracker_entry(path: str, reporter: Any) -> List[str]: + """Validate a single tracker entry.""" + errs: List[str] = [] + if isinstance(reporter, str): + if not reporter: + errs.append(f"tracker.{path}: reporter string cannot be empty.") + return errs + if isinstance(reporter, (dict, DictConfig)): + if "source" not in reporter: + errs.append(f"tracker.{path}: missing 'source'.") + else: + src = reporter.get("source") + if not isinstance(src, str) or not src: + errs.append(f"tracker.{path}.source: expected non-empty string.") + aggregate = reporter.get("aggregate", None) + if aggregate is not None and aggregate not in ( + "mean", + "sum", + "count", + "min", + "max", + "std", + ): + errs.append(f"tracker.{path}.aggregate: invalid value '{aggregate}'.") + return errs + errs.append(f"tracker.{path}: expected string or mapping, got {type(reporter)}") + return errs + + +def _format_validation_errors(errors: List[str]) -> str: + """Format validation errors for display.""" + return "\n".join(f"- {err}" for err in errors) + + +def apply_validation(config: DictConfig) -> None: + """Apply validation to configuration if enabled. + + Args: + config: Normalized configuration to validate. + + Raises: + ConfigurationError: If validation fails in strict mode. + """ + validate_cfg = config.get("validate", {}) + validate_enabled = validate_cfg.get("enabled", False) + strict_validation = validate_cfg.get("strict", False) + + if not validate_enabled: + return + + validation_errors = validate_config(config, strict=False) + if not validation_errors: + return + + error_msg = _format_validation_errors(validation_errors) + if strict_validation: + raise ConfigurationError(error_msg) + logger.warning("Configuration validation warnings:\n%s", error_msg) diff --git a/abses/utils/datacollector.py b/abses/utils/datacollector.py index 683ed2bf..60d85a41 100644 --- a/abses/utils/datacollector.py +++ b/abses/utils/datacollector.py @@ -31,6 +31,8 @@ from abses.main import MainModel from abses.time import TimeDriver +from abses.utils.tracker import TrackerProtocol + try: from typing import TypeAlias except ImportError: @@ -108,7 +110,19 @@ def clean_to_reporter( class ABSESpyDataCollector: """ABSESpyDataCollector, adapted from DataCollector of `mesa`.""" - def __init__(self, reports: Dict[ReportType, Dict[str, Reporter]]): + def __init__( + self, + reports: Dict[ReportType, Dict[str, Reporter]] | None = None, + tracker: Optional[TrackerProtocol] = None, + ): + """Initialize data collector. + + Args: + reports: Reporters configuration. + tracker: Optional tracker backend. + """ + reports = reports or {} + self.tracker = tracker self.model_reporters: Dict[str, Reporter] = {} self.final_reporters: Dict[str, Reporter] = {} self.agent_reporters: Dict[str, Dict[str, Reporter]] = {} @@ -227,14 +241,33 @@ def get_final_vars_report(self, model: MainModel) -> Dict[str, Any]: "No final reporters have been defined, returning empty dict." ) return {} - return {var: func(model) for var, func in self.final_reporters.items()} + results = {var: func(model) for var, func in self.final_reporters.items()} + if self.tracker is not None: + self.tracker.log_final_metrics(results) + return results def collect(self, model: MainModel): """Collect all the data for the given model object.""" if self.model_reporters: + model_snapshot = {} for var, func in self.model_reporters.items(): - self.model_vars[var].append(func(model)) + value = func(model) + self.model_vars[var].append(value) + model_snapshot[var] = value + if self.tracker is not None: + self.tracker.log_model_vars(model_snapshot, step=model.time.tick) if self.agent_reporters: self._record_agents(model) + if self.tracker is not None: + for breed, records in self._agent_records.items(): + if not records: + continue + latest_df = pd.DataFrame(records[-1]) + agent_vars = { + col: latest_df[col].tolist() + for col in latest_df.columns + if col not in ("AgentID", "Step", "Time") + } + self.tracker.log_agent_vars(breed, agent_vars, step=model.time.tick) diff --git a/abses/utils/errors.py b/abses/utils/errors.py index e59e32a8..bba0318f 100644 --- a/abses/utils/errors.py +++ b/abses/utils/errors.py @@ -8,3 +8,15 @@ class ABSESpyError(Exception): """Raised when Agent-based modeling logic is not satisfied.""" + + +class ConfigurationError(ABSESpyError): + """Raised when configuration validation fails.""" + + def __init__(self, message: str) -> None: + """Initialize configuration error. + + Args: + message: Description of the configuration issue. + """ + super().__init__(message) diff --git a/abses/utils/tracker/__init__.py b/abses/utils/tracker/__init__.py new file mode 100644 index 00000000..66ab992b --- /dev/null +++ b/abses/utils/tracker/__init__.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +# -*-coding:utf-8 -*- +# @Author : ABSESpy Team +from __future__ import annotations + +from typing import Any, Dict, Protocol + + +class TrackerProtocol(Protocol): + """Protocol for tracker backends.""" + + def start_run( + self, run_name: str | None = None, tags: Dict[str, str] | None = None + ) -> None: + """Start a tracking run.""" + + def log_metrics(self, metrics: Dict[str, float], step: int | None = None) -> None: + """Log scalar metrics.""" + + def log_model_vars( + self, model_vars: Dict[str, Any], step: int | None = None + ) -> None: + """Log model-level variables.""" + + def log_agent_vars( + self, breed: str, agent_vars: Dict[str, Any], step: int | None = None + ) -> None: + """Log agent-level variables.""" + + def log_final_metrics( + self, metrics: Dict[str, Any], step: int | None = None + ) -> None: + """Log final metrics.""" + + def end_run(self) -> None: + """End the tracking run.""" diff --git a/abses/utils/tracker/aim_tracker.py b/abses/utils/tracker/aim_tracker.py new file mode 100644 index 00000000..0f27131a --- /dev/null +++ b/abses/utils/tracker/aim_tracker.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python3 +# -*-coding:utf-8 -*- +# @Author : ABSESpy Team +from __future__ import annotations + +import statistics +from typing import Any, Dict + +from abses.utils.tracker import TrackerProtocol + +try: + from aim import Run +except ImportError: + Run = None + + +class AimTracker(TrackerProtocol): + """Aim tracker backend (requires `aim`). + + This tracker integrates with Aim (https://aimstack.io/) for experiment tracking. + Install with: pip install abses[aim] or pip install aim + + Example configuration: + tracker: + backend: aim + aim: + experiment: "my_experiment" + repo: "./aim_repo" # Optional, defaults to ~/.aim + """ + + def __init__(self, config: Dict[str, Any]) -> None: + """Initialize Aim tracker. + + Args: + config: Aim-specific configuration dictionary. Supported keys: + - experiment: Experiment name (optional) + - repo: Path to Aim repository (optional, defaults to ~/.aim) + + Raises: + ImportError: If aim is not installed. + """ + if Run is None: + raise ImportError( + "Aim is not installed. Install with: pip install abses[aim] or pip install aim" + ) + experiment = config.get("experiment", None) + repo = config.get("repo", None) + self._run = Run(experiment=experiment, repo=repo) + self._params_logged = False + + def start_run( + self, run_name: str | None = None, tags: Dict[str, str] | None = None + ) -> None: + """Start a tracking run. + + Args: + run_name: Name for this run (optional). + tags: Dictionary of tags to add to the run (optional). + """ + if run_name: + self._run.name = run_name + if tags: + for key, value in tags.items(): + self._run.add_tag(f"{key}:{value}") + + def log_metrics(self, metrics: Dict[str, float], step: int | None = None) -> None: + """Log scalar metrics to Aim. + + Args: + metrics: Dictionary of metric names to values. + step: Step number (optional). + """ + for name, value in metrics.items(): + # Only track numeric values as metrics + if isinstance(value, (int, float)): + self._run.track(value, name=name, step=step) + + def log_model_vars( + self, model_vars: Dict[str, Any], step: int | None = None + ) -> None: + """Log model variables as metrics. + + Args: + model_vars: Dictionary of variable names to values. + step: Step number (optional). + """ + # Filter numeric values for metrics + numeric_vars = { + k: v for k, v in model_vars.items() if isinstance(v, (int, float)) + } + if numeric_vars: + self.log_metrics(numeric_vars, step=step) + + def log_agent_vars( + self, breed: str, agent_vars: Dict[str, Any], step: int | None = None + ) -> None: + """Log agent variables with breed prefix. + + Args: + breed: Agent breed/class name. + agent_vars: Dictionary of variable names to values (can be lists for aggregation). + step: Step number (optional). + """ + # Handle list values (aggregated agent data) + metrics_to_log: Dict[str, float] = {} + for key, value in agent_vars.items(): + metric_name = f"{breed}.{key}" + if isinstance(value, (int, float)): + metrics_to_log[metric_name] = value + elif isinstance(value, list) and value: + # Aggregate list values (mean, min, max) + numeric_values = [v for v in value if isinstance(v, (int, float))] + if numeric_values: + metrics_to_log[f"{metric_name}.mean"] = statistics.mean( + numeric_values + ) + metrics_to_log[f"{metric_name}.min"] = min(numeric_values) + metrics_to_log[f"{metric_name}.max"] = max(numeric_values) + if len(numeric_values) > 1: + metrics_to_log[f"{metric_name}.std"] = statistics.stdev( + numeric_values + ) + + if metrics_to_log: + self.log_metrics(metrics_to_log, step=step) + + def log_final_metrics( + self, metrics: Dict[str, Any], step: int | None = None + ) -> None: + """Log final metrics. + + Args: + metrics: Dictionary of final metric names to values. + step: Step number (optional). + """ + # Filter numeric values + numeric_metrics = { + k: v for k, v in metrics.items() if isinstance(v, (int, float)) + } + if numeric_metrics: + self.log_metrics(numeric_metrics, step=step) + + def log_params(self, params: Dict[str, Any]) -> None: + """Log hyperparameters to Aim. + + Args: + params: Dictionary of parameter names to values. + """ + for key, value in params.items(): + # Aim supports various types for parameters + if isinstance(value, (int, float, str, bool)): + self._run.set(key, value, strict=False) + elif isinstance(value, (list, tuple)): + # Convert lists/tuples to strings for Aim + self._run.set(key, str(value), strict=False) + else: + # Convert other types to strings + self._run.set(key, str(value), strict=False) + + def end_run(self) -> None: + """End the Aim run.""" + self._run.close() diff --git a/abses/utils/tracker/default.py b/abses/utils/tracker/default.py new file mode 100644 index 00000000..6810ba50 --- /dev/null +++ b/abses/utils/tracker/default.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +# -*-coding:utf-8 -*- +# @Author : ABSESpy Team +from __future__ import annotations + +from typing import Any, Dict + +from abses.utils.tracker import TrackerProtocol + + +class DefaultTracker(TrackerProtocol): + """No-op tracker that keeps current in-memory behavior.""" + + def start_run( + self, run_name: str | None = None, tags: Dict[str, str] | None = None + ) -> None: + """Start run (no-op).""" + + def log_metrics(self, metrics: Dict[str, float], step: int | None = None) -> None: + """Log metrics (no-op).""" + + def log_model_vars( + self, model_vars: Dict[str, Any], step: int | None = None + ) -> None: + """Log model variables (no-op).""" + + def log_agent_vars( + self, breed: str, agent_vars: Dict[str, Any], step: int | None = None + ) -> None: + """Log agent variables (no-op).""" + + def log_final_metrics(self, metrics: Dict[str, Any]) -> None: + """Log final metrics (no-op).""" + + def end_run(self) -> None: + """End run (no-op).""" diff --git a/abses/utils/tracker/factory.py b/abses/utils/tracker/factory.py new file mode 100644 index 00000000..7cb2bd59 --- /dev/null +++ b/abses/utils/tracker/factory.py @@ -0,0 +1,255 @@ +#!/usr/bin/env python3 +# -*-coding:utf-8 -*- +# @Author : ABSESpy Team +from __future__ import annotations + +from typing import TYPE_CHECKING, Dict, Optional + +from omegaconf import DictConfig + +from abses.utils.errors import ConfigurationError +from abses.utils.logging import logger +from abses.utils.tracker import TrackerProtocol +from abses.utils.tracker.default import DefaultTracker + +if TYPE_CHECKING: + from abses.core.model import MainModel + + +def _to_plain(cfg: DictConfig) -> Dict: + """Convert DictConfig to plain dict safely.""" + from omegaconf import OmegaConf + + return OmegaConf.to_container(cfg, resolve=True) + + +def prepare_tracker_run_name( + tracker_cfg: DictConfig | Dict, + model_name: str, + version: str, + run_id: Optional[int], +) -> str: + """Prepare tracker run name from configuration or use default. + + Args: + tracker_cfg: Tracker configuration. + model_name: Name of the model. + version: Version of the model. + run_id: Optional run ID. + + Returns: + Formatted run name. + """ + template_vars = { + "model_name": model_name, + "run_id": str(run_id) if run_id is not None else "", + "version": version, + } + + run_name_template = tracker_cfg.get("run_name", None) + if not run_name_template: + return f"{model_name}_run_{run_id}" if run_id is not None else model_name + + try: + run_name = run_name_template.format(**template_vars) + return run_name.replace("__", "_").strip("_") + except (KeyError, ValueError): + logger.warning( + f"Failed to format run_name template '{run_name_template}', using as-is." + ) + return run_name_template + + +def prepare_tracker_tags( + tracker_cfg: DictConfig | Dict, + model_name: str, + version: str, + run_id: Optional[int], +) -> Dict[str, str]: + """Prepare tracker tags from configuration or use default. + + Args: + tracker_cfg: Tracker configuration. + model_name: Name of the model. + version: Version of the model. + run_id: Optional run ID. + + Returns: + Dictionary of tags. + """ + from omegaconf import OmegaConf + + template_vars = { + "model_name": model_name, + "run_id": str(run_id) if run_id is not None else "", + "version": version, + } + + tags_config = tracker_cfg.get("tags", None) + if not tags_config: + tags = {"model": model_name, "version": version} + if run_id is not None: + tags["run_id"] = str(run_id) + return tags + + # Convert to plain dict if needed + if isinstance(tags_config, DictConfig): + tags = OmegaConf.to_container(tags_config, resolve=True) + else: + tags = dict(tags_config) + + if not isinstance(tags, dict): + return {"model": model_name, "version": version} + + processed_tags = {} + for k, v in tags.items(): + if isinstance(v, str) and ("{" in v and "}" in v): + try: + processed_tags[k] = v.format(**template_vars) + except (KeyError, ValueError): + logger.warning(f"Failed to format tag '{k}' value '{v}', using as-is.") + processed_tags[k] = v + else: + processed_tags[k] = v + + return processed_tags + + +def start_tracker_run( + tracker: Optional[TrackerProtocol], + tracker_cfg: DictConfig | Dict, + model_name: str, + version: str, + run_id: Optional[int], + model_params: DictConfig | Dict, +) -> None: + """Start tracker run and log parameters. + + Args: + tracker: Tracker backend instance (None for no-op). + tracker_cfg: Tracker configuration. + model_name: Name of the model. + version: Version of the model. + run_id: Optional run ID. + model_params: Model parameters to log as hyperparameters. + """ + if tracker is None: + return + + from omegaconf import OmegaConf + + run_name = prepare_tracker_run_name(tracker_cfg, model_name, version, run_id) + tags = prepare_tracker_tags(tracker_cfg, model_name, version, run_id) + tracker.start_run(run_name=run_name, tags=tags) + + # Log model parameters as hyperparameters + if isinstance(tracker_cfg, DictConfig): + cfg_dict = OmegaConf.to_container(tracker_cfg, resolve=True) + else: + cfg_dict = dict(tracker_cfg) + + log_params = cfg_dict.get("log_params", True) + if log_params and hasattr(tracker, "log_params"): + if isinstance(model_params, DictConfig): + params_dict = OmegaConf.to_container(model_params, resolve=True) + else: + params_dict = dict(model_params) + if isinstance(params_dict, dict): + tracker.log_params(params_dict) + + +def create_tracker( + config: Optional[DictConfig | Dict] = None, model: Optional["MainModel"] = None +) -> TrackerProtocol: + """Create tracker backend from configuration and optionally start run. + + If a model instance is provided, the tracker run will be automatically started + with appropriate run name, tags, and hyperparameters. + + Args: + config: Tracker configuration (DictConfig or dict). + model: Current model instance. If provided, tracker run will be started automatically. + + Returns: + An implementation of TrackerProtocol. + + Raises: + ConfigurationError: If backend is unknown or missing dependencies. + """ + backend = None + cfg_dict: Dict[str, Dict] = {} + if config is not None and config: # Check for both None and empty + if isinstance(config, DictConfig): + backend = config.get("backend", None) + cfg_dict = _to_plain(config) + elif isinstance(config, dict): + backend = config.get("backend", None) + cfg_dict = config + + if backend in (None, "default"): + tracker = DefaultTracker() + elif backend == "aim": + try: + from abses.utils.tracker.aim_tracker import AimTracker + except ImportError as exc: + raise ConfigurationError( + "Aim tracker selected but aim is not installed. " + "Install with: pip install aim" + ) from exc + tracker = AimTracker(cfg_dict.get("aim", {})) + elif backend == "mlflow": + try: + from abses.utils.tracker.mlflow_tracker import MLflowTracker + except ImportError as exc: + raise ConfigurationError( + "MLflow tracker selected but mlflow is not installed. " + "Install with: pip install mlflow" + ) from exc + tracker = MLflowTracker(cfg_dict.get("mlflow", {})) + else: + logger.warning( + "Unknown tracker backend '%s', falling back to default.", backend + ) + tracker = DefaultTracker() + + # Start tracker run if model is provided + if model is not None: + start_tracker_run( + tracker=tracker, + tracker_cfg=config or {}, + model_name=model.name, + version=model.version, + run_id=model._run_id, + model_params=model.params, + ) + + return tracker + + +def prepare_collector_config(tracker_cfg: DictConfig | Dict | None) -> Dict: + """Prepare configuration for DataCollector from tracker config. + + Removes backend key and converts DictConfig to plain dict. + + Args: + tracker_cfg: Tracker configuration (may be DictConfig, dict, or None). + + Returns: + Plain dict suitable for DataCollector reports parameter. + """ + from omegaconf import DictConfig, OmegaConf + + if tracker_cfg is None: + return {} + + if isinstance(tracker_cfg, DictConfig): + collector_cfg = OmegaConf.to_container(tracker_cfg, resolve=True) + elif isinstance(tracker_cfg, dict): + collector_cfg = tracker_cfg.copy() + else: + collector_cfg = {} + + if isinstance(collector_cfg, dict) and "backend" in collector_cfg: + collector_cfg.pop("backend", None) + + return collector_cfg or {} diff --git a/abses/utils/tracker/mlflow_tracker.py b/abses/utils/tracker/mlflow_tracker.py new file mode 100644 index 00000000..d598402f --- /dev/null +++ b/abses/utils/tracker/mlflow_tracker.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +# -*-coding:utf-8 -*- +# @Author : ABSESpy Team +from __future__ import annotations + +from typing import Any, Dict + +from abses.utils.tracker import TrackerProtocol + +try: + import mlflow +except ImportError: + mlflow = None + + +class MLflowTracker(TrackerProtocol): + """MLflow tracker backend (requires `mlflow`).""" + + def __init__(self, config: Dict[str, Any]) -> None: + """Initialize MLflow tracker. + + Args: + config: MLflow-specific configuration. + + Raises: + ImportError: If mlflow is not installed. + """ + if mlflow is None: + raise ImportError( + "MLflow is not installed. Install with: pip install mlflow" + ) + self._experiment = config.get("experiment", None) + self._run = None + + def start_run( + self, run_name: str | None = None, tags: Dict[str, str] | None = None + ) -> None: + """Start MLflow run.""" + if self._experiment: + mlflow.set_experiment(self._experiment) + self._run = mlflow.start_run(run_name=run_name) + if tags: + mlflow.set_tags(tags) + + def log_metrics(self, metrics: Dict[str, float], step: int | None = None) -> None: + """Log metrics to MLflow.""" + mlflow.log_metrics(metrics, step=step) + + def log_model_vars( + self, model_vars: Dict[str, Any], step: int | None = None + ) -> None: + """Log model variables as metrics.""" + float_metrics = { + k: v for k, v in model_vars.items() if isinstance(v, (int, float)) + } + if float_metrics: + self.log_metrics(float_metrics, step=step) + + def log_agent_vars( + self, breed: str, agent_vars: Dict[str, Any], step: int | None = None + ) -> None: + """Log agent variables with breed prefix.""" + float_metrics = { + f"{breed}.{k}": v + for k, v in agent_vars.items() + if isinstance(v, (int, float)) + } + if float_metrics: + self.log_metrics(float_metrics, step=step) + + def log_final_metrics( + self, metrics: Dict[str, Any], step: int | None = None + ) -> None: + """Log final metrics.""" + self.log_metrics({f"final.{k}": v for k, v in metrics.items()}) + + def end_run(self) -> None: + """End MLflow run.""" + if self._run is not None: + mlflow.end_run() + self._run = None diff --git a/docs/home/configuration_schema.md b/docs/home/configuration_schema.md new file mode 100644 index 00000000..e0f5ea44 --- /dev/null +++ b/docs/home/configuration_schema.md @@ -0,0 +1,944 @@ +--- +title: Configuration Schema +authors: ABSESpy Team +date: 2024-12-20 +--- + +# Configuration Schema Reference + +This document provides a comprehensive reference for the ABSESpy configuration schema. All configurations are managed through [Hydra](https://hydra.cc/) and use YAML format. The schema is designed to be simple, clear, and support both single-run experiments and parameter sweeps. + +## Overview + +ABSESpy uses a hierarchical configuration system based on OmegaConf (via Hydra). The configuration file is organized into **four core sections**: + +1. **`time`** - Time driver configuration +2. **`exp`** - Experiment management settings +3. **`model`** - All model parameters +4. **`tracker`** - Data collection and tracking configuration + +### Basic Configuration Structure + +```yaml +defaults: + - default + - _self_ + +# 1. Time driver configuration +time: + start: "2020-01-01" + end: 100 + +# 2. Experiment management +exp: + name: my_experiment + repeats: 1 + seed: 42 + +# 3. Model parameters +model: + density: 0.7 + n_agents: 50 + +# 4. Data tracking +tracker: + model: {} + agents: {} + final: {} +``` + +## Table of Contents + +- [1. Time Configuration](#1-time-configuration) +- [2. Experiment Configuration](#2-experiment-configuration) +- [3. Model Parameters](#3-model-parameters) +- [4. Tracker Configuration](#4-tracker-configuration) +- [Parameter Sweeps](#parameter-sweeps) +- [Complete Examples](#complete-examples) +- [Best Practices](#best-practices) + +--- + +## 1. Time Configuration + +The `time` section configures the simulation time driver. It controls when the simulation starts, ends, and how time progresses. + +### Time Schema + +```yaml +time: + # Start time (optional) + start: str | datetime | null # ISO format string or datetime, null = current time + + # End condition (required for auto-stop) + end: int | str | datetime | null # int = max ticks, str/datetime = end date, null = no auto-stop + + # Time step configuration (optional) + days: int # Duration in days + hours: int # Duration in hours + minutes: int # Duration in minutes + seconds: int # Duration in seconds + + # Advanced options + irregular: bool # Enable irregular time mode (default: false) +``` + +### Examples + +#### Simple: Maximum Steps + +```yaml +time: + end: 100 # Run for 100 steps +``` + +#### Calendar Time + +```yaml +time: + start: "2020-01-01" + end: "2020-12-31" # Run until end of year +``` + +#### Duration Mode + +```yaml +time: + start: "2020-01-01" + days: 365 # Run for 365 days +``` + +#### Combined + +```yaml +time: + start: "2020-01-01" + end: 200 # Or stop at 200 ticks, whichever comes first + days: 1 # Each step = 1 day +``` + +### Time Configuration Reference + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `start` | str/datetime/null | Current time | Simulation start time (ISO format) | +| `end` | int/str/datetime/null | null | End condition: int=tick limit, str/datetime=end date | +| `days` | int | 0 | Time step duration in days | +| `hours` | int | 0 | Time step duration in hours | +| `minutes` | int | 0 | Time step duration in minutes | +| `seconds` | int | 0 | Time step duration in seconds | +| `irregular` | bool | false | Enable irregular time mode | + +--- + +## 2. Experiment Configuration + +The `exp` section configures batch experiment settings, including repetitions, output management, and random seeds. + +### Experiment Schema + +```yaml +exp: + name: str # Experiment name + outdir: str # Output directory + repeats: int # Number of repetitions + seed: int | null # Base random seed (null = no seed) + logging: str | bool # Logging mode: "once" | "always" | false +``` + +### Examples + +#### Single Run + +```yaml +exp: + name: "single_run" + outdir: "outputs" + repeats: 1 + seed: 42 +``` + +#### Batch Run with Repetitions + +```yaml +exp: + name: "batch_experiment" + outdir: "outputs" + repeats: 11 # Run 11 times with different seeds + seed: 42 # Base seed for reproducibility + logging: "once" # Log only first repeat +``` + +### Experiment Configuration Reference + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `name` | str | "ABSESpy" | Experiment name (used in output paths) | +| `outdir` | str | "out" | Output directory for results | +| `repeats` | int | 1 | Number of repetitions (each with different seed) | +| `seed` | int/null | null | Base random seed (null = no seeding) | +| `logging` | str/bool | "once" | Logging mode: "once", "always", or false | + +--- + +## 3. Model Parameters + +The `model` section contains **all model-specific parameters**. These can be accessed in your model code via `model.params` or `model.p`. + +### Basic Model Parameters + +```yaml +model: + # Simple parameters + name: "my_model" + shape: [100, 100] + density: 0.7 + n_agents: 50 +``` + +### Nested Parameters + +Organize parameters hierarchically for better structure: + +```yaml +model: + grid: + width: 100 + height: 100 + torus: true + agents: + sheep: + initial_count: 100 + reproduction_rate: 0.1 + wolves: + initial_count: 20 + reproduction_rate: 0.05 + environment: + temperature: 20.0 + humidity: 0.6 +``` + +**Access in code:** +```python +width = model.params.grid.width +count = model.params.agents.sheep.initial_count +``` + +### Parameter Types + +| Type | Example | Description | +|------|---------|-------------| +| `int` | `42` | Integer values | +| `float` | `3.14` | Floating-point numbers | +| `str` | `"hello"` | String values | +| `bool` | `true` | Boolean values | +| `list` | `[1, 2, 3]` | Lists/arrays | +| `dict` | `{key: value}` | Nested dictionaries | + +### Parameter Ranges (for Sweeps) + +Define parameter ranges for automated parameter sweeps: + +```yaml +model: + density: + value: 0.7 # Default/single value + min: 0.5 # Minimum for sweep + max: 0.9 # Maximum for sweep + step: 0.1 # Step size + + n_agents: + value: 50 + min: 10 + max: 100 + step: 10 +``` + +**Note:** When using parameter sweeps with Hydra, you'll use the sweeper syntax (see [Parameter Sweeps](#parameter-sweeps) section). + +--- + +## 4. Tracker Configuration + +The `tracker` section (formerly `reports`) defines what data to collect during simulation. It supports three types of tracking: + +1. **Model trackers**: Collect model-level metrics at each step +2. **Agent trackers**: Collect agent-level attributes at each step +3. **Final trackers**: Collect metrics only at the end of simulation + +### Tracker Schema + +```yaml +tracker: + # Model-level trackers (collected every step) + model: + metric_name: "attribute_name" # Simple format + # OR (future extension) + metric_name: + source: "attribute_name" + alias: "custom_name" + + # Agent-level trackers (collected every step) + agents: + AgentBreedName: # Agent class name (exact match) + attribute_name: "attribute_name" + # OR (future extension) + attribute_name: + source: "attribute_name" + alias: "custom_name" + aggregate: "mean" # Optional: mean, sum, count, min, max, std + + # Final trackers (collected only at end) + final: + final_metric: "method_name" +``` + +### Simple Format (Recommended) + +The simplest and most common format uses a string that references an attribute or method name: + +```yaml +tracker: + model: + n_sheep: "n_sheep" # Collects model.n_sheep at each step + n_wolves: "n_wolves" # Collects model.n_wolves at each step + population_ratio: "population_ratio" # Collects model.population_ratio + + agents: + Sheep: # Agent breed name (class name) + energy: "energy" # Collects agent.energy for all Sheep + age: "age" # Collects agent.age for all Sheep + Wolf: + energy: "energy" + hunger: "hunger" + + final: + burned_rate: "burned_rate" # Calls model.burned_rate() at end + total_agents: "total_agents" +``` + +### Model Trackers + +Model trackers collect data from the model instance at each simulation step. + +**Requirements:** +- The referenced attribute/method must exist on your model class +- Methods should take no arguments (or only `self`) +- Should return a scalar value (int, float, bool, str) + +**Example Model Class:** + +```python +class MyModel(MainModel): + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.n_sheep = 0 + self.n_wolves = 0 + + @property + def population_ratio(self) -> float: + """Calculate sheep to wolf ratio.""" + if self.n_wolves == 0: + return 0.0 + return self.n_sheep / self.n_wolves + + def total_population(self) -> int: + """Get total population.""" + return self.n_sheep + self.n_wolves +``` + +**Corresponding Config:** + +```yaml +tracker: + model: + n_sheep: "n_sheep" # Property access + n_wolves: "n_wolves" # Property access + population_ratio: "population_ratio" # Property access + total_population: "total_population" # Method call +``` + +### Agent Trackers + +Agent trackers collect data from agent instances at each step. + +**Requirements:** +- The breed name must match the agent class name **exactly** (case-sensitive) +- The referenced attribute must exist on the agent class +- Attributes should be scalar values + +**Example Agent Classes:** + +```python +class Sheep(Actor): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.energy = 50 + self.age = 0 + + @property + def is_alive(self) -> bool: + return self.energy > 0 + +class Wolf(Actor): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.energy = 100 + self.hunger = 0.0 +``` + +**Corresponding Config:** + +```yaml +tracker: + agents: + Sheep: # Must match class name exactly + energy: "energy" + age: "age" + is_alive: "is_alive" # Property access + Wolf: + energy: "energy" + hunger: "hunger" +``` + +### Final Trackers + +Final trackers are called once at the end of the simulation. + +**Requirements:** +- Should be a method on the model class +- Method should take no arguments (or only `self`) +- Can return any type (will be stored as-is) + +**Example:** + +```python +class MyModel(MainModel): + def burned_rate(self) -> float: + """Calculate final burn rate.""" + burned = self.nature.select({"state": "burned"}) + total = len(self.nature.cells) + return len(burned) / total if total > 0 else 0.0 +``` + +**Corresponding Config:** + +```yaml +tracker: + final: + burned_rate: "burned_rate" +``` + +### Common Tracker Errors + +| Error | Cause | Solution | +|-------|-------|----------| +| `AttributeError: 'MyModel' has no attribute 'n_sheep'` | Attribute doesn't exist | Check attribute name spelling | +| `KeyError: 'Sheep'` | Agent breed name mismatch | Use exact class name (case-sensitive) | +| Empty DataFrame | No trackers defined | Add at least one tracker | +| `TypeError: 'str' object is not callable` | Tried to call a string | Use method name without quotes in code | + +--- + +## Parameter Sweeps + +ABSESpy supports parameter sweeps for automated batch experiments using Hydra's sweeper. You can define parameter ranges that Hydra will automatically expand into all combinations. + +### Using Hydra Sweeper + +Define parameter ranges in your config and use Hydra's sweeper syntax: + +```yaml +# config.yaml +defaults: + - default + - _self_ + +# Base parameter values +model: + density: 0.7 + n_agents: 50 + +# Hydra sweeper configuration +hydra: + sweeper: + params: + model.density: "range(0.5, 0.9, 0.1)" # 5 values: 0.5, 0.6, ..., 0.9 + model.n_agents: "range(10, 100, 10)" # 10 values: 10, 20, ..., 100 + # Total: 5 × 10 = 50 combinations +``` + +**Run with:** +```bash +python main.py --multirun +``` + +### Range Syntax + +```yaml +hydra: + sweeper: + params: + # Integer range: range(start, stop, step) + model.n_agents: "range(10, 100, 10)" + + # Float range: range(start, stop, step) + model.density: "range(0.5, 0.9, 0.1)" + + # Discrete values: [value1, value2, ...] + model.reproduction_rate: "[0.05, 0.1, 0.15, 0.2]" +``` + +### Multiple Parameter Combinations + +Hydra generates all combinations automatically: + +```yaml +hydra: + sweeper: + params: + model.density: "range(0.5, 0.9, 0.1)" # 5 values + model.n_agents: "range(10, 100, 10)" # 10 values + model.reproduction_rate: "[0.05, 0.1, 0.2]" # 3 values + # Total: 5 × 10 × 3 = 150 combinations +``` + +### Nested Parameter Sweeps + +```yaml +model: + agents: + sheep: + reproduction_rate: 0.1 + initial_energy: 50 + +hydra: + sweeper: + params: + model.agents.sheep.reproduction_rate: "range(0.05, 0.2, 0.05)" + model.agents.sheep.initial_energy: "range(30, 70, 10)" +``` + +### Accessing Swept Parameters in Code + +When using parameter sweeps, access the actual value in your model: + +```python +class MyModel(MainModel): + def initialize(self): + # Hydra will set the actual value for each run + density = self.params.density # Could be 0.5, 0.6, ..., 0.9 + n_agents = self.params.n_agents # Could be 10, 20, ..., 100 + + # Use in your model logic + self.create_agents(n_agents, density) +``` + +--- + +## Complete Examples + +### Example 1: Simple Model + +```yaml +defaults: + - default + - _self_ + +# Time: Run for 100 steps +time: + end: 100 + +# Experiment: Single run +exp: + name: simple_model + outdir: outputs + repeats: 1 + +# Model: Basic parameters +model: + name: "SimpleModel" + shape: [50, 50] + n_agents: 100 + +# Tracker: Basic data collection +tracker: + model: + n_agents: "n_agents" + step_count: "time.tick" + agents: + Actor: + unique_id: "unique_id" + final: + total_steps: "time.tick" +``` + +### Example 2: Parameter Sweep Experiment + +```yaml +defaults: + - default + - _self_ + +# Time: Calendar time simulation +time: + start: "2020-01-01" + end: 365 + days: 1 # Each step = 1 day + +# Experiment: Batch run with repetitions +exp: + name: parameter_sweep + outdir: outputs + repeats: 5 + seed: 42 + +# Model: Parameters with ranges +model: + density: + value: 0.7 + min: 0.5 + max: 0.9 + step: 0.1 + n_agents: + value: 50 + min: 20 + max: 100 + step: 20 + +# Tracker: Comprehensive tracking +tracker: + model: + density: "params.density" + n_agents: "params.n_agents" + population: "agents.count" + final: + final_population: "agents.count" + +# Hydra sweeper configuration +hydra: + sweeper: + params: + model.density: "range(0.5, 0.9, 0.1)" + model.n_agents: "range(20, 100, 20)" +``` + +### Example 3: Complex Ecosystem Model + +```yaml +defaults: + - default + - _self_ + +# Time: One year simulation +time: + start: "2020-01-01" + end: 365 + days: 1 + +# Experiment: Multiple repetitions +exp: + name: ecosystem_simulation + outdir: outputs + repeats: 11 + seed: 42 + logging: "once" + +# Model: Hierarchical parameters +model: + grid: + width: 100 + height: 100 + torus: true + agents: + sheep: + initial_count: 100 + reproduction_rate: 0.1 + initial_energy: 50 + wolves: + initial_count: 20 + reproduction_rate: 0.05 + initial_energy: 100 + environment: + grass_growth_rate: 0.2 + initial_grass_coverage: 0.8 + +# Tracker: Multi-level tracking +tracker: + model: + n_sheep: "n_sheep" + n_wolves: "n_wolves" + grass_coverage: "grass_coverage" + population_ratio: "population_ratio" + agents: + Sheep: + energy: "energy" + age: "age" + is_alive: "is_alive" + Wolf: + energy: "energy" + hunger: "hunger" + is_alive: "is_alive" + final: + final_sheep_count: "n_sheep" + final_wolf_count: "n_wolves" + survival_rate: "calculate_survival_rate" +``` + +--- + +## Best Practices + +### 1. Organize Parameters Hierarchically + +```yaml +# ✅ Good: Organized by component +model: + grid: + width: 100 + height: 100 + agents: + sheep: {...} + wolves: {...} + +# ❌ Bad: Flat structure +model: + grid_width: 100 + grid_height: 100 + sheep_count: 100 + wolf_count: 20 +``` + +### 2. Use Descriptive Names + +```yaml +# ✅ Good: Clear names +tracker: + model: + sheep_population: "n_sheep" + wolf_population: "n_wolves" + +# ❌ Bad: Unclear names +tracker: + model: + s: "n_sheep" + w: "n_wolves" +``` + +### 3. Match Agent Breed Names Exactly + +```yaml +# ✅ Good: Exact class name match +tracker: + agents: + Sheep: # Must match class name exactly + energy: "energy" + +# ❌ Bad: Name mismatch +tracker: + agents: + sheep: # Wrong: lowercase + energy: "energy" +``` + +### 4. Document Custom Parameters + +```yaml +model: + # Reproduction probability per step + reproduction_rate: 0.1 + + # Initial energy for new agents + initial_energy: 50 +``` + +### 5. Define Parameter Ranges for Future Sweeps + +Even if not using sweeper immediately, define ranges: + +```yaml +model: + density: + value: 0.7 # Current value + min: 0.5 # For future sweeps + max: 0.9 + step: 0.1 +``` + +### 6. Keep Configurations Simple + +Start with simple tracker format, upgrade to advanced format only when needed: + +```yaml +# ✅ Good: Simple and clear +tracker: + model: + n_sheep: "n_sheep" + +# ⚠️ Advanced: Only when needed +tracker: + model: + n_sheep: + source: "n_sheep" + alias: "sheep_count" +``` + +--- + +## Configuration Quick Reference + +### Minimal Configuration + +```yaml +defaults: + - default + - _self_ + +time: + end: 100 + +exp: + name: my_experiment + +model: {} + +tracker: {} +``` + +### Full Configuration Template + +```yaml +defaults: + - default + - _self_ + +# Time driver +time: + start: "2020-01-01" + end: 365 + days: 1 + +# Experiment management +exp: + name: my_experiment + outdir: outputs + repeats: 1 + seed: 42 + logging: "once" + +# Model parameters +model: + # Your parameters here + param1: value1 + param2: value2 + +# Data tracking +tracker: + model: + metric1: "attribute1" + agents: + AgentBreed: + attr1: "attr1" + final: + final_metric: "method_name" + +# Parameter sweeps (optional) +hydra: + sweeper: + params: + model.param1: "range(min, max, step)" +``` + +--- + +## Troubleshooting + +### Configuration Not Loading + +**Problem:** Config file not found or not loading. + +**Solutions:** +- Check file path in `config_path` +- Ensure YAML syntax is correct (use a YAML validator) +- Check Hydra defaults list includes `default` and `_self_` + +### Tracker Not Collecting Data + +**Problem:** Empty DataFrames or missing columns. + +**Solutions:** +- Verify attribute/method names match exactly (case-sensitive) +- Check that attributes exist on model/agent classes +- Ensure trackers are defined in `tracker` section +- Check logs for validation warnings + +### Parameter Sweep Not Working + +**Problem:** Sweeper not generating combinations. + +**Solutions:** +- Verify Hydra sweeper configuration +- Check parameter range syntax: `range(min, max, step)` +- Ensure `--multirun` flag is used +- Check Hydra output directory for generated configs + +### Agent Breed Name Not Found + +**Problem:** `KeyError` when accessing agent trackers. + +**Solutions:** +- Ensure breed name matches class name exactly (case-sensitive) +- Check that agents of that breed exist in the model +- Verify agent class is properly registered + +--- + +## Migration from Old Schema + +### From `reports` to `tracker` + +The `reports` section has been renamed to `tracker` for clarity. The old name is still supported for backward compatibility but will be normalized automatically. + +**Old (still works):** +```yaml +reports: + model: + n_sheep: "n_sheep" +``` + +**New (recommended):** +```yaml +tracker: + model: + n_sheep: "n_sheep" +``` + +### From `agent` to `agents` + +The deprecated `agent` key is automatically normalized to `agents`. + +**Old (still works):** +```yaml +tracker: + agent: + Actor: + energy: "energy" +``` + +**New (recommended):** +```yaml +tracker: + agents: + Actor: + energy: "energy" +``` + +--- + +## See Also + +- [Getting Started Guide](get_started.md) - Basic usage +- [Parameter Management Tutorial](../tutorial/beginner/manage_parameters.ipynb) - Hands-on examples +- [Experiment API](../api/experiment.md) - Batch experiment details +- [Time Control API](../api/time.md) - Time driver details +- [Hydra Documentation](https://hydra.cc/docs/intro/) - Hydra framework reference diff --git a/examples/fire_spread/config.yaml b/examples/fire_spread/config.yaml index 5a6a5666..19c589ab 100644 --- a/examples/fire_spread/config.yaml +++ b/examples/fire_spread/config.yaml @@ -7,10 +7,28 @@ exp: outdir: out repeats: 11 -reports: - final: +# Tracker configuration for experiment tracking +# Use Aim for advanced experiment tracking (requires: pip install abses[aim]) +tracker: + backend: aim + # Customize run name (supports template variables: {model_name}, {run_id}, {version}) + run_name: "{model_name}_experiment_run_{run_id}" + # Customize tags (supports template variables in values) + tags: + experiment: "fire_spread" + model_type: "{model_name}" + version: "{version}" + # Backend-specific configuration + aim: + experiment: "fire_spread_experiment" + repo: null # Use default Aim repo (~/.aim) + # Data collection configuration + model: burned_rate: "burned_rate" - + final: + burned_rate_final: "burned_rate" + # Log model parameters as hyperparameters (default: true) + log_params: true model: density: 0.7 diff --git a/examples/fire_spread/model.py b/examples/fire_spread/model.py index 3c5a9b80..883cba51 100644 --- a/examples/fire_spread/model.py +++ b/examples/fire_spread/model.py @@ -134,6 +134,7 @@ def step(self) -> None: Executes step method on all cells using batch operation. """ self.nature.cells_lst.shuffle_do("step") + self.datacollector.collect(self) @property def burned_rate(self) -> float: diff --git a/mkdocs.yml b/mkdocs.yml index dd7c9fb8..340afad7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -6,6 +6,7 @@ nav: - Installation: home/Installation.md - Guide Checklist: home/guide_checklist.md - Getting started: home/get_started.md + - Configuration Schema: home/configuration_schema.md - Dependencies: home/dependencies.md - License: home/license.md - Contributions: home/contribution.md diff --git a/pyproject.toml b/pyproject.toml index 04c574dd..74e53e57 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,6 +41,7 @@ dependencies = [ "numpy>=1.26", "urllib3", "icons", + "aim>=3.29.1", ] [project.urls] @@ -49,6 +50,13 @@ Documentation = "https://absespy.github.io/ABSESpy/" Repository = "https://github.com/SongshGeoLab/ABSESpy" [project.optional-dependencies] +# Optional tracker dependencies for experiment tracking. +aim = [ + "aim>=3.0.0", +] +mlflow = [ + "mlflow>=2.0.0", +] # Optional docs-related dependencies. Not required at runtime. docs = [ "mkdocs>=1.6.1", diff --git a/tests/core/test_model_config.py b/tests/core/test_model_config.py new file mode 100644 index 00000000..2eaa5a6c --- /dev/null +++ b/tests/core/test_model_config.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +# -*-coding:utf-8 -*- +"""Integration tests for MainModel configuration validation.""" + +from __future__ import annotations + +import pytest +from omegaconf import OmegaConf + +from abses import MainModel +from abses.utils.errors import ConfigurationError + + +def test_main_model_validation_strict_raises() -> None: + """Strict validation should raise on invalid config.""" + cfg = OmegaConf.create( + { + "exp": {"repeats": 0}, + "tracker": {"model": {"m": 123}}, + } + ) + validate_cfg = OmegaConf.create({"enabled": True, "strict": True}) + cfg.validate = validate_cfg + with pytest.raises(ConfigurationError): + MainModel(parameters=cfg) + + +def test_main_model_normalizes_reports() -> None: + """reports should be accepted and normalized to tracker.""" + cfg = OmegaConf.create( + { + "reports": {"model": {"n_agents": "n_agents"}}, + "time": {"end": 1}, + } + ) + model = MainModel(parameters=cfg) + assert "n_agents" in model.datacollector.model_reporters diff --git a/tests/utils/test_config.py b/tests/utils/test_config.py new file mode 100644 index 00000000..8403543e --- /dev/null +++ b/tests/utils/test_config.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +# -*-coding:utf-8 -*- +"""Tests for configuration normalization and validation.""" + +from __future__ import annotations + +import pytest +from omegaconf import OmegaConf + +from abses.utils.config import ( + normalize_config, + validate_config, +) +from abses.utils.errors import ConfigurationError + + +def test_normalize_reports_to_tracker() -> None: + """reports should be normalized to tracker.""" + cfg = OmegaConf.create({"reports": {"model": {"a": "a"}, "agent": {"X": {}}}}) + normalized = normalize_config(cfg) + assert "tracker" in normalized + assert "agent" not in normalized.tracker + assert "agents" in normalized.tracker + assert normalized.tracker.model.a == "a" + + +def test_validate_time_section() -> None: + """Invalid time section should produce errors.""" + cfg = OmegaConf.create({"time": {"start": 123, "end": {"bad": "x"}}}) + errors = validate_config(cfg, strict=False) + assert any("time.start" in err for err in errors) + assert any("time.end" in err for err in errors) + + +def test_validate_exp_section() -> None: + """Invalid exp section should produce errors.""" + cfg = OmegaConf.create( + {"exp": {"repeats": -1, "seed": "bad", "logging": "invalid"}} + ) + errors = validate_config(cfg, strict=False) + assert any("exp.repeats" in err for err in errors) + assert any("exp.seed" in err for err in errors) + assert any("exp.logging" in err for err in errors) + + +def test_validate_model_range() -> None: + """Model range should enforce numeric and ordering.""" + cfg = OmegaConf.create({"model": {"density": {"min": 1.0, "max": 0.5, "step": -1}}}) + errors = validate_config(cfg, strict=False) + assert any("min should be < max" in err for err in errors) + assert any("step: must be > 0" in err for err in errors) + + +def test_validate_tracker_section() -> None: + """Tracker validation should capture invalid entries.""" + cfg = OmegaConf.create( + { + "tracker": { + "backend": "unknown", + "model": {"m": 123}, + "agents": {"X": "bad"}, + "final": {"f": {"aggregate": "invalid"}}, + } + } + ) + errors = validate_config(cfg, strict=False) + assert any("tracker.backend" in err for err in errors) + assert any("tracker.model.m" in err for err in errors) + assert any("tracker.agents.X" in err for err in errors) + assert any("aggregate" in err for err in errors) + + +def test_validate_strict_raises() -> None: + """Strict validation should raise ConfigurationError.""" + cfg = OmegaConf.create({"exp": {"repeats": 0}}) + with pytest.raises(ConfigurationError): + validate_config(cfg, strict=True) diff --git a/tests/utils/test_tracker.py b/tests/utils/test_tracker.py new file mode 100644 index 00000000..b557ccc1 --- /dev/null +++ b/tests/utils/test_tracker.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# -*-coding:utf-8 -*- +"""Tests for tracker factory and default tracker.""" + +from __future__ import annotations + +import pytest +from omegaconf import OmegaConf + +from abses.utils.tracker.default import DefaultTracker +from abses.utils.tracker.factory import create_tracker + + +def test_create_tracker_default() -> None: + """Default tracker is returned when backend not set.""" + tracker = create_tracker(OmegaConf.create({}), model=None) + assert isinstance(tracker, DefaultTracker) + + +def test_create_tracker_unknown_backend_fallback() -> None: + """Unknown backend should fall back to default.""" + tracker = create_tracker(OmegaConf.create({"backend": "unknown"}), model=None) + assert isinstance(tracker, DefaultTracker) + + +def test_create_tracker_aim_missing_dep() -> None: + """Aim backend without dependency should raise ConfigurationError.""" + cfg = OmegaConf.create({"backend": "mlflow"}) + with pytest.raises(Exception): + create_tracker(cfg, model=None) diff --git a/uv.lock b/uv.lock index e23e66d2..b060f086 100644 --- a/uv.lock +++ b/uv.lock @@ -11,6 +11,7 @@ name = "abses" version = "0.9.0" source = { editable = "." } dependencies = [ + { name = "aim" }, { name = "fiona" }, { name = "fontawesome" }, { name = "geocube" }, @@ -31,6 +32,9 @@ dependencies = [ ] [package.optional-dependencies] +aim = [ + { name = "aim" }, +] docs = [ { name = "mike" }, { name = "mkdocs" }, @@ -52,6 +56,9 @@ docs = [ { name = "nbmake" }, { name = "pymdown-extensions" }, ] +mlflow = [ + { name = "mlflow" }, +] [package.dev-dependencies] dev = [ @@ -106,6 +113,8 @@ docs = [ [package.metadata] requires-dist = [ + { name = "aim", specifier = ">=3.29.1" }, + { name = "aim", marker = "extra == 'aim'", specifier = ">=3.0.0" }, { name = "fiona", specifier = ">1.8" }, { name = "fontawesome", specifier = ">=5" }, { name = "geocube", specifier = ">=0.5.0" }, @@ -132,6 +141,7 @@ requires-dist = [ { name = "mkdocs-static-i18n", marker = "extra == 'docs'", specifier = ">=1.3.0" }, { name = "mkdocstrings", marker = "extra == 'docs'", specifier = ">=0.30.1" }, { name = "mkdocstrings", extras = ["python"], marker = "extra == 'docs'", specifier = ">=0.24.0" }, + { name = "mlflow", marker = "extra == 'mlflow'", specifier = ">=2.0.0" }, { name = "nbconvert", marker = "extra == 'docs'", specifier = ">=7.16.6" }, { name = "nbmake", marker = "extra == 'docs'", specifier = ">=1.5.5" }, { name = "netcdf4", specifier = ">=1.6" }, @@ -144,7 +154,7 @@ requires-dist = [ { name = "urllib3" }, { name = "xarray", specifier = ">=2023" }, ] -provides-extras = ["docs"] +provides-extras = ["aim", "mlflow", "docs"] [package.metadata.requires-dev] dev = [ @@ -207,6 +217,109 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0b/f7/85273299ab57117850cc0a936c64151171fac4da49bc6fba0dad984a7c5f/affine-2.4.0-py3-none-any.whl", hash = "sha256:8a3df80e2b2378aef598a83c1392efd47967afec4242021a0b06b4c7cbc61a92", size = 15662, upload-time = "2023-01-19T23:44:28.833Z" }, ] +[[package]] +name = "aim" +version = "3.29.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aim-ui" }, + { name = "aimrecords" }, + { name = "aimrocks" }, + { name = "aiofiles" }, + { name = "alembic" }, + { name = "boto3" }, + { name = "cachetools" }, + { name = "click" }, + { name = "cryptography" }, + { name = "fastapi" }, + { name = "filelock" }, + { name = "jinja2" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "psutil" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "requests" }, + { name = "restrictedpython" }, + { name = "sqlalchemy" }, + { name = "tqdm" }, + { name = "uvicorn" }, + { name = "watchdog" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/25/c825c73ec2f48c93324f631dba6e4cdac3bb60a7fde36e0b916820ae62a5/aim-3.29.1.tar.gz", hash = "sha256:30fb70f983844eebd270049206c839e6dc09ce9de500048dc97a7a8b22ed83fb", size = 1660733, upload-time = "2025-05-08T09:51:58.892Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/f4/e9a5d8fdb31d61c450492553e18f7aae6a5d5baf266747d99135c53b95c1/aim-3.29.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:a3fc8557dfb910c3aec784017c00ead65e1aeb5b994f7954de4679d83165d1ab", size = 2526973, upload-time = "2025-05-08T09:54:28.685Z" }, + { url = "https://files.pythonhosted.org/packages/7a/5b/f4d949cbd13ccdc8f5ede254d44155fbad1fe05a4cb9d517740439b87efd/aim-3.29.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ee164cfd5fceda2d751da2b10697e9bb3f64798d928596ee970e2b0891a1e0d0", size = 2480646, upload-time = "2025-05-08T09:52:52.495Z" }, + { url = "https://files.pythonhosted.org/packages/96/04/4d523b4c6c19a521558aba8145ff49ab43d6663c95b4bf9905be0e18e2bf/aim-3.29.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10bb79928b0017043428c227e33ee592b0f34f1f72e65ea94b74eddcc4038fc8", size = 7311952, upload-time = "2025-05-08T09:59:42.217Z" }, + { url = "https://files.pythonhosted.org/packages/b9/02/0bd5126aad71bf0f789cb3c28f534aaef6d92be90f1925e6f5cb63438daf/aim-3.29.1-cp311-cp311-manylinux_2_24_x86_64.whl", hash = "sha256:165dc403cb4f9f04b83977e9fc40c86689e3a3f3bb61341cbe03591a16ec041d", size = 5873965, upload-time = "2025-05-08T10:01:33.935Z" }, + { url = "https://files.pythonhosted.org/packages/8c/19/06cac7b4d200f337ee451482090cf02553e361595733ba5ee5e701a926fe/aim-3.29.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:350e640c3c6bc532e0ad6d5cf6479f82f309a38bf05f84d30947d00a36f94523", size = 7480743, upload-time = "2025-05-08T10:04:46.35Z" }, + { url = "https://files.pythonhosted.org/packages/70/07/4b6c4bda0469fad31a902ff1205ed010b3459be9565a8ffa2bfdcbb1e2bd/aim-3.29.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:301afdf3e80b3ccac4cdc786a73f81656bd3b1ec73836de19a67a77a0b3d3379", size = 2512723, upload-time = "2025-05-08T09:54:30.314Z" }, + { url = "https://files.pythonhosted.org/packages/a1/a5/da5e0de60a8d32ab8b2d725eec2ade3303117697cd714fb1b00134d01a64/aim-3.29.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fbb9a7f441721cd4999174d0d6fa1e18470c01dddf2f193e7c3d054543a4ca6", size = 2472712, upload-time = "2025-05-08T09:55:16.102Z" }, + { url = "https://files.pythonhosted.org/packages/bc/05/a55a86258f3505e0ab53ee81afcc0057d805a1de8d5a66390f75e32aa777/aim-3.29.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:15cea0c63d9a61812229d7140cec04f02117626d1724e10bebe410554f3f7561", size = 7590249, upload-time = "2025-05-08T10:04:48.663Z" }, +] + +[[package]] +name = "aim-ui" +version = "3.29.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0c/fe/723615593ee1bffbf89c1cb8028b72893120b3d4e737712d91041a5f0382/aim-ui-3.29.1.tar.gz", hash = "sha256:bc30278acad048a087f84e3f6e2565f53f75e87d8c29748c90420ec06b820113", size = 30971471, upload-time = "2025-05-08T09:50:58.888Z" } + +[[package]] +name = "aimrecords" +version = "0.0.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "base58" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/10/21182ef96acbd9a5ca4008556ba8590cbc1af833eccbf59d53308fa6d928/aimrecords-0.0.7.tar.gz", hash = "sha256:9b562fa5b5109b4b3dd4f83be0061cadbac63fa8031f281b8b5c8ae29967072f", size = 12667, upload-time = "2020-11-09T13:29:29.071Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/13/207ebb5b2315640a68378c31cb31cfe1182373d11a813bd52219c83700a7/aimrecords-0.0.7-py2.py3-none-any.whl", hash = "sha256:b9276890891c5fd68f817e20fc5d466a80c01e22fa468eaa979331448a75d601", size = 28657, upload-time = "2020-11-09T13:29:26.015Z" }, +] + +[[package]] +name = "aimrocks" +version = "0.5.2" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/95/725302cceba1e95dc5084d1a6244f3a97c9fd4923ae120195f5c79299e16/aimrocks-0.5.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:03ca9bd3a7d379f40c678e648d3ec1445738a32fee337009cfb6aa9aedc51964", size = 4187601, upload-time = "2024-04-24T17:21:55.249Z" }, + { url = "https://files.pythonhosted.org/packages/97/65/4facb46715fb7bf9b3f22930dd04165fcc74991366bedd74740c82a29d70/aimrocks-0.5.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d74170021b17451881df18683eb0aa97417cb8b030b3dea425d7580891c22608", size = 3933174, upload-time = "2024-04-24T17:21:56.887Z" }, + { url = "https://files.pythonhosted.org/packages/01/13/a35d72ec37d2f654db8b2d9025b19f5a50d73d28024dafbc11fdddf01af2/aimrocks-0.5.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff6334af4ac403438eae330bf25fff5b3a63ba9f8f87a77ebb2d34815ef36431", size = 6664690, upload-time = "2024-04-24T17:21:58.58Z" }, + { url = "https://files.pythonhosted.org/packages/e0/24/829d6f9bd21aa898fb5ef598c47123d509a369b59212bbfc88b901b657da/aimrocks-0.5.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f479567d8514a63ee7f227d0841dc886870c37c3f6e17a8724ecaebfaa1331b2", size = 6858965, upload-time = "2024-04-24T17:22:01.342Z" }, + { url = "https://files.pythonhosted.org/packages/63/d3/cd78c8f15ee8cd38b46ec6dc08404b9c6d65372c016ea755f06b9100bb29/aimrocks-0.5.2-cp311-cp311-manylinux_2_24_aarch64.whl", hash = "sha256:3f65583d29bcfc3baa422e45e73de89c4c781490664eb49c1a4c21c074f5bbfa", size = 5986214, upload-time = "2024-04-24T17:22:03.64Z" }, + { url = "https://files.pythonhosted.org/packages/01/11/3fcb62df83e2f147ff787b7aad6b934512016fbf38f217dd3ce7fbe1f03b/aimrocks-0.5.2-cp311-cp311-manylinux_2_24_x86_64.whl", hash = "sha256:762f7b41793165717a9e0589658cd81bffb54161295ec7403534d40692ac9281", size = 6170762, upload-time = "2024-04-24T17:22:06.031Z" }, + { url = "https://files.pythonhosted.org/packages/46/13/77aa7f850798104139ec1b6a3f5aa77a6d0333ee12ba31fe5bc166557976/aimrocks-0.5.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9becbd34b2bac33dae7db5ce85f9ef70b83b20fd547794a40b7a7bd846be45fa", size = 6606015, upload-time = "2024-04-24T17:22:08.124Z" }, + { url = "https://files.pythonhosted.org/packages/52/2d/21b14e4253b71378e70e7a0b21dd1fcca1d72dff22b1bd68b1e8f0694298/aimrocks-0.5.2-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:76258350f2715d686d5da12a5a2df0d7b88e1b33e45052e0ddeb549c7497a56e", size = 4178867, upload-time = "2024-04-24T17:22:09.989Z" }, + { url = "https://files.pythonhosted.org/packages/0d/1d/a26c0a19b403986cd6d56142fe8b2c254211e7a64ec40e130e75edb1711c/aimrocks-0.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:79400c6f4c72bcc4485f2a4411a3e6c1f6ead7a3928a00a72739abb1ef9ec0d3", size = 3925297, upload-time = "2024-04-24T17:22:12.171Z" }, + { url = "https://files.pythonhosted.org/packages/e1/80/e0c226cbf975b109589c56b7aecc7ca182e3a62f67eb4a791ee033cee2d1/aimrocks-0.5.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9faaecf4fe0335c27e63523f6a25e038877a33c63a261ff2192582e52493b39b", size = 6541866, upload-time = "2024-04-24T17:22:13.853Z" }, + { url = "https://files.pythonhosted.org/packages/e9/2b/109e6340b4ea052c762e61d386d79222855a2b0e27d6bb9ad7cd76d140b1/aimrocks-0.5.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9ba647f32934ac999c4119cbb8b59510dfe69aec98558539b84db7db9f20acf", size = 6743116, upload-time = "2024-04-24T17:22:15.548Z" }, + { url = "https://files.pythonhosted.org/packages/69/44/8b381f01ca1a1cbdec71e5329908ea1f782a6eced10dbbfa8cd9e44858d4/aimrocks-0.5.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:533eda940f4bd1641fee15da09595c965d6890e449706fb3442174472b468a19", size = 6506051, upload-time = "2024-04-24T17:22:17.224Z" }, +] + +[[package]] +name = "aiofiles" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/41/c3/534eac40372d8ee36ef40df62ec129bee4fdb5ad9706e58a29be53b2c970/aiofiles-25.1.0.tar.gz", hash = "sha256:a8d728f0a29de45dc521f18f07297428d56992a742f0cd2701ba86e44d23d5b2", size = 46354, upload-time = "2025-10-09T20:51:04.358Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/8a/340a1555ae33d7354dbca4faa54948d76d89a27ceef032c8c3bc661d003e/aiofiles-25.1.0-py3-none-any.whl", hash = "sha256:abe311e527c862958650f9438e859c1fa7568a141b22abcd015e120e86a85695", size = 14668, upload-time = "2025-10-09T20:51:03.174Z" }, +] + +[[package]] +name = "alembic" +version = "1.17.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mako" }, + { name = "sqlalchemy" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/a6/74c8cadc2882977d80ad756a13857857dbcf9bd405bc80b662eb10651282/alembic-1.17.2.tar.gz", hash = "sha256:bbe9751705c5e0f14877f02d46c53d10885e377e3d90eda810a016f9baa19e8e", size = 1988064, upload-time = "2025-11-14T20:35:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/88/6237e97e3385b57b5f1528647addea5cc03d4d65d5979ab24327d41fb00d/alembic-1.17.2-py3-none-any.whl", hash = "sha256:f483dd1fe93f6c5d49217055e4d15b905b425b6af906746abb35b69c1996c4e6", size = 248554, upload-time = "2025-11-14T20:35:05.699Z" }, +] + [[package]] name = "allure-pytest" version = "2.15.0" @@ -249,6 +362,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/aa/f3/0b6ced594e51cc95d8c1fc1640d3623770d01e4969d29c0bd09945fafefa/altair-5.5.0-py3-none-any.whl", hash = "sha256:91a310b926508d560fe0148d02a194f38b824122641ef528113d029fcd129f8c", size = 731200, upload-time = "2024-11-23T23:39:56.4Z" }, ] +[[package]] +name = "annotated-doc" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + [[package]] name = "antlr4-python3-runtime" version = "4.9.3" @@ -406,6 +537,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/48/ca/ba5f909b40ea12ec542d5d7bdd13ee31c4d65f3beed20211ef81c18fa1f3/bandit-1.8.6-py3-none-any.whl", hash = "sha256:3348e934d736fcdb68b6aa4030487097e23a501adf3e7827b63658df464dddd0", size = 133808, upload-time = "2025-07-06T03:10:49.134Z" }, ] +[[package]] +name = "base58" +version = "2.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/0c/44d075d9b07bb25c4733421057a46fe1847e9c64286e8af11458815ff872/base58-2.0.1.tar.gz", hash = "sha256:365c9561d9babac1b5f18ee797508cd54937a724b6e419a130abad69cec5ca79", size = 4960, upload-time = "2020-06-06T13:25:31.324Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/03/58572025c77b9e6027155b272a1b96298e711cd4f95c24967f7137ab0c4b/base58-2.0.1-py3-none-any.whl", hash = "sha256:447adc750d6b642987ffc6d397ecd15a799852d5f6a1d308d384500243825058", size = 4347, upload-time = "2020-06-06T13:25:30.022Z" }, +] + [[package]] name = "beautifulsoup4" version = "4.14.2" @@ -477,6 +617,43 @@ css = [ { name = "tinycss2" }, ] +[[package]] +name = "blinker" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" }, +] + +[[package]] +name = "boto3" +version = "1.42.19" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, + { name = "jmespath" }, + { name = "s3transfer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/34/36/045babbc2978fe3bf051a002e9ee492a815e50b90aea514b811d12aa33cb/boto3-1.42.19.tar.gz", hash = "sha256:5933696a28bf8eb62fc54e4de5583f78a0efef59c8164ee1850436aa22f53aa7", size = 112803, upload-time = "2025-12-30T20:29:30.785Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/ca/2b46e9fa3953c21a258b06e33f14452d97646ffbe7ace125e6aec62d4d7c/boto3-1.42.19-py3-none-any.whl", hash = "sha256:c55b8b303c64931272536813a476f130b90ea7041d7b79c154d89cf1c18256b4", size = 140576, upload-time = "2025-12-30T20:29:29.319Z" }, +] + +[[package]] +name = "botocore" +version = "1.42.19" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jmespath" }, + { name = "python-dateutil" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/39/d6/33565766764492a0a4956ef161498d81a8c48c1e918aeaeb29def52c3367/botocore-1.42.19.tar.gz", hash = "sha256:8d38f30de983720303e95951380a2c9ac515159636ee6b5ba4227d65f14551a4", size = 14874191, upload-time = "2025-12-30T20:29:21.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/f9/f75b8ff225895f26bda4981b04df68b0ece29aa18aaafe4f21a3e4d82139/botocore-1.42.19-py3-none-any.whl", hash = "sha256:30c276e0a96d822826d74e961089b9af16b274ac7ddcf7dcf6440bc90d856d88", size = 14550311, upload-time = "2025-12-30T20:29:18.223Z" }, +] + [[package]] name = "bracex" version = "2.6" @@ -716,6 +893,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/73/86/43fa9f15c5b9fb6e82620428827cd3c284aa933431405d1bcf5231ae3d3e/cligj-0.7.2-py3-none-any.whl", hash = "sha256:c1ca117dbce1fe20a5809dc96f01e1c2840f6dcc939b3ddbb1111bf330ba82df", size = 7069, upload-time = "2021-05-28T21:23:26.877Z" }, ] +[[package]] +name = "cloudpickle" +version = "3.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/27/fb/576f067976d320f5f0114a8d9fa1215425441bb35627b1993e5afd8111e5/cloudpickle-3.1.2.tar.gz", hash = "sha256:7fda9eb655c9c230dab534f1983763de5835249750e85fbcef43aaa30a9a2414", size = 22330, upload-time = "2025-11-03T09:25:26.604Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl", hash = "sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a", size = 22228, upload-time = "2025-11-03T09:25:25.534Z" }, +] + [[package]] name = "colorama" version = "0.4.6" @@ -860,6 +1046,53 @@ toml = [ { name = "tomli", marker = "python_full_version <= '3.11'" }, ] +[[package]] +name = "cryptography" +version = "46.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/33/c00162f49c0e2fe8064a62cb92b93e50c74a72bc370ab92f86112b33ff62/cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", size = 749258, upload-time = "2025-10-15T23:18:31.74Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/42/9c391dd801d6cf0d561b5890549d4b27bafcc53b39c31a817e69d87c625b/cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a", size = 7225004, upload-time = "2025-10-15T23:16:52.239Z" }, + { url = "https://files.pythonhosted.org/packages/1c/67/38769ca6b65f07461eb200e85fc1639b438bdc667be02cf7f2cd6a64601c/cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc", size = 4296667, upload-time = "2025-10-15T23:16:54.369Z" }, + { url = "https://files.pythonhosted.org/packages/5c/49/498c86566a1d80e978b42f0d702795f69887005548c041636df6ae1ca64c/cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d", size = 4450807, upload-time = "2025-10-15T23:16:56.414Z" }, + { url = "https://files.pythonhosted.org/packages/4b/0a/863a3604112174c8624a2ac3c038662d9e59970c7f926acdcfaed8d61142/cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb", size = 4299615, upload-time = "2025-10-15T23:16:58.442Z" }, + { url = "https://files.pythonhosted.org/packages/64/02/b73a533f6b64a69f3cd3872acb6ebc12aef924d8d103133bb3ea750dc703/cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849", size = 4016800, upload-time = "2025-10-15T23:17:00.378Z" }, + { url = "https://files.pythonhosted.org/packages/25/d5/16e41afbfa450cde85a3b7ec599bebefaef16b5c6ba4ec49a3532336ed72/cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8", size = 4984707, upload-time = "2025-10-15T23:17:01.98Z" }, + { url = "https://files.pythonhosted.org/packages/c9/56/e7e69b427c3878352c2fb9b450bd0e19ed552753491d39d7d0a2f5226d41/cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec", size = 4482541, upload-time = "2025-10-15T23:17:04.078Z" }, + { url = "https://files.pythonhosted.org/packages/78/f6/50736d40d97e8483172f1bb6e698895b92a223dba513b0ca6f06b2365339/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91", size = 4299464, upload-time = "2025-10-15T23:17:05.483Z" }, + { url = "https://files.pythonhosted.org/packages/00/de/d8e26b1a855f19d9994a19c702fa2e93b0456beccbcfe437eda00e0701f2/cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e", size = 4950838, upload-time = "2025-10-15T23:17:07.425Z" }, + { url = "https://files.pythonhosted.org/packages/8f/29/798fc4ec461a1c9e9f735f2fc58741b0daae30688f41b2497dcbc9ed1355/cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926", size = 4481596, upload-time = "2025-10-15T23:17:09.343Z" }, + { url = "https://files.pythonhosted.org/packages/15/8d/03cd48b20a573adfff7652b76271078e3045b9f49387920e7f1f631d125e/cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71", size = 4426782, upload-time = "2025-10-15T23:17:11.22Z" }, + { url = "https://files.pythonhosted.org/packages/fa/b1/ebacbfe53317d55cf33165bda24c86523497a6881f339f9aae5c2e13e57b/cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac", size = 4698381, upload-time = "2025-10-15T23:17:12.829Z" }, + { url = "https://files.pythonhosted.org/packages/96/92/8a6a9525893325fc057a01f654d7efc2c64b9de90413adcf605a85744ff4/cryptography-46.0.3-cp311-abi3-win32.whl", hash = "sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018", size = 3055988, upload-time = "2025-10-15T23:17:14.65Z" }, + { url = "https://files.pythonhosted.org/packages/7e/bf/80fbf45253ea585a1e492a6a17efcb93467701fa79e71550a430c5e60df0/cryptography-46.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb", size = 3514451, upload-time = "2025-10-15T23:17:16.142Z" }, + { url = "https://files.pythonhosted.org/packages/2e/af/9b302da4c87b0beb9db4e756386a7c6c5b8003cd0e742277888d352ae91d/cryptography-46.0.3-cp311-abi3-win_arm64.whl", hash = "sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c", size = 2928007, upload-time = "2025-10-15T23:17:18.04Z" }, + { url = "https://files.pythonhosted.org/packages/fd/23/45fe7f376a7df8daf6da3556603b36f53475a99ce4faacb6ba2cf3d82021/cryptography-46.0.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936", size = 7218248, upload-time = "2025-10-15T23:17:46.294Z" }, + { url = "https://files.pythonhosted.org/packages/27/32/b68d27471372737054cbd34c84981f9edbc24fe67ca225d389799614e27f/cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683", size = 4294089, upload-time = "2025-10-15T23:17:48.269Z" }, + { url = "https://files.pythonhosted.org/packages/26/42/fa8389d4478368743e24e61eea78846a0006caffaf72ea24a15159215a14/cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d", size = 4440029, upload-time = "2025-10-15T23:17:49.837Z" }, + { url = "https://files.pythonhosted.org/packages/5f/eb/f483db0ec5ac040824f269e93dd2bd8a21ecd1027e77ad7bdf6914f2fd80/cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0", size = 4297222, upload-time = "2025-10-15T23:17:51.357Z" }, + { url = "https://files.pythonhosted.org/packages/fd/cf/da9502c4e1912cb1da3807ea3618a6829bee8207456fbbeebc361ec38ba3/cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc", size = 4012280, upload-time = "2025-10-15T23:17:52.964Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8f/9adb86b93330e0df8b3dcf03eae67c33ba89958fc2e03862ef1ac2b42465/cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3", size = 4978958, upload-time = "2025-10-15T23:17:54.965Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a0/5fa77988289c34bdb9f913f5606ecc9ada1adb5ae870bd0d1054a7021cc4/cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971", size = 4473714, upload-time = "2025-10-15T23:17:56.754Z" }, + { url = "https://files.pythonhosted.org/packages/14/e5/fc82d72a58d41c393697aa18c9abe5ae1214ff6f2a5c18ac470f92777895/cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac", size = 4296970, upload-time = "2025-10-15T23:17:58.588Z" }, + { url = "https://files.pythonhosted.org/packages/78/06/5663ed35438d0b09056973994f1aec467492b33bd31da36e468b01ec1097/cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04", size = 4940236, upload-time = "2025-10-15T23:18:00.897Z" }, + { url = "https://files.pythonhosted.org/packages/fc/59/873633f3f2dcd8a053b8dd1d38f783043b5fce589c0f6988bf55ef57e43e/cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506", size = 4472642, upload-time = "2025-10-15T23:18:02.749Z" }, + { url = "https://files.pythonhosted.org/packages/3d/39/8e71f3930e40f6877737d6f69248cf74d4e34b886a3967d32f919cc50d3b/cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963", size = 4423126, upload-time = "2025-10-15T23:18:04.85Z" }, + { url = "https://files.pythonhosted.org/packages/cd/c7/f65027c2810e14c3e7268353b1681932b87e5a48e65505d8cc17c99e36ae/cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4", size = 4686573, upload-time = "2025-10-15T23:18:06.908Z" }, + { url = "https://files.pythonhosted.org/packages/0a/6e/1c8331ddf91ca4730ab3086a0f1be19c65510a33b5a441cb334e7a2d2560/cryptography-46.0.3-cp38-abi3-win32.whl", hash = "sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df", size = 3036695, upload-time = "2025-10-15T23:18:08.672Z" }, + { url = "https://files.pythonhosted.org/packages/90/45/b0d691df20633eff80955a0fc7695ff9051ffce8b69741444bd9ed7bd0db/cryptography-46.0.3-cp38-abi3-win_amd64.whl", hash = "sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f", size = 3501720, upload-time = "2025-10-15T23:18:10.632Z" }, + { url = "https://files.pythonhosted.org/packages/e8/cb/2da4cc83f5edb9c3257d09e1e7ab7b23f049c7962cae8d842bbef0a9cec9/cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372", size = 2918740, upload-time = "2025-10-15T23:18:12.277Z" }, + { url = "https://files.pythonhosted.org/packages/06/8a/e60e46adab4362a682cf142c7dcb5bf79b782ab2199b0dcb81f55970807f/cryptography-46.0.3-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7ce938a99998ed3c8aa7e7272dca1a610401ede816d36d0693907d863b10d9ea", size = 3698132, upload-time = "2025-10-15T23:18:17.056Z" }, + { url = "https://files.pythonhosted.org/packages/da/38/f59940ec4ee91e93d3311f7532671a5cef5570eb04a144bf203b58552d11/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:191bb60a7be5e6f54e30ba16fdfae78ad3a342a0599eb4193ba88e3f3d6e185b", size = 4243992, upload-time = "2025-10-15T23:18:18.695Z" }, + { url = "https://files.pythonhosted.org/packages/b0/0c/35b3d92ddebfdfda76bb485738306545817253d0a3ded0bfe80ef8e67aa5/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c70cc23f12726be8f8bc72e41d5065d77e4515efae3690326764ea1b07845cfb", size = 4409944, upload-time = "2025-10-15T23:18:20.597Z" }, + { url = "https://files.pythonhosted.org/packages/99/55/181022996c4063fc0e7666a47049a1ca705abb9c8a13830f074edb347495/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:9394673a9f4de09e28b5356e7fff97d778f8abad85c9d5ac4a4b7e25a0de7717", size = 4242957, upload-time = "2025-10-15T23:18:22.18Z" }, + { url = "https://files.pythonhosted.org/packages/ba/af/72cd6ef29f9c5f731251acadaeb821559fe25f10852f44a63374c9ca08c1/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:94cd0549accc38d1494e1f8de71eca837d0509d0d44bf11d158524b0e12cebf9", size = 4409447, upload-time = "2025-10-15T23:18:24.209Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c3/e90f4a4feae6410f914f8ebac129b9ae7a8c92eb60a638012dde42030a9d/cryptography-46.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6b5063083824e5509fdba180721d55909ffacccc8adbec85268b48439423d78c", size = 3438528, upload-time = "2025-10-15T23:18:26.227Z" }, +] + [[package]] name = "csscompressor" version = "0.9.5" @@ -875,6 +1108,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, ] +[[package]] +name = "databricks-sdk" +version = "0.76.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-auth" }, + { name = "protobuf" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/70/82/5efcfdca8779c84b5c6f61cc110d0938c9818e422f55c36a68d96b98c61f/databricks_sdk-0.76.0.tar.gz", hash = "sha256:fcfce4561b090b3c8e9cac2101f549766d9fb3bece31bb5720571919fa37d210", size = 822376, upload-time = "2025-12-17T17:11:31.907Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/96/ee7742b94f996560c57d6fb8d2e10eab3c489e8a72187369ed0917baf8aa/databricks_sdk-0.76.0-py3-none-any.whl", hash = "sha256:6696dda22bc52c8f50a50d24e6ccd1c855f92c0f68f5afe4eb2e77d5b1b1a65f", size = 774688, upload-time = "2025-12-17T17:11:29.925Z" }, +] + [[package]] name = "debugpy" version = "1.8.17" @@ -932,6 +1179,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, ] +[[package]] +name = "docker" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pywin32", marker = "sys_platform == 'win32'" }, + { name = "requests" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/91/9b/4a2ea29aeba62471211598dac5d96825bb49348fa07e906ea930394a83ce/docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c", size = 117834, upload-time = "2024-05-23T11:13:57.216Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/26/57c6fb270950d476074c087527a558ccb6f4436657314bfb6cdf484114c4/docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0", size = 147774, upload-time = "2024-05-23T11:13:55.01Z" }, +] + [[package]] name = "executing" version = "2.2.1" @@ -941,6 +1202,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, ] +[[package]] +name = "fastapi" +version = "0.128.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/52/08/8c8508db6c7b9aae8f7175046af41baad690771c9bcde676419965e338c7/fastapi-0.128.0.tar.gz", hash = "sha256:1cc179e1cef10a6be60ffe429f79b829dce99d8de32d7acb7e6c8dfdf7f2645a", size = 365682, upload-time = "2025-12-27T15:21:13.714Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/05/5cbb59154b093548acd0f4c7c474a118eda06da25aa75c616b72d8fcd92a/fastapi-0.128.0-py3-none-any.whl", hash = "sha256:aebd93f9716ee3b4f4fcfe13ffb7cf308d99c9f3ab5622d8877441072561582d", size = 103094, upload-time = "2025-12-27T15:21:12.154Z" }, +] + [[package]] name = "fastjsonschema" version = "2.21.2" @@ -1000,6 +1276,36 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9f/56/13ab06b4f93ca7cac71078fbe37fcea175d3216f31f85c3168a6bbd0bb9a/flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e", size = 57922, upload-time = "2025-06-20T19:31:34.425Z" }, ] +[[package]] +name = "flask" +version = "3.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "blinker" }, + { name = "click" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { name = "markupsafe" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/6d/cfe3c0fcc5e477df242b98bfe186a4c34357b4847e87ecaef04507332dab/flask-3.1.2.tar.gz", hash = "sha256:bf656c15c80190ed628ad08cdfd3aaa35beb087855e2f494910aa3774cc4fd87", size = 720160, upload-time = "2025-08-19T21:03:21.205Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/f9/7f9263c5695f4bd0023734af91bedb2ff8209e8de6ead162f35d8dc762fd/flask-3.1.2-py3-none-any.whl", hash = "sha256:ca1d8112ec8a6158cc29ea4858963350011b5c846a414cdb7a954aa9e967d03c", size = 103308, upload-time = "2025-08-19T21:03:19.499Z" }, +] + +[[package]] +name = "flask-cors" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flask" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/70/74/0fc0fa68d62f21daef41017dafab19ef4b36551521260987eb3a5394c7ba/flask_cors-6.0.2.tar.gz", hash = "sha256:6e118f3698249ae33e429760db98ce032a8bf9913638d085ca0f4c5534ad2423", size = 13472, upload-time = "2025-12-12T20:31:42.861Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/af/72ad54402e599152de6d067324c46fe6a4f531c7c65baf7e96c63db55eaf/flask_cors-6.0.2-py3-none-any.whl", hash = "sha256:e57544d415dfd7da89a9564e1e3a9e515042df76e12130641ca6f3f2f03b699a", size = 13257, upload-time = "2025-12-12T20:31:41.3Z" }, +] + [[package]] name = "folium" version = "0.20.0" @@ -1150,6 +1456,88 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/01/61/d4b89fec821f72385526e1b9d9a3a0385dda4a72b206d28049e2c7cd39b8/gitpython-3.1.45-py3-none-any.whl", hash = "sha256:8908cb2e02fb3b93b7eb0f2827125cb699869470432cc885f019b8fd0fccff77", size = 208168, upload-time = "2025-07-24T03:45:52.517Z" }, ] +[[package]] +name = "google-auth" +version = "2.45.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "pyasn1-modules" }, + { name = "rsa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e5/00/3c794502a8b892c404b2dea5b3650eb21bfc7069612fbfd15c7f17c1cb0d/google_auth-2.45.0.tar.gz", hash = "sha256:90d3f41b6b72ea72dd9811e765699ee491ab24139f34ebf1ca2b9cc0c38708f3", size = 320708, upload-time = "2025-12-15T22:58:42.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/97/451d55e05487a5cd6279a01a7e34921858b16f7dc8aa38a2c684743cd2b3/google_auth-2.45.0-py2.py3-none-any.whl", hash = "sha256:82344e86dc00410ef5382d99be677c6043d72e502b625aa4f4afa0bdacca0f36", size = 233312, upload-time = "2025-12-15T22:58:40.777Z" }, +] + +[[package]] +name = "graphene" +version = "3.4.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "graphql-core" }, + { name = "graphql-relay" }, + { name = "python-dateutil" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cc/f6/bf62ff950c317ed03e77f3f6ddd7e34aaa98fe89d79ebd660c55343d8054/graphene-3.4.3.tar.gz", hash = "sha256:2a3786948ce75fe7e078443d37f609cbe5bb36ad8d6b828740ad3b95ed1a0aaa", size = 44739, upload-time = "2024-11-09T20:44:25.757Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/66/e0/61d8e98007182e6b2aca7cf65904721fb2e4bce0192272ab9cb6f69d8812/graphene-3.4.3-py2.py3-none-any.whl", hash = "sha256:820db6289754c181007a150db1f7fff544b94142b556d12e3ebc777a7bf36c71", size = 114894, upload-time = "2024-11-09T20:44:23.851Z" }, +] + +[[package]] +name = "graphql-core" +version = "3.2.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/9b/037a640a2983b09aed4a823f9cf1729e6d780b0671f854efa4727a7affbe/graphql_core-3.2.7.tar.gz", hash = "sha256:27b6904bdd3b43f2a0556dad5d579bdfdeab1f38e8e8788e555bdcb586a6f62c", size = 513484, upload-time = "2025-11-01T22:30:40.436Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/14/933037032608787fb92e365883ad6a741c235e0ff992865ec5d904a38f1e/graphql_core-3.2.7-py3-none-any.whl", hash = "sha256:17fc8f3ca4a42913d8e24d9ac9f08deddf0a0b2483076575757f6c412ead2ec0", size = 207262, upload-time = "2025-11-01T22:30:38.912Z" }, +] + +[[package]] +name = "graphql-relay" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "graphql-core" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/13/98fbf8d67552f102488ffc16c6f559ce71ea15f6294728d33928ab5ff14d/graphql-relay-3.2.0.tar.gz", hash = "sha256:1ff1c51298356e481a0be009ccdff249832ce53f30559c1338f22a0e0d17250c", size = 50027, upload-time = "2022-04-16T11:03:45.447Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/74/16/a4cf06adbc711bd364a73ce043b0b08d8fa5aae3df11b6ee4248bcdad2e0/graphql_relay-3.2.0-py3-none-any.whl", hash = "sha256:c9b22bd28b170ba1fe674c74384a8ff30a76c8e26f88ac3aa1584dd3179953e5", size = 16940, upload-time = "2022-04-16T11:03:43.895Z" }, +] + +[[package]] +name = "greenlet" +version = "3.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/e5/40dbda2736893e3e53d25838e0f19a2b417dfc122b9989c91918db30b5d3/greenlet-3.3.0.tar.gz", hash = "sha256:a82bb225a4e9e4d653dd2fb7b8b2d36e4fb25bc0165422a11e48b88e9e6f78fb", size = 190651, upload-time = "2025-12-04T14:49:44.05Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/cb/48e964c452ca2b92175a9b2dca037a553036cb053ba69e284650ce755f13/greenlet-3.3.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e29f3018580e8412d6aaf5641bb7745d38c85228dacf51a73bd4e26ddf2a6a8e", size = 274908, upload-time = "2025-12-04T14:23:26.435Z" }, + { url = "https://files.pythonhosted.org/packages/28/da/38d7bff4d0277b594ec557f479d65272a893f1f2a716cad91efeb8680953/greenlet-3.3.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a687205fb22794e838f947e2194c0566d3812966b41c78709554aa883183fb62", size = 577113, upload-time = "2025-12-04T14:50:05.493Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f2/89c5eb0faddc3ff014f1c04467d67dee0d1d334ab81fadbf3744847f8a8a/greenlet-3.3.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4243050a88ba61842186cb9e63c7dfa677ec146160b0efd73b855a3d9c7fcf32", size = 590338, upload-time = "2025-12-04T14:57:41.136Z" }, + { url = "https://files.pythonhosted.org/packages/80/d7/db0a5085035d05134f8c089643da2b44cc9b80647c39e93129c5ef170d8f/greenlet-3.3.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:670d0f94cd302d81796e37299bcd04b95d62403883b24225c6b5271466612f45", size = 601098, upload-time = "2025-12-04T15:07:11.898Z" }, + { url = "https://files.pythonhosted.org/packages/dc/a6/e959a127b630a58e23529972dbc868c107f9d583b5a9f878fb858c46bc1a/greenlet-3.3.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cb3a8ec3db4a3b0eb8a3c25436c2d49e3505821802074969db017b87bc6a948", size = 590206, upload-time = "2025-12-04T14:26:01.254Z" }, + { url = "https://files.pythonhosted.org/packages/48/60/29035719feb91798693023608447283b266b12efc576ed013dd9442364bb/greenlet-3.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2de5a0b09eab81fc6a382791b995b1ccf2b172a9fec934747a7a23d2ff291794", size = 1550668, upload-time = "2025-12-04T15:04:22.439Z" }, + { url = "https://files.pythonhosted.org/packages/0a/5f/783a23754b691bfa86bd72c3033aa107490deac9b2ef190837b860996c9f/greenlet-3.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4449a736606bd30f27f8e1ff4678ee193bc47f6ca810d705981cfffd6ce0d8c5", size = 1615483, upload-time = "2025-12-04T14:27:28.083Z" }, + { url = "https://files.pythonhosted.org/packages/1d/d5/c339b3b4bc8198b7caa4f2bd9fd685ac9f29795816d8db112da3d04175bb/greenlet-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:7652ee180d16d447a683c04e4c5f6441bae7ba7b17ffd9f6b3aff4605e9e6f71", size = 301164, upload-time = "2025-12-04T14:42:51.577Z" }, + { url = "https://files.pythonhosted.org/packages/f8/0a/a3871375c7b9727edaeeea994bfff7c63ff7804c9829c19309ba2e058807/greenlet-3.3.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:b01548f6e0b9e9784a2c99c5651e5dc89ffcbe870bc5fb2e5ef864e9cc6b5dcb", size = 276379, upload-time = "2025-12-04T14:23:30.498Z" }, + { url = "https://files.pythonhosted.org/packages/43/ab/7ebfe34dce8b87be0d11dae91acbf76f7b8246bf9d6b319c741f99fa59c6/greenlet-3.3.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:349345b770dc88f81506c6861d22a6ccd422207829d2c854ae2af8025af303e3", size = 597294, upload-time = "2025-12-04T14:50:06.847Z" }, + { url = "https://files.pythonhosted.org/packages/a4/39/f1c8da50024feecd0793dbd5e08f526809b8ab5609224a2da40aad3a7641/greenlet-3.3.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e8e18ed6995e9e2c0b4ed264d2cf89260ab3ac7e13555b8032b25a74c6d18655", size = 607742, upload-time = "2025-12-04T14:57:42.349Z" }, + { url = "https://files.pythonhosted.org/packages/77/cb/43692bcd5f7a0da6ec0ec6d58ee7cddb606d055ce94a62ac9b1aa481e969/greenlet-3.3.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c024b1e5696626890038e34f76140ed1daf858e37496d33f2af57f06189e70d7", size = 622297, upload-time = "2025-12-04T15:07:13.552Z" }, + { url = "https://files.pythonhosted.org/packages/75/b0/6bde0b1011a60782108c01de5913c588cf51a839174538d266de15e4bf4d/greenlet-3.3.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:047ab3df20ede6a57c35c14bf5200fcf04039d50f908270d3f9a7a82064f543b", size = 609885, upload-time = "2025-12-04T14:26:02.368Z" }, + { url = "https://files.pythonhosted.org/packages/49/0e/49b46ac39f931f59f987b7cd9f34bfec8ef81d2a1e6e00682f55be5de9f4/greenlet-3.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2d9ad37fc657b1102ec880e637cccf20191581f75c64087a549e66c57e1ceb53", size = 1567424, upload-time = "2025-12-04T15:04:23.757Z" }, + { url = "https://files.pythonhosted.org/packages/05/f5/49a9ac2dff7f10091935def9165c90236d8f175afb27cbed38fb1d61ab6b/greenlet-3.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83cd0e36932e0e7f36a64b732a6f60c2fc2df28c351bae79fbaf4f8092fe7614", size = 1636017, upload-time = "2025-12-04T14:27:29.688Z" }, + { url = "https://files.pythonhosted.org/packages/6c/79/3912a94cf27ec503e51ba493692d6db1e3cd8ac7ac52b0b47c8e33d7f4f9/greenlet-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a7a34b13d43a6b78abf828a6d0e87d3385680eaf830cd60d20d52f249faabf39", size = 301964, upload-time = "2025-12-04T14:36:58.316Z" }, + { url = "https://files.pythonhosted.org/packages/02/2f/28592176381b9ab2cafa12829ba7b472d177f3acc35d8fbcf3673d966fff/greenlet-3.3.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:a1e41a81c7e2825822f4e068c48cb2196002362619e2d70b148f20a831c00739", size = 275140, upload-time = "2025-12-04T14:23:01.282Z" }, + { url = "https://files.pythonhosted.org/packages/2c/80/fbe937bf81e9fca98c981fe499e59a3f45df2a04da0baa5c2be0dca0d329/greenlet-3.3.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f515a47d02da4d30caaa85b69474cec77b7929b2e936ff7fb853d42f4bf8808", size = 599219, upload-time = "2025-12-04T14:50:08.309Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ff/7c985128f0514271b8268476af89aee6866df5eec04ac17dcfbc676213df/greenlet-3.3.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7d2d9fd66bfadf230b385fdc90426fcd6eb64db54b40c495b72ac0feb5766c54", size = 610211, upload-time = "2025-12-04T14:57:43.968Z" }, + { url = "https://files.pythonhosted.org/packages/79/07/c47a82d881319ec18a4510bb30463ed6891f2ad2c1901ed5ec23d3de351f/greenlet-3.3.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30a6e28487a790417d036088b3bcb3f3ac7d8babaa7d0139edbaddebf3af9492", size = 624311, upload-time = "2025-12-04T15:07:14.697Z" }, + { url = "https://files.pythonhosted.org/packages/fd/8e/424b8c6e78bd9837d14ff7df01a9829fc883ba2ab4ea787d4f848435f23f/greenlet-3.3.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:087ea5e004437321508a8d6f20efc4cfec5e3c30118e1417ea96ed1d93950527", size = 612833, upload-time = "2025-12-04T14:26:03.669Z" }, + { url = "https://files.pythonhosted.org/packages/b5/ba/56699ff9b7c76ca12f1cdc27a886d0f81f2189c3455ff9f65246780f713d/greenlet-3.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ab97cf74045343f6c60a39913fa59710e4bd26a536ce7ab2397adf8b27e67c39", size = 1567256, upload-time = "2025-12-04T15:04:25.276Z" }, + { url = "https://files.pythonhosted.org/packages/1e/37/f31136132967982d698c71a281a8901daf1a8fbab935dce7c0cf15f942cc/greenlet-3.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5375d2e23184629112ca1ea89a53389dddbffcf417dad40125713d88eb5f96e8", size = 1636483, upload-time = "2025-12-04T14:27:30.804Z" }, + { url = "https://files.pythonhosted.org/packages/7e/71/ba21c3fb8c5dce83b8c01f458a42e99ffdb1963aeec08fff5a18588d8fd7/greenlet-3.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:9ee1942ea19550094033c35d25d20726e4f1c40d59545815e1128ac58d416d38", size = 301833, upload-time = "2025-12-04T14:32:23.929Z" }, +] + [[package]] name = "griffe" version = "1.14.0" @@ -1162,6 +1550,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-py3-none-any.whl", hash = "sha256:0e9d52832cccf0f7188cfe585ba962d2674b241c01916d780925df34873bceb0", size = 144439, upload-time = "2025-09-05T15:02:27.511Z" }, ] +[[package]] +name = "gunicorn" +version = "23.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/34/72/9614c465dc206155d93eff0ca20d42e1e35afc533971379482de953521a4/gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec", size = 375031, upload-time = "2024-08-10T20:25:27.378Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", size = 85029, upload-time = "2024-08-10T20:25:24.996Z" }, +] + [[package]] name = "h11" version = "0.16.0" @@ -1216,6 +1616,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, ] +[[package]] +name = "huey" +version = "2.5.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/87/07796060836baf60727df5edae1579adcc8140f42fb7dea3c34ca7ce7fd3/huey-2.5.5.tar.gz", hash = "sha256:a39010628a9a1a9e91462f9bf33dc243b006a9f21193026ea47ae18949a12581", size = 895915, upload-time = "2025-12-05T02:26:54.226Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/c2/0543039071259cfdab525757022de8dad6d22c15a0e7352f1a50a1444a13/huey-2.5.5-py3-none-any.whl", hash = "sha256:82ac73343248c5d7acec04814f952c61f7793e11fd99d26ed9030137d32f912c", size = 76889, upload-time = "2025-12-05T02:26:52.668Z" }, +] + [[package]] name = "humanize" version = "4.14.0" @@ -1447,6 +1856,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7f/ed/e3705d6d02b4f7aea715a353c8ce193efd0b5db13e204df895d38734c244/isort-7.0.0-py3-none-any.whl", hash = "sha256:1bcabac8bc3c36c7fb7b98a76c8abb18e0f841a3ba81decac7691008592499c1", size = 94672, upload-time = "2025-10-11T13:30:57.665Z" }, ] +[[package]] +name = "itsdangerous" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, +] + [[package]] name = "jedi" version = "0.19.2" @@ -1471,6 +1889,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] +[[package]] +name = "jmespath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/2a/e867e8531cf3e36b41201936b7fa7ba7b5702dbef42922193f05c8976cd6/jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe", size = 25843, upload-time = "2022-06-17T18:00:12.224Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256, upload-time = "2022-06-17T18:00:10.251Z" }, +] + [[package]] name = "joblib" version = "1.5.2" @@ -1926,6 +2353,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6c/77/d7f491cbc05303ac6801651aabeb262d43f319288c1ea96c66b1d2692ff3/lxml-6.0.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:27220da5be049e936c3aca06f174e8827ca6445a4353a1995584311487fc4e3e", size = 3518768, upload-time = "2025-09-22T04:04:57.097Z" }, ] +[[package]] +name = "mako" +version = "1.3.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/38/bd5b78a920a64d708fe6bc8e0a2c075e1389d53bef8413725c63ba041535/mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28", size = 392474, upload-time = "2025-04-10T12:44:31.16Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/fb/99f81ac72ae23375f22b7afdb7642aba97c00a713c217124420147681a2f/mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59", size = 78509, upload-time = "2025-04-10T12:50:53.297Z" }, +] + [[package]] name = "markdown" version = "3.9" @@ -2472,6 +2911,84 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d5/8f/ce008599d9adebf33ed144e7736914385e8537f5fc686fdb7cceb8c22431/mkdocstrings_python-1.18.2-py3-none-any.whl", hash = "sha256:944fe6deb8f08f33fa936d538233c4036e9f53e840994f6146e8e94eb71b600d", size = 138215, upload-time = "2025-08-28T16:11:18.176Z" }, ] +[[package]] +name = "mlflow" +version = "3.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alembic" }, + { name = "cryptography" }, + { name = "docker" }, + { name = "flask" }, + { name = "flask-cors" }, + { name = "graphene" }, + { name = "gunicorn", marker = "sys_platform != 'win32'" }, + { name = "huey" }, + { name = "matplotlib" }, + { name = "mlflow-skinny" }, + { name = "mlflow-tracing" }, + { name = "numpy" }, + { name = "pandas" }, + { name = "pyarrow" }, + { name = "scikit-learn" }, + { name = "scipy" }, + { name = "sqlalchemy" }, + { name = "waitress", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/f8/10ccb111ed53732dfceae0369073023f96acd6b00f92fb3c24473938702d/mlflow-3.8.1.tar.gz", hash = "sha256:0823377bedff4d530b0d560bf394daf9f7e9fbba53453add04eadad34de962cc", size = 8550037, upload-time = "2025-12-26T16:46:49.199Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/d5/a20b87c6cd99395fee04d6034686512305530c71ceaabe3a151eeaa25ed7/mlflow-3.8.1-py3-none-any.whl", hash = "sha256:42f26b52438fdb615588e150407c6516d0f64d417436dfc75599c525a464f210", size = 9062281, upload-time = "2025-12-26T16:46:46.528Z" }, +] + +[[package]] +name = "mlflow-skinny" +version = "3.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "click" }, + { name = "cloudpickle" }, + { name = "databricks-sdk" }, + { name = "fastapi" }, + { name = "gitpython" }, + { name = "importlib-metadata" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-proto" }, + { name = "opentelemetry-sdk" }, + { name = "packaging" }, + { name = "protobuf" }, + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "sqlparse" }, + { name = "typing-extensions" }, + { name = "uvicorn" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7e/00/18486d9072739e63471c1e441e78cdb6a10c641312d98f6699715406451e/mlflow_skinny-3.8.1.tar.gz", hash = "sha256:0c0aade08187030a4653e267bcd63de2f12cbfebf4c6737832cba45d6fb3594d", size = 2082226, upload-time = "2025-12-26T16:30:11.171Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/24/42e52320636fcbabeaf50704f9269a328acc995e1b8a44df6fea33130a0a/mlflow_skinny-3.8.1-py3-none-any.whl", hash = "sha256:3a6ee27f5ac1e67c1d565fa0e12c070b27129b03e669dcaf88ff841176429142", size = 2506002, upload-time = "2025-12-26T16:30:09.357Z" }, +] + +[[package]] +name = "mlflow-tracing" +version = "3.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "databricks-sdk" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-proto" }, + { name = "opentelemetry-sdk" }, + { name = "packaging" }, + { name = "protobuf" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/52/e5/9cdca5a91afd71e15943f3dbe00e788bd36479637e9b2e0625224027ff01/mlflow_tracing-3.8.1.tar.gz", hash = "sha256:c032ba715994a4580323f3045fa2700a6323033d87e564bbcbda37e6ab993071", size = 1130700, upload-time = "2025-12-26T16:31:39.314Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/e3/ddbe3e2d1219fa9235559ab88ebf98e1e4f48c62672f0dfb8f1eb07276dc/mlflow_tracing-3.8.1-py3-none-any.whl", hash = "sha256:12d9b5b7177b4152979d003e0d967b280c4252758639aebdfd672734283b17bf", size = 1359007, upload-time = "2025-12-26T16:31:37.514Z" }, +] + [[package]] name = "mypy" version = "1.18.2" @@ -2724,6 +3241,58 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e3/94/1843518e420fa3ed6919835845df698c7e27e183cb997394e4a670973a65/omegaconf-2.3.0-py3-none-any.whl", hash = "sha256:7b4df175cdb08ba400f45cae3bdcae7ba8365db4d165fc65fd04b050ab63b46b", size = 79500, upload-time = "2022-12-08T20:59:19.686Z" }, ] +[[package]] +name = "opentelemetry-api" +version = "1.39.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/97/b9/3161be15bb8e3ad01be8be5a968a9237c3027c5be504362ff800fca3e442/opentelemetry_api-1.39.1.tar.gz", hash = "sha256:fbde8c80e1b937a2c61f20347e91c0c18a1940cecf012d62e65a7caf08967c9c", size = 65767, upload-time = "2025-12-11T13:32:39.182Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/df/d3f1ddf4bb4cb50ed9b1139cc7b1c54c34a1e7ce8fd1b9a37c0d1551a6bd/opentelemetry_api-1.39.1-py3-none-any.whl", hash = "sha256:2edd8463432a7f8443edce90972169b195e7d6a05500cd29e6d13898187c9950", size = 66356, upload-time = "2025-12-11T13:32:17.304Z" }, +] + +[[package]] +name = "opentelemetry-proto" +version = "1.39.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/1d/f25d76d8260c156c40c97c9ed4511ec0f9ce353f8108ca6e7561f82a06b2/opentelemetry_proto-1.39.1.tar.gz", hash = "sha256:6c8e05144fc0d3ed4d22c2289c6b126e03bcd0e6a7da0f16cedd2e1c2772e2c8", size = 46152, upload-time = "2025-12-11T13:32:48.681Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/95/b40c96a7b5203005a0b03d8ce8cd212ff23f1793d5ba289c87a097571b18/opentelemetry_proto-1.39.1-py3-none-any.whl", hash = "sha256:22cdc78efd3b3765d09e68bfbd010d4fc254c9818afd0b6b423387d9dee46007", size = 72535, upload-time = "2025-12-11T13:32:33.866Z" }, +] + +[[package]] +name = "opentelemetry-sdk" +version = "1.39.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/fb/c76080c9ba07e1e8235d24cdcc4d125ef7aa3edf23eb4e497c2e50889adc/opentelemetry_sdk-1.39.1.tar.gz", hash = "sha256:cf4d4563caf7bff906c9f7967e2be22d0d6b349b908be0d90fb21c8e9c995cc6", size = 171460, upload-time = "2025-12-11T13:32:49.369Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/98/e91cf858f203d86f4eccdf763dcf01cf03f1dae80c3750f7e635bfa206b6/opentelemetry_sdk-1.39.1-py3-none-any.whl", hash = "sha256:4d5482c478513ecb0a5d938dcc61394e647066e0cc2676bee9f3af3f3f45f01c", size = 132565, upload-time = "2025-12-11T13:32:35.069Z" }, +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.60b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/91/df/553f93ed38bf22f4b999d9be9c185adb558982214f33eae539d3b5cd0858/opentelemetry_semantic_conventions-0.60b1.tar.gz", hash = "sha256:87c228b5a0669b748c76d76df6c364c369c28f1c465e50f661e39737e84bc953", size = 137935, upload-time = "2025-12-11T13:32:50.487Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/5e/5958555e09635d09b75de3c4f8b9cae7335ca545d77392ffe7331534c402/opentelemetry_semantic_conventions-0.60b1-py3-none-any.whl", hash = "sha256:9fa8c8b0c110da289809292b0591220d3a7b53c1526a23021e977d68597893fb", size = 219982, upload-time = "2025-12-11T13:32:36.955Z" }, +] + [[package]] name = "overrides" version = "7.7.0" @@ -3021,6 +3590,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, ] +[[package]] +name = "protobuf" +version = "6.33.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/34/44/e49ecff446afeec9d1a66d6bbf9adc21e3c7cea7803a920ca3773379d4f6/protobuf-6.33.2.tar.gz", hash = "sha256:56dc370c91fbb8ac85bc13582c9e373569668a290aa2e66a590c2a0d35ddb9e4", size = 444296, upload-time = "2025-12-06T00:17:53.311Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/91/1e3a34881a88697a7354ffd177e8746e97a722e5e8db101544b47e84afb1/protobuf-6.33.2-cp310-abi3-win32.whl", hash = "sha256:87eb388bd2d0f78febd8f4c8779c79247b26a5befad525008e49a6955787ff3d", size = 425603, upload-time = "2025-12-06T00:17:41.114Z" }, + { url = "https://files.pythonhosted.org/packages/64/20/4d50191997e917ae13ad0a235c8b42d8c1ab9c3e6fd455ca16d416944355/protobuf-6.33.2-cp310-abi3-win_amd64.whl", hash = "sha256:fc2a0e8b05b180e5fc0dd1559fe8ebdae21a27e81ac77728fb6c42b12c7419b4", size = 436930, upload-time = "2025-12-06T00:17:43.278Z" }, + { url = "https://files.pythonhosted.org/packages/b2/ca/7e485da88ba45c920fb3f50ae78de29ab925d9e54ef0de678306abfbb497/protobuf-6.33.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d9b19771ca75935b3a4422957bc518b0cecb978b31d1dd12037b088f6bcc0e43", size = 427621, upload-time = "2025-12-06T00:17:44.445Z" }, + { url = "https://files.pythonhosted.org/packages/7d/4f/f743761e41d3b2b2566748eb76bbff2b43e14d5fcab694f494a16458b05f/protobuf-6.33.2-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:b5d3b5625192214066d99b2b605f5783483575656784de223f00a8d00754fc0e", size = 324460, upload-time = "2025-12-06T00:17:45.678Z" }, + { url = "https://files.pythonhosted.org/packages/b1/fa/26468d00a92824020f6f2090d827078c09c9c587e34cbfd2d0c7911221f8/protobuf-6.33.2-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8cd7640aee0b7828b6d03ae518b5b4806fdfc1afe8de82f79c3454f8aef29872", size = 339168, upload-time = "2025-12-06T00:17:46.813Z" }, + { url = "https://files.pythonhosted.org/packages/56/13/333b8f421738f149d4fe5e49553bc2a2ab75235486259f689b4b91f96cec/protobuf-6.33.2-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:1f8017c48c07ec5859106533b682260ba3d7c5567b1ca1f24297ce03384d1b4f", size = 323270, upload-time = "2025-12-06T00:17:48.253Z" }, + { url = "https://files.pythonhosted.org/packages/0e/15/4f02896cc3df04fc465010a4c6a0cd89810f54617a32a70ef531ed75d61c/protobuf-6.33.2-py3-none-any.whl", hash = "sha256:7636aad9bb01768870266de5dc009de2d1b936771b38a793f73cbbf279c91c5c", size = 170501, upload-time = "2025-12-06T00:17:52.211Z" }, +] + [[package]] name = "psutil" version = "7.1.1" @@ -3064,6 +3648,63 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378", size = 98708, upload-time = "2021-11-04T17:17:00.152Z" }, ] +[[package]] +name = "pyarrow" +version = "22.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/53/04a7fdc63e6056116c9ddc8b43bc28c12cdd181b85cbeadb79278475f3ae/pyarrow-22.0.0.tar.gz", hash = "sha256:3d600dc583260d845c7d8a6db540339dd883081925da2bd1c5cb808f720b3cd9", size = 1151151, upload-time = "2025-10-24T12:30:00.762Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/b7/18f611a8cdc43417f9394a3ccd3eace2f32183c08b9eddc3d17681819f37/pyarrow-22.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:3e294c5eadfb93d78b0763e859a0c16d4051fc1c5231ae8956d61cb0b5666f5a", size = 34272022, upload-time = "2025-10-24T10:04:28.973Z" }, + { url = "https://files.pythonhosted.org/packages/26/5c/f259e2526c67eb4b9e511741b19870a02363a47a35edbebc55c3178db22d/pyarrow-22.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:69763ab2445f632d90b504a815a2a033f74332997052b721002298ed6de40f2e", size = 35995834, upload-time = "2025-10-24T10:04:35.467Z" }, + { url = "https://files.pythonhosted.org/packages/50/8d/281f0f9b9376d4b7f146913b26fac0aa2829cd1ee7e997f53a27411bbb92/pyarrow-22.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:b41f37cabfe2463232684de44bad753d6be08a7a072f6a83447eeaf0e4d2a215", size = 45030348, upload-time = "2025-10-24T10:04:43.366Z" }, + { url = "https://files.pythonhosted.org/packages/f5/e5/53c0a1c428f0976bf22f513d79c73000926cb00b9c138d8e02daf2102e18/pyarrow-22.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:35ad0f0378c9359b3f297299c3309778bb03b8612f987399a0333a560b43862d", size = 47699480, upload-time = "2025-10-24T10:04:51.486Z" }, + { url = "https://files.pythonhosted.org/packages/95/e1/9dbe4c465c3365959d183e6345d0a8d1dc5b02ca3f8db4760b3bc834cf25/pyarrow-22.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8382ad21458075c2e66a82a29d650f963ce51c7708c7c0ff313a8c206c4fd5e8", size = 48011148, upload-time = "2025-10-24T10:04:59.585Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b4/7caf5d21930061444c3cf4fa7535c82faf5263e22ce43af7c2759ceb5b8b/pyarrow-22.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1a812a5b727bc09c3d7ea072c4eebf657c2f7066155506ba31ebf4792f88f016", size = 50276964, upload-time = "2025-10-24T10:05:08.175Z" }, + { url = "https://files.pythonhosted.org/packages/ae/f3/cec89bd99fa3abf826f14d4e53d3d11340ce6f6af4d14bdcd54cd83b6576/pyarrow-22.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:ec5d40dd494882704fb876c16fa7261a69791e784ae34e6b5992e977bd2e238c", size = 28106517, upload-time = "2025-10-24T10:05:14.314Z" }, + { url = "https://files.pythonhosted.org/packages/af/63/ba23862d69652f85b615ca14ad14f3bcfc5bf1b99ef3f0cd04ff93fdad5a/pyarrow-22.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:bea79263d55c24a32b0d79c00a1c58bb2ee5f0757ed95656b01c0fb310c5af3d", size = 34211578, upload-time = "2025-10-24T10:05:21.583Z" }, + { url = "https://files.pythonhosted.org/packages/b1/d0/f9ad86fe809efd2bcc8be32032fa72e8b0d112b01ae56a053006376c5930/pyarrow-22.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:12fe549c9b10ac98c91cf791d2945e878875d95508e1a5d14091a7aaa66d9cf8", size = 35989906, upload-time = "2025-10-24T10:05:29.485Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a8/f910afcb14630e64d673f15904ec27dd31f1e009b77033c365c84e8c1e1d/pyarrow-22.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:334f900ff08ce0423407af97e6c26ad5d4e3b0763645559ece6fbf3747d6a8f5", size = 45021677, upload-time = "2025-10-24T10:05:38.274Z" }, + { url = "https://files.pythonhosted.org/packages/13/95/aec81f781c75cd10554dc17a25849c720d54feafb6f7847690478dcf5ef8/pyarrow-22.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c6c791b09c57ed76a18b03f2631753a4960eefbbca80f846da8baefc6491fcfe", size = 47726315, upload-time = "2025-10-24T10:05:47.314Z" }, + { url = "https://files.pythonhosted.org/packages/bb/d4/74ac9f7a54cfde12ee42734ea25d5a3c9a45db78f9def949307a92720d37/pyarrow-22.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c3200cb41cdbc65156e5f8c908d739b0dfed57e890329413da2748d1a2cd1a4e", size = 47990906, upload-time = "2025-10-24T10:05:58.254Z" }, + { url = "https://files.pythonhosted.org/packages/2e/71/fedf2499bf7a95062eafc989ace56572f3343432570e1c54e6599d5b88da/pyarrow-22.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ac93252226cf288753d8b46280f4edf3433bf9508b6977f8dd8526b521a1bbb9", size = 50306783, upload-time = "2025-10-24T10:06:08.08Z" }, + { url = "https://files.pythonhosted.org/packages/68/ed/b202abd5a5b78f519722f3d29063dda03c114711093c1995a33b8e2e0f4b/pyarrow-22.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:44729980b6c50a5f2bfcc2668d36c569ce17f8b17bccaf470c4313dcbbf13c9d", size = 27972883, upload-time = "2025-10-24T10:06:14.204Z" }, + { url = "https://files.pythonhosted.org/packages/a6/d6/d0fac16a2963002fc22c8fa75180a838737203d558f0ed3b564c4a54eef5/pyarrow-22.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:e6e95176209257803a8b3d0394f21604e796dadb643d2f7ca21b66c9c0b30c9a", size = 34204629, upload-time = "2025-10-24T10:06:20.274Z" }, + { url = "https://files.pythonhosted.org/packages/c6/9c/1d6357347fbae062ad3f17082f9ebc29cc733321e892c0d2085f42a2212b/pyarrow-22.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:001ea83a58024818826a9e3f89bf9310a114f7e26dfe404a4c32686f97bd7901", size = 35985783, upload-time = "2025-10-24T10:06:27.301Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c0/782344c2ce58afbea010150df07e3a2f5fdad299cd631697ae7bd3bac6e3/pyarrow-22.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:ce20fe000754f477c8a9125543f1936ea5b8867c5406757c224d745ed033e691", size = 45020999, upload-time = "2025-10-24T10:06:35.387Z" }, + { url = "https://files.pythonhosted.org/packages/1b/8b/5362443737a5307a7b67c1017c42cd104213189b4970bf607e05faf9c525/pyarrow-22.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e0a15757fccb38c410947df156f9749ae4a3c89b2393741a50521f39a8cf202a", size = 47724601, upload-time = "2025-10-24T10:06:43.551Z" }, + { url = "https://files.pythonhosted.org/packages/69/4d/76e567a4fc2e190ee6072967cb4672b7d9249ac59ae65af2d7e3047afa3b/pyarrow-22.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cedb9dd9358e4ea1d9bce3665ce0797f6adf97ff142c8e25b46ba9cdd508e9b6", size = 48001050, upload-time = "2025-10-24T10:06:52.284Z" }, + { url = "https://files.pythonhosted.org/packages/01/5e/5653f0535d2a1aef8223cee9d92944cb6bccfee5cf1cd3f462d7cb022790/pyarrow-22.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:252be4a05f9d9185bb8c18e83764ebcfea7185076c07a7a662253af3a8c07941", size = 50307877, upload-time = "2025-10-24T10:07:02.405Z" }, + { url = "https://files.pythonhosted.org/packages/2d/f8/1d0bd75bf9328a3b826e24a16e5517cd7f9fbf8d34a3184a4566ef5a7f29/pyarrow-22.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:a4893d31e5ef780b6edcaf63122df0f8d321088bb0dee4c8c06eccb1ca28d145", size = 27977099, upload-time = "2025-10-24T10:08:07.259Z" }, + { url = "https://files.pythonhosted.org/packages/90/81/db56870c997805bf2b0f6eeeb2d68458bf4654652dccdcf1bf7a42d80903/pyarrow-22.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:f7fe3dbe871294ba70d789be16b6e7e52b418311e166e0e3cba9522f0f437fb1", size = 34336685, upload-time = "2025-10-24T10:07:11.47Z" }, + { url = "https://files.pythonhosted.org/packages/1c/98/0727947f199aba8a120f47dfc229eeb05df15bcd7a6f1b669e9f882afc58/pyarrow-22.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:ba95112d15fd4f1105fb2402c4eab9068f0554435e9b7085924bcfaac2cc306f", size = 36032158, upload-time = "2025-10-24T10:07:18.626Z" }, + { url = "https://files.pythonhosted.org/packages/96/b4/9babdef9c01720a0785945c7cf550e4acd0ebcd7bdd2e6f0aa7981fa85e2/pyarrow-22.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:c064e28361c05d72eed8e744c9605cbd6d2bb7481a511c74071fd9b24bc65d7d", size = 44892060, upload-time = "2025-10-24T10:07:26.002Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ca/2f8804edd6279f78a37062d813de3f16f29183874447ef6d1aadbb4efa0f/pyarrow-22.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:6f9762274496c244d951c819348afbcf212714902742225f649cf02823a6a10f", size = 47504395, upload-time = "2025-10-24T10:07:34.09Z" }, + { url = "https://files.pythonhosted.org/packages/b9/f0/77aa5198fd3943682b2e4faaf179a674f0edea0d55d326d83cb2277d9363/pyarrow-22.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a9d9ffdc2ab696f6b15b4d1f7cec6658e1d788124418cb30030afbae31c64746", size = 48066216, upload-time = "2025-10-24T10:07:43.528Z" }, + { url = "https://files.pythonhosted.org/packages/79/87/a1937b6e78b2aff18b706d738c9e46ade5bfcf11b294e39c87706a0089ac/pyarrow-22.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ec1a15968a9d80da01e1d30349b2b0d7cc91e96588ee324ce1b5228175043e95", size = 50288552, upload-time = "2025-10-24T10:07:53.519Z" }, + { url = "https://files.pythonhosted.org/packages/60/ae/b5a5811e11f25788ccfdaa8f26b6791c9807119dffcf80514505527c384c/pyarrow-22.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:bba208d9c7decf9961998edf5c65e3ea4355d5818dd6cd0f6809bec1afb951cc", size = 28262504, upload-time = "2025-10-24T10:08:00.932Z" }, +] + +[[package]] +name = "pyasn1" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322, upload-time = "2024-09-10T22:41:42.55Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135, upload-time = "2024-09-11T16:00:36.122Z" }, +] + +[[package]] +name = "pyasn1-modules" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, +] + [[package]] name = "pybtex" version = "0.25.1" @@ -3095,6 +3736,90 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, ] +[[package]] +name = "pydantic" +version = "2.12.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.41.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +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/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" }, + { url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" }, + { url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" }, + { url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" }, + { url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" }, + { url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761, upload-time = "2025-11-04T13:39:44.553Z" }, + { url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" }, + { url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" }, + { url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549, upload-time = "2025-11-04T13:39:51.842Z" }, + { url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305, upload-time = "2025-11-04T13:39:53.485Z" }, + { url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902, upload-time = "2025-11-04T13:39:56.488Z" }, + { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, + { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, + { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, + { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, + { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, + { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, + { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, + { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, + { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, + { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, + { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, + { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, + { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, + { 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/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" }, + { url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" }, + { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905, upload-time = "2025-11-04T13:42:47.156Z" }, + { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, + { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, + { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, + { url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" }, + { url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" }, + { url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762, upload-time = "2025-11-04T13:43:34.744Z" }, + { url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" }, + { url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" }, + { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" }, +] + [[package]] name = "pydocstyle" version = "6.3.0" @@ -3299,6 +4024,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] +[[package]] +name = "python-dotenv" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" }, +] + [[package]] name = "python-json-logger" version = "4.0.0" @@ -3342,6 +4076,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, ] +[[package]] +name = "pywin32" +version = "311" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/af/449a6a91e5d6db51420875c54f6aff7c97a86a3b13a0b4f1a5c13b988de3/pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151", size = 8697031, upload-time = "2025-07-14T20:13:13.266Z" }, + { url = "https://files.pythonhosted.org/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503", size = 9508308, upload-time = "2025-07-14T20:13:15.147Z" }, + { url = "https://files.pythonhosted.org/packages/44/7b/9c2ab54f74a138c491aba1b1cd0795ba61f144c711daea84a88b63dc0f6c/pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2", size = 8703930, upload-time = "2025-07-14T20:13:16.945Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, + { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" }, + { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" }, + { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" }, + { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" }, +] + [[package]] name = "pywinpty" version = "3.0.2" @@ -3537,6 +4287,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1c/4c/cc276ce57e572c102d9542d383b2cfd551276581dc60004cb94fe8774c11/responses-0.25.8-py3-none-any.whl", hash = "sha256:0c710af92def29c8352ceadff0c3fe340ace27cf5af1bbe46fb71275bcd2831c", size = 34769, upload-time = "2025-08-08T19:01:45.018Z" }, ] +[[package]] +name = "restrictedpython" +version = "8.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/1c/aec08bcb4ab14a1521579fbe21ceff2a634bb1f737f11cf7f9c8bb96e680/restrictedpython-8.1.tar.gz", hash = "sha256:4a69304aceacf6bee74bdf153c728221d4e3109b39acbfe00b3494927080d898", size = 838331, upload-time = "2025-10-19T14:11:32.531Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/c0/3848f4006f7e164ee20833ca984067e4b3fc99fe7f1dfa88b4927e681299/restrictedpython-8.1-py3-none-any.whl", hash = "sha256:4769449c6cdb10f2071649ba386902befff0eff2a8fd6217989fa7b16aeae926", size = 27651, upload-time = "2025-10-19T14:11:30.201Z" }, +] + [[package]] name = "rfc3339-validator" version = "0.1.4" @@ -3692,6 +4451,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ce/08/4349bdd5c64d9d193c360aa9db89adeee6f6682ab8825dca0a3f535f434f/rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:dc23e6820e3b40847e2f4a7726462ba0cf53089512abe9ee16318c366494c17a", size = 556523, upload-time = "2025-08-27T12:16:12.188Z" }, ] +[[package]] +name = "rsa" +version = "4.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" }, +] + [[package]] name = "rtree" version = "1.4.1" @@ -3784,6 +4555,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b8/81/4b6387be7014858d924b843530e1b2a8e531846807516e9bea2ee0936bf7/ruff-0.14.1-py3-none-win_arm64.whl", hash = "sha256:e3b443c4c9f16ae850906b8d0a707b2a4c16f8d2f0a7fe65c475c5886665ce44", size = 12436636, upload-time = "2025-10-16T18:05:38.995Z" }, ] +[[package]] +name = "s3transfer" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/05/04/74127fc843314818edfa81b5540e26dd537353b123a4edc563109d8f17dd/s3transfer-0.16.0.tar.gz", hash = "sha256:8e990f13268025792229cd52fa10cb7163744bf56e719e0b9cb925ab79abf920", size = 153827, upload-time = "2025-12-01T02:30:59.114Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/51/727abb13f44c1fcf6d145979e1535a35794db0f6e450a0cb46aa24732fe2/s3transfer-0.16.0-py3-none-any.whl", hash = "sha256:18e25d66fed509e3868dc1572b3f427ff947dd2c56f844a5bf09481ad3f3b2fe", size = 86830, upload-time = "2025-12-01T02:30:57.729Z" }, +] + [[package]] name = "scikit-learn" version = "1.7.2" @@ -4124,6 +4907,49 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/72/e9/5785124d28062b3a27aeaae9eef03b77fbec324883dd6d2c4ac5843b56c3/sourcery-1.39.0-py2.py3-none-win_amd64.whl", hash = "sha256:d269f7c174661494e1771dc393739363c959726c88c25294dc605aba1b2c9e19", size = 101307483, upload-time = "2025-10-20T12:51:21.79Z" }, ] +[[package]] +name = "sqlalchemy" +version = "2.0.45" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/be/f9/5e4491e5ccf42f5d9cfc663741d261b3e6e1683ae7812114e7636409fcc6/sqlalchemy-2.0.45.tar.gz", hash = "sha256:1632a4bda8d2d25703fdad6363058d882541bdaaee0e5e3ddfa0cd3229efce88", size = 9869912, upload-time = "2025-12-09T21:05:16.737Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/1c/769552a9d840065137272ebe86ffbb0bc92b0f1e0a68ee5266a225f8cd7b/sqlalchemy-2.0.45-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2e90a344c644a4fa871eb01809c32096487928bd2038bf10f3e4515cb688cc56", size = 2153860, upload-time = "2025-12-10T20:03:23.843Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f8/9be54ff620e5b796ca7b44670ef58bc678095d51b0e89d6e3102ea468216/sqlalchemy-2.0.45-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8c8b41b97fba5f62349aa285654230296829672fc9939cd7f35aab246d1c08b", size = 3309379, upload-time = "2025-12-09T22:06:07.461Z" }, + { url = "https://files.pythonhosted.org/packages/f6/2b/60ce3ee7a5ae172bfcd419ce23259bb874d2cddd44f67c5df3760a1e22f9/sqlalchemy-2.0.45-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:12c694ed6468333a090d2f60950e4250b928f457e4962389553d6ba5fe9951ac", size = 3309948, upload-time = "2025-12-09T22:09:57.643Z" }, + { url = "https://files.pythonhosted.org/packages/a3/42/bac8d393f5db550e4e466d03d16daaafd2bad1f74e48c12673fb499a7fc1/sqlalchemy-2.0.45-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f7d27a1d977a1cfef38a0e2e1ca86f09c4212666ce34e6ae542f3ed0a33bc606", size = 3261239, upload-time = "2025-12-09T22:06:08.879Z" }, + { url = "https://files.pythonhosted.org/packages/6f/12/43dc70a0528c59842b04ea1c1ed176f072a9b383190eb015384dd102fb19/sqlalchemy-2.0.45-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d62e47f5d8a50099b17e2bfc1b0c7d7ecd8ba6b46b1507b58cc4f05eefc3bb1c", size = 3284065, upload-time = "2025-12-09T22:09:59.454Z" }, + { url = "https://files.pythonhosted.org/packages/cf/9c/563049cf761d9a2ec7bc489f7879e9d94e7b590496bea5bbee9ed7b4cc32/sqlalchemy-2.0.45-cp311-cp311-win32.whl", hash = "sha256:3c5f76216e7b85770d5bb5130ddd11ee89f4d52b11783674a662c7dd57018177", size = 2113480, upload-time = "2025-12-09T21:29:57.03Z" }, + { url = "https://files.pythonhosted.org/packages/bc/fa/09d0a11fe9f15c7fa5c7f0dd26be3d235b0c0cbf2f9544f43bc42efc8a24/sqlalchemy-2.0.45-cp311-cp311-win_amd64.whl", hash = "sha256:a15b98adb7f277316f2c276c090259129ee4afca783495e212048daf846654b2", size = 2138407, upload-time = "2025-12-09T21:29:58.556Z" }, + { url = "https://files.pythonhosted.org/packages/2d/c7/1900b56ce19bff1c26f39a4ce427faec7716c81ac792bfac8b6a9f3dca93/sqlalchemy-2.0.45-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3ee2aac15169fb0d45822983631466d60b762085bc4535cd39e66bea362df5f", size = 3333760, upload-time = "2025-12-09T22:11:02.66Z" }, + { url = "https://files.pythonhosted.org/packages/0a/93/3be94d96bb442d0d9a60e55a6bb6e0958dd3457751c6f8502e56ef95fed0/sqlalchemy-2.0.45-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba547ac0b361ab4f1608afbc8432db669bd0819b3e12e29fb5fa9529a8bba81d", size = 3348268, upload-time = "2025-12-09T22:13:49.054Z" }, + { url = "https://files.pythonhosted.org/packages/48/4b/f88ded696e61513595e4a9778f9d3f2bf7332cce4eb0c7cedaabddd6687b/sqlalchemy-2.0.45-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:215f0528b914e5c75ef2559f69dca86878a3beeb0c1be7279d77f18e8d180ed4", size = 3278144, upload-time = "2025-12-09T22:11:04.14Z" }, + { url = "https://files.pythonhosted.org/packages/ed/6a/310ecb5657221f3e1bd5288ed83aa554923fb5da48d760a9f7622afeb065/sqlalchemy-2.0.45-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:107029bf4f43d076d4011f1afb74f7c3e2ea029ec82eb23d8527d5e909e97aa6", size = 3313907, upload-time = "2025-12-09T22:13:50.598Z" }, + { url = "https://files.pythonhosted.org/packages/5c/39/69c0b4051079addd57c84a5bfb34920d87456dd4c90cf7ee0df6efafc8ff/sqlalchemy-2.0.45-cp312-cp312-win32.whl", hash = "sha256:0c9f6ada57b58420a2c0277ff853abe40b9e9449f8d7d231763c6bc30f5c4953", size = 2112182, upload-time = "2025-12-09T21:39:30.824Z" }, + { url = "https://files.pythonhosted.org/packages/f7/4e/510db49dd89fc3a6e994bee51848c94c48c4a00dc905e8d0133c251f41a7/sqlalchemy-2.0.45-cp312-cp312-win_amd64.whl", hash = "sha256:8defe5737c6d2179c7997242d6473587c3beb52e557f5ef0187277009f73e5e1", size = 2139200, upload-time = "2025-12-09T21:39:32.321Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c8/7cc5221b47a54edc72a0140a1efa56e0a2730eefa4058d7ed0b4c4357ff8/sqlalchemy-2.0.45-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe187fc31a54d7fd90352f34e8c008cf3ad5d064d08fedd3de2e8df83eb4a1cf", size = 3277082, upload-time = "2025-12-09T22:11:06.167Z" }, + { url = "https://files.pythonhosted.org/packages/0e/50/80a8d080ac7d3d321e5e5d420c9a522b0aa770ec7013ea91f9a8b7d36e4a/sqlalchemy-2.0.45-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:672c45cae53ba88e0dad74b9027dddd09ef6f441e927786b05bec75d949fbb2e", size = 3293131, upload-time = "2025-12-09T22:13:52.626Z" }, + { url = "https://files.pythonhosted.org/packages/da/4c/13dab31266fc9904f7609a5dc308a2432a066141d65b857760c3bef97e69/sqlalchemy-2.0.45-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:470daea2c1ce73910f08caf10575676a37159a6d16c4da33d0033546bddebc9b", size = 3225389, upload-time = "2025-12-09T22:11:08.093Z" }, + { url = "https://files.pythonhosted.org/packages/74/04/891b5c2e9f83589de202e7abaf24cd4e4fa59e1837d64d528829ad6cc107/sqlalchemy-2.0.45-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9c6378449e0940476577047150fd09e242529b761dc887c9808a9a937fe990c8", size = 3266054, upload-time = "2025-12-09T22:13:54.262Z" }, + { url = "https://files.pythonhosted.org/packages/f1/24/fc59e7f71b0948cdd4cff7a286210e86b0443ef1d18a23b0d83b87e4b1f7/sqlalchemy-2.0.45-cp313-cp313-win32.whl", hash = "sha256:4b6bec67ca45bc166c8729910bd2a87f1c0407ee955df110d78948f5b5827e8a", size = 2110299, upload-time = "2025-12-09T21:39:33.486Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c5/d17113020b2d43073412aeca09b60d2009442420372123b8d49cc253f8b8/sqlalchemy-2.0.45-cp313-cp313-win_amd64.whl", hash = "sha256:afbf47dc4de31fa38fd491f3705cac5307d21d4bb828a4f020ee59af412744ee", size = 2136264, upload-time = "2025-12-09T21:39:36.801Z" }, + { url = "https://files.pythonhosted.org/packages/3d/8d/bb40a5d10e7a5f2195f235c0b2f2c79b0bf6e8f00c0c223130a4fbd2db09/sqlalchemy-2.0.45-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:83d7009f40ce619d483d26ac1b757dfe3167b39921379a8bd1b596cf02dab4a6", size = 3521998, upload-time = "2025-12-09T22:13:28.622Z" }, + { url = "https://files.pythonhosted.org/packages/75/a5/346128b0464886f036c039ea287b7332a410aa2d3fb0bb5d404cb8861635/sqlalchemy-2.0.45-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d8a2ca754e5415cde2b656c27900b19d50ba076aa05ce66e2207623d3fe41f5a", size = 3473434, upload-time = "2025-12-09T22:13:30.188Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e1/3ccb13c643399d22289c6a9786c1a91e3dcbb68bce4beb44926ac2c557bf/sqlalchemy-2.0.45-py3-none-any.whl", hash = "sha256:5225a288e4c8cc2308dbdd874edad6e7d0fd38eac1e9e5f23503425c8eee20d0", size = 1936672, upload-time = "2025-12-09T21:54:52.608Z" }, +] + +[[package]] +name = "sqlparse" +version = "0.5.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/90/76/437d71068094df0726366574cf3432a4ed754217b436eb7429415cf2d480/sqlparse-0.5.5.tar.gz", hash = "sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e", size = 120815, upload-time = "2025-12-19T07:17:45.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/4b/359f28a903c13438ef59ebeee215fb25da53066db67b305c125f1c6d2a25/sqlparse-0.5.5-py3-none-any.whl", hash = "sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba", size = 46138, upload-time = "2025-12-19T07:17:46.573Z" }, +] + [[package]] name = "stack-data" version = "0.6.3" @@ -4348,6 +5174,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, ] +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +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/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]] name = "tzdata" version = "2025.2" @@ -4420,6 +5258,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/27/73/d9a94da0e9d470a543c1b9d3ccbceb0f59455983088e727b8a1824ed90fb/virtualenv-20.35.3-py3-none-any.whl", hash = "sha256:63d106565078d8c8d0b206d48080f938a8b25361e19432d2c9db40d2899c810a", size = 5981061, upload-time = "2025-10-10T21:23:30.433Z" }, ] +[[package]] +name = "waitress" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/cb/04ddb054f45faa306a230769e868c28b8065ea196891f09004ebace5b184/waitress-3.0.2.tar.gz", hash = "sha256:682aaaf2af0c44ada4abfb70ded36393f0e307f4ab9456a215ce0020baefc31f", size = 179901, upload-time = "2024-11-16T20:02:35.195Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/57/a27182528c90ef38d82b636a11f606b0cbb0e17588ed205435f8affe3368/waitress-3.0.2-py3-none-any.whl", hash = "sha256:c56d67fd6e87c2ee598b76abdd4e96cfad1f24cacdea5078d382b1f9d7b5ed2e", size = 56232, upload-time = "2024-11-16T20:02:33.858Z" }, +] + [[package]] name = "watchdog" version = "6.0.0" @@ -4601,6 +5448,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, ] +[[package]] +name = "werkzeug" +version = "3.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/45/ea/b0f8eeb287f8df9066e56e831c7824ac6bab645dd6c7a8f4b2d767944f9b/werkzeug-3.1.4.tar.gz", hash = "sha256:cd3cd98b1b92dc3b7b3995038826c68097dcb16f9baa63abe35f20eafeb9fe5e", size = 864687, upload-time = "2025-11-29T02:15:22.841Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/f9/9e082990c2585c744734f85bec79b5dae5df9c974ffee58fe421652c8e91/werkzeug-3.1.4-py3-none-any.whl", hash = "sha256:2ad50fb9ed09cc3af22c54698351027ace879a0b60a3b5edf5730b2f7d876905", size = 224960, upload-time = "2025-11-29T02:15:21.13Z" }, +] + [[package]] name = "widgetsnbextension" version = "4.0.14" From 5b10768a56e632c9680c8592d2e4f65c5f6fa74b Mon Sep 17 00:00:00 2001 From: SongshGeo Date: Thu, 1 Jan 2026 21:32:35 +0100 Subject: [PATCH 2/4] fix(dependencies): :bug: Update Aim version constraint and improve compatibility with Mesa 3.4.0 This commit updates the version constraint for the `aim` package in both `pyproject.toml` and `uv.lock` files from `>=3.0.0` to `>=3.29.1`. This change ensures compatibility with the latest features and improvements in Aim, enhancing the tracking capabilities of the project. Additionally, it initializes the `_steps` attribute in the `MainModel` class for better type checking and adds a setter for the `time` property in the `TimeDriver` class to prevent unintended modifications, ensuring robust time management. --- abses/core/model.py | 14 ++++++++++++++ abses/core/time_driver.py | 31 +++++++++++++++++++++++++++++++ pyproject.toml | 3 +-- uv.lock | 4 +--- 4 files changed, 47 insertions(+), 5 deletions(-) diff --git a/abses/core/model.py b/abses/core/model.py index 9acb9619..4ca164fe 100644 --- a/abses/core/model.py +++ b/abses/core/model.py @@ -123,6 +123,7 @@ def __init__( self._settings = merge_parameters(normalized_params, **clean_kwargs) self._time = TimeDriver(model=self) + self._steps: int = 0 # Initialize steps counter for type checking self._setup_subsystems(human_class, nature_class) self._agents_handler = _ModelAgentsContainer( model=self, max_len=kwargs.get("max_agents", None) @@ -415,6 +416,19 @@ def time(self) -> TimeDriver: """The time driver & controller""" return self._time + @time.setter + def time(self, value: Any) -> None: + """Set the time driver (no-op for safety, prevents Mesa from overwriting). + + Mesa 3.4.0+ tries to set self.time = 0.0 in __init__ and self.time += 1 in step(). + This setter ignores those attempts to preserve our TimeDriver. + + Args: + value: Value to set (ignored, can be TimeDriver, float, or any type). + """ + # Ignore all attempts to set time - we manage it ourselves via _time + pass + @property def params(self) -> DictConfig: """The global parameters of this model.""" diff --git a/abses/core/time_driver.py b/abses/core/time_driver.py index ef7ee9d3..aeea0350 100644 --- a/abses/core/time_driver.py +++ b/abses/core/time_driver.py @@ -13,6 +13,7 @@ from functools import cached_property, total_ordering, wraps from typing import ( TYPE_CHECKING, + Any, Callable, Deque, Dict, @@ -149,6 +150,36 @@ def __lt__(self, other) -> bool: return self.dt < other_dt raise TypeError(f"Cannot compare {type(self)} with {type(other)}.") + def __add__(self, other: Any) -> "TimeDriver": + """Support addition with numbers (for Mesa 3.4.0+ compatibility). + + Mesa 3.4.0+ tries to execute self.time += 1 in its step() wrapper. + We ignore this and return self, as our time management is independent. + + Args: + other: Value to add (ignored). + + Returns: + Self, unchanged. + """ + # Ignore Mesa's automatic time increment - we manage time ourselves + return self + + def __iadd__(self, other: Any) -> "TimeDriver": + """Support in-place addition with numbers (for Mesa 3.4.0+ compatibility). + + Mesa 3.4.0+ tries to execute self.time += 1 in its step() wrapper. + We ignore this and return self, as our time management is independent. + + Args: + other: Value to add (ignored). + + Returns: + Self, unchanged. + """ + # Ignore Mesa's automatic time increment - we manage time ourselves + return self + def __deepcopy__(self, memo): return self diff --git a/pyproject.toml b/pyproject.toml index 74e53e57..a7f52897 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,6 @@ dependencies = [ "numpy>=1.26", "urllib3", "icons", - "aim>=3.29.1", ] [project.urls] @@ -52,7 +51,7 @@ Repository = "https://github.com/SongshGeoLab/ABSESpy" [project.optional-dependencies] # Optional tracker dependencies for experiment tracking. aim = [ - "aim>=3.0.0", + "aim>=3.29.1", ] mlflow = [ "mlflow>=2.0.0", diff --git a/uv.lock b/uv.lock index b060f086..e6b249a3 100644 --- a/uv.lock +++ b/uv.lock @@ -11,7 +11,6 @@ name = "abses" version = "0.9.0" source = { editable = "." } dependencies = [ - { name = "aim" }, { name = "fiona" }, { name = "fontawesome" }, { name = "geocube" }, @@ -113,8 +112,7 @@ docs = [ [package.metadata] requires-dist = [ - { name = "aim", specifier = ">=3.29.1" }, - { name = "aim", marker = "extra == 'aim'", specifier = ">=3.0.0" }, + { name = "aim", marker = "extra == 'aim'", specifier = ">=3.29.1" }, { name = "fiona", specifier = ">1.8" }, { name = "fontawesome", specifier = ">=5" }, { name = "geocube", specifier = ">=0.5.0" }, From 1bb75b0d7ed0583a2186280ffba5d4bffabcf101 Mon Sep 17 00:00:00 2001 From: SongshGeo Date: Thu, 1 Jan 2026 21:53:13 +0100 Subject: [PATCH 3/4] fix(config): :bug: Improve error handling in tracker configuration validation This commit enhances the `_validate_tracker` function in the `config.py` file by converting keys to strings to avoid bytes formatting issues. Additionally, the `_to_plain` function in `factory.py` is updated to ensure it returns a dictionary, improving type safety. These changes enhance the robustness of the configuration validation process and ensure consistent behavior across different data types. --- .pre-commit-config.yaml | 4 +++- abses/utils/config.py | 15 ++++++++++++--- abses/utils/tracker/factory.py | 11 +++++++++-- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b4e43d1a..d1b0e86f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,7 +23,9 @@ repos: rev: 'v1.15.0' # Use the sha / tag you want to point at hooks: - id: mypy - args: [--ignore-missing-imports, --follow-imports=skip] + # Use same config as CI - read from pyproject.toml + # CI uses: uv run mypy abses/ + # This ensures local and CI use identical mypy settings - repo: https://github.com/econchick/interrogate rev: '1.7.0' # Check for the latest version hooks: diff --git a/abses/utils/config.py b/abses/utils/config.py index 1258e164..c64338c8 100644 --- a/abses/utils/config.py +++ b/abses/utils/config.py @@ -198,15 +198,24 @@ def _validate_tracker(tracker_cfg: Any) -> List[str]: if section == "agents": for breed, reporters in sub.items(): if not isinstance(reporters, (dict, DictConfig)): - errs.append(f"tracker.agents.{breed}: must be a mapping.") + # Convert breed to string to avoid bytes formatting issue + breed_str = breed if isinstance(breed, str) else str(breed) + errs.append(f"tracker.agents.{breed_str}: must be a mapping.") continue for name, reporter in reporters.items(): + # Convert keys to strings to avoid bytes formatting issue + breed_str = breed if isinstance(breed, str) else str(breed) + name_str = name if isinstance(name, str) else str(name) errs.extend( - _validate_tracker_entry(f"agents.{breed}.{name}", reporter) + _validate_tracker_entry( + f"agents.{breed_str}.{name_str}", reporter + ) ) else: for name, reporter in sub.items(): - errs.extend(_validate_tracker_entry(f"{section}.{name}", reporter)) + # Convert name to string to avoid bytes formatting issue + name_str = name if isinstance(name, str) else str(name) + errs.extend(_validate_tracker_entry(f"{section}.{name_str}", reporter)) return errs diff --git a/abses/utils/tracker/factory.py b/abses/utils/tracker/factory.py index 7cb2bd59..8b39747a 100644 --- a/abses/utils/tracker/factory.py +++ b/abses/utils/tracker/factory.py @@ -20,7 +20,12 @@ def _to_plain(cfg: DictConfig) -> Dict: """Convert DictConfig to plain dict safely.""" from omegaconf import OmegaConf - return OmegaConf.to_container(cfg, resolve=True) + result = OmegaConf.to_container(cfg, resolve=True) + # Type narrowing: ensure we return a dict + if isinstance(result, dict): + return result + # Fallback for non-dict results (shouldn't happen in practice) + return {} def prepare_tracker_run_name( @@ -243,7 +248,9 @@ def prepare_collector_config(tracker_cfg: DictConfig | Dict | None) -> Dict: return {} if isinstance(tracker_cfg, DictConfig): - collector_cfg = OmegaConf.to_container(tracker_cfg, resolve=True) + container_result = OmegaConf.to_container(tracker_cfg, resolve=True) + # Type narrowing: ensure we have a dict + collector_cfg = container_result if isinstance(container_result, dict) else {} elif isinstance(tracker_cfg, dict): collector_cfg = tracker_cfg.copy() else: From 7259e53d250207a27673e82ffd81a8b63d8d93d4 Mon Sep 17 00:00:00 2001 From: SongshGeo Date: Thu, 1 Jan 2026 21:57:58 +0100 Subject: [PATCH 4/4] fix(dependencies): :bug: Update MLflow version constraint and improve configuration handling This commit updates the version constraint for the `mlflow` package in both `pyproject.toml` and `uv.lock` files from `>=2.0.0` to `>=2.3.1`, ensuring compatibility with the latest features and security updates. Additionally, the `normalize_config` function in `config.py` is modified to accept a broader range of input types (DictConfig, dict, or None), enhancing its flexibility and backward compatibility. The docstring for `log_final_metrics` in `default.py` is also updated to include an optional `step` parameter, improving clarity and documentation consistency. --- abses/utils/config.py | 4 ++-- abses/utils/tracker/default.py | 11 +++++++++-- abses/utils/tracker/factory.py | 4 ++-- abses/utils/tracker/mlflow_tracker.py | 6 +++++- pyproject.toml | 5 ++++- tests/utils/test_tracker.py | 8 -------- uv.lock | 2 +- 7 files changed, 23 insertions(+), 17 deletions(-) diff --git a/abses/utils/config.py b/abses/utils/config.py index c64338c8..855a64bc 100644 --- a/abses/utils/config.py +++ b/abses/utils/config.py @@ -12,14 +12,14 @@ from abses.utils.logging import logger -def normalize_config(config: DictConfig) -> DictConfig: +def normalize_config(config: DictConfig | Dict | None) -> DictConfig: """Normalize configuration for backward compatibility. This function converts deprecated keys to the new schema while preserving original data. Args: - config: Raw configuration. + config: Raw configuration (DictConfig, dict, or None). Returns: Normalized configuration. diff --git a/abses/utils/tracker/default.py b/abses/utils/tracker/default.py index 6810ba50..a196876b 100644 --- a/abses/utils/tracker/default.py +++ b/abses/utils/tracker/default.py @@ -29,8 +29,15 @@ def log_agent_vars( ) -> None: """Log agent variables (no-op).""" - def log_final_metrics(self, metrics: Dict[str, Any]) -> None: - """Log final metrics (no-op).""" + def log_final_metrics( + self, metrics: Dict[str, Any], step: int | None = None + ) -> None: + """Log final metrics (no-op). + + Args: + metrics: Dictionary of final metric names to values. + step: Step number (optional, ignored). + """ def end_run(self) -> None: """End run (no-op).""" diff --git a/abses/utils/tracker/factory.py b/abses/utils/tracker/factory.py index 8b39747a..9c7c1861 100644 --- a/abses/utils/tracker/factory.py +++ b/abses/utils/tracker/factory.py @@ -3,7 +3,7 @@ # @Author : ABSESpy Team from __future__ import annotations -from typing import TYPE_CHECKING, Dict, Optional +from typing import TYPE_CHECKING, Any, Dict, Optional from omegaconf import DictConfig @@ -182,7 +182,7 @@ def create_tracker( ConfigurationError: If backend is unknown or missing dependencies. """ backend = None - cfg_dict: Dict[str, Dict] = {} + cfg_dict: Dict[str, Any] = {} if config is not None and config: # Check for both None and empty if isinstance(config, DictConfig): backend = config.get("backend", None) diff --git a/abses/utils/tracker/mlflow_tracker.py b/abses/utils/tracker/mlflow_tracker.py index d598402f..5e2f5b0c 100644 --- a/abses/utils/tracker/mlflow_tracker.py +++ b/abses/utils/tracker/mlflow_tracker.py @@ -72,7 +72,11 @@ def log_final_metrics( self, metrics: Dict[str, Any], step: int | None = None ) -> None: """Log final metrics.""" - self.log_metrics({f"final.{k}": v for k, v in metrics.items()}) + numeric_metrics = { + f"final.{k}": v for k, v in metrics.items() if isinstance(v, (int, float)) + } + if numeric_metrics: + self.log_metrics(numeric_metrics, step=step) def end_run(self) -> None: """End MLflow run.""" diff --git a/pyproject.toml b/pyproject.toml index a7f52897..15c7b8f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,11 +50,14 @@ Repository = "https://github.com/SongshGeoLab/ABSESpy" [project.optional-dependencies] # Optional tracker dependencies for experiment tracking. +# Note: aim>=3.30.0 does not exist. Version 3.29.1 has CVE-2025-5321. +# Consider upgrading to a patched version when available (>3.29.1). +# See: https://github.com/aimhubio/aim for security updates. aim = [ "aim>=3.29.1", ] mlflow = [ - "mlflow>=2.0.0", + "mlflow>=2.3.1", ] # Optional docs-related dependencies. Not required at runtime. docs = [ diff --git a/tests/utils/test_tracker.py b/tests/utils/test_tracker.py index b557ccc1..1805db5e 100644 --- a/tests/utils/test_tracker.py +++ b/tests/utils/test_tracker.py @@ -4,7 +4,6 @@ from __future__ import annotations -import pytest from omegaconf import OmegaConf from abses.utils.tracker.default import DefaultTracker @@ -21,10 +20,3 @@ def test_create_tracker_unknown_backend_fallback() -> None: """Unknown backend should fall back to default.""" tracker = create_tracker(OmegaConf.create({"backend": "unknown"}), model=None) assert isinstance(tracker, DefaultTracker) - - -def test_create_tracker_aim_missing_dep() -> None: - """Aim backend without dependency should raise ConfigurationError.""" - cfg = OmegaConf.create({"backend": "mlflow"}) - with pytest.raises(Exception): - create_tracker(cfg, model=None) diff --git a/uv.lock b/uv.lock index e6b249a3..ee85a02a 100644 --- a/uv.lock +++ b/uv.lock @@ -139,7 +139,7 @@ requires-dist = [ { name = "mkdocs-static-i18n", marker = "extra == 'docs'", specifier = ">=1.3.0" }, { name = "mkdocstrings", marker = "extra == 'docs'", specifier = ">=0.30.1" }, { name = "mkdocstrings", extras = ["python"], marker = "extra == 'docs'", specifier = ">=0.24.0" }, - { name = "mlflow", marker = "extra == 'mlflow'", specifier = ">=2.0.0" }, + { name = "mlflow", marker = "extra == 'mlflow'", specifier = ">=2.3.1" }, { name = "nbconvert", marker = "extra == 'docs'", specifier = ">=7.16.6" }, { name = "nbmake", marker = "extra == 'docs'", specifier = ">=1.5.5" }, { name = "netcdf4", specifier = ">=1.6" },