diff --git a/docs/mcp/README.md b/docs/mcp/README.md new file mode 100644 index 00000000..56e7a1af --- /dev/null +++ b/docs/mcp/README.md @@ -0,0 +1,362 @@ +# PLEIADES MCP Server + +PLEIADES provides an optional MCP (Model Context Protocol) server that enables AI-assisted neutron resonance analysis. The MCP server exposes PLEIADES workflow functions as tools that AI applications (Claude Code, Claude Desktop, Cursor, etc.) can invoke. + +> **Note**: MCP support is available in PLEIADES v2.2.0+ + +## What is MCP? + +[Model Context Protocol](https://modelcontextprotocol.io/) is an open standard for connecting AI applications to external systems. It provides a standardized way for AI models to access data sources, use tools, and execute workflows. Think of it as "USB-C for AI applications." + +## Installation + +### From PyPI + +```bash +pip install pleiades-neutron[mcp] +``` + +### Editable Install (Development) + +```bash +git clone https://github.com/lanl/PLEIADES.git +cd PLEIADES +pip install -e ".[mcp]" +``` + +### Using Pixi + +```bash +pixi install -e mcp +pixi run mcp-server +``` + +## Quick Start + +### 1. Start the MCP Server + +```bash +# Using console script +pleiades-mcp + +# Or using module invocation +python -m pleiades.mcp +``` + +### 2. Register with Claude Code + +Create a `.mcp.json` file in your project directory: + +```json +{ + "mcpServers": { + "pleiades": { + "command": "pleiades-mcp" + } + } +} +``` + +For Pixi-based projects: + +```json +{ + "mcpServers": { + "pleiades": { + "command": "pixi", + "args": ["run", "mcp-server"] + } + } +} +``` + +### 3. Use with Claude Code + +Once registered, Claude Code will automatically connect to the PLEIADES MCP server. You can then make natural language requests: + +- "Validate the dataset at ./datasets/hafnium" +- "Extract the manifest from this resonance data" +- "Analyze the neutron resonance data using the Docker backend" + +### 4. Use with OpenAI Codex + +Codex CLI does not read `.mcp.json`. You must register MCP servers in the Codex MCP registry. + +```bash +# Register the PLEIADES MCP server (stdio transport) +codex mcp add pleiades -- /path/to/pixi run mcp-server + +``` + +Here is an example + +```bash +codex mcp add pleiades -- /home/user/.pixi/bin/pixi run mcp-server +``` + +Once registered then the MCP can be verified with the following: + +```bash +# Verify registration +codex mcp list + +# Start Codex in the project directory +codex +``` + +In the Codex prompt, ask it to list tools or run an analysis: + +- "Use the pleiades MCP server and list its tools." +- "Run analyze_resonance on ./datasets/hafnium with backend docker." + +> **Note**: Do not start the MCP server manually when using Codex. Codex will spawn the stdio server on first tool use. + +## Workflow Overview + +The MCP tools orchestrate the following workflow: + +```mermaid +flowchart TD + A[Validate Dataset] --> B[Extract Manifest] + B --> C{Detect Workflow Type} + C -->|raw + open_beam| D[Full Workflow] + C -->|sammy_data only| E[Simplified Workflow] + D --> F[Normalize Data] + F --> G[Run SAMMY] + E --> G + G --> H[Parse Results] + H --> I["chi², fit quality, isotope abundances"] +``` + +## Available Tools + +### `validate_resonance_dataset` + +Validate a neutron resonance dataset structure and check readiness for analysis. + +**Parameters:** +| Name | Type | Required | Description | +|------|------|----------|-------------| +| `dataset_path` | string | Yes | Path to the dataset directory | + +**Returns:** Validation result with: +- `valid`: Overall validity status +- `can_run_full_workflow`: Whether full imaging workflow is available +- `can_run_simplified_workflow`: Whether SAMMY-only workflow is available +- `recommended_workflow`: `"full"`, `"simplified"`, or `None` +- `issues`: List of validation issues with severity and messages + +### `extract_resonance_manifest` + +Extract and parse the manifest from a neutron resonance dataset. + +**Parameters:** +| Name | Type | Required | Description | +|------|------|----------|-------------| +| `dataset_path` | string | Yes | Path to the dataset directory | + +**Manifest file search order:** +1. `manifest_intermediate.md` +2. `smcp_manifest.md` +3. `manifest.md` + +**Returns:** Manifest data including: +- `name`, `description`, `version`: Basic metadata +- `facility`, `beamline`, `detector`: Experiment info +- `isotope`: Primary isotope (e.g., "Hf-177") +- `isotopes`: Explicit isotope list (takes priority over natural abundance) +- `material_properties`: Density, atomic mass, temperature + +See [Manifest Format](manifest-format.md) for complete specification. + +### `analyze_resonance` + +Perform neutron resonance analysis on a dataset using SAMMY. + +**Parameters:** +| Name | Type | Required | Default | Description | +|------|------|----------|---------|-------------| +| `dataset_path` | string | Yes | - | Path to the dataset directory | +| `backend` | string | No | `"auto"` | SAMMY backend: `"auto"`, `"local"`, or `"docker"` | +| `isotopes` | list[str] | No | `None` | Specific isotopes to analyze (e.g., `["Hf-177", "Hf-178"]`) | + +**Isotope Selection Priority:** + +When determining which isotopes to analyze, PLEIADES uses this priority order: + +1. **`isotopes` parameter** (highest priority) - If you pass isotopes to the function +2. **Manifest `isotopes` field** - Explicit list in the manifest file +3. **Manifest `enrichment` field** - Custom isotope composition (when `use_natural_abundance: false`) +4. **Natural abundance lookup** (default) - Uses PLEIADES isotope database + +**Returns:** Analysis result with: +- `success`: Whether analysis completed +- `workflow_type`: `"simplified"` or `"full"` +- `chi_squared`, `reduced_chi_squared`: Fit quality metrics +- `fit_quality`: `"excellent"`, `"good"`, `"acceptable"`, or `"poor"` +- `isotope_results`: Per-isotope abundances and uncertainties +- `output_dir`, `lpt_file`, `lst_file`: Output file paths + +## Output Format + +All tools return JSON-serializable dictionaries with a consistent format: + +**Success:** +```json +{ + "status": "success", + "data": { + "valid": true, + "recommended_workflow": "simplified", + ... + } +} +``` + +**Error:** +```json +{ + "status": "error", + "error": "Dataset validation failed: missing sammy_data/ directory" +} +``` + +**Common error messages:** +- `"No manifest found in /path (searched: manifest_intermediate.md, smcp_manifest.md, manifest.md)"` +- `"Dataset validation failed: ..."` +- `"SAMMY execution failed: ..."` +- `"No SAMMY backend available"` +- `"ENDF parameter file not found for isotope: Hf-999"` + +## Backend Options + +The `analyze_resonance` tool supports multiple SAMMY execution backends: + +| Backend | Description | Requirements | +|---------|-------------|--------------| +| `auto` | Auto-detect best available (tries Docker first, then local) | Any of the below | +| `local` | Run SAMMY binary directly | SAMMY installed locally | +| `docker` | Run SAMMY in Docker container | Docker installed and running | + +> **Note**: The `nova` backend (ORNL NOVA service) is currently disabled due to package instability. + +## Programmatic Usage + +You can also use the MCP tools directly in Python without the server: + +```python +from pleiades.mcp.tools import ( + validate_resonance_dataset, + extract_resonance_manifest, + analyze_resonance, +) + +# Validate a dataset +result = validate_resonance_dataset("/path/to/dataset") +if result["status"] == "success": + data = result["data"] + print(f"Valid: {data['valid']}") + print(f"Recommended workflow: {data['recommended_workflow']}") + for issue in data.get("issues", []): + print(f" [{issue['severity']}] {issue['message']}") +else: + print(f"Validation error: {result['error']}") + +# Extract manifest +manifest = extract_resonance_manifest("/path/to/dataset") +if manifest["status"] == "success": + data = manifest["data"] + print(f"Isotope: {data['isotope']}") + print(f"Isotopes list: {data.get('isotopes')}") # May be None + +# Run analysis +analysis = analyze_resonance( + "/path/to/dataset", + backend="docker", + isotopes=["Hf-177", "Hf-178"] +) +if analysis["status"] == "success": + data = analysis["data"] + print(f"Success: {data['success']}") + print(f"Chi-squared: {data['chi_squared']}") + print(f"Fit quality: {data['fit_quality']}") + for iso_result in data.get("isotope_results", []): + print(f" {iso_result['isotope']}: {iso_result['abundance']:.4f}") +else: + print(f"Analysis error: {analysis['error']}") +``` + +## Checking MCP Availability + +```python +from pleiades.mcp import MCP_AVAILABLE, check_mcp_available + +if MCP_AVAILABLE: + from pleiades.mcp.server import get_server + server = get_server() +else: + print("MCP not installed. Run: pip install pleiades-neutron[mcp]") + +# Or raise ImportError with instructions +check_mcp_available() # Raises if not installed +``` + +## Security Notes + +> **WARNING**: MCP tools have file system access to any path the server process can read. + +- Path traversal (`../`) is **explicitly permitted** by design +- An AI client can read any file accessible to the MCP server process user +- **Never** run the MCP server as root or with elevated privileges +- Consider OS-level sandboxing (Docker, chroot) in production environments +- Use filesystem ACLs to restrict access to sensitive data directories + +## Troubleshooting + +### "No SAMMY backend available" + +No backend could be found. Solutions: +1. **Docker**: Ensure Docker is running (`docker info`) +2. **Local**: Install SAMMY and ensure it's in your PATH + +### "ENDF parameter file not found for isotope: X" + +The specified isotope doesn't have ENDF data available. Solutions: +1. Check isotope format (e.g., `"Hf-177"` not `"177Hf"`) +2. Verify the isotope exists in the ENDF library +3. Use a different isotope that has available data + +### "Docker not running" or connection errors + +```bash +# Check Docker status +docker info + +# Start Docker if needed (Linux) +sudo systemctl start docker + +# On macOS/Windows, start Docker Desktop +``` + +### MCP server not connecting + +1. Verify `.mcp.json` is in your project root +2. Check the command path is correct +3. Restart Claude Code after editing `.mcp.json` + +### Codex MCP server not found + +If Codex reports `unknown MCP server`: + +1. Codex CLI does not read `.mcp.json`; it uses its own MCP registry +2. Verify the server is registered: `codex mcp list` +3. Register it if missing: `codex mcp add pleiades -- /path/to/pixi run mcp-server` +4. Restart Codex after adding the server +5. Trigger a tool call; Codex lazy-starts MCP servers on first use +6. Do not start the MCP server manually for stdio transport; Codex spawns it + +## Related Documentation + +- [Manifest Format Specification](manifest-format.md) +- [Integration Pattern Guide](integration-pattern.md) - How to add MCP to other scientific packages +- [MCP Protocol Specification](https://modelcontextprotocol.io/) diff --git a/docs/mcp/adr-001-mcp-integration-pattern.md b/docs/mcp/adr-001-mcp-integration-pattern.md new file mode 100644 index 00000000..fa224efb --- /dev/null +++ b/docs/mcp/adr-001-mcp-integration-pattern.md @@ -0,0 +1,216 @@ +# ADR-001: MCP Integration Pattern for PLEIADES + +**Status**: Accepted (for PLEIADES v2.2.0) +**Date**: 2025-12-09 +**Authors**: PLEIADES Development Team +**Related Issues**: #163 (Epic), #171 (Research) + +## Context + +### The Problem + +Scientific software faces a persistent challenge: **code rots, but physics doesn't**. Dependencies decay, platforms change, and significant engineering effort goes into maintenance that doesn't advance science. Meanwhile, the underlying physics—the equations, the data reduction algorithms, the validation criteria—remains constant. + +PLEIADES needed to integrate AI capabilities (via MCP - Model Context Protocol) while: + +1. Avoiding tight coupling that would create future maintenance burden +2. Keeping the core library functional without AI dependencies +3. Enabling future adaptation as MCP ecosystem evolves +4. Aligning with a longer-term vision where physics descriptions, not code, become the primary artifact + +### The Broader Vision + +This integration is part of a larger initiative (Scientific Model Context Protocol - sMCP) that treats physics knowledge as "source code" that AI can "compile" into executable implementations: + +``` +Assembly era: Hardware-specific code → New hardware = rewrite everything +Compiler era: High-level source → Compiler → Machine code (source unchanged) +sMCP era: Physics description → AI → Generated code (physics unchanged) +``` + +Just as we don't version control `.o` files or compiler assembly output, the vision is that we shouldn't need to version control Python scripts generated from physics descriptions. + +## Decisions + +### Decision 1: Optional Package (not Separate Package) + +**Choice**: MCP is an optional dependency within PLEIADES (`pip install pleiades-neutron[mcp]`), not a separate package. + +**Alternatives Considered**: +- Separate package (`pleiades-mcp`) that depends on PLEIADES core +- MCP as a required dependency + +**Rationale**: +- **Prevents drift**: Changes to core PLEIADES APIs are immediately reflected in MCP tools. A separate package would gradually diverge as the two evolve independently. +- **Single source of truth**: One repository, one test suite, one release cycle. +- **Simpler dependency graph**: Users install one package with optional extras, not multiple packages with version compatibility concerns. + +**Implementation**: +```toml +# pyproject.toml +[project.optional-dependencies] +mcp = ["fastmcp>=2.12.0,<3"] +``` + +### Decision 2: FastMCP 2.x (not MCP 1.0) + +**Choice**: Use FastMCP 2.x as the MCP server implementation. + +**Alternatives Considered**: +- Official `mcp` package (1.0) +- Building directly on MCP protocol primitives + +**Rationale**: +- **Future direction**: FastMCP represents where the MCP ecosystem is heading. The official MCP package adopted FastMCP v1.0 patterns, validating this direction. +- **No catch-up game**: Using state-of-the-art now avoids migration costs later. +- **Pydantic 2.x compatibility**: FastMCP 2.12+ resolved compatibility issues with modern Pydantic. +- **Developer experience**: Cleaner API, better error messages, less boilerplate. + +**Implementation**: +```toml +# Pin to 2.x series - version 3.x may have breaking API changes +mcp = ["fastmcp>=2.12.0,<3"] +``` + +### Decision 3: Thin Wrapper (not Direct FastMCP Usage) + +**Choice**: Create a custom `@mcp_tool` decorator that wraps FastMCP, rather than using FastMCP's built-in `@server.tool()` directly. + +**Alternatives Considered**: +- Direct use of `@server.tool()` decorator +- Code generation approach + +**Rationale**: +- **Decoupling**: PLEIADES is fundamentally a library for SAMMY-based resonance analysis. MCP is a nice-to-have feature, not core functionality. A thin wrapper allows switching frameworks if needed—for example, when the official `mcp` package adopted FastMCP patterns, we avoided migration pain by already abstracting the dependency. +- **Testability**: Tools can be tested without running an MCP server by calling the decorated functions directly. +- **Registry pattern**: Enables auto-discovery and runtime introspection of available tools without importing FastMCP. +- **Future flexibility**: If FastMCP 3.x introduces breaking changes or a better implementation emerges, only the FastMCP adapter layer in `server.py` needs updating—all tool definitions remain unchanged. + +**Implementation** (actual pattern from `decorators.py`): +```python +# pleiades/mcp/decorators.py - registry-based decorator +_mcp_registry: dict[str, dict[str, Any]] = {} + +def mcp_tool(func=None, *, name=None, description=None, parameter_descriptions=None): + """Decorator to register a function as an MCP tool.""" + def decorator(fn): + tool_name = name or fn.__name__ + _mcp_registry[tool_name] = {"name": tool_name, "func": fn, ...} + return fn # Return unchanged - no wrapper overhead + return decorator + +# Usage in tools.py: +@mcp_tool(description="Analyze resonance data") +def analyze_resonance(dataset_path: str) -> dict: + ... +``` + +### Decision 4: Physics-Centric Manifest Format + +**Choice**: Design manifest format to capture physics knowledge and validation criteria, not just configuration parameters. + +**Alternatives Considered**: +- Simple JSON configuration +- Tool-specific config formats +- Generic workflow description + +**Rationale**: +- **Longevity**: Physics doesn't change; code does. A manifest that captures the physics (isotope properties, expected resonances, validation criteria) remains valid even as implementation code evolves. +- **AI-compilable**: With sufficient physics description, an AI agent can generate appropriate processing code for any target platform. +- **Self-documenting data**: Datasets carry their own processing instructions, enabling cross-facility portability. +- **Validation-first**: Embedded correctness criteria (expected peak positions, statistical quality metrics) enable automated verification. + +**Implementation**: +```yaml +# Manifest captures physics, not just parameters +isotope: Hf-177 +material_properties: + density_g_cm3: 13.31 + atomic_mass_amu: 178.49 + temperature_k: 295.0 + +# Explicit isotope list for analysis +isotopes: + - Hf-176 + - Hf-177 + - Hf-178 +``` + +## Consequences + +### Benefits + +1. **Maintainability**: Core PLEIADES works without MCP; MCP integration is isolated. +2. **Evolvability**: Can upgrade or replace MCP implementation without touching workflows. +3. **Testability**: All components testable in isolation. +4. **Portability**: Manifest format enables cross-tool, cross-facility workflows. +5. **Future-proofing**: Architecture aligns with AI-first scientific computing vision. + +### Trade-offs + +1. **Additional abstraction**: Thin wrapper adds a layer of indirection (minimal overhead). +2. **Pattern duplication**: Other packages (iBeatles, etc.) must copy the pattern rather than import a shared library. +3. **Learning curve**: Developers must understand the registry pattern, not just FastMCP. + +### Security Considerations + +MCP tools accept file system paths from AI clients. The current implementation: +- Does **not** restrict path traversal (`../` is permitted) +- Relies on OS-level permissions for access control +- Should run with minimal necessary file system permissions + +See `docs/mcp/README.md` for deployment security guidance. + +### Why Not a Shared Package? + +We considered extracting a `scientific-mcp` shared package but decided against it: + +- **Premature abstraction**: The pattern is still evolving; freezing it in a package would slow iteration. +- **Maintenance burden**: Another package to version, release, and keep compatible. +- **Low value**: The pattern is simple enough to copy; documentation provides sufficient guidance. +- **Dependency coupling**: A shared package locks projects into synchronized releases and version compatibility constraints, reducing each project's ability to evolve independently. + +**Recommendation**: Document the pattern thoroughly (this ADR + `integration-pattern.md`) and let projects copy/adapt as needed. Revisit shared package decision when pattern stabilizes across 3+ implementations. + +## Vision Alignment + +This architecture supports the broader sMCP vision: + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Layer 1: Physics Compiler (Future) │ +│ full_sMCP_manifest (physics knowledge) │ +│ ↓ │ +│ Generated Code │ +└─────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────┐ +│ Layer 2: Pipeline Linker (Current: manifest_intermediate) │ +│ manifest_intermediate (tool usage instructions) │ +│ ↓ │ +│ Orchestrated Workflow │ +└─────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────┐ +│ Layer 3: Execution Runtime (Current: PLEIADES MCP) │ +│ MCP Server executes tools │ +│ ↓ │ +│ Validated Results │ +└─────────────────────────────────────────────────────────────┘ +``` + +PLEIADES MCP currently operates at **Layer 3** (execution runtime), with manifest format designed to support **Layer 2** (pipeline orchestration). The physics-centric manifest design anticipates **Layer 1** (AI-generated code from physics descriptions). + +## References + +- [MCP Protocol Specification](https://modelcontextprotocol.io/) +- [FastMCP Documentation](https://gofastmcp.com/) +- [PLEIADES MCP Integration Pattern](./integration-pattern.md) +- [PLEIADES Manifest Format](./manifest-format.md) +- sMCP Architecture Vision (internal documentation, not public) + +## Revision History + +| Date | Change | +|------|--------| +| 2025-12-09 | Initial version documenting decisions from MCP integration sprint | diff --git a/docs/mcp/integration-pattern.md b/docs/mcp/integration-pattern.md new file mode 100644 index 00000000..d23dc8c4 --- /dev/null +++ b/docs/mcp/integration-pattern.md @@ -0,0 +1,499 @@ +# MCP Integration Pattern for Scientific Software + +This guide documents the pattern used to add MCP capabilities to PLEIADES, designed for reuse in other scientific packages (iBeatles, etc.). + +> **Note**: This pattern was developed and tested with FastMCP 2.12.0+ and Pydantic 2.x. + +## Architecture Overview + +The MCP integration uses a layered architecture that keeps MCP concerns separate from core functionality: + +```mermaid +flowchart TD + subgraph AI["AI Application (Claude Code, Claude Desktop)"] + end + + subgraph MCP["MCP Server Layer"] + S["server.py (FastMCP)"] + D["decorators.py (@mcp_tool)"] + T["tools.py (JSON wrappers)"] + end + + subgraph WF["Workflows Layer"] + W["High-level functions with Pydantic models"] + end + + subgraph Core["Core Library"] + C["Domain-specific implementation"] + end + + AI -->|MCP Protocol| MCP + MCP -->|Python calls| WF + WF --> Core +``` + +**Key principle**: The MCP layer is a thin wrapper. All logic lives in the workflows and core layers, which work without MCP. + +## Decorator Approaches + +There are two ways to expose functions as MCP tools: + +### Option A: FastMCP's Built-in Decorator (Simpler) + +FastMCP 2.x provides a built-in `@server.tool()` decorator: + +```python +from fastmcp import FastMCP + +server = FastMCP("my-server") + +@server.tool() +def my_function(path: str) -> dict: + """This function is automatically exposed as an MCP tool.""" + return {"result": "success"} +``` + +**Use this when**: You're building a simple MCP server and don't need to switch backends. + +### Option B: Custom Registry Decorator (PLEIADES Pattern) + +PLEIADES uses a custom `@mcp_tool` decorator with a registry abstraction: + +```python +from pleiades.mcp.decorators import mcp_tool + +@mcp_tool(description="Analyze data") +def analyze(path: str) -> dict: + return {"result": "success"} +``` + +**Use this when**: +- You want to decouple from specific MCP implementations +- You may switch backends in the future (FastMCP → another library) +- You need custom parameter descriptions or validation +- You want tools to be testable without MCP server running + +**This guide documents Option B** (the custom decorator pattern) because it provides more flexibility for scientific software that may need to support multiple deployment scenarios. + +## Implementation Steps + +### Step 1: Create Workflows Module + +Create high-level functions that orchestrate your domain logic. These should: +- Accept simple types (Path, str, basic types) +- Return Pydantic models for structured output +- Handle errors gracefully +- Be usable without MCP + +```python +# src/yourpackage/workflows/analysis.py +from pathlib import Path +from pydantic import BaseModel + +class AnalysisResult(BaseModel): + success: bool + metric: float | None + error_message: str | None + +def run_analysis(data_path: Path, method: str = "default") -> AnalysisResult: + """High-level analysis function. + + This function is independent of MCP and can be used directly. + """ + try: + # Call your core library functions + result = core_library.analyze(data_path, method) + return AnalysisResult( + success=True, + metric=result.metric, + error_message=None + ) + except Exception as e: + return AnalysisResult( + success=False, + metric=None, + error_message=str(e) + ) +``` + +### Step 2: Create Decorator Module + +Implement a registry-based decorator for tool registration: + +```python +# src/yourpackage/mcp/decorators.py +from __future__ import annotations +from typing import Any, Callable, TypeVar +from copy import deepcopy + +F = TypeVar("F", bound=Callable[..., Any]) + +_mcp_registry: dict[str, dict[str, Any]] = {} + +def mcp_tool( + func: F | None = None, + *, + name: str | None = None, + description: str | None = None, + parameter_descriptions: dict[str, str] | None = None, +) -> F | Callable[[F], F]: + """Register a function as an MCP tool.""" + + def decorator(f: F) -> F: + tool_name = name or f.__name__ + tool_desc = description or (f.__doc__ or "").split("\n")[0] + + if tool_name in _mcp_registry: + raise ValueError(f"Tool '{tool_name}' already registered") + + _mcp_registry[tool_name] = { + "name": tool_name, + "description": tool_desc, + "func": f, + "parameter_descriptions": parameter_descriptions, + } + return f + + if func is not None: + return decorator(func) + return decorator + +def get_registered_tools() -> dict[str, dict[str, Any]]: + """Return deep copy of tool registry.""" + return deepcopy(_mcp_registry) + +def clear_registry() -> None: + """Clear all registered tools (for testing).""" + _mcp_registry.clear() +``` + +**Registry Scope Notes:** +- The registry is **global** within your package. All modules importing from `yourpackage.mcp.decorators` share the same registry. +- If multiple packages copy this pattern, each has its **own isolated registry** (different `_mcp_registry` variables). +- `clear_registry()` removes ALL tools from your package's registry - use it in tests to ensure clean state between test cases. +- Tools are registered at **import time** when Python executes the `@mcp_tool` decorator. + +### Step 3: Create Tools Module + +Wrap workflow functions with JSON serialization: + +```python +# src/yourpackage/mcp/tools.py +from pathlib import Path +from typing import Any + +from yourpackage.mcp.decorators import mcp_tool +from yourpackage.workflows import analysis + +def _to_json(obj: Any) -> Any: + """Convert objects to JSON-serializable format.""" + if obj is None or isinstance(obj, (str, int, float, bool)): + return obj + if isinstance(obj, Path): + return str(obj) + if hasattr(obj, "model_dump"): + return obj.model_dump(mode="json") + if isinstance(obj, dict): + return {k: _to_json(v) for k, v in obj.items()} + if isinstance(obj, (list, tuple)): + return [_to_json(item) for item in obj] + return str(obj) + +# Note: Use domain-specific parameter names for your package +# PLEIADES uses: dataset_path, backend, isotopes +# Your package might use: data_path, method, options, etc. +@mcp_tool( + description="Run analysis on a dataset", + parameter_descriptions={ + "data_path": "Path to the dataset directory", + "method": "Analysis method to use", + }, +) +def run_analysis(data_path: str, method: str = "default") -> dict: + """MCP wrapper for analysis workflow.""" + try: + result = analysis.run_analysis(Path(data_path), method) + return {"status": "success", "data": _to_json(result)} + except Exception as e: + return {"status": "error", "error": str(e)} +``` + +### Step 4: Create Server Module + +Set up FastMCP with auto-discovery: + +```python +# src/yourpackage/mcp/server.py +from typing import Any + +def discover_tools() -> dict[str, dict[str, Any]]: + """Discover all registered MCP tools.""" + from yourpackage.mcp.decorators import get_registered_tools + return get_registered_tools() + +def register_tools(server: Any) -> None: + """Register discovered tools with FastMCP server.""" + tools = discover_tools() + for name, info in tools.items(): + server.tool(name=name, description=info["description"])(info["func"]) + +def get_server() -> Any: + """Get FastMCP server instance.""" + from fastmcp import FastMCP + return FastMCP("yourpackage-mcp") + +def main() -> None: + """Start the MCP server.""" + # Import tools module to trigger registration + import yourpackage.mcp.tools # noqa: F401 + + server = get_server() + register_tools(server) + server.run() +``` + +### Step 5: Create Package Init + +Handle optional dependency gracefully: + +```python +# src/yourpackage/mcp/__init__.py +try: + from fastmcp import FastMCP + MCP_AVAILABLE = True +except ImportError: + MCP_AVAILABLE = False + FastMCP = None + +def check_mcp_available() -> None: + """Raise ImportError if MCP dependencies not installed.""" + if not MCP_AVAILABLE: + raise ImportError( + "MCP dependencies not installed.\n" + "Install with: pip install yourpackage[mcp]" + ) + +__all__ = ["MCP_AVAILABLE", "check_mcp_available"] +if MCP_AVAILABLE: + __all__.append("FastMCP") +``` + +### Step 6: Create Module Entry Point + +```python +# src/yourpackage/mcp/__main__.py +import sys + +def run() -> None: + """Entry point for python -m yourpackage.mcp""" + try: + from yourpackage.mcp.server import main + main() + except ImportError as e: + print(f"Error: {e}") + sys.exit(1) + except KeyboardInterrupt: + print("\nServer stopped.") + sys.exit(0) + +if __name__ == "__main__": + run() +``` + +### Step 7: Configure pyproject.toml + +```toml +[project.optional-dependencies] +# Pin to FastMCP 2.x - version 3.x may have breaking API changes +# The >=2.12.0 minimum ensures Pydantic 2.x compatibility +mcp = ["fastmcp>=2.12.0,<3"] + +[project.scripts] +yourpackage-mcp = "yourpackage.mcp.server:main" +``` + +## Directory Structure + +``` +src/yourpackage/ +├── mcp/ +│ ├── __init__.py # MCP_AVAILABLE check +│ ├── __main__.py # Module entry point +│ ├── decorators.py # @mcp_tool registry +│ ├── server.py # FastMCP server setup +│ └── tools.py # MCP tool wrappers +├── workflows/ +│ ├── __init__.py +│ ├── models.py # Pydantic models +│ └── analysis.py # High-level functions +└── core/ + └── ... # Domain-specific code +``` + +## Design Principles + +### 1. Optional Dependency + +MCP should never be required for core functionality: + +```python +# Good: Graceful degradation +if MCP_AVAILABLE: + from yourpackage.mcp import server + +# Bad: Hard dependency +from yourpackage.mcp import server # Crashes if fastmcp not installed +``` + +### 2. Thin Wrapper Layer + +MCP tools should only: +- Convert types (str → Path) +- Call workflow functions +- Serialize results to JSON +- Handle errors + +They should NOT contain business logic. + +### 3. Consistent Output Format + +All tools return the same structure: + +```python +# Success +{"status": "success", "data": {...}} + +# Error +{"status": "error", "error": "message"} +``` + +### 4. Type Safety + +Use type hints everywhere: + +```python +@mcp_tool( + parameter_descriptions={ + "path": "Dataset path", + "method": "Analysis method", + }, +) +def analyze(path: str, method: str = "default") -> dict: + ... +``` + +### 5. Documentation via Decorators + +Use `parameter_descriptions` to provide AI-friendly documentation: + +```python +@mcp_tool( + description="Analyze neutron resonance data using SAMMY fitting", + parameter_descriptions={ + "dataset_path": "Path to directory containing resonance data files", + "backend": "Execution backend: 'auto', 'local', or 'docker'", + "isotopes": "List of isotopes to analyze (e.g., ['Hf-177', 'Hf-178'])", + }, +) +def analyze_resonance(...): + ... +``` + +## Testing + +### Unit Tests for Decorators + +```python +def test_mcp_tool_registration(): + from yourpackage.mcp.decorators import mcp_tool, get_registered_tools, clear_registry + + clear_registry() + + @mcp_tool(description="Test tool") + def my_tool(x: int) -> int: + return x * 2 + + tools = get_registered_tools() + assert "my_tool" in tools + assert tools["my_tool"]["description"] == "Test tool" + assert my_tool(5) == 10 # Original function works +``` + +### Integration Tests + +```python +def test_tool_execution(): + from yourpackage.mcp.tools import run_analysis + + result = run_analysis("/path/to/test/data") + assert result["status"] in ("success", "error") + if result["status"] == "success": + assert "data" in result +``` + +## Common Pitfalls + +### 1. Import Timing for Decorator Registration + +Import tools module inside `main()`, not at module level. This ensures decorators are executed at the right time: + +```python +# Good: Import inside main() triggers registration before server starts +def main(): + import yourpackage.mcp.tools # Triggers @mcp_tool decorator registration + ... + +# Bad: Module-level import may run before registry is ready +import yourpackage.mcp.tools +def main(): + ... +``` + +### 2. Non-Serializable Returns + +Always convert complex types: + +```python +# Bad: Returns Path object +return {"path": Path("/some/path")} + +# Good: Convert to string +return {"path": str(Path("/some/path"))} +``` + +### 3. Missing Error Handling + +Always wrap workflow calls: + +```python +# Bad: Exception crashes MCP +result = workflow.analyze(path) +return {"data": result} + +# Good: Return error in standard format +try: + result = workflow.analyze(path) + return {"status": "success", "data": result} +except Exception as e: + return {"status": "error", "error": str(e)} +``` + +## Implementation Scope + +The MCP integration consists of a small number of focused modules: + +| Component | Purpose | Complexity | +|-----------|---------|------------| +| `decorators.py` | Registry-based `@mcp_tool` decorator | Low - minimal for basic use, grows with validation features | +| `server.py` | FastMCP server setup and tool discovery | Low to Medium - depends on schema generation needs | +| `tools.py` | MCP wrappers for workflow functions | Scales with number of tools | +| `__init__.py` | Optional dependency check | Minimal | +| `__main__.py` | Module entry point | Minimal | + +A minimal implementation requires only the decorator and server modules. Production implementations (like PLEIADES) add comprehensive validation, detailed documentation, and robust error handling. Refer to the PLEIADES source code for current implementation details. + +## References + +- [MCP Protocol Specification](https://modelcontextprotocol.io/) +- [FastMCP Documentation](https://gofastmcp.com/) +- [PLEIADES MCP Implementation](https://github.com/lanl/PLEIADES/tree/next/src/pleiades/mcp) diff --git a/docs/mcp/manifest-format.md b/docs/mcp/manifest-format.md new file mode 100644 index 00000000..dc9189eb --- /dev/null +++ b/docs/mcp/manifest-format.md @@ -0,0 +1,247 @@ +# Manifest Format Specification + +PLEIADES uses manifest files to describe neutron resonance datasets. Manifests contain metadata about the sample, experiment conditions, and analysis parameters. + +## File Format + +Manifests use YAML frontmatter followed by an optional Markdown body: + +```markdown +--- +name: sample-identifier +description: Human-readable description +version: "1.0.0" +# ... other fields +--- + +# Optional Markdown Content + +Analysis notes, processing instructions, or other documentation. +``` + +## File Naming + +PLEIADES searches for manifest files in this order: + +1. `manifest_intermediate.md` +2. `smcp_manifest.md` +3. `manifest.md` + +Place the manifest file in your dataset root directory. + +## Field Reference + +### Required Fields + +These fields have defaults if not specified: + +| Field | Type | Default | Description | +|-------|------|---------|-------------| +| `name` | string | `"unknown"` | Unique dataset identifier | +| `description` | string | `""` | Human-readable description | +| `version` | string | `"1.0.0"` | Manifest version (semver) | +| `created` | string | `""` | ISO-8601 timestamp (e.g., `2024-06-15T10:30:45Z`) | + +### Experiment Metadata (Optional) + +| Field | Type | Description | +|-------|------|-------------| +| `facility` | string | Facility name (e.g., `"SNS"`, `"LANSCE"`) | +| `beamline` | string | Beamline identifier (e.g., `"VENUS"`) | +| `detector` | string | Detector type (e.g., `"MCP"`) | +| `sample_id` | string | Sample reference number | + +### Primary Isotope + +| Field | Type | Description | +|-------|------|-------------| +| `isotope` | string | Primary isotope for analysis | + +**Accepted formats:** +- Full specification: `"Au-197"`, `"Hf-177"`, `"U-235"` +- Element only: `"Hf"` (uses natural abundance) +- Natural indicator: `"Hf-nat"` (equivalent to element only) + +### Material Properties (Optional) + +Nested object describing physical properties: + +```yaml +material_properties: + density_g_cm3: 19.32 # Required if section present, must be > 0 + atomic_mass_amu: 196.97 # Required if section present, must be > 0 + temperature_k: 293.6 # Optional, must be > 0 if provided +``` + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `density_g_cm3` | float | Yes* | Material density in g/cm³ | +| `atomic_mass_amu` | float | Yes* | Atomic mass in amu | +| `temperature_k` | float | No | Sample temperature in Kelvin | + +*Required if `material_properties` section is present. + +### Isotope Composition + +PLEIADES supports three ways to specify which isotopes to analyze: + +#### Option 1: Explicit Isotope List (Highest Priority) + +```yaml +isotope: Hf-177 +isotopes: + - Hf-176 + - Hf-177 + - Hf-178 +``` + +When `isotopes` is set, only these isotopes are included regardless of natural abundance. Each isotope receives equal weight in the analysis. + +**Validation rules:** +- Each entry must match format: `Element-MassNumber` (e.g., `"Hf-177"`) +- Element symbol: 1-2 characters, first uppercase +- Mass number: numeric only (metastable states like `"U-235m"` are not supported) + +> **Important**: Each isotope you specify must have ENDF parameter data available. If an isotope lacks ENDF data, the analysis will fail with "ENDF parameter file not found for isotope: X". + +**Validation failure behavior**: If isotope format is invalid, a `ValueError` is raised during manifest parsing (e.g., "Invalid isotope format: '177Hf'. Expected format: 'Element-MassNumber' (e.g., 'Hf-177')"). + +#### Option 2: Custom Enrichment + +```yaml +isotope: U-235 +use_natural_abundance: false +enrichment: + U-235: 0.90 + U-238: 0.10 +``` + +| Field | Type | Default | Description | +|-------|------|---------|-------------| +| `use_natural_abundance` | boolean | `true` | Whether to use natural abundances | +| `enrichment` | dict | `null` | Custom isotope composition | + +**Enrichment validation rules:** +- All values must be in range [0, 1] +- Values must sum to ~1.0 (within 1% tolerance) +- Keys must be explicit isotopes (e.g., `"U-235"`, not `"U"`) + +**Validation failure behavior**: If enrichment validation fails, a `ValueError` is raised during manifest parsing with a descriptive message (e.g., "Enrichment values must sum to approximately 1.0"). + +#### Option 3: Natural Abundance (Default) + +```yaml +isotope: Au-197 +use_natural_abundance: true # This is the default +``` + +Uses isotope data from PLEIADES internal database to determine natural abundances. + +### Priority Order + +When determining isotope composition, PLEIADES uses this priority: + +1. **User-specified isotopes parameter** (in `analyze_resonance` call) +2. **Manifest `isotopes` field** (explicit list) +3. **Manifest `enrichment` field** (when `use_natural_abundance: false`) +4. **Natural abundance lookup** (default behavior) + +## Complete Example + +```yaml +--- +name: Hf_foil_sample_001 +description: > + Natural hafnium foil resonance measurement from VENUS beamline. + Sample thickness: 0.5mm, measured at room temperature. +version: "2.0.0" +created: "2024-11-15T14:30:00Z" + +# Experiment metadata +facility: SNS +beamline: VENUS +detector: MCP +sample_id: IPTS-35945-HF001 + +# Analysis parameters +isotope: Hf-177 + +# Material properties +material_properties: + density_g_cm3: 13.31 + atomic_mass_amu: 178.49 + temperature_k: 295.0 + +# Isotope composition - analyze specific isotopes only +isotopes: + - Hf-176 + - Hf-177 + - Hf-178 + - Hf-179 + - Hf-180 +--- + +# Analysis Notes + +## Sample Preparation + +Foil was cleaned with ethanol and mounted in standard sample holder. + +## Expected Results + +Primary resonances expected at: +- Hf-177: 1.1 eV, 2.4 eV, 5.9 eV +- Hf-178: 7.8 eV + +## Post-Processing + +Results will be compared with ENDF/B-VIII.0 library values. +``` + +## Minimal Example + +For quick testing, a minimal manifest: + +```yaml +--- +name: quick-test +isotope: Au-197 +--- +``` + +## Synthetic Data Example + +For documentation and testing without real experimental data: + +```yaml +--- +name: synthetic-gold-sample +description: Synthetic Au-197 data for testing and documentation +version: "1.0.0" +created: "2024-01-01T00:00:00Z" + +facility: Simulated +beamline: Virtual +isotope: Au-197 + +material_properties: + density_g_cm3: 19.32 + atomic_mass_amu: 196.97 + temperature_k: 300.0 + +use_natural_abundance: true +--- + +# Synthetic Dataset + +This manifest describes synthetic/simulated data for testing purposes. +Not derived from actual experimental measurements. +``` + +## Parsing Notes + +- YAML datetime values are automatically converted to ISO format strings +- Empty `material_properties` sections return `null` +- Invalid material properties are logged but don't fail parsing +- All raw YAML data is preserved in `raw_frontmatter` for extensibility +- Markdown horizontal rules (`---`) in the body are handled correctly diff --git a/pixi.lock b/pixi.lock index 5fcfc063..ea929cbf 100644 --- a/pixi.lock +++ b/pixi.lock @@ -13,59 +13,59 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/alabaster-1.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.14-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.11.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.12.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aom-3.9.1-hac33072_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/archspec-0.2.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py312h4c3975b_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py313h07c4f96_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.0.5-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.2-h39aace5_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.4.0-pyh71513ae_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.17.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/backports-1.0-pyhd8ed1ab_5.conda - conda: https://conda.anaconda.org/conda-forge/noarch/backports.tarfile-1.2.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.2-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.2.0-pyh29332c3_4.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.2.0-h82add2a_4.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-h5f6438b_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/blosc-1.21.6-he440d0b_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/boltons-25.0.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-1.1.0-hb03c661_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.1.0-hb03c661_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py312h1289d80_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/brunsli-0.1-he3183e4_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-1.2.0-hed03a55_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py313hf159716_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brunsli-0.1-hd1e3526_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_8.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.5-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/c-blosc2-2.21.3-h4cfbee9_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-blosc2-2.22.0-hc31b594_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.11.12-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/cairo-1.18.4-h3394656_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.10.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py312h35888ee_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.4-py312h4f23490_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.11.12-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py313hf46b229_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.5.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.4-py313h29aa505_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/charls-2.4.2-h59595ed_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cmarkgfm-2024.11.20-py312h66e93f0_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cmarkgfm-2024.11.20-py313h07c4f96_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/conda-25.9.0-py312h7900ff3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/conda-libmamba-solver-25.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/conda-25.11.0-py313h78bf25f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-libmamba-solver-25.11.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-handling-2.4.0-pyh7900ff3_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-streaming-0.12.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/conda-tree-1.1.1-pyhff2d567_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.3.3-py312hd9148b4_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/coverage-7.10.7-py312h8a5da7c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-tree-1.1.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.3.3-py313h7037e92_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/coverage-7.12.0-py313h3dea7bd_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cpp-expected-1.3.1-h171cf75_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.11-py312hd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cryptography-46.0.2-py312hee9fe19_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.10-py313hd8ed1ab_100.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cryptography-46.0.3-py313heb322e3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cyrus-sasl-2.1.28-hd9c7081_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/dav1d-1.2.1-hd590300_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/dbus-1.16.2-h3c4dab8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.17-py312h8285ef7_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/dbus-1.16.2-h24cb091_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.17-py313h5d5ffb9_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.0-pyhd8ed1ab_0.conda @@ -75,30 +75,30 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/dxchange-0.1.8-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/dxfile-0.5-py_1.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/edffile-5.9.4-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.19.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/fmt-11.2.0-h07f6e7f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.20.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fmt-12.0.0-h2b0788b_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.15.0-h7e30c49_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.60.1-py312h8a5da7c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.61.0-py313h3dea7bd_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/freetype-2.14.1-ha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/frozendict-2.4.6-py312h4c3975b_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/frozendict-2.4.7-py313h07c4f96_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.2-hd590300_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.14-hecca717_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/h5py-3.14.0-nompi_py312ha4f8f14_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-12.1.0-h15599e2_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/h5py-3.15.1-nompi_py313h253c126_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-12.2.0-h15599e2_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h2a13503_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.14.6-nompi_h6e4c0c1_103.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.14.6-nompi_h1b119a7_104.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda @@ -106,14 +106,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/id-1.5.0-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.15-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/imagecodecs-2025.8.2-py312h4ecb025_4.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/imagecodecs-2025.11.11-py313hf092b87_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-6.5.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.30.1-pyh82676e8_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.6.0-pyhfa0c392_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.1.0-pyha191276_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.8.0-pyh53cf698_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jaraco.classes-3.4.0-pyhd8ed1ab_2.conda @@ -121,94 +121,94 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/jaraco.functools-4.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jeepney-0.9.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.12.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpatch-1.33-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/jsonpointer-3.0.0-py312h7900ff3_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/jsonpointer-3.0.0-py313h78bf25f_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.25.1-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.25.1-he01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.8.1-pyh31011fe_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.4.9-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.27.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/jxrlib-1.1-hd590300_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/keyring-25.6.0-pyha804496_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/keyring-25.7.0-pyha804496_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.9-py312h0a2e395_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.9-py313hc8edb43_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.21.3-h659f571_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.17-h717163a_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.44-ha97dd6f_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45-default_hbd61a6d_104.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h0aef613_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libaec-1.1.4-h3f801dc_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarchive-3.8.1-gpl_h7be2006_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarchive-3.8.2-gpl_h7be2006_100.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libavif16-1.3.0-h6395336_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-36_h4a7cf45_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.1.0-hb03c661_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.1.0-hb03c661_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.1.0-hb03c661_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-36_h0358290_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libclang-cpp21.1-21.1.2-default_h99862b1_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libclang13-21.1.2-default_h746c552_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-4_h4a7cf45_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-4_h0358290_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libclang-cpp21.1-21.1.7-default_h99862b1_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libclang13-21.1.7-default_h746c552_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-hb8b1518_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.14.1-h332b0f4_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.24-h86f0d12_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.17.0-h4e3cde8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.25-h17f619e_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libdrm-2.4.125-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libegl-1.7.0-ha4b6fd6_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.1-hecca717_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.3-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h9ec8514_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.14.1-ha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype6-2.14.1-h73754d4_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-h767d61c_6.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_6.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_6.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-hcd61629_6.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_15.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_15.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_15.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_15.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgl-1.7.0-ha4b6fd6_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.86.0-h1fed272_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.86.2-h32235b2_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libglvnd-1.7.0-ha4b6fd6_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libglx-1.7.0-ha4b6fd6_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-h767d61c_6.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_15.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libhwy-1.3.0-h4c17acf_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.1.0-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libjxl-0.11.1-h6cb5226_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-36_h47877c9_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libllvm21-21.1.2-hf7376ad_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.1.2-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libjxl-0.11.1-hf08fa70_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-4_h47877c9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libllvm21-21.1.7-hf7376ad_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libmamba-2.3.2-hae34dd5_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libmambapy-2.3.2-py312he1eb750_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmamba-2.4.0-hed7d790_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmambapy-2.4.0-py313hb20a507_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.9.3-nompi_h11f7409_103.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.67.0-had1ee68_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libntlm-1.8-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.30-pthreads_h94d23a6_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.30-pthreads_h94d23a6_4.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libopengl-1.7.0-ha4b6fd6_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libpciaccess-0.18-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.50-h421ea60_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libpq-18.0-h3675c94_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.51-h421ea60_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpq-18.1-h5c52fec_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.20-h4ab18f5_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsolv-0.7.35-h9463b59_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.50.4-h0c1763c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.51.1-h0c1763c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.1-hcf80075_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h8f9b012_6.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-h4852527_6.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.1-h8261f1e_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.2-he9a06e4_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libvulkan-loader-1.4.328.0-h5279c79_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_15.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_15.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.1-h9d88235_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.2-h5347b49_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libvulkan-loader-1.4.328.1-h5279c79_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.6.0-hd42ef1d_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.17.0-h8a09558_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.11.0-hca5e8e5_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.0-ha9997c6_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.0-h26afc86_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.13.1-hca5e8e5_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.1-ha9997c6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.1-h26afc86_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxslt-1.1.43-h711ed8c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzip-1.11.2-h6991a6a_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda @@ -217,150 +217,846 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lzo-2.10-h280c20c_1002.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.0.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py312h8a5da7c_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.10.6-py312h7900ff3_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.10.6-py312he3d6523_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py313h3dea7bd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.10.8-py313h78bf25f_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.10.8-py313h683a580_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/menuinst-2.3.1-py312h7900ff3_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/menuinst-2.4.1-py313h78bf25f_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.1.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/more-itertools-10.8.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/more-itertools-10.8.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/msgpack-python-1.1.2-py313h7037e92_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.6-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.6-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.7.2-nompi_py312hf6400b3_104.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.5-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/nh3-0.3.1-py310h1570de5_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/nlohmann_json-3.11.3-he02047a_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.7.3-nompi_py313hfae5b86_100.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.6-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/nh3-0.3.2-py310h1570de5_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nlohmann_json-abi-3.12.0-h0f90c79_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.9.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.3.3-py312h33ff503_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.3.5-py313hf6604e3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/objprint-0.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.4-h55fea9a_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openldap-2.6.10-he970967_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.4-h26f9b46_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/orjson-3.11.3-py312h868fb18_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.0-h26f9b46_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/orjson-3.11.4-py313h843e2db_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pandas-2.3.3-py312hf79963d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pandas-2.3.3-py313h08cd8bf_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.5-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.46-h1321c63_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pickleshare-0.7.5-pyhd8ed1ab_1004.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-11.3.0-py312h7b42cdd_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh8b19718_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-12.0.0-py313h80991f8_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.3-pyh145f28c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pixman-0.46.4-h54a6638_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.5.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-4.3.0-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-4.5.0-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.23.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.1.0-py312h4c3975b_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.1.3-py313h54dd161_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-hb9d3cd8_1002.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-4-hd8ed1ab_3.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/pycosat-0.6.6-py312h4c3975b_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-11-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pycosat-0.6.6-py313h07c4f96_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.11.10-pyh3cfb1c2_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.33.2-py312h680f630_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.12.5-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.41.5-py313h843e2db_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.2.5-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject_hooks-1.2.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pyside6-6.9.3-py312h9da60e5_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyside6-6.9.3-py313h85046ba_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.4.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.0.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-cov-7.0.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-repeat-0.9.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.8.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.11-h9e4cc4f_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.10-hc97d973_100_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-build-1.3.0-pyhff2d567_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.12.11-hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.10-h4df99d1_100.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2025.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py313h3dea7bd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py312hfb55c3c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/qhull-2020.2-h434a139_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/qt6-main-6.9.3-h5c1c036_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/rav1e-0.7.1-h8fae777_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/readme_renderer-44.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/reproc-14.2.5.post0-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/reproc-cpp-14.2.5.post0-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-toolbelt-1.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-2.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.2.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/roman-numerals-py-3.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py313h843e2db_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ruamel.yaml-0.18.16-py313h07c4f96_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ruamel.yaml.clib-0.2.14-py313h07c4f96_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ruff-0.14.8-h813ae00_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.16.3-py313h11c21cd_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/secretstorage-3.4.1-py313h78bf25f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.3-pyh0d859eb_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/simdjson-4.2.2-hb700be7_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/snappy-1.2.2-h03e3b7b_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-3.0.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/spefile-1.6.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-8.2.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx_rtd_theme-3.0.2-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-2.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-devhelp-2.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.1.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-jquery-4.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-jsmath-1.0.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-qthelp-2.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-1.1.10-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/svt-av1-3.1.2-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh0d859eb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tifffile-2025.10.16-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.5.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_ha0e22de_103.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.2-py313h07c4f96_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/truststore-0.10.3-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/twine-6.2.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py313h7037e92_6.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/versioningit-3.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.35.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/viztracer-1.1.1-py313h6459d8b_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/wayland-1.24.0-hd6090a7_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.14-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.1-h4f16b4b_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-cursor-0.1.6-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-hb711507_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.1-hb711507_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.10-hb711507_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-wm-0.4.2-hb711507_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xkeyboard-config-2.46-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libice-1.1.2-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.6-he73a12e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.8.12-h4f16b4b_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxcomposite-0.4.6-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxcursor-1.2.3-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdamage-1.1.6-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.6-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxfixes-6.0.2-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxi-1.8.2-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrandr-1.5.4-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.12-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxtst-1.2.5-hb9d3cd8_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxxf86vm-1.1.6-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-cpp-0.8.0-h3f2d84a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zeromq-4.3.5-h387f397_9.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zfp-1.0.1-h909a3a2_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.1-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-ng-2.3.2-h54a6638_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.25.0-py313h54dd161_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda + - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/0d/4764669bdf47bd472899b3d3db91fffbe925c8e3038ec591a2fd2ad6a14d/aiohttp-3.13.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c3/1c/f06ad85180e7dd9855aa5ede901bfc2be858d7bee17d4e978a14c0ecec14/astropy-7.2.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/02/48/501f82ad7806e57bed6e2a4b3cca0897302f51ddfb7ac24891fe6a200360/astropy_iers_data-0.2025.12.1.0.45.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b4/ba/ff3c536ab50e179a4e1eef0ea3de7b878b6b81629ded651f0af7156a0f9c/bioblend-1.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/fb/fe/301e0936b79bcab4cacc7548bf2853fc28dced0a578bab1f7ef53c9aa75b/imageio-2.37.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/83/60/d497a310bde3f01cb805196ac61b7ad6dc5dcf8dce66634dc34364b20b4f/lazy_loader-0.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c6/2d/f0b184fa88d6630aa267680bdb8623fb69cb0d024b8c6f0d23f9a0f406d3/multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ad/0a/8bf67f63245e39626bc69357f932005d97ee2ac48e6aff897a71142004ec/nova_galaxy-0.7.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e5/e0/050018d855d26d3c0b4a7d1b2ed692be758ce276d8289e2a2b44ba1014a5/pyerfa-2.0.1.5-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/cd/9b/c3da56a145f52cd61a68b8465d6a29d9503bc45bc993bb45e84371c97d94/scikit_image-0.25.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/78/17/853354204e1ca022d6b7d011ca7f3206c4f8faa3cc743e92609b49c1d83f/tinydb-4.8.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/32/8e/b94ec25fcd4384de2a1f5e0403284f005ec743c04bafdb6698c851df5c6d/tuspy-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/11/c9/cd8538dc2e7727095e0c1d867bad1e40c98f37763e6d995c1939f5fdc7b1/yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: ./ + osx-arm64: + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/_openmp_mutex-4.5-7_kmp_llvm.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/alabaster-1.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.12.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aom-3.9.1-h7bae524_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/archspec-0.2.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py312h4409184_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.0.5-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.4.0-pyh71513ae_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.17.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports-1.0-pyhd8ed1ab_5.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.tarfile-1.2.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-h5f6438b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/blosc-1.21.6-h7dd00d9_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/boltons-25.0.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-1.2.0-h7d5ae5b_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-bin-1.2.0-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.2.0-py312h0dfefe5_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brunsli-0.1-he0dfb12_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_8.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.5-h5505292_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-blosc2-2.22.0-hb5916c8_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.11.12-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.11.12-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py312h1b4d9a2_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.5.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cftime-1.6.4-py312hc7121bb_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/charls-2.4.2-h13dd4ca_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cmarkgfm-2024.11.20-py312h163523d_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/conda-25.11.0-py312h81bd7bf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-libmamba-solver-25.11.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-handling-2.4.0-pyh7900ff3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-streaming-0.12.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-tree-1.1.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/contourpy-1.3.3-py312h84eede6_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/coverage-7.12.0-py312h5748b74_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cpp-expected-1.3.1-h4f10f1e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.12-py312hd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/dav1d-1.2.1-hb547adb_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.17-py312h56d30c9_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distro-1.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/docutils-0.21.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/dxchange-0.1.8-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/dxfile-0.5-py_1.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/edffile-5.9.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.20.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/fmt-12.0.0-h669d743_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/fonttools-4.61.0-py312h5748b74_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/freetype-2.14.1-hce30654_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/frozendict-2.4.7-py312h4409184_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/giflib-5.2.2-h93a5062_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/h5py-3.15.1-nompi_py312h4eecd6b_101.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdf4-4.2.15-h2ee6834_7.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdf5-1.14.6-nompi_hd3baa01_104.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/id-1.5.0-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.15-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/imagecodecs-2025.11.11-py312h9d9c187_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-6.5.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.1.0-pyh5552912_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.8.0-pyh53cf698_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jaraco.classes-3.4.0-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jaraco.context-6.0.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jaraco.functools-4.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.12.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpatch-1.33-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/jsonpointer-3.0.0-py312h81bd7bf_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.25.1-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.25.1-he01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.6.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/jxrlib-1.1-h93a5062_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/keyring-25.7.0-pyh534df25_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/kiwisolver-1.4.9-py312hd8c8125_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.21.3-h237132a_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lcms2-2.17-h7eeda09_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lerc-4.0.0-hd64df32_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libaec-1.1.4-h51d1e36_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarchive-3.8.2-gpl_h46575ef_100.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libavif16-1.3.0-hb06b76e_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-4_h51639a9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.2.0-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.2.0-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.2.0-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-4_hb0561ab_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcurl-8.17.0-hdece5d2_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-21.1.7-hf598326_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libdeflate-1.25-hc11a715_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20250104-pl5321hafb1f1b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h93a5062_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.3-haf25636_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-he5f378a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype-2.14.1-hce30654_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype6-2.14.1-h6da58f4_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_15.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_15.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_15.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libhwy-1.3.0-h48b13b8_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libiconv-1.18-h23cfdf5_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libjpeg-turbo-3.1.2-hc919400_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libjxl-0.11.1-h3dcb153_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.11.0-4_hd9741b5_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.1-h39f12f2_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmamba-2.4.0-h7516003_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmambapy-2.4.0-py312ha7ae334_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnetcdf-4.9.3-nompi_h80c4520_103.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.67.0-hc438710_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.30-openmp_ha158390_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpng-1.6.51-hfab5511_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsodium-1.0.20-h99b78c6_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsolv-0.7.35-h5f525b2_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.51.1-h9a5124b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libssh2-1.11.1-h1590b86_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libtiff-4.7.1-h4030677_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libwebp-base-1.6.0-h07db88b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxcb-1.17.0-hdb1d25a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-16-2.15.1-h8eac4d7_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-2.15.1-hba2cd1d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzip-1.11.2-h1336266_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzopfli-1.0.3-h9f76cd9_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-21.1.7-h4a912ad_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/loguru-0.7.3-pyh707e725_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lz4-c-1.10.0-h286801f_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lzo-2.10-h925e9cb_1002.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.0.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py312h5748b74_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-3.10.8-py312h1f38498_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-base-3.10.8-py312h605b88b_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/menuinst-2.4.1-py312h81bd7bf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.1.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/more-itertools-10.8.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgpack-python-1.1.2-py312h84eede6_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.6-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/netcdf4-1.7.3-nompi_py312h947358d_100.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.6-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nh3-0.3.2-py310h06fc29a_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nlohmann_json-abi-3.12.0-h0f90c79_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.9.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.3.5-py312h85ea64e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/objprint-0.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openjpeg-2.5.4-hbfb3c88_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.0-h5503f6c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/orjson-3.11.4-py312h6ef9ec0_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pandas-2.3.3-py312h5978115_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.5-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pillow-12.0.0-py312h261a3e5_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.3-pyh8b19718_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.5.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-4.5.0-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.23.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.1.3-py312h37e1c23_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pthread-stubs-0.4-hd74edd7_1002.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-11-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pycosat-0.6.6-py312h163523d_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.12.5-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.41.5-py312h6ef9ec0_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-12.1-py312h19bbe71_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-12.1-py312h1de3e18_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.2.5-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject_hooks-1.2.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.0.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-cov-7.0.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-repeat-0.9.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.8.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.12.12-h18782d2_1_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-build-1.3.0-pyhff2d567_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.12.12-hd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2025.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-8_cp312.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py312h8a5da7c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py312h5748b74_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py312hd65ceae_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/qhull-2020.2-h420ef59_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rav1e-0.7.1-h0716509_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.2-h1d1bf99_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/readme_renderer-44.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/reproc-14.2.5.post0-h5505292_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/reproc-cpp-14.2.5.post0-h286801f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-toolbelt-1.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-2.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.2.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/roman-numerals-py-3.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.30.0-py312h6ef9ec0_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruamel.yaml-0.18.16-py312h4409184_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruamel.yaml.clib-0.2.14-py312h4409184_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruff-0.14.8-h382de68_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/scipy-1.16.3-py312ha6bbf71_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.3-pyh31c8845_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/simdjson-4.2.2-ha7d2532_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/snappy-1.2.2-hada39a4_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-3.0.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/spefile-1.6.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-8.2.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx_rtd_theme-3.0.2-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-2.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-devhelp-2.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.1.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-jquery-4.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-jsmath-1.0.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-qthelp-2.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-1.1.10-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/svt-av1-3.1.2-h12ba402_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh31c8845_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tifffile-2025.10.16-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.5.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h892fb3f_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.2-py312h4409184_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/truststore-0.10.3-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/twine-6.2.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ukkonen-1.0.1-py312ha0dd364_6.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/unicodedata2-17.0.0-py312h4409184_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/versioningit-3.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.35.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/viztracer-1.1.1-py312h4409184_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.14-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxau-1.0.12-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxdmcp-1.1.5-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h925e9cb_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-cpp-0.8.0-ha1acc90_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zeromq-4.3.5-h888dc83_9.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zfp-1.0.1-hfd287c0_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.1-h8359307_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-ng-2.2.5-h3470cca_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstandard-0.25.0-py312h37e1c23_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda + - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/28/66/d35dcfea8050e131cdd731dff36434390479b4045a8d0b9d7111b0a968f1/aiohttp-3.13.2-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a6/ba/3418133ba144dfcd1530bca5a6b695f4cdd21a8abaaa2ac4e5450d11b028/astropy-7.2.0-cp311-abi3-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/02/48/501f82ad7806e57bed6e2a4b3cca0897302f51ddfb7ac24891fe6a200360/astropy_iers_data-0.2025.12.1.0.45.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b4/ba/ff3c536ab50e179a4e1eef0ea3de7b878b6b81629ded651f0af7156a0f9c/bioblend-1.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/fb/fe/301e0936b79bcab4cacc7548bf2853fc28dced0a578bab1f7ef53c9aa75b/imageio-2.37.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/83/60/d497a310bde3f01cb805196ac61b7ad6dc5dcf8dce66634dc34364b20b4f/lazy_loader-0.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/93/c4f67a436dd026f2e780c433277fff72be79152894d9fc36f44569cab1a6/multidict-6.7.0-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/ad/0a/8bf67f63245e39626bc69357f932005d97ee2ac48e6aff897a71142004ec/nova_galaxy-0.7.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0a/b6/5c9a0e42df4d00bfb4a3cbbe5cf9f54260300c88a0e9af1f47ca5ce17ac0/propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/11/4a/31a363370478b63c6289a34743f2ba2d3ae1bd8223e004d18ab28fb92385/pyerfa-2.0.1.5-cp39-abi3-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/ce/e6/93bebe1abcdce9513ffec01d8af02528b4c41fb3c1e46336d70b9ed4ef0d/scikit_image-0.25.2-cp312-cp312-macosx_12_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/78/17/853354204e1ca022d6b7d011ca7f3206c4f8faa3cc743e92609b49c1d83f/tinydb-4.8.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/32/8e/b94ec25fcd4384de2a1f5e0403284f005ec743c04bafdb6698c851df5c6d/tuspy-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ba/f5/0601483296f09c3c65e303d60c070a5c19fcdbc72daa061e96170785bc7d/yarl-1.22.0-cp312-cp312-macosx_11_0_arm64.whl + - pypi: ./ + jupyter: + channels: + - url: https://conda.anaconda.org/conda-forge/ + indexes: + - https://pypi.org/simple + packages: + linux-64: + - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.14-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.12.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aom-3.9.1-hac33072_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/archspec-0.2.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py313h07c4f96_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.0.5-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.2-h39aace5_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.4.0-pyh71513ae_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.17.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-h5f6438b_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/blosc-1.21.6-he440d0b_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/boltons-25.0.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-1.2.0-hed03a55_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py313hf159716_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brunsli-0.1-hd1e3526_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_8.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.5-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-blosc2-2.22.0-hc31b594_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.11.12-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/cairo-1.18.4-h3394656_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.11.12-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py313hf46b229_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.5.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.4-py313h29aa505_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/charls-2.4.2-h59595ed_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/conda-25.11.0-py313h78bf25f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-libmamba-solver-25.11.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-handling-2.4.0-pyh7900ff3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-streaming-0.12.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-tree-1.1.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.3.3-py313h7037e92_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cpp-expected-1.3.1-h171cf75_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.10-py313hd8ed1ab_100.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cyrus-sasl-2.1.28-hd9c7081_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/dav1d-1.2.1-hd590300_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/dbus-1.16.2-h24cb091_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.17-py313h5d5ffb9_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distro-1.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/double-conversion-3.3.1-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/dxchange-0.1.8-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/dxfile-0.5-py_1.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/edffile-5.9.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.20.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fmt-12.0.0-h2b0788b_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.15.0-h7e30c49_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.61.0-py313h3dea7bd_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/freetype-2.14.1-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/frozendict-2.4.7-py313h07c4f96_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.2-hd590300_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.14-hecca717_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/h5py-3.15.1-nompi_py313h253c126_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-12.2.0-h15599e2_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h2a13503_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.14.6-nompi_h1b119a7_104.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.15-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/imagecodecs-2025.11.11-py313hf092b87_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.1.0-pyha191276_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipympl-0.9.8-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.8.0-pyh53cf698_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipywidgets-8.1.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.12.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpatch-1.33-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/jsonpointer-3.0.0-py313h78bf25f_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.25.1-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.25.1-he01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.6.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_widgets-3.0.16-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/jxrlib-1.1-hd590300_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.9-py313hc8edb43_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.21.3-h659f571_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.17-h717163a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45-default_hbd61a6d_104.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h0aef613_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libaec-1.1.4-h3f801dc_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarchive-3.8.2-gpl_h7be2006_100.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libavif16-1.3.0-h6395336_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-4_h4a7cf45_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-4_h0358290_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libclang-cpp21.1-21.1.7-default_h99862b1_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libclang13-21.1.7-default_h746c552_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-hb8b1518_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.17.0-h4e3cde8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.25-h17f619e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libdrm-2.4.125-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libegl-1.7.0-ha4b6fd6_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.3-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h9ec8514_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.14.1-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype6-2.14.1-h73754d4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_15.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_15.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_15.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_15.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgl-1.7.0-ha4b6fd6_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.86.2-h32235b2_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libglvnd-1.7.0-ha4b6fd6_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libglx-1.7.0-ha4b6fd6_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_15.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libhwy-1.3.0-h4c17acf_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.1.2-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libjxl-0.11.1-hf08fa70_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-4_h47877c9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libllvm21-21.1.7-hf7376ad_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmamba-2.4.0-hed7d790_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmambapy-2.4.0-py313hb20a507_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.9.3-nompi_h11f7409_103.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.67.0-had1ee68_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libntlm-1.8-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.30-pthreads_h94d23a6_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopengl-1.7.0-ha4b6fd6_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpciaccess-0.18-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.51-h421ea60_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpq-18.1-h5c52fec_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.20-h4ab18f5_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsolv-0.7.35-h9463b59_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.51.1-h0c1763c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.1-hcf80075_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_15.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_15.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.1-h9d88235_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.2-h5347b49_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libvulkan-loader-1.4.328.1-h5279c79_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.6.0-hd42ef1d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.17.0-h8a09558_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.13.1-hca5e8e5_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.1-ha9997c6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.1-h26afc86_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxslt-1.1.43-h711ed8c_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzip-1.11.2-h6991a6a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzopfli-1.0.3-h9c3ff4c_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/loguru-0.7.3-pyh707e725_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lzo-2.10-h280c20c_1002.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py313h3dea7bd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.10.8-py313h78bf25f_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.10.8-py313h683a580_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/menuinst-2.4.1-py313h78bf25f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.1.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/msgpack-python-1.1.2-py313h7037e92_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.6-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.7.3-nompi_py313hfae5b86_100.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.6-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nlohmann_json-abi-3.12.0-h0f90c79_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.9.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.3.5-py313hf6604e3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/objprint-0.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.4-h55fea9a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openldap-2.6.10-he970967_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.0-h26f9b46_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/orjson-3.11.4-py313h843e2db_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pandas-2.3.3-py313h08cd8bf_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.5-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.46-h1321c63_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-12.0.0-py313h80991f8_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.3-pyh145f28c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pixman-0.46.4-h54a6638_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.5.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-4.5.0-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.23.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.1.3-py313h54dd161_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-hb9d3cd8_1002.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-11-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pycosat-0.6.6-py313h07c4f96_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.12.5-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.41.5-py313h843e2db_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.2.5-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyside6-6.9.3-py313h85046ba_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.10-hc97d973_100_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.10-h4df99d1_100.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2025.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py313h3dea7bd_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py312hfb55c3c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/qhull-2020.2-h434a139_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/qt6-main-6.9.3-h5c1c036_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/qt6-main-6.9.3-h5c1c036_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/rav1e-0.7.1-h8fae777_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/readme_renderer-44.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.36.2-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/reproc-14.2.5.post0-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/reproc-cpp-14.2.5.post0-h5888daf_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-toolbelt-1.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-2.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.1.0-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/roman-numerals-py-3.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.27.1-py312h868fb18_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ruamel.yaml-0.18.15-py312h4c3975b_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ruamel.yaml.clib-0.2.12-py312h4c3975b_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ruff-0.13.3-ha3a3aed_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.16.2-py312h7a1785b_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/secretstorage-3.4.0-py312h7900ff3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py313h843e2db_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ruamel.yaml-0.18.16-py313h07c4f96_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ruamel.yaml.clib-0.2.14-py313h07c4f96_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ruff-0.14.8-h813ae00_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.16.3-py313h11c21cd_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.3-pyh0d859eb_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/simdjson-4.0.7-hb700be7_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/simdjson-4.2.2-hb700be7_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/snappy-1.2.2-h03e3b7b_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-3.0.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/snappy-1.2.2-h03e3b7b_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/spefile-1.6.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-8.2.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx_rtd_theme-3.0.2-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-2.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-devhelp-2.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.1.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-jquery-4.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-jsmath-1.0.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-qthelp-2.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-1.1.10-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/svt-av1-3.1.2-hecca717_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh0d859eb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tifffile-2025.10.4-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_hd72426e_102.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.2-py312h4c3975b_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tifffile-2025.10.16-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.5.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_ha0e22de_103.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.2-py313h07c4f96_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/truststore-0.10.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/twine-6.2.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/types-python-dateutil-2.9.0.20250822-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py312h68727a3_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-16.0.0-py312h4c3975b_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py313h7037e92_6.conda - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/versioningit-3.3.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.34.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/viztracer-1.0.4-py312h0f154a2_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/wayland-1.24.0-h3e06ad9_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.35.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/viztracer-1.1.1-py313h6459d8b_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/wayland-1.24.0-hd6090a7_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.14-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-24.11.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.8.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/widgetsnbextension-4.0.15-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.1-h4f16b4b_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-cursor-0.1.5-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-cursor-0.1.6-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-hb711507_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.1-hb711507_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.10-hb711507_0.conda @@ -369,11 +1065,11 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libice-1.1.2-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.6-he73a12e_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.8.12-h4f16b4b_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxcomposite-0.4.6-hb9d3cd8_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxcursor-1.2.3-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdamage-1.1.6-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.6-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxfixes-6.0.2-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxi-1.8.2-hb9d3cd8_0.conda @@ -385,363 +1081,316 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-cpp-0.8.0-h3f2d84a_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zeromq-4.3.5-h387f397_9.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zfp-1.0.1-h909a3a2_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.1-hb9d3cd8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-ng-2.2.5-hde8ca8f_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.25.0-py312h5253ce2_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-ng-2.3.2-h54a6638_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.25.0-py313h54dd161_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/14/51/d0c1701a79fcb0109cff5304da16226581569b89a282d8e7f1549a7e3ec0/aiohttp-3.13.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f7/0d/4764669bdf47bd472899b3d3db91fffbe925c8e3038ec591a2fd2ad6a14d/aiohttp-3.13.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fb/69/a34f20db7146912f25e2487c5283f1ae2aed5d24f615fa976439ece35f7e/astropy-7.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/c5/38/be921eaf8fc67333fd2c2657ea965103ccc4050c29a66d5fd5f3db881c0f/astropy_iers_data-0.2025.10.6.0.35.25-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/84/19/529e6ade85e681cb84d5e5c6501f2492f17ec2c8f8fe246a3898ffcb0e2c/bioblend-1.6.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/cb/bd/b394387b598ed84d8d0fa90611a90bee0adc2021820ad5729f7ced74a8e2/imageio-2.37.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c3/1c/f06ad85180e7dd9855aa5ede901bfc2be858d7bee17d4e978a14c0ecec14/astropy-7.2.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/02/48/501f82ad7806e57bed6e2a4b3cca0897302f51ddfb7ac24891fe6a200360/astropy_iers_data-0.2025.12.1.0.45.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b4/ba/ff3c536ab50e179a4e1eef0ea3de7b878b6b81629ded651f0af7156a0f9c/bioblend-1.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/fb/fe/301e0936b79bcab4cacc7548bf2853fc28dced0a578bab1f7ef53c9aa75b/imageio-2.37.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/60/d497a310bde3f01cb805196ac61b7ad6dc5dcf8dce66634dc34364b20b4f/lazy_loader-0.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0d/e2/9baffdae21a76f77ef8447f1a05a96ec4bc0a24dae08767abc0a2fe680b8/multidict-6.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/c6/2d/f0b184fa88d6630aa267680bdb8623fb69cb0d024b8c6f0d23f9a0f406d3/multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ad/0a/8bf67f63245e39626bc69357f932005d97ee2ac48e6aff897a71142004ec/nova_galaxy-0.7.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/84/5e/036d2b105927ae7f179346c9911d16c345f4dba5a19a063f23a8d28acfbd/propcache-0.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e5/e0/050018d855d26d3c0b4a7d1b2ed692be758ce276d8289e2a2b44ba1014a5/pyerfa-2.0.1.5-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/6b/b5/b75527c0f9532dd8a93e8e7cd8e62e547b9f207d4c11e24f0006e8646b36/scikit_image-0.25.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cd/9b/c3da56a145f52cd61a68b8465d6a29d9503bc45bc993bb45e84371c97d94/scikit_image-0.25.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/78/17/853354204e1ca022d6b7d011ca7f3206c4f8faa3cc743e92609b49c1d83f/tinydb-4.8.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/8e/b94ec25fcd4384de2a1f5e0403284f005ec743c04bafdb6698c851df5c6d/tuspy-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/db/0f/0d52c98b8a885aeda831224b78f3be7ec2e1aa4a62091f9f9188c3c65b56/yarl-1.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/11/c9/cd8538dc2e7727095e0c1d867bad1e40c98f37763e6d995c1939f5fdc7b1/yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: ./ osx-arm64: + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/_openmp_mutex-4.5-7_kmp_llvm.conda - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/alabaster-1.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.11.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aom-3.9.1-h7bae524_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.12.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/archspec-0.2.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py313h6535dbc_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py312h4409184_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.0.5-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.4.0-pyh71513ae_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.17.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/backports-1.0-pyhd8ed1ab_5.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/backports.tarfile-1.2.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.2-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.2.0-pyh29332c3_4.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.2.0-h82add2a_4.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-h5f6438b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/blosc-1.21.6-h7dd00d9_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/boltons-25.0.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-1.1.0-h6caf38d_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-bin-1.1.0-h6caf38d_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.1.0-py313hb4b7877_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brunsli-0.1-h97083b6_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-1.2.0-h7d5ae5b_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-bin-1.2.0-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.2.0-py312h0dfefe5_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_8.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.5-h5505292_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-blosc2-2.21.3-hb5916c8_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.11.12-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.10.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py313h89bd988_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cftime-1.6.4-py313h2732efb_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/charls-2.4.2-h13dd4ca_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cmarkgfm-2024.11.20-py313h90d716c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.11.12-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py312h1b4d9a2_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.5.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cftime-1.6.4-py312hc7121bb_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/conda-25.9.0-py313h8f79df9_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/conda-libmamba-solver-25.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/conda-25.11.0-py312h81bd7bf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-libmamba-solver-25.11.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-handling-2.4.0-pyh7900ff3_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-streaming-0.12.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/conda-tree-1.1.1-pyhff2d567_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/contourpy-1.3.3-py313hc50a443_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/coverage-7.10.7-py313h7d74516_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-tree-1.1.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/contourpy-1.3.3-py312h84eede6_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cpp-expected-1.3.1-h4f10f1e_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.7-py313hd8ed1ab_100.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/dav1d-1.2.1-hb547adb_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.17-py313hc37fe24_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.12-py312hd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.17-py312h56d30c9_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/distro-1.9.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/docutils-0.21.2-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/dxchange-0.1.8-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/dxfile-0.5-py_1.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/edffile-5.9.4-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.19.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/fmt-11.2.0-h440487c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/fonttools-4.60.1-py313h7d74516_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.20.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/fmt-12.0.0-h669d743_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/fonttools-4.61.0-py312h5748b74_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/freetype-2.14.1-hce30654_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/frozendict-2.4.6-py313hcdf3177_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/giflib-5.2.2-h93a5062_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/frozendict-2.4.7-py312h4409184_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/h5py-3.14.0-nompi_py313ha10fd41_101.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/h5py-3.15.1-nompi_py312h4eecd6b_101.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdf4-4.2.15-h2ee6834_7.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdf5-1.14.6-nompi_he65715a_103.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdf5-1.14.6-nompi_hd3baa01_104.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-75.1-hfee45f7_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/id-1.5.0-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.15-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/imagecodecs-2025.8.2-py313h8be30f9_4.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/imagecodecs-lite-2019.12.3-py312hbebd99a_8.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-6.5.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.30.1-pyh92f572d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.6.0-pyhfa0c392_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.1.0-pyh5552912_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipympl-0.9.8-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.8.0-pyh53cf698_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipywidgets-8.1.8-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jaraco.classes-3.4.0-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jaraco.context-6.0.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jaraco.functools-4.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.12.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpatch-1.33-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/jsonpointer-3.0.0-py313h8f79df9_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/jsonpointer-3.0.0-py312h81bd7bf_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.25.1-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.25.1-he01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.8.1-pyh31011fe_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.4.9-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.27.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/jxrlib-1.1-h93a5062_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/keyring-25.6.0-pyh534df25_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/kiwisolver-1.4.9-py313hf88c9ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_widgets-3.0.16-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/kiwisolver-1.4.9-py312hd8c8125_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.21.3-h237132a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lcms2-2.17-h7eeda09_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lerc-4.0.0-hd64df32_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libaec-1.1.4-h51d1e36_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarchive-3.8.1-gpl_h46575ef_101.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libavif16-1.3.0-hb06b76e_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.9.0-36_h51639a9_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.1.0-h6caf38d_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.1.0-h6caf38d_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.1.0-h6caf38d_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.9.0-36_hb0561ab_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcurl-8.14.1-h73640d1_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-21.1.2-hf598326_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libdeflate-1.24-h5773f1b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarchive-3.8.2-gpl_h46575ef_100.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-4_h51639a9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.2.0-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.2.0-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.2.0-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-4_hb0561ab_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcurl-8.17.0-hdece5d2_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-21.1.7-hf598326_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libdeflate-1.25-hc11a715_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20250104-pl5321hafb1f1b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h93a5062_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.1-hec049ff_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.4.6-h1da3d7d_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.3-haf25636_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-he5f378a_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype-2.14.1-hce30654_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype6-2.14.1-h6da58f4_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-hfcf01ff_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-h742603c_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libhwy-1.3.0-h48b13b8_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_15.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_15.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_15.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libiconv-1.18-h23cfdf5_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libjpeg-turbo-3.1.0-h5505292_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libjxl-0.11.1-h7274d02_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.9.0-36_hd9741b5_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libjpeg-turbo-3.1.2-hc919400_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.11.0-4_hd9741b5_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.1-h39f12f2_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmamba-2.3.2-hbdbd6ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmambapy-2.3.2-py313h904c928_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h5505292_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmamba-2.4.0-h7516003_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmambapy-2.4.0-py312ha7ae334_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnetcdf-4.9.3-nompi_h80c4520_103.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.67.0-hc438710_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.30-openmp_h60d53f8_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpng-1.6.50-h280e0eb_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.30-openmp_ha158390_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpng-1.6.51-hfab5511_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsodium-1.0.20-h99b78c6_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsolv-0.7.35-h5f525b2_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.50.4-h4237e3c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.51.1-h9a5124b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libssh2-1.11.1-h1590b86_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libtiff-4.7.1-h7dc4979_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libtiff-4.7.1-h4030677_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libwebp-base-1.6.0-h07db88b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxcb-1.17.0-hdb1d25a_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-16-2.15.0-h0ff4647_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-2.15.0-h9329255_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-16-2.15.1-h8eac4d7_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-2.15.1-hba2cd1d_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzip-1.11.2-h1336266_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzopfli-1.0.3-h9f76cd9_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-21.1.2-h4a912ad_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-21.1.7-h4a912ad_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/loguru-0.7.3-pyh707e725_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lz4-c-1.10.0-h286801f_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lzo-2.10-h925e9cb_1002.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.0.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py313h7d74516_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-3.10.6-py313h39782a4_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-base-3.10.6-py313h58042b9_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/menuinst-2.3.1-py313h8f79df9_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py312h5748b74_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-3.10.8-py312h1f38498_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-base-3.10.8-py312h605b88b_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/menuinst-2.4.1-py312h81bd7bf_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.1.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/more-itertools-10.8.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgpack-python-1.1.2-py312h84eede6_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.6-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.6-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/netcdf4-1.7.2-nompi_py313hb28a5cb_104.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.5-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nh3-0.3.0-py310h1620c0a_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nlohmann_json-3.11.3-h00cdb27_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/netcdf4-1.7.3-nompi_py312h947358d_100.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.6-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nlohmann_json-abi-3.12.0-h0f90c79_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.9.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.3.3-py313h9771d21_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.3.5-py312h85ea64e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/objprint-0.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openjpeg-2.5.4-hbfb3c88_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.5.4-h5503f6c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/orjson-3.11.3-py313h80e0809_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.0-h5503f6c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/orjson-3.11.4-py312h6ef9ec0_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pandas-2.3.3-py313h7d16b84_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pandas-2.3.3-py312h5978115_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.5-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pickleshare-0.7.5-pyhd8ed1ab_1004.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pillow-11.3.0-py313he4c6d0d_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh145f28c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pillow-12.0.0-py312h95c711c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.3-pyh8b19718_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.5.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-4.3.0-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-4.5.0-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.23.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.1.0-py313h6535dbc_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.1.3-py312h37e1c23_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pthread-stubs-0.4-hd74edd7_1002.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-4-hd8ed1ab_3.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pycosat-0.6.6-py313hcdf3177_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-11-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pycosat-0.6.6-py312h163523d_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.11.10-pyh3cfb1c2_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.33.2-py313hf3ab51e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.12.5-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.41.5-py312h6ef9ec0_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-11.1-py313had225c5_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-11.1-py313h4e140e3_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-12.1-py312h19bbe71_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-12.1-py312h1de3e18_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.2.5-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject_hooks-1.2.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.4.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-cov-7.0.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-repeat-0.9.4-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.8.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.7-h5c937ed_100_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-build-1.3.0-pyhff2d567_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.12.12-h18782d2_1_cpython.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.7-h4df99d1_100.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.12.12-hd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2025.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-8_cp312.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py313h7d74516_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py312h5748b74_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py312hd65ceae_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/qhull-2020.2-h420ef59_5.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rav1e-0.7.1-h0716509_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.2-h1d1bf99_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/readme_renderer-44.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.36.2-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/reproc-14.2.5.post0-h5505292_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/reproc-cpp-14.2.5.post0-h286801f_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-toolbelt-1.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-2.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.1.0-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/roman-numerals-py-3.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.27.1-py313h80e0809_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruamel.yaml-0.18.15-py313h6535dbc_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruamel.yaml.clib-0.2.12-py313hcdf3177_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruff-0.13.3-h492a034_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/scipy-1.16.2-py313h0d10b07_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.30.0-py312h6ef9ec0_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruamel.yaml-0.18.16-py312h4409184_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruamel.yaml.clib-0.2.14-py312h4409184_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruff-0.14.8-h382de68_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/scipy-1.16.3-py312ha6bbf71_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.3-pyh31c8845_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/simdjson-4.0.7-ha7d2532_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/simdjson-4.2.2-ha7d2532_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/snappy-1.2.2-hd121638_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-3.0.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/snappy-1.2.2-hada39a4_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/spefile-1.6.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-8.2.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx_rtd_theme-3.0.2-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-2.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-devhelp-2.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.1.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-jquery-4.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-jsmath-1.0.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-qthelp-2.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-1.1.10-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/svt-av1-3.1.2-h12ba402_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh31c8845_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tifffile-2025.10.4-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h892fb3f_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.2-py313hcdf3177_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tifffile-2020.6.3-py_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.5.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h892fb3f_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.2-py312h4409184_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/truststore-0.10.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/twine-6.2.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/types-python-dateutil-2.9.0.20250822-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ukkonen-1.0.1-py313hf9c7212_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ukkonen-1.0.1-py312ha0dd364_6.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/unicodedata2-17.0.0-py312h4409184_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/versioningit-3.3.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.34.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/viztracer-1.0.4-py313h90d716c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.35.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/viztracer-1.1.1-py312h4409184_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.14-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-24.11.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.8.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxau-1.0.12-h5505292_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxdmcp-1.1.5-hd74edd7_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/widgetsnbextension-4.0.15-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxau-1.0.12-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxdmcp-1.1.5-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h925e9cb_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-cpp-0.8.0-ha1acc90_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zeromq-4.3.5-h888dc83_9.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zfp-1.0.1-hfd287c0_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.1-h8359307_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-ng-2.2.5-hf787086_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstandard-0.25.0-py313h9734d34_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-h6491c7d_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-ng-2.3.2-h248ca61_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstandard-0.25.0-py312h37e1c23_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fd/79/ef0d477c771a642d1a881b92d226314c43d3c74bc674c93e12e679397a97/aiohttp-3.13.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/28/66/d35dcfea8050e131cdd731dff36434390479b4045a8d0b9d7111b0a968f1/aiohttp-3.13.2-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2b/79/0f6eacbe575c3b677d70193c22b68c3a95bec68204ccf53068b657c7ed06/astropy-7.1.0-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/c5/38/be921eaf8fc67333fd2c2657ea965103ccc4050c29a66d5fd5f3db881c0f/astropy_iers_data-0.2025.10.6.0.35.25-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/84/19/529e6ade85e681cb84d5e5c6501f2492f17ec2c8f8fe246a3898ffcb0e2c/bioblend-1.6.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/cb/bd/b394387b598ed84d8d0fa90611a90bee0adc2021820ad5729f7ced74a8e2/imageio-2.37.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a6/ba/3418133ba144dfcd1530bca5a6b695f4cdd21a8abaaa2ac4e5450d11b028/astropy-7.2.0-cp311-abi3-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/02/48/501f82ad7806e57bed6e2a4b3cca0897302f51ddfb7ac24891fe6a200360/astropy_iers_data-0.2025.12.1.0.45.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b4/ba/ff3c536ab50e179a4e1eef0ea3de7b878b6b81629ded651f0af7156a0f9c/bioblend-1.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/fb/fe/301e0936b79bcab4cacc7548bf2853fc28dced0a578bab1f7ef53c9aa75b/imageio-2.37.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/60/d497a310bde3f01cb805196ac61b7ad6dc5dcf8dce66634dc34364b20b4f/lazy_loader-0.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f1/d8/6c3442322e41fb1dd4de8bd67bfd11cd72352ac131f6368315617de752f1/multidict-6.7.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/b3/93/c4f67a436dd026f2e780c433277fff72be79152894d9fc36f44569cab1a6/multidict-6.7.0-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/ad/0a/8bf67f63245e39626bc69357f932005d97ee2ac48e6aff897a71142004ec/nova_galaxy-0.7.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d0/d9/b15e88b4463df45a7793fb04e2b5497334f8fcc24e281c221150a0af9aff/propcache-0.4.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/0a/b6/5c9a0e42df4d00bfb4a3cbbe5cf9f54260300c88a0e9af1f47ca5ce17ac0/propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/11/4a/31a363370478b63c6289a34743f2ba2d3ae1bd8223e004d18ab28fb92385/pyerfa-2.0.1.5-cp39-abi3-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/84/06/66a2e7661d6f526740c309e9717d3bd07b473661d5cdddef4dd978edab25/scikit_image-0.25.2-cp313-cp313-macosx_12_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/36/65/a5549325daafc3eae4b52de076798839eaf529a07218f8fb18cccefe76a1/pywavelets-1.9.0-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/55/0ff6e41c39c64d9ad18bf31c953c28f525533609c7371fa2790558ca8197/scikit_image-0.20.0.tar.gz - pypi: https://files.pythonhosted.org/packages/78/17/853354204e1ca022d6b7d011ca7f3206c4f8faa3cc743e92609b49c1d83f/tinydb-4.8.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/8e/b94ec25fcd4384de2a1f5e0403284f005ec743c04bafdb6698c851df5c6d/tuspy-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/18/91/3274b215fd8442a03975ce6bee5fe6aa57a8326b29b9d3d56234a1dca244/yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/ba/f5/0601483296f09c3c65e303d60c070a5c19fcdbc72daa061e96170785bc7d/yarl-1.22.0-cp312-cp312-macosx_11_0_arm64.whl - pypi: ./ - jupyter: + mcp: channels: - url: https://conda.anaconda.org/conda-forge/ indexes: @@ -753,54 +1402,55 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.14-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.11.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.12.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/aom-3.9.1-hac33072_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/archspec-0.2.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py312h4c3975b_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py313h07c4f96_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.0.5-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.2-h39aace5_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.4.0-pyh71513ae_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.17.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.2-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.2.0-pyh29332c3_4.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.2.0-h82add2a_4.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-h5f6438b_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/blosc-1.21.6-he440d0b_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/boltons-25.0.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-1.1.0-hb03c661_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.1.0-hb03c661_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py312h1289d80_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/brunsli-0.1-he3183e4_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-1.2.0-hed03a55_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py313hf159716_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brunsli-0.1-hd1e3526_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_8.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.5-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/c-blosc2-2.21.3-h4cfbee9_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-blosc2-2.22.0-hc31b594_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.11.12-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/cairo-1.18.4-h3394656_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.10.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py312h35888ee_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.4-py312h4f23490_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.11.12-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py313hf46b229_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.5.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.4-py313h29aa505_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/charls-2.4.2-h59595ed_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/conda-25.9.0-py312h7900ff3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/conda-libmamba-solver-25.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/conda-25.11.0-py313h78bf25f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-libmamba-solver-25.11.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-handling-2.4.0-pyh7900ff3_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-streaming-0.12.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/conda-tree-1.1.1-pyhff2d567_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.3.3-py312hd9148b4_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-tree-1.1.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.3.3-py313h7037e92_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/coverage-7.12.0-py313h3dea7bd_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cpp-expected-1.3.1-h171cf75_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.11-py312hd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.10-py313hd8ed1ab_100.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cyrus-sasl-2.1.28-hd9c7081_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/dav1d-1.2.1-hd590300_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/dbus-1.16.2-h3c4dab8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.17-py312h8285ef7_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/dbus-1.16.2-h24cb091_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.17-py313h5d5ffb9_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.0-pyhd8ed1ab_0.conda @@ -809,133 +1459,132 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/dxchange-0.1.8-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/dxfile-0.5-py_1.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/edffile-5.9.4-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.19.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/fmt-11.2.0-h07f6e7f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.20.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fmt-12.0.0-h2b0788b_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.15.0-h7e30c49_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.60.1-py312h8a5da7c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.61.0-py313h3dea7bd_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/freetype-2.14.1-ha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/frozendict-2.4.6-py312h4c3975b_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/frozendict-2.4.7-py313h07c4f96_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.2-hd590300_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.14-hecca717_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/h5py-3.14.0-nompi_py312ha4f8f14_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-12.1.0-h15599e2_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/h5py-3.15.1-nompi_py313h253c126_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-12.2.0-h15599e2_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h2a13503_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.14.6-nompi_h6e4c0c1_103.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.14.6-nompi_h1b119a7_104.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.15-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/imagecodecs-2025.8.2-py312h4ecb025_4.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/imagecodecs-2025.11.11-py313hf092b87_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.30.1-pyh82676e8_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipympl-0.9.7-pyhcf101f3_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.6.0-pyhfa0c392_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.1.0-pyha191276_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.8.0-pyh53cf698_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipywidgets-8.1.7-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.12.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpatch-1.33-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/jsonpointer-3.0.0-py312h7900ff3_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/jsonpointer-3.0.0-py313h78bf25f_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.25.1-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.25.1-he01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.8.1-pyh31011fe_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.4.9-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.27.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_widgets-3.0.15-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/jxrlib-1.1-hd590300_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.9-py312h0a2e395_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.9-py313hc8edb43_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.21.3-h659f571_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.17-h717163a_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.44-ha97dd6f_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45-default_hbd61a6d_104.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h0aef613_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libaec-1.1.4-h3f801dc_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarchive-3.8.1-gpl_h7be2006_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarchive-3.8.2-gpl_h7be2006_100.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libavif16-1.3.0-h6395336_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-36_h4a7cf45_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.1.0-hb03c661_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.1.0-hb03c661_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.1.0-hb03c661_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-36_h0358290_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libclang-cpp21.1-21.1.2-default_h99862b1_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libclang13-21.1.2-default_h746c552_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-4_h4a7cf45_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-4_h0358290_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libclang-cpp21.1-21.1.7-default_h99862b1_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libclang13-21.1.7-default_h746c552_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-hb8b1518_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.14.1-h332b0f4_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.24-h86f0d12_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.17.0-h4e3cde8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.25-h17f619e_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libdrm-2.4.125-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libegl-1.7.0-ha4b6fd6_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.1-hecca717_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.3-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h9ec8514_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.14.1-ha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype6-2.14.1-h73754d4_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-h767d61c_6.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_6.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_6.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-hcd61629_6.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_15.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_15.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_15.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_15.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgl-1.7.0-ha4b6fd6_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.86.0-h1fed272_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.86.2-h32235b2_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libglvnd-1.7.0-ha4b6fd6_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libglx-1.7.0-ha4b6fd6_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-h767d61c_6.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_15.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libhwy-1.3.0-h4c17acf_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.1.0-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libjxl-0.11.1-h6cb5226_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-36_h47877c9_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libllvm21-21.1.2-hf7376ad_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.1.2-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libjxl-0.11.1-hf08fa70_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-4_h47877c9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libllvm21-21.1.7-hf7376ad_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libmamba-2.3.2-hae34dd5_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libmambapy-2.3.2-py312he1eb750_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmamba-2.4.0-hed7d790_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmambapy-2.4.0-py313hb20a507_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.9.3-nompi_h11f7409_103.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.67.0-had1ee68_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libntlm-1.8-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.30-pthreads_h94d23a6_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.30-pthreads_h94d23a6_4.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libopengl-1.7.0-ha4b6fd6_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libpciaccess-0.18-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.50-h421ea60_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libpq-18.0-h3675c94_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.51-h421ea60_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpq-18.1-h5c52fec_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.20-h4ab18f5_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsolv-0.7.35-h9463b59_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.50.4-h0c1763c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.51.1-h0c1763c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.1-hcf80075_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h8f9b012_6.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-h4852527_6.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.1-h8261f1e_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.2-he9a06e4_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libvulkan-loader-1.4.328.0-h5279c79_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_15.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_15.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.1-h9d88235_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.2-h5347b49_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libvulkan-loader-1.4.328.1-h5279c79_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.6.0-hd42ef1d_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.17.0-h8a09558_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.11.0-hca5e8e5_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.0-ha9997c6_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.0-h26afc86_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.13.1-hca5e8e5_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.1-ha9997c6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.1-h26afc86_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxslt-1.1.43-h711ed8c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzip-1.11.2-h6991a6a_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda @@ -943,70 +1592,74 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/loguru-0.7.3-pyh707e725_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lzo-2.10-h280c20c_1002.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py312h8a5da7c_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.10.6-py312h7900ff3_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.10.6-py312he3d6523_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/menuinst-2.3.1-py312h7900ff3_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py313h3dea7bd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.10.8-py313h78bf25f_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.10.8-py313h683a580_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/menuinst-2.4.1-py313h78bf25f_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.1.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/msgpack-python-1.1.2-py313h7037e92_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.6-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.6-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.7.2-nompi_py312hf6400b3_104.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.5-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/nlohmann_json-3.11.3-he02047a_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.7.3-nompi_py313hfae5b86_100.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.6-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nlohmann_json-abi-3.12.0-h0f90c79_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.9.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.3.3-py312h33ff503_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.3.5-py313hf6604e3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/objprint-0.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.4-h55fea9a_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openldap-2.6.10-he970967_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.4-h26f9b46_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/orjson-3.11.3-py312h868fb18_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.0-h26f9b46_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/orjson-3.11.4-py313h843e2db_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pandas-2.3.3-py312hf79963d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pandas-2.3.3-py313h08cd8bf_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.5-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.46-h1321c63_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pickleshare-0.7.5-pyhd8ed1ab_1004.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-11.3.0-py312h7b42cdd_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh8b19718_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-12.0.0-py313h80991f8_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.3-pyh145f28c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pixman-0.46.4-h54a6638_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.5.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-4.3.0-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-4.5.0-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.23.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.1.0-py312h4c3975b_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.1.3-py313h54dd161_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-hb9d3cd8_1002.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-4-hd8ed1ab_3.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/pycosat-0.6.6-py312h4c3975b_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-11-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pycosat-0.6.6-py313h07c4f96_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.11.10-pyh3cfb1c2_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.33.2-py312h680f630_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.12.5-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.41.5-py313h843e2db_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.2.5-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pyside6-6.9.3-py312h9da60e5_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyside6-6.9.3-py313h85046ba_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.11-h9e4cc4f_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.0.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-cov-7.0.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-repeat-0.9.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.8.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.10-hc97d973_100_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.12.11-hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.10-h4df99d1_100.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2025.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-8_cp312.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py312h8a5da7c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py313h3dea7bd_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py312hfb55c3c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/qhull-2020.2-h434a139_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/qt6-main-6.9.3-h5c1c036_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/qt6-main-6.9.3-h5c1c036_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/rav1e-0.7.1-h8fae777_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.36.2-pyh29332c3_0.conda @@ -1016,52 +1669,48 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.27.1-py312h868fb18_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ruamel.yaml-0.18.15-py312h4c3975b_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ruamel.yaml.clib-0.2.12-py312h4c3975b_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ruff-0.13.3-ha3a3aed_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.16.2-py312h7a1785b_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py313h843e2db_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ruamel.yaml-0.18.16-py313h07c4f96_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ruamel.yaml.clib-0.2.14-py313h07c4f96_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ruff-0.14.8-h813ae00_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.16.3-py313h11c21cd_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.3-pyh0d859eb_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/simdjson-4.0.7-hb700be7_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/simdjson-4.2.2-hb700be7_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/snappy-1.2.2-h03e3b7b_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/snappy-1.2.2-h03e3b7b_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/spefile-1.6.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/svt-av1-3.1.2-hecca717_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh0d859eb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tifffile-2025.10.4-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_hd72426e_102.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.2-py312h4c3975b_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tifffile-2025.10.16-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.5.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_ha0e22de_103.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.2-py313h07c4f96_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/truststore-0.10.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/types-python-dateutil-2.9.0.20250822-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py312h68727a3_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-16.0.0-py312h4c3975b_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py313h7037e92_6.conda - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/versioningit-3.3.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.34.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/viztracer-1.0.4-py312h0f154a2_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/wayland-1.24.0-h3e06ad9_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.35.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/viztracer-1.1.1-py313h6459d8b_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/wayland-1.24.0-hd6090a7_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.14-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-24.11.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.8.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/widgetsnbextension-4.0.14-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.1-h4f16b4b_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-cursor-0.1.5-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-cursor-0.1.6-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-hb711507_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.1-hb711507_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.10-hb711507_0.conda @@ -1070,11 +1719,11 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libice-1.1.2-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.6-he73a12e_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.8.12-h4f16b4b_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxcomposite-0.4.6-hb9d3cd8_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxcursor-1.2.3-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdamage-1.1.6-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.6-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxfixes-6.0.2-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxi-1.8.2-hb9d3cd8_0.conda @@ -1086,78 +1735,111 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-cpp-0.8.0-h3f2d84a_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zeromq-4.3.5-h387f397_9.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zfp-1.0.1-h909a3a2_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.1-hb9d3cd8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-ng-2.2.5-hde8ca8f_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.25.0-py312h5253ce2_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-ng-2.3.2-h54a6638_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.25.0-py313h54dd161_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/14/51/d0c1701a79fcb0109cff5304da16226581569b89a282d8e7f1549a7e3ec0/aiohttp-3.13.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f7/0d/4764669bdf47bd472899b3d3db91fffbe925c8e3038ec591a2fd2ad6a14d/aiohttp-3.13.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fb/69/a34f20db7146912f25e2487c5283f1ae2aed5d24f615fa976439ece35f7e/astropy-7.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/c5/38/be921eaf8fc67333fd2c2657ea965103ccc4050c29a66d5fd5f3db881c0f/astropy_iers_data-0.2025.10.6.0.35.25-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/84/19/529e6ade85e681cb84d5e5c6501f2492f17ec2c8f8fe246a3898ffcb0e2c/bioblend-1.6.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/cb/bd/b394387b598ed84d8d0fa90611a90bee0adc2021820ad5729f7ced74a8e2/imageio-2.37.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c3/1c/f06ad85180e7dd9855aa5ede901bfc2be858d7bee17d4e978a14c0ecec14/astropy-7.2.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/02/48/501f82ad7806e57bed6e2a4b3cca0897302f51ddfb7ac24891fe6a200360/astropy_iers_data-0.2025.12.1.0.45.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f8/aa/5082412d1ee302e9e7d80b6949bc4d2a8fa1149aaab610c5fc24709605d6/authlib-1.6.5-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/14/2a/fbcbf5a025d3e71ddafad7efd43e34ec4362f4d523c3c471b457148fb211/beartype-0.22.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b4/ba/ff3c536ab50e179a4e1eef0ea3de7b878b6b81629ded651f0af7156a0f9c/bioblend-1.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e6/46/eb6eca305c77a4489affe1c5d8f4cae82f285d9addd8de4ec084a7184221/cachetools-6.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c9/56/e7e69b427c3878352c2fb9b450bd0e19ed552753491d39d7d0a2f5226d41/cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/7a/e8/77a231ae531cf38765b75ddf27dae28bb5f70b41d8bb4f15ce1650e93f57/cyclopts-4.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/27/4570e78fc0bf5ea0ca45eb1de3818a23787af9b390c0b0a0033a1b8236f9/diskcache-5.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/11/a8/c6a4b901d17399c77cd81fb001ce8961e9f5e04d3daf27e8925cb012e163/docutils-0.22.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/db/bc/56925f1202357dbfcfdfd0c75afc6c27ec1e6ef1d89b7e7410df3945ceb4/fastmcp-2.13.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/d2/fd/6668e5aec43ab844de6fc74927e155a3b37bf40d7c3790e49fc0406b6578/httpx_sse-0.4.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/fe/301e0936b79bcab4cacc7548bf2853fc28dced0a578bab1f7ef53c9aa75b/imageio-2.37.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/58/3485da8cb93d2f393bce453adeef16896751f14ba3e2024bc21dc9597646/jsonschema_path-0.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/60/d497a310bde3f01cb805196ac61b7ad6dc5dcf8dce66634dc34364b20b4f/lazy_loader-0.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0d/e2/9baffdae21a76f77ef8447f1a05a96ec4bc0a24dae08767abc0a2fe680b8/multidict-6.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a9/bb/711099f9c6bb52770f56e56401cdfb10da5b67029f701e0df29362df4c8e/mcp-1.22.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c6/2d/f0b184fa88d6630aa267680bdb8623fb69cb0d024b8c6f0d23f9a0f406d3/multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ad/0a/8bf67f63245e39626bc69357f932005d97ee2ac48e6aff897a71142004ec/nova_galaxy-0.7.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/84/5e/036d2b105927ae7f179346c9911d16c345f4dba5a19a063f23a8d28acfbd/propcache-0.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/12/cf/03675d8bd8ecbf4445504d8071adab19f5f993676795708e36402ab38263/openapi_pydantic-0.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7d/eb/b6260b31b1a96386c0a880edebe26f89669098acea8e0318bff6adb378fd/pathable-0.4.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9a/70/875f4a23bfc4731703a5835487d0d2fb999031bd415e7d17c0ae615c18b7/pathvalidate-3.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/99/10/72f6f213b8f0bce36eff21fda0a13271834e9eeff7f9609b01afdc253c79/py_key_value_aio-0.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/e4/b8b0a03ece72f47dce2307d36e1c34725b7223d209fc679315ffe6a4e2c3/py_key_value_shared-0.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/60/5d4751ba3f4a40a6891f24eec885f51afd78d208498268c734e256fb13c4/pydantic_settings-2.12.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e5/e0/050018d855d26d3c0b4a7d1b2ed692be758ce276d8289e2a2b44ba1014a5/pyerfa-2.0.1.5-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/df/80/fc9d01d5ed37ba4c42ca2b55b4339ae6e200b456be3a1aaddf4a9fa99b8c/pyperclip-1.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6b/b5/b75527c0f9532dd8a93e8e7cd8e62e547b9f207d4c11e24f0006e8646b36/scikit_image-0.25.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/2f/b4530fbf948867702d0a3f27de4a6aab1d156f406d72852ab902c4d04de9/rich_rst-1.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cd/9b/c3da56a145f52cd61a68b8465d6a29d9503bc45bc993bb45e84371c97d94/scikit_image-0.25.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/23/a0/984525d19ca5c8a6c33911a0c164b11490dd0f90ff7fd689f704f84e9a11/sse_starlette-3.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d9/52/1064f510b141bd54025f9b55105e26d1fa970b9be67ad766380a3c9b74b0/starlette-0.50.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/17/853354204e1ca022d6b7d011ca7f3206c4f8faa3cc743e92609b49c1d83f/tinydb-4.8.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/8e/b94ec25fcd4384de2a1f5e0403284f005ec743c04bafdb6698c851df5c6d/tuspy-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/db/0f/0d52c98b8a885aeda831224b78f3be7ec2e1aa4a62091f9f9188c3c65b56/yarl-1.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ee/d9/d88e73ca598f4f6ff671fb5fde8a32925c2e08a637303a1d12883c7305fa/uvicorn-0.38.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/11/c9/cd8538dc2e7727095e0c1d867bad1e40c98f37763e6d995c1939f5fdc7b1/yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: ./ osx-arm64: + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/_openmp_mutex-4.5-7_kmp_llvm.conda - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.11.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.12.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aom-3.9.1-h7bae524_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/archspec-0.2.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py313h6535dbc_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py312h4409184_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.0.5-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.4.0-pyh71513ae_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.17.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.2-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.2.0-pyh29332c3_4.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.2.0-h82add2a_4.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-h5f6438b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/blosc-1.21.6-h7dd00d9_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/boltons-25.0.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-1.1.0-h6caf38d_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-bin-1.1.0-h6caf38d_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.1.0-py313hb4b7877_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brunsli-0.1-h97083b6_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-1.2.0-h7d5ae5b_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-bin-1.2.0-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.2.0-py312h0dfefe5_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brunsli-0.1-he0dfb12_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_8.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.5-h5505292_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-blosc2-2.21.3-hb5916c8_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-blosc2-2.22.0-hb5916c8_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.11.12-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.10.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py313h89bd988_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cftime-1.6.4-py313h2732efb_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.11.12-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py312h1b4d9a2_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.5.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cftime-1.6.4-py312hc7121bb_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/charls-2.4.2-h13dd4ca_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/conda-25.9.0-py313h8f79df9_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/conda-libmamba-solver-25.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/conda-25.11.0-py312h81bd7bf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-libmamba-solver-25.11.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-handling-2.4.0-pyh7900ff3_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-streaming-0.12.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/conda-tree-1.1.1-pyhff2d567_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/contourpy-1.3.3-py313hc50a443_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/conda-tree-1.1.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/contourpy-1.3.3-py312h84eede6_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/coverage-7.12.0-py312h5748b74_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cpp-expected-1.3.1-h4f10f1e_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.7-py313hd8ed1ab_100.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.12-py312hd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/dav1d-1.2.1-hb547adb_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.17-py313hc37fe24_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.17-py312h56d30c9_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.0-pyhd8ed1ab_0.conda @@ -1165,166 +1847,168 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/dxchange-0.1.8-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/dxfile-0.5-py_1.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/edffile-5.9.4-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.19.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/fmt-11.2.0-h440487c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/fonttools-4.60.1-py313h7d74516_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.20.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/fmt-12.0.0-h669d743_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/fonttools-4.61.0-py312h5748b74_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/freetype-2.14.1-hce30654_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/frozendict-2.4.6-py313hcdf3177_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/frozendict-2.4.7-py312h4409184_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/giflib-5.2.2-h93a5062_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/h5py-3.14.0-nompi_py313ha10fd41_101.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/h5py-3.15.1-nompi_py312h4eecd6b_101.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdf4-4.2.15-h2ee6834_7.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdf5-1.14.6-nompi_he65715a_103.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdf5-1.14.6-nompi_hd3baa01_104.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-75.1-hfee45f7_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.15-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/imagecodecs-2025.8.2-py313h8be30f9_4.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/imagecodecs-2025.11.11-py312h9d9c187_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.30.1-pyh92f572d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipympl-0.9.7-pyhcf101f3_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.6.0-pyhfa0c392_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.1.0-pyh5552912_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.8.0-pyh53cf698_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipywidgets-8.1.7-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.12.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpatch-1.33-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/jsonpointer-3.0.0-py313h8f79df9_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/jsonpointer-3.0.0-py312h81bd7bf_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.25.1-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.25.1-he01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.8.1-pyh31011fe_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.4.9-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.27.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_widgets-3.0.15-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/jxrlib-1.1-h93a5062_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/kiwisolver-1.4.9-py313hf88c9ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/kiwisolver-1.4.9-py312hd8c8125_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.21.3-h237132a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lcms2-2.17-h7eeda09_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lerc-4.0.0-hd64df32_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libaec-1.1.4-h51d1e36_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarchive-3.8.1-gpl_h46575ef_101.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarchive-3.8.2-gpl_h46575ef_100.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libavif16-1.3.0-hb06b76e_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.9.0-36_h51639a9_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.1.0-h6caf38d_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.1.0-h6caf38d_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.1.0-h6caf38d_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.9.0-36_hb0561ab_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcurl-8.14.1-h73640d1_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-21.1.2-hf598326_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libdeflate-1.24-h5773f1b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-4_h51639a9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.2.0-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.2.0-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.2.0-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-4_hb0561ab_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcurl-8.17.0-hdece5d2_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-21.1.7-hf598326_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libdeflate-1.25-hc11a715_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20250104-pl5321hafb1f1b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h93a5062_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.1-hec049ff_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.4.6-h1da3d7d_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.3-haf25636_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-he5f378a_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype-2.14.1-hce30654_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype6-2.14.1-h6da58f4_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-hfcf01ff_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-h742603c_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_15.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_15.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_15.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libhwy-1.3.0-h48b13b8_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libiconv-1.18-h23cfdf5_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libjpeg-turbo-3.1.0-h5505292_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libjxl-0.11.1-h7274d02_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.9.0-36_hd9741b5_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libjpeg-turbo-3.1.2-hc919400_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libjxl-0.11.1-h3dcb153_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.11.0-4_hd9741b5_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.1-h39f12f2_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmamba-2.3.2-hbdbd6ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmambapy-2.3.2-py313h904c928_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h5505292_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmamba-2.4.0-h7516003_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmambapy-2.4.0-py312ha7ae334_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnetcdf-4.9.3-nompi_h80c4520_103.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.67.0-hc438710_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.30-openmp_h60d53f8_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpng-1.6.50-h280e0eb_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.30-openmp_ha158390_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpng-1.6.51-hfab5511_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsodium-1.0.20-h99b78c6_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsolv-0.7.35-h5f525b2_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.50.4-h4237e3c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.51.1-h9a5124b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libssh2-1.11.1-h1590b86_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libtiff-4.7.1-h7dc4979_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libtiff-4.7.1-h4030677_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libwebp-base-1.6.0-h07db88b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxcb-1.17.0-hdb1d25a_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-16-2.15.0-h0ff4647_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-2.15.0-h9329255_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-16-2.15.1-h8eac4d7_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-2.15.1-hba2cd1d_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzip-1.11.2-h1336266_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzopfli-1.0.3-h9f76cd9_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-21.1.2-h4a912ad_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-21.1.7-h4a912ad_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/loguru-0.7.3-pyh707e725_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lz4-c-1.10.0-h286801f_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lzo-2.10-h925e9cb_1002.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py313h7d74516_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-3.10.6-py313h39782a4_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-base-3.10.6-py313h58042b9_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/menuinst-2.3.1-py313h8f79df9_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py312h5748b74_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-3.10.8-py312h1f38498_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-base-3.10.8-py312h605b88b_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/menuinst-2.4.1-py312h81bd7bf_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.1.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgpack-python-1.1.2-py312h84eede6_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.6-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.6-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/netcdf4-1.7.2-nompi_py313hb28a5cb_104.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.5-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nlohmann_json-3.11.3-h00cdb27_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/netcdf4-1.7.3-nompi_py312h947358d_100.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.6-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nlohmann_json-abi-3.12.0-h0f90c79_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.9.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.3.3-py313h9771d21_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.3.5-py312h85ea64e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/objprint-0.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openjpeg-2.5.4-hbfb3c88_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.5.4-h5503f6c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/orjson-3.11.3-py313h80e0809_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.0-h5503f6c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/orjson-3.11.4-py312h6ef9ec0_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pandas-2.3.3-py313h7d16b84_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pandas-2.3.3-py312h5978115_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.5-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pickleshare-0.7.5-pyhd8ed1ab_1004.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pillow-11.3.0-py313he4c6d0d_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh145f28c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pillow-12.0.0-py312h261a3e5_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.3-pyh8b19718_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.5.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-4.3.0-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-4.5.0-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.23.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.1.0-py313h6535dbc_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.1.3-py312h37e1c23_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pthread-stubs-0.4-hd74edd7_1002.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-4-hd8ed1ab_3.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pycosat-0.6.6-py313hcdf3177_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-11-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pycosat-0.6.6-py312h163523d_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.11.10-pyh3cfb1c2_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.33.2-py313hf3ab51e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.12.5-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.41.5-py312h6ef9ec0_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-11.1-py313had225c5_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-11.1-py313h4e140e3_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-12.1-py312h19bbe71_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-12.1-py312h1de3e18_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.2.5-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.7-h5c937ed_100_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.0.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-cov-7.0.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-repeat-0.9.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.8.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.12.12-h18782d2_1_cpython.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.7-h4df99d1_100.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.12.12-hd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2025.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-8_cp312.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py313h7d74516_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py312h5748b74_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py312hd65ceae_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/qhull-2020.2-h420ef59_5.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rav1e-0.7.1-h0716509_3.conda @@ -1336,76 +2020,107 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.27.1-py313h80e0809_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruamel.yaml-0.18.15-py313h6535dbc_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruamel.yaml.clib-0.2.12-py313hcdf3177_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruff-0.13.3-h492a034_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/scipy-1.16.2-py313h0d10b07_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.30.0-py312h6ef9ec0_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruamel.yaml-0.18.16-py312h4409184_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruamel.yaml.clib-0.2.14-py312h4409184_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruff-0.14.8-h382de68_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/scipy-1.16.3-py312ha6bbf71_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.3-pyh31c8845_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/simdjson-4.0.7-ha7d2532_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/simdjson-4.2.2-ha7d2532_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/snappy-1.2.2-hd121638_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/snappy-1.2.2-hada39a4_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/spefile-1.6.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/svt-av1-3.1.2-h12ba402_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh31c8845_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tifffile-2025.10.4-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h892fb3f_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.2-py313hcdf3177_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tifffile-2025.10.16-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.5.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h892fb3f_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.2-py312h4409184_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/truststore-0.10.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/types-python-dateutil-2.9.0.20250822-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ukkonen-1.0.1-py313hf9c7212_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ukkonen-1.0.1-py312ha0dd364_6.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/unicodedata2-17.0.0-py312h4409184_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/versioningit-3.3.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.34.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/viztracer-1.0.4-py313h90d716c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.35.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/viztracer-1.1.1-py312h4409184_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.14-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-24.11.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.8.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/widgetsnbextension-4.0.14-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxau-1.0.12-h5505292_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxdmcp-1.1.5-hd74edd7_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxau-1.0.12-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxdmcp-1.1.5-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h925e9cb_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-cpp-0.8.0-ha1acc90_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zeromq-4.3.5-h888dc83_9.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zfp-1.0.1-hfd287c0_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.1-h8359307_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-ng-2.2.5-hf787086_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstandard-0.25.0-py313h9734d34_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-h6491c7d_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-ng-2.2.5-h3470cca_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstandard-0.25.0-py312h37e1c23_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fd/79/ef0d477c771a642d1a881b92d226314c43d3c74bc674c93e12e679397a97/aiohttp-3.13.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/28/66/d35dcfea8050e131cdd731dff36434390479b4045a8d0b9d7111b0a968f1/aiohttp-3.13.2-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2b/79/0f6eacbe575c3b677d70193c22b68c3a95bec68204ccf53068b657c7ed06/astropy-7.1.0-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/c5/38/be921eaf8fc67333fd2c2657ea965103ccc4050c29a66d5fd5f3db881c0f/astropy_iers_data-0.2025.10.6.0.35.25-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/84/19/529e6ade85e681cb84d5e5c6501f2492f17ec2c8f8fe246a3898ffcb0e2c/bioblend-1.6.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/cb/bd/b394387b598ed84d8d0fa90611a90bee0adc2021820ad5729f7ced74a8e2/imageio-2.37.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a6/ba/3418133ba144dfcd1530bca5a6b695f4cdd21a8abaaa2ac4e5450d11b028/astropy-7.2.0-cp311-abi3-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/02/48/501f82ad7806e57bed6e2a4b3cca0897302f51ddfb7ac24891fe6a200360/astropy_iers_data-0.2025.12.1.0.45.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f8/aa/5082412d1ee302e9e7d80b6949bc4d2a8fa1149aaab610c5fc24709605d6/authlib-1.6.5-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/14/2a/fbcbf5a025d3e71ddafad7efd43e34ec4362f4d523c3c471b457148fb211/beartype-0.22.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b4/ba/ff3c536ab50e179a4e1eef0ea3de7b878b6b81629ded651f0af7156a0f9c/bioblend-1.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e6/46/eb6eca305c77a4489affe1c5d8f4cae82f285d9addd8de4ec084a7184221/cachetools-6.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1d/42/9c391dd801d6cf0d561b5890549d4b27bafcc53b39c31a817e69d87c625b/cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl + - pypi: https://files.pythonhosted.org/packages/7a/e8/77a231ae531cf38765b75ddf27dae28bb5f70b41d8bb4f15ce1650e93f57/cyclopts-4.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/27/4570e78fc0bf5ea0ca45eb1de3818a23787af9b390c0b0a0033a1b8236f9/diskcache-5.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/11/a8/c6a4b901d17399c77cd81fb001ce8961e9f5e04d3daf27e8925cb012e163/docutils-0.22.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/db/bc/56925f1202357dbfcfdfd0c75afc6c27ec1e6ef1d89b7e7410df3945ceb4/fastmcp-2.13.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/d2/fd/6668e5aec43ab844de6fc74927e155a3b37bf40d7c3790e49fc0406b6578/httpx_sse-0.4.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/fe/301e0936b79bcab4cacc7548bf2853fc28dced0a578bab1f7ef53c9aa75b/imageio-2.37.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/58/3485da8cb93d2f393bce453adeef16896751f14ba3e2024bc21dc9597646/jsonschema_path-0.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/60/d497a310bde3f01cb805196ac61b7ad6dc5dcf8dce66634dc34364b20b4f/lazy_loader-0.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f1/d8/6c3442322e41fb1dd4de8bd67bfd11cd72352ac131f6368315617de752f1/multidict-6.7.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a9/bb/711099f9c6bb52770f56e56401cdfb10da5b67029f701e0df29362df4c8e/mcp-1.22.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/93/c4f67a436dd026f2e780c433277fff72be79152894d9fc36f44569cab1a6/multidict-6.7.0-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/ad/0a/8bf67f63245e39626bc69357f932005d97ee2ac48e6aff897a71142004ec/nova_galaxy-0.7.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d0/d9/b15e88b4463df45a7793fb04e2b5497334f8fcc24e281c221150a0af9aff/propcache-0.4.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/12/cf/03675d8bd8ecbf4445504d8071adab19f5f993676795708e36402ab38263/openapi_pydantic-0.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7d/eb/b6260b31b1a96386c0a880edebe26f89669098acea8e0318bff6adb378fd/pathable-0.4.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9a/70/875f4a23bfc4731703a5835487d0d2fb999031bd415e7d17c0ae615c18b7/pathvalidate-3.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0a/b6/5c9a0e42df4d00bfb4a3cbbe5cf9f54260300c88a0e9af1f47ca5ce17ac0/propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/99/10/72f6f213b8f0bce36eff21fda0a13271834e9eeff7f9609b01afdc253c79/py_key_value_aio-0.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/e4/b8b0a03ece72f47dce2307d36e1c34725b7223d209fc679315ffe6a4e2c3/py_key_value_shared-0.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/60/5d4751ba3f4a40a6891f24eec885f51afd78d208498268c734e256fb13c4/pydantic_settings-2.12.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/4a/31a363370478b63c6289a34743f2ba2d3ae1bd8223e004d18ab28fb92385/pyerfa-2.0.1.5-cp39-abi3-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/df/80/fc9d01d5ed37ba4c42ca2b55b4339ae6e200b456be3a1aaddf4a9fa99b8c/pyperclip-1.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/84/06/66a2e7661d6f526740c309e9717d3bd07b473661d5cdddef4dd978edab25/scikit_image-0.25.2-cp313-cp313-macosx_12_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/2f/b4530fbf948867702d0a3f27de4a6aab1d156f406d72852ab902c4d04de9/rich_rst-1.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ce/e6/93bebe1abcdce9513ffec01d8af02528b4c41fb3c1e46336d70b9ed4ef0d/scikit_image-0.25.2-cp312-cp312-macosx_12_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/23/a0/984525d19ca5c8a6c33911a0c164b11490dd0f90ff7fd689f704f84e9a11/sse_starlette-3.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d9/52/1064f510b141bd54025f9b55105e26d1fa970b9be67ad766380a3c9b74b0/starlette-0.50.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/17/853354204e1ca022d6b7d011ca7f3206c4f8faa3cc743e92609b49c1d83f/tinydb-4.8.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/8e/b94ec25fcd4384de2a1f5e0403284f005ec743c04bafdb6698c851df5c6d/tuspy-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/18/91/3274b215fd8442a03975ce6bee5fe6aa57a8326b29b9d3d56234a1dca244/yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/ee/d9/d88e73ca598f4f6ff671fb5fde8a32925c2e08a637303a1d12883c7305fa/uvicorn-0.38.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/ba/f5/0601483296f09c3c65e303d60c070a5c19fcdbc72daa061e96170785bc7d/yarl-1.22.0-cp312-cp312-macosx_11_0_arm64.whl - pypi: ./ packages: - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 @@ -1429,6 +2144,17 @@ packages: purls: [] size: 23621 timestamp: 1650670423406 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/_openmp_mutex-4.5-7_kmp_llvm.conda + build_number: 7 + sha256: 7acaa2e0782cad032bdaf756b536874346ac1375745fb250e9bdd6a48a7ab3cd + md5: a44032f282e7d2acdeb1c240308052dd + depends: + - llvm-openmp >=9.0.1 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 8325 + timestamp: 1764092507920 - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda sha256: a3967b937b9abf0f2a99f3173fa4630293979bd1644709d89580e7c62a544661 md5: aaa2a381ccc56eac91d63b6c1240312f @@ -1445,10 +2171,10 @@ packages: version: 2.6.1 sha256: f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/14/51/d0c1701a79fcb0109cff5304da16226581569b89a282d8e7f1549a7e3ec0/aiohttp-3.13.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/28/66/d35dcfea8050e131cdd731dff36434390479b4045a8d0b9d7111b0a968f1/aiohttp-3.13.2-cp312-cp312-macosx_11_0_arm64.whl name: aiohttp - version: 3.13.0 - sha256: 2e66c57416352f36bf98f6641ddadd47c93740a22af7150d3e9a1ef6e983f9a8 + version: 3.13.2 + sha256: c5c94825f744694c4b8db20b71dba9a257cd2ba8e010a803042123f3a25d50d7 requires_dist: - aiohappyeyeballs>=2.5.0 - aiosignal>=1.4.0 @@ -1461,12 +2187,12 @@ packages: - aiodns>=3.3.0 ; extra == 'speedups' - brotli ; platform_python_implementation == 'CPython' and extra == 'speedups' - brotlicffi ; platform_python_implementation != 'CPython' and extra == 'speedups' - - zstandard ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' + - backports-zstd ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/fd/79/ef0d477c771a642d1a881b92d226314c43d3c74bc674c93e12e679397a97/aiohttp-3.13.0-cp313-cp313-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/f7/0d/4764669bdf47bd472899b3d3db91fffbe925c8e3038ec591a2fd2ad6a14d/aiohttp-3.13.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: aiohttp - version: 3.13.0 - sha256: 564b36512a7da3b386143c611867e3f7cfb249300a1bf60889bd9985da67ab77 + version: 3.13.2 + sha256: ac6cde5fba8d7d8c6ac963dbb0256a9854e9fafff52fbcc58fdf819357892c3e requires_dist: - aiohappyeyeballs>=2.5.0 - aiosignal>=1.4.0 @@ -1479,7 +2205,7 @@ packages: - aiodns>=3.3.0 ; extra == 'speedups' - brotli ; platform_python_implementation == 'CPython' and extra == 'speedups' - brotlicffi ; platform_python_implementation != 'CPython' and extra == 'speedups' - - zstandard ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' + - backports-zstd ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl name: aiosignal @@ -1523,25 +2249,24 @@ packages: - pkg:pypi/annotated-types?source=hash-mapping size: 18074 timestamp: 1733247158254 -- conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.11.0-pyhcf101f3_0.conda - sha256: 7378b5b9d81662d73a906fabfc2fb81daddffe8dc0680ed9cda7a9562af894b0 - md5: 814472b61da9792fae28156cb9ee54f5 +- conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.12.0-pyhcf101f3_0.conda + sha256: 830fc81970cd9d19869909b9b16d241f4d557e4f201a1030aa6ed87c6aa8b930 + md5: 9958d4a1ee7e9c768fe8f4fb51bd07ea depends: - exceptiongroup >=1.0.2 - idna >=2.8 - python >=3.10 - - sniffio >=1.1 - typing_extensions >=4.5 - python constrains: - - trio >=0.31.0 + - trio >=0.32.0 - uvloop >=0.21 license: MIT license_family: MIT purls: - - pkg:pypi/anyio?source=compressed-mapping - size: 138159 - timestamp: 1758634638734 + - pkg:pypi/anyio?source=hash-mapping + size: 144702 + timestamp: 1764375386926 - conda: https://conda.anaconda.org/conda-forge/linux-64/aom-3.9.1-hac33072_0.conda sha256: b08ef033817b5f9f76ce62dfcac7694e7b6b4006420372de22494503decac855 md5: 346722a0be40f6edc53f12640d301338 @@ -1600,84 +2325,84 @@ packages: - pkg:pypi/argon2-cffi?source=hash-mapping size: 18715 timestamp: 1749017288144 -- conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py312h4c3975b_1.conda - sha256: 2eeb4a3a81149fec8e94e1454a21042f9eeb63b8f667672afcc63af69fb0bbbf - md5: 161fbbd6a2f41743c687ef83ba220ce9 +- conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py313h07c4f96_2.conda + sha256: ad188ccc06a06c633dc124b09e9e06fb9df4c32ffc38acc96ecc86e506062090 + md5: 27bbec9f2f3a15d32b60ec5734f5b41c depends: - __glibc >=2.17,<3.0.a0 - cffi >=1.0.1 - libgcc >=14 - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 license: MIT license_family: MIT purls: - pkg:pypi/argon2-cffi-bindings?source=hash-mapping - size: 35458 - timestamp: 1759486360792 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py313h6535dbc_1.conda - sha256: 23860322b12b1d4b365e9dca09a1b74449ee17b1701ee99cf7739dd5f4500b56 - md5: d15e31a7352fc18a07745df32e60ba7c + size: 35943 + timestamp: 1762509452935 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py312h4409184_2.conda + sha256: 24c475f6f7abf03ef3cc2ac572b7a6d713bede00ef984591be92cdc439b09fbc + md5: 0a2a07b42db3f92b8dccf0f60b5ebee8 depends: - __osx >=11.0 - cffi >=1.0.1 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 license: MIT license_family: MIT purls: - - pkg:pypi/argon2-cffi-bindings?source=compressed-mapping - size: 34013 - timestamp: 1759487134505 -- conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.3.0-pyhd8ed1ab_1.conda - sha256: c4b0bdb3d5dee50b60db92f99da3e4c524d5240aafc0a5fcc15e45ae2d1a3cd1 - md5: 46b53236fdd990271b03c3978d4218a9 + - pkg:pypi/argon2-cffi-bindings?source=hash-mapping + size: 34224 + timestamp: 1762509989973 +- conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda + sha256: 792da8131b1b53ff667bd6fc617ea9087b570305ccb9913deb36b8e12b3b5141 + md5: 85c4f19f377424eafc4ed7911b291642 depends: - - python >=3.9 + - python >=3.10 - python-dateutil >=2.7.0 - - types-python-dateutil >=2.8.10 + - python-tzdata + - python license: Apache-2.0 - license_family: Apache + license_family: APACHE purls: - pkg:pypi/arrow?source=hash-mapping - size: 99951 - timestamp: 1733584345583 -- pypi: https://files.pythonhosted.org/packages/2b/79/0f6eacbe575c3b677d70193c22b68c3a95bec68204ccf53068b657c7ed06/astropy-7.1.0-cp313-cp313-macosx_11_0_arm64.whl + size: 113854 + timestamp: 1760831179410 +- pypi: https://files.pythonhosted.org/packages/a6/ba/3418133ba144dfcd1530bca5a6b695f4cdd21a8abaaa2ac4e5450d11b028/astropy-7.2.0-cp311-abi3-macosx_11_0_arm64.whl name: astropy - version: 7.1.0 - sha256: 734d5e93e76d2e2175f991b17ca130463f2edb56c107fbc07c6532ca461a6ea7 + version: 7.2.0 + sha256: 52e9a7d9c86b21f1af911a2930cd0c4a275fb302d455c89e11eedaffef6f2ad0 requires_dist: - - numpy>=1.23.2 + - numpy>=1.24 - pyerfa>=2.0.1.1 - - astropy-iers-data>=0.2025.4.28.0.37.27 + - astropy-iers-data>=0.2025.10.27.0.39.10 - pyyaml>=6.0.0 - packaging>=22.0.0 - scipy>=1.9.2 ; extra == 'recommended' - - matplotlib>=3.6.0 ; extra == 'recommended' + - matplotlib>=3.8.0 ; extra == 'recommended' + - narwhals>=1.42.0 ; extra == 'recommended' - ipython>=8.0.0 ; extra == 'ipython' - astropy[ipython] ; extra == 'jupyter' - ipywidgets>=7.7.3 ; extra == 'jupyter' - ipykernel>=6.16.0 ; extra == 'jupyter' - ipydatagrid>=1.1.13 ; extra == 'jupyter' - jupyter-core>=4.11.2 ; extra == 'jupyter' - - pandas>=1.5.0 ; extra == 'jupyter' + - pandas>=2.0.0 ; extra == 'jupyter' - astropy[recommended] ; extra == 'all' - astropy[ipython] ; extra == 'all' - astropy[jupyter] ; extra == 'all' - certifi>=2022.6.15.1 ; extra == 'all' - - dask[array]>=2022.5.1 ; extra == 'all' - - h5py>=3.8.0 ; extra == 'all' - - pyarrow>=10.0.1 ; extra == 'all' + - dask[dataframe]>=2024.8.0 ; extra == 'all' + - h5py>=3.9.0 ; extra == 'all' + - pyarrow>=14.0.2 ; extra == 'all' - beautifulsoup4>=4.9.3 ; extra == 'all' - html5lib>=1.1 ; extra == 'all' - bleach>=3.2.1 ; extra == 'all' - - pandas>=2.0 ; extra == 'all' - - sortedcontainers>=1.5.7 ; extra == 'all' + - sortedcontainers>=2.1.0 ; extra == 'all' - pytz>=2016.10 ; extra == 'all' - - jplephem>=2.6 ; extra == 'all' + - jplephem>=2.17.0 ; extra == 'all' - mpmath>=1.2.1 ; extra == 'all' - - asdf>=2.8.3 ; extra == 'all' - asdf-astropy>=0.3 ; extra == 'all' - bottleneck>=1.3.3 ; extra == 'all' - fsspec[http]>=2023.4.0 ; extra == 'all' @@ -1693,17 +2418,19 @@ packages: - threadpoolctl>=3.0.0 ; extra == 'test' - astropy[all] ; extra == 'test-all' - astropy[test] ; extra == 'test-all' - - objgraph>=1.6.0 ; extra == 'test-all' - - skyfield>=1.20 ; extra == 'test-all' + - objgraph>=3.1.2 ; extra == 'test-all' + - skyfield>=1.42.0 ; extra == 'test-all' - sgp4>=2.3 ; extra == 'test-all' - array-api-strict>=1.0 ; extra == 'test-all' - - pandas-stubs>=2.0 ; extra == 'typing' + - array-api-strict<2.4 ; python_full_version < '3.12' and extra == 'test-all' + - pandas-stubs>=2.0.0 ; extra == 'typing' + - narwhals>=1.42.0 ; extra == 'typing' - astropy[recommended] ; extra == 'docs' - - sphinx ; extra == 'docs' + - sphinx>=8.2.0 ; extra == 'docs' - sphinx-astropy[confv2]>=1.9.1 ; extra == 'docs' - pytest>=8.0.0 ; extra == 'docs' - sphinx-changelog>=1.2.0 ; extra == 'docs' - - sphinx-design ; extra == 'docs' + - sphinx-design>=0.6.1 ; extra == 'docs' - jinja2>=3.1.3 ; extra == 'docs' - sphinxcontrib-globalsubs>=0.1.1 ; extra == 'docs' - matplotlib>=3.9.1 ; extra == 'docs' @@ -1711,45 +2438,44 @@ packages: - astropy[test] ; extra == 'dev' - astropy[docs] ; extra == 'dev' - astropy[typing] ; extra == 'dev' - - tox ; extra == 'dev-all' + - tox>=4.22.0 ; extra == 'dev-all' - astropy[dev] ; extra == 'dev-all' - astropy[test-all] ; extra == 'dev-all' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/fb/69/a34f20db7146912f25e2487c5283f1ae2aed5d24f615fa976439ece35f7e/astropy-7.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/c3/1c/f06ad85180e7dd9855aa5ede901bfc2be858d7bee17d4e978a14c0ecec14/astropy-7.2.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: astropy - version: 7.1.0 - sha256: 654261df547c150e5b6a022f3785f47a2e547e0cc1c06fbcf6293b5f4e85722a + version: 7.2.0 + sha256: 2f39ce2c80211fbceb005d377a5478cd0d66c42aa1498d252f2239fe5a025c24 requires_dist: - - numpy>=1.23.2 + - numpy>=1.24 - pyerfa>=2.0.1.1 - - astropy-iers-data>=0.2025.4.28.0.37.27 + - astropy-iers-data>=0.2025.10.27.0.39.10 - pyyaml>=6.0.0 - packaging>=22.0.0 - scipy>=1.9.2 ; extra == 'recommended' - - matplotlib>=3.6.0 ; extra == 'recommended' + - matplotlib>=3.8.0 ; extra == 'recommended' + - narwhals>=1.42.0 ; extra == 'recommended' - ipython>=8.0.0 ; extra == 'ipython' - astropy[ipython] ; extra == 'jupyter' - ipywidgets>=7.7.3 ; extra == 'jupyter' - ipykernel>=6.16.0 ; extra == 'jupyter' - ipydatagrid>=1.1.13 ; extra == 'jupyter' - jupyter-core>=4.11.2 ; extra == 'jupyter' - - pandas>=1.5.0 ; extra == 'jupyter' + - pandas>=2.0.0 ; extra == 'jupyter' - astropy[recommended] ; extra == 'all' - astropy[ipython] ; extra == 'all' - astropy[jupyter] ; extra == 'all' - certifi>=2022.6.15.1 ; extra == 'all' - - dask[array]>=2022.5.1 ; extra == 'all' - - h5py>=3.8.0 ; extra == 'all' - - pyarrow>=10.0.1 ; extra == 'all' + - dask[dataframe]>=2024.8.0 ; extra == 'all' + - h5py>=3.9.0 ; extra == 'all' + - pyarrow>=14.0.2 ; extra == 'all' - beautifulsoup4>=4.9.3 ; extra == 'all' - html5lib>=1.1 ; extra == 'all' - bleach>=3.2.1 ; extra == 'all' - - pandas>=2.0 ; extra == 'all' - - sortedcontainers>=1.5.7 ; extra == 'all' + - sortedcontainers>=2.1.0 ; extra == 'all' - pytz>=2016.10 ; extra == 'all' - - jplephem>=2.6 ; extra == 'all' + - jplephem>=2.17.0 ; extra == 'all' - mpmath>=1.2.1 ; extra == 'all' - - asdf>=2.8.3 ; extra == 'all' - asdf-astropy>=0.3 ; extra == 'all' - bottleneck>=1.3.3 ; extra == 'all' - fsspec[http]>=2023.4.0 ; extra == 'all' @@ -1765,17 +2491,19 @@ packages: - threadpoolctl>=3.0.0 ; extra == 'test' - astropy[all] ; extra == 'test-all' - astropy[test] ; extra == 'test-all' - - objgraph>=1.6.0 ; extra == 'test-all' - - skyfield>=1.20 ; extra == 'test-all' + - objgraph>=3.1.2 ; extra == 'test-all' + - skyfield>=1.42.0 ; extra == 'test-all' - sgp4>=2.3 ; extra == 'test-all' - array-api-strict>=1.0 ; extra == 'test-all' - - pandas-stubs>=2.0 ; extra == 'typing' + - array-api-strict<2.4 ; python_full_version < '3.12' and extra == 'test-all' + - pandas-stubs>=2.0.0 ; extra == 'typing' + - narwhals>=1.42.0 ; extra == 'typing' - astropy[recommended] ; extra == 'docs' - - sphinx ; extra == 'docs' + - sphinx>=8.2.0 ; extra == 'docs' - sphinx-astropy[confv2]>=1.9.1 ; extra == 'docs' - pytest>=8.0.0 ; extra == 'docs' - sphinx-changelog>=1.2.0 ; extra == 'docs' - - sphinx-design ; extra == 'docs' + - sphinx-design>=0.6.1 ; extra == 'docs' - jinja2>=3.1.3 ; extra == 'docs' - sphinxcontrib-globalsubs>=0.1.1 ; extra == 'docs' - matplotlib>=3.9.1 ; extra == 'docs' @@ -1783,33 +2511,33 @@ packages: - astropy[test] ; extra == 'dev' - astropy[docs] ; extra == 'dev' - astropy[typing] ; extra == 'dev' - - tox ; extra == 'dev-all' + - tox>=4.22.0 ; extra == 'dev-all' - astropy[dev] ; extra == 'dev-all' - astropy[test-all] ; extra == 'dev-all' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/c5/38/be921eaf8fc67333fd2c2657ea965103ccc4050c29a66d5fd5f3db881c0f/astropy_iers_data-0.2025.10.6.0.35.25-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/02/48/501f82ad7806e57bed6e2a4b3cca0897302f51ddfb7ac24891fe6a200360/astropy_iers_data-0.2025.12.1.0.45.12-py3-none-any.whl name: astropy-iers-data - version: 0.2025.10.6.0.35.25 - sha256: b6f572ee3ae4e02e4ae377229b533ed89d6f8eb027ae31e380cbc8096735ba49 + version: 0.2025.12.1.0.45.12 + sha256: c7c14bb3c2f421b705253a129649db949ac2f9bee3d71d32955e6a8e0888d882 requires_dist: - pytest ; extra == 'docs' - hypothesis ; extra == 'test' - pytest ; extra == 'test' - pytest-remotedata ; extra == 'test' requires_python: '>=3.8' -- conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.0-pyhd8ed1ab_1.conda - sha256: 93b14414b3b3ed91e286e1cbe4e7a60c4e1b1c730b0814d1e452a8ac4b9af593 - md5: 8f587de4bcf981e26228f268df374a9b +- conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda + sha256: ee4da0f3fe9d59439798ee399ef3e482791e48784873d546e706d0935f9ff010 + md5: 9673a61a297b00016442e022d689faa6 depends: - - python >=3.9 + - python >=3.10 constrains: - - astroid >=2,<4 + - astroid >=2,<5 license: Apache-2.0 license_family: Apache purls: - - pkg:pypi/asttokens?source=hash-mapping - size: 28206 - timestamp: 1733250564754 + - pkg:pypi/asttokens?source=compressed-mapping + size: 28797 + timestamp: 1763410017955 - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.0.5-pyh29332c3_0.conda sha256: 3b7233041e462d9eeb93ea1dfe7b18aca9c358832517072054bb8761df0c324b md5: d9d0f99095a9bb7e3641bca8c6ad2ac7 @@ -1842,9 +2570,16 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/attrs?source=compressed-mapping + - pkg:pypi/attrs?source=hash-mapping size: 60101 timestamp: 1759762331492 +- pypi: https://files.pythonhosted.org/packages/f8/aa/5082412d1ee302e9e7d80b6949bc4d2a8fa1149aaab610c5fc24709605d6/authlib-1.6.5-py2.py3-none-any.whl + name: authlib + version: 1.6.5 + sha256: 3e0e0507807f842b02175507bdee8957a1d5707fd4afb17c32fb43fee90b6e3a + requires_dist: + - cryptography + requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.17.0-pyhd8ed1ab_0.conda sha256: 1c656a35800b7f57f7371605bc6507c8d3ad60fbaaec65876fce7f73df1fc8ac md5: 0a01c169f0ab0f91b26e77a3301fbfe4 @@ -1879,9 +2614,111 @@ packages: - pkg:pypi/backports-tarfile?source=hash-mapping size: 32786 timestamp: 1733325872620 -- conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.2-pyha770c72_0.conda - sha256: b949bd0121bb1eabc282c4de0551cc162b621582ee12b415e6f8297398e3b3b4 - md5: 749ebebabc2cae99b2e5b3edd04c6ca2 +- pypi: https://files.pythonhosted.org/packages/14/2a/fbcbf5a025d3e71ddafad7efd43e34ec4362f4d523c3c471b457148fb211/beartype-0.22.8-py3-none-any.whl + name: beartype + version: 0.22.8 + sha256: b832882d04e41a4097bab9f63e6992bc6de58c414ee84cba9b45b67314f5ab2e + requires_dist: + - autoapi>=0.9.0 ; extra == 'dev' + - celery ; extra == 'dev' + - click ; extra == 'dev' + - coverage>=5.5 ; extra == 'dev' + - docutils>=0.22.0 ; extra == 'dev' + - equinox ; python_full_version < '3.15' and sys_platform == 'linux' and extra == 'dev' + - fastmcp ; python_full_version < '3.14' and extra == 'dev' + - jax[cpu] ; python_full_version < '3.15' and sys_platform == 'linux' and extra == 'dev' + - jaxtyping ; sys_platform == 'linux' and extra == 'dev' + - langchain ; python_full_version < '3.14' and platform_python_implementation != 'PyPy' and sys_platform != 'darwin' and extra == 'dev' + - mypy>=0.800 ; platform_python_implementation != 'PyPy' and extra == 'dev' + - nuitka>=1.2.6 ; python_full_version < '3.14' and sys_platform == 'linux' and extra == 'dev' + - numba ; python_full_version < '3.14' and extra == 'dev' + - numpy ; python_full_version < '3.15' and platform_python_implementation != 'PyPy' and sys_platform != 'darwin' and extra == 'dev' + - pandera>=0.26.0 ; python_full_version < '3.14' and extra == 'dev' + - poetry ; extra == 'dev' + - polars ; python_full_version < '3.14' and extra == 'dev' + - pydata-sphinx-theme<=0.7.2 ; extra == 'dev' + - pygments ; extra == 'dev' + - pyright>=1.1.370 ; extra == 'dev' + - pytest>=6.2.0 ; extra == 'dev' + - redis ; extra == 'dev' + - rich-click ; extra == 'dev' + - setuptools ; extra == 'dev' + - sphinx ; extra == 'dev' + - sphinx>=4.2.0,<6.0.0 ; extra == 'dev' + - sphinxext-opengraph>=0.7.5 ; extra == 'dev' + - sqlalchemy ; extra == 'dev' + - torch ; python_full_version < '3.14' and sys_platform == 'linux' and extra == 'dev' + - tox>=3.20.1 ; extra == 'dev' + - typer ; extra == 'dev' + - typing-extensions>=3.10.0.0 ; extra == 'dev' + - xarray ; python_full_version < '3.15' and extra == 'dev' + - mkdocs-material[imaging]>=9.6.0 ; extra == 'doc-ghp' + - mkdocstrings-python-xref>=1.16.0 ; extra == 'doc-ghp' + - mkdocstrings-python>=1.16.0 ; extra == 'doc-ghp' + - autoapi>=0.9.0 ; extra == 'doc-rtd' + - pydata-sphinx-theme<=0.7.2 ; extra == 'doc-rtd' + - setuptools ; extra == 'doc-rtd' + - sphinx>=4.2.0,<6.0.0 ; extra == 'doc-rtd' + - sphinxext-opengraph>=0.7.5 ; extra == 'doc-rtd' + - celery ; extra == 'test' + - click ; extra == 'test' + - coverage>=5.5 ; extra == 'test' + - docutils>=0.22.0 ; extra == 'test' + - equinox ; python_full_version < '3.15' and sys_platform == 'linux' and extra == 'test' + - fastmcp ; python_full_version < '3.14' and extra == 'test' + - jax[cpu] ; python_full_version < '3.15' and sys_platform == 'linux' and extra == 'test' + - jaxtyping ; sys_platform == 'linux' and extra == 'test' + - langchain ; python_full_version < '3.14' and platform_python_implementation != 'PyPy' and sys_platform != 'darwin' and extra == 'test' + - mypy>=0.800 ; platform_python_implementation != 'PyPy' and extra == 'test' + - nuitka>=1.2.6 ; python_full_version < '3.14' and sys_platform == 'linux' and extra == 'test' + - numba ; python_full_version < '3.14' and extra == 'test' + - numpy ; python_full_version < '3.15' and platform_python_implementation != 'PyPy' and sys_platform != 'darwin' and extra == 'test' + - pandera>=0.26.0 ; python_full_version < '3.14' and extra == 'test' + - poetry ; extra == 'test' + - polars ; python_full_version < '3.14' and extra == 'test' + - pygments ; extra == 'test' + - pyright>=1.1.370 ; extra == 'test' + - pytest>=6.2.0 ; extra == 'test' + - redis ; extra == 'test' + - rich-click ; extra == 'test' + - sphinx ; extra == 'test' + - sqlalchemy ; extra == 'test' + - torch ; python_full_version < '3.14' and sys_platform == 'linux' and extra == 'test' + - tox>=3.20.1 ; extra == 'test' + - typer ; extra == 'test' + - typing-extensions>=3.10.0.0 ; extra == 'test' + - xarray ; python_full_version < '3.15' and extra == 'test' + - celery ; extra == 'test-tox' + - click ; extra == 'test-tox' + - docutils>=0.22.0 ; extra == 'test-tox' + - equinox ; python_full_version < '3.15' and sys_platform == 'linux' and extra == 'test-tox' + - fastmcp ; python_full_version < '3.14' and extra == 'test-tox' + - jax[cpu] ; python_full_version < '3.15' and sys_platform == 'linux' and extra == 'test-tox' + - jaxtyping ; sys_platform == 'linux' and extra == 'test-tox' + - langchain ; python_full_version < '3.14' and platform_python_implementation != 'PyPy' and sys_platform != 'darwin' and extra == 'test-tox' + - mypy>=0.800 ; platform_python_implementation != 'PyPy' and extra == 'test-tox' + - nuitka>=1.2.6 ; python_full_version < '3.14' and sys_platform == 'linux' and extra == 'test-tox' + - numba ; python_full_version < '3.14' and extra == 'test-tox' + - numpy ; python_full_version < '3.15' and platform_python_implementation != 'PyPy' and sys_platform != 'darwin' and extra == 'test-tox' + - pandera>=0.26.0 ; python_full_version < '3.14' and extra == 'test-tox' + - poetry ; extra == 'test-tox' + - polars ; python_full_version < '3.14' and extra == 'test-tox' + - pygments ; extra == 'test-tox' + - pyright>=1.1.370 ; extra == 'test-tox' + - pytest>=6.2.0 ; extra == 'test-tox' + - redis ; extra == 'test-tox' + - rich-click ; extra == 'test-tox' + - sphinx ; extra == 'test-tox' + - sqlalchemy ; extra == 'test-tox' + - torch ; python_full_version < '3.14' and sys_platform == 'linux' and extra == 'test-tox' + - typer ; extra == 'test-tox' + - typing-extensions>=3.10.0.0 ; extra == 'test-tox' + - xarray ; python_full_version < '3.15' and extra == 'test-tox' + - coverage>=5.5 ; extra == 'test-tox-coverage' + requires_python: '>=3.10' +- conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda + sha256: bf1e71c3c0a5b024e44ff928225a0874fc3c3356ec1a0b6fe719108e6d1288f6 + md5: 5267bef8efea4127aacd1f4e1f149b6e depends: - python >=3.10 - soupsieve >=1.2 @@ -1889,13 +2726,13 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/beautifulsoup4?source=compressed-mapping - size: 89146 - timestamp: 1759146127397 -- pypi: https://files.pythonhosted.org/packages/84/19/529e6ade85e681cb84d5e5c6501f2492f17ec2c8f8fe246a3898ffcb0e2c/bioblend-1.6.0-py3-none-any.whl + - pkg:pypi/beautifulsoup4?source=hash-mapping + size: 90399 + timestamp: 1764520638652 +- pypi: https://files.pythonhosted.org/packages/b4/ba/ff3c536ab50e179a4e1eef0ea3de7b878b6b81629ded651f0af7156a0f9c/bioblend-1.7.0-py3-none-any.whl name: bioblend - version: 1.6.0 - sha256: 7127e7b654d75595836d7910a197f690f11af2eed94dea05b440ee3b03cc5f86 + version: 1.7.0 + sha256: 91f5b5ca0f563a14f297efaa4b68e1b88b6c98704f4b7d898ea5e7661bac3c35 requires_dist: - pyyaml - requests>=2.20.0 @@ -1903,11 +2740,11 @@ packages: - tuspy - pytest ; extra == 'testing' requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.2.0-pyh29332c3_4.conda - sha256: a05971bb80cca50ce9977aad3f7fc053e54ea7d5321523efc7b9a6e12901d3cd - md5: f0b4c8e370446ef89797608d60a564b3 +- conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_0.conda + sha256: e03ba1a2b93fe0383c57920a9dc6b4e0c2c7972a3f214d531ed3c21dc8f8c717 + md5: b1a27250d70881943cca0dd6b4ba0956 depends: - - python >=3.9 + - python >=3.10 - webencodings - python constrains: @@ -1915,18 +2752,18 @@ packages: license: Apache-2.0 AND MIT purls: - pkg:pypi/bleach?source=hash-mapping - size: 141405 - timestamp: 1737382993425 -- conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.2.0-h82add2a_4.conda - sha256: 0aba699344275b3972bd751f9403316edea2ceb942db12f9f493b63c74774a46 - md5: a30e9406c873940383555af4c873220d + size: 141952 + timestamp: 1763589981635 +- conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-h5f6438b_0.conda + sha256: f85f6b2c7938d8c20c80ce5b7e6349fafbb49294641b5648273c5f892b150768 + md5: 08a03378bc5293c6f97637323802f480 depends: - - bleach ==6.2.0 pyh29332c3_4 + - bleach ==6.3.0 pyhcf101f3_0 - tinycss2 license: Apache-2.0 AND MIT purls: [] - size: 4213 - timestamp: 1737382993425 + size: 4386 + timestamp: 1763589981639 - conda: https://conda.anaconda.org/conda-forge/linux-64/blosc-1.21.6-he440d0b_1.conda sha256: e7af5d1183b06a206192ff440e08db1c4e8b2ca1f8376ee45fb2f3a85d4ee45d md5: 2c2fae981fd2afd00812c92ac47d023d @@ -1969,121 +2806,121 @@ packages: - pkg:pypi/boltons?source=hash-mapping size: 302296 timestamp: 1749686302834 -- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-1.1.0-hb03c661_4.conda - sha256: 294526a54fa13635341729f250d0b1cf8f82cad1e6b83130304cbf3b6d8b74cc - md5: eaf3fbd2aa97c212336de38a51fe404e +- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-1.2.0-hed03a55_1.conda + sha256: e511644d691f05eb12ebe1e971fd6dc3ae55a4df5c253b4e1788b789bdf2dfa6 + md5: 8ccf913aaba749a5496c17629d859ed1 depends: - __glibc >=2.17,<3.0.a0 - - brotli-bin 1.1.0 hb03c661_4 - - libbrotlidec 1.1.0 hb03c661_4 - - libbrotlienc 1.1.0 hb03c661_4 + - brotli-bin 1.2.0 hb03c661_1 + - libbrotlidec 1.2.0 hb03c661_1 + - libbrotlienc 1.2.0 hb03c661_1 - libgcc >=14 license: MIT license_family: MIT purls: [] - size: 19883 - timestamp: 1756599394934 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-1.1.0-h6caf38d_4.conda - sha256: 8aa8ee52b95fdc3ef09d476cbfa30df722809b16e6dca4a4f80e581012035b7b - md5: ce8659623cea44cc812bc0bfae4041c5 + size: 20103 + timestamp: 1764017231353 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-1.2.0-h7d5ae5b_1.conda + sha256: 422ac5c91f8ef07017c594d9135b7ae068157393d2a119b1908c7e350938579d + md5: 48ece20aa479be6ac9a284772827d00c depends: - __osx >=11.0 - - brotli-bin 1.1.0 h6caf38d_4 - - libbrotlidec 1.1.0 h6caf38d_4 - - libbrotlienc 1.1.0 h6caf38d_4 + - brotli-bin 1.2.0 hc919400_1 + - libbrotlidec 1.2.0 hc919400_1 + - libbrotlienc 1.2.0 hc919400_1 license: MIT license_family: MIT purls: [] - size: 20003 - timestamp: 1756599758165 -- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.1.0-hb03c661_4.conda - sha256: 444903c6e5c553175721a16b7c7de590ef754a15c28c99afbc8a963b35269517 - md5: ca4ed8015764937c81b830f7f5b68543 + size: 20237 + timestamp: 1764018058424 +- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.2.0-hb03c661_1.conda + sha256: 64b137f30b83b1dd61db6c946ae7511657eead59fdf74e84ef0ded219605aa94 + md5: af39b9a8711d4a8d437b52c1d78eb6a1 depends: - __glibc >=2.17,<3.0.a0 - - libbrotlidec 1.1.0 hb03c661_4 - - libbrotlienc 1.1.0 hb03c661_4 + - libbrotlidec 1.2.0 hb03c661_1 + - libbrotlienc 1.2.0 hb03c661_1 - libgcc >=14 license: MIT license_family: MIT purls: [] - size: 19615 - timestamp: 1756599385418 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-bin-1.1.0-h6caf38d_4.conda - sha256: e57d402b02c9287b7c02d9947d7b7b55a4f7d73341c210c233f6b388d4641e08 - md5: ab57f389f304c4d2eb86d8ae46d219c3 + size: 21021 + timestamp: 1764017221344 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-bin-1.2.0-hc919400_1.conda + sha256: e2d142052a83ff2e8eab3fe68b9079cad80d109696dc063a3f92275802341640 + md5: 377d015c103ad7f3371be1777f8b584c depends: - __osx >=11.0 - - libbrotlidec 1.1.0 h6caf38d_4 - - libbrotlienc 1.1.0 h6caf38d_4 + - libbrotlidec 1.2.0 hc919400_1 + - libbrotlienc 1.2.0 hc919400_1 license: MIT license_family: MIT purls: [] - size: 17373 - timestamp: 1756599741779 -- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py312h1289d80_4.conda - sha256: 52a9ac412512b418ecdb364ba21c0f3dc96f0abbdb356b3cfbb980020b663d9b - md5: fd0e7746ed0676f008daacb706ce69e4 + size: 18628 + timestamp: 1764018033635 +- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py313hf159716_1.conda + sha256: dadec2879492adede0a9af0191203f9b023f788c18efd45ecac676d424c458ae + md5: 6c4d3597cf43f3439a51b2b13e29a4ba depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 - libstdcxx >=14 - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 constrains: - - libbrotlicommon 1.1.0 hb03c661_4 + - libbrotlicommon 1.2.0 hb03c661_1 license: MIT license_family: MIT purls: - - pkg:pypi/brotli?source=compressed-mapping - size: 354149 - timestamp: 1756599553574 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.1.0-py313hb4b7877_4.conda - sha256: a6402a7186ace5c3eb21ed4ce50eda3592c44ce38ab4e9a7ddd57d72b1e61fb3 - md5: 9518cd948fc334d66119c16a2106a959 + - pkg:pypi/brotli?source=hash-mapping + size: 367721 + timestamp: 1764017371123 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.2.0-py312h0dfefe5_1.conda + sha256: 6178775a86579d5e8eec6a7ab316c24f1355f6c6ccbe84bb341f342f1eda2440 + md5: 311fcf3f6a8c4eb70f912798035edd35 depends: - __osx >=11.0 - libcxx >=19 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 constrains: - - libbrotlicommon 1.1.0 h6caf38d_4 + - libbrotlicommon 1.2.0 hc919400_1 license: MIT license_family: MIT purls: - - pkg:pypi/brotli?source=compressed-mapping - size: 341104 - timestamp: 1756600117644 -- conda: https://conda.anaconda.org/conda-forge/linux-64/brunsli-0.1-he3183e4_1.conda - sha256: fddad9bb57ee7ec619a5cf4591151578a2501c3bf8cb3b4b066ac5b54c85a4dd - md5: 799ebfe432cb3949e246b69278ef851c + - pkg:pypi/brotli?source=hash-mapping + size: 359503 + timestamp: 1764018572368 +- conda: https://conda.anaconda.org/conda-forge/linux-64/brunsli-0.1-hd1e3526_2.conda + sha256: b4831ac06bb65561342cedf3d219cf9b096f20b8d62cda74f0177dffed79d4d5 + md5: 5948f4fead433c6e5c46444dbfb01162 depends: - __glibc >=2.17,<3.0.a0 - - libbrotlicommon >=1.1.0,<1.2.0a0 - - libbrotlidec >=1.1.0,<1.2.0a0 - - libbrotlienc >=1.1.0,<1.2.0a0 + - libbrotlicommon >=1.2.0,<1.3.0a0 + - libbrotlidec >=1.2.0,<1.3.0a0 + - libbrotlienc >=1.2.0,<1.3.0a0 - libgcc >=14 - libstdcxx >=14 license: MIT license_family: MIT purls: [] - size: 168813 - timestamp: 1757453968120 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/brunsli-0.1-h97083b6_1.conda - sha256: 3bf4ef58d2c11efe5926c5a2efc77f54f2e3905e5b3ed6ea7f129157f446a989 - md5: b36fe588d614b5dc3279e080a6925b3d + size: 168501 + timestamp: 1761758949420 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/brunsli-0.1-he0dfb12_2.conda + sha256: f32d7c6285601ac3f6baf0715b225fd017702cf24260c6f7f14f7b6e721d92bc + md5: 4cfe5258439d88ce2ef0b159007ee067 depends: - __osx >=11.0 - - libbrotlicommon >=1.1.0,<1.2.0a0 - - libbrotlidec >=1.1.0,<1.2.0a0 - - libbrotlienc >=1.1.0,<1.2.0a0 + - libbrotlicommon >=1.2.0,<1.3.0a0 + - libbrotlidec >=1.2.0,<1.3.0a0 + - libbrotlienc >=1.2.0,<1.3.0a0 - libcxx >=19 license: MIT license_family: MIT purls: [] - size: 141426 - timestamp: 1757454314055 + size: 141089 + timestamp: 1761759272675 - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_8.conda sha256: c30daba32ddebbb7ded490f0e371eae90f51e72db620554089103b4a6934b0d5 md5: 51a19bba1b8ebfb60df25cde030b7ebc @@ -2126,24 +2963,24 @@ packages: purls: [] size: 179696 timestamp: 1744128058734 -- conda: https://conda.anaconda.org/conda-forge/linux-64/c-blosc2-2.21.3-h4cfbee9_0.conda - sha256: 4a222cff1b3507b289352ab94d110974dad3dace11e2d0eea405ba3147764eba - md5: 93027b8ac9d0e596eb5b759ef56a03f1 +- conda: https://conda.anaconda.org/conda-forge/linux-64/c-blosc2-2.22.0-hc31b594_1.conda + sha256: efe06a982fe7f4e483a2043c4b43fc3598a538a66ed11364ee5b25d3400ef415 + md5: 52019609422a72ec80c32bbc16a889d8 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 - libstdcxx >=14 - lz4-c >=1.10.0,<1.11.0a0 - - zlib-ng >=2.2.5,<2.3.0a0 + - zlib-ng >=2.3.1,<2.4.0a0 - zstd >=1.5.7,<1.6.0a0 license: BSD-3-Clause license_family: BSD purls: [] - size: 350470 - timestamp: 1758798098407 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-blosc2-2.21.3-hb5916c8_0.conda - sha256: 245a0d5293d726cc740482cb27b1160ecccb79901c6412f570e51af4947050f3 - md5: 659358a2c4f5e15947f1ce755d36d4a6 + size: 352332 + timestamp: 1764291444176 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-blosc2-2.22.0-hb5916c8_0.conda + sha256: f21adb460fca9b99bb55072a1e2ba114debbaf74d6e0411433ac8e3759fe7fbc + md5: b608ba4ed02f5b56623de713fd70c151 depends: - __osx >=11.0 - libcxx >=19 @@ -2153,17 +2990,17 @@ packages: license: BSD-3-Clause license_family: BSD purls: [] - size: 254341 - timestamp: 1758798578175 -- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda - sha256: 3b5ad78b8bb61b6cdc0978a6a99f8dfb2cc789a451378d054698441005ecbdb6 - md5: f9e5fbc24009179e8b0409624691758a + size: 253964 + timestamp: 1761677915407 +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.11.12-hbd8a1cb_0.conda + sha256: b986ba796d42c9d3265602bc038f6f5264095702dd546c14bc684e60c385e773 + md5: f0991f0f84902f6b6009b4d2350a83aa depends: - __unix license: ISC purls: [] - size: 155907 - timestamp: 1759649036195 + size: 152432 + timestamp: 1762967197890 - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 noarch: python sha256: 561e6660f26c35d137ee150187d89767c988413c978e1b712d53f27ddf70ea17 @@ -2186,6 +3023,11 @@ packages: - pkg:pypi/cached-property?source=hash-mapping size: 11065 timestamp: 1615209567874 +- pypi: https://files.pythonhosted.org/packages/e6/46/eb6eca305c77a4489affe1c5d8f4cae82f285d9addd8de4ec084a7184221/cachetools-6.2.2-py3-none-any.whl + name: cachetools + version: 6.2.2 + sha256: 6c09c98183bf58560c97b2abfcedcbaf6a896a490f534b031b661d3723b45ace + requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/linux-64/cairo-1.18.4-h3394656_0.conda sha256: 3bd6a391ad60e471de76c0e9db34986c4b5058587fbf2efa5a7f54645e28c2c7 md5: 09262e66b19567aff4f592fb53b28760 @@ -2212,89 +3054,89 @@ packages: purls: [] size: 978114 timestamp: 1741554591855 -- conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.10.5-pyhd8ed1ab_0.conda - sha256: 955bac31be82592093f6bc006e09822cd13daf52b28643c9a6abd38cd5f4a306 - md5: 257ae203f1d204107ba389607d375ded +- conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.11.12-pyhd8ed1ab_0.conda + sha256: 083a2bdad892ccf02b352ecab38ee86c3e610ba9a4b11b073ea769d55a115d32 + md5: 96a02a5c1a65470a7e4eedb644c872fd depends: - python >=3.10 license: ISC purls: - - pkg:pypi/certifi?source=hash-mapping - size: 160248 - timestamp: 1759648987029 -- conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py312h35888ee_0.conda - sha256: f9e906b2cb9ae800b5818259472c3f781b14eb1952e867ac5c1f548e92bf02d9 - md5: 60b9cd087d22272885a6b8366b1d3d43 + - pkg:pypi/certifi?source=compressed-mapping + size: 157131 + timestamp: 1762976260320 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py313hf46b229_1.conda + sha256: 2162a91819945c826c6ef5efe379e88b1df0fe9a387eeba23ddcf7ebeacd5bd6 + md5: d0616e7935acab407d1543b28c446f6f depends: - __glibc >=2.17,<3.0.a0 - - libffi >=3.4.6,<3.5.0a0 + - libffi >=3.5.2,<3.6.0a0 - libgcc >=14 - pycparser - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 license: MIT license_family: MIT purls: - - pkg:pypi/cffi?source=compressed-mapping - size: 296986 - timestamp: 1758716192805 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py313h89bd988_0.conda - sha256: 97404dd3e363d7fe546ef317502a0f26a4f314b986adc700de2c9840084459cd - md5: 7768e6a259b378e0722b7f64e3f64c80 + - pkg:pypi/cffi?source=hash-mapping + size: 298357 + timestamp: 1761202966461 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py312h1b4d9a2_1.conda + sha256: 597e986ac1a1bd1c9b29d6850e1cdea4a075ce8292af55568952ec670e7dd358 + md5: 503ac138ad3cfc09459738c0f5750705 depends: - __osx >=11.0 - - libffi >=3.4.6,<3.5.0a0 + - libffi >=3.5.2,<3.6.0a0 - pycparser - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 license: MIT license_family: MIT purls: - - pkg:pypi/cffi?source=compressed-mapping - size: 291107 - timestamp: 1758716485269 -- conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_1.conda - sha256: d5696636733b3c301054b948cdd793f118efacce361d9bd4afb57d5980a9064f - md5: 57df494053e17dce2ac3a0b33e1b2a2e + - pkg:pypi/cffi?source=hash-mapping + size: 288080 + timestamp: 1761203317419 +- conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.5.0-pyhd8ed1ab_0.conda + sha256: aa589352e61bb221351a79e5946d56916e3c595783994884accdb3b97fe9d449 + md5: 381bd45fb7aa032691f3063aff47e3a1 depends: - - python >=3.9 + - python >=3.10 license: MIT license_family: MIT purls: - - pkg:pypi/cfgv?source=hash-mapping - size: 12973 - timestamp: 1734267180483 -- conda: https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.4-py312h4f23490_2.conda - sha256: ec8ec46cc9d9d17d904aa82a297708effcafb299fd22b07ca59cc278ec122b17 - md5: ff4b5976814bc00861f962276c8fb87f + - pkg:pypi/cfgv?source=compressed-mapping + size: 13589 + timestamp: 1763607964133 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.4-py313h29aa505_2.conda + sha256: 8cc3a1ce59dabdc875f39a96392444bf50c49aee94fe443a53c24a5792c65382 + md5: 1363e8db910e403edc8fd486f8470ec6 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 - numpy >=1.23,<3 - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 license: MIT license_family: MIT purls: - pkg:pypi/cftime?source=hash-mapping - size: 234335 - timestamp: 1756511954678 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cftime-1.6.4-py313h2732efb_2.conda - sha256: 25314a5f7b0173f4f3d6429b6b985bed7a7c6adc797cdc7c1266d60a53d987a1 - md5: 550a36cc5dcb02c359b4e2b8485df8d8 + size: 237570 + timestamp: 1756511905402 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cftime-1.6.4-py312hc7121bb_2.conda + sha256: df520ad3d99bf28aa4f8a5f5a04993418a001ef63c7bde215b32f9427da1f433 + md5: a2ed43e189d6f163a5b15ceebec4c09b depends: - __osx >=11.0 - numpy >=1.23,<3 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 license: MIT license_family: MIT purls: - pkg:pypi/cftime?source=hash-mapping - size: 192061 - timestamp: 1756512120915 + size: 190885 + timestamp: 1756512150892 - conda: https://conda.anaconda.org/conda-forge/linux-64/charls-2.4.2-h59595ed_0.conda sha256: 18f1c43f91ccf28297f92b094c2c8dbe9c6e8241c0d3cbd6cda014a990660fdd md5: 4336bd67920dd504cd8c6761d6a99645 @@ -2316,47 +3158,54 @@ packages: purls: [] size: 116255 timestamp: 1684263271583 -- conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.3-pyhd8ed1ab_0.conda - sha256: 838d5a011f0e7422be6427becba3de743c78f3874ad2743c341accbba9bb2624 - md5: 7e7d5ef1b9ed630e4a1c358d6bc62284 +- conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.4-pyhd8ed1ab_0.conda + sha256: b32f8362e885f1b8417bac2b3da4db7323faa12d5db62b7fd6691c02d60d6f59 + md5: a22d1fd9bf98827e280a02875d9a007a depends: - - python >=3.9 + - python >=3.10 license: MIT license_family: MIT purls: - pkg:pypi/charset-normalizer?source=hash-mapping - size: 51033 - timestamp: 1754767444665 -- conda: https://conda.anaconda.org/conda-forge/linux-64/cmarkgfm-2024.11.20-py312h66e93f0_0.conda - sha256: 294643f1ad5cbaa8646f803b89cc2da2b43c41cf4d3855883662ab0bb5455d3e - md5: bf99b4a864e31ecd9244affd27f3ceb6 + size: 50965 + timestamp: 1760437331772 +- pypi: https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl + name: click + version: 8.3.1 + sha256: 981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6 + requires_dist: + - colorama ; sys_platform == 'win32' + requires_python: '>=3.10' +- conda: https://conda.anaconda.org/conda-forge/linux-64/cmarkgfm-2024.11.20-py313h07c4f96_1.conda + sha256: 8edab14df6259d9678c83ea876a56902da44b50022cda8789c706d4d5336a4b9 + md5: d57661f0421c6fdb9a617280e633e4c8 depends: - __glibc >=2.17,<3.0.a0 - cffi >=1.0.0 - - libgcc >=13 - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 + - libgcc >=14 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 license: MIT license_family: MIT purls: - pkg:pypi/cmarkgfm?source=hash-mapping - size: 139452 - timestamp: 1732193337513 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cmarkgfm-2024.11.20-py313h90d716c_0.conda - sha256: 1db57935a8b697598b9cea3ccebf528db76d81eb651aa1003d5843e008ac04ae - md5: ed5171986d1267e7d823ba589b4281fe + size: 142385 + timestamp: 1760363028602 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cmarkgfm-2024.11.20-py312h163523d_1.conda + sha256: 6aea1c9887e69ac0e37211023190fb1526ba356affbe537eb75da028aed666fe + md5: 660087d3044bbb4f8fe780dd4f6d767d depends: - __osx >=11.0 - cffi >=1.0.0 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 license: MIT license_family: MIT purls: - pkg:pypi/cmarkgfm?source=hash-mapping - size: 113823 - timestamp: 1732193485989 + size: 115798 + timestamp: 1760363773598 - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda sha256: ab29d57dc70786c1269633ba3dff20288b81664d3ff8d21af995742e2bb03287 md5: 962b9857ee8e7018c22f2776ffa0b2d7 @@ -2380,9 +3229,9 @@ packages: - pkg:pypi/comm?source=hash-mapping size: 14690 timestamp: 1753453984907 -- conda: https://conda.anaconda.org/conda-forge/linux-64/conda-25.9.0-py312h7900ff3_1.conda - sha256: 66feb9238ecde33be547b1c6ec1c5d1272833127049e8d862d8026a1289fc21a - md5: 2d4cc5218cfcdf31a199107a4dbf0760 +- conda: https://conda.anaconda.org/conda-forge/linux-64/conda-25.11.0-py313h78bf25f_0.conda + sha256: b2901fae65c9b80f22686188006dfc59681be0e99ee012cd923289e20858bab9 + md5: 83fe274b65a0f1c13e7c25e410c5b403 depends: - archspec >=0.2.3 - boltons >=23.0.0 @@ -2397,8 +3246,8 @@ packages: - platformdirs >=3.10.0 - pluggy >=1.0.0 - pycosat >=0.6.3 - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 - requests >=2.28.0,<3 - ruamel.yaml >=0.11.14,<0.19 - setuptools >=60.0.0 @@ -2406,18 +3255,18 @@ packages: - truststore >=0.8.0 - zstandard >=0.19.0 constrains: + - conda-build >=25.9 - conda-env >=2.6 - conda-content-trust >=0.1.1 - - conda-build >=25.9 license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/conda?source=hash-mapping - size: 1206623 - timestamp: 1759487861953 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/conda-25.9.0-py313h8f79df9_1.conda - sha256: aea531423e34f34ea31150edccb6eb60869bdf060298097509a470fd798b044e - md5: 998293195bdb057b4e2f06aab0e61f5e + size: 1233572 + timestamp: 1764077546296 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/conda-25.11.0-py312h81bd7bf_0.conda + sha256: 54363026721fc2fda0f330c3433a9189360b83d31336f84c46d12c9e5c7a5da8 + md5: 5f394a12ca104883a93b3ad5014d2b99 depends: - archspec >=0.2.3 - boltons >=23.0.0 @@ -2432,9 +3281,9 @@ packages: - platformdirs >=3.10.0 - pluggy >=1.0.0 - pycosat >=0.6.3 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 - requests >=2.28.0,<3 - ruamel.yaml >=0.11.14,<0.19 - setuptools >=60.0.0 @@ -2442,29 +3291,32 @@ packages: - truststore >=0.8.0 - zstandard >=0.19.0 constrains: + - conda-build >=25.9 - conda-content-trust >=0.1.1 - conda-env >=2.6 - - conda-build >=25.9 license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/conda?source=hash-mapping - size: 1227059 - timestamp: 1759488246744 -- conda: https://conda.anaconda.org/conda-forge/noarch/conda-libmamba-solver-25.4.0-pyhd8ed1ab_0.conda - sha256: 48999a7a6e300075e4ef1c85130614d75429379eea8fe78f18a38a8aab8da384 - md5: d62b8f745ff471d5594ad73605cb9b59 + size: 1216562 + timestamp: 1764077875688 +- conda: https://conda.anaconda.org/conda-forge/noarch/conda-libmamba-solver-25.11.0-pyhd8ed1ab_0.conda + sha256: c90742e4168529807f5acb0907a5f829b5d479ac3291d863b327917d74908c27 + md5: 48d786fa77e34d2a915db2248910e8f5 depends: - boltons >=23.0.0 - - conda >=24.11 + - conda >=25.9 - libmambapy >=2.0.0 - - python >=3.9 + - msgpack-python >=1.1.1 + - python >=3.10 + - requests >=2.28.0,<3 + - zstandard >=0.15 license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/conda-libmamba-solver?source=hash-mapping - size: 41985 - timestamp: 1745834587643 + size: 56528 + timestamp: 1764081424684 - conda: https://conda.anaconda.org/conda-forge/noarch/conda-package-handling-2.4.0-pyh7900ff3_2.conda sha256: 8b2b1c235b7cbfa8488ad88ff934bdad25bac6a4c035714681fbff85b602f3f0 md5: 32c158f481b4fd7630c565030f7bc482 @@ -2491,82 +3343,82 @@ packages: - pkg:pypi/conda-package-streaming?source=hash-mapping size: 21933 timestamp: 1751548225624 -- conda: https://conda.anaconda.org/conda-forge/noarch/conda-tree-1.1.1-pyhff2d567_0.conda - sha256: fe639101014a9110739a066652f8ad6ecc9eac62b431f278f89b5f81d1a08de4 - md5: 0188455b560a329f74523f5097a28a90 +- conda: https://conda.anaconda.org/conda-forge/noarch/conda-tree-1.1.2-pyhd8ed1ab_0.conda + sha256: 94ddd2c0fb738138d1733c2838413172a3864c25f820d7b74620f8894efeccb1 + md5: 8d659bd4054debf8a4ac24acd1fcc304 depends: - colorama - conda - networkx - - python >=3.9 + - python >=3.10 license: MIT license_family: MIT purls: - pkg:pypi/conda-tree?source=hash-mapping - size: 12895 - timestamp: 1732136733585 -- conda: https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.3.3-py312hd9148b4_2.conda - sha256: cedae3c71ad59b6796d182f9198e881738b7a2c7b70f18427d7788f3173befb2 - md5: bce621e43978c245261c76b45edeaa3d + size: 13556 + timestamp: 1764597802352 +- conda: https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.3.3-py313h7037e92_3.conda + sha256: c545751fd48f119f2c28635514e6aa6ae784d9a1d4eb0e10be16c776e961f333 + md5: 6186382cb34a9953bf2a18fc763dc346 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 - libstdcxx >=14 - numpy >=1.25 - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/contourpy?source=hash-mapping - size: 295534 - timestamp: 1756544766129 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/contourpy-1.3.3-py313hc50a443_2.conda - sha256: 7979594ebdb0e5e5b5d374af67e68a8f614d9a6de55417dad0b6b246a2399962 - md5: 5b18003b1d9e2b7806a19b9d464c7a50 + - pkg:pypi/contourpy?source=compressed-mapping + size: 297459 + timestamp: 1762525479137 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/contourpy-1.3.3-py312h84eede6_3.conda + sha256: ee6a2497f2d9aff6ec53b6998a37c546916b79118e386bb90a7cb1f389d35197 + md5: e3fbe173dea7137a6d766cbacf697df2 depends: - __osx >=11.0 - libcxx >=19 - numpy >=1.25 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/contourpy?source=hash-mapping - size: 260411 - timestamp: 1756545032560 -- conda: https://conda.anaconda.org/conda-forge/linux-64/coverage-7.10.7-py312h8a5da7c_0.conda - sha256: 31a5117c6b9ff110deafb007ca781f65409046973744ffb33072604481b333fd - md5: 03d83efc728a6721a0f1616a04a7fc84 + size: 258388 + timestamp: 1762525877844 +- conda: https://conda.anaconda.org/conda-forge/linux-64/coverage-7.12.0-py313h3dea7bd_0.conda + sha256: 73d28a272fb11be79232f43f35a205079773000cf076b65c8cebbb01d5fb8bfd + md5: 8ef99d298907bfd688a95cc714662ae7 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 - tomli license: Apache-2.0 license_family: APACHE purls: - - pkg:pypi/coverage?source=hash-mapping - size: 382934 - timestamp: 1758501072565 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/coverage-7.10.7-py313h7d74516_0.conda - sha256: f3e3414ffda0d03741ebbd60447114f81f362d3f568e434a963448303dd11565 - md5: 6165cb718b857579763bd1408459a530 + - pkg:pypi/coverage?source=compressed-mapping + size: 390635 + timestamp: 1763480857821 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/coverage-7.12.0-py312h5748b74_0.conda + sha256: 22f2ea856e3f43ce9645c6bd08e6de1d97c5fc0d9412bd0d86ccfa6422d4e166 + md5: 143507f2b7b8b91e7f13dd1fdf5ac134 depends: - __osx >=11.0 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 - tomli license: Apache-2.0 license_family: APACHE purls: - pkg:pypi/coverage?source=hash-mapping - size: 390053 - timestamp: 1758501053435 + size: 383339 + timestamp: 1763480811310 - conda: https://conda.anaconda.org/conda-forge/linux-64/cpp-expected-1.3.1-h171cf75_0.conda sha256: 0d9405d9f2de5d4b15d746609d87807aac10e269072d6408b769159762ed113d md5: d17488e343e4c5c0bd0db18b3934d517 @@ -2588,57 +3440,151 @@ packages: purls: [] size: 24960 timestamp: 1756734870487 -- conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.11-py312hd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.12-py312hd8ed1ab_1.conda noarch: generic - sha256: 7e7bc8e73a2f3736444a8564cbece7216464c00f0bc38e604b0c792ff60d621a - md5: e5279009e7a7f7edd3cd2880c502b3cc + sha256: b88c76a6d6b45378552ccfd9e88b2a073161fe83fd1294c8fa103ffd32f7934a + md5: 99d689ccc1a360639eec979fd7805be9 depends: - python >=3.12,<3.13.0a0 - python_abi * *_cp312 license: Python-2.0 purls: [] - size: 45852 - timestamp: 1749047748072 -- conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.7-py313hd8ed1ab_100.conda + size: 45767 + timestamp: 1761175217281 +- conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.10-py313hd8ed1ab_100.conda noarch: generic - sha256: e9ac20662d1c97ef96e44751a78c0057ec308f7cc208ef1fbdc868993c9f5eb3 - md5: c5623ddbd37c5dafa7754a83f97de01e + sha256: af094d0dbefdc770f8cc183f167fdee7ab27933b783db28d8a8d73bc59a1b876 + md5: 461990e34c5d23c92026c758ed8f8cb3 depends: - python >=3.13,<3.14.0a0 - python_abi * *_cp313 license: Python-2.0 purls: [] - size: 48174 - timestamp: 1756909387263 -- conda: https://conda.anaconda.org/conda-forge/linux-64/cryptography-46.0.2-py312hee9fe19_0.conda - sha256: 927bba47bb07ab18e7521521891654aabf241336886a72f28d7f4921591560ef - md5: 3c4a18c7e247b6db8cf5605b1846d511 + size: 48228 + timestamp: 1764752978745 +- pypi: https://files.pythonhosted.org/packages/1d/42/9c391dd801d6cf0d561b5890549d4b27bafcc53b39c31a817e69d87c625b/cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl + name: cryptography + version: 46.0.3 + sha256: 109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a + requires_dist: + - cffi>=1.14 ; python_full_version == '3.8.*' and platform_python_implementation != 'PyPy' + - cffi>=2.0.0 ; python_full_version >= '3.9' and platform_python_implementation != 'PyPy' + - typing-extensions>=4.13.2 ; python_full_version < '3.11' + - bcrypt>=3.1.5 ; extra == 'ssh' + - nox[uv]>=2024.4.15 ; extra == 'nox' + - cryptography-vectors==46.0.3 ; extra == 'test' + - pytest>=7.4.0 ; extra == 'test' + - pytest-benchmark>=4.0 ; extra == 'test' + - pytest-cov>=2.10.1 ; extra == 'test' + - pytest-xdist>=3.5.0 ; extra == 'test' + - pretend>=0.7 ; extra == 'test' + - certifi>=2024 ; extra == 'test' + - pytest-randomly ; extra == 'test-randomorder' + - sphinx>=5.3.0 ; extra == 'docs' + - sphinx-rtd-theme>=3.0.0 ; extra == 'docs' + - sphinx-inline-tabs ; extra == 'docs' + - pyenchant>=3 ; extra == 'docstest' + - readme-renderer>=30.0 ; extra == 'docstest' + - sphinxcontrib-spelling>=7.3.1 ; extra == 'docstest' + - build>=1.0.0 ; extra == 'sdist' + - ruff>=0.11.11 ; extra == 'pep8test' + - mypy>=1.14 ; extra == 'pep8test' + - check-sdist ; extra == 'pep8test' + - click>=8.0.1 ; extra == 'pep8test' + requires_python: '>=3.8,!=3.9.0,!=3.9.1' +- pypi: https://files.pythonhosted.org/packages/c9/56/e7e69b427c3878352c2fb9b450bd0e19ed552753491d39d7d0a2f5226d41/cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl + name: cryptography + version: 46.0.3 + sha256: a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec + requires_dist: + - cffi>=1.14 ; python_full_version == '3.8.*' and platform_python_implementation != 'PyPy' + - cffi>=2.0.0 ; python_full_version >= '3.9' and platform_python_implementation != 'PyPy' + - typing-extensions>=4.13.2 ; python_full_version < '3.11' + - bcrypt>=3.1.5 ; extra == 'ssh' + - nox[uv]>=2024.4.15 ; extra == 'nox' + - cryptography-vectors==46.0.3 ; extra == 'test' + - pytest>=7.4.0 ; extra == 'test' + - pytest-benchmark>=4.0 ; extra == 'test' + - pytest-cov>=2.10.1 ; extra == 'test' + - pytest-xdist>=3.5.0 ; extra == 'test' + - pretend>=0.7 ; extra == 'test' + - certifi>=2024 ; extra == 'test' + - pytest-randomly ; extra == 'test-randomorder' + - sphinx>=5.3.0 ; extra == 'docs' + - sphinx-rtd-theme>=3.0.0 ; extra == 'docs' + - sphinx-inline-tabs ; extra == 'docs' + - pyenchant>=3 ; extra == 'docstest' + - readme-renderer>=30.0 ; extra == 'docstest' + - sphinxcontrib-spelling>=7.3.1 ; extra == 'docstest' + - build>=1.0.0 ; extra == 'sdist' + - ruff>=0.11.11 ; extra == 'pep8test' + - mypy>=1.14 ; extra == 'pep8test' + - check-sdist ; extra == 'pep8test' + - click>=8.0.1 ; extra == 'pep8test' + requires_python: '>=3.8,!=3.9.0,!=3.9.1' +- conda: https://conda.anaconda.org/conda-forge/linux-64/cryptography-46.0.3-py313heb322e3_1.conda + sha256: beb4f2fa46bf3d550bf5bf2a07796be14cbe73ceebe43b28e634ee778b547e99 + md5: 4e6278c519f2766ea707361f81b33364 depends: - __glibc >=2.17,<3.0.a0 - cffi >=1.14 - libgcc >=14 - - openssl >=3.5.3,<4.0a0 - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 + - openssl >=3.5.4,<4.0a0 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 constrains: - __glibc >=2.17 license: Apache-2.0 AND BSD-3-Clause AND PSF-2.0 AND MIT license_family: BSD purls: - - pkg:pypi/cryptography?source=compressed-mapping - size: 1712569 - timestamp: 1759320688700 -- conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhd8ed1ab_1.conda - sha256: 9827efa891e507a91a8a2acf64e210d2aff394e1cde432ad08e1f8c66b12293c - md5: 44600c4667a319d67dbe0681fc0bc833 + - pkg:pypi/cryptography?source=hash-mapping + size: 1723198 + timestamp: 1764805305302 +- conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda + sha256: bb47aec5338695ff8efbddbc669064a3b10fe34ad881fb8ad5d64fbfa6910ed1 + md5: 4c2a8fef270f6c69591889b93f9f55c1 depends: - - python >=3.9 + - python >=3.10 + - python license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/cycler?source=hash-mapping - size: 13399 - timestamp: 1733332563512 + - pkg:pypi/cycler?source=compressed-mapping + size: 14778 + timestamp: 1764466758386 +- pypi: https://files.pythonhosted.org/packages/7a/e8/77a231ae531cf38765b75ddf27dae28bb5f70b41d8bb4f15ce1650e93f57/cyclopts-4.3.0-py3-none-any.whl + name: cyclopts + version: 4.3.0 + sha256: 91a30b69faf128ada7cfeaefd7d9649dc222e8b2a8697f1fc99e4ee7b7ca44f3 + requires_dist: + - attrs>=23.1.0 + - docstring-parser>=0.15,<4.0 + - rich-rst>=1.3.1,<2.0.0 + - rich>=13.6.0 + - tomli>=2.0.0 ; python_full_version < '3.11' + - typing-extensions>=4.8.0 ; python_full_version < '3.11' + - ipdb>=0.13.9 ; extra == 'debug' + - line-profiler>=3.5.1 ; extra == 'debug' + - coverage[toml]>=5.1 ; extra == 'dev' + - pre-commit>=2.16.0 ; extra == 'dev' + - pydantic>=2.11.2,<3.0.0 ; extra == 'dev' + - pytest-cov>=3.0.0 ; extra == 'dev' + - pytest-mock>=3.7.0 ; extra == 'dev' + - pytest>=8.2.0 ; extra == 'dev' + - pyyaml>=6.0.1 ; extra == 'dev' + - toml>=0.10.2,<1.0.0 ; extra == 'dev' + - trio>=0.10.0 ; extra == 'dev' + - gitpython>=3.1.31 ; extra == 'docs' + - myst-parser[linkify]>=3.0.1,<5.0.0 ; extra == 'docs' + - sphinx-autodoc-typehints>=1.25.2,<4.0.0 ; extra == 'docs' + - sphinx-copybutton>=0.5,<1.0 ; extra == 'docs' + - sphinx-rtd-dark-mode>=1.3.0,<2.0.0 ; extra == 'docs' + - sphinx-rtd-theme>=3.0.0,<4.0.0 ; extra == 'docs' + - sphinx>=7.4.7,<8.2.0 ; extra == 'docs' + - tomli>=2.0.0 ; python_full_version < '3.11' and extra == 'toml' + - trio>=0.10.0 ; extra == 'trio' + - pyyaml>=6.0.1 ; extra == 'yaml' + requires_python: '>=3.10' - conda: https://conda.anaconda.org/conda-forge/linux-64/cyrus-sasl-2.1.28-hd9c7081_0.conda sha256: ee09ad7610c12c7008262d713416d0b58bf365bc38584dce48950025850bdf3f md5: cae723309a49399d2949362f4ab5c9e4 @@ -2673,52 +3619,51 @@ packages: purls: [] size: 316394 timestamp: 1685695959391 -- conda: https://conda.anaconda.org/conda-forge/linux-64/dbus-1.16.2-h3c4dab8_0.conda - sha256: 3b988146a50e165f0fa4e839545c679af88e4782ec284cc7b6d07dd226d6a068 - md5: 679616eb5ad4e521c83da4650860aba7 +- conda: https://conda.anaconda.org/conda-forge/linux-64/dbus-1.16.2-h24cb091_1.conda + sha256: 8bb557af1b2b7983cf56292336a1a1853f26555d9c6cecf1e5b2b96838c9da87 + md5: ce96f2f470d39bd96ce03945af92e280 depends: - - libstdcxx >=13 - - libgcc >=13 - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - libexpat >=2.7.0,<3.0a0 + - libgcc >=14 + - libstdcxx >=14 - libzlib >=1.3.1,<2.0a0 - - libglib >=2.84.2,<3.0a0 - license: GPL-2.0-or-later - license_family: GPL + - libglib >=2.86.2,<3.0a0 + - libexpat >=2.7.3,<3.0a0 + license: AFL-2.1 OR GPL-2.0-or-later purls: [] - size: 437860 - timestamp: 1747855126005 -- conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.17-py312h8285ef7_0.conda - sha256: c715221c434f7762dc2709239b32f61c0df5e3da94cc0d34f2d2be4acbb5099f - md5: 14938d17d7a91e2bf132330c7f2f61a2 + size: 447649 + timestamp: 1764536047944 +- conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.17-py313h5d5ffb9_0.conda + sha256: 4c12ca7541d488f64ee92d6368e9a0a418e919c0b8c51517ff329b4259b4aaf8 + md5: be318961d544421f4c8d8a91bff4f118 depends: - python + - libgcc >=14 - libstdcxx >=14 - libgcc >=14 - __glibc >=2.17,<3.0.a0 - - python_abi 3.12.* *_cp312 + - python_abi 3.13.* *_cp313 license: MIT license_family: MIT purls: - pkg:pypi/debugpy?source=hash-mapping - size: 2855535 - timestamp: 1758162043806 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.17-py313hc37fe24_0.conda - sha256: ada3d5ab7e33fdefe66b7d21f2a7876e6a00ba6d8866ee1b2101b9a34d1fad66 - md5: 42070edf971f5e14d0f51670ea1fb5e0 + size: 2868018 + timestamp: 1758162048107 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.17-py312h56d30c9_0.conda + sha256: a4b6bc13efa1bfa803650697b0db67349fa06e5afedac4098884aabafe391ab1 + md5: 474409589d1e7dc64692837012d752a9 depends: - python - __osx >=11.0 + - python 3.12.* *_cpython - libcxx >=19 - - python 3.13.* *_cp313 - - python_abi 3.13.* *_cp313 + - python_abi 3.12.* *_cp312 license: MIT license_family: MIT purls: - pkg:pypi/debugpy?source=hash-mapping - size: 2757716 - timestamp: 1758162092566 + size: 2752026 + timestamp: 1758162054436 - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda sha256: c17c6b9937c08ad63cb20a26f403a3234088e57d4455600974a0ce865cb14017 md5: 9ce473d1d1be1cc3810856a48b3fab32 @@ -2741,6 +3686,11 @@ packages: - pkg:pypi/defusedxml?source=hash-mapping size: 24062 timestamp: 1615232388757 +- pypi: https://files.pythonhosted.org/packages/3f/27/4570e78fc0bf5ea0ca45eb1de3818a23787af9b390c0b0a0033a1b8236f9/diskcache-5.6.3-py3-none-any.whl + name: diskcache + version: 5.6.3 + sha256: 5e31b2d5fbad117cc363ebaf6b689474db18a1f6438bc82358b024abd4c2ca19 + requires_python: '>=3' - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.0-pyhd8ed1ab_0.conda sha256: 6d977f0b2fc24fee21a9554389ab83070db341af6d6f09285360b2e09ef8b26e md5: 003b8ba0a94e2f1e117d0bd46aebc901 @@ -2763,6 +3713,22 @@ packages: - pkg:pypi/distro?source=hash-mapping size: 41773 timestamp: 1734729953882 +- pypi: https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl + name: docstring-parser + version: 0.17.0 + sha256: cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708 + requires_dist: + - pre-commit>=2.16.0 ; python_full_version >= '3.9' and extra == 'dev' + - pydoctor>=25.4.0 ; extra == 'dev' + - pytest ; extra == 'dev' + - pydoctor>=25.4.0 ; extra == 'docs' + - pytest ; extra == 'test' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/11/a8/c6a4b901d17399c77cd81fb001ce8961e9f5e04d3daf27e8925cb012e163/docutils-0.22.3-py3-none-any.whl + name: docutils + version: 0.22.3 + sha256: bd772e4aca73aff037958d44f2be5229ded4c09927fcf8690c577b66234d6ceb + requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/noarch/docutils-0.21.2-pyhd8ed1ab_1.conda sha256: fa5966bb1718bbf6967a85075e30e4547901410cc7cb7b16daf68942e9a94823 md5: 24c1ca34138ee57de72a943237cde4cc @@ -2831,28 +3797,28 @@ packages: - pkg:pypi/edffile?source=hash-mapping size: 18942 timestamp: 1737083955274 -- conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda - sha256: ce61f4f99401a4bd455b89909153b40b9c823276aefcbb06f2044618696009ca - md5: 72e42d28960d875c7654614f8b50939a +- conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + sha256: ee6cf346d017d954255bbcbdb424cddea4d14e4ed7e9813e429db1d795d01144 + md5: 8e662bd460bda79b1ea39194e3c4c9ab depends: - - python >=3.9 + - python >=3.10 - typing_extensions >=4.6.0 license: MIT and PSF-2.0 purls: - pkg:pypi/exceptiongroup?source=hash-mapping - size: 21284 - timestamp: 1746947398083 -- conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda - sha256: 9abc6c128cd40733e9b24284d0462e084d4aff6afe614f0754aa8533ebe505e4 - md5: a71efeae2c160f6789900ba2631a2c90 + size: 21333 + timestamp: 1763918099466 +- conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.2-pyhd8ed1ab_0.conda + sha256: 1acc6a420efc5b64c384c1f35f49129966f8a12c93b4bb2bdc30079e5dc9d8a8 + md5: a57b4be42619213a94f31d2c69c5dda7 depends: - - python >=3.9 + - python >=3.10 license: MIT license_family: MIT purls: - - pkg:pypi/execnet?source=hash-mapping - size: 38835 - timestamp: 1733231086305 + - pkg:pypi/execnet?source=compressed-mapping + size: 39499 + timestamp: 1762974150770 - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda sha256: 210c8165a58fdbf16e626aac93cc4c14dbd551a01d1516be5ecad795d2422cad md5: ff9efb7f7469aed3c4a8106ffa29593c @@ -2861,42 +3827,64 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/executing?source=compressed-mapping + - pkg:pypi/executing?source=hash-mapping size: 30753 timestamp: 1756729456476 -- conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.19.1-pyhd8ed1ab_0.conda - sha256: 7a2497c775cc7da43b5e32fc5cf9f4e8301ca723f0eb7f808bbe01c6094a3693 - md5: 9c418d067409452b2e87e0016257da68 +- pypi: https://files.pythonhosted.org/packages/db/bc/56925f1202357dbfcfdfd0c75afc6c27ec1e6ef1d89b7e7410df3945ceb4/fastmcp-2.13.3-py3-none-any.whl + name: fastmcp + version: 2.13.3 + sha256: 5173d335f4e6aabcfb5a5131af3fa092f604b303130fd3a49226b7a844a48e65 + requires_dist: + - authlib>=1.6.5 + - cyclopts>=4.0.0 + - exceptiongroup>=1.2.2 + - httpx>=0.28.1 + - jsonschema-path>=0.3.4 + - mcp>=1.19.0,!=1.21.1,<1.23 + - openapi-pydantic>=0.5.1 + - platformdirs>=4.0.0 + - py-key-value-aio[disk,memory]>=0.2.8,<0.4.0 + - pydantic[email]>=2.11.7 + - pyperclip>=1.9.0 + - python-dotenv>=1.1.0 + - rich>=13.9.4 + - uvicorn>=0.35 + - websockets>=15.0.1 + - openai>=1.102.0 ; extra == 'openai' + requires_python: '>=3.10' +- conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.20.0-pyhd8ed1ab_0.conda + sha256: 19025a4078ff3940d97eb0da29983d5e0deac9c3e09b0eabf897daeaf9d1114e + md5: 66b8b26023b8efdf8fcb23bac4b6325d depends: - - python >=3.9 + - python >=3.10 license: Unlicense purls: - - pkg:pypi/filelock?source=compressed-mapping - size: 18003 - timestamp: 1755216353218 -- conda: https://conda.anaconda.org/conda-forge/linux-64/fmt-11.2.0-h07f6e7f_0.conda - sha256: e0f53b7801d0bcb5d61a1ddcb873479bfe8365e56fd3722a232fbcc372a9ac52 - md5: 0c2f855a88fab6afa92a7aa41217dc8e + - pkg:pypi/filelock?source=hash-mapping + size: 17976 + timestamp: 1759948208140 +- conda: https://conda.anaconda.org/conda-forge/linux-64/fmt-12.0.0-h2b0788b_0.conda + sha256: b546c4eb5e11c2d8eab0685593e078fd0cd483e467d5d6e307d60d887488230f + md5: d90bf58b03d9a958cb4f9d3de539af17 depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - libstdcxx >=13 + - libgcc >=14 + - libstdcxx >=14 license: MIT license_family: MIT purls: [] - size: 192721 - timestamp: 1751277120358 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/fmt-11.2.0-h440487c_0.conda - sha256: 1449ec46468860f6fb77edba87797ce22d4f6bfe8d5587c46fd5374c4f7383ee - md5: 24109723ac700cce5ff96ea3e63a83a3 + size: 197164 + timestamp: 1760369692240 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/fmt-12.0.0-h669d743_0.conda + sha256: 2d14f30be9ef23efd1776166a68f01d7b561b7a04a7846cb0cc5a46021ff82df + md5: 364025d9b6f6305a73f8a5e84a2310d5 depends: - __osx >=11.0 - - libcxx >=18 + - libcxx >=19 license: MIT license_family: MIT purls: [] - size: 177090 - timestamp: 1751277262419 + size: 179725 + timestamp: 1760370178553 - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 sha256: 58d7f40d2940dd0a8aa28651239adbf5613254df0f75789919c4e6762054403b md5: 0c96522c6bdaed4b1566d11387caaf45 @@ -2954,52 +3942,52 @@ packages: purls: [] size: 3667 timestamp: 1566974674465 -- conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2 - sha256: 53f23a3319466053818540bcdf2091f253cbdbab1e0e9ae7b9e509dcaa2a5e38 - md5: f766549260d6815b0c52253f1fb1bb29 +- conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda + sha256: 54eea8469786bc2291cc40bca5f46438d3e062a399e8f53f013b6a9f50e98333 + md5: a7970cd949a077b7cb9696379d338681 depends: - - font-ttf-dejavu-sans-mono + - font-ttf-ubuntu - font-ttf-inconsolata + - font-ttf-dejavu-sans-mono - font-ttf-source-code-pro - - font-ttf-ubuntu license: BSD-3-Clause license_family: BSD purls: [] - size: 4102 - timestamp: 1566932280397 -- conda: https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.60.1-py312h8a5da7c_0.conda - sha256: 1be46e58f063c1f563f114df9e78bcb70c4b59760104c5456bbe3b0cb17af9cf - md5: b12bb9cc477156ce84038e0be6d0f763 + size: 4059 + timestamp: 1762351264405 +- conda: https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.61.0-py313h3dea7bd_0.conda + sha256: edea7e66bb068551cab49335776e26e4003af3acec78d643d7142cd39a8db670 + md5: 92f09729a821c52943d4b0b3749a2380 depends: - __glibc >=2.17,<3.0.a0 - brotli - libgcc >=14 - munkres - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 - - unicodedata2 >=15.1.0 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 license: MIT license_family: MIT purls: - pkg:pypi/fonttools?source=hash-mapping - size: 2888637 - timestamp: 1759187635166 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/fonttools-4.60.1-py313h7d74516_0.conda - sha256: 19460daa027062c663ff0a5b9c39d531c94937f2e5042cc00a706f4136d6cfc7 - md5: 107233e5dccf267cfc6fd551a10aea4e + size: 2956476 + timestamp: 1764353003818 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/fonttools-4.61.0-py312h5748b74_0.conda + sha256: b5ce806bc4061beb212d30cc312fd42339972506905212d9745108b3d2fc269b + md5: 581124b2a1391b41f7c9ea9cae1b699d depends: - __osx >=11.0 - brotli - munkres - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 + - unicodedata2 >=15.1.0 license: MIT license_family: MIT purls: - pkg:pypi/fonttools?source=hash-mapping - size: 2868336 - timestamp: 1759187425694 + size: 2897086 + timestamp: 1764353307542 - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda sha256: 2509992ec2fd38ab27c7cdb42cf6cadc566a1cc0d1021a2673475d9fa87c6276 md5: d3549fd50d450b6d9e7dddff25dd2110 @@ -3032,43 +4020,43 @@ packages: purls: [] size: 173399 timestamp: 1757947175403 -- conda: https://conda.anaconda.org/conda-forge/linux-64/frozendict-2.4.6-py312h4c3975b_1.conda - sha256: 8a8f891211b0706e9e5f19b702f8f3c103c83b82c878832dcfee7524189a66c5 - md5: 61b9cc4bc7a93aec29886b95a3c68950 +- conda: https://conda.anaconda.org/conda-forge/linux-64/frozendict-2.4.7-py313h07c4f96_0.conda + sha256: 1dc15e9bc7c457a36b238696bca67d2e330aa1596320ad9673ed0e5c4aaa94bc + md5: 4a1bc2334e0e0f1b062e1f6d8a30e9b4 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 license: LGPL-3.0-only license_family: LGPL purls: - pkg:pypi/frozendict?source=hash-mapping - size: 31281 - timestamp: 1756048056756 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/frozendict-2.4.6-py313hcdf3177_1.conda - sha256: 2b33347ba4208eaa9784987ac4c5915235455f54b0f8a432381c1c67cd2c7796 - md5: e97127b6918a9307e68ba2b73762c958 + size: 31275 + timestamp: 1763082974198 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/frozendict-2.4.7-py312h4409184_0.conda + sha256: f67aa4071a283af3e8f2ed72bf973dd3b3ac0df5ba350662fca9e8d9b001bad2 + md5: 6a6f7794843c151f539dbbac9f8193d0 depends: - __osx >=11.0 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 license: LGPL-3.0-only license_family: LGPL purls: - pkg:pypi/frozendict?source=hash-mapping - size: 31640 - timestamp: 1756048167743 -- pypi: https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl + size: 31857 + timestamp: 1763083173824 +- pypi: https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl name: frozenlist version: 1.8.0 - sha256: f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40 + sha256: f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl name: frozenlist version: 1.8.0 - sha256: 494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383 + sha256: fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027 requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.2-hd590300_0.conda sha256: aac402a8298f0c0cc528664249170372ef6b37ac39fdc92b40601a6aed1e32ff @@ -3126,43 +4114,43 @@ packages: - pkg:pypi/h2?source=compressed-mapping size: 95967 timestamp: 1756364871835 -- conda: https://conda.anaconda.org/conda-forge/linux-64/h5py-3.14.0-nompi_py312ha4f8f14_101.conda - sha256: 6736b00b257aecef97e5e607ff275780cacdec48ff85963fe53abeb9ee4fb53f - md5: fff67e7204b34a6e82ccf076786d1a7a +- conda: https://conda.anaconda.org/conda-forge/linux-64/h5py-3.15.1-nompi_py313h253c126_101.conda + sha256: 2de2c63ad6e7483456f6ff359380df63edf32770c140ec08c904ff89b6ed3903 + md5: 5d90c98527ecc832287115d57c121062 depends: - __glibc >=2.17,<3.0.a0 - cached-property - hdf5 >=1.14.6,<1.14.7.0a0 - libgcc >=14 - numpy >=1.23,<3 - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/h5py?source=hash-mapping - size: 1328987 - timestamp: 1756767099673 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/h5py-3.14.0-nompi_py313ha10fd41_101.conda - sha256: d7defda21b05f5b61c3c4ae3ffb2ef6f811daf9f7aeca23f44e28bb7f79b143e - md5: 3049f570d9b7beafe56b34e7654f419e + size: 1285688 + timestamp: 1764016673819 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/h5py-3.15.1-nompi_py312h4eecd6b_101.conda + sha256: 914d4f00a4d8cb86a70ce60241acc631a0e9d0cd939c0091b06de2d6cef51a3b + md5: 1f19a033f9c3f388c8f3d3c1643d6611 depends: - __osx >=11.0 - cached-property - hdf5 >=1.14.6,<1.14.7.0a0 - numpy >=1.23,<3 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/h5py?source=hash-mapping - size: 1173760 - timestamp: 1756767793170 -- conda: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-12.1.0-h15599e2_0.conda - sha256: df2a964f5b7dd652b59da018f1d2d9ae402b815c4e5d849384344df358d2a565 - md5: 7704b1edaa8316b8792424f254c1f586 + size: 1139768 + timestamp: 1764017732485 +- conda: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-12.2.0-h15599e2_0.conda + sha256: 6bd8b22beb7d40562b2889dc68232c589ff0d11a5ad3addd41a8570d11f039d9 + md5: b8690f53007e9b5ee2c2178dd4ac778c depends: - __glibc >=2.17,<3.0.a0 - cairo >=1.18.4,<2.0a0 @@ -3172,14 +4160,14 @@ packages: - libfreetype >=2.14.1 - libfreetype6 >=2.14.1 - libgcc >=14 - - libglib >=2.86.0,<3.0a0 + - libglib >=2.86.1,<3.0a0 - libstdcxx >=14 - libzlib >=1.3.1,<2.0a0 license: MIT license_family: MIT purls: [] - size: 2058414 - timestamp: 1759365674184 + size: 2411408 + timestamp: 1762372726141 - conda: https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h2a13503_7.conda sha256: 0d09b6dc1ce5c4005ae1c6a19dc10767932ef9a5e9c755cfdbb5189ac8fb0684 md5: bd77f8da987968ec3927990495dc22e4 @@ -3205,42 +4193,41 @@ packages: purls: [] size: 762257 timestamp: 1695661864625 -- conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.14.6-nompi_h6e4c0c1_103.conda - sha256: 4f173af9e2299de7eee1af3d79e851bca28ee71e7426b377e841648b51d48614 - md5: c74d83614aec66227ae5199d98852aaf +- conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.14.6-nompi_h1b119a7_104.conda + sha256: 454e9724b322cee277abd7acf4f8d688e9c4ded006b6d5bc9fcc2a1ff907d27a + md5: 0857f4d157820dcd5625f61fdfefb780 depends: - __glibc >=2.17,<3.0.a0 - libaec >=1.1.4,<2.0a0 - - libcurl >=8.14.1,<9.0a0 + - libcurl >=8.17.0,<9.0a0 - libgcc >=14 - libgfortran - libgfortran5 >=14.3.0 - libstdcxx >=14 - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.1,<4.0a0 + - openssl >=3.5.4,<4.0a0 license: BSD-3-Clause license_family: BSD purls: [] - size: 3710057 - timestamp: 1753357500665 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdf5-1.14.6-nompi_he65715a_103.conda - sha256: 8948a63fc4a56536ce7b2716b781616c3909507300d26e9f265a3c13d59708a0 - md5: fcc9aca330f13d071bfc4de3d0942d78 + size: 3720961 + timestamp: 1764771748126 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdf5-1.14.6-nompi_hd3baa01_104.conda + sha256: 3cd591334a838b127dfe8a626f38241892063eac8873abb93255962c71155533 + md5: 5a1cbaf2349dd2e6dd6cfaab378de51b depends: - __osx >=11.0 - libaec >=1.1.4,<2.0a0 - - libcurl >=8.14.1,<9.0a0 + - libcurl >=8.17.0,<9.0a0 - libcxx >=19 - libgfortran - libgfortran5 >=14.3.0 - - libgfortran5 >=15.1.0 - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.1,<4.0a0 + - openssl >=3.5.4,<4.0a0 license: BSD-3-Clause license_family: BSD purls: [] - size: 3308443 - timestamp: 1753356976982 + size: 3292042 + timestamp: 1764771887501 - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda sha256: 6ad78a180576c706aabeb5b4c8ceb97c0cb25f1e112d76495bff23e3779948ba md5: 0a802cb9888dd14eeefc611f05c40b6e @@ -3284,6 +4271,11 @@ packages: - pkg:pypi/httpx?source=hash-mapping size: 63082 timestamp: 1733663449209 +- pypi: https://files.pythonhosted.org/packages/d2/fd/6668e5aec43ab844de6fc74927e155a3b37bf40d7c3790e49fc0406b6578/httpx_sse-0.4.3-py3-none-any.whl + name: httpx-sse + version: 0.4.3 + sha256: 0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc + requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda sha256: 77af6f5fe8b62ca07d09ac60127a30d9069fdc3c68d6b256754d0ffb1f7779f8 md5: 8e6923fc12f1fe8f8c4e5c9f343256ac @@ -3307,16 +4299,6 @@ packages: purls: [] size: 12129203 timestamp: 1720853576813 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-75.1-hfee45f7_0.conda - sha256: 9ba12c93406f3df5ab0a43db8a4b4ef67a5871dfd401010fbe29b218b2cbe620 - md5: 5eb22c1d7b3fc4abb50d92d621583137 - depends: - - __osx >=11.0 - license: MIT - license_family: MIT - purls: [] - size: 11857802 - timestamp: 1720853997952 - conda: https://conda.anaconda.org/conda-forge/noarch/id-1.5.0-pyh29332c3_0.conda sha256: 161e3eb5aba887d0329bb4099f72cb92eed9072cf63f551d08540480116e69a2 md5: d37314c8f553e3b4b44d113a0ee10196 @@ -3339,29 +4321,29 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/identify?source=compressed-mapping + - pkg:pypi/identify?source=hash-mapping size: 79151 timestamp: 1759437561529 -- conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - sha256: d7a472c9fd479e2e8dcb83fb8d433fce971ea369d704ece380e876f9c3494e87 - md5: 39a4f67be3286c86d696df570b1201b7 +- conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda + sha256: ae89d0299ada2a3162c2614a9d26557a92aa6a77120ce142f8e0109bbf0342b0 + md5: 53abe63df7e10a6ba605dc5f9f961d36 depends: - - python >=3.9 + - python >=3.10 license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/idna?source=hash-mapping - size: 49765 - timestamp: 1733211921194 -- conda: https://conda.anaconda.org/conda-forge/linux-64/imagecodecs-2025.8.2-py312h4ecb025_4.conda - sha256: 8a839d301b228f7cda26ac5e0b1f8387a8c7ac1244bc44b7f8e03f78bc7fdf2c - md5: 2d422ec63f084653496c3e809247c268 + size: 50721 + timestamp: 1760286526795 +- conda: https://conda.anaconda.org/conda-forge/linux-64/imagecodecs-2025.11.11-py313hf092b87_1.conda + sha256: ca5acabb4f0a7453b05ea15fa269282e11f1f5073d67332b0f8a3d83f69ec944 + md5: 5e7a22009e354b90af41a2f9d935d8b4 depends: - __glibc >=2.17,<3.0.a0 - blosc >=1.21.6,<2.0a0 - brunsli >=0.1,<1.0a0 - bzip2 >=1.0.8,<2.0a0 - - c-blosc2 >=2.21.2,<2.22.0a0 + - c-blosc2 >=2.22.0,<2.23.0a0 - charls >=2.4.2,<2.5.0a0 - giflib >=5.2.2,<5.3.0a0 - jxrlib >=1.1,<1.2.0a0 @@ -3369,44 +4351,44 @@ packages: - lerc >=4.0.0,<5.0a0 - libaec >=1.1.4,<2.0a0 - libavif16 >=1.3.0,<2.0a0 - - libbrotlicommon >=1.1.0,<1.2.0a0 - - libbrotlidec >=1.1.0,<1.2.0a0 - - libbrotlienc >=1.1.0,<1.2.0a0 - - libdeflate >=1.24,<1.25.0a0 + - libbrotlicommon >=1.2.0,<1.3.0a0 + - libbrotlidec >=1.2.0,<1.3.0a0 + - libbrotlienc >=1.2.0,<1.3.0a0 + - libdeflate >=1.25,<1.26.0a0 - libgcc >=14 - - libjpeg-turbo >=3.1.0,<4.0a0 + - libjpeg-turbo >=3.1.2,<4.0a0 - libjxl >=0.11,<0.12.0a0 - liblzma >=5.8.1,<6.0a0 - - libpng >=1.6.50,<1.7.0a0 + - libpng >=1.6.51,<1.7.0a0 - libstdcxx >=14 - - libtiff >=4.7.0,<4.8.0a0 + - libtiff >=4.7.1,<4.8.0a0 - libwebp-base >=1.6.0,<2.0a0 - libzlib >=1.3.1,<2.0a0 - libzopfli >=1.0.3,<1.1.0a0 - lz4-c >=1.10.0,<1.11.0a0 - numpy >=1.23,<3 - - openjpeg >=2.5.3,<3.0a0 - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 + - openjpeg >=2.5.4,<3.0a0 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 - snappy >=1.2.2,<1.3.0a0 - zfp >=1.0.1,<2.0a0 - - zlib-ng >=2.2.5,<2.3.0a0 + - zlib-ng >=2.3.1,<2.4.0a0 - zstd >=1.5.7,<1.6.0a0 license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/imagecodecs?source=hash-mapping - size: 1935819 - timestamp: 1757610082608 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/imagecodecs-2025.8.2-py313h8be30f9_4.conda - sha256: 4d0438850a9ebfcc5bbc27b9d96591bdfca6d795d8ea875b79e82e4f4f4925b3 - md5: d2b62a194c1c2a34cfc4abe9201b8b8d + size: 1896062 + timestamp: 1764308929734 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/imagecodecs-2025.11.11-py312h9d9c187_0.conda + sha256: 7f876c14dac3e10917876ccc64d8fc6878206f157838deae7f2d5d64ae534351 + md5: 519241636a048d19b86a4640ac02edb4 depends: - __osx >=11.0 - blosc >=1.21.6,<2.0a0 - brunsli >=0.1,<1.0a0 - bzip2 >=1.0.8,<2.0a0 - - c-blosc2 >=2.21.2,<2.22.0a0 + - c-blosc2 >=2.22.0,<2.23.0a0 - charls >=2.4.2,<2.5.0a0 - giflib >=5.2.2,<5.3.0a0 - jxrlib >=1.1,<1.2.0a0 @@ -3414,25 +4396,25 @@ packages: - lerc >=4.0.0,<5.0a0 - libaec >=1.1.4,<2.0a0 - libavif16 >=1.3.0,<2.0a0 - - libbrotlicommon >=1.1.0,<1.2.0a0 - - libbrotlidec >=1.1.0,<1.2.0a0 - - libbrotlienc >=1.1.0,<1.2.0a0 + - libbrotlicommon >=1.2.0,<1.3.0a0 + - libbrotlidec >=1.2.0,<1.3.0a0 + - libbrotlienc >=1.2.0,<1.3.0a0 - libcxx >=19 - - libdeflate >=1.24,<1.25.0a0 - - libjpeg-turbo >=3.1.0,<4.0a0 + - libdeflate >=1.25,<1.26.0a0 + - libjpeg-turbo >=3.1.2,<4.0a0 - libjxl >=0.11,<0.12.0a0 - liblzma >=5.8.1,<6.0a0 - libpng >=1.6.50,<1.7.0a0 - - libtiff >=4.7.0,<4.8.0a0 + - libtiff >=4.7.1,<4.8.0a0 - libwebp-base >=1.6.0,<2.0a0 - libzlib >=1.3.1,<2.0a0 - libzopfli >=1.0.3,<1.1.0a0 - lz4-c >=1.10.0,<1.11.0a0 - numpy >=1.23,<3 - - openjpeg >=2.5.3,<3.0a0 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 + - openjpeg >=2.5.4,<3.0a0 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 - snappy >=1.2.2,<1.3.0a0 - zfp >=1.0.1,<2.0a0 - zlib-ng >=2.2.5,<2.3.0a0 @@ -3441,50 +4423,76 @@ packages: license_family: BSD purls: - pkg:pypi/imagecodecs?source=hash-mapping - size: 1682919 - timestamp: 1757610729403 -- pypi: https://files.pythonhosted.org/packages/cb/bd/b394387b598ed84d8d0fa90611a90bee0adc2021820ad5729f7ced74a8e2/imageio-2.37.0-py3-none-any.whl + size: 1492787 + timestamp: 1762836499795 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/imagecodecs-lite-2019.12.3-py312hbebd99a_8.conda + sha256: 78ada21c140370d5244e379e1cfc18dab29414452a3439834c64e1d34c36a2ea + md5: 7e4828007947ae4818b136eef7e22fc4 + depends: + - __osx >=11.0 + - numpy >=1.19,<3 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/imagecodecs-lite?source=hash-mapping + size: 146958 + timestamp: 1716011409340 +- pypi: https://files.pythonhosted.org/packages/fb/fe/301e0936b79bcab4cacc7548bf2853fc28dced0a578bab1f7ef53c9aa75b/imageio-2.37.2-py3-none-any.whl name: imageio - version: 2.37.0 - sha256: 11efa15b87bc7871b61590326b2d635439acc321cf7f8ce996f812543ce10eed + version: 2.37.2 + sha256: ad9adfb20335d718c03de457358ed69f141021a333c40a53e57273d8a5bd0b9b requires_dist: - numpy - pillow>=8.3.2 - - astropy ; extra == 'all-plugins' + - imageio-ffmpeg ; extra == 'ffmpeg' + - psutil ; extra == 'ffmpeg' + - fsspec[http] ; extra == 'freeimage' + - pillow-heif ; extra == 'pillow-heif' + - tifffile ; extra == 'tifffile' + - av ; extra == 'pyav' + - astropy ; extra == 'fits' + - rawpy ; extra == 'rawpy' + - numpy>2 ; extra == 'rawpy' + - gdal ; extra == 'gdal' + - itk ; extra == 'itk' + - black ; extra == 'linting' + - flake8 ; extra == 'linting' + - pytest ; extra == 'test' + - pytest-cov ; extra == 'test' + - fsspec[github] ; extra == 'test' + - sphinx<6 ; extra == 'docs' + - numpydoc ; extra == 'docs' + - pydata-sphinx-theme ; extra == 'docs' + - pytest ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - fsspec[github] ; extra == 'dev' + - black ; extra == 'dev' + - flake8 ; extra == 'dev' - av ; extra == 'all-plugins' + - astropy ; extra == 'all-plugins' + - fsspec[http] ; extra == 'all-plugins' - imageio-ffmpeg ; extra == 'all-plugins' - numpy>2 ; extra == 'all-plugins' - pillow-heif ; extra == 'all-plugins' - psutil ; extra == 'all-plugins' - rawpy ; extra == 'all-plugins' - tifffile ; extra == 'all-plugins' - - av ; extra == 'all-plugins-pypy' + - fsspec[http] ; extra == 'all-plugins-pypy' - imageio-ffmpeg ; extra == 'all-plugins-pypy' - pillow-heif ; extra == 'all-plugins-pypy' - psutil ; extra == 'all-plugins-pypy' - tifffile ; extra == 'all-plugins-pypy' - - wheel ; extra == 'build' - - pytest ; extra == 'dev' - - pytest-cov ; extra == 'dev' - - fsspec[github] ; extra == 'dev' - - black ; extra == 'dev' - - flake8 ; extra == 'dev' - - sphinx<6 ; extra == 'docs' - - numpydoc ; extra == 'docs' - - pydata-sphinx-theme ; extra == 'docs' - - imageio-ffmpeg ; extra == 'ffmpeg' - - psutil ; extra == 'ffmpeg' - - astropy ; extra == 'fits' - astropy ; extra == 'full' - av ; extra == 'full' - black ; extra == 'full' - flake8 ; extra == 'full' - - fsspec[github] ; extra == 'full' - - gdal ; extra == 'full' + - fsspec[github,http] ; extra == 'full' - imageio-ffmpeg ; extra == 'full' - - itk ; extra == 'full' - - numpy>2 ; extra == 'full' - numpydoc ; extra == 'full' + - numpy>2 ; extra == 'full' - pillow-heif ; extra == 'full' - psutil ; extra == 'full' - pydata-sphinx-theme ; extra == 'full' @@ -3493,19 +4501,6 @@ packages: - rawpy ; extra == 'full' - sphinx<6 ; extra == 'full' - tifffile ; extra == 'full' - - wheel ; extra == 'full' - - gdal ; extra == 'gdal' - - itk ; extra == 'itk' - - black ; extra == 'linting' - - flake8 ; extra == 'linting' - - pillow-heif ; extra == 'pillow-heif' - - av ; extra == 'pyav' - - rawpy ; extra == 'rawpy' - - numpy>2 ; extra == 'rawpy' - - pytest ; extra == 'test' - - pytest-cov ; extra == 'test' - - fsspec[github] ; extra == 'test' - - tifffile ; extra == 'tifffile' requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2 sha256: c2bfd7043e0c4c12d8b5593de666c1e81d67b83c474a0a79282cc5c4ef845460 @@ -3545,22 +4540,23 @@ packages: - pkg:pypi/importlib-resources?source=hash-mapping size: 33781 timestamp: 1736252433366 -- conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - sha256: 0ec8f4d02053cd03b0f3e63168316530949484f80e16f5e2fb199a1d117a89ca - md5: 6837f3eff7dcea42ecd714ce1ac2b108 +- conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda + sha256: e1a9e3b1c8fe62dc3932a616c284b5d8cbe3124bbfbedcf4ce5c828cb166ee19 + md5: 9614359868482abba1bd15ce465e3c42 depends: - - python >=3.9 + - python >=3.10 license: MIT license_family: MIT purls: - - pkg:pypi/iniconfig?source=hash-mapping - size: 11474 - timestamp: 1733223232820 -- conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.30.1-pyh82676e8_0.conda - sha256: cfc2c4e31dfedbb3d124d0055f55fda4694538fb790d52cd1b37af5312833e36 - md5: b0cc25825ce9212b8bee37829abad4d6 + - pkg:pypi/iniconfig?source=compressed-mapping + size: 13387 + timestamp: 1760831448842 +- conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.1.0-pyh5552912_0.conda + sha256: b5f7eaba3bb109be49d00a0a8bda267ddf8fa66cc1b54fc5944529ed6f3e8503 + md5: 1849eec35b60082d2bd66b4e36dec2b6 depends: - - __linux + - appnope + - __osx - comm >=0.1.1 - debugpy >=1.6.5 - ipython >=7.23.1 @@ -3570,22 +4566,25 @@ packages: - nest-asyncio >=1.4 - packaging >=22 - psutil >=5.7 - - python >=3.9 + - python >=3.10 - pyzmq >=25 - tornado >=6.2 - traitlets >=5.4.0 + - python + constrains: + - appnope >=0.1.2 license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/ipykernel?source=hash-mapping - size: 121367 - timestamp: 1754352984703 -- conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.30.1-pyh92f572d_0.conda - sha256: ec80ed5f68c96dd46ff1b533b28d2094b6f07e2ec8115c8c60803920fdd6eb13 - md5: f208c1a85786e617a91329fa5201168c + size: 132289 + timestamp: 1761567969884 +- conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.1.0-pyha191276_0.conda + sha256: a9d6b74115dbd62e19017ff8fa4885b07b5164427f262cc15b5307e5aaf3ee73 + md5: c6f63cfe66adaa5650788e3106b6683a depends: - - __osx - - appnope + - python + - __linux - comm >=0.1.1 - debugpy >=1.6.5 - ipython >=7.23.1 @@ -3595,19 +4594,22 @@ packages: - nest-asyncio >=1.4 - packaging >=22 - psutil >=5.7 - - python >=3.9 + - python >=3.10 - pyzmq >=25 - tornado >=6.2 - traitlets >=5.4.0 + - python + constrains: + - appnope >=0.1.2 license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/ipykernel?source=hash-mapping - size: 121397 - timestamp: 1754353050327 -- conda: https://conda.anaconda.org/conda-forge/noarch/ipympl-0.9.7-pyhcf101f3_2.conda - sha256: 2d76276fad64622d11b81a8675bf491b9fe5873a12dae5238e57ad8b20050fc2 - md5: d0208859583c760068fdf82ae60384fb + size: 133820 + timestamp: 1761567932044 +- conda: https://conda.anaconda.org/conda-forge/noarch/ipympl-0.9.8-pyhcf101f3_0.conda + sha256: ae6f3ff0f3d524dc7469293dc41488ec7d3ffe6de07ac6d9feede4a051689e03 + md5: 32a83ababd92433f97b7a22392e0ea6d depends: - ipython <10 - ipywidgets >=7.6.0,<9 @@ -3621,33 +4623,30 @@ packages: license_family: BSD purls: - pkg:pypi/ipympl?source=hash-mapping - size: 240748 - timestamp: 1755877497098 -- conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.6.0-pyhfa0c392_0.conda - sha256: 5b679431867704b46c0f412de1a4963bf2c9b65e55a325a22c4624f88b939453 - md5: ad6641ef96dd7872acbb802fa3fcb8d1 + size: 240951 + timestamp: 1760104175360 +- conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.8.0-pyh53cf698_0.conda + sha256: 8a72c9945dc4726ee639a9652b622ae6b03f3eba0e16a21d1c6e5bfb562f5a3f + md5: fd77b1039118a3e8ce1070ac8ed45bae depends: - __unix - pexpect >4.3 - - decorator - - exceptiongroup - - ipython_pygments_lexers - - jedi >=0.16 - - matplotlib-inline - - pickleshare + - decorator >=4.3.2 + - ipython_pygments_lexers >=1.0.0 + - jedi >=0.18.1 + - matplotlib-inline >=0.1.5 - prompt-toolkit >=3.0.41,<3.1.0 - - pygments >=2.4.0 + - pygments >=2.11.0 - python >=3.11 - - stack_data + - stack_data >=0.6.0 - traitlets >=5.13.0 - typing_extensions >=4.6 - python license: BSD-3-Clause - license_family: BSD purls: - pkg:pypi/ipython?source=compressed-mapping - size: 638573 - timestamp: 1759151815538 + size: 645145 + timestamp: 1764766793792 - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda sha256: 894682a42a7d659ae12878dbcb274516a7031bbea9104e92f8e88c1f2765a104 md5: bd80ba060603cc228d9d81c257093119 @@ -3660,22 +4659,22 @@ packages: - pkg:pypi/ipython-pygments-lexers?source=hash-mapping size: 13993 timestamp: 1737123723464 -- conda: https://conda.anaconda.org/conda-forge/noarch/ipywidgets-8.1.7-pyhd8ed1ab_0.conda - sha256: fd496e7d48403246f534c5eec09fc1e63ac7beb1fa06541d6ba71f56b30cf29b - md5: 7c9449eac5975ef2d7753da262a72707 +- conda: https://conda.anaconda.org/conda-forge/noarch/ipywidgets-8.1.8-pyhd8ed1ab_0.conda + sha256: 6bb58afb7eabc8b4ac0c7e92707fb498313cc0164cf04e7ba1090dbf49af514b + md5: d68e3f70d1f068f1b66d94822fdc644e depends: - comm >=0.1.3 - ipython >=6.1.0 - jupyterlab_widgets >=3.0.15,<3.1.0 - - python >=3.9 + - python >=3.10 - traitlets >=4.3.1 - widgetsnbextension >=4.0.14,<4.1.0 license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/ipywidgets?source=hash-mapping - size: 114557 - timestamp: 1746454722402 + size: 114376 + timestamp: 1762040524661 - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda sha256: 08e838d29c134a7684bca0468401d26840f41c92267c4126d7b43a6b533b0aed md5: 0b0154421989637d424ccf0f104be51a @@ -3746,18 +4745,19 @@ packages: - pkg:pypi/jeepney?source=hash-mapping size: 40015 timestamp: 1740828380668 -- conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - sha256: f1ac18b11637ddadc05642e8185a851c7fab5998c6f5470d716812fae943b2af - md5: 446bd6c8cb26050d528881df495ce646 +- conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + sha256: fc9ca7348a4f25fed2079f2153ecdcf5f9cf2a0bc36c4172420ca09e1849df7b + md5: 04558c96691bed63104678757beb4f8d depends: - markupsafe >=2.0 - - python >=3.9 + - python >=3.10 + - python license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/jinja2?source=hash-mapping - size: 112714 - timestamp: 1741263433881 + - pkg:pypi/jinja2?source=compressed-mapping + size: 120685 + timestamp: 1764517220861 - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.12.1-pyhd8ed1ab_0.conda sha256: 4e08ccf9fa1103b617a4167a270768de736a36be795c6cd34c2761100d332f74 md5: 0fc93f473c31a2f85c0bde213e7c63ca @@ -3781,31 +4781,31 @@ packages: - pkg:pypi/jsonpatch?source=hash-mapping size: 17311 timestamp: 1733814664790 -- conda: https://conda.anaconda.org/conda-forge/linux-64/jsonpointer-3.0.0-py312h7900ff3_2.conda - sha256: 39c77cd86d9f544e3ce11fdbab1047181d08dd14a72461d06d957b5fcfc78615 - md5: eeaf37c3dc2d1660668bd102c841f783 +- conda: https://conda.anaconda.org/conda-forge/linux-64/jsonpointer-3.0.0-py313h78bf25f_2.conda + sha256: 9174f5209f835cc8918acddc279be919674d874179197e025181fe2a71cb0bce + md5: c1375f38e5f3ee38a9ee0e405a601c35 depends: - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/jsonpointer?source=hash-mapping - size: 17957 - timestamp: 1756754245172 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/jsonpointer-3.0.0-py313h8f79df9_2.conda - sha256: 6e6097b156b2c69bcf05842f86a6650eb474477bec5e2233b4b8bcd2936cda5a - md5: 51a61cf0de7b177b4c09fa5eb4775c76 + size: 18143 + timestamp: 1756754243113 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/jsonpointer-3.0.0-py312h81bd7bf_2.conda + sha256: 1580c22576df479b8a05370a162aa1bca8ba048f6f5c43ec9269e600c64f43b0 + md5: bfd72094f8390de02e426ac61fb7b8ee depends: - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/jsonpointer?source=hash-mapping - size: 18604 - timestamp: 1756754452012 + size: 18540 + timestamp: 1756754421272 - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.25.1-pyhe01879c_0.conda sha256: ac377ef7762e49cb9c4f985f1281eeff471e9adc3402526eea78e6ac6589cf1d md5: 341fd940c242cf33e832c0402face56f @@ -3822,6 +4822,16 @@ packages: - pkg:pypi/jsonschema?source=hash-mapping size: 81688 timestamp: 1755595646123 +- pypi: https://files.pythonhosted.org/packages/cb/58/3485da8cb93d2f393bce453adeef16896751f14ba3e2024bc21dc9597646/jsonschema_path-0.3.4-py3-none-any.whl + name: jsonschema-path + version: 0.3.4 + sha256: f502191fdc2b22050f9a81c9237be9d27145b9001c55842bece5e94e382e52f8 + requires_dist: + - pyyaml>=5.1 + - pathable>=0.4.1,<0.5.0 + - referencing<0.37.0 + - requests>=2.31.0,<3.0.0 + requires_python: '>=3.8.0,<4.0.0' - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda sha256: 0a4f3b132f0faca10c89fdf3b60e15abb62ded6fa80aebfc007d05965192aa04 md5: 439cd0f567d697b20a8f45cb70a1005a @@ -3832,7 +4842,7 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/jsonschema-specifications?source=compressed-mapping + - pkg:pypi/jsonschema-specifications?source=hash-mapping size: 19236 timestamp: 1757335715225 - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.25.1-he01879c_0.conda @@ -3885,20 +4895,24 @@ packages: - pkg:pypi/jupyter-client?source=hash-mapping size: 106342 timestamp: 1733441040958 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.8.1-pyh31011fe_0.conda - sha256: 56a7a7e907f15cca8c4f9b0c99488276d4cb10821d2d15df9245662184872e81 - md5: b7d89d860ebcda28a5303526cdee68ab +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda + sha256: 1d34b80e5bfcd5323f104dbf99a2aafc0e5d823019d626d0dce5d3d356a2a52a + md5: b38fe4e78ee75def7e599843ef4c1ab0 depends: - __unix + - python - platformdirs >=2.5 - - python >=3.8 + - python >=3.10 - traitlets >=5.3 + - python + constrains: + - pywin32 >=300 license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/jupyter-core?source=hash-mapping - size: 59562 - timestamp: 1748333186063 + size: 65503 + timestamp: 1760643864586 - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyh29332c3_0.conda sha256: 37e6ac3ccf7afcc730c3b93cb91a13b9ae827fd306f35dd28f958a74a14878b5 md5: f56000b36f09ab7533877e695e4e8cb0 @@ -3961,19 +4975,18 @@ packages: - pkg:pypi/jupyter-server-terminals?source=hash-mapping size: 19711 timestamp: 1733428049134 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.4.9-pyhd8ed1ab_0.conda - sha256: 79c5b7280b7de7019bb45d9ad6b2131fc03cae7dcca9a8d48e04fbc43627a8c0 - md5: 6fcc0ffe96c13a864ec6a1defc830526 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.0-pyhd8ed1ab_0.conda + sha256: 6f35218db61b7c42026a14b8c6630302ebbc7624a39f1aa65b8335c3e61cb401 + md5: e6dc3d6bf1591f0ebe8e77959e950660 depends: - async-lru >=1.0.0 - httpx >=0.25.0,<1 - - importlib-metadata >=4.8.3 - ipykernel >=6.5.0,!=6.30.0 - jinja2 >=3.0.3 - jupyter-lsp >=2.0.0 - jupyter_core - jupyter_server >=2.4.0,<3 - - jupyterlab_server >=2.27.1,<3 + - jupyterlab_server >=2.28.0,<3 - notebook-shim >=0.2 - packaging - python >=3.10 @@ -3984,9 +4997,9 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/jupyterlab?source=compressed-mapping - size: 8454849 - timestamp: 1758914033168 + - pkg:pypi/jupyterlab?source=hash-mapping + size: 8323112 + timestamp: 1763479901072 - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda sha256: dc24b900742fdaf1e077d9a3458fd865711de80bca95fe3c6d46610c532c6ef0 md5: fd312693df06da3578383232528c468d @@ -4001,40 +5014,39 @@ packages: - pkg:pypi/jupyterlab-pygments?source=hash-mapping size: 18711 timestamp: 1733328194037 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.27.3-pyhd8ed1ab_1.conda - sha256: d03d0b7e23fa56d322993bc9786b3a43b88ccc26e58b77c756619a921ab30e86 - md5: 9dc4b2b0f41f0de41d27f3293e319357 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda + sha256: 381d2d6a259a3be5f38a69463e0f6c5dcf1844ae113058007b51c3bef13a7cee + md5: a63877cb23de826b1620d3adfccc4014 depends: - babel >=2.10 - - importlib-metadata >=4.8.3 - jinja2 >=3.0.3 - json5 >=0.9.0 - jsonschema >=4.18 - jupyter_server >=1.21,<3 - packaging >=21.3 - - python >=3.9 + - python >=3.10 - requests >=2.31 - constrains: - - openapi-core >=0.18.0,<0.19.0 + - python license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/jupyterlab-server?source=hash-mapping - size: 49449 - timestamp: 1733599666357 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_widgets-3.0.15-pyhd8ed1ab_0.conda - sha256: 6214d345861b106076e7cb38b59761b24cd340c09e3f787e4e4992036ca3cd7e - md5: ad100d215fad890ab0ee10418f36876f + size: 51621 + timestamp: 1761145478692 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_widgets-3.0.16-pyhcf101f3_1.conda + sha256: 5c03de243d7ae6247f39a402f4785d95e61c3be79ef18738e8f17155585d31a8 + md5: dbf8b81974504fa51d34e436ca7ef389 depends: - - python >=3.9 + - python >=3.10 + - python constrains: - jupyterlab >=3,<5 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/jupyterlab-widgets?source=hash-mapping - size: 189133 - timestamp: 1746450926999 + - pkg:pypi/jupyterlab-widgets?source=compressed-mapping + size: 216779 + timestamp: 1762267481404 - conda: https://conda.anaconda.org/conda-forge/linux-64/jxrlib-1.1-hd590300_3.conda sha256: 2057ca87b313bde5b74b93b0e696f8faab69acd4cb0edebb78469f3f388040c0 md5: 5aeabe88534ea4169d4c49998f293d6c @@ -4053,9 +5065,9 @@ packages: purls: [] size: 197843 timestamp: 1703334079437 -- conda: https://conda.anaconda.org/conda-forge/noarch/keyring-25.6.0-pyh534df25_0.conda - sha256: c8b436fa9853bf8b836c96afbb7684a04955b80b37f5d5285fd836b6a8566cc5 - md5: d2c0c5bda93c249f877c7fceea9e63af +- conda: https://conda.anaconda.org/conda-forge/noarch/keyring-25.7.0-pyh534df25_0.conda + sha256: 9def5c6fb3b3b4952a4f6b55a019b5c7065b592682b84710229de5a0b73f6364 + md5: c88f9579d08eb4031159f03640714ce3 depends: - __osx - importlib-metadata >=4.11.4 @@ -4063,16 +5075,16 @@ packages: - jaraco.classes - jaraco.context - jaraco.functools - - python >=3.9 + - python >=3.10 license: MIT license_family: MIT purls: - pkg:pypi/keyring?source=hash-mapping - size: 37280 - timestamp: 1735210369348 -- conda: https://conda.anaconda.org/conda-forge/noarch/keyring-25.6.0-pyha804496_0.conda - sha256: b6f57c17cf098022c32fe64e85e9615d427a611c48a5947cdfc357490210a124 - md5: cdd58ab99c214b55d56099108a914282 + size: 37924 + timestamp: 1763320995459 +- conda: https://conda.anaconda.org/conda-forge/noarch/keyring-25.7.0-pyha804496_0.conda + sha256: 010718b1b1a35ce72782d38e6d6b9495d8d7d0dbea9a3e42901d030ff2189545 + md5: 9eeb0eaf04fa934808d3e070eebbe630 depends: - __linux - importlib-metadata >=4.11.4 @@ -4081,14 +5093,14 @@ packages: - jaraco.context - jaraco.functools - jeepney >=0.4.2 - - python >=3.9 + - python >=3.10 - secretstorage >=3.2 license: MIT license_family: MIT purls: - pkg:pypi/keyring?source=hash-mapping - size: 36985 - timestamp: 1735210286595 + size: 37717 + timestamp: 1763320674488 - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda sha256: 0960d06048a7185d3542d850986d807c6e37ca2e644342dd0c72feefcf26c2a4 md5: b38117a3c920364aff79f870c984b4a3 @@ -4099,36 +5111,36 @@ packages: purls: [] size: 134088 timestamp: 1754905959823 -- conda: https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.9-py312h0a2e395_1.conda - sha256: 42f856c17ea4b9bce5ac5e91d6e58e15d835a3cac32d71bc592dd5031f9c0fb8 - md5: cec5c1ea565944a94f82cdd6fba7cc76 +- conda: https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.9-py313hc8edb43_2.conda + sha256: 60d7d525db89401f88f5c91bdbb79d3afbf005e7d7c1326318659fa097607e51 + md5: 3e0e65595330e26515e31b7fc6d933c7 depends: - python + - __glibc >=2.17,<3.0.a0 - libstdcxx >=14 - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - - python_abi 3.12.* *_cp312 + - python_abi 3.13.* *_cp313 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/kiwisolver?source=compressed-mapping - size: 77266 - timestamp: 1756467527669 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/kiwisolver-1.4.9-py313hf88c9ab_1.conda - sha256: 18e99c68458ddb014eb37b959a61be5c3a3a802534e5c33b14130e7ec0c18481 - md5: 109f613ee5f40f67e379e3fd17e97c19 + - pkg:pypi/kiwisolver?source=hash-mapping + size: 77616 + timestamp: 1762488778882 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/kiwisolver-1.4.9-py312hd8c8125_2.conda + sha256: 8d68f6ec4d947902034fe9ed9d4a4c1180b5767bd9731af940f5a0e436bc3dfd + md5: ddf4775023a2466ee308792ed80ca408 depends: - python + - python 3.12.* *_cpython - libcxx >=19 - - python 3.13.* *_cp313 - __osx >=11.0 - - python_abi 3.13.* *_cp313 + - python_abi 3.12.* *_cp312 license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/kiwisolver?source=hash-mapping - size: 68324 - timestamp: 1756467625109 + size: 67752 + timestamp: 1762488827477 - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.21.3-h659f571_0.conda sha256: 99df692f7a8a5c27cd14b5fb1374ee55e756631b9c3d659ed3ee60830249b238 md5: 3f43953b7d3fb3aaa1d0d0723d91e368 @@ -4158,17 +5170,17 @@ packages: purls: [] size: 1155530 timestamp: 1719463474401 -- conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.0-pyhd8ed1ab_0.conda - sha256: 6370d6a458b4f11a9ab5db7eb05e895f55f276e6aa4c4bbac7dde412c87fae35 - md5: c9ee16acbcea5cc91d9f3eb1d8f903bd +- conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda + sha256: 49570840fb15f5df5d4b4464db8ee43a6d643031a2bc70ef52120a52e3809699 + md5: 9b965c999135d43a3d0f7bd7d024e26a depends: - python >=3.10 license: MIT license_family: MIT purls: - pkg:pypi/lark?source=compressed-mapping - size: 94267 - timestamp: 1758590674960 + size: 94312 + timestamp: 1761596921009 - pypi: https://files.pythonhosted.org/packages/83/60/d497a310bde3f01cb805196ac61b7ad6dc5dcf8dce66634dc34364b20b4f/lazy_loader-0.4-py3-none-any.whl name: lazy-loader version: '0.4' @@ -4206,18 +5218,19 @@ packages: purls: [] size: 212125 timestamp: 1739161108467 -- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.44-ha97dd6f_2.conda - sha256: 707dfb8d55d7a5c6f95c772d778ef07a7ca85417d9971796f7d3daad0b615de8 - md5: 14bae321b8127b63cba276bd53fac237 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45-default_hbd61a6d_104.conda + sha256: 9e191baf2426a19507f1d0a17be0fdb7aa155cdf0f61d5a09c808e0a69464312 + md5: a6abd2796fc332536735f68ba23f7901 depends: - __glibc >=2.17,<3.0.a0 + - zstd >=1.5.7,<1.6.0a0 constrains: - - binutils_impl_linux-64 2.44 + - binutils_impl_linux-64 2.45 license: GPL-3.0-only license_family: GPL purls: [] - size: 747158 - timestamp: 1758810907507 + size: 725545 + timestamp: 1764007826689 - conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h0aef613_1.conda sha256: 412381a43d5ff9bbed82cd52a0bbca5b90623f62e41007c9c42d3870c60945ff md5: 9344155d33912347b37f0ae6c410a835 @@ -4264,9 +5277,9 @@ packages: purls: [] size: 30173 timestamp: 1749993648288 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libarchive-3.8.1-gpl_h7be2006_101.conda - sha256: 6a1a1a660a5911d0c8bf423a2ceddedd578c8685737afc9fb1b150c17df32484 - md5: e6f55365ce179b10b48de5a1677ae013 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libarchive-3.8.2-gpl_h7be2006_100.conda + sha256: 3fb3baf9f6ac39a4720f91dbb6fdace0208fd76500d362e8d6ae985a8bd42451 + md5: 9d0eaa26e3c5d7af747b3ddee928327b depends: - __glibc >=2.17,<3.0.a0 - bzip2 >=1.0.8,<2.0a0 @@ -4277,16 +5290,16 @@ packages: - libzlib >=1.3.1,<2.0a0 - lz4-c >=1.10.0,<1.11.0a0 - lzo >=2.10,<3.0a0 - - openssl >=3.5.2,<4.0a0 + - openssl >=3.5.4,<4.0a0 - zstd >=1.5.7,<1.6.0a0 license: BSD-2-Clause license_family: BSD purls: [] - size: 896229 - timestamp: 1757963333811 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarchive-3.8.1-gpl_h46575ef_101.conda - sha256: 1daa1a55e3f276d08843615f1fd42c95d2035344f11ed8a9e92d0f9a2063d195 - md5: a2ca5bd1b4eaf2c51c1a82edb2aa7f48 + size: 884698 + timestamp: 1760610562105 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarchive-3.8.2-gpl_h46575ef_100.conda + sha256: db847a255f9c61893f5ee364c194410fcdac57bf819bf1ed6e72c429c1aee055 + md5: 5ab60a0e4c99d6fa08605e0ea91e4fda depends: - __osx >=11.0 - bzip2 >=1.0.8,<2.0a0 @@ -4297,13 +5310,13 @@ packages: - libzlib >=1.3.1,<2.0a0 - lz4-c >=1.10.0,<1.11.0a0 - lzo >=2.10,<3.0a0 - - openssl >=3.5.2,<4.0a0 + - openssl >=3.5.4,<4.0a0 - zstd >=1.5.7,<1.6.0a0 license: BSD-2-Clause license_family: BSD purls: [] - size: 788668 - timestamp: 1757963940681 + size: 790591 + timestamp: 1760611525393 - conda: https://conda.anaconda.org/conda-forge/linux-64/libavif16-1.3.0-h6395336_2.conda sha256: e3a44c0eda23aa15c9a8dfa8c82ecf5c8b073e68a16c29edd0e409e687056d30 md5: c09c4ac973f7992ba0c6bb1aafd77bd4 @@ -4333,165 +5346,161 @@ packages: purls: [] size: 110723 timestamp: 1756124882419 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-36_h4a7cf45_openblas.conda - build_number: 36 - sha256: a1670eb8c9293f37a245e313bd9d72a301c79e8668a6a5d418c90335719fbaff - md5: 2a6122504dc8ea139337046d34a110cb +- conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-4_h4a7cf45_openblas.conda + build_number: 4 + sha256: f35fee1eb3fe1a80b2c8473f145a830cf6f98c3b15b232b256b93d44bd9c93b3 + md5: 14ff9fdfbd8bd590fca383b995470711 depends: - libopenblas >=0.3.30,<0.3.31.0a0 - libopenblas >=0.3.30,<1.0a0 constrains: - - liblapacke 3.9.0 36*_openblas - - blas 2.136 openblas - - liblapack 3.9.0 36*_openblas - - mkl <2025 - - libcblas 3.9.0 36*_openblas + - liblapack 3.11.0 4*_openblas + - blas 2.304 openblas + - mkl <2026 + - libcblas 3.11.0 4*_openblas + - liblapacke 3.11.0 4*_openblas license: BSD-3-Clause - license_family: BSD purls: [] - size: 17421 - timestamp: 1758396490057 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.9.0-36_h51639a9_openblas.conda - build_number: 36 - sha256: acedf4c86be500172ed84a1bf37425e5c538f0494341ebdc829001cd37707564 - md5: 3bf1e49358861ce86825eaa47c092f29 + size: 18529 + timestamp: 1764823833499 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-4_h51639a9_openblas.conda + build_number: 4 + sha256: db31cdcd24b9f4be562c37a780d6a665f5eddc88a97d59997e293d91c522ffc1 + md5: f5c7d8c3256cd95d5ec31afc24c9dd30 depends: - libopenblas >=0.3.30,<0.3.31.0a0 - libopenblas >=0.3.30,<1.0a0 constrains: - - liblapacke 3.9.0 36*_openblas - - libcblas 3.9.0 36*_openblas - - liblapack 3.9.0 36*_openblas - - mkl <2025 - - blas 2.136 openblas + - libcblas 3.11.0 4*_openblas + - blas 2.304 openblas + - liblapack 3.11.0 4*_openblas + - liblapacke 3.11.0 4*_openblas + - mkl <2026 license: BSD-3-Clause - license_family: BSD purls: [] - size: 17733 - timestamp: 1758397710974 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.1.0-hb03c661_4.conda - sha256: 2338a92d1de71f10c8cf70f7bb9775b0144a306d75c4812276749f54925612b6 - md5: 1d29d2e33fe59954af82ef54a8af3fe1 + size: 18767 + timestamp: 1764824430403 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda + sha256: 318f36bd49ca8ad85e6478bd8506c88d82454cc008c1ac1c6bf00a3c42fa610e + md5: 72c8fd1af66bd67bf580645b426513ed depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 license: MIT license_family: MIT purls: [] - size: 69333 - timestamp: 1756599354727 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.1.0-h6caf38d_4.conda - sha256: 023b609ecc35bfee7935d65fcc5aba1a3ba6807cbba144a0730198c0914f7c79 - md5: 231cffe69d41716afe4525c5c1cc5ddd + size: 79965 + timestamp: 1764017188531 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.2.0-hc919400_1.conda + sha256: a7cb9e660531cf6fbd4148cff608c85738d0b76f0975c5fc3e7d5e92840b7229 + md5: 006e7ddd8a110771134fcc4e1e3a6ffa depends: - __osx >=11.0 license: MIT license_family: MIT purls: [] - size: 68938 - timestamp: 1756599687687 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.1.0-hb03c661_4.conda - sha256: fcec0d26f67741b122f0d5eff32f0393d7ebd3ee6bb866ae2f17f3425a850936 - md5: 5cb5a1c9a94a78f5b23684bcb845338d + size: 79443 + timestamp: 1764017945924 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda + sha256: 12fff21d38f98bc446d82baa890e01fd82e3b750378fedc720ff93522ffb752b + md5: 366b40a69f0ad6072561c1d09301c886 depends: - __glibc >=2.17,<3.0.a0 - - libbrotlicommon 1.1.0 hb03c661_4 + - libbrotlicommon 1.2.0 hb03c661_1 - libgcc >=14 license: MIT license_family: MIT purls: [] - size: 33406 - timestamp: 1756599364386 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.1.0-h6caf38d_4.conda - sha256: 7f1cf83a00a494185fc087b00c355674a0f12e924b1b500d2c20519e98fdc064 - md5: cb7e7fe96c9eee23a464afd57648d2cd + size: 34632 + timestamp: 1764017199083 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.2.0-hc919400_1.conda + sha256: 2eae444039826db0454b19b52a3390f63bfe24f6b3e63089778dd5a5bf48b6bf + md5: 079e88933963f3f149054eec2c487bc2 depends: - __osx >=11.0 - - libbrotlicommon 1.1.0 h6caf38d_4 + - libbrotlicommon 1.2.0 hc919400_1 license: MIT license_family: MIT purls: [] - size: 29015 - timestamp: 1756599708339 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.1.0-hb03c661_4.conda - sha256: d42c7f0afce21d5279a0d54ee9e64a2279d35a07a90e0c9545caae57d6d7dc57 - md5: 2e55011fa483edb8bfe3fd92e860cd79 + size: 29452 + timestamp: 1764017979099 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda + sha256: a0c15c79997820bbd3fbc8ecf146f4fe0eca36cc60b62b63ac6cf78857f1dd0d + md5: 4ffbb341c8b616aa2494b6afb26a0c5f depends: - __glibc >=2.17,<3.0.a0 - - libbrotlicommon 1.1.0 hb03c661_4 + - libbrotlicommon 1.2.0 hb03c661_1 - libgcc >=14 license: MIT license_family: MIT purls: [] - size: 289680 - timestamp: 1756599375485 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.1.0-h6caf38d_4.conda - sha256: a2f2c1c2369360147c46f48124a3a17f5122e78543275ff9788dc91a1d5819dc - md5: 4ce5651ae5cd6eebc5899f9bfe0eac3c + size: 298378 + timestamp: 1764017210931 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.2.0-hc919400_1.conda + sha256: 01436c32bb41f9cb4bcf07dda647ce4e5deb8307abfc3abdc8da5317db8189d1 + md5: b2b7c8288ca1a2d71ff97a8e6a1e8883 depends: - __osx >=11.0 - - libbrotlicommon 1.1.0 h6caf38d_4 + - libbrotlicommon 1.2.0 hc919400_1 license: MIT license_family: MIT purls: [] - size: 275791 - timestamp: 1756599724058 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-36_h0358290_openblas.conda - build_number: 36 - sha256: 45110023d1661062288168c6ee01510bcb472ba2f5184492acdcdd3d1af9b58d - md5: 13a3fe5f9812ac8c5710ef8c03105121 + size: 290754 + timestamp: 1764018009077 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-4_h0358290_openblas.conda + build_number: 4 + sha256: 7abc88e2fdccddab27d2a889b9c9063df84a05766cc24828c9b5ca879f25c92c + md5: 25f5e5af61cee1ffedd9b4c9947d3af8 depends: - - libblas 3.9.0 36_h4a7cf45_openblas + - libblas 3.11.0 4_h4a7cf45_openblas constrains: - - liblapacke 3.9.0 36*_openblas - - blas 2.136 openblas - - liblapack 3.9.0 36*_openblas + - liblapack 3.11.0 4*_openblas + - blas 2.304 openblas + - liblapacke 3.11.0 4*_openblas license: BSD-3-Clause - license_family: BSD purls: [] - size: 17401 - timestamp: 1758396499759 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.9.0-36_hb0561ab_openblas.conda - build_number: 36 - sha256: ee8b3386c9fe8668eb9013ffea5c60f7546d0d298931f7ec0637b08d796029de - md5: 46aefc2fcef5f1f128d0549cb0fad584 + size: 18521 + timestamp: 1764823852735 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-4_hb0561ab_openblas.conda + build_number: 4 + sha256: fd57f4c8863ac78f42c55ee68351c963fe14fb3d46575c6f236082076690dd0f + md5: be77be52a6f01b46b1eb9aa5270023cc depends: - - libblas 3.9.0 36_h51639a9_openblas + - libblas 3.11.0 4_h51639a9_openblas constrains: - - liblapack 3.9.0 36*_openblas - - liblapacke 3.9.0 36*_openblas - - blas 2.136 openblas + - liblapack 3.11.0 4*_openblas + - blas 2.304 openblas + - liblapacke 3.11.0 4*_openblas license: BSD-3-Clause - license_family: BSD purls: [] - size: 17717 - timestamp: 1758397731650 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libclang-cpp21.1-21.1.2-default_h99862b1_2.conda - sha256: 14911afa3464f16e06df67f44180fa0e4e16adf694139ec03bbacf943416d979 - md5: e062fd46c26c8ddf8189123a0d9e363e + size: 18722 + timestamp: 1764824449333 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libclang-cpp21.1-21.1.7-default_h99862b1_1.conda + sha256: ce8b8464b1230dd93d2b5a2646d2c80639774c9e781097f041581c07b83d4795 + md5: d3042ebdaacc689fd1daa701885fc96c depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 - - libllvm21 >=21.1.2,<21.2.0a0 + - libllvm21 >=21.1.7,<21.2.0a0 - libstdcxx >=14 license: Apache-2.0 WITH LLVM-exception license_family: Apache purls: [] - size: 21064015 - timestamp: 1759733242259 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libclang13-21.1.2-default_h746c552_2.conda - sha256: a5f9ef2ebd0a7fd957c791c65a4dd625960726480dd0398a315e01d171051b4d - md5: 594b05398fe0afa089103e934453368f + size: 21055642 + timestamp: 1764816319608 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libclang13-21.1.7-default_h746c552_1.conda + sha256: a9bcd5fc463ddf088077eceaf314d560af347d10c4d92ca3177fa313a79a6e46 + md5: 66508e5f84c3dc9af1a0a62694325ef2 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 - - libllvm21 >=21.1.2,<21.2.0a0 + - libllvm21 >=21.1.7,<21.2.0a0 - libstdcxx >=14 license: Apache-2.0 WITH LLVM-exception license_family: Apache purls: [] - size: 12346313 - timestamp: 1759733496742 + size: 12347100 + timestamp: 1764816644936 - conda: https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-hb8b1518_5.conda sha256: cb83980c57e311783ee831832eb2c20ecb41e7dee6e86e8b70b8cef0e43eab55 md5: d4a250da4737ee127fb1fa6452a9002e @@ -4506,70 +5515,70 @@ packages: purls: [] size: 4523621 timestamp: 1749905341688 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.14.1-h332b0f4_0.conda - sha256: b6c5cf340a4f80d70d64b3a29a7d9885a5918d16a5cb952022820e6d3e79dc8b - md5: 45f6713cb00f124af300342512219182 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.17.0-h4e3cde8_0.conda + sha256: 100e29ca864c32af15a5cc354f502d07b2600218740fdf2439fa7d66b50b3529 + md5: 01e149d4a53185622dc2e788281961f2 depends: - __glibc >=2.17,<3.0.a0 - krb5 >=1.21.3,<1.22.0a0 - - libgcc >=13 - - libnghttp2 >=1.64.0,<2.0a0 + - libgcc >=14 + - libnghttp2 >=1.67.0,<2.0a0 - libssh2 >=1.11.1,<2.0a0 - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.0,<4.0a0 + - openssl >=3.5.4,<4.0a0 - zstd >=1.5.7,<1.6.0a0 license: curl license_family: MIT purls: [] - size: 449910 - timestamp: 1749033146806 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcurl-8.14.1-h73640d1_0.conda - sha256: 0055b68137309db41ec34c938d95aec71d1f81bd9d998d5be18f32320c3ccba0 - md5: 1af57c823803941dfc97305248a56d57 + size: 460366 + timestamp: 1762333743748 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcurl-8.17.0-hdece5d2_0.conda + sha256: 2980c5de44ac3ca2ecbd4a00756da1648ea2945d9e4a2ad9f216c7787df57f10 + md5: 791003efe92c17ed5949b309c61a5ab1 depends: - __osx >=11.0 - krb5 >=1.21.3,<1.22.0a0 - - libnghttp2 >=1.64.0,<2.0a0 + - libnghttp2 >=1.67.0,<2.0a0 - libssh2 >=1.11.1,<2.0a0 - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.0,<4.0a0 + - openssl >=3.5.4,<4.0a0 - zstd >=1.5.7,<1.6.0a0 license: curl license_family: MIT purls: [] - size: 403456 - timestamp: 1749033320430 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-21.1.2-hf598326_0.conda - sha256: 3de00998c8271f599d6ed9aea60dc0b3e5b1b7ff9f26f8eac95f86f135aa9beb - md5: edfa256c5391f789384e470ce5c9f340 + size: 394183 + timestamp: 1762334288445 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-21.1.7-hf598326_0.conda + sha256: 4bdbef0241b52e7a8552e8af7425f0b56d5621dd69df46c816546fefa17d77ab + md5: 0de94f39727c31c0447e408c5a210a56 depends: - __osx >=11.0 license: Apache-2.0 WITH LLVM-exception license_family: Apache purls: [] - size: 568154 - timestamp: 1758698306949 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.24-h86f0d12_0.conda - sha256: 8420748ea1cc5f18ecc5068b4f24c7a023cc9b20971c99c824ba10641fb95ddf - md5: 64f0c503da58ec25ebd359e4d990afa8 + size: 568715 + timestamp: 1764676451068 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.25-h17f619e_0.conda + sha256: aa8e8c4be9a2e81610ddf574e05b64ee131fab5e0e3693210c9d6d2fba32c680 + md5: 6c77a605a7a689d17d4819c0f8ac9a00 depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 + - libgcc >=14 license: MIT license_family: MIT purls: [] - size: 72573 - timestamp: 1747040452262 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libdeflate-1.24-h5773f1b_0.conda - sha256: 417d52b19c679e1881cce3f01cad3a2d542098fa2d6df5485aac40f01aede4d1 - md5: 3baf58a5a87e7c2f4d243ce2f8f2fe5c + size: 73490 + timestamp: 1761979956660 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libdeflate-1.25-hc11a715_0.conda + sha256: 5e0b6961be3304a5f027a8c00bd0967fc46ae162cffb7553ff45c70f51b8314c + md5: a6130c709305cd9828b4e1bd9ba0000c depends: - __osx >=11.0 license: MIT license_family: MIT purls: [] - size: 54790 - timestamp: 1747040549847 + size: 55420 + timestamp: 1761980066242 - conda: https://conda.anaconda.org/conda-forge/linux-64/libdrm-2.4.125-hb03c661_1.conda sha256: c076a213bd3676cc1ef22eeff91588826273513ccc6040d9bea68bccdc849501 md5: 9314bc5a1fe7d1044dc9dfd3ef400535 @@ -4635,52 +5644,52 @@ packages: purls: [] size: 107458 timestamp: 1702146414478 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.1-hecca717_0.conda - sha256: da2080da8f0288b95dd86765c801c6e166c4619b910b11f9a8446fb852438dc2 - md5: 4211416ecba1866fab0c6470986c22d6 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.3-hecca717_0.conda + sha256: 1e1b08f6211629cbc2efe7a5bca5953f8f6b3cae0eeb04ca4dacee1bd4e2db2f + md5: 8b09ae86839581147ef2e5c5e229d164 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 constrains: - - expat 2.7.1.* + - expat 2.7.3.* license: MIT license_family: MIT purls: [] - size: 74811 - timestamp: 1752719572741 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.1-hec049ff_0.conda - sha256: 8fbb17a56f51e7113ed511c5787e0dec0d4b10ef9df921c4fd1cccca0458f648 - md5: b1ca5f21335782f71a8bd69bdc093f67 + size: 76643 + timestamp: 1763549731408 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.3-haf25636_0.conda + sha256: fce22610ecc95e6d149e42a42fbc3cc9d9179bd4eb6232639a60f06e080eec98 + md5: b79875dbb5b1db9a4a22a4520f918e1a depends: - __osx >=11.0 constrains: - - expat 2.7.1.* + - expat 2.7.3.* license: MIT license_family: MIT purls: [] - size: 65971 - timestamp: 1752719657566 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda - sha256: 764432d32db45466e87f10621db5b74363a9f847d2b8b1f9743746cd160f06ab - md5: ede4673863426c0883c0063d853bbd85 + size: 67800 + timestamp: 1763549994166 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h9ec8514_0.conda + sha256: 25cbdfa65580cfab1b8d15ee90b4c9f1e0d72128f1661449c9a999d341377d54 + md5: 35f29eec58405aaf55e01cb470d8c26a depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 + - libgcc >=14 license: MIT license_family: MIT purls: [] - size: 57433 - timestamp: 1743434498161 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.4.6-h1da3d7d_1.conda - sha256: c6a530924a9b14e193ea9adfe92843de2a806d1b7dbfd341546ece9653129e60 - md5: c215a60c2935b517dcda8cad4705734d + size: 57821 + timestamp: 1760295480630 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-he5f378a_0.conda + sha256: 9b8acdf42df61b7bfe8bdc545c016c29e61985e79748c64ad66df47dbc2e295f + md5: 411ff7cd5d1472bba0f55c0faf04453b depends: - __osx >=11.0 license: MIT license_family: MIT purls: [] - size: 39839 - timestamp: 1743434670405 + size: 40251 + timestamp: 1760295839166 - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.14.1-ha770c72_0.conda sha256: 4641d37faeb97cf8a121efafd6afd040904d4bca8c46798122f417c31d5dfbec md5: f4084e4e6577797150f9b04a4560ceb0 @@ -4726,52 +5735,65 @@ packages: purls: [] size: 346703 timestamp: 1757947166116 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-h767d61c_6.conda - sha256: 29c6ce15cf54f89282581d19329c99d1639036c5dde049bf1cae48dcc4137470 - md5: 99eee6aa5abea12f326f7fc010aef0c8 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_15.conda + sha256: 37f2edde2f8281672987c63f13c85a57d04d889dc929ce38204426d5eb2059cc + md5: a5d86b0496174a412d531eac03af9174 depends: - __glibc >=2.17,<3.0.a0 - _openmp_mutex >=4.5 constrains: - - libgomp 15.2.0 h767d61c_6 - - libgcc-ng ==15.2.0=*_6 + - libgomp 15.2.0 he0feb66_15 + - libgcc-ng ==15.2.0=*_15 + license: GPL-3.0-only WITH GCC-exception-3.1 + purls: [] + size: 1041379 + timestamp: 1764836112865 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_15.conda + sha256: 42515b0e242b7e9e0909652a7bbe053c827aa86b57803c334fd69cbe5ebc3363 + md5: 9633bbd83cdc75ca0d325bf26fa32375 + depends: + - _openmp_mutex + constrains: + - libgcc-ng ==15.2.0=*_15 + - libgomp 15.2.0 15 license: GPL-3.0-only WITH GCC-exception-3.1 purls: [] - size: 823770 - timestamp: 1759796589812 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_6.conda - sha256: 12c91470ceb8d7d38fcee1a4ff1f50524625349059988f6bd0e8e6b27599a1ad - md5: d9717466cca9b9584226ce57a7cd58e6 + size: 402189 + timestamp: 1764837679327 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_15.conda + sha256: 497d8cdba0da8fa154613d1c15f585674cadc194964ed1b4fe7c2809938dc41f + md5: 7b742943660c5173bb6a5c823021c9a0 depends: - - libgcc 15.2.0 h767d61c_6 + - libgcc 15.2.0 he0feb66_15 license: GPL-3.0-only WITH GCC-exception-3.1 purls: [] - size: 29249 - timestamp: 1759796603487 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_6.conda - sha256: 37ac19d22718d371b1fd78207eee37bc384c5e7f8880e06187cb817f1124d1e2 - md5: c41f84a30601e911a1e7a30f53531a59 + size: 26834 + timestamp: 1764836127111 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_15.conda + sha256: d0277c81db5fc943f59fee5718d95ee04b0a50f59207c11c229c4961b6cb4aa8 + md5: 7deffdc77cda3d2bbc9c558efa33d3ed depends: - - libgfortran5 15.2.0 hcd61629_6 + - libgfortran5 15.2.0 h68bc16d_15 constrains: - - libgfortran-ng ==15.2.0=*_6 + - libgfortran-ng ==15.2.0=*_15 license: GPL-3.0-only WITH GCC-exception-3.1 purls: [] - size: 29238 - timestamp: 1759796648430 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-hfcf01ff_1.conda - sha256: e9a5d1208b9dc0b576b35a484d527d9b746c4e65620e0d77c44636033b2245f0 - md5: f699348e3f4f924728e33551b1920f79 + size: 26859 + timestamp: 1764836174548 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_15.conda + sha256: aca978d4fe96c3838e87fe607888267a9d5a3a99159535a0ba418f781639eaab + md5: 5c9f004d0b98ce792a022f1095d1b338 depends: - - libgfortran5 15.2.0 h742603c_1 + - libgfortran5 15.2.0 hdae7583_15 + constrains: + - libgfortran-ng ==15.2.0=*_15 license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL purls: [] - size: 134016 - timestamp: 1759712902814 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-hcd61629_6.conda - sha256: 5046054c10fe5c81433849aa5d569e50ae051c4eac44b3e2a9bb34945eb16fc8 - md5: 8fc1650fb7c7fca583cc3537a808e21b + size: 138828 + timestamp: 1764837867299 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_15.conda + sha256: 4474ac4d8488952d702894938a267f4250040c616b6b3599655270ea10d53c75 + md5: 356b7358fcd6df32ad50d07cdfadd27d depends: - __glibc >=2.17,<3.0.a0 - libgcc >=15.2.0 @@ -4779,20 +5801,19 @@ packages: - libgfortran 15.2.0 license: GPL-3.0-only WITH GCC-exception-3.1 purls: [] - size: 1572922 - timestamp: 1759796621088 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-h742603c_1.conda - sha256: 18808697013a625ca876eeee3d86ee5b656f17c391eca4a4bc70867717cc5246 - md5: afccf412b03ce2f309f875ff88419173 + size: 2482302 + timestamp: 1764836144744 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_15.conda + sha256: a257f9994212496fdfa27f20e20ab3554099e95d8d12e5a4f5cd889a96f3da90 + md5: 75737d092770ee4695e13f6b181bdbd2 depends: - - llvm-openmp >=8.0.0 + - libgcc >=15.2.0 constrains: - libgfortran 15.2.0 license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL purls: [] - size: 764028 - timestamp: 1759712189275 + size: 598504 + timestamp: 1764837685572 - conda: https://conda.anaconda.org/conda-forge/linux-64/libgl-1.7.0-ha4b6fd6_2.conda sha256: dc2752241fa3d9e40ce552c1942d0a4b5eeb93740c9723873f6fcf8d39ef8d2d md5: 928b8be80851f5d8ffb016f9c81dae7a @@ -4804,22 +5825,22 @@ packages: purls: [] size: 134712 timestamp: 1731330998354 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.86.0-h1fed272_0.conda - sha256: 33336bd55981be938f4823db74291e1323454491623de0be61ecbe6cf3a4619c - md5: b8e4c93f4ab70c3b6f6499299627dbdc +- conda: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.86.2-h32235b2_0.conda + sha256: 918306d6ed211ab483e4e19368e5748b265d24e75c88a1c66a61f72b9fa30b29 + md5: 0cb0612bc9cb30c62baf41f9d600611b depends: - __glibc >=2.17,<3.0.a0 - - libffi >=3.4.6,<3.5.0a0 + - libffi >=3.5.2,<3.6.0a0 - libgcc >=14 - libiconv >=1.18,<2.0a0 - libzlib >=1.3.1,<2.0a0 - pcre2 >=10.46,<10.47.0a0 constrains: - - glib 2.86.0 *_0 + - glib 2.86.2 *_0 license: LGPL-2.1-or-later purls: [] - size: 3978602 - timestamp: 1757403291664 + size: 3974801 + timestamp: 1763672326986 - conda: https://conda.anaconda.org/conda-forge/linux-64/libglvnd-1.7.0-ha4b6fd6_2.conda sha256: 1175f8a7a0c68b7f81962699751bb6574e6f07db4c9f72825f978e3016f46850 md5: 434ca7e50e40f4918ab701e3facd59a0 @@ -4840,15 +5861,15 @@ packages: purls: [] size: 75504 timestamp: 1731330988898 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-h767d61c_6.conda - sha256: 60263a73f3826f4e24a45d18826cb324711c980c13c0155e9d10eaca8a399851 - md5: a8637a77aec40557feb12dbc8dc37c6f +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_15.conda + sha256: b3c4e39be7aba6f5a8695d428362c5c918b96a281ce0a7037f1e889dfc340615 + md5: a90d6983da0757f4c09bb8fcfaf34e71 depends: - __glibc >=2.17,<3.0.a0 license: GPL-3.0-only WITH GCC-exception-3.1 purls: [] - size: 448095 - timestamp: 1759796487876 + size: 602978 + timestamp: 1764836011147 - conda: https://conda.anaconda.org/conda-forge/linux-64/libhwy-1.3.0-h4c17acf_1.conda sha256: 2bdd1cdd677b119abc5e83069bec2e28fe6bfb21ebaea3cd07acee67f38ea274 md5: c2a0c1d0120520e979685034e0b79859 @@ -4889,91 +5910,89 @@ packages: purls: [] size: 750379 timestamp: 1754909073836 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.1.0-hb9d3cd8_0.conda - sha256: 98b399287e27768bf79d48faba8a99a2289748c65cd342ca21033fab1860d4a4 - md5: 9fa334557db9f63da6c9285fd2a48638 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.1.2-hb03c661_0.conda + sha256: cc9aba923eea0af8e30e0f94f2ad7156e2984d80d1e8e7fe6be5a1f257f0eb32 + md5: 8397539e3a0bbd1695584fb4f927485a depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 + - libgcc >=14 constrains: - jpeg <0.0.0a license: IJG AND BSD-3-Clause AND Zlib purls: [] - size: 628947 - timestamp: 1745268527144 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libjpeg-turbo-3.1.0-h5505292_0.conda - sha256: 78df2574fa6aa5b6f5fc367c03192f8ddf8e27dc23641468d54e031ff560b9d4 - md5: 01caa4fbcaf0e6b08b3aef1151e91745 + size: 633710 + timestamp: 1762094827865 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libjpeg-turbo-3.1.2-hc919400_0.conda + sha256: 6c061c56058bb10374daaef50e81b39cf43e8aee21f0037022c0c39c4f31872f + md5: f0695fbecf1006f27f4395d64bd0c4b8 depends: - __osx >=11.0 constrains: - jpeg <0.0.0a license: IJG AND BSD-3-Clause AND Zlib purls: [] - size: 553624 - timestamp: 1745268405713 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libjxl-0.11.1-h6cb5226_4.conda - sha256: b9d924d69fc84cd3c660a181985748d9c2df34cd7c7bb03b92d8f70efa7753d9 - md5: f2840d9c2afb19e303e126c9d3a04b36 + size: 551197 + timestamp: 1762095054358 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libjxl-0.11.1-hf08fa70_5.conda + sha256: 6b9524a6a7ea6ef1ac791b697f660c2898171ae505d12e6d27509b59cf059ee6 + md5: 82954a6f42e3fba59628741dca105c98 depends: - __glibc >=2.17,<3.0.a0 - - libbrotlidec >=1.1.0,<1.2.0a0 - - libbrotlienc >=1.1.0,<1.2.0a0 + - libbrotlidec >=1.2.0,<1.3.0a0 + - libbrotlienc >=1.2.0,<1.3.0a0 - libgcc >=14 - libhwy >=1.3.0,<1.4.0a0 - libstdcxx >=14 license: BSD-3-Clause license_family: BSD purls: [] - size: 1740823 - timestamp: 1757583994233 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libjxl-0.11.1-h7274d02_4.conda - sha256: 74b3ded8f7f85c20b7fce0d28dfd462c49880f88458846c4f8b946d7ecb94076 - md5: 3c87b077b788e7844f0c8b866c5621ac + size: 1740728 + timestamp: 1761788390905 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libjxl-0.11.1-h3dcb153_5.conda + sha256: 9a85b1dcdc66aee22f11084c4dfd7004a568689272cf7f755ff6ab2e85212f10 + md5: 52777e8b5ecf41edbba953c677cfbbbd depends: - __osx >=11.0 - - libbrotlidec >=1.1.0,<1.2.0a0 - - libbrotlienc >=1.1.0,<1.2.0a0 + - libbrotlidec >=1.2.0,<1.3.0a0 + - libbrotlienc >=1.2.0,<1.3.0a0 - libcxx >=19 - libhwy >=1.3.0,<1.4.0a0 license: BSD-3-Clause license_family: BSD purls: [] - size: 918558 - timestamp: 1757584152666 -- conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-36_h47877c9_openblas.conda - build_number: 36 - sha256: 1bbd142b34dfc8cb55e1e37c00e78ebba909542ac1054d22fc54843a94797337 - md5: 55daaac7ecf8ebd169cdbe34dc79549e + size: 921063 + timestamp: 1761788642413 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-4_h47877c9_openblas.conda + build_number: 4 + sha256: 5a6ed95bf093d709c8ba8373890773b912767eafdd2e8e4ad0fa6413d13ae3c9 + md5: 8ba8431802764597f400ee3e99026367 depends: - - libblas 3.9.0 36_h4a7cf45_openblas + - libblas 3.11.0 4_h4a7cf45_openblas constrains: - - liblapacke 3.9.0 36*_openblas - - libcblas 3.9.0 36*_openblas - - blas 2.136 openblas + - blas 2.304 openblas + - libcblas 3.11.0 4*_openblas + - liblapacke 3.11.0 4*_openblas license: BSD-3-Clause - license_family: BSD purls: [] - size: 17409 - timestamp: 1758396509549 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.9.0-36_hd9741b5_openblas.conda - build_number: 36 - sha256: ffadfc04f5fb9075715fc4db0b6f2e88c23931eb06a193531ee3ba936dedc433 - md5: e0b918b8232902da02c2c5b4eb81f4d5 + size: 18533 + timestamp: 1764823871307 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.11.0-4_hd9741b5_openblas.conda + build_number: 4 + sha256: 63c9ac0c44c99fdf8de038b66f549d29a7b71e51223ad3fac1b4ba79080581c1 + md5: 3b949d8c584bc30932e41c755507bdc1 depends: - - libblas 3.9.0 36_h51639a9_openblas + - libblas 3.11.0 4_h51639a9_openblas constrains: - - libcblas 3.9.0 36*_openblas - - liblapacke 3.9.0 36*_openblas - - blas 2.136 openblas + - libcblas 3.11.0 4*_openblas + - blas 2.304 openblas + - liblapacke 3.11.0 4*_openblas license: BSD-3-Clause - license_family: BSD purls: [] - size: 17728 - timestamp: 1758397741587 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libllvm21-21.1.2-hf7376ad_0.conda - sha256: 8a18dc5e1d46cb2be46658c3c8c5afba2a3ca9866c679d4a310084d257d7c24f - md5: 85ccb5afca5e1fa67364ea19154e8148 + size: 18764 + timestamp: 1764824468301 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libllvm21-21.1.7-hf7376ad_0.conda + sha256: afe5c5cfc90dc8b5b394e21cf02188394e36766119ad5d78a1d8619d011bbfb1 + md5: 27dc1a582b442f24979f2a28641fe478 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 @@ -4985,8 +6004,8 @@ packages: license: Apache-2.0 WITH LLVM-exception license_family: Apache purls: [] - size: 44347174 - timestamp: 1758823362425 + size: 44320825 + timestamp: 1764711528746 - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_2.conda sha256: f2591c0069447bbe28d4d696b7fcb0c5bd0b4ac582769b89addbcf26fb3430d8 md5: 1a580f7796c7bf6393fddb8bbbde58dc @@ -5010,107 +6029,110 @@ packages: purls: [] size: 92286 timestamp: 1749230283517 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libmamba-2.3.2-hae34dd5_1.conda - sha256: 076c794defe20c4ba4700903b182a210b9f57409ef6accb952debfd84f09e3df - md5: 0d007783a3bc981d98613023c5024b95 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libmamba-2.4.0-hed7d790_1.conda + sha256: 15fa54644c3abb3b18649720f08ca9d7ead508223e9ea7c8c5eae33a2445d035 + md5: b09cb61d8ab64c4a5533149a571ff888 depends: - - nlohmann_json >=3.11.3,<3.11.4.0a0 - cpp-expected >=1.3.1,<1.3.2.0a0 + - __glibc >=2.17,<3.0.a0 - libstdcxx >=14 - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - - simdjson >=4.0.7,<4.1.0a0 - - fmt >=11.2.0,<11.3.0a0 - - libcurl >=8.14.1,<9.0a0 + - reproc >=14.2,<15.0a0 + - simdjson >=4.2.2,<4.3.0a0 + - nlohmann_json-abi ==3.12.0 + - fmt >=12.0.0,<12.1.0a0 - yaml-cpp >=0.8.0,<0.9.0a0 - - libarchive >=3.8.1,<3.9.0a0 - libsolv >=0.7.35,<0.8.0a0 - - reproc-cpp >=14.2,<15.0a0 + - libcurl >=8.17.0,<9.0a0 - openssl >=3.5.4,<4.0a0 - - reproc >=14.2,<15.0a0 + - libarchive >=3.8.2,<3.9.0a0 + - reproc-cpp >=14.2,<15.0a0 - zstd >=1.5.7,<1.6.0a0 license: BSD-3-Clause license_family: BSD purls: [] - size: 2489858 - timestamp: 1759416143198 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmamba-2.3.2-hbdbd6ab_1.conda - sha256: e00ec157942d5e5a868f52f35d82454a59f1eff887c8d75c717a6e7988a4578b - md5: f0b199b611aaeb4879d39a435ec941f2 + size: 2569926 + timestamp: 1764158555905 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmamba-2.4.0-h7516003_1.conda + sha256: b4d0a08e0fdb6bf18d68a9f1e4b720c1e1ed33a507f982c3494b5eb383f65704 + md5: 1826dd03161996cffbb991568d760e42 depends: - - nlohmann_json >=3.11.3,<3.11.4.0a0 - cpp-expected >=1.3.1,<1.3.2.0a0 - __osx >=11.0 - libcxx >=19 - - libarchive >=3.8.1,<3.9.0a0 + - libarchive >=3.8.2,<3.9.0a0 + - yaml-cpp >=0.8.0,<0.9.0a0 + - reproc >=14.2,<15.0a0 - openssl >=3.5.4,<4.0a0 - - fmt >=11.2.0,<11.3.0a0 - libsolv >=0.7.35,<0.8.0a0 - - yaml-cpp >=0.8.0,<0.9.0a0 - - simdjson >=4.0.7,<4.1.0a0 - - libcurl >=8.14.1,<9.0a0 - zstd >=1.5.7,<1.6.0a0 - - reproc >=14.2,<15.0a0 + - libcurl >=8.17.0,<9.0a0 + - fmt >=12.0.0,<12.1.0a0 - reproc-cpp >=14.2,<15.0a0 + - simdjson >=4.2.2,<4.3.0a0 + - nlohmann_json-abi ==3.12.0 license: BSD-3-Clause license_family: BSD purls: [] - size: 1633892 - timestamp: 1759416203496 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libmambapy-2.3.2-py312he1eb750_1.conda - sha256: 5d03aa2e56d7ee0092d95237f7c00fcf8970cebbfae830c54585e3f9588879d2 - md5: c0bb0ee98991a17df2ba9473db68b63f + size: 1686552 + timestamp: 1764158592482 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libmambapy-2.4.0-py313hb20a507_1.conda + sha256: 058eae5fe95fe3b72f1d994aee5741b42e38e4ae3f1cd34d816ba549026d6abe + md5: 7f370f4d8707f936cc1bf9ab7c35809f depends: - python - - libmamba ==2.3.2 hae34dd5_1 + - libmamba ==2.4.0 hed7d790_1 - __glibc >=2.17,<3.0.a0 - libstdcxx >=14 - libgcc >=14 - - fmt >=11.2.0,<11.3.0a0 - - yaml-cpp >=0.8.0,<0.9.0a0 + - nlohmann_json-abi ==3.12.0 + - pybind11-abi ==11 + - fmt >=12.0.0,<12.1.0a0 - openssl >=3.5.4,<4.0a0 - - libmamba >=2.3.2,<2.4.0a0 - - pybind11-abi ==4 - - python_abi 3.12.* *_cp312 + - python_abi 3.13.* *_cp313 + - yaml-cpp >=0.8.0,<0.9.0a0 - zstd >=1.5.7,<1.6.0a0 + - libmamba >=2.4.0,<2.5.0a0 license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/libmambapy?source=hash-mapping - size: 773225 - timestamp: 1759416143199 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmambapy-2.3.2-py313h904c928_1.conda - sha256: aae4a297ad5f7264730a2d4888724f2e4a0360b2b653a5ad0adea199aad620fe - md5: 6a5369fa62608b249dc6f877dbfb6399 + size: 889917 + timestamp: 1764158555913 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmambapy-2.4.0-py312ha7ae334_1.conda + sha256: 75bb69e8202048cd7554f5e02e659efa0c197078c7dbbb9021b62c44b5de061f + md5: eb640d9abbd54a08fadc60b365a478e6 depends: - python - - libmamba ==2.3.2 hbdbd6ab_1 - - python 3.13.* *_cp313 - - __osx >=11.0 + - libmamba ==2.4.0 h7516003_1 + - python 3.12.* *_cpython - libcxx >=19 - - libmamba >=2.3.2,<2.4.0a0 + - __osx >=11.0 + - nlohmann_json-abi ==3.12.0 - zstd >=1.5.7,<1.6.0a0 + - libmamba >=2.4.0,<2.5.0a0 - openssl >=3.5.4,<4.0a0 - - pybind11-abi ==4 - - fmt >=11.2.0,<11.3.0a0 - - python_abi 3.13.* *_cp313 + - pybind11-abi ==11 - yaml-cpp >=0.8.0,<0.9.0a0 + - fmt >=12.0.0,<12.1.0a0 + - python_abi 3.12.* *_cp312 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/libmambapy?source=compressed-mapping - size: 654481 - timestamp: 1759416203497 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h5505292_0.conda - sha256: 0a1875fc1642324ebd6c4ac864604f3f18f57fbcf558a8264f6ced028a3c75b2 - md5: 85ccccb47823dd9f7a99d2c7f530342f + - pkg:pypi/libmambapy?source=hash-mapping + size: 728693 + timestamp: 1764158592492 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb9d3cd8_0.conda + sha256: 3aa92d4074d4063f2a162cd8ecb45dccac93e543e565c01a787e16a43501f7ee + md5: c7e925f37e3b40d893459e625f6a53f1 depends: - - __osx >=11.0 + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 license: BSD-2-Clause license_family: BSD purls: [] - size: 71829 - timestamp: 1748393749336 + size: 91183 + timestamp: 1748393666725 - conda: https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.9.3-nompi_h11f7409_103.conda sha256: e9a8668212719a91a6b0348db05188dfc59de5a21888db13ff8510918a67b258 md5: 3ccff1066c05a1e6c221356eecc40581 @@ -5194,17 +6216,6 @@ packages: purls: [] size: 575454 timestamp: 1756835746393 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda - sha256: 927fe72b054277cde6cb82597d0fcf6baf127dcbce2e0a9d8925a68f1265eef5 - md5: d864d34357c3b65a4b731f78c0801dc4 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - license: LGPL-2.1-only - license_family: GPL - purls: [] - size: 33731 - timestamp: 1750274110928 - conda: https://conda.anaconda.org/conda-forge/linux-64/libntlm-1.8-hb9d3cd8_0.conda sha256: 3b3f19ced060013c2dd99d9d46403be6d319d4601814c772a3472fe2955612b0 md5: 7c7927b404672409d9917d49bff5f2d6 @@ -5215,9 +6226,9 @@ packages: purls: [] size: 33418 timestamp: 1734670021371 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.30-pthreads_h94d23a6_2.conda - sha256: 1b51d1f96e751dc945cc06f79caa91833b0c3326efe24e9b506bd64ef49fc9b0 - md5: dfc5aae7b043d9f56ba99514d5e60625 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.30-pthreads_h94d23a6_4.conda + sha256: 199d79c237afb0d4780ccd2fbf829cea80743df60df4705202558675e07dd2c5 + md5: be43915efc66345cccb3c310b6ed0374 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 @@ -5228,11 +6239,11 @@ packages: license: BSD-3-Clause license_family: BSD purls: [] - size: 5938936 - timestamp: 1755474342204 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.30-openmp_h60d53f8_2.conda - sha256: 7b8551a4d21cf0b19f9a162f1f283a201b17f1bd5a6579abbd0d004788c511fa - md5: d004259fd8d3d2798b16299d6ad6c9e9 + size: 5927939 + timestamp: 1763114673331 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.30-openmp_ha158390_3.conda + sha256: dcc626c7103503d1dfc0371687ad553cb948b8ed0249c2a721147bdeb8db4a73 + md5: a18a7f471c517062ee71b843ef95eb8a depends: - __osx >=11.0 - libgfortran @@ -5243,8 +6254,8 @@ packages: license: BSD-3-Clause license_family: BSD purls: [] - size: 4284696 - timestamp: 1755471861128 + size: 4285762 + timestamp: 1761749506256 - conda: https://conda.anaconda.org/conda-forge/linux-64/libopengl-1.7.0-ha4b6fd6_2.conda sha256: 215086c108d80349e96051ad14131b751d17af3ed2cb5a34edd62fa89bfe8ead md5: 7df50d44d4a14d6c31a2c54f2cd92157 @@ -5266,41 +6277,41 @@ packages: purls: [] size: 28424 timestamp: 1749901812541 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.50-h421ea60_1.conda - sha256: e75a2723000ce3a4b9fd9b9b9ce77553556c93e475a4657db6ed01abc02ea347 - md5: 7af8e91b0deb5f8e25d1a595dea79614 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.51-h421ea60_0.conda + sha256: 1eb769c0f2778d07428947f64272592cc2d3b9bce63b41600abe5dc2b683d829 + md5: d8b81203d08435eb999baa249427884e depends: - libgcc >=14 - __glibc >=2.17,<3.0.a0 - libzlib >=1.3.1,<2.0a0 license: zlib-acknowledgement purls: [] - size: 317390 - timestamp: 1753879899951 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpng-1.6.50-h280e0eb_1.conda - sha256: a2e0240fb0c79668047b528976872307ea80cb330baf8bf6624ac2c6443449df - md5: 4d0f5ce02033286551a32208a5519884 + size: 317576 + timestamp: 1763764145606 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpng-1.6.51-hfab5511_0.conda + sha256: b9478927bb77e48e3b300856060a8e1d1fa16bc6fc16fb554abe0f0475ca5679 + md5: 06efb9eace7676738ced2f9661c59fb8 depends: - __osx >=11.0 - libzlib >=1.3.1,<2.0a0 license: zlib-acknowledgement purls: [] - size: 287056 - timestamp: 1753879907258 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libpq-18.0-h3675c94_0.conda - sha256: 81d9ac5c23257745eb73b81103b3c42442ac13c5d38226916debbf55573540dd - md5: 064887eafa473cbfae9ee8bedd3b7432 + size: 287724 + timestamp: 1763764174318 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libpq-18.1-h5c52fec_2.conda + sha256: bbab2c3e6f650f2bd1bc84d88e6a20fefa6a401fa445bb4b97c509c1b3a89fa8 + md5: a8ac9a6342569d1714ae1b53ae2fcadb depends: - __glibc >=2.17,<3.0.a0 - icu >=75.1,<76.0a0 - krb5 >=1.21.3,<1.22.0a0 - libgcc >=14 - openldap >=2.6.10,<2.7.0a0 - - openssl >=3.5.3,<4.0a0 + - openssl >=3.5.4,<4.0a0 license: PostgreSQL purls: [] - size: 2849367 - timestamp: 1758820440469 + size: 2711480 + timestamp: 1764345810429 - conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.20-h4ab18f5_0.conda sha256: 0105bd108f19ea8e6a78d2d994a6d4a8db16d19a41212070d2d1d48a63c34161 md5: a587892d3c13b6621a6091be690dbca2 @@ -5344,28 +6355,27 @@ packages: purls: [] size: 428408 timestamp: 1754325703193 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.50.4-h0c1763c_0.conda - sha256: 6d9c32fc369af5a84875725f7ddfbfc2ace795c28f246dc70055a79f9b2003da - md5: 0b367fad34931cb79e0d6b7e5c06bb1c +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.51.1-h0c1763c_0.conda + sha256: 6f0e8a812e8e33a4d8b7a0e595efe28373080d27b78ee4828aa4f6649a088454 + md5: 2e1b84d273b01835256e53fd938de355 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 - libzlib >=1.3.1,<2.0a0 license: blessing purls: [] - size: 932581 - timestamp: 1753948484112 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.50.4-h4237e3c_0.conda - sha256: 802ebe62e6bc59fc26b26276b793e0542cfff2d03c086440aeaf72fb8bbcec44 - md5: 1dcb0468f5146e38fae99aef9656034b + size: 938979 + timestamp: 1764359444435 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.51.1-h9a5124b_0.conda + sha256: a46b167447e2a9e38586320c30b29e3b68b6f7e6b873c18d6b1aa2efd2626917 + md5: 67e50e5bd4e5e2310d66b88c4da50096 depends: - __osx >=11.0 - - icu >=75.1,<76.0a0 - libzlib >=1.3.1,<2.0a0 license: blessing purls: [] - size: 902645 - timestamp: 1753948599139 + size: 906292 + timestamp: 1764359907797 - conda: https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.1-hcf80075_0.conda sha256: fa39bfd69228a13e553bd24601332b7cfeb30ca11a3ca50bb028108fe90a7661 md5: eecce068c7e4eddeb169591baac20ac4 @@ -5390,32 +6400,34 @@ packages: purls: [] size: 279193 timestamp: 1745608793272 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h8f9b012_6.conda - sha256: fafd1c1320384a664f57e5d75568f214a31fe2201fc8baace6c15d88b8cf89a8 - md5: 9acaf38d72dcddace144f28506d45afa +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_15.conda + sha256: 2648485aa2dcd5ca385423841a728f262458aec5d814a79da5ab75098e223e3f + md5: fccfb26375ec5e4a2192dee6604b6d02 depends: - __glibc >=2.17,<3.0.a0 - - libgcc 15.2.0 h767d61c_6 + - libgcc 15.2.0 he0feb66_15 + constrains: + - libstdcxx-ng ==15.2.0=*_15 license: GPL-3.0-only WITH GCC-exception-3.1 purls: [] - size: 3903545 - timestamp: 1759796640725 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-h4852527_6.conda - sha256: 462fa002d3ab6702045ee330ab45719ac2958a092a4634a955cebc095f564794 - md5: 89611cb5b685d19e6201065720f97561 + size: 5856371 + timestamp: 1764836166363 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_15.conda + sha256: 2ffaec42c561f53dcc025277043aa02e2557dc0db62bc009be4c7559a7f19f09 + md5: 20a8584ff8677ac9d724345b9d4eb757 depends: - - libstdcxx 15.2.0 h8f9b012_6 + - libstdcxx 15.2.0 h934c35e_15 license: GPL-3.0-only WITH GCC-exception-3.1 purls: [] - size: 29290 - timestamp: 1759796693929 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.1-h8261f1e_0.conda - sha256: ddda0d7ee67e71e904a452010c73e32da416806f5cb9145fb62c322f97e717fb - md5: 72b531694ebe4e8aa6f5745d1015c1b4 + size: 26905 + timestamp: 1764836222826 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.1-h9d88235_1.conda + sha256: e5f8c38625aa6d567809733ae04bb71c161a42e44a9fa8227abe61fa5c60ebe0 + md5: cd5a90476766d53e901500df9215e927 depends: - __glibc >=2.17,<3.0.a0 - lerc >=4.0.0,<5.0a0 - - libdeflate >=1.24,<1.25.0a0 + - libdeflate >=1.25,<1.26.0a0 - libgcc >=14 - libjpeg-turbo >=3.1.0,<4.0a0 - liblzma >=5.8.1,<6.0a0 @@ -5425,16 +6437,16 @@ packages: - zstd >=1.5.7,<1.6.0a0 license: HPND purls: [] - size: 437211 - timestamp: 1758278398952 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libtiff-4.7.1-h7dc4979_0.conda - sha256: 6bc1b601f0d3ee853acd23884a007ac0a0290f3609dabb05a47fc5a0295e2b53 - md5: 2bb9e04e2da869125e2dc334d665f00d + size: 435273 + timestamp: 1762022005702 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libtiff-4.7.1-h4030677_1.conda + sha256: e9248077b3fa63db94caca42c8dbc6949c6f32f94d1cafad127f9005d9b1507f + md5: e2a72ab2fa54ecb6abab2b26cde93500 depends: - __osx >=11.0 - lerc >=4.0.0,<5.0a0 - libcxx >=19 - - libdeflate >=1.24,<1.25.0a0 + - libdeflate >=1.25,<1.26.0a0 - libjpeg-turbo >=3.1.0,<4.0a0 - liblzma >=5.8.1,<6.0a0 - libwebp-base >=1.6.0,<2.0a0 @@ -5442,34 +6454,34 @@ packages: - zstd >=1.5.7,<1.6.0a0 license: HPND purls: [] - size: 373640 - timestamp: 1758278641520 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.2-he9a06e4_0.conda - sha256: e5ec6d2ad7eef538ddcb9ea62ad4346fde70a4736342c4ad87bd713641eb9808 - md5: 80c07c68d2f6870250959dcc95b209d1 + size: 373892 + timestamp: 1762022345545 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.2-h5347b49_1.conda + sha256: 030447cf827c471abd37092ab9714fde82b8222106f22fde94bc7a64e2704c40 + md5: 41f5c09a211985c3ce642d60721e7c3e depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 license: BSD-3-Clause - license_family: BSD purls: [] - size: 37135 - timestamp: 1758626800002 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libvulkan-loader-1.4.328.0-h5279c79_0.conda - sha256: 04737da4417a86906efb694083a8d0622bad9748be31285f24cfabf64f8ba633 - md5: 73afdfbc5bb1233a4634f287f4b92119 + size: 40235 + timestamp: 1764790744114 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libvulkan-loader-1.4.328.1-h5279c79_0.conda + sha256: bbabc5c48b63ff03f440940a11d4648296f5af81bb7630d98485405cd32ac1ce + md5: 372a62464d47d9e966b630ffae3abe73 depends: + - __glibc >=2.17,<3.0.a0 - libstdcxx >=14 - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - xorg-libx11 >=1.8.12,<2.0a0 - xorg-libxrandr >=1.5.4,<2.0a0 constrains: - - libvulkan-headers 1.4.328.0.* + - libvulkan-headers 1.4.328.1.* license: Apache-2.0 + license_family: APACHE purls: [] - size: 197691 - timestamp: 1759796838427 + size: 197672 + timestamp: 1759972155030 - conda: https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.6.0-hd42ef1d_0.conda sha256: 3aed21ab28eddffdaf7f804f49be7a7d701e8f0e46c856d801270b470820a37b md5: aea31d2e5b1091feca96fcfe945c3cf9 @@ -5531,9 +6543,9 @@ packages: purls: [] size: 100393 timestamp: 1702724383534 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.11.0-hca5e8e5_1.conda - sha256: 2febd0cdea153a97737df3cfb900c312b012c0af3cc5a62f2968bd398d25b6b6 - md5: 9abb1e8cbc0039155a8ed2aa149b1067 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.13.1-hca5e8e5_0.conda + sha256: d2195b5fbcb0af1ff7b345efdf89290c279b8d1d74f325ae0ac98148c375863c + md5: 2bca1fbb221d9c3c8e3a155784bbc2e9 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 @@ -5546,42 +6558,43 @@ packages: license: MIT/X11 Derivative license_family: MIT purls: [] - size: 800138 - timestamp: 1757977095650 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.0-h26afc86_1.conda - sha256: 4310577d7eea817d35a1c05e1e54575b06ce085d73e6dd59aa38523adf50168f - md5: 8337b675e0cad517fbcb3daf7588087a + size: 837922 + timestamp: 1764794163823 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.1-h26afc86_0.conda + sha256: ec0735ae56c3549149eebd7dc22c0bed91fd50c02eaa77ff418613ddda190aa8 + md5: e512be7dc1f84966d50959e900ca121f depends: - __glibc >=2.17,<3.0.a0 - icu >=75.1,<76.0a0 - libgcc >=14 - libiconv >=1.18,<2.0a0 - liblzma >=5.8.1,<6.0a0 - - libxml2-16 2.15.0 ha9997c6_1 + - libxml2-16 2.15.1 ha9997c6_0 - libzlib >=1.3.1,<2.0a0 license: MIT license_family: MIT purls: [] - size: 45363 - timestamp: 1758640621036 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-2.15.0-h9329255_1.conda - sha256: 5714b6c1fdd7a981a027d4951e111b1826cc746a02405a0c15b0f95f984e274c - md5: 738e842efb251f6efd430f47432bf0ee + size: 45283 + timestamp: 1761015644057 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-2.15.1-hba2cd1d_0.conda + sha256: fa01101fe7d95085846c16825f0e7dc0efe1f1c7438a96fe7395c885d6179495 + md5: a53d5f7fff38853ddb6bdc8fb609c039 depends: - __osx >=11.0 - - icu >=75.1,<76.0a0 - libiconv >=1.18,<2.0a0 - liblzma >=5.8.1,<6.0a0 - - libxml2-16 2.15.0 h0ff4647_1 + - libxml2-16 2.15.1 h8eac4d7_0 - libzlib >=1.3.1,<2.0a0 + constrains: + - icu <0.0a0 license: MIT license_family: MIT purls: [] - size: 40624 - timestamp: 1758641317371 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.0-ha9997c6_1.conda - sha256: 5420ea77505a8d5ca7b5351ddb2da7e8a178052fccf8fca00189af7877608e89 - md5: b24dd2bd61cd8e4f8a13ee2a945a723c + size: 40611 + timestamp: 1761016283558 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.1-ha9997c6_0.conda + sha256: 71436e72a286ef8b57d6f4287626ff91991eb03c7bdbe835280521791efd1434 + md5: e7733bc6785ec009e47a224a71917e84 depends: - __glibc >=2.17,<3.0.a0 - icu >=75.1,<76.0a0 @@ -5590,28 +6603,28 @@ packages: - liblzma >=5.8.1,<6.0a0 - libzlib >=1.3.1,<2.0a0 constrains: - - libxml2 2.15.0 + - libxml2 2.15.1 license: MIT license_family: MIT purls: [] - size: 556276 - timestamp: 1758640612398 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-16-2.15.0-h0ff4647_1.conda - sha256: 37e85b5a2df4fbd213c5cdf803c57e244722c2dc47300951645c22a2bff6dfe8 - md5: 6b4f950d2dc566afd7382d2380eb2136 + size: 556302 + timestamp: 1761015637262 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-16-2.15.1-h8eac4d7_0.conda + sha256: 3f3f9ba64a3fca15802d4eaf2a97696e6dcd916effa6a683756fd9f11245df5a + md5: cf7291a970b93fe3bb726879f2037af8 depends: - __osx >=11.0 - - icu >=75.1,<76.0a0 - libiconv >=1.18,<2.0a0 - liblzma >=5.8.1,<6.0a0 - libzlib >=1.3.1,<2.0a0 constrains: - - libxml2 2.15.0 + - libxml2 2.15.1 + - icu <0.0a0 license: MIT license_family: MIT purls: [] - size: 464871 - timestamp: 1758641298001 + size: 464186 + timestamp: 1761016258891 - conda: https://conda.anaconda.org/conda-forge/linux-64/libxslt-1.1.43-h711ed8c_1.conda sha256: 0694760a3e62bdc659d90a14ae9c6e132b525a7900e59785b18a08bb52a5d7e5 md5: 87e6096ec6d542d1c1f8b33245fe8300 @@ -5698,19 +6711,18 @@ packages: purls: [] size: 147901 timestamp: 1607309166373 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-21.1.2-h4a912ad_3.conda - sha256: a30d442e9fc9d80cc8925324c8e10f33213c090deca4f45fadfc1ffc79a73a74 - md5: b46e55b406cc7c8a8fbc9681268c2260 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-21.1.7-h4a912ad_0.conda + sha256: 002695e79b0e4c2d117a8bd190ffd62ef3d74a4cae002afa580bd1f98f9560a3 + md5: 05d475f50ddcc2173a6beece9960c6cb depends: - __osx >=11.0 constrains: + - openmp 21.1.7|21.1.7.* - intel-openmp <0.0a0 - - openmp 21.1.2|21.1.2.* license: Apache-2.0 WITH LLVM-exception - license_family: APACHE purls: [] - size: 285932 - timestamp: 1759429144574 + size: 286129 + timestamp: 1764721670250 - conda: https://conda.anaconda.org/conda-forge/noarch/loguru-0.7.3-pyh707e725_0.conda sha256: e4a07f357a4cf195a2345dabd98deab80f4d53574abe712a9cc7f22d3f2cc2c3 md5: 49647ac1de4d1e4b49124aedf3934e02 @@ -5767,6 +6779,39 @@ packages: purls: [] size: 152755 timestamp: 1753889267953 +- pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl + name: markdown-it-py + version: 4.0.0 + sha256: 87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147 + requires_dist: + - mdurl~=0.1 + - psutil ; extra == 'benchmarking' + - pytest ; extra == 'benchmarking' + - pytest-benchmark ; extra == 'benchmarking' + - commonmark~=0.9 ; extra == 'compare' + - markdown~=3.4 ; extra == 'compare' + - mistletoe~=1.0 ; extra == 'compare' + - mistune~=3.0 ; extra == 'compare' + - panflute~=2.3 ; extra == 'compare' + - markdown-it-pyrs ; extra == 'compare' + - linkify-it-py>=1,<3 ; extra == 'linkify' + - mdit-py-plugins>=0.5.0 ; extra == 'plugins' + - gprof2dot ; extra == 'profiling' + - mdit-py-plugins>=0.5.0 ; extra == 'rtd' + - myst-parser ; extra == 'rtd' + - pyyaml ; extra == 'rtd' + - sphinx ; extra == 'rtd' + - sphinx-copybutton ; extra == 'rtd' + - sphinx-design ; extra == 'rtd' + - sphinx-book-theme~=1.0 ; extra == 'rtd' + - jupyter-sphinx ; extra == 'rtd' + - ipykernel ; extra == 'rtd' + - coverage ; extra == 'testing' + - pytest ; extra == 'testing' + - pytest-cov ; extra == 'testing' + - pytest-regressions ; extra == 'testing' + - requests ; extra == 'testing' + requires_python: '>=3.10' - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.0.0-pyhd8ed1ab_0.conda sha256: 7b1da4b5c40385791dbc3cc85ceea9fad5da680a27d5d3cb8bfaa185e304a89e md5: 5b5203189eb668f042ac2b0826244964 @@ -5776,71 +6821,71 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/markdown-it-py?source=compressed-mapping + - pkg:pypi/markdown-it-py?source=hash-mapping size: 64736 timestamp: 1754951288511 -- conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py312h8a5da7c_0.conda - sha256: f77f9f1a4da45cbc8792d16b41b6f169f649651a68afdc10b2da9da12b9aa42b - md5: f775a43412f7f3d7ed218113ad233869 +- conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py313h3dea7bd_0.conda + sha256: a530a411bdaaf0b1e4de8869dfaca46cb07407bc7dc0702a9e231b0e5ce7ca85 + md5: c14389156310b8ed3520d84f854be1ee depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 constrains: - jinja2 >=3.0.0 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/markupsafe?source=compressed-mapping - size: 25321 - timestamp: 1759055268795 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py313h7d74516_0.conda - sha256: e06902a1bf370fdd4ada0a8c81c504868fdb7e9971b72c6bd395aa4e5a497bd2 - md5: 3df5979cc0b761dda0053ffdb0bca3ea + - pkg:pypi/markupsafe?source=hash-mapping + size: 25909 + timestamp: 1759055357045 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py312h5748b74_0.conda + sha256: b6aadcee6a0b814a0cb721e90575cbbe911b17ec46542460a9416ed2ec1a568e + md5: 82221456841d3014a175199e4792465b depends: - __osx >=11.0 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 constrains: - jinja2 >=3.0.0 license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/markupsafe?source=hash-mapping - size: 25778 - timestamp: 1759055530601 -- conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.10.6-py312h7900ff3_1.conda - sha256: fad6790e783123b30639099f45736b53346ea82081be354444c55df61aa3ed34 - md5: 356383b1a6b6d7afae92a012cfd05210 + size: 25121 + timestamp: 1759055677633 +- conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.10.8-py313h78bf25f_0.conda + sha256: ad3eb40a91d456620936c88ea4eb2700ca24e474acd9498fdad831a87771399e + md5: 85bce686dd57910d533807562204e16b depends: - - matplotlib-base >=3.10.6,<3.10.7.0a0 - - pyside6 >=6.7.2 - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 + - matplotlib-base >=3.10.8,<3.10.9.0a0 + - pyside6 >=6.7.2 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 - tornado >=5 license: PSF-2.0 license_family: PSF purls: [] - size: 17381 - timestamp: 1756869738505 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-3.10.6-py313h39782a4_1.conda - sha256: bc1f9c3943ac920e8e9577aadf62ef278287beba7a6690385f7aa9bf61c189e8 - md5: 9ff22b73fb719a391cf17fedbdbc07e1 + size: 17429 + timestamp: 1763055377972 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-3.10.8-py312h1f38498_0.conda + sha256: e3e8448b10273807bf1aa9b1aa6a4ee3a686ccfd0c296560b51b1d1581bb42ae + md5: 534ed7eb4471c088285fdb382805e6ef depends: - - matplotlib-base >=3.10.6,<3.10.7.0a0 - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 + - matplotlib-base >=3.10.8,<3.10.9.0a0 + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 - tornado >=5 license: PSF-2.0 license_family: PSF purls: [] - size: 17486 - timestamp: 1756870321038 -- conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.10.6-py312he3d6523_1.conda - sha256: 9af1c0e8a9551edfb1fbee0595a00108204af3d34c1680271b0121846dc21e77 - md5: 94926ee1d68e678fb4cfdb0727a0927e + size: 17526 + timestamp: 1763060540928 +- conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.10.8-py313h683a580_0.conda + sha256: b1117aa2c1d11ca70d1704054cdc8801cbcf2dfb846c565531edd417ddd82559 + md5: ffe67570e1a9192d2f4c189b27f75f89 depends: - __glibc >=2.17,<3.0.a0 - contourpy >=1.0.1 @@ -5848,8 +6893,8 @@ packages: - fonttools >=4.22.0 - freetype - kiwisolver >=1.3.1 - - libfreetype >=2.13.3 - - libfreetype6 >=2.13.3 + - libfreetype >=2.14.1 + - libfreetype6 >=2.14.1 - libgcc >=14 - libstdcxx >=14 - numpy >=1.23 @@ -5857,20 +6902,20 @@ packages: - packaging >=20.0 - pillow >=8 - pyparsing >=2.3.1 - - python >=3.12,<3.13.0a0 + - python >=3.13,<3.14.0a0 - python-dateutil >=2.7 - - python_abi 3.12.* *_cp312 + - python_abi 3.13.* *_cp313 - qhull >=2020.2,<2020.3.0a0 - tk >=8.6.13,<8.7.0a0 license: PSF-2.0 license_family: PSF purls: - - pkg:pypi/matplotlib?source=hash-mapping - size: 8250974 - timestamp: 1756869718533 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-base-3.10.6-py313h58042b9_1.conda - sha256: 08b23a9a377bb513f46a37d9aefa51c3b92099e36c639fefebdfdb8998570c39 - md5: 655f0eb426c8ddbbc4ccc17a9968dd83 + - pkg:pypi/matplotlib?source=compressed-mapping + size: 8405862 + timestamp: 1763055358671 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-base-3.10.8-py312h605b88b_0.conda + sha256: 3c96c85dd723a4c16fce4446d1f0dc7d64e46b6ae4629c66d65984b8593ee999 + md5: fbc4f90b3d63ea4e6c30f7733a0b5bfd depends: - __osx >=11.0 - contourpy >=1.0.1 @@ -5879,36 +6924,65 @@ packages: - freetype - kiwisolver >=1.3.1 - libcxx >=19 - - libfreetype >=2.13.3 - - libfreetype6 >=2.13.3 + - libfreetype >=2.14.1 + - libfreetype6 >=2.14.1 - numpy >=1.23 - numpy >=1.23,<3 - packaging >=20.0 - pillow >=8 - pyparsing >=2.3.1 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython - python-dateutil >=2.7 - - python_abi 3.13.* *_cp313 + - python_abi 3.12.* *_cp312 - qhull >=2020.2,<2020.3.0a0 license: PSF-2.0 license_family: PSF purls: - pkg:pypi/matplotlib?source=hash-mapping - size: 8276975 - timestamp: 1756870275203 -- conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda - sha256: 69b7dc7131703d3d60da9b0faa6dd8acbf6f6c396224cf6aef3e855b8c0c41c6 - md5: af6ab708897df59bd6e7283ceab1b56b + size: 8243636 + timestamp: 1763060482877 +- conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda + sha256: 9d690334de0cd1d22c51bc28420663f4277cfa60d34fa5cad1ce284a13f1d603 + md5: 00e120ce3e40bad7bfc78861ce3c4a25 depends: - - python >=3.9 + - python >=3.10 - traitlets license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/matplotlib-inline?source=hash-mapping - size: 14467 - timestamp: 1733417051523 + size: 15175 + timestamp: 1761214578417 +- pypi: https://files.pythonhosted.org/packages/a9/bb/711099f9c6bb52770f56e56401cdfb10da5b67029f701e0df29362df4c8e/mcp-1.22.0-py3-none-any.whl + name: mcp + version: 1.22.0 + sha256: bed758e24df1ed6846989c909ba4e3df339a27b4f30f1b8b627862a4bade4e98 + requires_dist: + - anyio>=4.5 + - httpx-sse>=0.4 + - httpx>=0.27.1 + - jsonschema>=4.20.0 + - pydantic-settings>=2.5.2 + - pydantic>=2.11.0,<3.0.0 + - pyjwt[crypto]>=2.10.1 + - python-multipart>=0.0.9 + - pywin32>=310 ; sys_platform == 'win32' + - sse-starlette>=1.6.1 + - starlette>=0.27 + - typing-extensions>=4.9.0 + - typing-inspection>=0.4.1 + - uvicorn>=0.31.1 ; sys_platform != 'emscripten' + - python-dotenv>=1.0.0 ; extra == 'cli' + - typer>=0.16.0 ; extra == 'cli' + - rich>=13.9.4 ; extra == 'rich' + - websockets>=15.0.1 ; extra == 'ws' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + name: mdurl + version: 0.1.2 + sha256: 84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 + requires_python: '>=3.7' - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda sha256: 78c1bbe1723449c52b7a9df1af2ee5f005209f67e40b6e1d3c7619127c43b1c7 md5: 592132998493b3ff25fd7479396e8351 @@ -5920,29 +6994,29 @@ packages: - pkg:pypi/mdurl?source=hash-mapping size: 14465 timestamp: 1733255681319 -- conda: https://conda.anaconda.org/conda-forge/linux-64/menuinst-2.3.1-py312h7900ff3_1.conda - sha256: 821673bb1c5470782565dbf4c23057a3f595dcb676b790e40b8eb78d2fb909de - md5: 6353b0bba226e718356d183890f95d15 +- conda: https://conda.anaconda.org/conda-forge/linux-64/menuinst-2.4.1-py313h78bf25f_0.conda + sha256: ae07f4579fe134c3cb58c48d094b68225f7a6932b59fafc3bd8bfaa55eea65be + md5: e5b8f37425983507c3c2d6e7073c3d08 depends: - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 license: BSD-3-Clause AND MIT purls: - pkg:pypi/menuinst?source=hash-mapping - size: 175653 - timestamp: 1756243618085 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/menuinst-2.3.1-py313h8f79df9_1.conda - sha256: 928a5e9007742b785c9d340be26204135af5eb344eb41659800b5e84bacbde85 - md5: 8333933e44fbf2c57625512685bbd988 + size: 180827 + timestamp: 1761299857535 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/menuinst-2.4.1-py312h81bd7bf_0.conda + sha256: 424d0f8487c784f9306b8dc00f954bc733ea7f9e8da559101939fa842adb2030 + md5: b89b7f2194556cf2a5bab36a726aa873 depends: - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 license: BSD-3-Clause AND MIT purls: - pkg:pypi/menuinst?source=hash-mapping - size: 176812 - timestamp: 1756243988929 + size: 180904 + timestamp: 1761300335213 - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.1.4-pyhcf101f3_0.conda sha256: 609ea628ace5c6cdbdce772704e6cb159ead26969bb2f386ca1757632b0f74c6 md5: f5a4d548d1d3bdd517260409fc21e205 @@ -5956,28 +7030,59 @@ packages: - pkg:pypi/mistune?source=hash-mapping size: 72996 timestamp: 1756495311698 -- conda: https://conda.anaconda.org/conda-forge/noarch/more-itertools-10.8.0-pyhd8ed1ab_0.conda - sha256: fabe81c8f8f3e1d0ef227fc1306526c76189b3f1175f12302c707e0972dd707c - md5: d7620a15dc400b448e1c88a981b23ddd +- conda: https://conda.anaconda.org/conda-forge/noarch/more-itertools-10.8.0-pyhcf101f3_1.conda + sha256: 449609f0d250607a300754474350a3b61faf45da183d3071e9720e453c765b8a + md5: 32f78e9d06e8593bc4bbf1338da06f5f depends: - python >=3.10 + - python license: MIT license_family: MIT purls: - pkg:pypi/more-itertools?source=hash-mapping - size: 65129 - timestamp: 1756855971031 -- pypi: https://files.pythonhosted.org/packages/0d/e2/9baffdae21a76f77ef8447f1a05a96ec4bc0a24dae08767abc0a2fe680b8/multidict-6.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + size: 69210 + timestamp: 1764487059562 +- conda: https://conda.anaconda.org/conda-forge/linux-64/msgpack-python-1.1.2-py313h7037e92_1.conda + sha256: fac37e267dd1d07527f0b078ffe000916e80e8c89cfe69d466f5775b88e93df2 + md5: cd1cfde0ea3bca6c805c73ffa988b12a + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/msgpack?source=hash-mapping + size: 103129 + timestamp: 1762504205590 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgpack-python-1.1.2-py312h84eede6_1.conda + sha256: 1540339678e13365001453fdcb698887075a2b326d5fab05cfd0f4fdefae4eab + md5: e3973f0ac5ac854bf86f0d5674a1a289 + depends: + - __osx >=11.0 + - libcxx >=19 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/msgpack?source=hash-mapping + size: 91268 + timestamp: 1762504467174 +- pypi: https://files.pythonhosted.org/packages/b3/93/c4f67a436dd026f2e780c433277fff72be79152894d9fc36f44569cab1a6/multidict-6.7.0-cp312-cp312-macosx_11_0_arm64.whl name: multidict version: 6.7.0 - sha256: 123e2a72e20537add2f33a79e605f6191fba2afda4cbb876e35c1a7074298a7d + sha256: 2049be98fb57a31b4ccf870bf377af2504d4ae35646a19037ec271e4c07998aa requires_dist: - typing-extensions>=4.1.0 ; python_full_version < '3.11' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/f1/d8/6c3442322e41fb1dd4de8bd67bfd11cd72352ac131f6368315617de752f1/multidict-6.7.0-cp313-cp313-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/c6/2d/f0b184fa88d6630aa267680bdb8623fb69cb0d024b8c6f0d23f9a0f406d3/multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: multidict version: 6.7.0 - sha256: 9c0359b1ec12b1d6849c59f9d319610b7f20ef990a6d454ab151aa0e3b9f78ca + sha256: 9ff96e8815eecacc6645da76c413eb3b3d34cfca256c70b16b286a687d013c32 requires_dist: - typing-extensions>=4.1.0 ; python_full_version < '3.11' requires_python: '>=3.9' @@ -6007,9 +7112,9 @@ packages: - pkg:pypi/nbclient?source=hash-mapping size: 28045 timestamp: 1734628936013 -- conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.6-pyh29332c3_0.conda - sha256: dcccb07c5a1acb7dc8be94330e62d54754c0e9c9cb2bb6865c8e3cfe44cf5a58 - md5: d24beda1d30748afcc87c429454ece1b +- conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.6-pyhcf101f3_1.conda + sha256: 8f575e5c042b17f4677179a6ba474bdbe76573936d3d3e2aeb42b511b9cb1f3f + md5: cfc86ccc3b1de35d36ccaae4c50391f5 depends: - beautifulsoup4 - bleach-with-css !=5.0.0 @@ -6025,18 +7130,18 @@ packages: - packaging - pandocfilters >=1.4.1 - pygments >=2.4.1 - - python >=3.9 + - python >=3.10 - traitlets >=5.1 - python constrains: - pandoc >=2.9.2,<4.0.0 - - nbconvert ==7.16.6 *_0 + - nbconvert ==7.16.6 *_1 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/nbconvert?source=hash-mapping - size: 200601 - timestamp: 1738067871724 + - pkg:pypi/nbconvert?source=compressed-mapping + size: 199273 + timestamp: 1760797634443 - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda sha256: 7a5bd30a2e7ddd7b85031a5e2e14f290898098dc85bea5b3a5bf147c25122838 md5: bbe1963f1e47f594070ffe87cdf612ea @@ -6082,9 +7187,9 @@ packages: - pkg:pypi/nest-asyncio?source=hash-mapping size: 11543 timestamp: 1733325673691 -- conda: https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.7.2-nompi_py312hf6400b3_104.conda - sha256: 34116a63cc8ef0fc3f55561dd7367ea535dcb218b488adf5c637608a25c1fe41 - md5: e3f2d3ba3f36a10a32219dff624f4ea0 +- conda: https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.7.3-nompi_py313hfae5b86_100.conda + sha256: cc8f8c27707048683f2150f898b3f1f1588a028665f73ce87ea954b23cd9e81c + md5: d5247c4087289475a8c324bbe03a71ce depends: - __glibc >=2.17,<3.0.a0 - certifi @@ -6094,17 +7199,17 @@ packages: - libnetcdf >=4.9.3,<4.9.4.0a0 - libzlib >=1.3.1,<2.0a0 - numpy >=1.23,<3 - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 license: MIT license_family: MIT purls: - pkg:pypi/netcdf4?source=hash-mapping - size: 1107826 - timestamp: 1756898547759 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/netcdf4-1.7.2-nompi_py313hb28a5cb_104.conda - sha256: 1916b41ab13cb04093ce01899b636220c1a72ffdcb002d7f9d7c6312d1218ebe - md5: 322faa588adb0ceb09755c6cfdfdcf48 + size: 1115744 + timestamp: 1760540572685 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/netcdf4-1.7.3-nompi_py312h947358d_100.conda + sha256: 0af13b708a7f540fe2eaf5155ca254530112d64eb8dd93c6b74846b30c37ee14 + md5: 4d0a4809609db6402315d591d0fdc90a depends: - __osx >=11.0 - certifi @@ -6113,36 +7218,36 @@ packages: - libnetcdf >=4.9.3,<4.9.4.0a0 - libzlib >=1.3.1,<2.0a0 - numpy >=1.23,<3 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 license: MIT license_family: MIT purls: - pkg:pypi/netcdf4?source=hash-mapping - size: 1007894 - timestamp: 1756899779520 -- conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.5-pyhe01879c_0.conda - sha256: 02019191a2597865940394ff42418b37bc585a03a1c643d7cea9981774de2128 - md5: 16bff3d37a4f99e3aa089c36c2b8d650 + size: 1004901 + timestamp: 1760541856800 +- conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.6-pyhcf101f3_0.conda + sha256: 57b0bbb72ed5647438a81e7caf4890075390f80030c1333434467f9366762db7 + md5: 6725bfdf8ea7a8bf6415f096f3f1ffa5 depends: - python >=3.11 - python constrains: - numpy >=1.25 - scipy >=1.11.2 - - matplotlib >=3.8 + - matplotlib-base >=3.8 - pandas >=2.0 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/networkx?source=hash-mapping - size: 1564462 - timestamp: 1749078300258 -- conda: https://conda.anaconda.org/conda-forge/linux-64/nh3-0.3.1-py310h1570de5_0.conda + - pkg:pypi/networkx?source=compressed-mapping + size: 1584885 + timestamp: 1763962034867 +- conda: https://conda.anaconda.org/conda-forge/linux-64/nh3-0.3.2-py310h1570de5_0.conda noarch: python - sha256: 01da3cb9332b933dc5a6da743814023fd2cf7f03844fd1857f66244e27eddc6d - md5: 5408ec76dfdab005a3f43be2d2c2cef3 + sha256: f6095c759df15baa9cccc20394b21667f4d0440f3c432e07539c3b47ef195c0b + md5: 383616287311316d120b028aac89f6f4 depends: - python - __glibc >=2.17,<3.0.a0 @@ -6152,14 +7257,15 @@ packages: constrains: - __glibc >=2.17 license: MIT + license_family: MIT purls: - pkg:pypi/nh3?source=hash-mapping - size: 669151 - timestamp: 1759841284082 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/nh3-0.3.0-py310h1620c0a_1.conda + size: 669207 + timestamp: 1761831302988 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/nh3-0.3.2-py310h06fc29a_0.conda noarch: python - sha256: fb11036890e3ac425ee05f6ab20c62d0c9e554d02c48ee144176879cad3e6d5a - md5: 42cc71a7337ac72d1e85511f03d8f5aa + sha256: cee5e0acd60b4377cc5b66a7d4741fbe3dbd6613660d5fa10a7a92003710f405 + md5: cdd83f3879f87dab2b214cd3db09b71b depends: - python - __osx >=11.0 @@ -6171,31 +7277,16 @@ packages: license_family: MIT purls: - pkg:pypi/nh3?source=hash-mapping - size: 620918 - timestamp: 1756737501179 -- conda: https://conda.anaconda.org/conda-forge/linux-64/nlohmann_json-3.11.3-he02047a_1.conda - sha256: ce4bcced4f8eea71b7cac8bc3daac097abf7a5792f278cd811dedada199500c1 - md5: e46f7ac4917215b49df2ea09a694a3fa - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc-ng >=12 - - libstdcxx-ng >=12 - license: MIT - license_family: MIT - purls: [] - size: 122743 - timestamp: 1723652407663 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/nlohmann_json-3.11.3-h00cdb27_1.conda - sha256: 3f4e6a4fa074bb297855f8111ab974dab6d9f98b7d4317d4dd46f8687ee2363b - md5: d2dee849c806430eee64d3acc98ce090 - depends: - - __osx >=11.0 - - libcxx >=16 + size: 624675 + timestamp: 1761831518941 +- conda: https://conda.anaconda.org/conda-forge/noarch/nlohmann_json-abi-3.12.0-h0f90c79_1.conda + sha256: 2a909594ca78843258e4bda36e43d165cda844743329838a29402823c8f20dec + md5: 59659d0213082bc13be8500bab80c002 license: MIT license_family: MIT purls: [] - size: 123250 - timestamp: 1723652704997 + size: 4335 + timestamp: 1758194464430 - conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.9.1-pyhd8ed1ab_1.conda sha256: 3636eec0e60466a00069b47ce94b6d88b01419b6577d8e393da44bb5bc8d3468 md5: 7ba3f09fceae6a120d664217e58fe686 @@ -6228,46 +7319,47 @@ packages: - bioblend>=1.3.0,<2.0.0 - tomli>=2.0.2,<3.0.0 requires_python: '>=3.10,<4.0' -- conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.3.3-py312h33ff503_0.conda - sha256: 8443315a60500ea8e3d7ecd9756cee07a60b8c3497e0fc98884963c3108f8bef - md5: 261a82ff799db441c7122f6a83ade061 +- conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.3.5-py313hf6604e3_0.conda + sha256: d54453cb875ed66139c973313465f757a5d6c7ab5760b96484ae56cb8a16ca23 + md5: 15f43bcd12c90186e78801fafc53d89b depends: - python + - libgcc >=14 - libstdcxx >=14 - libgcc >=14 - __glibc >=2.17,<3.0.a0 - liblapack >=3.9.0,<4.0a0 - - python_abi 3.12.* *_cp312 - - libblas >=3.9.0,<4.0a0 - libcblas >=3.9.0,<4.0a0 + - libblas >=3.9.0,<4.0a0 + - python_abi 3.13.* *_cp313 constrains: - numpy-base <0a0 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/numpy?source=compressed-mapping - size: 8786490 - timestamp: 1757505242032 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.3.3-py313h9771d21_0.conda - sha256: 0a8d31fbb49be666f17f746104255e3ccfdb27eac79fe9b8178c8f8bd6a6b2ee - md5: 54cfeb1b41a3c21da642f9b925545478 + - pkg:pypi/numpy?source=hash-mapping + size: 8919466 + timestamp: 1763351050066 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.3.5-py312h85ea64e_0.conda + sha256: 095dc7f15d2f8d9970a6a4e9d4a1980989a4209cd34c2b756fbd40e71f6990cc + md5: ee4c185ae9c1edeb8e8cd26273c90a9a depends: - python - - libcxx >=19 - - python 3.13.* *_cp313 - __osx >=11.0 + - python 3.12.* *_cpython + - libcxx >=19 - libcblas >=3.9.0,<4.0a0 - - python_abi 3.13.* *_cp313 - - liblapack >=3.9.0,<4.0a0 - libblas >=3.9.0,<4.0a0 + - liblapack >=3.9.0,<4.0a0 + - python_abi 3.12.* *_cp312 constrains: - numpy-base <0a0 license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/numpy?source=hash-mapping - size: 6749676 - timestamp: 1757504939745 + size: 6704341 + timestamp: 1763350985482 - conda: https://conda.anaconda.org/conda-forge/noarch/objprint-0.3.0-pyhd8ed1ab_0.conda sha256: ff58f788e9e8c74a6eb2f194b4c18e5bc39a0000172f0b1ec016afae637961f2 md5: 8f8399ecb94bd96e0d73e02053525808 @@ -6279,6 +7371,13 @@ packages: - pkg:pypi/objprint?source=hash-mapping size: 38864 timestamp: 1731340445369 +- pypi: https://files.pythonhosted.org/packages/12/cf/03675d8bd8ecbf4445504d8071adab19f5f993676795708e36402ab38263/openapi_pydantic-0.5.1-py3-none-any.whl + name: openapi-pydantic + version: 0.5.1 + sha256: a3a09ef4586f5bd760a8df7f43028b60cafb6d9f61de2acba9574766255ab146 + requires_dist: + - pydantic>=1.8 + requires_python: '>=3.8,<4.0' - conda: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.4-h55fea9a_0.conda sha256: 3900f9f2dbbf4129cf3ad6acf4e4b6f7101390b53843591c53b00f034343bc4d md5: 11b3379b191f63139e29c0d19dee24cd @@ -6323,9 +7422,9 @@ packages: purls: [] size: 780253 timestamp: 1748010165522 -- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.4-h26f9b46_0.conda - sha256: e807f3bad09bdf4075dbb4168619e14b0c0360bacb2e12ef18641a834c8c5549 - md5: 14edad12b59ccbfa3910d42c72adc2a0 +- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.0-h26f9b46_0.conda + sha256: a47271202f4518a484956968335b2521409c8173e123ab381e775c358c67fe6d + md5: 9ee58d5c534af06558933af3c845a780 depends: - __glibc >=2.17,<3.0.a0 - ca-certificates @@ -6333,51 +7432,51 @@ packages: license: Apache-2.0 license_family: Apache purls: [] - size: 3119624 - timestamp: 1759324353651 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.5.4-h5503f6c_0.conda - sha256: f0512629f9589392c2fb9733d11e753d0eab8fc7602f96e4d7f3bd95c783eb07 - md5: 71118318f37f717eefe55841adb172fd + size: 3165399 + timestamp: 1762839186699 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.0-h5503f6c_0.conda + sha256: ebe93dafcc09e099782fe3907485d4e1671296bc14f8c383cb6f3dfebb773988 + md5: b34dc4172653c13dcf453862f251af2b depends: - __osx >=11.0 - ca-certificates license: Apache-2.0 license_family: Apache purls: [] - size: 3067808 - timestamp: 1759324763146 -- conda: https://conda.anaconda.org/conda-forge/linux-64/orjson-3.11.3-py312h868fb18_1.conda - sha256: ff40cc9c5f0d175ee328c668020dc57956569ea7f93695744127273d2a5e21ab - md5: 1b727788f141188ef09e85fc03716588 + size: 3108371 + timestamp: 1762839712322 +- conda: https://conda.anaconda.org/conda-forge/linux-64/orjson-3.11.4-py313h843e2db_1.conda + sha256: 9f62e5e935d122a3c919277308cf4fd03ac4547e9d83e291adec5004e5eb36be + md5: 2f7b4471c250f2b249a7b49599aa2ad8 depends: - python - - __glibc >=2.17,<3.0.a0 - libgcc >=14 - - python_abi 3.12.* *_cp312 + - __glibc >=2.17,<3.0.a0 + - python_abi 3.13.* *_cp313 constrains: - __glibc >=2.17 license: Apache-2.0 license_family: APACHE purls: - - pkg:pypi/orjson?source=compressed-mapping - size: 332917 - timestamp: 1756855079591 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/orjson-3.11.3-py313h80e0809_1.conda - sha256: f941db01dbbe4a1ad8ee093045ed828ef8a26b70780e79af85f9500419149747 - md5: 63e436563cc7da12dfa92504726627e2 + - pkg:pypi/orjson?source=hash-mapping + size: 317430 + timestamp: 1764441542172 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/orjson-3.11.4-py312h6ef9ec0_1.conda + sha256: a5ca8e18b05c677a1e163de71ff82a1eaf68758dc841522cde0c2bb74123bd8d + md5: 9a1253b028ab8526c51ba73ad1c4d3e6 depends: - python + - python 3.12.* *_cpython - __osx >=11.0 - - python 3.13.* *_cp313 - - python_abi 3.13.* *_cp313 + - python_abi 3.12.* *_cp312 constrains: - __osx >=11.0 license: Apache-2.0 license_family: APACHE purls: - pkg:pypi/orjson?source=hash-mapping - size: 294909 - timestamp: 1756855036702 + size: 288317 + timestamp: 1764441580932 - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda sha256: 1840bd90d25d4930d60f57b4f38d4e0ae3f5b8db2819638709c36098c6ba770c md5: e51f1e4089cad105b6cac64bd8166587 @@ -6402,110 +7501,110 @@ packages: - pkg:pypi/packaging?source=hash-mapping size: 62477 timestamp: 1745345660407 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pandas-2.3.3-py312hf79963d_1.conda - sha256: f633d5f9b28e4a8f66a6ec9c89ef1b6743b880b0511330184b4ab9b7e2dda247 - md5: e597b3e812d9613f659b7d87ad252d18 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pandas-2.3.3-py313h08cd8bf_2.conda + sha256: b998c30e7ff13fc966220891dc0a8318b0a6730933280d76ffa5be46ff928af5 + md5: 8a69ea71fdd37bfe42a28f0967dbb75a depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 - libstdcxx >=14 - numpy >=1.22.4 - numpy >=1.23,<3 - - python >=3.12,<3.13.0a0 + - python >=3.13,<3.14.0a0 - python-dateutil >=2.8.2 - python-tzdata >=2022.7 - - python_abi 3.12.* *_cp312 + - python_abi 3.13.* *_cp313 - pytz >=2020.1 constrains: + - pytables >=3.8.0 - xarray >=2022.12.0 - - qtpy >=2.3.0 - - html5lib >=1.1 + - zstandard >=0.19.0 + - fastparquet >=2022.12.0 + - bottleneck >=1.3.6 + - psycopg2 >=2.9.6 + - lxml >=4.9.2 + - numba >=0.56.4 + - pyreadstat >=1.2.0 + - openpyxl >=3.1.0 + - matplotlib >=3.6.3 + - xlrd >=2.0.1 - pandas-gbq >=0.19.0 + - python-calamine >=0.1.7 + - beautifulsoup4 >=4.11.2 - tzdata >=2022.7 - - fsspec >=2022.11.0 - - fastparquet >=2022.12.0 - - odfpy >=1.4.1 - - pyxlsb >=1.0.10 - scipy >=1.10.0 + - blosc >=1.21.3 + - qtpy >=2.3.0 + - gcsfs >=2022.11.0 - sqlalchemy >=2.0.0 - - pytables >=3.8.0 - - bottleneck >=1.3.6 - pyarrow >=10.0.1 - - numexpr >=2.8.4 + - odfpy >=1.4.1 + - fsspec >=2022.11.0 + - html5lib >=1.1 + - s3fs >=2022.11.0 - pyqt5 >=5.15.9 - xlsxwriter >=3.0.5 - - openpyxl >=3.1.0 - - blosc >=1.21.3 - - matplotlib >=3.6.3 - - lxml >=4.9.2 - - numba >=0.56.4 - - s3fs >=2022.11.0 + - numexpr >=2.8.4 + - pyxlsb >=1.0.10 - tabulate >=0.9.0 - - xlrd >=2.0.1 - - gcsfs >=2022.11.0 - - pyreadstat >=1.2.0 - - python-calamine >=0.1.7 - - zstandard >=0.19.0 - - psycopg2 >=2.9.6 - - beautifulsoup4 >=4.11.2 license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/pandas?source=compressed-mapping - size: 15099922 - timestamp: 1759266031115 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pandas-2.3.3-py313h7d16b84_1.conda - sha256: 39c1ceac0e4484fd3ec1324f0550a21aee7578f6ed2f21981b878573c197a40e - md5: 5ddddcc319d3aee21cc4fe4640a61f8a + size: 14912799 + timestamp: 1764615091147 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pandas-2.3.3-py312h5978115_2.conda + sha256: 93aa5b02e2394080a32fee9fb151da3384d317a42472586850abb37b28f314db + md5: fcbba82205afa4956c39136c68929385 depends: - __osx >=11.0 - libcxx >=19 - numpy >=1.22.4 - numpy >=1.23,<3 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython - python-dateutil >=2.8.2 - python-tzdata >=2022.7 - - python_abi 3.13.* *_cp313 + - python_abi 3.12.* *_cp312 - pytz >=2020.1 constrains: - - sqlalchemy >=2.0.0 - - openpyxl >=3.1.0 - - pytables >=3.8.0 - - gcsfs >=2022.11.0 + - xarray >=2022.12.0 + - scipy >=1.10.0 - tabulate >=0.9.0 - - bottleneck >=1.3.6 - - pandas-gbq >=0.19.0 + - pytables >=3.8.0 + - xlsxwriter >=3.0.5 + - pyxlsb >=1.0.10 - odfpy >=1.4.1 + - zstandard >=0.19.0 + - fastparquet >=2022.12.0 + - gcsfs >=2022.11.0 - beautifulsoup4 >=4.11.2 - - xarray >=2022.12.0 - - html5lib >=1.1 - - matplotlib >=3.6.3 + - qtpy >=2.3.0 + - xlrd >=2.0.1 + - pandas-gbq >=0.19.0 + - s3fs >=2022.11.0 - pyreadstat >=1.2.0 - - zstandard >=0.19.0 - - scipy >=1.10.0 + - tzdata >=2022.7 + - html5lib >=1.1 + - fsspec >=2022.11.0 - lxml >=4.9.2 - - s3fs >=2022.11.0 + - numexpr >=2.8.4 - blosc >=1.21.3 - - fastparquet >=2022.12.0 + - openpyxl >=3.1.0 + - pyarrow >=10.0.1 + - python-calamine >=0.1.7 - numba >=0.56.4 - - numexpr >=2.8.4 - - xlsxwriter >=3.0.5 - - pyxlsb >=1.0.10 - - psycopg2 >=2.9.6 + - sqlalchemy >=2.0.0 - pyqt5 >=5.15.9 - - python-calamine >=0.1.7 - - pyarrow >=10.0.1 - - fsspec >=2022.11.0 - - tzdata >=2022.7 - - xlrd >=2.0.1 - - qtpy >=2.3.0 + - psycopg2 >=2.9.6 + - bottleneck >=1.3.6 + - matplotlib >=3.6.3 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/pandas?source=hash-mapping - size: 14052072 - timestamp: 1759266462037 + - pkg:pypi/pandas?source=compressed-mapping + size: 13893993 + timestamp: 1764615503244 - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 sha256: 2bb9ba9857f4774b85900c2562f7e711d08dd48e2add9bee4e1612fbee27e16f md5: 457c2c8c08e54905d6954e79cb5b5db9 @@ -6529,6 +7628,27 @@ packages: - pkg:pypi/parso?source=hash-mapping size: 81562 timestamp: 1755974222274 +- pypi: https://files.pythonhosted.org/packages/7d/eb/b6260b31b1a96386c0a880edebe26f89669098acea8e0318bff6adb378fd/pathable-0.4.4-py3-none-any.whl + name: pathable + version: 0.4.4 + sha256: 5ae9e94793b6ef5a4cbe0a7ce9dbbefc1eec38df253763fd0aeeacf2762dbbc2 + requires_python: '>=3.7.0,<4.0.0' +- pypi: https://files.pythonhosted.org/packages/9a/70/875f4a23bfc4731703a5835487d0d2fb999031bd415e7d17c0ae615c18b7/pathvalidate-3.3.1-py3-none-any.whl + name: pathvalidate + version: 3.3.1 + sha256: 5263baab691f8e1af96092fa5137ee17df5bdfbd6cff1fcac4d6ef4bc2e1735f + requires_dist: + - sphinx-rtd-theme>=1.2.2 ; extra == 'docs' + - sphinx>=2.4 ; extra == 'docs' + - urllib3<2 ; extra == 'docs' + - readmemaker>=1.2.0 ; extra == 'readme' + - path>=13,<18 ; extra == 'readme' + - allpairspy>=2 ; extra == 'test' + - click>=6.2 ; extra == 'test' + - faker>=1.0.8 ; extra == 'test' + - pytest>=6.0.1 ; extra == 'test' + - pytest-md-report>=0.6.2 ; extra == 'test' + requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.46-h1321c63_0.conda sha256: 5c7380c8fd3ad5fc0f8039069a45586aa452cf165264bc5a437ad80397b32934 md5: 7fa07cb0fb1b625a089ccc01218ee5b1 @@ -6553,87 +7673,99 @@ packages: - pkg:pypi/pexpect?source=hash-mapping size: 53561 timestamp: 1733302019362 -- conda: https://conda.anaconda.org/conda-forge/noarch/pickleshare-0.7.5-pyhd8ed1ab_1004.conda - sha256: e2ac3d66c367dada209fc6da43e645672364b9fd5f9d28b9f016e24b81af475b - md5: 11a9d1d09a3615fc07c3faf79bc0b943 - depends: - - python >=3.9 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pickleshare?source=hash-mapping - size: 11748 - timestamp: 1733327448200 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-11.3.0-py312h7b42cdd_3.conda - sha256: ad4a22899819a2bb86550d1fc3833a44e073aac80ea61529676b5e73220fcc2b - md5: 1d7f05c3f8bb4e98d02fca45f0920b23 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-12.0.0-py313h80991f8_2.conda + sha256: 5319da7c24f4f876c966fc6e83789aa4530779d4454c37c4169f79050555bc26 + md5: 37ca27d2f726f29a068230d8f6917ce4 depends: - python - libgcc >=14 - __glibc >=2.17,<3.0.a0 - - lcms2 >=2.17,<3.0a0 - libwebp-base >=1.6.0,<2.0a0 - - libjpeg-turbo >=3.1.0,<4.0a0 - - libzlib >=1.3.1,<2.0a0 - - tk >=8.6.13,<8.7.0a0 + - libjpeg-turbo >=3.1.2,<4.0a0 + - lcms2 >=2.17,<3.0a0 - libxcb >=1.17.0,<2.0a0 - - libtiff >=4.7.0,<4.8.0a0 + - python_abi 3.13.* *_cp313 + - zlib-ng >=2.3.1,<2.4.0a0 - libfreetype >=2.14.1 - libfreetype6 >=2.14.1 - - python_abi 3.12.* *_cp312 - - openjpeg >=2.5.3,<3.0a0 + - openjpeg >=2.5.4,<3.0a0 + - libtiff >=4.7.1,<4.8.0a0 + - tk >=8.6.13,<8.7.0a0 license: HPND purls: - - pkg:pypi/pillow?source=compressed-mapping - size: 1028547 - timestamp: 1758208668856 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pillow-11.3.0-py313he4c6d0d_3.conda - sha256: 060f14a270d39f5c3df89ea2c46f68b6cadd4b6950360af6b7524f5ea33c9354 - md5: 2f6f5c3fa80054f42d8cd4d23e4d93d6 + - pkg:pypi/pillow?source=hash-mapping + size: 1040806 + timestamp: 1764330106863 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pillow-12.0.0-py312h261a3e5_1.conda + sha256: 675d9d31ab1b818808214752ae26e2d4d18325c64c21ef0e9543378b3a5fc3ff + md5: 01a90594b16feff1693bdd5a1fe0bd50 depends: - python - - python 3.13.* *_cp313 + - python 3.12.* *_cpython - __osx >=11.0 - - libtiff >=4.7.0,<4.8.0a0 - - libzlib >=1.3.1,<2.0a0 - - lcms2 >=2.17,<3.0a0 - libfreetype >=2.14.1 - libfreetype6 >=2.14.1 + - libtiff >=4.7.1,<4.8.0a0 + - zlib-ng >=2.2.5,<2.3.0a0 + - python_abi 3.12.* *_cp312 + - tk >=8.6.13,<8.7.0a0 + - libxcb >=1.17.0,<2.0a0 + - libjpeg-turbo >=3.1.2,<4.0a0 + - openjpeg >=2.5.4,<3.0a0 + - lcms2 >=2.17,<3.0a0 - libwebp-base >=1.6.0,<2.0a0 - - libjpeg-turbo >=3.1.0,<4.0a0 + license: HPND + purls: + - pkg:pypi/pillow?source=hash-mapping + size: 950506 + timestamp: 1764033310549 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pillow-12.0.0-py312h95c711c_2.conda + sha256: b720df83d27af31466c77554b95a78fa03e458810537570fb05850a119667c07 + md5: 817cd66153338f403cf05d8a09d93fad + depends: + - python + - python 3.12.* *_cpython + - __osx >=11.0 + - libjpeg-turbo >=3.1.2,<4.0a0 - libxcb >=1.17.0,<2.0a0 - - python_abi 3.13.* *_cp313 + - libfreetype >=2.14.1 + - libfreetype6 >=2.14.1 + - zlib-ng >=2.3.1,<2.4.0a0 + - libtiff >=4.7.1,<4.8.0a0 + - libwebp-base >=1.6.0,<2.0a0 - tk >=8.6.13,<8.7.0a0 - - openjpeg >=2.5.3,<3.0a0 + - lcms2 >=2.17,<3.0a0 + - openjpeg >=2.5.4,<3.0a0 + - python_abi 3.12.* *_cp312 license: HPND purls: - - pkg:pypi/pillow?source=compressed-mapping - size: 963129 - timestamp: 1758208741247 -- conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh145f28c_0.conda - sha256: 20fe420bb29c7e655988fd0b654888e6d7755c1d380f82ca2f1bd2493b95d650 - md5: e7ab34d5a93e0819b62563c78635d937 + - pkg:pypi/pillow?source=hash-mapping + size: 950740 + timestamp: 1764330196015 +- conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.3-pyh145f28c_0.conda + sha256: 4d5e2faca810459724f11f78d19a0feee27a7be2b3fc5f7abbbec4c9fdcae93d + md5: bf47878473e5ab9fdb4115735230e191 depends: - python >=3.13.0a0 license: MIT license_family: MIT purls: - - pkg:pypi/pip?source=compressed-mapping - size: 1179951 - timestamp: 1753925011027 -- conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh8b19718_0.conda - sha256: ec9ed3cef137679f3e3a68e286c6efd52144684e1be0b05004d9699882dadcdd - md5: dfce4b2af4bfe90cdcaf56ca0b28ddf5 + - pkg:pypi/pip?source=hash-mapping + size: 1177084 + timestamp: 1762776338614 +- conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.3-pyh8b19718_0.conda + sha256: b67692da1c0084516ac1c9ada4d55eaf3c5891b54980f30f3f444541c2706f1e + md5: c55515ca43c6444d2572e0f0d93cb6b9 depends: - - python >=3.9,<3.13.0a0 + - python >=3.10,<3.13.0a0 - setuptools - wheel license: MIT license_family: MIT purls: - pkg:pypi/pip?source=compressed-mapping - size: 1177168 - timestamp: 1753924973872 + size: 1177534 + timestamp: 1762776258783 - conda: https://conda.anaconda.org/conda-forge/linux-64/pixman-0.46.4-h54a6638_1.conda sha256: 43d37bc9ca3b257c5dd7bf76a8426addbdec381f6786ff441dc90b1a49143b6a md5: c01af13bdc553d1a8fbfff6e8db075f0 @@ -6647,22 +7779,22 @@ packages: purls: [] size: 450960 timestamp: 1754665235234 -- conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.4.0-pyhcf101f3_0.conda - sha256: dfe0fa6e351d2b0cef95ac1a1533d4f960d3992f9e0f82aeb5ec3623a699896b - md5: cc9d9a3929503785403dbfad9f707145 +- conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.5.0-pyhcf101f3_0.conda + sha256: 7efd51b48d908de2d75cbb3c4a2e80dd9454e1c5bb8191b261af3136f7fa5888 + md5: 5c7a868f8241e64e1cf5fdf4962f23e2 depends: - python >=3.10 - python license: MIT license_family: MIT purls: - - pkg:pypi/platformdirs?source=compressed-mapping - size: 23653 - timestamp: 1756227402815 + - pkg:pypi/platformdirs?source=hash-mapping + size: 23625 + timestamp: 1759953252315 - pypi: ./ name: pleiades-neutron - version: 2.1.0.dev10+d202510271845 - sha256: 74832b7841b7d85e43041ad642b66430d41316cb743b40ede148e6203ff4f151 + version: 2.1.0.dev21+d202512041930 + sha256: 02ce70ac0b12f5b8c3763a45a0daf19873c40104853684040f3119e8fa5c8315 requires_dist: - numpy - scipy @@ -6677,6 +7809,7 @@ packages: - pydantic - loguru - nova-galaxy>=0.7.4,<0.8 ; extra == 'nova' + - fastmcp>=2.12.0,<3 ; extra == 'mcp' requires_python: '>=3.10,<4.0' editable: true - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhd8ed1ab_0.conda @@ -6690,22 +7823,22 @@ packages: - pkg:pypi/pluggy?source=hash-mapping size: 24246 timestamp: 1747339794916 -- conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-4.3.0-pyha770c72_0.conda - sha256: 66b6d429ab2201abaa7282af06b17f7631dcaafbc5aff112922b48544514b80a - md5: bc6c44af2a9e6067dd7e949ef10cdfba +- conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-4.5.0-pyha770c72_0.conda + sha256: 8481f4939b1f81cf0db12456819368b41e3f998e4463e41611de4b13752b2c08 + md5: af8d4882203bccefec6f1aeed70030c6 depends: - cfgv >=2.0.0 - identify >=1.0.0 - nodeenv >=0.11.1 - - python >=3.9 + - python >=3.10 - pyyaml >=5.1 - virtualenv >=20.10.0 license: MIT license_family: MIT purls: - pkg:pypi/pre-commit?source=hash-mapping - size: 195839 - timestamp: 1754831350570 + size: 201265 + timestamp: 1764067809524 - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.23.1-pyhd8ed1ab_0.conda sha256: 13dc67de68db151ff909f2c1d2486fa7e2d51355b25cee08d26ede1b62d48d40 md5: a1e91db2d17fd258c64921cb38e6745a @@ -6731,44 +7864,44 @@ packages: - pkg:pypi/prompt-toolkit?source=hash-mapping size: 273927 timestamp: 1756321848365 -- pypi: https://files.pythonhosted.org/packages/84/5e/036d2b105927ae7f179346c9911d16c345f4dba5a19a063f23a8d28acfbd/propcache-0.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/0a/b6/5c9a0e42df4d00bfb4a3cbbe5cf9f54260300c88a0e9af1f47ca5ce17ac0/propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl name: propcache - version: 0.4.0 - sha256: 22f589652ee38de96aa58dd219335604e09666092bc250c1d9c26a55bcef9932 + version: 0.4.1 + sha256: f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/d0/d9/b15e88b4463df45a7793fb04e2b5497334f8fcc24e281c221150a0af9aff/propcache-0.4.0-cp313-cp313-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: propcache - version: 0.4.0 - sha256: 227892597953611fce2601d49f1d1f39786a6aebc2f253c2de775407f725a3f6 + version: 0.4.1 + sha256: d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.1.0-py312h4c3975b_0.conda - sha256: 15484f43cf8a5c08b073a28e9789bc76abaf5ef328148d00ad0c1f05079a9455 - md5: d99ab14339ac25676f1751b76b26c9b2 +- conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.1.3-py313h54dd161_0.conda + sha256: 26cf5a69d04ef66f03516b8a8211a43bb23d5225faacd7d36e5c987b0d66af0a + md5: 1d719fc61f91ab2644a2eeb35fcab360 depends: - - __glibc >=2.17,<3.0.a0 + - python - libgcc >=14 - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 + - __glibc >=2.17,<3.0.a0 + - python_abi 3.13.* *_cp313 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/psutil?source=compressed-mapping - size: 475455 - timestamp: 1758169358813 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.1.0-py313h6535dbc_0.conda - sha256: e29785861f5a3af7feb010a5d58501994f672ca4c76a1676f5b80886dcce5613 - md5: 7ef1ef75e236bea54eebb1df1584f8ee + - pkg:pypi/psutil?source=hash-mapping + size: 501735 + timestamp: 1762092897061 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.1.3-py312h37e1c23_0.conda + sha256: cd831dfe655fdb581e1c2c71fa072d2fce38538474a36cbde3ae2dd910a2ae76 + md5: d0b2f83de57eafaa6d7700b589c66096 depends: + - python - __osx >=11.0 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 + - python 3.12.* *_cpython + - python_abi 3.12.* *_cp312 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/psutil?source=compressed-mapping - size: 491438 - timestamp: 1758169690805 + - pkg:pypi/psutil?source=hash-mapping + size: 508014 + timestamp: 1762093047823 - conda: https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-hb9d3cd8_1002.conda sha256: 9c88f8c64590e9567c6c80823f0328e58d3b1efb0e1c539c0315ceca764e0973 md5: b3c17d95b5a10c6e64a21fa17573e70e @@ -6811,42 +7944,82 @@ packages: - pkg:pypi/pure-eval?source=hash-mapping size: 16668 timestamp: 1733569518868 -- conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-4-hd8ed1ab_3.tar.bz2 - sha256: d4fb485b79b11042a16dc6abfb0c44c4f557707c2653ac47c81e5d32b24a3bb0 - md5: 878f923dd6acc8aeb47a75da6c4098be +- pypi: https://files.pythonhosted.org/packages/99/10/72f6f213b8f0bce36eff21fda0a13271834e9eeff7f9609b01afdc253c79/py_key_value_aio-0.3.0-py3-none-any.whl + name: py-key-value-aio + version: 0.3.0 + sha256: 1c781915766078bfd608daa769fefb97e65d1d73746a3dfb640460e322071b64 + requires_dist: + - py-key-value-shared==0.3.0 + - beartype>=0.20.0 + - diskcache>=5.0.0 ; extra == 'disk' + - pathvalidate>=3.3.1 ; extra == 'disk' + - duckdb>=1.1.1 ; extra == 'duckdb' + - pytz>=2025.2 ; extra == 'duckdb' + - aioboto3>=13.3.0 ; extra == 'dynamodb' + - types-aiobotocore-dynamodb>=2.16.0 ; extra == 'dynamodb' + - elasticsearch>=8.0.0 ; extra == 'elasticsearch' + - aiohttp>=3.12 ; extra == 'elasticsearch' + - aiofile>=3.5.0 ; extra == 'filetree' + - anyio>=4.4.0 ; extra == 'filetree' + - keyring>=25.6.0 ; extra == 'keyring' + - keyring>=25.6.0 ; extra == 'keyring-linux' + - dbus-python>=1.4.0 ; extra == 'keyring-linux' + - aiomcache>=0.8.0 ; extra == 'memcached' + - cachetools>=5.0.0 ; extra == 'memory' + - pymongo>=4.0.0 ; extra == 'mongodb' + - pydantic>=2.11.9 ; extra == 'pydantic' + - redis>=4.3.0 ; extra == 'redis' + - rocksdict>=0.3.24 ; python_full_version >= '3.12' and extra == 'rocksdb' + - rocksdict>=0.3.2 ; python_full_version < '3.12' and extra == 'rocksdb' + - valkey-glide>=2.1.0 ; extra == 'valkey' + - hvac>=2.3.0 ; extra == 'vault' + - types-hvac>=2.3.0 ; extra == 'vault' + - cryptography>=45.0.0 ; extra == 'wrappers-encryption' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/51/e4/b8b0a03ece72f47dce2307d36e1c34725b7223d209fc679315ffe6a4e2c3/py_key_value_shared-0.3.0-py3-none-any.whl + name: py-key-value-shared + version: 0.3.0 + sha256: 5b0efba7ebca08bb158b1e93afc2f07d30b8f40c2fc12ce24a4c0d84f42f9298 + requires_dist: + - typing-extensions>=4.15.0 + - beartype>=0.20.0 + requires_python: '>=3.10' +- conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-11-hc364b38_1.conda + sha256: 9e7fe12f727acd2787fb5816b2049cef4604b7a00ad3e408c5e709c298ce8bf1 + md5: f0599959a2447c1e544e216bddf393fa license: BSD-3-Clause license_family: BSD purls: [] - size: 9906 - timestamp: 1610372835205 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pycosat-0.6.6-py312h4c3975b_3.conda - sha256: 4e8db35a8bae47b64abfff65cb69cdf364010e2543829147d44a8604eaafc4dc - md5: 6534fb2caeb49f52355851df731b48b4 + size: 14671 + timestamp: 1752769938071 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pycosat-0.6.6-py313h07c4f96_3.conda + sha256: c8dee181d424b405914d87344abec25302927ce69f07186f3a01c4fc42ec6aee + md5: 7b943aff00c5b521fe35332b1dd6aeeb depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 license: MIT license_family: MIT purls: - pkg:pypi/pycosat?source=hash-mapping - size: 88488 - timestamp: 1757744773264 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pycosat-0.6.6-py313hcdf3177_3.conda - sha256: 59384b3df6783fb9826f75bfac1ae90a30908f9eb5ec5d516074a6b63d03ca4b - md5: ea1ac4959a65715e89d09390d03041a8 + size: 87894 + timestamp: 1757744775176 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pycosat-0.6.6-py312h163523d_3.conda + sha256: 4f70ae766f95f27c726ee91f1ee919513a448685d9384f51ce8389b831f82968 + md5: 67a88a3b78523ce45c19a9c508c392fc depends: - __osx >=11.0 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 license: MIT license_family: MIT purls: - pkg:pypi/pycosat?source=hash-mapping - size: 92405 - timestamp: 1757745077396 + size: 93012 + timestamp: 1757744906432 - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda sha256: 79db7928d13fab2d892592223d7570f5061c192f27b9febd1a418427b719acc6 md5: 12c566707c80111f9799308d9e265aef @@ -6859,56 +8032,73 @@ packages: - pkg:pypi/pycparser?source=hash-mapping size: 110100 timestamp: 1733195786147 -- conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.11.10-pyh3cfb1c2_0.conda - sha256: 26779821ba83b896f319837d7c5301cc244dee41b311d2bd57cbd693ed9e43ef - md5: 918d9adfc81cb14ab4cced31d22c7711 +- conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.12.5-pyhcf101f3_1.conda + sha256: 868569d9505b7fe246c880c11e2c44924d7613a8cdcc1f6ef85d5375e892f13d + md5: c3946ed24acdb28db1b5d63321dbca7d depends: - - annotated-types >=0.6.0 - - pydantic-core 2.33.2 + - typing-inspection >=0.4.2 + - typing_extensions >=4.14.1 - python >=3.10 - typing-extensions >=4.6.1 - - typing-inspection >=0.4.0 - - typing_extensions >=4.12.2 + - annotated-types >=0.6.0 + - pydantic-core ==2.41.5 + - python license: MIT license_family: MIT purls: - pkg:pypi/pydantic?source=hash-mapping - size: 307863 - timestamp: 1759584847417 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.33.2-py312h680f630_0.conda - sha256: 4d14d7634c8f351ff1e63d733f6bb15cba9a0ec77e468b0de9102014a4ddc103 - md5: cfbd96e5a0182dfb4110fc42dda63e57 + size: 340482 + timestamp: 1764434463101 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.41.5-py313h843e2db_1.conda + sha256: b15568ddc03bd33ea41610e5df951be4e245cd61957cbf8c2cfd12557f3d53b5 + md5: f27c39a1906771bbe56cd26a76bf0b8b depends: - python - typing-extensions >=4.6.0,!=4.7.0 + - libgcc >=14 - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - python_abi 3.12.* *_cp312 + - python_abi 3.13.* *_cp313 constrains: - __glibc >=2.17 license: MIT license_family: MIT purls: - pkg:pypi/pydantic-core?source=hash-mapping - size: 1890081 - timestamp: 1746625309715 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.33.2-py313hf3ab51e_0.conda - sha256: a70d31e04b81df4c98821668d87089279284d2dbcc70413f791eaa60b28f42fd - md5: 0d5685f410c4234af909cde6fac63cb0 + size: 1940186 + timestamp: 1762989000579 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.41.5-py312h6ef9ec0_1.conda + sha256: 048da0a49d644dba126905a1abcea0aee75efe88b5d621b9007b569dd753cfbc + md5: 88a76b4c912b6127d64298e3d8db980c depends: - python - typing-extensions >=4.6.0,!=4.7.0 - - python 3.13.* *_cp313 + - python 3.12.* *_cpython - __osx >=11.0 - - python_abi 3.13.* *_cp313 + - python_abi 3.12.* *_cp312 constrains: - __osx >=11.0 license: MIT license_family: MIT purls: - pkg:pypi/pydantic-core?source=hash-mapping - size: 1720344 - timestamp: 1746625313921 + size: 1769018 + timestamp: 1762989029329 +- pypi: https://files.pythonhosted.org/packages/c1/60/5d4751ba3f4a40a6891f24eec885f51afd78d208498268c734e256fb13c4/pydantic_settings-2.12.0-py3-none-any.whl + name: pydantic-settings + version: 2.12.0 + sha256: fddb9fd99a5b18da837b29710391e945b1e30c135477f484084ee513adb93809 + requires_dist: + - pydantic>=2.7.0 + - python-dotenv>=0.21.0 + - typing-inspection>=0.4.0 + - boto3-stubs[secretsmanager] ; extra == 'aws-secrets-manager' + - boto3>=1.35.0 ; extra == 'aws-secrets-manager' + - azure-identity>=1.16.0 ; extra == 'azure-key-vault' + - azure-keyvault-secrets>=4.8.0 ; extra == 'azure-key-vault' + - google-cloud-secret-manager>=2.23.1 ; extra == 'gcp-secret-manager' + - tomli>=2.0.1 ; extra == 'toml' + - pyyaml>=6.0.1 ; extra == 'yaml' + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/11/4a/31a363370478b63c6289a34743f2ba2d3ae1bd8223e004d18ab28fb92385/pyerfa-2.0.1.5-cp39-abi3-macosx_11_0_arm64.whl name: pyerfa version: 2.0.1.5 @@ -6940,38 +8130,57 @@ packages: - pkg:pypi/pygments?source=hash-mapping size: 889287 timestamp: 1750615908735 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-11.1-py313had225c5_1.conda - sha256: 9d4d32942bb2b11141836dadcebd7f54b60837447bc7eef3a9ad01a8adc11547 - md5: 60277e90c6cd9972a9e53f812e5ce83f +- pypi: https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl + name: pyjwt + version: 2.10.1 + sha256: dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb + requires_dist: + - cryptography>=3.4.0 ; extra == 'crypto' + - coverage[toml]==5.0.4 ; extra == 'dev' + - cryptography>=3.4.0 ; extra == 'dev' + - pre-commit ; extra == 'dev' + - pytest>=6.0.0,<7.0.0 ; extra == 'dev' + - sphinx ; extra == 'dev' + - sphinx-rtd-theme ; extra == 'dev' + - zope-interface ; extra == 'dev' + - sphinx ; extra == 'docs' + - sphinx-rtd-theme ; extra == 'docs' + - zope-interface ; extra == 'docs' + - coverage[toml]==5.0.4 ; extra == 'tests' + - pytest>=6.0.0,<7.0.0 ; extra == 'tests' + requires_python: '>=3.9' +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-12.1-py312h19bbe71_0.conda + sha256: b015f430fe9ea2c53e14be13639f1b781f68deaa5ae74cd8c1d07720890cd02a + md5: c65d7abdc9e60fd3af0ed852591adf1b depends: - __osx >=11.0 - - libffi >=3.4.6,<3.5.0a0 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 + - libffi >=3.5.2,<3.6.0a0 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 - setuptools license: MIT license_family: MIT purls: - pkg:pypi/pyobjc-core?source=hash-mapping - size: 478509 - timestamp: 1756813300773 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-11.1-py313h4e140e3_1.conda - sha256: 139322c875d35199836802fc545b814ffb2001af2cee956b9da4d2fc7d3aec45 - md5: 1057463a9f16501716f978534aa2d838 + size: 476750 + timestamp: 1763151865523 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-12.1-py312h1de3e18_0.conda + sha256: 3710f5ae09c2ea77ba4d82cc51e876d9fc009b878b197a40d3c6347c09ae7d7c + md5: f0bae1b67ece138378923e340b940051 depends: - __osx >=11.0 - - libffi >=3.4.6,<3.5.0a0 - - pyobjc-core 11.1.* - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 + - libffi >=3.5.2,<3.6.0a0 + - pyobjc-core 12.1.* + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 license: MIT license_family: MIT purls: - pkg:pypi/pyobjc-framework-cocoa?source=hash-mapping - size: 381682 - timestamp: 1756824258635 + size: 377723 + timestamp: 1763160705325 - conda: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.2.5-pyhcf101f3_0.conda sha256: 6814b61b94e95ffc45ec539a6424d8447895fef75b0fec7e1be31f5beee883fb md5: 6c8979be6d7a17692793114fa26916e8 @@ -6981,9 +8190,13 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/pyparsing?source=compressed-mapping + - pkg:pypi/pyparsing?source=hash-mapping size: 104044 timestamp: 1758436411254 +- pypi: https://files.pythonhosted.org/packages/df/80/fc9d01d5ed37ba4c42ca2b55b4339ae6e200b456be3a1aaddf4a9fa99b8c/pyperclip-1.11.0-py3-none-any.whl + name: pyperclip + version: 1.11.0 + sha256: 299403e9ff44581cb9ba2ffeed69c7aa96a008622ad0c46cb575ca75b5b84273 - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject_hooks-1.2.0-pyhd8ed1ab_1.conda sha256: 065ac44591da9abf1ff740feb25929554b920b00d09287a551fcced2c9791092 md5: d4582021af437c931d7d77ec39007845 @@ -6996,9 +8209,9 @@ packages: - pkg:pypi/pyproject-hooks?source=hash-mapping size: 15528 timestamp: 1733710122949 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pyside6-6.9.3-py312h9da60e5_1.conda - sha256: 31f0d79f4f9c989a9acf566948cbd7d2d1c08e4840a04461f58bc3a734b8332b - md5: 30e8545156cab1f5ff0fe9f0297c77c6 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pyside6-6.9.3-py313h85046ba_1.conda + sha256: 8d143b89d075b39fa25e69ad9be2396f4b591a205f95b2bf5a81a14cd397c56f + md5: bb7ac52bfa917611096023598a7df152 depends: - __glibc >=2.17,<3.0.a0 - libclang13 >=21.1.2 @@ -7011,8 +8224,8 @@ packages: - libxml2 - libxml2-16 >=2.14.6 - libxslt >=1.1.43,<2.0a0 - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 - qt6-main 6.9.3.* - qt6-main >=6.9.3,<6.10.0a0 license: LGPL-3.0-only @@ -7020,8 +8233,8 @@ packages: purls: - pkg:pypi/pyside6?source=hash-mapping - pkg:pypi/shiboken6?source=hash-mapping - size: 10161603 - timestamp: 1759403426235 + size: 10101334 + timestamp: 1759403237088 - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda sha256: ba3b032fa52709ce0d9fd388f63d330a026754587a2f461117cac9ab73d8d0d8 md5: 461219d1a5bd61342293efa2c0c90eac @@ -7034,26 +8247,27 @@ packages: - pkg:pypi/pysocks?source=hash-mapping size: 21085 timestamp: 1733217331982 -- conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.4.2-pyhd8ed1ab_0.conda - sha256: 41053d9893e379a3133bb9b557b98a3d2142fca474fb6b964ba5d97515f78e2d - md5: 1f987505580cb972cf28dc5f74a0f81b +- conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.0.1-pyhcf101f3_0.conda + sha256: 7f25f71e4890fb60a4c4cb4563d10acf2d741804fec51e9b85a6fd97cd686f2f + md5: fa7f71faa234947d9c520f89b4bda1a2 depends: - - colorama >=0.4 - - exceptiongroup >=1 - - iniconfig >=1 - - packaging >=20 - - pluggy >=1.5,<2 - pygments >=2.7.2 - python >=3.10 + - iniconfig >=1.0.1 + - packaging >=22 + - pluggy >=1.5,<2 - tomli >=1 + - colorama >=0.4 + - exceptiongroup >=1 + - python constrains: - pytest-faulthandler >=2 license: MIT license_family: MIT purls: - pkg:pypi/pytest?source=compressed-mapping - size: 276734 - timestamp: 1757011891753 + size: 299017 + timestamp: 1763049198670 - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-cov-7.0.0-pyhcf101f3_1.conda sha256: d0f45586aad48ef604590188c33c83d76e4fc6370ac569ba0900906b24fd6a26 md5: 6891acad5e136cb62a8c2ed2679d6528 @@ -7066,7 +8280,7 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/pytest-cov?source=compressed-mapping + - pkg:pypi/pytest-cov?source=hash-mapping size: 29016 timestamp: 1757612051022 - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-repeat-0.9.4-pyhd8ed1ab_0.conda @@ -7096,57 +8310,56 @@ packages: - pkg:pypi/pytest-xdist?source=hash-mapping size: 39300 timestamp: 1751452761594 -- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.11-h9e4cc4f_0_cpython.conda - sha256: 6cca004806ceceea9585d4d655059e951152fc774a471593d4f5138e6a54c81d - md5: 94206474a5608243a10c92cefbe0908f +- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.10-hc97d973_100_cp313.conda + build_number: 100 + sha256: fbc193c94b72b09b1e5b4ae7d65764b90bb9e6e4d672481d91d8769ab20e909e + md5: 9e9d4de52c55af3563a00981fb39cbed depends: - __glibc >=2.17,<3.0.a0 - bzip2 >=1.0.8,<2.0a0 - ld_impl_linux-64 >=2.36.1 - - libexpat >=2.7.0,<3.0a0 - - libffi >=3.4.6,<3.5.0a0 - - libgcc >=13 + - libexpat >=2.7.3,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - libgcc >=14 - liblzma >=5.8.1,<6.0a0 - - libnsl >=2.0.1,<2.1.0a0 - - libsqlite >=3.50.0,<4.0a0 - - libuuid >=2.38.1,<3.0a0 - - libxcrypt >=4.4.36 + - libmpdec >=4.0.0,<5.0a0 + - libsqlite >=3.51.1,<4.0a0 + - libuuid >=2.41.2,<3.0a0 - libzlib >=1.3.1,<2.0a0 - ncurses >=6.5,<7.0a0 - - openssl >=3.5.0,<4.0a0 + - openssl >=3.5.4,<4.0a0 + - python_abi 3.13.* *_cp313 - readline >=8.2,<9.0a0 - tk >=8.6.13,<8.7.0a0 - tzdata - constrains: - - python_abi 3.12.* *_cp312 license: Python-2.0 purls: [] - size: 31445023 - timestamp: 1749050216615 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.7-h5c937ed_100_cp313.conda - build_number: 100 - sha256: b9776cc330fa4836171a42e0e9d9d3da145d7702ba6ef9fad45e94f0f016eaef - md5: 445d057271904b0e21e14b1fa1d07ba5 + size: 37104697 + timestamp: 1764755723771 + python_site_packages_path: lib/python3.13/site-packages +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.12.12-h18782d2_1_cpython.conda + build_number: 1 + sha256: 626da9bb78459ce541407327d1e22ee673fd74e9103f1a0e0f4e3967ad0a23a7 + md5: 0322f2ddca2cafbf34ef3ddbea100f73 depends: - __osx >=11.0 - bzip2 >=1.0.8,<2.0a0 - libexpat >=2.7.1,<3.0a0 - - libffi >=3.4.6,<3.5.0a0 + - libffi >=3.5.2,<3.6.0a0 - liblzma >=5.8.1,<6.0a0 - - libmpdec >=4.0.0,<5.0a0 - libsqlite >=3.50.4,<4.0a0 - libzlib >=1.3.1,<2.0a0 - ncurses >=6.5,<7.0a0 - - openssl >=3.5.2,<4.0a0 - - python_abi 3.13.* *_cp313 + - openssl >=3.5.4,<4.0a0 - readline >=8.2,<9.0a0 - tk >=8.6.13,<8.7.0a0 - tzdata + constrains: + - python_abi 3.12.* *_cp312 license: Python-2.0 purls: [] - size: 11926240 - timestamp: 1756909724811 - python_site_packages_path: lib/python3.13/site-packages + size: 12062421 + timestamp: 1761176476561 - conda: https://conda.anaconda.org/conda-forge/noarch/python-build-1.3.0-pyhff2d567_0.conda sha256: b2df2264f0936b9f95e13ac79b596fac86d3b649812da03a61543e11e669714c md5: ed5d43e9ef92cc2a9872f9bdfe94b984 @@ -7178,6 +8391,13 @@ packages: - pkg:pypi/python-dateutil?source=hash-mapping size: 233310 timestamp: 1751104122689 +- pypi: https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl + name: python-dotenv + version: 1.2.1 + sha256: b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61 + requires_dist: + - click>=5.0 ; extra == 'cli' + requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda sha256: df9aa74e9e28e8d1309274648aac08ec447a92512c33f61a8de0afa9ce32ebe8 md5: 23029aae904a2ba587daba708208012f @@ -7190,26 +8410,26 @@ packages: - pkg:pypi/fastjsonschema?source=hash-mapping size: 244628 timestamp: 1755304154927 -- conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.12.11-hd8ed1ab_0.conda - sha256: b8afeaefe409d61fa4b68513b25a66bb17f3ca430d67cfea51083c7bfbe098ef - md5: 859c6bec94cd74119f12b961aba965a8 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.12.12-hd8ed1ab_1.conda + sha256: 59f17182813f8b23709b7d4cfda82c33b72dd007cb729efa0033c609fbd92122 + md5: c20172b4c59fbe288fa50cdc1b693d73 depends: - - cpython 3.12.11.* + - cpython 3.12.12.* - python_abi * *_cp312 license: Python-2.0 purls: [] - size: 45836 - timestamp: 1749047798827 -- conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.7-h4df99d1_100.conda - sha256: 109794a80cf31450903522e2613b6d760ae4655e65d6fff68467934fbe297ea1 - md5: 47a123ca8e727d886a2c6d0c71658f8c + size: 45888 + timestamp: 1761175248278 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.10-h4df99d1_100.conda + sha256: 33d21adcc7ea6c3631e7de8c0ef2f789d027365558f8b00ac0683c4be32f21da + md5: 2432ffd09843b984b4fc4e1c82f7c836 depends: - - cpython 3.13.7.* + - cpython 3.13.10.* - python_abi * *_cp313 license: Python-2.0 purls: [] - size: 48178 - timestamp: 1756909461701 + size: 48209 + timestamp: 1764753035421 - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda sha256: 4790787fe1f4e8da616edca4acf6a4f8ed4e7c6967aa31b920208fc8f95efcca md5: a61bf9ec79426938ff785eb69dbb1960 @@ -7221,6 +8441,11 @@ packages: - pkg:pypi/python-json-logger?source=hash-mapping size: 13383 timestamp: 1677079727691 +- pypi: https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl + name: python-multipart + version: 0.0.20 + sha256: 8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104 + requires_python: '>=3.8' - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2025.2-pyhd8ed1ab_0.conda sha256: e8392a8044d56ad017c08fec2b0eb10ae3d1235ac967d0aab8bd7b41c4a5eaf0 md5: 88476ae6ebd24f39261e0854ac244f33 @@ -7265,36 +8490,43 @@ packages: - pkg:pypi/pytz?source=hash-mapping size: 189015 timestamp: 1742920947249 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py312h8a5da7c_0.conda - sha256: 1b3dc4c25c83093fff08b86a3574bc6b94ba355c8eba1f35d805c5e256455fc7 - md5: fba10c2007c8b06f77c5a23ce3a635ad +- pypi: https://files.pythonhosted.org/packages/36/65/a5549325daafc3eae4b52de076798839eaf529a07218f8fb18cccefe76a1/pywavelets-1.9.0-cp312-cp312-macosx_11_0_arm64.whl + name: pywavelets + version: 1.9.0 + sha256: df7436a728339696a7aa955c020ae65c85b0d9d2b5ff5b4cf4551f5d4c50f2c7 + requires_dist: + - numpy>=1.25,<3 + requires_python: '>=3.11' +- conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py313h3dea7bd_0.conda + sha256: 40dcd6718dce5fbee8aabdd0519f23d456d8feb2e15ac352eaa88bbfd3a881af + md5: 4794ea0adaebd9f844414e594b142cb2 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 - yaml >=0.2.5,<0.3.0a0 license: MIT license_family: MIT purls: - - pkg:pypi/pyyaml?source=compressed-mapping - size: 204539 - timestamp: 1758892248166 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py313h7d74516_0.conda - sha256: f5be0d84f72a567b7333b9efa74a65bfa44a25658cf107ffa3fc65d3ae6660d7 - md5: 0e8e3235217b4483a7461b63dca5826b + - pkg:pypi/pyyaml?source=hash-mapping + size: 207109 + timestamp: 1758892173548 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py312h5748b74_0.conda + sha256: 690943c979a5bf014348933a68cd39e3bb9114d94371c4c5d846d2daaa82c7d9 + md5: 6a2d7f8a026223c2fa1027c96c615752 depends: - __osx >=11.0 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 - yaml >=0.2.5,<0.3.0a0 license: MIT license_family: MIT purls: - - pkg:pypi/pyyaml?source=compressed-mapping - size: 191630 - timestamp: 1758892258120 + - pkg:pypi/pyyaml?source=hash-mapping + size: 190579 + timestamp: 1758891996097 - conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py312hfb55c3c_0.conda noarch: python sha256: a00a41b66c12d9c60e66b391e9a4832b7e28743348cf4b48b410b91927cd7819 @@ -7310,7 +8542,7 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/pyzmq?source=compressed-mapping + - pkg:pypi/pyzmq?source=hash-mapping size: 212218 timestamp: 1757387023399 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py312hd65ceae_0.conda @@ -7351,9 +8583,9 @@ packages: purls: [] size: 516376 timestamp: 1720814307311 -- conda: https://conda.anaconda.org/conda-forge/linux-64/qt6-main-6.9.3-h5c1c036_0.conda - sha256: 999ce4a6af5f9570373047f7c61ccc86d17bef294b402b7297b8efc25ec3b5e9 - md5: cc0bffcaf6410aba9c6581dfdc18012d +- conda: https://conda.anaconda.org/conda-forge/linux-64/qt6-main-6.9.3-h5c1c036_1.conda + sha256: 51537408ce1493d267b375b33ec02a060d77c4e00c7bef5e2e1c6724e08a23e3 + md5: 762af6d08fdfa7a45346b1466740bacd depends: - __glibc >=2.17,<3.0.a0 - alsa-lib >=1.2.14,<1.3.0a0 @@ -7361,11 +8593,11 @@ packages: - double-conversion >=3.3.1,<3.4.0a0 - fontconfig >=2.15.0,<3.0a0 - fonts-conda-ecosystem - - harfbuzz >=12.0.0 + - harfbuzz >=12.1.0 - icu >=75.1,<76.0a0 - krb5 >=1.21.3,<1.22.0a0 - - libclang-cpp21.1 >=21.1.2,<21.2.0a0 - - libclang13 >=21.1.2 + - libclang-cpp21.1 >=21.1.4,<21.2.0a0 + - libclang13 >=21.1.4 - libcups >=2.3.3,<2.4.0a0 - libdrm >=2.4.125,<2.5.0a0 - libegl >=1.7.0,<2.0a0 @@ -7375,20 +8607,20 @@ packages: - libgl >=1.7.0,<2.0a0 - libglib >=2.86.0,<3.0a0 - libjpeg-turbo >=3.1.0,<4.0a0 - - libllvm21 >=21.1.2,<21.2.0a0 + - libllvm21 >=21.1.4,<21.2.0a0 - libpng >=1.6.50,<1.7.0a0 - libpq >=18.0,<19.0a0 - libsqlite >=3.50.4,<4.0a0 - libstdcxx >=14 - libtiff >=4.7.1,<4.8.0a0 - - libvulkan-loader >=1.4.313.0,<2.0a0 + - libvulkan-loader >=1.4.328.1,<2.0a0 - libwebp-base >=1.6.0,<2.0a0 - libxcb >=1.17.0,<2.0a0 - - libxkbcommon >=1.11.0,<2.0a0 + - libxkbcommon >=1.12.2,<2.0a0 - libxml2 - libxml2-16 >=2.14.6 - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.3,<4.0a0 + - openssl >=3.5.4,<4.0a0 - pcre2 >=10.46,<10.47.0a0 - wayland >=1.24.0,<2.0a0 - xcb-util >=0.4.1,<0.5.0a0 @@ -7413,8 +8645,8 @@ packages: license: LGPL-3.0-only license_family: LGPL purls: [] - size: 54537863 - timestamp: 1759251856141 + size: 54785664 + timestamp: 1761308850008 - conda: https://conda.anaconda.org/conda-forge/linux-64/rav1e-0.7.1-h8fae777_3.conda sha256: 6e5e704c1c21f820d760e56082b276deaf2b53cf9b751772761c3088a365f6f4 md5: 2c42649888aac645608191ffdc80d13a @@ -7491,6 +8723,21 @@ packages: - pkg:pypi/referencing?source=hash-mapping size: 51668 timestamp: 1737836872415 +- conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda + sha256: 0577eedfb347ff94d0f2fa6c052c502989b028216996b45c7f21236f25864414 + md5: 870293df500ca7e18bedefa5838a22ab + depends: + - attrs >=22.2.0 + - python >=3.10 + - rpds-py >=0.7.0 + - typing_extensions >=4.4.0 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/referencing?source=hash-mapping + size: 51788 + timestamp: 1760379115194 - conda: https://conda.anaconda.org/conda-forge/linux-64/reproc-14.2.5.post0-hb9d3cd8_0.conda sha256: a1973f41a6b956f1305f9aaefdf14b2f35a8c9615cfe5f143f1784ed9aa6bf47 md5: 69fbc0a9e42eb5fe6733d2d60d818822 @@ -7620,21 +8867,38 @@ packages: - pkg:pypi/rfc3987-syntax?source=hash-mapping size: 22913 timestamp: 1752876729969 -- conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.1.0-pyhe01879c_0.conda - sha256: 3bda3cd6aa2ca8f266aeb8db1ec63683b4a7252d7832e8ec95788fb176d0e434 - md5: c41e49bd1f1479bed6c6300038c5466e +- pypi: https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl + name: rich + version: 14.2.0 + sha256: 76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd + requires_dist: + - ipywidgets>=7.5.1,<9 ; extra == 'jupyter' + - markdown-it-py>=2.2.0 + - pygments>=2.13.0,<3.0.0 + requires_python: '>=3.8.0' +- conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.2.0-pyhcf101f3_0.conda + sha256: edfb44d0b6468a8dfced728534c755101f06f1a9870a7ad329ec51389f16b086 + md5: a247579d8a59931091b16a1e932bbed6 depends: - markdown-it-py >=2.2.0 - pygments >=2.13.0,<3.0.0 - - python >=3.9 + - python >=3.10 - typing_extensions >=4.0.0,<5.0.0 - python license: MIT license_family: MIT purls: - - pkg:pypi/rich?source=compressed-mapping - size: 201098 - timestamp: 1753436991345 + - pkg:pypi/rich?source=hash-mapping + size: 200840 + timestamp: 1760026188268 +- pypi: https://files.pythonhosted.org/packages/13/2f/b4530fbf948867702d0a3f27de4a6aab1d156f406d72852ab902c4d04de9/rich_rst-1.3.2-py3-none-any.whl + name: rich-rst + version: 1.3.2 + sha256: a99b4907cbe118cf9d18b0b44de272efa61f15117c61e39ebdc431baf5df722a + requires_dist: + - docutils + - rich>=12.0.0 + - sphinx ; extra == 'docs' - conda: https://conda.anaconda.org/conda-forge/noarch/roman-numerals-py-3.1.0-pyhd8ed1ab_0.conda sha256: 0116a9ca9bf3487e18979b58b2f280116dba55cb53475af7a6d835f7aa133db8 md5: 5f0f24f8032c2c1bb33f59b75974f5fc @@ -7645,131 +8909,198 @@ packages: - pkg:pypi/roman-numerals-py?source=hash-mapping size: 13348 timestamp: 1740240332327 -- conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.27.1-py312h868fb18_1.conda - sha256: 76efba673e02d4d47bc2de6e48a8787ed98bae4933233dee5ce810fa3de6ef2b - md5: 0e32f9c8ca00c1b926a1b77be6937112 +- conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py313h843e2db_0.conda + sha256: 076d26e51c62c8ecfca6eb19e3c1febdd7632df1990a7aa53da5df5e54482b1c + md5: 779e3307a0299518713765b83a36f4b1 depends: - python - - __glibc >=2.17,<3.0.a0 - libgcc >=14 - - python_abi 3.12.* *_cp312 + - __glibc >=2.17,<3.0.a0 + - python_abi 3.13.* *_cp313 constrains: - __glibc >=2.17 license: MIT license_family: MIT purls: - - pkg:pypi/rpds-py?source=hash-mapping - size: 389483 - timestamp: 1756737801011 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.27.1-py313h80e0809_1.conda - sha256: 26a8e509a1fc87986c24534bc3eddfa25ed3bbcea32ed64663f380b5b28e8c94 - md5: 22c8096afd85182d01d95f5a411ef804 + - pkg:pypi/rpds-py?source=compressed-mapping + size: 383230 + timestamp: 1764543223529 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.30.0-py312h6ef9ec0_0.conda + sha256: ea06f6f66b1bea97244c36fd2788ccd92fd1fb06eae98e469dd95ee80831b057 + md5: a7cfbbdeb93bb9a3f249bc4c3569cd4c depends: - python - - python 3.13.* *_cp313 - __osx >=11.0 - - python_abi 3.13.* *_cp313 + - python 3.12.* *_cpython + - python_abi 3.12.* *_cp312 constrains: - __osx >=11.0 license: MIT license_family: MIT purls: - pkg:pypi/rpds-py?source=hash-mapping - size: 354648 - timestamp: 1756737507794 -- conda: https://conda.anaconda.org/conda-forge/linux-64/ruamel.yaml-0.18.15-py312h4c3975b_1.conda - sha256: 11f0de9337430669870085619e7538a56fc093b77e0c950d10b9c3ee69d6b8bc - md5: 4b4e6e4507091de3adba4c44c537cd35 + size: 358853 + timestamp: 1764543161524 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ruamel.yaml-0.18.16-py313h07c4f96_0.conda + sha256: fa1667f1c61610191960d0f6c33a55f3afacfd9c43ff4c9b1507ba164eb3f28f + md5: 88717b72e54ee6ef081c9ecfafd728c8 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 - ruamel.yaml.clib >=0.1.2 license: MIT license_family: MIT purls: - pkg:pypi/ruamel-yaml?source=hash-mapping - size: 269847 - timestamp: 1756839057460 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruamel.yaml-0.18.15-py313h6535dbc_1.conda - sha256: 35432334ada52614f11e1ff856c6ccf1aae9954361b2625bf6d8c30ebdc2a66f - md5: cc0c2ccafb07034da2b7936a50b033b1 + size: 272227 + timestamp: 1761160797563 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruamel.yaml-0.18.16-py312h4409184_0.conda + sha256: a7d944ed52656ff0ae85199bb1fe152298ab9613daca4d83653fc12a3c9f2ebf + md5: 1672b251b3ef1ea8d1c36450d5a5cd54 depends: - __osx >=11.0 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 - ruamel.yaml.clib >=0.1.2 license: MIT license_family: MIT purls: - pkg:pypi/ruamel-yaml?source=hash-mapping - size: 271650 - timestamp: 1756839583204 -- conda: https://conda.anaconda.org/conda-forge/linux-64/ruamel.yaml.clib-0.2.12-py312h4c3975b_1.conda - sha256: 6edd3b228450cd614229700b41dc1615775b58961267e7017883eb5937264886 - md5: 0b612e709fe8c0b0f45db418037a7424 + size: 270195 + timestamp: 1761161065046 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ruamel.yaml.clib-0.2.14-py313h07c4f96_0.conda + sha256: 1d5607b463001ed480aea45b7f3c1035151c0c44a3bf4f3539a00fe097c5e30a + md5: 3cff82488dd3935fdb3ba37911387fec depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 license: MIT license_family: MIT purls: - pkg:pypi/ruamel-yaml-clib?source=hash-mapping - size: 139438 - timestamp: 1756828829310 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruamel.yaml.clib-0.2.12-py313hcdf3177_1.conda - sha256: 9b31250214afd0760eb5a01ba093cb714d2560472ebe0bb3a15b6ce831eb887f - md5: fadc1eb7fac86cdd160eae1a5115e184 + size: 140261 + timestamp: 1760564339468 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruamel.yaml.clib-0.2.14-py312h4409184_0.conda + sha256: 6e3b59e64c26f62562834752476b7308e72b9aada1a85ee98d3b57c4f7ab9139 + md5: 701d9a8bcd57c5221df9b2384a6aef93 depends: - __osx >=11.0 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 license: MIT license_family: MIT purls: - pkg:pypi/ruamel-yaml-clib?source=hash-mapping - size: 116717 - timestamp: 1756829279540 -- conda: https://conda.anaconda.org/conda-forge/linux-64/ruff-0.13.3-ha3a3aed_0.conda + size: 116030 + timestamp: 1760564523506 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ruff-0.14.8-h813ae00_0.conda noarch: python - sha256: ffce5c3048361d2e28440c390649447736945da0bf461ebf245ea8c04b20bb75 - md5: 8df601783615b5f96dd1e49f0e514383 + sha256: 4adf379daccb73f03297a6966d1200f6ea65e6a1513d749e7f782e32267fe2bb + md5: 295ce05c06920527a581a5e148a4eec6 depends: - python - - libgcc >=14 - __glibc >=2.17,<3.0.a0 + - libgcc >=14 constrains: - __glibc >=2.17 license: MIT - license_family: MIT purls: - - pkg:pypi/ruff?source=compressed-mapping - size: 11092180 - timestamp: 1759452645178 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruff-0.13.3-h492a034_0.conda + - pkg:pypi/ruff?source=hash-mapping + size: 11340280 + timestamp: 1764866215629 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruff-0.14.8-h382de68_0.conda noarch: python - sha256: fd111ed1ca1105cb86666ad641b08ba6a15b90b4692cf20bbbd21e4bb01d369a - md5: a0d43feb7099c58bd130d6a8a76851d0 + sha256: 97135a37ab2c55eac06d75569f08ff388af63ec1a0a2a122528b4951b8536027 + md5: f8c69cb8d0c9ac4ab0593926f21a2a3b depends: - python - __osx >=11.0 constrains: - __osx >=11.0 license: MIT - license_family: MIT purls: - pkg:pypi/ruff?source=hash-mapping - size: 10223125 - timestamp: 1759452842941 -- pypi: https://files.pythonhosted.org/packages/6b/b5/b75527c0f9532dd8a93e8e7cd8e62e547b9f207d4c11e24f0006e8646b36/scikit_image-0.25.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + size: 10302078 + timestamp: 1764866315123 +- pypi: https://files.pythonhosted.org/packages/03/55/0ff6e41c39c64d9ad18bf31c953c28f525533609c7371fa2790558ca8197/scikit_image-0.20.0.tar.gz + name: scikit-image + version: 0.20.0 + sha256: 2cd784fce18bd31d71ade62c6221440199ead03acf7544086261ee032264cf61 + requires_dist: + - numpy>=1.21.1 + - scipy>=1.8,<1.9.2 ; python_full_version < '3.10' + - scipy>=1.8 ; python_full_version >= '3.10' + - networkx>=2.8 + - pillow>=9.0.1 + - imageio>=2.4.1 + - tifffile>=2019.7.26 + - pywavelets>=1.1.1 + - packaging>=20.0 + - lazy-loader>=0.1 + - meson-python>=0.13.0rc0 ; extra == 'build' + - wheel ; extra == 'build' + - setuptools>=67 ; extra == 'build' + - packaging>=20 ; extra == 'build' + - ninja ; extra == 'build' + - cython>=0.29.24 ; extra == 'build' + - pythran ; extra == 'build' + - numpy>=1.21.1 ; extra == 'build' + - build ; extra == 'build' + - pooch>=1.3.0 ; extra == 'data' + - numpy>=1.21.1 ; extra == 'default' + - scipy>=1.8,<1.9.2 ; python_full_version < '3.10' and extra == 'default' + - scipy>=1.8 ; python_full_version >= '3.10' and extra == 'default' + - networkx>=2.8 ; extra == 'default' + - pillow>=9.0.1 ; extra == 'default' + - imageio>=2.4.1 ; extra == 'default' + - tifffile>=2019.7.26 ; extra == 'default' + - pywavelets>=1.1.1 ; extra == 'default' + - packaging>=20.0 ; extra == 'default' + - lazy-loader>=0.1 ; extra == 'default' + - pre-commit ; extra == 'developer' + - rtoml ; extra == 'developer' + - sphinx>=5.2 ; extra == 'docs' + - sphinx-gallery>=0.11 ; extra == 'docs' + - numpydoc>=1.5 ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - pytest-runner ; extra == 'docs' + - scikit-learn ; extra == 'docs' + - matplotlib>=3.6 ; extra == 'docs' + - dask[array]>=2022.9.2 ; extra == 'docs' + - pandas>=1.5 ; extra == 'docs' + - seaborn>=0.11 ; extra == 'docs' + - pooch>=1.6 ; extra == 'docs' + - tifffile>=2022.8.12 ; extra == 'docs' + - myst-parser ; extra == 'docs' + - ipywidgets ; extra == 'docs' + - plotly>=5.10 ; extra == 'docs' + - kaleido ; extra == 'docs' + - simpleitk ; extra == 'optional' + - astropy>=3.1.2 ; extra == 'optional' + - cloudpickle>=0.2.1 ; extra == 'optional' + - dask[array]>=1.0.0,!=2.17.0 ; extra == 'optional' + - matplotlib>=3.3 ; extra == 'optional' + - pooch>=1.3.0 ; extra == 'optional' + - pyamg ; extra == 'optional' + - asv ; extra == 'test' + - codecov ; extra == 'test' + - matplotlib>=3.3 ; extra == 'test' + - pooch>=1.3.0 ; extra == 'test' + - pytest>=5.2.0 ; extra == 'test' + - pytest-cov>=2.7.0 ; extra == 'test' + - pytest-localserver ; extra == 'test' + - pytest-faulthandler ; extra == 'test' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/cd/9b/c3da56a145f52cd61a68b8465d6a29d9503bc45bc993bb45e84371c97d94/scikit_image-0.25.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl name: scikit-image version: 0.25.2 - sha256: a17e17eb8562660cc0d31bb55643a4da996a81944b82c54805c91b3fe66f4824 + sha256: b8abd3c805ce6944b941cfed0406d88faeb19bab3ed3d4b50187af55cf24d147 requires_dist: - numpy>=1.24 - scipy>=1.11.4 @@ -7829,10 +9160,10 @@ packages: - pytest-faulthandler ; extra == 'test' - pytest-doctestplus ; extra == 'test' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/84/06/66a2e7661d6f526740c309e9717d3bd07b473661d5cdddef4dd978edab25/scikit_image-0.25.2-cp313-cp313-macosx_12_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/ce/e6/93bebe1abcdce9513ffec01d8af02528b4c41fb3c1e46336d70b9ed4ef0d/scikit_image-0.25.2-cp312-cp312-macosx_12_0_arm64.whl name: scikit-image version: 0.25.2 - sha256: dd8011efe69c3641920614d550f5505f83658fe33581e49bed86feab43a180fc + sha256: 483bd8cc10c3d8a7a37fae36dfa5b21e239bd4ee121d91cad1f81bba10cfb0ed requires_dist: - numpy>=1.24 - scipy>=1.11.4 @@ -7892,9 +9223,9 @@ packages: - pytest-faulthandler ; extra == 'test' - pytest-doctestplus ; extra == 'test' requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.16.2-py312h7a1785b_0.conda - sha256: 2e1f0d26b0a716f8b7e92e87a83c697220034e5e74e86494f7d71cd4a6640026 - md5: 86a0cf3ba594247a8c44bd2111d7549c +- conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.16.3-py313h11c21cd_1.conda + sha256: 901d040d684202b73ea55b10a6994ba7fdc9b332764d50e3c29c3e1f542c9330 + md5: 26b089b9e5fcdcdca714b01f8008d808 depends: - __glibc >=2.17,<3.0.a0 - libblas >=3.9.0,<4.0a0 @@ -7907,17 +9238,17 @@ packages: - numpy <2.6 - numpy >=1.23,<3 - numpy >=1.25.2 - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/scipy?source=hash-mapping - size: 17114633 - timestamp: 1757682871398 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/scipy-1.16.2-py313h0d10b07_0.conda - sha256: eb9b5d46f1a259508a83dc6c3339e5e877d6b16ca113fc2bf111973b44ddbae8 - md5: 7e15b3f27103f3c637a1977dbcddb5bb + size: 16925821 + timestamp: 1763220671565 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/scipy-1.16.3-py312ha6bbf71_1.conda + sha256: 39586c1ebc804d481e1062551f7c39a2cfe6f3e3a2c18a9e460388fb8bbd5302 + md5: d196eb3cfffef4a8ea51fbb55dbe8188 depends: - __osx >=11.0 - libblas >=3.9.0,<4.0a0 @@ -7925,35 +9256,35 @@ packages: - libcxx >=19 - libgfortran - libgfortran5 >=14.3.0 - - libgfortran5 >=15.1.0 + - libgfortran5 >=15.2.0 - liblapack >=3.9.0,<4.0a0 - numpy <2.6 - numpy >=1.23,<3 - numpy >=1.25.2 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/scipy?source=hash-mapping - size: 13927445 - timestamp: 1757683194090 -- conda: https://conda.anaconda.org/conda-forge/linux-64/secretstorage-3.4.0-py312h7900ff3_0.conda - sha256: 5c2ba91cf23f7928fd8d906d17cb4aacc46afc2b2ad8b8c6a2013814cf2ee846 - md5: 17ac9aa340fe2e83b67886e11a1ea48d + size: 13777809 + timestamp: 1763221087258 +- conda: https://conda.anaconda.org/conda-forge/linux-64/secretstorage-3.4.1-py313h78bf25f_0.conda + sha256: 43ea89b53cbede879e57ac9dd20153c5cd2bb9575228e7faf5a8764aa6c201b7 + md5: 013a7d73eaef154f0dc5e415ffa8ff87 depends: - cryptography >=2.0 - dbus - jeepney >=0.6 - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/secretstorage?source=compressed-mapping - size: 32448 - timestamp: 1757606914831 + - pkg:pypi/secretstorage?source=hash-mapping + size: 32933 + timestamp: 1763045369115 - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.3-pyh0d859eb_1.conda sha256: 00926652bbb8924e265caefdb1db100f86a479e8f1066efe395d5552dde54d02 md5: 938c8de6b9de091997145b3bf25cdbf9 @@ -7990,9 +9321,9 @@ packages: - pkg:pypi/setuptools?source=hash-mapping size: 748788 timestamp: 1748804951958 -- conda: https://conda.anaconda.org/conda-forge/linux-64/simdjson-4.0.7-hb700be7_0.conda - sha256: 5e29efa1927929885e00909c0386b160d13100a73e031432c42e74df2151f775 - md5: cc9c262a71dd584aa5a3a22fc963255c +- conda: https://conda.anaconda.org/conda-forge/linux-64/simdjson-4.2.2-hb700be7_0.conda + sha256: b6283c2f7cfe4ecb14113e42f1dbb4103b7aba83bffbbf6c879b15cea0cb9855 + md5: aabe029c49221de004c3e4c16759a80a depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 @@ -8000,19 +9331,19 @@ packages: license: Apache-2.0 license_family: APACHE purls: [] - size: 267708 - timestamp: 1759262988515 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/simdjson-4.0.7-ha7d2532_0.conda - sha256: a0c961c56ad6606841576ae179172eed30f8b2ae435632e00f91689a6a675dea - md5: 66990c8e1331805f3a553e76b9d1a62a + size: 295278 + timestamp: 1762970942501 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/simdjson-4.2.2-ha7d2532_0.conda + sha256: 9f92000f166a752d297f411e87984cd66cf2912fb6f89c563806baf526702d4f + md5: 2657ae28aba98b52213b2e14f3b191ff depends: - __osx >=11.0 - libcxx >=19 license: Apache-2.0 license_family: APACHE purls: [] - size: 225118 - timestamp: 1759263294981 + size: 252682 + timestamp: 1762971407909 - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda sha256: 458227f759d5e3fcec5d9b7acce54e10c9e1f4f4b7ec978f3bfd54ce4ee9853d md5: 3339e3b65d58accf4ca4fb8748ab16b3 @@ -8025,41 +9356,41 @@ packages: - pkg:pypi/six?source=hash-mapping size: 18455 timestamp: 1753199211006 -- conda: https://conda.anaconda.org/conda-forge/linux-64/snappy-1.2.2-h03e3b7b_0.conda - sha256: 8b8acbde6814d1643da509e11afeb6bb30eb1e3004cf04a7c9ae43e9b097f063 - md5: 3d8da0248bdae970b4ade636a104b7f5 +- conda: https://conda.anaconda.org/conda-forge/linux-64/snappy-1.2.2-h03e3b7b_1.conda + sha256: 48f3f6a76c34b2cfe80de9ce7f2283ecb55d5ed47367ba91e8bb8104e12b8f11 + md5: 98b6c9dc80eb87b2519b97bcf7e578dd depends: - libgcc >=14 + - __glibc >=2.17,<3.0.a0 - libstdcxx >=14 - libgcc >=14 - - __glibc >=2.17,<3.0.a0 license: BSD-3-Clause license_family: BSD purls: [] - size: 45805 - timestamp: 1753083455352 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/snappy-1.2.2-hd121638_0.conda - sha256: b3d447d72d2af824006f4ba78ae4188747886d6d95f2f165fe67b95541f02b05 - md5: ba9ca3813f4db8c0d85d3c84404e02ba + size: 45829 + timestamp: 1762948049098 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/snappy-1.2.2-hada39a4_1.conda + sha256: cb9305ede19584115f43baecdf09a3866bfcd5bcca0d9e527bd76d9a1dbe2d8d + md5: fca4a2222994acd7f691e57f94b750c5 depends: - libcxx >=19 - __osx >=11.0 license: BSD-3-Clause license_family: BSD purls: [] - size: 38824 - timestamp: 1753083462800 -- conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_1.conda - sha256: c2248418c310bdd1719b186796ae50a8a77ce555228b6acd32768e2543a15012 - md5: bf7a226e58dfb8346c70df36065d86c9 + size: 38883 + timestamp: 1762948066818 +- conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda + sha256: dce518f45e24cd03f401cb0616917773159a210c19d601c5f2d4e0e5879d30ad + md5: 03fe290994c5e4ec17293cfb6bdce520 depends: - - python >=3.9 + - python >=3.10 license: Apache-2.0 license_family: Apache purls: - - pkg:pypi/sniffio?source=hash-mapping - size: 15019 - timestamp: 1733244175724 + - pkg:pypi/sniffio?source=compressed-mapping + size: 15698 + timestamp: 1762941572482 - conda: https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-3.0.1-pyhd8ed1ab_0.conda sha256: 17007a4cfbc564dc3e7310dcbe4932c6ecb21593d4fec3c68610720f19e73fb2 md5: 755cf22df8693aa0d1aec1c123fa5863 @@ -8079,7 +9410,7 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/soupsieve?source=compressed-mapping + - pkg:pypi/soupsieve?source=hash-mapping size: 37803 timestamp: 1756330614547 - conda: https://conda.anaconda.org/conda-forge/noarch/spefile-1.6.1-pyhd8ed1ab_1.conda @@ -8218,6 +9549,21 @@ packages: - pkg:pypi/sphinxcontrib-serializinghtml?source=hash-mapping size: 28669 timestamp: 1733750596111 +- pypi: https://files.pythonhosted.org/packages/23/a0/984525d19ca5c8a6c33911a0c164b11490dd0f90ff7fd689f704f84e9a11/sse_starlette-3.0.3-py3-none-any.whl + name: sse-starlette + version: 3.0.3 + sha256: af5bf5a6f3933df1d9c7f8539633dc8444ca6a97ab2e2a7cd3b6e431ac03a431 + requires_dist: + - anyio>=4.7.0 + - uvicorn>=0.34.0 ; extra == 'examples' + - fastapi>=0.115.12 ; extra == 'examples' + - sqlalchemy[asyncio]>=2.0.41 ; extra == 'examples' + - starlette>=0.49.1 ; extra == 'examples' + - aiosqlite>=0.21.0 ; extra == 'examples' + - uvicorn>=0.34.0 ; extra == 'uvicorn' + - granian>=2.3.1 ; extra == 'granian' + - daphne>=4.2.0 ; extra == 'daphne' + requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda sha256: 570da295d421661af487f1595045760526964f41471021056e993e73089e9c41 md5: b1b505328da7a6b246787df4b5a49fbc @@ -8232,6 +9578,19 @@ packages: - pkg:pypi/stack-data?source=hash-mapping size: 26988 timestamp: 1733569565672 +- pypi: https://files.pythonhosted.org/packages/d9/52/1064f510b141bd54025f9b55105e26d1fa970b9be67ad766380a3c9b74b0/starlette-0.50.0-py3-none-any.whl + name: starlette + version: 0.50.0 + sha256: 9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca + requires_dist: + - anyio>=3.6.2,<5 + - typing-extensions>=4.10.0 ; python_full_version < '3.13' + - httpx>=0.27.0,<0.29.0 ; extra == 'full' + - itsdangerous ; extra == 'full' + - jinja2 ; extra == 'full' + - python-multipart>=0.0.18 ; extra == 'full' + - pyyaml ; extra == 'full' + requires_python: '>=3.10' - conda: https://conda.anaconda.org/conda-forge/linux-64/svt-av1-3.1.2-hecca717_0.conda sha256: 34e2e9c505cd25dba0a9311eb332381b15147cf599d972322a7c197aedfc8ce2 md5: 9859766c658e78fec9afa4a54891d920 @@ -8283,9 +9642,22 @@ packages: - pkg:pypi/terminado?source=hash-mapping size: 22717 timestamp: 1710265922593 -- conda: https://conda.anaconda.org/conda-forge/noarch/tifffile-2025.10.4-pyhd8ed1ab_0.conda - sha256: c32e084bbb187ff00588f8cdfd5cd0d236e84ac8a137af61ec82a1cb1f995d6b - md5: d9102cc0b1c041ce488df1ed9362f7c2 +- conda: https://conda.anaconda.org/conda-forge/noarch/tifffile-2020.6.3-py_0.tar.bz2 + sha256: 333d6882dd0913196b6e486650416cf4e26dc3d6f28260e56be5ba656770ee83 + md5: 1fb771bb25b2eecbc73abf5143fa35bd + depends: + - imagecodecs-lite >=2019.4.20 + - numpy >=1.15.1 + - python >=3.6 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/tifffile?source=hash-mapping + size: 111182 + timestamp: 1591280338868 +- conda: https://conda.anaconda.org/conda-forge/noarch/tifffile-2025.10.16-pyhd8ed1ab_0.conda + sha256: 84d4c49b648971147f93a6c873ce24703fd4047bc57f91f20ff1060ca7feda8f + md5: f5b9f02d19761f79c564900a2a399984 depends: - imagecodecs >=2024.12.30 - numpy >=1.19.2 @@ -8295,89 +9667,92 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/tifffile?source=compressed-mapping - size: 182441 - timestamp: 1759734609079 -- conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - sha256: cad582d6f978276522f84bd209a5ddac824742fe2d452af6acf900f8650a73a2 - md5: f1acf5fdefa8300de697982bcb1761c9 + - pkg:pypi/tifffile?source=hash-mapping + size: 182347 + timestamp: 1760696502467 +- conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.5.1-pyhcf101f3_0.conda + sha256: 7c803480dbfb8b536b9bf6287fa2aa0a4f970f8c09075694174eb4550a4524cd + md5: c0d0b883e97906f7524e2aac94be0e0d depends: - - python >=3.5 + - python >=3.10 - webencodings >=0.4 + - python license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/tinycss2?source=hash-mapping - size: 28285 - timestamp: 1729802975370 + - pkg:pypi/tinycss2?source=compressed-mapping + size: 30571 + timestamp: 1764621508086 - pypi: https://files.pythonhosted.org/packages/78/17/853354204e1ca022d6b7d011ca7f3206c4f8faa3cc743e92609b49c1d83f/tinydb-4.8.2-py3-none-any.whl name: tinydb version: 4.8.2 sha256: f97030ee5cbc91eeadd1d7af07ab0e48ceb04aa63d4a983adbaca4cba16e86c3 requires_python: '>=3.8,<4.0' -- conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_hd72426e_102.conda - sha256: a84ff687119e6d8752346d1d408d5cf360dee0badd487a472aa8ddedfdc219e1 - md5: a0116df4f4ed05c303811a837d5b39d8 +- conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_ha0e22de_103.conda + sha256: 1544760538a40bcd8ace2b1d8ebe3eb5807ac268641f8acdc18c69c5ebfeaf64 + md5: 86bc20552bf46075e3d92b67f089172d depends: - __glibc >=2.17,<3.0.a0 - libgcc >=13 - libzlib >=1.3.1,<2.0a0 + constrains: + - xorg-libx11 >=1.8.12,<2.0a0 license: TCL license_family: BSD purls: [] - size: 3285204 - timestamp: 1748387766691 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h892fb3f_2.conda - sha256: cb86c522576fa95c6db4c878849af0bccfd3264daf0cc40dd18e7f4a7bfced0e - md5: 7362396c170252e7b7b0c8fb37fe9c78 + size: 3284905 + timestamp: 1763054914403 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h892fb3f_3.conda + sha256: ad0c67cb03c163a109820dc9ecf77faf6ec7150e942d1e8bb13e5d39dc058ab7 + md5: a73d54a5abba6543cb2f0af1bfbd6851 depends: - __osx >=11.0 - libzlib >=1.3.1,<2.0a0 license: TCL license_family: BSD purls: [] - size: 3125538 - timestamp: 1748388189063 -- conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhe01879c_2.conda - sha256: 040a5a05c487647c089ad5e05ad5aff5942830db2a4e656f1e300d73436436f1 - md5: 30a0a26c8abccf4b7991d590fe17c699 + size: 3125484 + timestamp: 1763055028377 +- conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.3.0-pyhcf101f3_0.conda + sha256: cb77c660b646c00a48ef942a9e1721ee46e90230c7c570cdeb5a893b5cce9bff + md5: d2732eb636c264dc9aa4cbee404b1a53 depends: - - python >=3.9 + - python >=3.10 - python license: MIT license_family: MIT purls: - pkg:pypi/tomli?source=compressed-mapping - size: 21238 - timestamp: 1753796677376 -- conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.2-py312h4c3975b_1.conda - sha256: 7cd30a558a00293a33ab9bfe0e174311546f0a1573c9f6908553ecd9a9bc417b - md5: 66b988f7f1dc9fcc9541483cb0ab985b + size: 20973 + timestamp: 1760014679845 +- conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.2-py313h07c4f96_2.conda + sha256: 8ef12814ebf787553b351c919d40a599e2331aefec639aef5ce6117cbcfc6a28 + md5: 7824f18e343d1f846dcde7b23c9bf31a depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 license: Apache-2.0 license_family: Apache purls: - pkg:pypi/tornado?source=hash-mapping - size: 850925 - timestamp: 1756855054247 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.2-py313hcdf3177_1.conda - sha256: 30fbb92cc119595e4ac7691789d45d367f5d6850103b97ca4a130d98e8ec27f0 - md5: 728311ebaa740a1efa6fab80bbcdf335 + size: 871569 + timestamp: 1762506888003 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.2-py312h4409184_2.conda + sha256: f36ab4a716ad18541fe04c30625fee45826ffeb5e112ed3a5fdb7e529bcc685d + md5: fb37abf9d8222ece113ffa6128bd5357 depends: - __osx >=11.0 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 license: Apache-2.0 license_family: Apache purls: - pkg:pypi/tornado?source=hash-mapping - size: 874955 - timestamp: 1756855212446 + size: 851615 + timestamp: 1762507232247 - conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.1-pyhd8ed1ab_1.conda sha256: 11e2c85468ae9902d24a27137b6b39b4a78099806e551d390e394a8c34b48e40 md5: 9efbfdc37242619130ea42b1cc4ed861 @@ -8452,16 +9827,6 @@ packages: - pkg:pypi/twine?source=hash-mapping size: 42488 timestamp: 1757013705407 -- conda: https://conda.anaconda.org/conda-forge/noarch/types-python-dateutil-2.9.0.20250822-pyhd8ed1ab_0.conda - sha256: dfdf6e3dea87c873a86cfa47f7cba6ffb500bad576d083b3de6ad1b17e1a59c3 - md5: 5e9220c892fe069da8de2b9c63663319 - depends: - - python >=3.10 - license: Apache-2.0 AND MIT - purls: - - pkg:pypi/types-python-dateutil?source=hash-mapping - size: 24939 - timestamp: 1755865615651 - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda sha256: 7c2df5721c742c2a47b2c8f960e718c930031663ac1174da67c1ed5999f7938c md5: edd329d7d3a4ab45dcf905899a7a6115 @@ -8472,9 +9837,9 @@ packages: purls: [] size: 91383 timestamp: 1756220668932 -- conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhd8ed1ab_0.conda - sha256: 8aaf69b828c2b94d0784f18f70f11aa032950d304e57e88467120b45c18c24fd - md5: 399701494e731ce73fdd86c185a3d1b4 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhd8ed1ab_1.conda + sha256: 70db27de58a97aeb7ba7448366c9853f91b21137492e0b4430251a1870aa8ff4 + md5: a0a4a3035667fc34f29bfbd5c190baa6 depends: - python >=3.10 - typing_extensions >=4.12.0 @@ -8482,8 +9847,8 @@ packages: license_family: MIT purls: - pkg:pypi/typing-inspection?source=compressed-mapping - size: 18799 - timestamp: 1759301271883 + size: 18923 + timestamp: 1764158430324 - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda sha256: 032271135bca55aeb156cee361c81350c6f3fb203f57d024d7e5a1fc9ef18731 md5: 0caa1af407ecff61170c9437a808404d @@ -8493,7 +9858,7 @@ packages: license: PSF-2.0 license_family: PSF purls: - - pkg:pypi/typing-extensions?source=compressed-mapping + - pkg:pypi/typing-extensions?source=hash-mapping size: 51692 timestamp: 1756220668932 - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda @@ -8514,52 +9879,52 @@ packages: purls: [] size: 122968 timestamp: 1742727099393 -- conda: https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py312h68727a3_5.conda - sha256: 9fb020083a7f4fee41f6ece0f4840f59739b3e249f157c8a407bb374ffb733b5 - md5: f9664ee31aed96c85b7319ab0a693341 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py313h7037e92_6.conda + sha256: bd1f3d159b204be5aeeb3dd165fad447d3a1c5df75fec64407a68f210a0cb722 + md5: 1fa8d662361896873a165b051322073e depends: - __glibc >=2.17,<3.0.a0 - cffi - - libgcc >=13 - - libstdcxx >=13 - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 + - libgcc >=14 + - libstdcxx >=14 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 license: MIT license_family: MIT purls: - pkg:pypi/ukkonen?source=hash-mapping - size: 13904 - timestamp: 1725784191021 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ukkonen-1.0.1-py313hf9c7212_5.conda - sha256: 482eac475928c031948790647ae10c2cb1d4a779c2e8f35f5fd1925561b13203 - md5: 8ddba23e26957f0afe5fc9236c73124a + size: 14648 + timestamp: 1761594865380 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ukkonen-1.0.1-py312ha0dd364_6.conda + sha256: ba54fd3c178d30816fff864e5f6c7d05d4ec5f72a42ad15ec576a81fe28bea48 + md5: 678a837ca1469257c13895124d4055b8 depends: - __osx >=11.0 - cffi - - libcxx >=17 - - python >=3.13.0rc1,<3.14.0a0 - - python >=3.13.0rc1,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 + - libcxx >=19 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 license: MIT license_family: MIT purls: - pkg:pypi/ukkonen?source=hash-mapping - size: 13689 - timestamp: 1725784235751 -- conda: https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-16.0.0-py312h4c3975b_1.conda - sha256: cbf7d13819cf526a094f0cfe2da7f7ba22c4fbae4d231c9004520fbbf93f7027 - md5: 4da303c1e91703d178817252615ca0a7 + size: 14510 + timestamp: 1761595134634 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/unicodedata2-17.0.0-py312h4409184_1.conda + sha256: 567cebbb3a1a5c76e5ec43508e01ccbe98923ad0003eafd87acbbc546fcd588c + md5: b0b0c7ea4888b6f4009afa7001e6adaa depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 + - __osx >=11.0 - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython - python_abi 3.12.* *_cp312 license: Apache-2.0 license_family: Apache purls: - pkg:pypi/unicodedata2?source=hash-mapping - size: 404974 - timestamp: 1756494558558 + size: 416271 + timestamp: 1763055285615 - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda sha256: e0eb6c8daf892b3056f08416a96d68b0a358b7c46b99c8a50481b22631a4dfc0 md5: e7cb0f5745e4c5035a460248334af7eb @@ -8586,6 +9951,22 @@ packages: - pkg:pypi/urllib3?source=hash-mapping size: 101735 timestamp: 1750271478254 +- pypi: https://files.pythonhosted.org/packages/ee/d9/d88e73ca598f4f6ff671fb5fde8a32925c2e08a637303a1d12883c7305fa/uvicorn-0.38.0-py3-none-any.whl + name: uvicorn + version: 0.38.0 + sha256: 48c0afd214ceb59340075b4a052ea1ee91c16fbc2a9b1469cca0e54566977b02 + requires_dist: + - click>=7.0 + - h11>=0.8 + - typing-extensions>=4.0 ; python_full_version < '3.11' + - colorama>=0.4 ; sys_platform == 'win32' and extra == 'standard' + - httptools>=0.6.3 ; extra == 'standard' + - python-dotenv>=0.13 ; extra == 'standard' + - pyyaml>=5.1 ; extra == 'standard' + - uvloop>=0.15.1 ; platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32' and extra == 'standard' + - watchfiles>=0.13 ; extra == 'standard' + - websockets>=10.4 ; extra == 'standard' + requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/noarch/versioningit-3.3.0-pyhd8ed1ab_0.conda sha256: 4b9a3f6738ab6e241b12b2fe9258f7e051678b911ca0f0ab042becc29096ff51 md5: 57b96d99ac0f5a548f7001618db6a561 @@ -8600,68 +9981,68 @@ packages: - pkg:pypi/versioningit?source=hash-mapping size: 167034 timestamp: 1751113901223 -- conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.34.0-pyhd8ed1ab_0.conda - sha256: 398f40090e80ec5084483bb798555d0c5be3d1bb30f8bb5e4702cd67cdb595ee - md5: 2bd6c0c96cfc4dbe9bde604a122e3e55 +- conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.35.4-pyhd8ed1ab_0.conda + sha256: 77193c99c6626c58446168d3700f9643d8c0dab1f6deb6b9dd039e6872781bfb + md5: cfccfd4e8d9de82ed75c8e2c91cab375 depends: - distlib >=0.3.7,<1 - filelock >=3.12.2,<4 - platformdirs >=3.9.1,<5 - - python >=3.9 + - python >=3.10 - typing_extensions >=4.13.2 license: MIT license_family: MIT purls: - - pkg:pypi/virtualenv?source=compressed-mapping - size: 4381624 - timestamp: 1755111905876 -- conda: https://conda.anaconda.org/conda-forge/linux-64/viztracer-1.0.4-py312h0f154a2_0.conda - sha256: 787c9dacdd9c78aee506ec2d27ee724325182bb4997ad7ff5481d987c90fac81 - md5: de9aa88869ae84f74638858f871dc682 + - pkg:pypi/virtualenv?source=hash-mapping + size: 4401341 + timestamp: 1761726489722 +- conda: https://conda.anaconda.org/conda-forge/linux-64/viztracer-1.1.1-py313h6459d8b_0.conda + sha256: 4fb59330faca98a34f9d0d6a08504a6f2be336621446a1091f0b93f118a7685f + md5: 268d662d0f099beb559d7dbc22eb875a depends: - __glibc >=2.17,<3.0.a0 - libgcc - libgcc-ng >=12 - objprint >=0.3.0 - orjson - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 license: Apache-2.0 license_family: APACHE purls: - pkg:pypi/viztracer?source=hash-mapping - size: 8346019 - timestamp: 1746849593537 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/viztracer-1.0.4-py313h90d716c_0.conda - sha256: 6cddf4282135a4a2c2150715cfe358d91f09381c5a00831d7ff2b44f0b7339f0 - md5: ebe00cb349f41601a7eb030287caceee + size: 13083419 + timestamp: 1762827663098 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/viztracer-1.1.1-py312h4409184_0.conda + sha256: 2474ed69437ce6a405a51b8c22179ffc3f9c33c1cb4e4f6f90087233fd11c9af + md5: 679f36d48360e211be9cd56978c98078 depends: - __osx >=11.0 - objprint >=0.3.0 - orjson - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 license: Apache-2.0 license_family: APACHE purls: - pkg:pypi/viztracer?source=hash-mapping - size: 8327552 - timestamp: 1746849717679 -- conda: https://conda.anaconda.org/conda-forge/linux-64/wayland-1.24.0-h3e06ad9_0.conda - sha256: ba673427dcd480cfa9bbc262fd04a9b1ad2ed59a159bd8f7e750d4c52282f34c - md5: 0f2ca7906bf166247d1d760c3422cb8a + size: 13044477 + timestamp: 1762827891499 +- conda: https://conda.anaconda.org/conda-forge/linux-64/wayland-1.24.0-hd6090a7_1.conda + sha256: 3aa04ae8e9521d9b56b562376d944c3e52b69f9d2a0667f77b8953464822e125 + md5: 035da2e4f5770f036ff704fa17aace24 depends: - __glibc >=2.17,<3.0.a0 - - libexpat >=2.7.0,<3.0a0 - - libffi >=3.4.6,<3.5.0a0 - - libgcc >=13 - - libstdcxx >=13 + - libexpat >=2.7.1,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - libgcc >=14 + - libstdcxx >=14 license: MIT license_family: MIT purls: [] - size: 330474 - timestamp: 1751817998141 + size: 329779 + timestamp: 1761174273487 - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.14-pyhd8ed1ab_0.conda sha256: e311b64e46c6739e2a35ab8582c20fa30eb608da130625ed379f4467219d4813 md5: 7e1e5ff31239f9cd5855714df8a3783d @@ -8670,20 +10051,20 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/wcwidth?source=compressed-mapping + - pkg:pypi/wcwidth?source=hash-mapping size: 33670 timestamp: 1758622418893 -- conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-24.11.1-pyhd8ed1ab_0.conda - sha256: 08315dc2e61766a39219b2d82685fc25a56b2817acf84d5b390176080eaacf99 - md5: b49f7b291e15494aafb0a7d74806f337 +- conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda + sha256: 21f6c8a20fe050d09bfda3fb0a9c3493936ce7d6e1b3b5f8b01319ee46d6c6f6 + md5: 6639b6b0d8b5a284f027a2003669aa65 depends: - - python >=3.9 + - python >=3.10 license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/webcolors?source=hash-mapping - size: 18431 - timestamp: 1733359823938 + size: 18987 + timestamp: 1761899393153 - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda sha256: 19ff205e138bb056a46f9e3839935a2e60bd1cf01c8241a5e172a422fed4f9c6 md5: 2841eb5bfc75ce15e9a0054b98dcd64d @@ -8695,17 +10076,27 @@ packages: - pkg:pypi/webencodings?source=hash-mapping size: 15496 timestamp: 1733236131358 -- conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.8.0-pyhd8ed1ab_1.conda - sha256: 1dd84764424ffc82030c19ad70607e6f9e3b9cb8e633970766d697185652053e - md5: 84f8f77f0a9c6ef401ee96611745da8f +- conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda + sha256: 42a2b61e393e61cdf75ced1f5f324a64af25f347d16c60b14117393a98656397 + md5: 2f1ed718fcd829c184a6d4f0f2e07409 depends: - - python >=3.9 + - python >=3.10 license: Apache-2.0 license_family: APACHE purls: - pkg:pypi/websocket-client?source=hash-mapping - size: 46718 - timestamp: 1733157432924 + size: 61391 + timestamp: 1759928175142 +- pypi: https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl + name: websockets + version: 15.0.1 + sha256: 0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl + name: websockets + version: 15.0.1 + sha256: 0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8 + requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda sha256: 1b34021e815ff89a4d902d879c3bd2040bc1bd6169b32e9427497fa05c55f1ce md5: 75cb7132eb58d97896e173ef12ac9986 @@ -8717,17 +10108,17 @@ packages: - pkg:pypi/wheel?source=hash-mapping size: 62931 timestamp: 1733130309598 -- conda: https://conda.anaconda.org/conda-forge/noarch/widgetsnbextension-4.0.14-pyhd8ed1ab_0.conda - sha256: 7df3620c88343f2d960a58a81b79d4e4aa86ab870249e7165db7c3e2971a2664 - md5: 2f1f99b13b9d2a03570705030a0b3e7c +- conda: https://conda.anaconda.org/conda-forge/noarch/widgetsnbextension-4.0.15-pyhd8ed1ab_0.conda + sha256: 826af5e2c09e5e45361fa19168f46ff524e7a766022615678c3a670c45895d9a + md5: dc257b7e7cad9b79c1dfba194e92297b depends: - - python >=3.9 + - python >=3.10 license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/widgetsnbextension?source=hash-mapping - size: 889285 - timestamp: 1744291155057 + size: 889195 + timestamp: 1762040404362 - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.1-h4f16b4b_2.conda sha256: ad8cab7e07e2af268449c2ce855cbb51f43f4664936eff679b1f3862e6e4b01d md5: fdc27cb255a7a2cc73b7919a968b48f0 @@ -8740,21 +10131,21 @@ packages: purls: [] size: 20772 timestamp: 1750436796633 -- conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-cursor-0.1.5-hb9d3cd8_0.conda - sha256: c7b35db96f6e32a9e5346f97adc968ef2f33948e3d7084295baebc0e33abdd5b - md5: eb44b3b6deb1cab08d72cb61686fe64c +- conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-cursor-0.1.6-hb03c661_0.conda + sha256: c2be9cae786fdb2df7c2387d2db31b285cf90ab3bfabda8fa75a596c3d20fc67 + md5: 4d1fc190b99912ed557a8236e958c559 depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 + - libgcc >=14 - libxcb >=1.13 - - libxcb >=1.16,<2.0.0a0 + - libxcb >=1.17.0,<2.0a0 - xcb-util-image >=0.4.0,<0.5.0a0 - xcb-util-renderutil >=0.3.10,<0.4.0a0 license: MIT license_family: MIT purls: [] - size: 20296 - timestamp: 1726125844850 + size: 20829 + timestamp: 1763366954390 - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-hb711507_2.conda sha256: 94b12ff8b30260d9de4fd7a28cca12e028e572cbc504fd42aa2646ec4a5bded7 md5: a0901183f08b6c7107aab109733a3c91 @@ -8848,27 +10239,27 @@ packages: purls: [] size: 835896 timestamp: 1741901112627 -- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb9d3cd8_0.conda - sha256: ed10c9283974d311855ae08a16dfd7e56241fac632aec3b92e3cfe73cff31038 - md5: f6ebe2cb3f82ba6c057dde5d9debe4f7 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb03c661_1.conda + sha256: 6bc6ab7a90a5d8ac94c7e300cc10beb0500eeba4b99822768ca2f2ef356f731b + md5: b2895afaf55bf96a8c8282a2e47a5de0 depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 + - libgcc >=14 license: MIT license_family: MIT purls: [] - size: 14780 - timestamp: 1734229004433 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxau-1.0.12-h5505292_0.conda - sha256: f33e6f013fc36ebc200f09ddead83468544cb5c353a3b50499b07b8c34e28a8d - md5: 50901e0764b7701d8ed7343496f4f301 + size: 15321 + timestamp: 1762976464266 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxau-1.0.12-hc919400_1.conda + sha256: adae11db0f66f86156569415ed79cda75b2dbf4bea48d1577831db701438164f + md5: 78b548eed8227a689f93775d5d23ae09 depends: - __osx >=11.0 license: MIT license_family: MIT purls: [] - size: 13593 - timestamp: 1734229104321 + size: 14105 + timestamp: 1762976976084 - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxcomposite-0.4.6-hb9d3cd8_2.conda sha256: 753f73e990c33366a91fd42cc17a3d19bb9444b9ca5ff983605fa9e953baf57f md5: d3c295b50f092ab525ffe3c2aa4b7413 @@ -8910,27 +10301,27 @@ packages: purls: [] size: 13217 timestamp: 1727891438799 -- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb9d3cd8_0.conda - sha256: 6b250f3e59db07c2514057944a3ea2044d6a8cdde8a47b6497c254520fade1ee - md5: 8035c64cb77ed555e3f150b7b3972480 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb03c661_1.conda + sha256: 25d255fb2eef929d21ff660a0c687d38a6d2ccfbcbf0cc6aa738b12af6e9d142 + md5: 1dafce8548e38671bea82e3f5c6ce22f depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 + - libgcc >=14 license: MIT license_family: MIT purls: [] - size: 19901 - timestamp: 1727794976192 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxdmcp-1.1.5-hd74edd7_0.conda - sha256: 9939a166d780700d81023546759102b33fdc2c5f11ef09f5f66c77210fd334c8 - md5: 77c447f48cab5d3a15ac224edb86a968 + size: 20591 + timestamp: 1762976546182 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxdmcp-1.1.5-hc919400_1.conda + sha256: f7fa0de519d8da589995a1fe78ef74556bb8bc4172079ae3a8d20c3c81354906 + md5: 9d1299ace1924aa8f4e0bc8e71dd0cf7 depends: - __osx >=11.0 license: MIT license_family: MIT purls: [] - size: 18487 - timestamp: 1727795205022 + size: 19156 + timestamp: 1762977035194 - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.6-hb9d3cd8_0.conda sha256: da5dc921c017c05f38a38bd75245017463104457b63a1ce633ed41f214159c14 md5: febbab7d15033c913d53c7a2c102309d @@ -9067,19 +10458,19 @@ packages: purls: [] size: 136222 timestamp: 1745308075886 -- pypi: https://files.pythonhosted.org/packages/18/91/3274b215fd8442a03975ce6bee5fe6aa57a8326b29b9d3d56234a1dca244/yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/11/c9/cd8538dc2e7727095e0c1d867bad1e40c98f37763e6d995c1939f5fdc7b1/yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: yarl version: 1.22.0 - sha256: 22965c2af250d20c873cdbee8ff958fb809940aeb2e74ba5f20aaf6b7ac8c70c + sha256: bec03d0d388060058f5d291a813f21c011041938a441c593374da6077fe21b1b requires_dist: - idna>=2.0 - multidict>=4.0 - propcache>=0.2.1 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/db/0f/0d52c98b8a885aeda831224b78f3be7ec2e1aa4a62091f9f9188c3c65b56/yarl-1.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/ba/f5/0601483296f09c3c65e303d60c070a5c19fcdbc72daa061e96170785bc7d/yarl-1.22.0-cp312-cp312-macosx_11_0_arm64.whl name: yarl version: 1.22.0 - sha256: 50678a3b71c751d58d7908edc96d332af328839eea883bb554a43f539101277a + sha256: 939fe60db294c786f6b7c2d2e121576628468f65453d86b0fe36cb52f987bd74 requires_dist: - idna>=2.0 - multidict>=4.0 @@ -9138,17 +10529,18 @@ packages: purls: [] size: 203160 timestamp: 1756514364308 -- conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhd8ed1ab_0.conda - sha256: 7560d21e1b021fd40b65bfb72f67945a3fcb83d78ad7ccf37b8b3165ec3b68ad - md5: df5e78d904988eb55042c0c97446079f +- conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda + sha256: b4533f7d9efc976511a73ef7d4a2473406d7f4c750884be8e8620b0ce70f4dae + md5: 30cd29cb87d819caead4d55184c1d115 depends: - - python >=3.9 + - python >=3.10 + - python license: MIT license_family: MIT purls: - - pkg:pypi/zipp?source=hash-mapping - size: 22963 - timestamp: 1749421737203 + - pkg:pypi/zipp?source=compressed-mapping + size: 24194 + timestamp: 1764460141901 - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.1-hb9d3cd8_2.conda sha256: 5d7c0e5f0005f74112a34a7425179f4eb6e73c92f5d109e6af4ddeca407c92ab md5: c9f075ab2f33b3bbee9e62d4ad0a6cd8 @@ -9172,84 +10564,89 @@ packages: purls: [] size: 77606 timestamp: 1727963209370 -- conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-ng-2.2.5-hde8ca8f_0.conda - sha256: 3a8e7798deafd0722b6b5da50c36b7f361a80b30165d600f7760d569a162ff95 - md5: 1920c3502e7f6688d650ab81cd3775fd +- conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-ng-2.3.2-h54a6638_0.conda + sha256: 0afb07f3511031c35202036e2cd819c90edaa0c6a39a7a865146d3cb066bec96 + md5: 0faadd01896315ceea58bcc3479b1d21 depends: - - __glibc >=2.17,<3.0.a0 - libgcc >=14 + - __glibc >=2.17,<3.0.a0 - libstdcxx >=14 license: Zlib - license_family: Other purls: [] - size: 110843 - timestamp: 1754587144298 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-ng-2.2.5-hf787086_0.conda - sha256: 4b4220f03844aaaffa7868023963415e14cddd456f00b8fa78ee4a16fecd1b60 - md5: fb78d469f8e0f1eb5bbbfd249aeed447 + size: 135032 + timestamp: 1764715875371 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-ng-2.2.5-h3470cca_1.conda + sha256: 90aaf9d2aab71610607e48b5d8110147d71b5316ebff28b18de8b460b9c92e83 + md5: c2a50447e98f4b6b1357f54bbd9379dd depends: - __osx >=11.0 - libcxx >=19 license: Zlib license_family: Other purls: [] - size: 87638 - timestamp: 1754587529491 -- conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.25.0-py312h5253ce2_0.conda - sha256: 1a3beda8068b55639edb92da8e0dc2d487e2a11aba627f709aab1d3cd5dd271c - md5: 05d73100768745631ab3de9dc1e08da2 + size: 87305 + timestamp: 1764163041065 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-ng-2.3.2-h248ca61_0.conda + sha256: 2fe2befe061a51c24fce7f5f071c47b45b43f8c8781c0c557edf7c733ab13b18 + md5: c2a30a3b30cf86ef97ec880d53a6571a + depends: + - libcxx >=19 + - __osx >=11.0 + license: Zlib + purls: [] + size: 105035 + timestamp: 1764716000870 +- conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.25.0-py313h54dd161_1.conda + sha256: e6921de3669e1bbd5d050a3b771b46a887e7f4ffeb1ddd5e4d9fb01062a2f6e9 + md5: 710d4663806d0f72b2fb414e936223b5 depends: - python - cffi >=1.11 - zstd >=1.5.7,<1.5.8.0a0 - - __glibc >=2.17,<3.0.a0 - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - python_abi 3.13.* *_cp313 - zstd >=1.5.7,<1.6.0a0 - - python_abi 3.12.* *_cp312 license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/zstandard?source=hash-mapping - size: 466871 - timestamp: 1757930116600 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstandard-0.25.0-py313h9734d34_0.conda - sha256: 8a1bf8a66c05f724e8a56cb1918eae70bcb467a7c5d43818e37e04d86332c513 - md5: ce17795bf104a29a2c7ed0bba7a804cb + size: 471496 + timestamp: 1762512679097 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstandard-0.25.0-py312h37e1c23_1.conda + sha256: af843b0fe62d128a70f91dc954b2cb692f349a237b461788bd25dd928d0d1ef8 + md5: 9300889791d4decceea3728ad3b423ec depends: - python - cffi >=1.11 - zstd >=1.5.7,<1.5.8.0a0 + - python 3.12.* *_cpython - __osx >=11.0 - - python 3.13.* *_cp313 + - python_abi 3.12.* *_cp312 - zstd >=1.5.7,<1.6.0a0 - - python_abi 3.13.* *_cp313 license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/zstandard?source=hash-mapping - size: 396477 - timestamp: 1757930170468 -- conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda - sha256: a4166e3d8ff4e35932510aaff7aa90772f84b4d07e9f6f83c614cba7ceefe0eb - md5: 6432cb5d4ac0046c3ac0a8a0f95842f9 + size: 390920 + timestamp: 1762512713481 +- conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda + sha256: 68f0206ca6e98fea941e5717cec780ed2873ffabc0e1ed34428c061e2c6268c7 + md5: 4a13eeac0b5c8e5b8ab496e6c4ddd829 depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - libstdcxx >=13 - libzlib >=1.3.1,<2.0a0 license: BSD-3-Clause - license_family: BSD purls: [] - size: 567578 - timestamp: 1742433379869 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-h6491c7d_2.conda - sha256: 0d02046f57f7a1a3feae3e9d1aa2113788311f3cf37a3244c71e61a93177ba67 - md5: e6f69c7bcccdefa417f056fa593b40f0 + size: 601375 + timestamp: 1764777111296 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda + sha256: 9485ba49e8f47d2b597dd399e88f4802e100851b27c21d7525625b0b4025a5d9 + md5: ab136e4c34e97f34fb621d2592a393d8 depends: - __osx >=11.0 - libzlib >=1.3.1,<2.0a0 license: BSD-3-Clause - license_family: BSD purls: [] - size: 399979 - timestamp: 1742433432699 + size: 433413 + timestamp: 1764777166076 diff --git a/pyproject.toml b/pyproject.toml index 9f44a04e..4ffa3b02 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,8 @@ dependencies = [ [project.optional-dependencies] nova = ["nova-galaxy>=0.7.4,<0.8"] +# fastmcp 2.12+ fixed pydantic 2.12 compatibility issues +mcp = ["fastmcp>=2.12.0,<3"] [project.urls] homepage = "https://github.com/lanl/PLEIADES" @@ -41,6 +43,7 @@ issues = "https://github.com/lanl/PLEIADES/issues" [project.scripts] pleiades = "pleiades:main" # Console script entry point +pleiades-mcp = "pleiades.mcp.server:main" # MCP server entry point post_install_check = "pleiades.post_install:check_sammy_installed" [build-system] @@ -197,6 +200,17 @@ jupyterlab = "*" ipympl = "*" ipywidgets = "*" +[tool.pixi.feature.mcp.dependencies] +# Override referencing to allow fastmcp's jsonschema-path dependency +referencing = ">=0.35.0,<0.37" + +[tool.pixi.feature.mcp.pypi-dependencies] +pleiades-neutron = { path = ".", editable = true, extras = ["mcp"] } + +[tool.pixi.feature.mcp.tasks] +mcp-server = { cmd = "python -m pleiades.mcp", description = "Start the PLEIADES MCP server" } + [tool.pixi.environments] default = {features = ["test", "package", "docs", "developer"]} jupyter = {features = ["developer", "jupyter"]} +mcp = {features = ["developer", "mcp", "test"]} diff --git a/src/pleiades/mcp/__init__.py b/src/pleiades/mcp/__init__.py new file mode 100644 index 00000000..be1b8b7b --- /dev/null +++ b/src/pleiades/mcp/__init__.py @@ -0,0 +1,60 @@ +"""PLEIADES MCP Server - AI-assisted neutron resonance analysis. + +This module provides Model Context Protocol (MCP) server capabilities for PLEIADES, +enabling AI-assisted neutron resonance imaging analysis. + +Installation: + pip install pleiades-neutron[mcp] # PyPI install + pip install -e ".[mcp]" # Editable install + +Usage: + pleiades-mcp # Start the MCP server (console script) + python -m pleiades.mcp # Module invocation + +Example: + >>> from pleiades.mcp import MCP_AVAILABLE + >>> if MCP_AVAILABLE: + ... from pleiades.mcp.server import get_server + ... server = get_server() +""" + +from __future__ import annotations + +# Attempt to import FastMCP, track availability +# Following the nova backend pattern: no warning on import, clear error on use +try: + from fastmcp import FastMCP + + MCP_AVAILABLE: bool = True +except ImportError: + MCP_AVAILABLE = False + FastMCP = None # type: ignore[assignment, misc] + + +def check_mcp_available() -> None: + """Check if MCP dependencies are installed. + + Call this before using MCP functionality to get a clear error message. + + Raises: + ImportError: If MCP dependencies are not installed. + + Example: + >>> from pleiades.mcp import check_mcp_available + >>> check_mcp_available() # Raises ImportError if MCP not installed + """ + if not MCP_AVAILABLE: + raise ImportError( + "MCP dependencies not installed.\n" + "Install with:\n" + " pip install pleiades-neutron[mcp] # PyPI install\n" + " pip install -e '.[mcp]' # Editable install\n" + " pixi install -e mcp # Pixi environment" + ) + + +# Export FastMCP only when available +if MCP_AVAILABLE: + __all__ = ["MCP_AVAILABLE", "FastMCP", "check_mcp_available"] +else: + __all__ = ["MCP_AVAILABLE", "check_mcp_available"] diff --git a/src/pleiades/mcp/__main__.py b/src/pleiades/mcp/__main__.py new file mode 100644 index 00000000..d26ef66c --- /dev/null +++ b/src/pleiades/mcp/__main__.py @@ -0,0 +1,32 @@ +"""Entry point for running PLEIADES MCP server as a module. + +Usage: + python -m pleiades.mcp + +This is equivalent to running the `pleiades-mcp` console script. +""" + +from __future__ import annotations + +import sys + + +def run() -> None: + """Run the MCP server with proper error handling.""" + try: + from pleiades.mcp.server import main + + main() + except ImportError as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) + except KeyboardInterrupt: + print("\nServer stopped.", file=sys.stderr) + sys.exit(0) + except Exception as e: + print(f"Unexpected error: {e}", file=sys.stderr) + sys.exit(1) + + +if __name__ == "__main__": + run() diff --git a/src/pleiades/mcp/decorators.py b/src/pleiades/mcp/decorators.py new file mode 100644 index 00000000..8abd9b2f --- /dev/null +++ b/src/pleiades/mcp/decorators.py @@ -0,0 +1,250 @@ +"""MCP tool decorator for PLEIADES. + +This module provides a decorator to register functions as MCP tools. +It creates a thin abstraction layer that decouples PLEIADES from +specific MCP implementations (like FastMCP). + +Architecture: + - The decorator registers functions in a global registry at import time + - The registry is read-only from external code (get_registered_tools returns a deep copy) + - The MCP server (pleiades.mcp.server) reads the registry and exposes tools + - This separation allows switching MCP backends without changing decorated functions + +Thread Safety: + - Dictionary operations are atomic (protected by GIL in CPython) + - get_registered_tools() returns a deep copy, so mutations don't affect registry + - Note: Duplicate name detection between concurrent registrations is not atomic + +Performance: + - get_registered_tools() performs a deep copy of the entire registry + - Performance scales linearly with the number of registered tools + - For ~1000 tools: ~2-3ms per call; for ~10000 tools: ~20-30ms per call + - Consider caching the result if called frequently in hot paths + +Decorator Ordering: + - When combining with @classmethod or @staticmethod, @mcp_tool must come LAST + - Correct: @classmethod @mcp_tool() - the function is registered + - Wrong: @mcp_tool() @classmethod - raises TypeError (descriptor not callable) + +Limitations: + - Instance methods can be registered but cannot be called from the registry + without an instance (missing self argument) + - Lambda functions are registered with name '' which may cause conflicts + +Example: + from pleiades.mcp.decorators import mcp_tool, get_registered_tools + + @mcp_tool(description="Validate a dataset") + def validate_dataset(path: str) -> dict: + return {"valid": True} + + tools = get_registered_tools() + assert "validate_dataset" in tools +""" + +from __future__ import annotations + +import copy +import re +from typing import Any, Callable, TypeVar, overload + +# Valid tool name pattern: alphanumeric, underscore, hyphen only +_VALID_NAME_PATTERN = re.compile(r"^[a-zA-Z0-9_-]+$") + +__all__ = ["mcp_tool", "get_registered_tools", "clear_registry"] + +# Global registry for MCP tools +_mcp_registry: dict[str, dict[str, Any]] = {} + +# Type variable for decorated function +F = TypeVar("F", bound=Callable[..., Any]) + + +@overload +def mcp_tool(func: F) -> F: + """Decorator without parentheses - registers function with defaults.""" + ... + + +@overload +def mcp_tool( + func: None = None, + *, + name: str | None = None, + description: str | None = None, + parameter_descriptions: dict[str, str] | None = None, +) -> Callable[[F], F]: + """Decorator with parentheses.""" + ... + + +def mcp_tool( + func: F | None = None, + *, + name: str | None = None, + description: str | None = None, + parameter_descriptions: dict[str, str] | None = None, +) -> F | Callable[[F], F]: + """Decorator to register a function as an MCP tool. + + This decorator registers the function in a global registry that can + be discovered by the MCP server. The function itself is not modified + and can still be called directly. + + Args: + func: The function to decorate (when used without parentheses). + name: Custom name for the tool. Defaults to function.__name__. + description: Custom description. Defaults to function docstring. + parameter_descriptions: Dict mapping parameter names to descriptions. + + Returns: + The decorated function, unchanged. + + Raises: + ValueError: If a tool with the same name is already registered. + TypeError: If arguments have incorrect types or decorator is misused. + + Example: + @mcp_tool() + def add(a: int, b: int) -> int: + '''Add two numbers.''' + return a + b + + @mcp_tool(name="custom_name", description="Custom description") + def another_tool(): + pass + """ + # Handle @mcp_tool without parentheses + if func is not None: + # Called as @mcp_tool (without parens) - func is the decorated function + if callable(func): + return _register_tool(func, None, None, None) + else: + raise TypeError(f"mcp_tool() argument must be callable or None, not {type(func).__name__}") + + # Validate inputs + if name is not None: + if not isinstance(name, str): + raise TypeError(f"name must be str or None, not {type(name).__name__}") + if not name.strip(): + raise ValueError("name cannot be empty or whitespace") + if not _VALID_NAME_PATTERN.match(name): + raise ValueError( + f"name contains invalid characters: '{name}'. " + "Tool names must be alphanumeric with underscores or hyphens only." + ) + + if description is not None and not isinstance(description, str): + raise TypeError(f"description must be str or None, not {type(description).__name__}") + + if parameter_descriptions is not None: + if not isinstance(parameter_descriptions, dict): + raise TypeError(f"parameter_descriptions must be dict or None, not {type(parameter_descriptions).__name__}") + # Validate dict contents + for key, value in parameter_descriptions.items(): + if not isinstance(key, str): + raise TypeError(f"parameter_descriptions keys must be str, not {type(key).__name__}") + if not key.strip(): + raise ValueError("parameter_descriptions keys cannot be empty or whitespace") + if not isinstance(value, str): + raise TypeError(f"parameter_descriptions values must be str, not {type(value).__name__}") + + # Return decorator that will register the function + def decorator(fn: F) -> F: + return _register_tool(fn, name, description, parameter_descriptions) + + return decorator + + +def _register_tool( + func: F, + name: str | None, + description: str | None, + parameter_descriptions: dict[str, str] | None, +) -> F: + """Internal function to register a tool in the registry. + + Args: + func: The function to register. + name: Custom tool name or None to use func.__name__. + description: Custom description or None to use docstring. + parameter_descriptions: Parameter descriptions dict or None. + + Returns: + The original function, unchanged. + + Raises: + ValueError: If tool name is already registered. + TypeError: If func is not callable. + """ + if not callable(func): + raise TypeError(f"Decorated object must be callable, not {type(func).__name__}") + + # Determine tool name + tool_name = name if name is not None else func.__name__ + + # Check for duplicate registration + if tool_name in _mcp_registry: + raise ValueError(f"Tool '{tool_name}' is already registered. Use a different name or remove the duplicate.") + + # Extract description from docstring if not provided + tool_description: str + if description is not None: + tool_description = description + elif func.__doc__: + tool_description = func.__doc__ + else: + tool_description = "" + + # Register the tool (store original function directly, no wrapper) + _mcp_registry[tool_name] = { + "name": tool_name, + "description": tool_description, + "func": func, + "parameter_descriptions": parameter_descriptions, + } + + # Return original function unchanged (no wrapper overhead) + return func + + +def get_registered_tools() -> dict[str, dict[str, Any]]: + """Get all registered MCP tools. + + Returns a deep copy of the registry to prevent external mutation + of internal state. + + Note: + This function performs a deep copy of the entire registry, which + has O(n) time complexity. For ~1000 tools this takes ~2-3ms. + Consider caching the result if called frequently. + + Returns: + Dict mapping tool names to their metadata. Each entry contains: + - name: The tool name + - description: The tool description + - func: Reference to the callable function + - parameter_descriptions: Optional dict of parameter descriptions + + Example: + tools = get_registered_tools() + for name, info in tools.items(): + print(f"{name}: {info['description']}") + """ + return copy.deepcopy(_mcp_registry) + + +def clear_registry() -> None: + """Clear all registered tools from the registry. + + WARNING: This removes ALL registered tools, including those from + other modules. Use with caution in production code. + + This is primarily useful for testing to ensure a clean state + between test cases. + + Example: + clear_registry() + assert len(get_registered_tools()) == 0 + """ + _mcp_registry.clear() diff --git a/src/pleiades/mcp/server.py b/src/pleiades/mcp/server.py new file mode 100644 index 00000000..2bc77eed --- /dev/null +++ b/src/pleiades/mcp/server.py @@ -0,0 +1,303 @@ +"""PLEIADES MCP Server implementation. + +This module provides the FastMCP server that exposes PLEIADES workflow functions +as MCP tools for AI-assisted neutron resonance analysis. + +The server auto-discovers tools registered with @mcp_tool decorator and exposes +them via the MCP protocol. + +Usage: + pleiades-mcp # Console script + python -m pleiades.mcp # Module invocation + +Functions: + discover_tools(): Read registered tools from decorator registry + register_tools(server): Register discovered tools with FastMCP server + generate_tool_schema(func): Generate JSON schema from function signature + get_server(): Get the FastMCP server instance + main(): Start the MCP server +""" + +from __future__ import annotations + +import inspect +import types +from collections.abc import Callable +from typing import Any, Union, get_args, get_origin + +from pleiades.mcp import MCP_AVAILABLE, check_mcp_available +from pleiades.mcp.decorators import get_registered_tools +from pleiades.utils.logger import loguru_logger + +# Module-level logger with context binding (follows codebase convention) +logger = loguru_logger.bind(name=__name__) + +# NoneType constant for efficient type comparisons in union handling +NoneType: type = type(None) + +# Server instance created at module load for decorator support +_server: Any = None + +if MCP_AVAILABLE: + from fastmcp import FastMCP + + _server = FastMCP("pleiades-mcp") + + +# Type mapping from Python types to JSON schema types +_TYPE_MAP: dict[type, str] = { + str: "string", + int: "integer", + float: "number", + bool: "boolean", + list: "array", + dict: "object", + NoneType: "null", +} + + +def discover_tools() -> dict[str, dict[str, Any]]: + """Discover all tools registered with @mcp_tool decorator. + + Reads from the decorator registry and returns tool metadata. + The returned dict is a deep copy (from get_registered_tools) to + prevent external mutation of the registry. + + Returns: + Dict mapping tool names to their metadata. Each entry contains: + - name: The tool name + - description: The tool description + - func: Reference to the callable function + - parameter_descriptions: Optional dict of parameter descriptions + + Example: + tools = discover_tools() + for name, info in tools.items(): + print(f"Tool: {name} - {info['description']}") + # Call the discovered function + result = info['func'](*args, **kwargs) + """ + return get_registered_tools() + + +def generate_tool_schema( + func: Callable[..., Any], + parameter_descriptions: dict[str, str] | None = None, +) -> dict[str, Any]: + """Generate JSON schema from function signature and type hints. + + Inspects the function's parameters and type annotations to create + a JSON schema suitable for MCP tool registration. + + If the function is registered with @mcp_tool decorator and has parameter + descriptions, those will be included automatically (unless overridden by + the parameter_descriptions argument). + + Args: + func: The function to generate schema for. + parameter_descriptions: Optional dict mapping parameter names to descriptions. + If not provided, will look up from decorator registry. + + Returns: + Dict containing JSON schema with 'properties' and 'required' fields. + + Example: + def add(a: int, b: int) -> int: + return a + b + + schema = generate_tool_schema(add) + # {'properties': {'a': {'type': 'integer'}, 'b': {'type': 'integer'}}, + # 'required': ['a', 'b']} + """ + sig = inspect.signature(func) + annotations = getattr(func, "__annotations__", {}) + + # If parameter_descriptions not provided, try to get from registry. + # Note: This performs a linear search O(n) through all registered tools. + # For large numbers of tools (>1000), consider passing parameter_descriptions + # explicitly for better performance. + param_descs = parameter_descriptions + if param_descs is None: + tools = get_registered_tools() + for tool_info in tools.values(): + if tool_info.get("func") is func: + param_descs = tool_info.get("parameter_descriptions") + break + param_descs = param_descs or {} + + properties: dict[str, dict[str, Any]] = {} + required: list[str] = [] + + for param_name, param in sig.parameters.items(): + # Skip *args and **kwargs + if param.kind in ( + inspect.Parameter.VAR_POSITIONAL, + inspect.Parameter.VAR_KEYWORD, + ): + continue + + # Get type annotation + param_type = annotations.get(param_name) + json_type = _python_type_to_json_type(param_type) + + # Build property schema + prop_schema: dict[str, Any] = {"type": json_type} + + # Add description if available + if param_name in param_descs: + prop_schema["description"] = param_descs[param_name] + + properties[param_name] = prop_schema + + # Mark as required if no default value + if param.default is inspect.Parameter.empty: + required.append(param_name) + + return { + "properties": properties, + "required": required, + } + + +def _python_type_to_json_type(python_type: type | None) -> str: + """Convert Python type annotation to JSON schema type. + + Args: + python_type: Python type annotation or None. + + Returns: + JSON schema type string (e.g., 'string', 'integer', 'array'). + + Note: + Unrecognized types default to 'string' with a debug log message. + """ + if python_type is None: + return "string" # Default for untyped parameters + + # Handle direct type matches + if python_type in _TYPE_MAP: + return _TYPE_MAP[python_type] + + # Handle generic types (list[str], dict[str, int], Optional[int], etc.) + origin = get_origin(python_type) + + if origin is not None: + # Handle Union types (including Optional which is Union[T, None]) + # Also handle Python 3.10+ union syntax (X | Y) via types.UnionType + is_union = origin is Union or (hasattr(types, "UnionType") and origin is types.UnionType) + if is_union: + args = get_args(python_type) + # Filter out NoneType for Optional + non_none_args = [a for a in args if a is not NoneType] + if non_none_args: + # Return type of first non-None arg + return _python_type_to_json_type(non_none_args[0]) + # Defensive fallback: typing module normalizes Union[None] to NoneType, + # which is caught by direct type match, so this is theoretically unreachable + return "string" + + # Handle list, dict, etc. + if origin in _TYPE_MAP: + return _TYPE_MAP[origin] + + # Handle typing.List, typing.Dict (older style) + origin_name = getattr(origin, "__name__", str(origin)) + if origin_name.lower() == "list": + return "array" + if origin_name.lower() == "dict": + return "object" + + # Handle Any type + type_name = getattr(python_type, "__name__", str(python_type)) + if type_name == "Any": + return "string" + + # Fallback: try to match by name + if hasattr(python_type, "__name__"): + name = python_type.__name__.lower() + if name in ("list", "tuple", "set", "frozenset"): + return "array" + if name in ("dict", "mapping"): + return "object" + + # Log unrecognized type and fall back to string + logger.debug(f"Unrecognized type '{python_type}' defaulting to 'string' in JSON schema") + return "string" + + +def register_tools(server: Any) -> None: + """Register all discovered tools with the FastMCP server. + + Reads tools from the decorator registry and registers each one + with the provided FastMCP server instance. Continues registering + remaining tools even if one fails. + + Args: + server: FastMCP server instance to register tools with. + + Example: + from fastmcp import FastMCP + server = FastMCP("my-server") + register_tools(server) + """ + tools = discover_tools() + + if not tools: + logger.debug("No tools found in registry") + return + + for tool_name, tool_info in tools.items(): + func = tool_info["func"] + description = tool_info.get("description", "") + + try: + # Register with FastMCP using its decorator + server.tool(name=tool_name, description=description)(func) + logger.debug(f"Registered tool: {tool_name}") + except (TypeError, ValueError, AttributeError, RuntimeError) as e: + logger.warning(f"Failed to register tool '{tool_name}': {e}") + # Continue registering other tools + + +def get_server() -> Any: + """Get the MCP server instance. + + Returns: + The FastMCP server instance. + + Raises: + ImportError: If MCP dependencies are not installed. + """ + check_mcp_available() + # After check_mcp_available() passes, _server is guaranteed to be initialized + return _server + + +def main() -> None: + """Start the PLEIADES MCP server. + + Discovers and registers all @mcp_tool decorated functions, + then starts the server. + + Raises: + ImportError: If MCP dependencies are not installed. + """ + # Import tools module to trigger @mcp_tool decorator registration + import pleiades.mcp.tools # noqa: F401 + + server = get_server() + + # Register all discovered tools + register_tools(server) + + logger.info("Starting PLEIADES MCP server...") + server.run() + + +__all__ = [ + "discover_tools", + "generate_tool_schema", + "get_server", + "main", + "register_tools", +] diff --git a/src/pleiades/mcp/tools.py b/src/pleiades/mcp/tools.py new file mode 100644 index 00000000..5b2df751 --- /dev/null +++ b/src/pleiades/mcp/tools.py @@ -0,0 +1,238 @@ +"""MCP tool wrappers for PLEIADES workflow functions. + +This module exposes PLEIADES workflow functions as MCP tools using +the @mcp_tool decorator. Tools accept string paths (MCP convention) +and return JSON-serializable dicts. + +Output format: + Success: {"status": "success", "data": {...}} + Error: {"status": "error", "error": "error message"} + +Security Notes: + These tools accept any file system path provided by the MCP client. + Path traversal (../) is permitted - ensure MCP server runs with + appropriate file system permissions for your deployment. +""" + +from __future__ import annotations + +from datetime import date, datetime, time +from decimal import Decimal +from enum import Enum +from pathlib import Path +from typing import Any + +from pleiades.mcp.decorators import mcp_tool +from pleiades.utils.logger import loguru_logger +from pleiades.workflows import resonance as workflows + +logger = loguru_logger.bind(name=__name__) + +# Maximum recursion depth for JSON serialization (protects against circular refs) +_MAX_SERIALIZATION_DEPTH = 100 + + +def _convert_to_json_serializable(obj: Any, _depth: int = 0) -> Any: + """Convert an object to JSON-serializable format. + + Handles Path objects, Pydantic models, datetime/date/time, Decimal, bytes, + sets/frozensets, Enums, and nested structures (dicts, lists, tuples). + Includes depth protection against circular references. + + Args: + obj: Object to convert. + _depth: Internal recursion depth counter (do not set manually). + + Returns: + JSON-serializable version of the object. + + Raises: + ValueError: If recursion depth exceeds limit (possible circular reference). + """ + if _depth > _MAX_SERIALIZATION_DEPTH: + raise ValueError("Exceeded maximum serialization depth (possible circular reference)") + + # None is JSON-serializable + if obj is None: + return None + + # Primitives are JSON-serializable + if isinstance(obj, (str, int, float, bool)): + return obj + + # Path objects -> string + if isinstance(obj, Path): + return str(obj) + + # datetime/date/time -> ISO format string + if isinstance(obj, (datetime, date, time)): + return obj.isoformat() + + # Decimal -> float (some precision loss, but JSON-compatible) + if isinstance(obj, Decimal): + return float(obj) + + # bytes -> UTF-8 string with replacement for invalid chars + if isinstance(obj, bytes): + return obj.decode("utf-8", errors="replace") + + # Enum -> value + if isinstance(obj, Enum): + return obj.value + + # sets/frozensets -> sorted list (for deterministic output) + if isinstance(obj, (set, frozenset)): + return [_convert_to_json_serializable(item, _depth + 1) for item in sorted(obj, key=str)] + + # Pydantic model -> use model_dump with JSON mode + if hasattr(obj, "model_dump"): + return _convert_to_json_serializable(obj.model_dump(mode="json"), _depth + 1) + + # dict -> recurse on values + if isinstance(obj, dict): + return {k: _convert_to_json_serializable(v, _depth + 1) for k, v in obj.items()} + + # list/tuple -> recurse on items + if isinstance(obj, (list, tuple)): + return [_convert_to_json_serializable(item, _depth + 1) for item in obj] + + # Unknown type - log warning and convert to string as fallback + logger.warning(f"Unknown type {type(obj).__name__} in JSON serialization, converting to string") + return str(obj) + + +@mcp_tool( + description="Validate a neutron resonance dataset structure and check readiness for analysis.", + parameter_descriptions={ + "dataset_path": "Path to the dataset directory containing resonance data.", + }, +) +def validate_resonance_dataset(dataset_path: str) -> dict: + """Validate a resonance dataset for analysis. + + Checks that the dataset has the required structure and files + to run resonance analysis. + + Args: + dataset_path: Path to the dataset directory. + + Returns: + Dict with status and validation results or error message. + """ + try: + path = Path(dataset_path) + result = workflows.validate_dataset(path) + + # Convert result to JSON-serializable dict + data = _convert_to_json_serializable(result) + + return { + "status": "success", + "data": data, + } + except Exception as e: + logger.warning(f"validate_resonance_dataset failed: {e}") + return { + "status": "error", + "error": str(e), + } + + +@mcp_tool( + description="Extract and parse the manifest from a neutron resonance dataset.", + parameter_descriptions={ + "dataset_path": "Path to the dataset directory containing a manifest file.", + }, +) +def extract_resonance_manifest(dataset_path: str) -> dict: + """Extract manifest from a resonance dataset. + + Searches for manifest files in the dataset directory and extracts + the YAML frontmatter containing dataset metadata. + + Args: + dataset_path: Path to the dataset directory. + + Returns: + Dict with status and manifest data or error message. + """ + try: + path = Path(dataset_path) + # Pass directory path - workflow searches for manifest files internally + result = workflows.extract_manifest(path) + + if result is None: + return { + "status": "error", + "error": f"No manifest found in {path} (searched: manifest_intermediate.md, smcp_manifest.md, manifest.md)", + } + + # Convert result to JSON-serializable dict + data = _convert_to_json_serializable(result) + + return { + "status": "success", + "data": data, + } + except Exception as e: + logger.warning(f"extract_resonance_manifest failed: {e}") + return { + "status": "error", + "error": str(e), + } + + +@mcp_tool( + description="Perform neutron resonance analysis on a dataset using SAMMY.", + parameter_descriptions={ + "dataset_path": "Path to the dataset directory containing resonance data.", + "backend": "SAMMY execution backend: 'auto', 'local', or 'docker'.", + "isotopes": "List of isotopes to analyze (e.g., ['Hf-177', 'Hf-178']). If not specified, all natural isotopes for the element are used.", + }, +) +def analyze_resonance(dataset_path: str, backend: str = "auto", isotopes: list[str] | None = None) -> dict: + """Analyze a resonance dataset. + + Runs resonance analysis on the dataset using the appropriate + workflow (full imaging or simplified SAMMY-only). + + Args: + dataset_path: Path to the dataset directory. + backend: SAMMY execution backend (default: "auto"). + isotopes: List of specific isotopes to analyze. If None, uses all natural isotopes. + + Returns: + Dict with status and analysis results or error message. + """ + try: + path = Path(dataset_path) + result = workflows.analyze_resonance(path, backend=backend, isotopes=isotopes) + + # Defensive check: workflow always returns ResonanceResult per its type annotation, + # but we handle None gracefully in case of future API changes or edge cases. + if result is None: + return { + "status": "error", + "error": "Analysis returned no results", + } + + # Convert result to JSON-serializable dict + data = _convert_to_json_serializable(result) + + return { + "status": "success", + "data": data, + } + except Exception as e: + logger.warning(f"analyze_resonance failed: {e}") + return { + "status": "error", + "error": str(e), + } + + +__all__ = [ + "analyze_resonance", + "extract_resonance_manifest", + "validate_resonance_dataset", +] diff --git a/src/pleiades/nuclear/isotopes/manager.py b/src/pleiades/nuclear/isotopes/manager.py index 2e619b0e..244d8e43 100644 --- a/src/pleiades/nuclear/isotopes/manager.py +++ b/src/pleiades/nuclear/isotopes/manager.py @@ -302,3 +302,128 @@ def get_isotope_parameters_from_isotope_string(self, isotope_str: str) -> Option except Exception as e: logger.error(f"Error getting isotope parameters for {isotope_str}: {str(e)}") raise + + def get_isotopes_by_element(self, element: str) -> List[str]: + """ + Get all naturally occurring isotopes for a given element. + + Reads isotopes.info to find all isotopes of the specified element + that have non-zero natural abundance. + + Args: + element: Element symbol (e.g., "Hf", "U", "Au"). Case-insensitive. + + Returns: + List of isotope strings sorted by mass number (e.g., ["Hf-174", "Hf-176", ...]). + Returns empty list if element not found or has no natural isotopes. + """ + # Normalize element symbol for comparison (first letter upper, rest lower) + element_normalized = element.capitalize() + + isotopes: List[str] = [] + + try: + with self.get_file_path(FileCategory.ISOTOPES, "isotopes.info").open() as f: + for line in f: + line = line.strip() + # Skip comments and empty lines + if not line or line.startswith("%"): + continue + # Data lines start with a digit (atomic number) + if line[0].isdigit(): + data = line.split() + # Format: atomic_num mass_num stable/radio element name spin g_factor abundance quadrupole + # Index: 0 1 2 3 4 5 6 7 8 + if len(data) >= 8: + file_element = data[3] + mass_number = int(data[1]) + abundance = float(data[7]) + + # Match element and check for non-zero natural abundance + if file_element == element_normalized and abundance > 0: + isotopes.append(f"{file_element}-{mass_number}") + except Exception as e: + logger.warning(f"Error reading isotopes for element {element}: {e}") + return [] + + # Sort by mass number + isotopes.sort(key=lambda iso: int(iso.split("-")[1])) + + return isotopes + + def get_natural_composition(self, element: str) -> Dict[str, float]: + """ + Get natural isotopic composition for a given element. + + Reads isotopes.info to get all naturally occurring isotopes and their + abundances, returned as fractions (0-1) rather than percentages. + + Note: The isotopes.info file stores abundances as PERCENTAGES (0-100). + This method converts them to fractions (0-1) and validates that + the sum is approximately 1.0 (within 1% tolerance). + + Args: + element: Element symbol (e.g., "Hf", "U", "Au"). Case-insensitive. + + Returns: + Dict mapping isotope strings to abundance fractions. + E.g., {"Hf-174": 0.0016, "Hf-176": 0.0526, ...} + Returns empty dict if element not found or has no natural isotopes. + """ + # Normalize element symbol for comparison (first letter upper, rest lower) + element_normalized = element.capitalize() + + composition: Dict[str, float] = {} + + try: + with self.get_file_path(FileCategory.ISOTOPES, "isotopes.info").open() as f: + for line in f: + line = line.strip() + # Skip comments and empty lines + if not line or line.startswith("%"): + continue + # Data lines start with a digit (atomic number) + if line[0].isdigit(): + data = line.split() + # Format: atomic_num mass_num stable/radio element name spin g_factor abundance quadrupole + # Index: 0 1 2 3 4 5 6 7 8 + if len(data) >= 8: + file_element = data[3] + mass_number = int(data[1]) + abundance_percent = float(data[7]) + + # Match element and check for non-zero natural abundance + # Use tolerance for floating point comparison + if file_element == element_normalized and abundance_percent > 1e-10: + isotope_str = f"{file_element}-{mass_number}" + # Convert percentage to fraction + abundance_fraction = abundance_percent / 100.0 + + # Validate converted value is in valid range + if abundance_fraction > 1.0: + logger.error( + f"Abundance {abundance_percent}% for {isotope_str} exceeds 100%. " + f"Check isotopes.info file format." + ) + # Still include but cap at 1.0 + abundance_fraction = min(abundance_fraction, 1.0) + + composition[isotope_str] = abundance_fraction + + except (IOError, FileNotFoundError, PermissionError) as e: + logger.error(f"Failed to read isotopes.info for element {element}: {e}") + return {} + except Exception as e: + logger.warning(f"Error reading composition for element {element}: {e}") + return {} + + # Validate that abundances sum to approximately 1.0 (within 1% tolerance) + if composition: + total = sum(composition.values()) + if abs(total - 1.0) > 0.01: + logger.warning( + f"Natural abundances for {element} sum to {total:.4f}, expected ~1.0. " + f"This may indicate data quality issues in isotopes.info." + ) + + return composition diff --git a/src/pleiades/sammy/factory.py b/src/pleiades/sammy/factory.py index 8fbfda03..eda7456f 100644 --- a/src/pleiades/sammy/factory.py +++ b/src/pleiades/sammy/factory.py @@ -5,7 +5,6 @@ import re import shutil import subprocess -import warnings from enum import Enum from pathlib import Path from typing import Dict, Optional, Union @@ -14,11 +13,17 @@ from pleiades.sammy.backends.docker import DockerSammyRunner from pleiades.sammy.backends.local import LocalSammyRunner -from pleiades.sammy.backends.nova_ornl import NovaSammyRunner from pleiades.sammy.config import DockerSammyConfig, LocalSammyConfig, NovaSammyConfig from pleiades.sammy.interface import SammyFiles, SammyRunner from pleiades.utils.logger import loguru_logger +# NOVA backend is disabled - nova-galaxy package is unstable and not a development priority +# See GitHub issue #202 for details. The code remains for future use when nova stabilizes. +# To re-enable: uncomment the import and set _NOVA_AVAILABLE = True +# from pleiades.sammy.backends.nova_ornl import NovaSammyRunner +_NOVA_AVAILABLE = False +NovaSammyRunner = None # type: ignore[misc] + logger = loguru_logger.bind(name=__name__) @@ -93,15 +98,17 @@ def list_available_backends() -> Dict[BackendType, bool]: logger.debug(f"Error checking docker backend: {str(e)}") available[BackendType.DOCKER] = False - # Check NOVA backend - try: - nova_available = all(k in os.environ for k in ["NOVA_URL", "NOVA_API_KEY"]) - available[BackendType.NOVA] = nova_available - if nova_available: - logger.debug("NOVA credentials found") - except Exception as e: - logger.debug(f"Error checking NOVA backend: {str(e)}") - available[BackendType.NOVA] = False + # Check NOVA backend (currently disabled - see _NOVA_AVAILABLE flag) + available[BackendType.NOVA] = False + if _NOVA_AVAILABLE: + try: + nova_available = all(k in os.environ for k in ["NOVA_URL", "NOVA_API_KEY"]) + available[BackendType.NOVA] = nova_available + if nova_available: + logger.debug("NOVA credentials found") + except Exception as e: + logger.debug(f"Error checking NOVA backend: {str(e)}") + available[BackendType.NOVA] = False return available @@ -189,13 +196,9 @@ def create_runner( runner = DockerSammyRunner(config) elif backend == BackendType.NOVA: - # Warn users that NOVA backend is paused - warnings.warn( - "NOVA backend support is currently paused. The NOVA API is under " - "active development. Use local or docker backends for production work.", - UserWarning, - stacklevel=2, - ) + # NOVA backend is currently disabled + if not _NOVA_AVAILABLE or NovaSammyRunner is None: + raise BackendNotAvailableError("NOVA backend is disabled - nova-galaxy package is unstable") # For NOVA, try environment variables if not in kwargs url = kwargs.get("url") or os.environ.get("NOVA_URL") api_key = kwargs.get("api_key") or os.environ.get("NOVA_API_KEY") diff --git a/src/pleiades/workflows/__init__.py b/src/pleiades/workflows/__init__.py new file mode 100644 index 00000000..dd9f0390 --- /dev/null +++ b/src/pleiades/workflows/__init__.py @@ -0,0 +1,59 @@ +"""PLEIADES high-level workflow APIs. + +This module provides simple, high-level functions for common PLEIADES +operations. These functions orchestrate multiple lower-level APIs to +perform complete analysis workflows. + +Example: + >>> from pleiades.workflows import validate_dataset, analyze_resonance + >>> + >>> # Validate dataset before analysis + >>> validation = validate_dataset("/path/to/dataset") + >>> if validation.valid: + ... print(f"Dataset ready for {validation.recommended_workflow.value} workflow") + ... + >>> # Run analysis + >>> result = analyze_resonance("/path/to/dataset") + >>> if result.success: + ... if result.reduced_chi_squared is not None: + ... print(f"Chi²/dof: {result.reduced_chi_squared:.3f}") + ... if result.fit_quality is not None: + ... print(f"Fit quality: {result.fit_quality.value}") + >>> else: + ... print(f"Analysis failed: {result.error_message}") +""" + +from __future__ import annotations + +from pleiades.workflows.models import ( + FitQuality, + IsotopeResult, + ManifestData, + MaterialProperties, + ResonanceResult, + ValidationIssue, + ValidationResult, + WorkflowType, +) +from pleiades.workflows.resonance import ( + analyze_resonance, + extract_manifest, + validate_dataset, +) + +__all__ = [ + # Main workflow functions + "analyze_resonance", + "validate_dataset", + "extract_manifest", + # Result models + "ResonanceResult", + "ValidationResult", + "ValidationIssue", + "ManifestData", + "MaterialProperties", + "IsotopeResult", + # Enums + "WorkflowType", + "FitQuality", +] diff --git a/src/pleiades/workflows/models.py b/src/pleiades/workflows/models.py new file mode 100644 index 00000000..35bab84f --- /dev/null +++ b/src/pleiades/workflows/models.py @@ -0,0 +1,300 @@ +"""Pydantic models for PLEIADES workflow results. + +This module defines structured result types for workflow operations, +enabling type-safe results and consistent output formatting. +""" + +from __future__ import annotations + +import math +from enum import Enum +from pathlib import Path +from typing import Any, Literal + +from pydantic import BaseModel, ConfigDict, Field, field_validator + + +class WorkflowType(str, Enum): + """Type of resonance analysis workflow.""" + + FULL = "full" + SIMPLIFIED = "simplified" + + +class FitQuality(str, Enum): + """Quality assessment of SAMMY fit.""" + + EXCELLENT = "excellent" + GOOD = "good" + ACCEPTABLE = "acceptable" + POOR = "poor" + + @classmethod + def from_chi_squared(cls, reduced_chi_sq: float) -> FitQuality: + """Determine fit quality from reduced chi-squared value. + + Args: + reduced_chi_sq: Reduced chi-squared value (must be non-negative and finite). + + Returns: + FitQuality enum value based on the chi-squared value. + + Raises: + ValueError: If reduced_chi_sq is negative, NaN, or infinite. + """ + if not math.isfinite(reduced_chi_sq) or reduced_chi_sq < 0: + raise ValueError(f"Invalid reduced chi-squared value: {reduced_chi_sq}") + if reduced_chi_sq < 1.2: + return cls.EXCELLENT + elif reduced_chi_sq < 2.0: + return cls.GOOD + elif reduced_chi_sq < 5.0: + return cls.ACCEPTABLE + else: + return cls.POOR + + +class MaterialProperties(BaseModel): + """Material properties for resonance analysis.""" + + density_g_cm3: float = Field(..., description="Material density in g/cm³") + atomic_mass_amu: float = Field(..., description="Atomic mass in amu") + temperature_k: float | None = Field(None, description="Sample temperature in Kelvin") + + @field_validator("density_g_cm3", "atomic_mass_amu") + @classmethod + def validate_positive(cls, v: float, info) -> float: + """Validate that physical properties are positive.""" + if v <= 0: + raise ValueError(f"{info.field_name} must be positive, got {v}") + return v + + @field_validator("temperature_k") + @classmethod + def validate_temperature(cls, v: float | None) -> float | None: + """Validate that temperature is positive if provided.""" + if v is not None and v <= 0: + raise ValueError(f"temperature_k must be positive, got {v}") + return v + + +class IsotopeResult(BaseModel): + """Results for a single isotope in the analysis.""" + + isotope: str = Field(..., description="Isotope identifier (e.g., 'Au-197')") + abundance: float = Field(..., description="Fitted abundance fraction") + abundance_uncertainty: float | None = Field(None, description="Uncertainty in abundance") + + +class ResonanceResult(BaseModel): + """Complete results from resonance analysis workflow. + + This model captures all outputs from a successful resonance analysis, + including fit quality metrics, physical parameters, and output file paths. + """ + + success: bool = Field(..., description="Whether the analysis completed successfully") + workflow_type: WorkflowType = Field(..., description="Type of workflow executed") + + # Primary isotope info + primary_isotope: str = Field(..., description="Primary isotope analyzed") + isotopes_analyzed: list[str] = Field(default_factory=list, description="All isotopes in analysis") + + # Fit results + chi_squared: float | None = Field(None, description="Chi-squared value") + reduced_chi_squared: float | None = Field(None, description="Reduced chi-squared (chi²/dof)") + degrees_of_freedom: int | None = Field(None, description="Degrees of freedom") + fit_quality: FitQuality | None = Field(None, description="Qualitative fit assessment") + + # Physical parameters (fitted) + number_density: float | None = Field(None, description="Number density (atoms/barn-cm)") + temperature_k: float | None = Field(None, description="Effective temperature (K)") + + # Isotope-specific results (for multi-isotope analysis) + isotope_results: list[IsotopeResult] = Field(default_factory=list, description="Per-isotope fit results") + + # Output paths + output_dir: Path | None = Field(None, description="SAMMY output directory") + lpt_file: Path | None = Field(None, description="SAMMY .LPT log file") + lst_file: Path | None = Field(None, description="SAMMY .LST listing file") + par_file: Path | None = Field(None, description="Updated .PAR parameter file") + plot_file: Path | None = Field(None, description="Generated fit plot") + + # Error info (if success=False) + error_message: str | None = Field(None, description="Error message if analysis failed") + error_step: str | None = Field(None, description="Workflow step where error occurred") + + # Workflow metadata + runtime_seconds: float | None = Field(None, description="Total execution time") + workflow_steps: dict[str, str] = Field(default_factory=dict, description="Status of each workflow step") + + model_config = ConfigDict(arbitrary_types_allowed=True) + + +class ValidationIssue(BaseModel): + """Single validation issue found in dataset.""" + + severity: Literal["error", "warning"] = Field(..., description="Issue severity") + message: str = Field(..., description="Description of the issue") + path: Path | None = Field(None, description="Path related to the issue") + + +class ValidationResult(BaseModel): + """Results from dataset validation. + + Validates that a dataset has the required structure and files + for resonance analysis. + """ + + valid: bool = Field(..., description="Whether the dataset is valid for analysis") + dataset_path: Path = Field(..., description="Path to the validated dataset") + + # Detected workflow type + can_run_full_workflow: bool = Field(False, description="Dataset supports full imaging workflow") + can_run_simplified_workflow: bool = Field(False, description="Dataset has pre-existing SAMMY files") + recommended_workflow: WorkflowType | None = Field(None, description="Recommended workflow type") + + # Dataset contents + has_raw_data: bool = Field(False, description="Has raw imaging data") + has_open_beam: bool = Field(False, description="Has open beam normalization data") + has_metadata: bool = Field(False, description="Has NeXus metadata") + has_sammy_files: bool = Field(False, description="Has pre-existing SAMMY files") + has_manifest: bool = Field(False, description="Has sMCP manifest file") + + # Validation issues + issues: list[ValidationIssue] = Field(default_factory=list, description="All validation issues found") + + @property + def errors(self) -> list[ValidationIssue]: + """Get only error-level issues.""" + return [i for i in self.issues if i.severity == "error"] + + @property + def warnings(self) -> list[ValidationIssue]: + """Get only warning-level issues.""" + return [i for i in self.issues if i.severity == "warning"] + + model_config = ConfigDict(arbitrary_types_allowed=True) + + +class ManifestData(BaseModel): + """Parsed manifest data from dataset. + + Structured representation of the sMCP manifest frontmatter + and processing instructions. + """ + + name: str = Field(..., description="Unique dataset identifier") + description: str = Field(..., description="Human-readable description") + version: str = Field(..., description="Manifest version (semver)") + created: str = Field(..., description="ISO-8601 creation timestamp") + + # Experiment metadata + facility: str | None = Field(None, description="Facility identifier (e.g., 'SNS')") + beamline: str | None = Field(None, description="Beamline name (e.g., 'VENUS')") + detector: str | None = Field(None, description="Detector type") + sample_id: str | None = Field(None, description="Sample identifier") + + # Analysis parameters + isotope: str | None = Field(None, description="Primary isotope (e.g., 'Au-197')") + material_properties: MaterialProperties | None = Field(None, description="Material physical properties") + + # Isotope composition settings (Issue #204) + use_natural_abundance: bool = Field( + True, + description="If True, use natural abundance from isotopes.info. If False, use custom enrichment values.", + ) + enrichment: dict[str, float] | None = Field( + None, + description="Custom isotope composition for enriched samples. " + "Keys are isotope strings (e.g., 'U-235'), values are fractions (0-1). " + "Only used when use_natural_abundance=False.", + ) + + # Explicit isotope list (Issue #206) + isotopes: list[str] | None = Field( + None, + description="Explicit list of isotopes to analyze (e.g., ['Hf-176', 'Hf-177']). " + "When set, only these isotopes are included regardless of natural abundance. " + "Takes priority over both enrichment and natural abundance lookup.", + ) + + @field_validator("isotopes") + @classmethod + def validate_isotopes(cls, v: list[str] | None) -> list[str] | None: + """Validate isotopes list contains valid isotope format strings. + + Checks: + - All elements are strings + - All strings match isotope format (Element-MassNumber, e.g., 'Hf-177') + """ + import re + + if v is None: + return v + + if not v: # Empty list is valid + return v + + # Validate each isotope in the list + isotope_pattern = re.compile(r"^[A-Z][a-z]?-\d+$") + for idx, isotope in enumerate(v): + if not isinstance(isotope, str): + raise ValueError(f"Invalid isotope at index {idx}: expected string, got {type(isotope).__name__}") + if not isotope_pattern.match(isotope): + raise ValueError( + f"Invalid isotope format '{isotope}' at index {idx}. " + f"Expected format: 'Element-MassNumber' (e.g., 'Hf-177', 'U-235')" + ) + + return v + + @field_validator("enrichment") + @classmethod + def validate_enrichment(cls, v: dict[str, float] | None) -> dict[str, float] | None: + """Validate enrichment dict has valid isotope keys and abundance fractions. + + Checks: + - All values are in range [0, 1] + - Values sum to approximately 1.0 (within 1% tolerance) + - Keys match isotope format (Element-MassNumber) + """ + import re + + if v is None: + return v + + if not v: + return v + + # Validate isotope key format + isotope_pattern = re.compile(r"^[A-Z][a-z]?-\d+$") + for key in v.keys(): + if not isotope_pattern.match(key): + raise ValueError( + f"Invalid isotope key '{key}' in enrichment. " + f"Expected format: 'Element-MassNumber' (e.g., 'U-235', 'Hf-177')" + ) + + # Validate values are in valid range + for key, value in v.items(): + if not isinstance(value, (int, float)): + raise ValueError(f"Enrichment value for '{key}' must be a number, got {type(value).__name__}") + if value < 0: + raise ValueError(f"Enrichment value for '{key}' cannot be negative: {value}") + if value > 1.0: + raise ValueError( + f"Enrichment value for '{key}' exceeds 1.0: {value}. " + f"Values should be fractions (0-1), not percentages." + ) + + # Validate sum is approximately 1.0 + total = sum(v.values()) + if abs(total - 1.0) > 0.01: + raise ValueError(f"Enrichment values must sum to approximately 1.0, got {total:.4f}. Values: {v}") + + return v + + # Raw content + body: str = Field("", description="Markdown body with processing instructions") + raw_frontmatter: dict[str, Any] = Field(default_factory=dict, description="Raw YAML frontmatter") diff --git a/src/pleiades/workflows/resonance.py b/src/pleiades/workflows/resonance.py new file mode 100644 index 00000000..d0d200af --- /dev/null +++ b/src/pleiades/workflows/resonance.py @@ -0,0 +1,1290 @@ +"""High-level resonance analysis workflows for PLEIADES. + +This module provides simple, high-level functions that orchestrate +complete resonance analysis workflows. These functions can be called +directly by users or exposed via MCP tools. + +Example: + >>> from pleiades.workflows import validate_dataset, analyze_resonance + >>> result = validate_dataset("/path/to/dataset") + >>> if result.valid: + ... analysis = analyze_resonance("/path/to/dataset") +""" + +from __future__ import annotations + +import math +import re +from pathlib import Path +from typing import TYPE_CHECKING + +import yaml + +from pleiades.utils.logger import loguru_logger +from pleiades.workflows.models import ( + FitQuality, + ManifestData, + MaterialProperties, + ResonanceResult, + ValidationIssue, + ValidationResult, + WorkflowType, +) + +if TYPE_CHECKING: + from pleiades.sammy.interface import SammyRunner + +from pleiades.nuclear.isotopes.manager import IsotopeManager + +logger = loguru_logger.bind(name=__name__) + +# Module-level IsotopeManager instance for data-driven isotope lookup +_isotope_manager = IsotopeManager() + +# Compiled regex for validating primary_isotope format +# Format: Element symbol (1-2 chars, first uppercase) optionally followed by -number or -nat +# Examples: "Hf", "Hf-177", "Hf-nat", "U-235", "Au-197" +_ISOTOPE_PATTERN = re.compile(r"^[A-Z][a-z]?(-(\d+|nat))?$") + + +def validate_dataset(dataset_path: str | Path) -> ValidationResult: + """Validate a dataset for resonance analysis. + + Checks that the dataset has the required structure and files + to run resonance analysis. Determines whether the dataset supports + the full imaging workflow or simplified SAMMY-only workflow. + + Args: + dataset_path: Path to the dataset directory. + + Returns: + ValidationResult with validation status and detected capabilities. + + Example: + >>> result = validate_dataset("/data/Au197_sample") + >>> if result.valid: + ... print(f"Ready for {result.recommended_workflow.value} workflow") + >>> else: + ... for error in result.errors: + ... print(f"Error: {error.message}") + """ + dataset_path = Path(dataset_path) + issues: list[ValidationIssue] = [] + + logger.info(f"Validating dataset: {dataset_path}") + + # Check dataset exists + if not dataset_path.exists(): + return ValidationResult( + valid=False, + dataset_path=dataset_path, + issues=[ + ValidationIssue( + severity="error", + message=f"Dataset directory does not exist: {dataset_path}", + path=dataset_path, + ) + ], + ) + + if not dataset_path.is_dir(): + return ValidationResult( + valid=False, + dataset_path=dataset_path, + issues=[ + ValidationIssue( + severity="error", + message=f"Path is not a directory: {dataset_path}", + path=dataset_path, + ) + ], + ) + + # Check for imaging data (full workflow) + raw_dir = dataset_path / "raw" + ob_dir = dataset_path / "open_beam" + metadata_dir = dataset_path / "metadata" + + has_raw = raw_dir.exists() and raw_dir.is_dir() + has_ob = ob_dir.exists() and ob_dir.is_dir() + has_metadata = metadata_dir.exists() and metadata_dir.is_dir() + + # Check for SAMMY files (simplified workflow) + sammy_data_dir = dataset_path / "sammy_data" + has_sammy_files = False + if sammy_data_dir.exists(): + inp_files = list(sammy_data_dir.glob("*.inp")) + has_sammy_files = len(inp_files) > 0 + + # Check for manifest + manifest_paths = [ + dataset_path / "manifest_intermediate.md", + dataset_path / "smcp_manifest.md", + dataset_path / "manifest.md", + ] + has_manifest = any(p.exists() for p in manifest_paths) + + # Determine workflow capabilities + can_run_full = has_raw and has_ob + can_run_simplified = has_sammy_files + + # Build validation issues + if not can_run_full and not can_run_simplified: + issues.append( + ValidationIssue( + severity="error", + message="Dataset does not contain imaging data (raw/, open_beam/) or SAMMY files (sammy_data/*.inp)", + path=dataset_path, + ) + ) + + if has_raw and not has_ob: + issues.append( + ValidationIssue( + severity="warning", + message="Open beam directory not found - normalization may fail", + path=ob_dir, + ) + ) + + if has_raw and not has_metadata: + issues.append( + ValidationIssue( + severity="warning", + message="Metadata directory not found - may need manual TOF calibration", + path=metadata_dir, + ) + ) + + if not has_manifest: + issues.append( + ValidationIssue( + severity="warning", + message="No manifest file found - analysis parameters must be provided explicitly", + path=dataset_path, + ) + ) + + # Validate SAMMY files if present + if has_sammy_files: + inp_files = list(sammy_data_dir.glob("*.inp")) + for inp_file in inp_files: + par_file = inp_file.with_suffix(".par") + dat_file = inp_file.with_suffix(".dat") + + if not par_file.exists(): + issues.append( + ValidationIssue( + severity="error", + message=f"Missing parameter file for {inp_file.name}", + path=par_file, + ) + ) + if not dat_file.exists(): + issues.append( + ValidationIssue( + severity="error", + message=f"Missing data file for {inp_file.name}", + path=dat_file, + ) + ) + + # Determine recommended workflow + # Prefer full workflow when both are available so imaging data is used + recommended = None + if can_run_full: + recommended = WorkflowType.FULL + elif can_run_simplified: + recommended = WorkflowType.SIMPLIFIED + + # Dataset is valid if there are no errors + has_errors = any(i.severity == "error" for i in issues) + + result = ValidationResult( + valid=not has_errors, + dataset_path=dataset_path, + can_run_full_workflow=can_run_full, + can_run_simplified_workflow=can_run_simplified, + recommended_workflow=recommended, + has_raw_data=has_raw, + has_open_beam=has_ob, + has_metadata=has_metadata, + has_sammy_files=has_sammy_files, + has_manifest=has_manifest, + issues=issues, + ) + + if result.valid: + logger.info(f"Dataset valid, recommended workflow: {recommended}") + else: + logger.warning(f"Dataset validation failed: {len(result.errors)} errors") + + return result + + +def extract_manifest(dataset_path: str | Path) -> ManifestData | None: + """Extract and parse manifest from dataset. + + Args: + dataset_path: Path to the dataset directory. + + Returns: + ManifestData if manifest found and valid, None otherwise. + + Raises: + ValueError: If manifest exists but has invalid format. + """ + dataset_path = Path(dataset_path) + + # Try different manifest names + manifest_paths = [ + dataset_path / "manifest_intermediate.md", + dataset_path / "smcp_manifest.md", + dataset_path / "manifest.md", + ] + + manifest_path = None + for candidate in manifest_paths: + if candidate.exists(): + manifest_path = candidate + break + + if manifest_path is None: + logger.debug(f"No manifest found in {dataset_path}") + return None + + logger.info(f"Parsing manifest: {manifest_path}") + + with open(manifest_path, encoding="utf-8") as f: + content = f.read() + + # Split YAML frontmatter from Markdown body + # Use maxsplit=2 to handle "---" in the markdown body (e.g., horizontal rules) + parts = content.split("---", 2) + if len(parts) < 3: + raise ValueError(f"Invalid manifest format in {manifest_path}: missing YAML frontmatter") + + # Parse YAML frontmatter + yaml_content = parts[1].strip() + frontmatter = yaml.safe_load(yaml_content) + + # Handle empty frontmatter (yaml.safe_load returns None for empty content) + if frontmatter is None: + frontmatter = {} + + # Handle datetime objects (PyYAML auto-parses ISO timestamps) + if "created" in frontmatter and hasattr(frontmatter["created"], "isoformat"): + frontmatter["created"] = frontmatter["created"].isoformat() + + # Extract markdown body (everything after the second "---") + body = parts[2].strip() + + # Parse material properties if present + material_props = None + if "material_properties" in frontmatter and frontmatter["material_properties"]: + mp = frontmatter["material_properties"] + try: + material_props = MaterialProperties( + density_g_cm3=mp.get("density_g_cm3"), + atomic_mass_amu=mp.get("atomic_mass_amu"), + temperature_k=mp.get("temperature_k"), + ) + except Exception as e: + # Log but don't fail - material properties are optional for some workflows + logger.warning(f"Invalid material_properties in manifest: {e}") + + # Parse enrichment configuration (Issue #204) + use_natural_abundance = frontmatter.get("use_natural_abundance", True) + enrichment = frontmatter.get("enrichment") + + # Parse explicit isotopes list (Issue #206) + isotopes = frontmatter.get("isotopes") + + return ManifestData( + name=frontmatter.get("name", "unknown"), + description=frontmatter.get("description", ""), + version=frontmatter.get("version", "1.0.0"), + created=frontmatter.get("created", ""), + facility=frontmatter.get("facility"), + beamline=frontmatter.get("beamline"), + detector=frontmatter.get("detector"), + sample_id=frontmatter.get("sample_id"), + isotope=frontmatter.get("isotope"), + material_properties=material_props, + use_natural_abundance=use_natural_abundance, + enrichment=enrichment, + isotopes=isotopes, + body=body, + raw_frontmatter=frontmatter, + ) + + +def analyze_resonance( + dataset_path: str | Path, + *, + backend: str = "auto", + isotopes: list[str] | None = None, + skip_validation: bool = False, +) -> ResonanceResult: + """Execute complete resonance analysis workflow. + + This is the main entry point for resonance analysis. It automatically + detects the appropriate workflow (full or simplified) based on the + dataset contents and executes all necessary steps. + + Full workflow steps (imaging data): + 1. Validate dataset structure + 2. Normalize transmission data + 3. Convert to SAMMY format + 4. Retrieve ENDF nuclear parameters + 5. Execute SAMMY fitting + 6. Parse and return results + + Simplified workflow steps (pre-existing SAMMY files): + 1. Validate dataset structure + 2. Execute SAMMY fitting + 3. Parse and return results + + Args: + dataset_path: Path to the dataset directory. + backend: SAMMY backend to use ('local', 'docker', 'nova', or 'auto'). + isotopes: List of isotopes to analyze. If None, detected from manifest. + skip_validation: Skip dataset validation (use with caution). + + Returns: + ResonanceResult with analysis results and output paths. + If the dataset is invalid and skip_validation=False, returns a + ResonanceResult with success=False and an appropriate error message. + + Example: + >>> result = analyze_resonance("/data/Au197_sample") + >>> if result.success: + ... print(f"Reduced chi²: {result.reduced_chi_squared:.3f}") + ... print(f"Fit quality: {result.fit_quality.value}") + >>> else: + ... print(f"Analysis failed: {result.error_message}") + """ + import time + + dataset_path = Path(dataset_path) + start_time = time.time() + workflow_steps: dict[str, str] = {} + + # Validate isotopes parameter + if isotopes is not None and len(isotopes) == 0: + return ResonanceResult( + success=False, + workflow_type=WorkflowType.SIMPLIFIED, + primary_isotope="unknown", + error_message="isotopes parameter cannot be an empty list", + error_step="parameter_validation", + workflow_steps={}, + ) + + logger.info(f"Starting resonance analysis: {dataset_path}") + + # Step 1: Validate dataset + if not skip_validation: + workflow_steps["validation"] = "running" + validation = validate_dataset(dataset_path) + + if not validation.valid: + return ResonanceResult( + success=False, + workflow_type=WorkflowType.SIMPLIFIED, + primary_isotope="unknown", + error_message="Dataset validation failed", + error_step="validation", + workflow_steps={"validation": "failed"}, + ) + workflow_steps["validation"] = "completed" + + # Determine workflow type + # Prefer full workflow when both imaging and SAMMY data exist + if validation.can_run_full_workflow: + workflow_type = WorkflowType.FULL + elif validation.can_run_simplified_workflow: + workflow_type = WorkflowType.SIMPLIFIED + else: + return ResonanceResult( + success=False, + workflow_type=WorkflowType.SIMPLIFIED, + primary_isotope="unknown", + error_message="No valid workflow detected for dataset", + error_step="validation", + workflow_steps=workflow_steps, + ) + else: + # When skipping validation, detect workflow from available files + sammy_data_dir = dataset_path / "sammy_data" + try: + has_sammy_files = sammy_data_dir.exists() and any(sammy_data_dir.glob("*.inp")) + except (PermissionError, OSError) as e: + logger.warning(f"Cannot access sammy_data directory: {e}") + has_sammy_files = False + has_imaging_data = (dataset_path / "raw").exists() and (dataset_path / "open_beam").exists() + + if has_imaging_data: + workflow_type = WorkflowType.FULL + elif has_sammy_files: + workflow_type = WorkflowType.SIMPLIFIED + else: + # Neither workflow is available - return clear error + return ResonanceResult( + success=False, + workflow_type=WorkflowType.SIMPLIFIED, + primary_isotope="unknown", + error_message="No SAMMY files (sammy_data/*.inp) or imaging data (raw/, open_beam/) found", + error_step="file_discovery", + workflow_steps=workflow_steps, + ) + + # Extract manifest for parameters + manifest = extract_manifest(dataset_path) + primary_isotope = "unknown" + if manifest and manifest.isotope: + primary_isotope = manifest.isotope + if isotopes: + primary_isotope = isotopes[0] + + # Execute appropriate workflow + if workflow_type == WorkflowType.SIMPLIFIED: + if isotopes: + logger.warning("isotopes parameter is not supported for simplified workflow datasets") + primary_for_error = isotopes[0] + return ResonanceResult( + success=False, + workflow_type=WorkflowType.SIMPLIFIED, + primary_isotope=primary_for_error, + error_message=( + "isotopes parameter is only supported when imaging data is available so SAMMY inputs " + "can be regenerated. Remove the parameter or provide raw/open_beam data." + ), + error_step="parameter_validation", + workflow_steps=workflow_steps, + ) + return _execute_simplified_workflow( + dataset_path=dataset_path, + primary_isotope=primary_isotope, + backend=backend, + start_time=start_time, + workflow_steps=workflow_steps, + ) + else: + return _execute_full_workflow( + dataset_path=dataset_path, + manifest=manifest, + primary_isotope=primary_isotope, + isotopes=isotopes, + backend=backend, + start_time=start_time, + workflow_steps=workflow_steps, + ) + + +def _get_isotope_composition( + user_isotopes: list[str] | None, + manifest: ManifestData | None, + primary_isotope: str, +) -> tuple[list[str], list[float]]: + """Get isotope list and abundances for analysis. + + Determines the isotopes and their relative abundances based on: + 1. User-specified isotopes (highest priority) - equal weights + 2. Manifest isotopes list (Issue #206) - explicit subset with equal weights + 3. Manifest enrichment data - custom composition + 4. Natural abundance from isotopes.info - data-driven lookup + + Args: + user_isotopes: User-specified list of isotopes (takes priority). + manifest: Parsed manifest data (may contain isotopes list or enrichment info). + primary_isotope: Primary isotope from manifest or detection. + Valid formats: "Hf-177", "Hf-nat", "Hf" + + Returns: + Tuple of (isotope_list, abundance_list). + + Raises: + ValueError: If no isotopes can be determined or primary_isotope format is invalid. + + Example: + >>> isotopes, abundances = _get_isotope_composition(None, None, "Hf-177") + >>> # Returns all 6 natural Hf isotopes with natural abundances + """ + # Priority 1: User-specified isotopes with equal weights + if user_isotopes: + abundances = [1.0 / len(user_isotopes)] * len(user_isotopes) + return user_isotopes, abundances + + # Priority 2: Manifest isotopes list with equal weights (Issue #206) + # Note: Empty list falls through to natural abundance lookup + if manifest and manifest.isotopes: + abundances = [1.0 / len(manifest.isotopes)] * len(manifest.isotopes) + return list(manifest.isotopes), abundances + + # Priority 3: Manifest enrichment data + # Use explicit iteration to ensure isotope-abundance pairing is correct + if manifest and not manifest.use_natural_abundance and manifest.enrichment: + items = list(manifest.enrichment.items()) + isotopes = [iso for iso, _ in items] + abundances = [abund for _, abund in items] + return isotopes, abundances + + # Priority 4: Natural abundance from isotopes.info + # Validate primary_isotope format before extraction + if not primary_isotope or not primary_isotope.strip(): + raise ValueError("primary_isotope cannot be empty. Valid formats: 'Hf-177', 'Hf-nat', 'Hf'") + + # Validate format using module-level compiled pattern + if not _ISOTOPE_PATTERN.match(primary_isotope.strip()): + raise ValueError( + f"Invalid primary_isotope format: '{primary_isotope}'. " + f"Valid formats: 'Hf-177', 'Hf-nat', 'Hf'. " + f"Element symbol must start with uppercase letter (e.g., 'Hf', not 'hf')." + ) + + # Extract element from primary_isotope (e.g., "Hf-177" -> "Hf", "Hf" -> "Hf", "Hf-nat" -> "Hf") + element = primary_isotope.split("-")[0] + + # Get natural composition from IsotopeManager + composition = _isotope_manager.get_natural_composition(element) + + if not composition: + raise ValueError( + f"No natural isotopes found for element '{element}' (from primary_isotope='{primary_isotope}'). " + f"Valid formats: 'Hf-177', 'Hf-nat', 'Hf'. " + f"Element symbol must be a valid chemical element (e.g., 'Hf', 'U', 'Au'). " + f"Alternatively, provide explicit isotopes via the isotopes parameter." + ) + + # Use explicit iteration to ensure isotope-abundance pairing is correct + items = list(composition.items()) + isotopes = [iso for iso, _ in items] + abundances = [abund for _, abund in items] + + return isotopes, abundances + + +def _execute_simplified_workflow( + dataset_path: Path, + primary_isotope: str, + backend: str, + start_time: float, + workflow_steps: dict[str, str], +) -> ResonanceResult: + """Execute simplified workflow with pre-existing SAMMY files.""" + import time + + from pleiades.sammy.interface import SammyFiles + from pleiades.sammy.results.manager import ResultsManager + + sammy_data_dir = dataset_path / "sammy_data" + + # Find SAMMY input files + inp_files = list(sammy_data_dir.glob("*.inp")) + if not inp_files: + return ResonanceResult( + success=False, + workflow_type=WorkflowType.SIMPLIFIED, + primary_isotope=primary_isotope, + error_message=f"No .inp files found in {sammy_data_dir}", + error_step="file_discovery", + workflow_steps=workflow_steps, + ) + + inp_file = inp_files[0] + par_file = inp_file.with_suffix(".par") + dat_file = inp_file.with_suffix(".dat") + + # Validate required files + if not par_file.exists(): + return ResonanceResult( + success=False, + workflow_type=WorkflowType.SIMPLIFIED, + primary_isotope=primary_isotope, + error_message=f"Parameter file not found: {par_file}", + error_step="file_discovery", + workflow_steps=workflow_steps, + ) + + if not dat_file.exists(): + return ResonanceResult( + success=False, + workflow_type=WorkflowType.SIMPLIFIED, + primary_isotope=primary_isotope, + error_message=f"Data file not found: {dat_file}", + error_step="file_discovery", + workflow_steps=workflow_steps, + ) + + logger.info(f"Running simplified workflow with {inp_file.name}") + + # Create working/output directories + sammy_working = dataset_path / "sammy_working" + sammy_output = dataset_path / "sammy_output" + sammy_working.mkdir(exist_ok=True) + sammy_output.mkdir(exist_ok=True) + + # Get SAMMY runner + workflow_steps["backend_setup"] = "running" + try: + runner = _get_sammy_runner(backend, sammy_working, sammy_output) + workflow_steps["backend_setup"] = "completed" + except Exception as e: + logger.error(f"Failed to create SAMMY runner: {e}") + return ResonanceResult( + success=False, + workflow_type=WorkflowType.SIMPLIFIED, + primary_isotope=primary_isotope, + error_message=str(e), + error_step="backend_setup", + workflow_steps=workflow_steps, + ) + + # Execute SAMMY + workflow_steps["sammy_execution"] = "running" + files = SammyFiles( + input_file=inp_file, + parameter_file=par_file, + data_file=dat_file, + ) + + try: + runner.prepare_environment(files) + exec_result = runner.execute_sammy(files) + + if not exec_result.success: + return ResonanceResult( + success=False, + workflow_type=WorkflowType.SIMPLIFIED, + primary_isotope=primary_isotope, + error_message=f"SAMMY execution failed: {exec_result.error_message}", + error_step="sammy_execution", + workflow_steps=workflow_steps, + ) + + runner.collect_outputs(exec_result) + workflow_steps["sammy_execution"] = "completed" + except Exception as e: + logger.error(f"SAMMY execution error: {e}") + return ResonanceResult( + success=False, + workflow_type=WorkflowType.SIMPLIFIED, + primary_isotope=primary_isotope, + error_message=str(e), + error_step="sammy_execution", + workflow_steps=workflow_steps, + ) + + # Parse results + workflow_steps["results_parsing"] = "running" + try: + lpt_file = sammy_output / "SAMMY.LPT" + lst_file = sammy_output / "SAMMY.LST" + + # Verify SAMMY produced output files + if not lpt_file.exists(): + error_msg = f"SAMMY output file not found: {lpt_file}" + logger.error(error_msg) + return ResonanceResult( + success=False, + workflow_type=WorkflowType.SIMPLIFIED, + primary_isotope=primary_isotope, + error_message=error_msg, + error_step="results_parsing", + workflow_steps=workflow_steps, + ) + + if not lst_file.exists(): + error_msg = f"SAMMY output file not found: {lst_file}" + logger.error(error_msg) + return ResonanceResult( + success=False, + workflow_type=WorkflowType.SIMPLIFIED, + primary_isotope=primary_isotope, + error_message=error_msg, + error_step="results_parsing", + workflow_steps=workflow_steps, + ) + + results_manager = ResultsManager( + lpt_file_path=lpt_file, + lst_file_path=lst_file, + ) + + fit_results = results_manager.run_results.fit_results + if not fit_results: + error_msg = "No fit results found in SAMMY output" + logger.error(error_msg) + return ResonanceResult( + success=False, + workflow_type=WorkflowType.SIMPLIFIED, + primary_isotope=primary_isotope, + error_message=error_msg, + error_step="results_parsing", + workflow_steps=workflow_steps, + ) + + final_fit = fit_results[-1] + chi_sq = final_fit.chi_squared_results + + # Extract broadening parameters (Issue #204: upgraded from debug to warning) + temperature = None + number_density = None + try: + broadening = final_fit.physics_data.broadening_parameters + temperature = float(broadening.temp) + number_density = float(broadening.thick) + except (AttributeError, KeyError, ValueError, TypeError) as e: + # Broadening parameters are important for fit quality assessment + # Missing parameters may indicate incomplete SAMMY output + logger.warning(f"Broadening parameters not available in SAMMY output: {e}") + + workflow_steps["results_parsing"] = "completed" + except Exception as e: + logger.error(f"Results parsing error: {e}") + return ResonanceResult( + success=False, + workflow_type=WorkflowType.SIMPLIFIED, + primary_isotope=primary_isotope, + error_message=str(e), + error_step="results_parsing", + workflow_steps=workflow_steps, + ) + + # Build successful result + runtime = time.time() - start_time + + # Use 'is not None' instead of truthiness check since 0.0 is a valid chi-squared value + # Also check chi_sq itself is not None before accessing its attributes + chi_squared_val = None + reduced_chi_sq_val = None + dof_val = None + if chi_sq is not None: + chi_squared_val = float(chi_sq.chi_squared) if chi_sq.chi_squared is not None else None + reduced_chi_sq_val = float(chi_sq.reduced_chi_squared) if chi_sq.reduced_chi_squared is not None else None + dof_val = chi_sq.dof if chi_sq.dof is not None else None + fit_quality_val = FitQuality.from_chi_squared(reduced_chi_sq_val) if reduced_chi_sq_val is not None else None + + return ResonanceResult( + success=True, + workflow_type=WorkflowType.SIMPLIFIED, + primary_isotope=primary_isotope, + isotopes_analyzed=[primary_isotope], + chi_squared=chi_squared_val, + reduced_chi_squared=reduced_chi_sq_val, + degrees_of_freedom=dof_val, + fit_quality=fit_quality_val, + number_density=number_density, + temperature_k=temperature, + output_dir=sammy_output, + lpt_file=lpt_file, + lst_file=lst_file, + par_file=sammy_output / "SAMMY.PAR", + runtime_seconds=runtime, + workflow_steps=workflow_steps, + ) + + +def _execute_full_workflow( + dataset_path: Path, + manifest: ManifestData | None, + primary_isotope: str, + isotopes: list[str] | None, + backend: str, + start_time: float, + workflow_steps: dict[str, str], +) -> ResonanceResult: + """Execute full workflow from imaging data. + + Full workflow steps: + 1. Normalization (TIFF images → transmission spectra) + 2. Format conversion (CSV → SAMMY .twenty format) + 3. ENDF retrieval (download nuclear parameters) + 4. INP file generation (create SAMMY input) + 5. SAMMY execution (run fitting) + 6. Results parsing (extract fit parameters) + 7. Plotting (generate fit plot) + + Args: + dataset_path: Path to dataset directory. + manifest: Parsed manifest data (optional but recommended). + primary_isotope: Primary isotope being analyzed. + isotopes: List of isotopes for multi-isotope analysis. + backend: SAMMY backend to use. + start_time: Workflow start time for runtime calculation. + workflow_steps: Dictionary to track step completion. + + Returns: + ResonanceResult with analysis results. + """ + import time + + from pleiades.processing import Facility + from pleiades.processing.normalization import normalization + from pleiades.sammy.fitting.config import FitConfig + from pleiades.sammy.interface import SammyFilesMultiMode + from pleiades.sammy.io.data_manager import convert_csv_to_sammy_twenty + from pleiades.sammy.io.inp_manager import InpDatasetMetadata, InpManager + from pleiades.sammy.io.json_manager import JsonManager + from pleiades.sammy.results.manager import ResultsManager + + # Step 1: Normalization (imaging data -> transmission spectra) + workflow_steps["normalization"] = "running" + logger.info("Step 1: Running normalization") + + try: + sample_folders = [str(dataset_path / "raw")] + ob_folders = [str(dataset_path / "open_beam")] + nexus_path = str(dataset_path / "metadata") + + # Determine facility from manifest or default to ORNL (Issue #204) + # Facility class only has 'ornl' and 'lanl' values + facility = Facility.ornl # Default + if manifest and manifest.facility: + facility_str = manifest.facility.lower() + if facility_str in ("sns", "ornl"): + facility = Facility.ornl + elif facility_str in ("lansce", "lanl"): + # LANSCE is at LANL (Los Alamos Neutron Science Center) + facility = Facility.lanl + else: + # Unsupported facilities (e.g., J-PARC) default to ORNL with warning + logger.warning(f"Unsupported facility '{manifest.facility}', defaulting to ORNL") + + spectra_dir = dataset_path / "spectra" + spectra_dir.mkdir(exist_ok=True) + + transmissions = normalization( + list_sample_folders=sample_folders, + list_obs_folders=ob_folders, + nexus_path=nexus_path, + facility=facility, + output_folder=str(spectra_dir), + ) + + if not transmissions: + raise ValueError("Normalization produced no transmission spectra") + + workflow_steps["normalization"] = f"completed ({len(transmissions)} spectra)" + logger.info(f"Normalization completed: {len(transmissions)} spectra") + except Exception as e: + logger.error(f"Normalization failed: {e}") + return ResonanceResult( + success=False, + workflow_type=WorkflowType.FULL, + primary_isotope=primary_isotope, + error_message=f"Normalization failed: {e}", + error_step="normalization", + workflow_steps=workflow_steps, + ) + + # Step 2: Format conversion (CSV -> SAMMY .twenty) + workflow_steps["format_conversion"] = "running" + logger.info("Step 2: Converting to SAMMY format") + + try: + twenty_dir = dataset_path / "twenty" + twenty_dir.mkdir(exist_ok=True) + + # Find transmission files created by normalization (space/tab-delimited .txt) + txt_files = list(spectra_dir.glob("*transmission*.txt")) + if not txt_files: + txt_files = list(spectra_dir.glob("*.txt")) + + if not txt_files: + raise ValueError( + f"No transmission files found in {spectra_dir}. " + f"Expected files matching '*transmission*.txt' or '*.txt'. " + f"Check normalization output_folder setting." + ) + + twenty_files = [] + for txt_file in txt_files: + # Replace .txt extension with .twenty + twenty_file = twenty_dir / (txt_file.stem + ".twenty") + convert_csv_to_sammy_twenty(str(txt_file), str(twenty_file)) + twenty_files.append(twenty_file) + + workflow_steps["format_conversion"] = f"completed ({len(twenty_files)} files)" + logger.info(f"Format conversion completed: {len(twenty_files)} files") + except Exception as e: + logger.error(f"Format conversion failed: {e}") + return ResonanceResult( + success=False, + workflow_type=WorkflowType.FULL, + primary_isotope=primary_isotope, + error_message=f"Format conversion failed: {e}", + error_step="format_conversion", + workflow_steps=workflow_steps, + ) + + # Step 3: ENDF retrieval (nuclear data download) + workflow_steps["endf_retrieval"] = "running" + logger.info("Step 3: Retrieving ENDF nuclear data") + + try: + # Determine isotopes for analysis using data-driven lookup (Issue #204) + # Priority: user-specified > manifest enrichment > natural abundance from isotopes.info + analysis_isotopes, abundances = _get_isotope_composition( + user_isotopes=isotopes, + manifest=manifest, + primary_isotope=primary_isotope, + ) + + logger.info(f"Isotope composition: {dict(zip(analysis_isotopes, abundances))}") + + # Validate isotope list + if not analysis_isotopes or not all(analysis_isotopes): + raise ValueError(f"Invalid isotope configuration: {analysis_isotopes}") + + # Use dataset_path as working_dir (ENDF .par files go here) + working_dir = dataset_path + + json_manager = JsonManager() + json_path = json_manager.create_json_config( + isotopes=analysis_isotopes, + abundances=abundances, + working_dir=str(working_dir), + custom_global_settings={ + "forceRMoore": "yes", + "purgeSpinGroups": "yes", + "fudge": "0.7", + }, + ) + + # Validate JSON config was created + if json_path is None or not json_path.exists(): + raise FileNotFoundError(f"JSON config file not created: {json_path}") + + # Verify ENDF parameter files were downloaded + # Files use ENDF naming convention: e.g., 072-Hf-174.B-VIII.0.par + for isotope in analysis_isotopes: + # Match pattern like "*Hf-174*.par" + pattern = f"*{isotope}*.par" + matching_files = list(working_dir.glob(pattern)) + if not matching_files: + raise FileNotFoundError(f"ENDF parameter file not found for {isotope} (pattern: {pattern})") + + workflow_steps["endf_retrieval"] = f"completed ({len(analysis_isotopes)} isotopes)" + logger.info(f"ENDF retrieval completed: {', '.join(analysis_isotopes)}") + except Exception as e: + logger.error(f"ENDF retrieval failed: {e}") + return ResonanceResult( + success=False, + workflow_type=WorkflowType.FULL, + primary_isotope=primary_isotope, + error_message=f"ENDF retrieval failed: {e}", + error_step="endf_retrieval", + workflow_steps=workflow_steps, + ) + + # Step 4: SAMMY input file generation + workflow_steps["inp_generation"] = "running" + logger.info("Step 4: Generating SAMMY input file") + + try: + # Get material properties from manifest + if manifest and manifest.material_properties: + mat_props = manifest.material_properties + density = mat_props.density_g_cm3 + atomic_mass = mat_props.atomic_mass_amu + temperature = mat_props.temperature_k or 293.6 + else: + raise ValueError("Material properties required in manifest for full workflow") + + element = primary_isotope.split("-")[0] + # Calculate weighted average mass number from isotope composition (Issue #204) + mass_number = 0.0 + valid_isotope_count = 0 + for iso, abund in zip(analysis_isotopes, abundances): + parts = iso.split("-") + if len(parts) > 1: + mass_part = parts[1] + # Skip "nat" suffix - it's not a mass number + if mass_part.lower() == "nat": + continue + try: + mass_number += int(mass_part) * abund + valid_isotope_count += 1 + except ValueError: + logger.warning(f"Invalid mass number in isotope '{iso}', skipping") + continue + + # Validate we processed at least one valid isotope with a mass number + if valid_isotope_count == 0: + raise ValueError( + f"No valid mass numbers found in isotopes {analysis_isotopes}. " + f"Isotope strings must include numeric mass number (e.g., 'Hf-177', not 'Hf' or 'Hf-nat')." + ) + + # Use floor + 0.5 for consistent rounding (avoids banker's rounding) + mass_number = int(math.floor(mass_number + 0.5)) + + # Build FitConfig from workflow variables + fit_config = FitConfig( + fit_title=f"{primary_isotope} resonance analysis", + ) + for iso_str in analysis_isotopes: + fit_config.append_isotope_from_string(iso_str) + fit_config.physics_params.energy_parameters.min_energy = 1.0 + fit_config.physics_params.energy_parameters.max_energy = 200.0 + fit_config.physics_params.broadening_parameters.temp = temperature + + # Build dataset metadata for INP generation + dataset_metadata = InpDatasetMetadata( + element=element, + mass_number=mass_number, + atomic_mass_amu=atomic_mass, + min_energy_eV=1.0, + max_energy_eV=200.0, + temperature_K=temperature, + density_g_cm3=density, + thickness_mm=0.05, + ) + + # Get resolution file path if available + resolution_file_path = _get_resolution_file_path(dataset_path) + + inp_file = working_dir / "fitting.inp" + InpManager.create_multi_isotope_inp( + inp_file, + fit_config=fit_config, + title=f"{primary_isotope} resonance analysis", + dataset_metadata=dataset_metadata, + resolution_file_path=resolution_file_path, + ) + workflow_steps["inp_generation"] = "completed" + logger.info("INP file generation completed") + except Exception as e: + logger.error(f"INP generation failed: {e}") + return ResonanceResult( + success=False, + workflow_type=WorkflowType.FULL, + primary_isotope=primary_isotope, + error_message=f"INP generation failed: {e}", + error_step="inp_generation", + workflow_steps=workflow_steps, + ) + + # Step 5: SAMMY execution + workflow_steps["sammy_execution"] = "running" + logger.info("Step 5: Running SAMMY") + + try: + sammy_working = dataset_path / "sammy_working" + sammy_output = dataset_path / "sammy_output" + + runner = _get_sammy_runner(backend, sammy_working, sammy_output) + + # Use first transmission file + data_file = twenty_files[0] if twenty_files else None + if not data_file: + raise ValueError("No transmission data available for fitting") + + files = SammyFilesMultiMode( + input_file=inp_file, + json_config_file=Path(json_path), + data_file=data_file, + endf_directory=working_dir, + ) + + runner.prepare_environment(files) + exec_result = runner.execute_sammy(files) + + if not exec_result.success: + raise RuntimeError(f"SAMMY execution failed: {exec_result.error_message}") + + runner.collect_outputs(exec_result) + workflow_steps["sammy_execution"] = f"completed ({exec_result.runtime_seconds:.2f}s)" + logger.info(f"SAMMY execution completed in {exec_result.runtime_seconds:.2f}s") + except Exception as e: + logger.error(f"SAMMY execution failed: {e}") + return ResonanceResult( + success=False, + workflow_type=WorkflowType.FULL, + primary_isotope=primary_isotope, + error_message=f"SAMMY execution failed: {e}", + error_step="sammy_execution", + workflow_steps=workflow_steps, + ) + + # Step 6: Results parsing + workflow_steps["results_parsing"] = "running" + logger.info("Step 6: Parsing results") + + try: + lpt_file = sammy_output / "SAMMY.LPT" + lst_file = sammy_output / "SAMMY.LST" + + if not lpt_file.exists() or not lst_file.exists(): + raise FileNotFoundError("SAMMY output files not found") + + results_manager = ResultsManager( + lpt_file_path=lpt_file, + lst_file_path=lst_file, + ) + + fit_results = results_manager.run_results.fit_results + if not fit_results: + raise ValueError("No fit results found in SAMMY output") + + final_fit = fit_results[-1] + chi_sq = final_fit.chi_squared_results + + # Extract broadening parameters (Issue #204: upgraded from debug to warning) + temperature_result = None + number_density = None + try: + broadening = final_fit.physics_data.broadening_parameters + temperature_result = float(broadening.temp) + number_density = float(broadening.thick) + except (AttributeError, KeyError, ValueError, TypeError) as e: + # Broadening parameters are important for fit quality assessment + # Missing parameters may indicate incomplete SAMMY output + logger.warning(f"Broadening parameters not available in SAMMY output: {e}") + + workflow_steps["results_parsing"] = "completed" + logger.info("Results parsing completed") + except Exception as e: + logger.error(f"Results parsing failed: {e}") + return ResonanceResult( + success=False, + workflow_type=WorkflowType.FULL, + primary_isotope=primary_isotope, + error_message=f"Results parsing failed: {e}", + error_step="results_parsing", + workflow_steps=workflow_steps, + ) + + # Step 7: Generate fitting plot + workflow_steps["plotting"] = "running" + logger.info("Step 7: Generating plot") + + plot_file = None + try: + plot_file = dataset_path / "fit_results.png" + + fig = results_manager.plot_transmission( + figsize=(12, 8), + title=f"{primary_isotope} Resonance Fit", + xscale="log", + data_color="blue", + final_color="red", + show=False, + show_diff=True, + plot_uncertainty=True, + ) + + fig.savefig(str(plot_file), dpi=300, bbox_inches="tight") + workflow_steps["plotting"] = f"completed ({plot_file.name})" + logger.info(f"Plot saved to {plot_file}") + + import matplotlib.pyplot as plt + + plt.close(fig) + except Exception as e: + logger.warning(f"Plotting failed (non-critical): {e}") + workflow_steps["plotting"] = f"failed ({e})" + plot_file = None + + # Build successful result + runtime = time.time() - start_time + + chi_squared_val = float(chi_sq.chi_squared) if chi_sq and chi_sq.chi_squared is not None else None + reduced_chi_sq_val = ( + float(chi_sq.reduced_chi_squared) if chi_sq and chi_sq.reduced_chi_squared is not None else None + ) + dof_val = chi_sq.dof if chi_sq and chi_sq.dof is not None else None + fit_quality_val = FitQuality.from_chi_squared(reduced_chi_sq_val) if reduced_chi_sq_val is not None else None + + return ResonanceResult( + success=True, + workflow_type=WorkflowType.FULL, + primary_isotope=primary_isotope, + isotopes_analyzed=analysis_isotopes, + chi_squared=chi_squared_val, + reduced_chi_squared=reduced_chi_sq_val, + degrees_of_freedom=dof_val, + fit_quality=fit_quality_val, + number_density=number_density, + temperature_k=temperature_result, + output_dir=sammy_output, + lpt_file=lpt_file, + lst_file=lst_file, + par_file=sammy_output / "SAMMY.PAR", + plot_file=plot_file, + runtime_seconds=runtime, + workflow_steps=workflow_steps, + ) + + +def _get_resolution_file_path(dataset_path: Path) -> Path | None: + """Get resolution file path for VENUS resonance analysis. + + Resolution file is facility-specific. Checks: + 1. VENUS_RES_FUNC environment variable + 2. Default VENUS location + + Args: + dataset_path: Dataset root directory. + + Returns: + Path to resolution file or None. + """ + import os + + # Check environment variable + venus_res = os.environ.get("VENUS_RES_FUNC", "") + if venus_res: + res_path = Path(venus_res) + if res_path.exists(): + return res_path + + # Check default VENUS location + default_venus_res = Path.home() / "SNS" / "VENUS" / "shared" / "instrument" / "resonance" + if default_venus_res.exists(): + res_files = list(default_venus_res.glob("*.txt")) + if res_files: + return res_files[0] + + return None + + +def _get_sammy_runner( + backend: str, + working_dir: Path, + output_dir: Path, +) -> SammyRunner: + """Get appropriate SAMMY runner based on backend selection. + + Args: + backend: Backend type ('local', 'docker', 'nova', or 'auto'). + working_dir: SAMMY working directory. + output_dir: SAMMY output directory. + + Returns: + Configured SammyRunner instance. + + Raises: + ValueError: If backend is not available. + FileNotFoundError: If local SAMMY executable not found. + """ + from pleiades.sammy.factory import SammyFactory + + working_dir.mkdir(parents=True, exist_ok=True) + output_dir.mkdir(parents=True, exist_ok=True) + + if backend == "auto": + logger.info("Auto-selecting SAMMY backend") + return SammyFactory.auto_select( + working_dir=working_dir, + output_dir=output_dir, + ) + + logger.info(f"Creating {backend} SAMMY backend") + return SammyFactory.create_runner( + backend_type=backend, + working_dir=working_dir, + output_dir=output_dir, + ) diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/integration/test_mcp/__init__.py b/tests/integration/test_mcp/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/integration/test_mcp/conftest.py b/tests/integration/test_mcp/conftest.py new file mode 100644 index 00000000..8fe837e6 --- /dev/null +++ b/tests/integration/test_mcp/conftest.py @@ -0,0 +1,78 @@ +"""Fixtures for MCP integration tests. + +Provides test data and common setup for integration testing. +""" + +import pytest + + +@pytest.fixture +def sample_dataset(tmp_path): + """Create a minimal sample dataset for testing. + + Returns: + Path to temporary dataset directory with manifest and sample files. + """ + dataset_dir = tmp_path / "test_dataset" + dataset_dir.mkdir() + + # Create manifest + manifest = dataset_dir / "manifest.md" + manifest.write_text( + """--- +name: integration_test_dataset +description: Dataset for MCP integration testing +version: 1.0.0 +created: 2024-06-15T10:00:00 +facility: TEST +beamline: TEST_BEAM +isotope: Si-28 +material_properties: + density_g_cm3: 2.33 + atomic_mass_amu: 28.0855 + temperature_k: 300.0 +--- +# Integration Test Dataset + +This is a minimal dataset for integration testing. +""" + ) + + return dataset_dir + + +@pytest.fixture +def sample_dataset_no_manifest(tmp_path): + """Create a dataset directory without manifest. + + Returns: + Path to temporary dataset directory without manifest. + """ + dataset_dir = tmp_path / "no_manifest_dataset" + dataset_dir.mkdir() + + # Create a dummy file so directory isn't empty + (dataset_dir / "data.txt").write_text("sample data") + + return dataset_dir + + +@pytest.fixture +def sample_dataset_invalid_manifest(tmp_path): + """Create a dataset with invalid manifest. + + Returns: + Path to temporary dataset directory with invalid manifest. + """ + dataset_dir = tmp_path / "invalid_manifest_dataset" + dataset_dir.mkdir() + + # Create invalid manifest (no YAML delimiters) + manifest = dataset_dir / "manifest.md" + manifest.write_text( + """name: test +This is not valid YAML frontmatter format +""" + ) + + return dataset_dir diff --git a/tests/integration/test_mcp/test_mcp_workflow.py b/tests/integration/test_mcp/test_mcp_workflow.py new file mode 100644 index 00000000..2ca66c66 --- /dev/null +++ b/tests/integration/test_mcp/test_mcp_workflow.py @@ -0,0 +1,281 @@ +"""Integration tests for MCP workflow. + +Tests end-to-end MCP tool functionality with real dataset processing. +Some tests require Docker backend and are skipped if unavailable. +""" + +import json + +import pytest + + +class TestValidateWorkflow: + """Test validate_resonance_dataset tool end-to-end.""" + + def test_validates_sample_dataset(self, sample_dataset): + """Should successfully validate a well-formed dataset.""" + from pleiades.mcp.tools import validate_resonance_dataset + + result = validate_resonance_dataset(str(sample_dataset)) + + assert result["status"] == "success" + assert "data" in result + # Validation result should include key fields + assert isinstance(result["data"], dict) + + def test_validates_nonexistent_path(self): + """Should handle nonexistent path gracefully.""" + from pleiades.mcp.tools import validate_resonance_dataset + + result = validate_resonance_dataset("/nonexistent/path/to/dataset") + + # Should return result (not crash) - either error or validation failure + assert "status" in result + assert isinstance(result, dict) + + def test_result_is_json_serializable(self, sample_dataset): + """Validation result should be fully JSON-serializable.""" + from pleiades.mcp.tools import validate_resonance_dataset + + result = validate_resonance_dataset(str(sample_dataset)) + + # Should not raise + json_str = json.dumps(result) + parsed = json.loads(json_str) + + assert parsed["status"] == result["status"] + + +class TestExtractManifestWorkflow: + """Test extract_resonance_manifest tool end-to-end.""" + + def test_extracts_manifest_from_sample(self, sample_dataset): + """Should extract manifest from valid dataset.""" + from pleiades.mcp.tools import extract_resonance_manifest + + result = extract_resonance_manifest(str(sample_dataset)) + + assert result["status"] == "success" + assert result["data"]["name"] == "integration_test_dataset" + assert result["data"]["isotope"] == "Si-28" + assert result["data"]["material_properties"]["density_g_cm3"] == 2.33 + + def test_handles_missing_manifest(self, sample_dataset_no_manifest): + """Should return error for missing manifest.""" + from pleiades.mcp.tools import extract_resonance_manifest + + result = extract_resonance_manifest(str(sample_dataset_no_manifest)) + + assert result["status"] == "error" + assert "No manifest found" in result["error"] + + def test_handles_invalid_manifest(self, sample_dataset_invalid_manifest): + """Should return error for invalid manifest format.""" + from pleiades.mcp.tools import extract_resonance_manifest + + result = extract_resonance_manifest(str(sample_dataset_invalid_manifest)) + + assert result["status"] == "error" + # Should indicate format error + assert "error" in result + + def test_manifest_result_is_json_serializable(self, sample_dataset): + """Manifest result should be fully JSON-serializable.""" + from pleiades.mcp.tools import extract_resonance_manifest + + result = extract_resonance_manifest(str(sample_dataset)) + + # datetime and other types should be converted + json_str = json.dumps(result) + parsed = json.loads(json_str) + + assert parsed["status"] == "success" + assert parsed["data"]["created"] == "2024-06-15T10:00:00" + + +class TestAnalyzeWorkflow: + """Test analyze_resonance tool end-to-end.""" + + def test_handles_incomplete_dataset(self, sample_dataset): + """Should handle dataset without full SAMMY setup.""" + from pleiades.mcp.tools import analyze_resonance + + # Dataset has manifest but no SAMMY files + result = analyze_resonance(str(sample_dataset)) + + # Should return error (missing required files) or partial result + assert "status" in result + assert isinstance(result, dict) + + def test_handles_nonexistent_path(self): + """Should handle nonexistent path gracefully.""" + from pleiades.mcp.tools import analyze_resonance + + result = analyze_resonance("/nonexistent/path") + + # Should return a result (not crash) - may be success with validation failure + # or error depending on how the workflow handles missing paths + assert "status" in result + assert isinstance(result, dict) + + def test_backend_parameter_accepted(self, sample_dataset): + """Should accept backend parameter.""" + from pleiades.mcp.tools import analyze_resonance + + # Should not crash with different backend values + result = analyze_resonance(str(sample_dataset), backend="auto") + assert "status" in result + + def test_result_is_json_serializable(self, sample_dataset): + """Analysis result should be JSON-serializable.""" + from pleiades.mcp.tools import analyze_resonance + + result = analyze_resonance(str(sample_dataset)) + + # Should not raise + json_str = json.dumps(result) + assert isinstance(json_str, str) + + +class TestFullWorkflowPipeline: + """Test complete validate -> manifest -> analyze pipeline.""" + + def test_pipeline_with_sample_dataset(self, sample_dataset): + """Test complete workflow pipeline.""" + from pleiades.mcp.tools import ( + analyze_resonance, + extract_resonance_manifest, + validate_resonance_dataset, + ) + + # Step 1: Validate + validation = validate_resonance_dataset(str(sample_dataset)) + assert validation["status"] == "success" + + # Step 2: Extract manifest + manifest = extract_resonance_manifest(str(sample_dataset)) + assert manifest["status"] == "success" + assert manifest["data"]["name"] == "integration_test_dataset" + + # Step 3: Analyze (will fail due to missing SAMMY files, but shouldn't crash) + analysis = analyze_resonance(str(sample_dataset)) + assert "status" in analysis + + def test_pipeline_stops_on_validation_failure(self, sample_dataset_no_manifest): + """Pipeline should handle validation/manifest failures.""" + from pleiades.mcp.tools import extract_resonance_manifest, validate_resonance_dataset + + # Validate passes (directory exists) + validation = validate_resonance_dataset(str(sample_dataset_no_manifest)) + assert "status" in validation + + # Manifest extraction fails + manifest = extract_resonance_manifest(str(sample_dataset_no_manifest)) + assert manifest["status"] == "error" + + +class TestDockerBackendIntegration: + """Integration tests requiring Docker backend. + + These tests are skipped if Docker is not available. + """ + + @pytest.mark.skipif( + not __import__("shutil").which("docker"), + reason="Docker executable not available", + ) + def test_analyze_with_docker_backend(self, sample_dataset): + """Test analyze_resonance with explicit docker backend.""" + from pleiades.mcp.tools import analyze_resonance + + # Run analysis with docker backend + # Will fail gracefully if SAMMY Docker image not available + result = analyze_resonance(str(sample_dataset), backend="docker") + + # Should return a valid result structure (success or error) + assert "status" in result + assert isinstance(result, dict) + + +class TestErrorRecovery: + """Test error recovery and graceful degradation.""" + + def test_recovers_from_permission_error(self, tmp_path): + """Should handle permission errors gracefully.""" + from pleiades.mcp.tools import validate_resonance_dataset + + # Create restricted directory + restricted = tmp_path / "restricted" + restricted.mkdir() + restricted.chmod(0o000) + + try: + result = validate_resonance_dataset(str(restricted)) + + # Should return error, not crash + assert "status" in result + finally: + restricted.chmod(0o755) # Restore for cleanup + + def test_handles_empty_directory(self, tmp_path): + """Should handle empty directory.""" + from pleiades.mcp.tools import validate_resonance_dataset + + empty_dir = tmp_path / "empty" + empty_dir.mkdir() + + result = validate_resonance_dataset(str(empty_dir)) + + assert "status" in result + + def test_handles_file_instead_of_directory(self, tmp_path): + """Should handle file path instead of directory.""" + from pleiades.mcp.tools import validate_resonance_dataset + + file_path = tmp_path / "file.txt" + file_path.write_text("content") + + result = validate_resonance_dataset(str(file_path)) + + # Should handle gracefully + assert "status" in result + + +class TestResultStructure: + """Test that all results follow consistent structure.""" + + def test_success_result_structure(self, sample_dataset): + """Success results should have status='success' and 'data' field.""" + from pleiades.mcp.tools import extract_resonance_manifest + + result = extract_resonance_manifest(str(sample_dataset)) + + assert result["status"] == "success" + assert "data" in result + assert isinstance(result["data"], dict) + + def test_error_result_structure(self, sample_dataset_no_manifest): + """Error results should have status='error' and 'error' field.""" + from pleiades.mcp.tools import extract_resonance_manifest + + result = extract_resonance_manifest(str(sample_dataset_no_manifest)) + + assert result["status"] == "error" + assert "error" in result + assert isinstance(result["error"], str) + + def test_all_tools_return_dict(self, sample_dataset): + """All tools should return dict type.""" + from pleiades.mcp.tools import ( + analyze_resonance, + extract_resonance_manifest, + validate_resonance_dataset, + ) + + assert isinstance(validate_resonance_dataset(str(sample_dataset)), dict) + assert isinstance(extract_resonance_manifest(str(sample_dataset)), dict) + assert isinstance(analyze_resonance(str(sample_dataset)), dict) + + +if __name__ == "__main__": + pytest.main(["-v", __file__]) diff --git a/tests/unit/pleiades/nuclear/isotopes/test_isotope_manager.py b/tests/unit/pleiades/nuclear/isotopes/test_isotope_manager.py index 34272dcd..18198100 100644 --- a/tests/unit/pleiades/nuclear/isotopes/test_isotope_manager.py +++ b/tests/unit/pleiades/nuclear/isotopes/test_isotope_manager.py @@ -153,3 +153,123 @@ def test_get_mat_number_invalid(manager): # Assert that None is returned for an invalid isotope assert mat_number is None + + +# ============================================================================= +# Tests for get_isotopes_by_element() and get_natural_composition() +# Issue #204: Replace hardcoded demo values with data-driven isotope lookup +# ============================================================================= + + +class TestGetIsotopesByElement: + """Tests for IsotopeManager.get_isotopes_by_element().""" + + def test_hafnium_returns_six_isotopes(self, manager): + """Hafnium has 6 naturally occurring isotopes.""" + isotopes = manager.get_isotopes_by_element("Hf") + assert len(isotopes) == 6 + assert set(isotopes) == {"Hf-174", "Hf-176", "Hf-177", "Hf-178", "Hf-179", "Hf-180"} + + def test_gold_returns_one_isotope(self, manager): + """Gold (Au) has only one naturally occurring isotope.""" + isotopes = manager.get_isotopes_by_element("Au") + assert len(isotopes) == 1 + assert isotopes == ["Au-197"] + + def test_uranium_returns_natural_isotopes(self, manager): + """Uranium has 3 naturally occurring isotopes (234, 235, 238).""" + isotopes = manager.get_isotopes_by_element("U") + assert len(isotopes) == 3 + assert set(isotopes) == {"U-234", "U-235", "U-238"} + + def test_case_insensitive_lookup(self, manager): + """Element lookup should be case-insensitive.""" + isotopes_upper = manager.get_isotopes_by_element("HF") + isotopes_lower = manager.get_isotopes_by_element("hf") + isotopes_mixed = manager.get_isotopes_by_element("Hf") + assert isotopes_upper == isotopes_mixed + assert isotopes_lower == isotopes_mixed + + def test_invalid_element_returns_empty_list(self, manager): + """Invalid element should return empty list, not raise exception.""" + isotopes = manager.get_isotopes_by_element("Xx") + assert isotopes == [] + + def test_results_sorted_by_mass_number(self, manager): + """Results should be sorted by mass number.""" + isotopes = manager.get_isotopes_by_element("Hf") + mass_numbers = [int(iso.split("-")[1]) for iso in isotopes] + assert mass_numbers == sorted(mass_numbers) + + def test_excludes_radioactive_isotopes(self, manager): + """By default, should only return stable (naturally occurring) isotopes. + + Uranium is special - its isotopes are marked radioactive (*) in isotopes.info + but they do have natural abundances. We include isotopes with non-zero abundance. + """ + # Carbon has C-12, C-13 stable and C-14 radioactive (no natural abundance) + isotopes = manager.get_isotopes_by_element("C") + assert "C-14" not in isotopes # Radioactive with 0.0 abundance + assert "C-12" in isotopes + assert "C-13" in isotopes + + +class TestGetNaturalComposition: + """Tests for IsotopeManager.get_natural_composition().""" + + def test_hafnium_abundances_as_fractions(self, manager): + """Abundances should be returned as fractions (0-1), not percentages.""" + composition = manager.get_natural_composition("Hf") + # Values from isotopes.info are in percent, method should convert to fractions + assert abs(composition["Hf-174"] - 0.0016) < 0.0001 + assert abs(composition["Hf-176"] - 0.0526) < 0.0001 + assert abs(composition["Hf-177"] - 0.1860) < 0.0001 + assert abs(composition["Hf-178"] - 0.2728) < 0.0001 + assert abs(composition["Hf-179"] - 0.1362) < 0.0001 + assert abs(composition["Hf-180"] - 0.3508) < 0.0001 + + def test_abundances_sum_to_one(self, manager): + """Natural abundances should sum to approximately 1.0.""" + composition = manager.get_natural_composition("Hf") + total = sum(composition.values()) + assert abs(total - 1.0) < 0.01 # Allow small rounding error + + def test_gold_single_isotope_is_100_percent(self, manager): + """Single-isotope elements should have abundance ~1.0.""" + composition = manager.get_natural_composition("Au") + assert len(composition) == 1 + assert abs(composition["Au-197"] - 1.0) < 0.01 + + def test_case_insensitive_lookup(self, manager): + """Element lookup should be case-insensitive.""" + comp_upper = manager.get_natural_composition("HF") + comp_lower = manager.get_natural_composition("hf") + comp_mixed = manager.get_natural_composition("Hf") + assert comp_upper == comp_mixed + assert comp_lower == comp_mixed + + def test_invalid_element_returns_empty_dict(self, manager): + """Invalid element should return empty dict, not raise exception.""" + composition = manager.get_natural_composition("Xx") + assert composition == {} + + def test_includes_zero_abundance_isotopes(self, manager): + """Should include isotopes with zero spin (and non-zero abundance) but not radioactive zeros. + + Some stable isotopes have zero nuclear spin but non-zero abundance. + Example: Hf-174 has spin 0.0 but abundance 0.16%. + """ + composition = manager.get_natural_composition("Hf") + # Hf-174 has spin 0.0 but should be included (has natural abundance) + assert "Hf-174" in composition + assert composition["Hf-174"] > 0 + + def test_uranium_natural_composition(self, manager): + """Test uranium's known natural composition.""" + composition = manager.get_natural_composition("U") + # U-238 is ~99.27%, U-235 is ~0.72%, U-234 is ~0.0055% + assert "U-238" in composition + assert "U-235" in composition + assert "U-234" in composition + assert composition["U-238"] > 0.99 # Most abundant + assert composition["U-235"] > 0.007 # ~0.72% diff --git a/tests/unit/pleiades/sammy/backends/test_nova.py b/tests/unit/pleiades/sammy/backends/test_nova.py index 6f5d5e21..a94678e6 100644 --- a/tests/unit/pleiades/sammy/backends/test_nova.py +++ b/tests/unit/pleiades/sammy/backends/test_nova.py @@ -1,5 +1,9 @@ #!/usr/bin/env python -"""Unit tests for NOVA SAMMY backend implementation (updated for new API).""" +"""Unit tests for NOVA SAMMY backend implementation (updated for new API). + +NOTE: NOVA backend is currently disabled. These tests are skipped. +See GitHub issue for details on why NOVA support is disabled. +""" import os import zipfile @@ -7,9 +11,12 @@ import pytest -from pleiades.sammy.backends.nova_ornl import NovaSammyRunner -from pleiades.sammy.config import NovaSammyConfig -from pleiades.sammy.interface import SammyFiles +# Skip entire module - NOVA backend is disabled (nova-galaxy package is unstable) +pytest.skip("NOVA backend is disabled - nova-galaxy package is unstable", allow_module_level=True) + +from pleiades.sammy.backends.nova_ornl import NovaSammyRunner # noqa: E402 +from pleiades.sammy.config import NovaSammyConfig # noqa: E402 +from pleiades.sammy.interface import SammyFiles # noqa: E402 # Mock environment variables os.environ["NOVA_URL"] = "https://mock_nova_url" diff --git a/tests/unit/pleiades/sammy/test_config.py b/tests/unit/pleiades/sammy/test_config.py index b45f6176..8b5dea75 100644 --- a/tests/unit/pleiades/sammy/test_config.py +++ b/tests/unit/pleiades/sammy/test_config.py @@ -175,8 +175,13 @@ def test_validate_same_container_dirs(self, temp_working_dir): assert "must be different" in str(exc.value) +@pytest.mark.skip(reason="NOVA backend is disabled - nova-galaxy package is unstable") class TestNovaSammyConfig: - """Tests for NovaSammyConfig.""" + """Tests for NovaSammyConfig. + + NOTE: These tests are skipped because NOVA backend is disabled. + See GitHub issue for details. + """ def test_create_with_valid_config(self, temp_working_dir): """Should create config with valid values.""" diff --git a/tests/unit/pleiades/sammy/test_factory.py b/tests/unit/pleiades/sammy/test_factory.py index 5eaed7f7..dc3c8929 100644 --- a/tests/unit/pleiades/sammy/test_factory.py +++ b/tests/unit/pleiades/sammy/test_factory.py @@ -7,7 +7,9 @@ from pleiades.sammy.backends.docker import DockerSammyRunner from pleiades.sammy.backends.local import LocalSammyRunner -from pleiades.sammy.backends.nova_ornl import NovaSammyRunner + +# NOVA backend is disabled - nova-galaxy package is unstable +# from pleiades.sammy.backends.nova_ornl import NovaSammyRunner from pleiades.sammy.factory import ( BackendNotAvailableError, BackendType, @@ -133,13 +135,14 @@ class TestSammyFactory: """Tests for SammyFactory.""" def test_list_available_backends_all_available(self, mock_which, mock_subprocess_run, mock_nova_env_vars): - """All backends should be available.""" + """All backends should be available (except NOVA which is disabled).""" _ = mock_which, mock_subprocess_run, mock_nova_env_vars # implicitly used by the fixture available = SammyFactory.list_available_backends() + # NOVA is disabled at code level, so it's always False regardless of env vars assert available == { BackendType.LOCAL: True, BackendType.DOCKER: True, - BackendType.NOVA: True, + BackendType.NOVA: False, } def test_list_available_backends_none_available( @@ -171,11 +174,13 @@ def test_create_runner_docker(self, mock_sammy_runner, tmp_path): runner = SammyFactory.create_runner("docker", tmp_path) assert isinstance(runner, DockerSammyRunner) + @pytest.mark.skip(reason="NOVA backend is disabled - nova-galaxy package is unstable") def test_create_runner_nova(self, mock_sammy_runner, tmp_path): """Should create NovaSammyRunner.""" _ = mock_sammy_runner # implicitly used by the fixture runner = SammyFactory.create_runner("nova", tmp_path) - assert isinstance(runner, NovaSammyRunner) + # NovaSammyRunner import is commented out, this test is skipped + # assert isinstance(runner, NovaSammyRunner) def test_create_runner_invalid_backend(self, tmp_path): """Should raise error for invalid backend.""" diff --git a/tests/unit/pleiades/test_mcp/__init__.py b/tests/unit/pleiades/test_mcp/__init__.py new file mode 100644 index 00000000..8d105171 --- /dev/null +++ b/tests/unit/pleiades/test_mcp/__init__.py @@ -0,0 +1 @@ +"""Tests for pleiades.mcp module.""" diff --git a/tests/unit/pleiades/test_mcp/test_decorators.py b/tests/unit/pleiades/test_mcp/test_decorators.py new file mode 100644 index 00000000..77652d4d --- /dev/null +++ b/tests/unit/pleiades/test_mcp/test_decorators.py @@ -0,0 +1,956 @@ +#!/usr/bin/env python +"""Test suite for pleiades.mcp.decorators module. + +Tests cover: +- Basic decorator registration functionality +- Name handling (default from function name vs. override) +- Description handling (from docstring vs. override) +- Function preservation (decorated functions work identically to originals) +- Parameter descriptions +- Registry access and management +- Type information storage +- Edge cases (class methods, complex type hints, varargs, async) +""" + +import asyncio +from typing import Any, Optional + +import pytest + + +class TestMcpToolDecoratorRegistration: + """Test basic registration functionality of @mcp_tool decorator.""" + + def setup_method(self): + """Clear registry before each test.""" + from pleiades.mcp.decorators import clear_registry + + clear_registry() + + def test_decorator_registers_function(self): + """Decorated function should be added to registry.""" + from pleiades.mcp.decorators import get_registered_tools, mcp_tool + + @mcp_tool() + def example_tool(): + """Example tool.""" + return "result" + + tools = get_registered_tools() + assert "example_tool" in tools + assert tools["example_tool"]["func"] is example_tool + + def test_multiple_functions_can_be_registered(self): + """Multiple decorated functions should all be registered.""" + from pleiades.mcp.decorators import get_registered_tools, mcp_tool + + @mcp_tool() + def tool_one(): + """First tool.""" + pass + + @mcp_tool() + def tool_two(): + """Second tool.""" + pass + + @mcp_tool() + def tool_three(): + """Third tool.""" + pass + + tools = get_registered_tools() + assert len(tools) == 3 + assert "tool_one" in tools + assert "tool_two" in tools + assert "tool_three" in tools + + def test_registry_entry_has_function_reference(self): + """Registry entry should contain reference to the original function.""" + from pleiades.mcp.decorators import get_registered_tools, mcp_tool + + @mcp_tool() + def my_function(): + """My function.""" + return 42 + + tools = get_registered_tools() + registered_func = tools["my_function"]["func"] + + # Calling registered function should produce same result + assert registered_func() == 42 + + +class TestMcpToolNameHandling: + """Test name handling for @mcp_tool decorator.""" + + def setup_method(self): + """Clear registry before each test.""" + from pleiades.mcp.decorators import clear_registry + + clear_registry() + + def test_default_name_uses_function_name(self): + """When no name provided, should use function.__name__.""" + from pleiades.mcp.decorators import get_registered_tools, mcp_tool + + @mcp_tool() + def calculate_sum(): + """Calculate sum.""" + pass + + tools = get_registered_tools() + assert "calculate_sum" in tools + assert tools["calculate_sum"]["name"] == "calculate_sum" + + def test_name_override_uses_provided_name(self): + """When name provided, should use that instead of function name.""" + from pleiades.mcp.decorators import get_registered_tools, mcp_tool + + @mcp_tool(name="custom_name") + def original_name(): + """Function with custom name.""" + pass + + tools = get_registered_tools() + assert "custom_name" in tools + assert tools["custom_name"]["name"] == "custom_name" + # Function name should still be in metadata + assert tools["custom_name"]["func"].__name__ == "original_name" + + def test_duplicate_names_raise_error(self): + """Registering two functions with same name should raise error.""" + from pleiades.mcp.decorators import mcp_tool + + @mcp_tool(name="duplicate") + def first_function(): + """First function.""" + pass + + # Second function with same name should raise + with pytest.raises(ValueError, match="already registered|duplicate"): + + @mcp_tool(name="duplicate") + def second_function(): + """Second function.""" + pass + + def test_duplicate_function_names_raise_error(self): + """Registering two functions with same default name should raise error.""" + from pleiades.mcp.decorators import mcp_tool + + @mcp_tool() + def same_name(): + """First function.""" + pass + + # Second function with same name should raise + with pytest.raises(ValueError, match="already registered|duplicate"): + + @mcp_tool() + def same_name(): # noqa: F811 + """Second function.""" + pass + + +class TestMcpToolDescriptionHandling: + """Test description handling for @mcp_tool decorator.""" + + def setup_method(self): + """Clear registry before each test.""" + from pleiades.mcp.decorators import clear_registry + + clear_registry() + + def test_default_description_uses_docstring(self): + """When no description provided, should extract from docstring.""" + from pleiades.mcp.decorators import get_registered_tools, mcp_tool + + @mcp_tool() + def tool_with_docstring(): + """This is a helpful tool that does things. + + It has multiple lines but we should get the first line or full docstring. + """ + pass + + tools = get_registered_tools() + description = tools["tool_with_docstring"]["description"] + assert description is not None + assert "helpful tool" in description.lower() + + def test_description_override_uses_provided_description(self): + """When description provided, should use that instead of docstring.""" + from pleiades.mcp.decorators import get_registered_tools, mcp_tool + + @mcp_tool(description="Custom description provided") + def tool_with_docstring(): + """This docstring should be ignored.""" + pass + + tools = get_registered_tools() + assert tools["tool_with_docstring"]["description"] == "Custom description provided" + + def test_no_docstring_and_no_override_returns_empty_string(self): + """Function without docstring or description should have empty/None description.""" + from pleiades.mcp.decorators import get_registered_tools, mcp_tool + + @mcp_tool() + def no_docstring(): + pass + + tools = get_registered_tools() + description = tools["no_docstring"]["description"] + assert description is None or description == "" + + def test_multiline_docstring_preserved(self): + """Multiline docstrings should be preserved or properly formatted.""" + from pleiades.mcp.decorators import get_registered_tools, mcp_tool + + @mcp_tool() + def multiline_doc(): + """First line of docstring. + + Second paragraph with more details. + And even more information here. + """ + pass + + tools = get_registered_tools() + description = tools["multiline_doc"]["description"] + # Should contain at least the first line + assert "First line" in description + + +class TestMcpToolFunctionPreservation: + """Test that decorated functions work identically to originals.""" + + def setup_method(self): + """Clear registry before each test.""" + from pleiades.mcp.decorators import clear_registry + + clear_registry() + + def test_decorated_function_returns_same_result(self): + """Decorated function should return same result as undecorated.""" + from pleiades.mcp.decorators import mcp_tool + + @mcp_tool() + def add_numbers(a: int, b: int) -> int: + """Add two numbers.""" + return a + b + + assert add_numbers(5, 3) == 8 + assert add_numbers(-1, 1) == 0 + + def test_decorated_function_accepts_same_arguments(self): + """Decorated function should accept same arguments as original.""" + from pleiades.mcp.decorators import mcp_tool + + @mcp_tool() + def greet(name: str, greeting: str = "Hello") -> str: + """Greet someone.""" + return f"{greeting}, {name}!" + + assert greet("Alice") == "Hello, Alice!" + assert greet("Bob", "Hi") == "Hi, Bob!" + assert greet(name="Charlie", greeting="Hey") == "Hey, Charlie!" + + def test_decorated_function_raises_same_exceptions(self): + """Decorated function should raise same exceptions as original.""" + from pleiades.mcp.decorators import mcp_tool + + @mcp_tool() + def divide(a: float, b: float) -> float: + """Divide two numbers.""" + if b == 0: + raise ValueError("Cannot divide by zero") + return a / b + + assert divide(10, 2) == 5.0 + + with pytest.raises(ValueError, match="Cannot divide by zero"): + divide(10, 0) + + def test_decorated_async_function_works(self): + """Async functions should work when decorated.""" + from pleiades.mcp.decorators import mcp_tool + + @mcp_tool() + async def async_operation(value: int) -> int: + """Async operation.""" + await asyncio.sleep(0.001) # Minimal delay + return value * 2 + + # Test async function + result = asyncio.run(async_operation(5)) + assert result == 10 + + def test_function_with_complex_return_type(self): + """Functions with complex return types should work.""" + from pleiades.mcp.decorators import mcp_tool + + @mcp_tool() + def get_data() -> dict[str, list[int]]: + """Get some data.""" + return {"numbers": [1, 2, 3], "values": [4, 5, 6]} + + result = get_data() + assert isinstance(result, dict) + assert result["numbers"] == [1, 2, 3] + + +class TestMcpToolParameterDescriptions: + """Test parameter descriptions handling.""" + + def setup_method(self): + """Clear registry before each test.""" + from pleiades.mcp.decorators import clear_registry + + clear_registry() + + def test_custom_parameter_descriptions_are_stored(self): + """Custom parameter descriptions should be stored in registry.""" + from pleiades.mcp.decorators import get_registered_tools, mcp_tool + + @mcp_tool( + parameter_descriptions={ + "name": "The name of the person to greet", + "age": "The age of the person", + } + ) + def greet_person(name: str, age: int) -> str: + """Greet a person.""" + return f"Hello {name}, you are {age} years old" + + tools = get_registered_tools() + param_descriptions = tools["greet_person"]["parameter_descriptions"] + + assert param_descriptions is not None + assert param_descriptions["name"] == "The name of the person to greet" + assert param_descriptions["age"] == "The age of the person" + + def test_missing_parameter_descriptions_default_to_empty(self): + """When no parameter_descriptions provided, should be None or empty dict.""" + from pleiades.mcp.decorators import get_registered_tools, mcp_tool + + @mcp_tool() + def simple_function(x: int) -> int: + """Simple function.""" + return x * 2 + + tools = get_registered_tools() + param_descriptions = tools["simple_function"].get("parameter_descriptions") + assert param_descriptions is None or param_descriptions == {} + + def test_partial_parameter_descriptions(self): + """Parameter descriptions can be provided for subset of parameters.""" + from pleiades.mcp.decorators import get_registered_tools, mcp_tool + + @mcp_tool(parameter_descriptions={"important": "This parameter is important"}) + def multi_param(important: str, other: int, another: float) -> str: + """Function with multiple parameters.""" + return f"{important}-{other}-{another}" + + tools = get_registered_tools() + param_descriptions = tools["multi_param"]["parameter_descriptions"] + + assert "important" in param_descriptions + assert param_descriptions["important"] == "This parameter is important" + + +class TestMcpToolRegistryAccess: + """Test registry access and management functions.""" + + def setup_method(self): + """Clear registry before each test.""" + from pleiades.mcp.decorators import clear_registry + + clear_registry() + + def test_get_registered_tools_returns_all_tools(self): + """get_registered_tools should return all registered tools.""" + from pleiades.mcp.decorators import get_registered_tools, mcp_tool + + @mcp_tool() + def tool_a(): + """Tool A.""" + pass + + @mcp_tool() + def tool_b(): + """Tool B.""" + pass + + tools = get_registered_tools() + assert len(tools) == 2 + assert "tool_a" in tools + assert "tool_b" in tools + + def test_get_registered_tools_returns_empty_when_nothing_registered(self): + """get_registered_tools should return empty dict when nothing registered.""" + from pleiades.mcp.decorators import get_registered_tools + + tools = get_registered_tools() + assert isinstance(tools, dict) + assert len(tools) == 0 + + def test_clear_registry_empties_registry(self): + """clear_registry should remove all registered tools.""" + from pleiades.mcp.decorators import clear_registry, get_registered_tools, mcp_tool + + @mcp_tool() + def temporary_tool(): + """Temporary tool.""" + pass + + assert len(get_registered_tools()) == 1 + + clear_registry() + + assert len(get_registered_tools()) == 0 + + def test_registry_entries_have_required_fields(self): + """Each registry entry should have required fields.""" + from pleiades.mcp.decorators import get_registered_tools, mcp_tool + + @mcp_tool() + def complete_tool(param: str) -> str: + """A complete tool.""" + return param + + tools = get_registered_tools() + entry = tools["complete_tool"] + + # Check required fields exist + assert "name" in entry + assert "description" in entry + assert "func" in entry + assert callable(entry["func"]) + + +class TestMcpToolEdgeCases: + """Test edge cases and special scenarios.""" + + def setup_method(self): + """Clear registry before each test.""" + from pleiades.mcp.decorators import clear_registry + + clear_registry() + + def test_classmethod_wrong_order_raises_typeerror(self): + """Decorating classmethod descriptor (wrong order) should raise TypeError.""" + from pleiades.mcp.decorators import mcp_tool + + # Wrong order: @mcp_tool() before @classmethod + # This applies mcp_tool to the classmethod descriptor, which is not callable + with pytest.raises(TypeError, match="must be callable"): + + class _WrongOrderClass: + @mcp_tool() + @classmethod + def wrong_order(cls): + pass + + _ = _WrongOrderClass # Suppress unused variable warning + + def test_classmethod_correct_order_works(self): + """Decorating with correct order (classmethod first) should work.""" + from pleiades.mcp.decorators import get_registered_tools, mcp_tool + + class MyClass: + @classmethod + @mcp_tool() + def correct_order(cls): + """Correct order.""" + return "works" + + # Should be registered and callable via class + tools = get_registered_tools() + assert "correct_order" in tools + assert MyClass.correct_order() == "works" + + def test_static_method_decoration(self): + """Static methods should be decoratable.""" + from pleiades.mcp.decorators import get_registered_tools, mcp_tool + + class MyClass: + @staticmethod + @mcp_tool() + def static_tool(value: int) -> int: + """Static method tool.""" + return value * 2 + + # Should be registered + tools = get_registered_tools() + assert "static_tool" in tools + + # Should still work as static method + assert MyClass.static_tool(5) == 10 + + def test_function_with_complex_type_hints(self): + """Functions with complex type hints should be registered.""" + from pleiades.mcp.decorators import get_registered_tools, mcp_tool + + @mcp_tool() + def complex_types( + items: list[str], + mapping: dict[str, Any], + optional: Optional[int] = None, + ) -> list[dict[str, Any]]: + """Function with complex type hints.""" + return [{"items": items, "mapping": mapping, "optional": optional}] + + tools = get_registered_tools() + assert "complex_types" in tools + + # Function should still work + result = complex_types(["a", "b"], {"key": "value"}, 42) + assert result[0]["items"] == ["a", "b"] + + def test_function_with_default_values(self): + """Functions with default parameter values should work.""" + from pleiades.mcp.decorators import mcp_tool + + @mcp_tool() + def with_defaults( + required: str, + optional: int = 10, + another: str = "default", + ) -> str: + """Function with defaults.""" + return f"{required}-{optional}-{another}" + + # Should work with defaults + assert with_defaults("test") == "test-10-default" + assert with_defaults("test", 20) == "test-20-default" + assert with_defaults("test", 20, "custom") == "test-20-custom" + + def test_function_with_varargs(self): + """Functions with *args should work.""" + from pleiades.mcp.decorators import mcp_tool + + @mcp_tool() + def sum_all(*numbers: int) -> int: + """Sum all numbers.""" + return sum(numbers) + + assert sum_all(1, 2, 3, 4, 5) == 15 + assert sum_all() == 0 + + def test_function_with_kwargs(self): + """Functions with **kwargs should work.""" + from pleiades.mcp.decorators import mcp_tool + + @mcp_tool() + def build_dict(**kwargs: Any) -> dict[str, Any]: + """Build a dictionary.""" + return kwargs + + result = build_dict(a=1, b=2, c=3) + assert result == {"a": 1, "b": 2, "c": 3} + + def test_function_with_no_type_hints(self): + """Functions without type hints should still work.""" + from pleiades.mcp.decorators import mcp_tool + + @mcp_tool() + def no_hints(x, y): + """Function without type hints.""" + return x + y + + assert no_hints(5, 3) == 8 + assert no_hints("hello", "world") == "helloworld" + + +class TestMcpToolTypeInformationStorage: + """Test that type information is stored for schema generation.""" + + def setup_method(self): + """Clear registry before each test.""" + from pleiades.mcp.decorators import clear_registry + + clear_registry() + + def test_type_hints_are_accessible(self): + """Registry should preserve function's type hints.""" + from pleiades.mcp.decorators import get_registered_tools, mcp_tool + + @mcp_tool() + def typed_function(name: str, age: int) -> str: + """Function with type hints.""" + return f"{name} is {age}" + + tools = get_registered_tools() + func = tools["typed_function"]["func"] + + # Type hints should be accessible via __annotations__ + assert hasattr(func, "__annotations__") + assert func.__annotations__["name"] is str + assert func.__annotations__["age"] is int + assert func.__annotations__["return"] is str + + def test_functions_without_type_hints_handled_gracefully(self): + """Functions without type hints should not cause errors.""" + from pleiades.mcp.decorators import get_registered_tools, mcp_tool + + @mcp_tool() + def no_types(x, y): + """No type hints.""" + return x + y + + tools = get_registered_tools() + func = tools["no_types"]["func"] + + # Should have empty or minimal annotations + annotations = getattr(func, "__annotations__", {}) + assert isinstance(annotations, dict) + + def test_optional_type_hints_preserved(self): + """Optional type hints should be preserved correctly.""" + from pleiades.mcp.decorators import get_registered_tools, mcp_tool + + @mcp_tool() + def optional_params( + required: str, + optional: Optional[int] = None, + ) -> Optional[str]: + """Function with optional types.""" + if optional is not None: + return f"{required}-{optional}" + return required + + tools = get_registered_tools() + func = tools["optional_params"]["func"] + + # Check annotations are preserved + assert "required" in func.__annotations__ + assert "optional" in func.__annotations__ + assert "return" in func.__annotations__ + + +class TestMcpToolIntegrationScenarios: + """Test realistic integration scenarios.""" + + def setup_method(self): + """Clear registry before each test.""" + from pleiades.mcp.decorators import clear_registry + + clear_registry() + + def test_multiple_tools_with_mixed_configurations(self): + """Multiple tools with different configurations should coexist.""" + from pleiades.mcp.decorators import get_registered_tools, mcp_tool + + @mcp_tool() + def simple_tool(): + """Simple tool with defaults.""" + return "simple" + + @mcp_tool(name="custom", description="Custom description") + def another_tool(): + """This docstring is overridden.""" + return "custom" + + @mcp_tool( + parameter_descriptions={ + "x": "First number", + "y": "Second number", + } + ) + def math_tool(x: int, y: int) -> int: + """Perform math.""" + return x + y + + tools = get_registered_tools() + assert len(tools) == 3 + + # Verify each tool's configuration + assert tools["simple_tool"]["name"] == "simple_tool" + assert "Simple tool" in tools["simple_tool"]["description"] + + assert tools["custom"]["name"] == "custom" + assert tools["custom"]["description"] == "Custom description" + + assert tools["math_tool"]["name"] == "math_tool" + assert tools["math_tool"]["parameter_descriptions"]["x"] == "First number" + + def test_tool_can_be_called_after_registration(self): + """Registered tools should remain callable.""" + from pleiades.mcp.decorators import get_registered_tools, mcp_tool + + @mcp_tool() + def calculate(x: int, y: int) -> int: + """Calculate something.""" + return x * y + x - y + + # Direct call + assert calculate(5, 3) == 17 + + # Call via registry + tools = get_registered_tools() + registered_func = tools["calculate"]["func"] + assert registered_func(5, 3) == 17 + + +class TestMcpToolInputValidation: + """Test input validation for decorator arguments.""" + + def setup_method(self): + """Clear registry before each test.""" + from pleiades.mcp.decorators import clear_registry + + clear_registry() + + def test_non_callable_argument_raises_typeerror(self): + """Passing a non-callable as func argument should raise TypeError.""" + from pleiades.mcp.decorators import mcp_tool + + # Using mcp_tool(func=...) with non-callable should fail + with pytest.raises(TypeError, match="must be callable"): + mcp_tool(func=123) + + def test_non_callable_string_raises_typeerror(self): + """Passing a string as func argument should raise TypeError.""" + from pleiades.mcp.decorators import mcp_tool + + with pytest.raises(TypeError, match="must be callable"): + mcp_tool(func="not_a_function") + + def test_name_with_newline_raises_valueerror(self): + """Tool name with newline should raise ValueError.""" + from pleiades.mcp.decorators import mcp_tool + + with pytest.raises(ValueError, match="invalid character|alphanumeric"): + + @mcp_tool(name="tool\nname") + def tool(): + pass + + def test_name_with_null_byte_raises_valueerror(self): + """Tool name with null byte should raise ValueError.""" + from pleiades.mcp.decorators import mcp_tool + + with pytest.raises(ValueError, match="invalid character|alphanumeric"): + + @mcp_tool(name="tool\x00name") + def tool(): + pass + + def test_name_with_space_raises_valueerror(self): + """Tool name with space should raise ValueError.""" + from pleiades.mcp.decorators import mcp_tool + + with pytest.raises(ValueError, match="invalid character|alphanumeric"): + + @mcp_tool(name="tool name") + def tool(): + pass + + def test_valid_name_with_underscore_and_hyphen(self): + """Tool name with underscore and hyphen should be valid.""" + from pleiades.mcp.decorators import get_registered_tools, mcp_tool + + @mcp_tool(name="my_tool-v2") + def tool(): + pass + + tools = get_registered_tools() + assert "my_tool-v2" in tools + + def test_empty_parameter_descriptions_key_raises_valueerror(self): + """Empty string key in parameter_descriptions should raise ValueError.""" + from pleiades.mcp.decorators import mcp_tool + + with pytest.raises(ValueError, match="empty|invalid"): + + @mcp_tool(parameter_descriptions={"": "description"}) + def tool(): + pass + + def test_whitespace_parameter_descriptions_key_raises_valueerror(self): + """Whitespace-only key in parameter_descriptions should raise ValueError.""" + from pleiades.mcp.decorators import mcp_tool + + with pytest.raises(ValueError, match="empty|invalid"): + + @mcp_tool(parameter_descriptions={" ": "description"}) + def tool(): + pass + + def test_invalid_name_type_raises_typeerror(self): + """Non-string name should raise TypeError.""" + from pleiades.mcp.decorators import mcp_tool + + with pytest.raises(TypeError, match="name must be str"): + + @mcp_tool(name=123) + def tool(): + pass + + def test_empty_name_raises_valueerror(self): + """Empty string name should raise ValueError.""" + from pleiades.mcp.decorators import mcp_tool + + with pytest.raises(ValueError, match="cannot be empty"): + + @mcp_tool(name="") + def tool(): + pass + + def test_whitespace_name_raises_valueerror(self): + """Whitespace-only name should raise ValueError.""" + from pleiades.mcp.decorators import mcp_tool + + with pytest.raises(ValueError, match="cannot be empty"): + + @mcp_tool(name=" ") + def tool(): + pass + + def test_invalid_description_type_raises_typeerror(self): + """Non-string description should raise TypeError.""" + from pleiades.mcp.decorators import mcp_tool + + with pytest.raises(TypeError, match="description must be str"): + + @mcp_tool(description=["list"]) + def tool(): + pass + + def test_invalid_parameter_descriptions_type_raises_typeerror(self): + """Non-dict parameter_descriptions should raise TypeError.""" + from pleiades.mcp.decorators import mcp_tool + + with pytest.raises(TypeError, match="parameter_descriptions must be dict"): + + @mcp_tool(parameter_descriptions="string") + def tool(): + pass + + def test_invalid_parameter_descriptions_key_type_raises_typeerror(self): + """Non-string keys in parameter_descriptions should raise TypeError.""" + from pleiades.mcp.decorators import mcp_tool + + with pytest.raises(TypeError, match="keys must be str"): + + @mcp_tool(parameter_descriptions={123: "value"}) + def tool(): + pass + + def test_invalid_parameter_descriptions_value_type_raises_typeerror(self): + """Non-string values in parameter_descriptions should raise TypeError.""" + from pleiades.mcp.decorators import mcp_tool + + with pytest.raises(TypeError, match="values must be str"): + + @mcp_tool(parameter_descriptions={"key": 123}) + def tool(): + pass + + +class TestMcpToolRegistryMutationProtection: + """Test that registry is protected from external mutation.""" + + def setup_method(self): + """Clear registry before each test.""" + from pleiades.mcp.decorators import clear_registry + + clear_registry() + + def test_registry_mutation_does_not_affect_internal_state(self): + """Mutating returned registry should not affect internal registry.""" + from pleiades.mcp.decorators import get_registered_tools, mcp_tool + + @mcp_tool() + def original_tool(): + """Original description.""" + return 42 + + # Get registry and mutate it + tools = get_registered_tools() + tools["original_tool"]["description"] = "HACKED" + tools["original_tool"]["name"] = "HACKED_NAME" + + # Get registry again - should be unchanged + fresh_tools = get_registered_tools() + assert fresh_tools["original_tool"]["description"] == "Original description." + assert fresh_tools["original_tool"]["name"] == "original_tool" + + def test_adding_to_returned_registry_does_not_affect_internal(self): + """Adding entries to returned registry should not affect internal registry.""" + from pleiades.mcp.decorators import get_registered_tools, mcp_tool + + @mcp_tool() + def real_tool(): + """Real tool.""" + pass + + # Get registry and add fake entry + tools = get_registered_tools() + tools["fake_tool"] = {"name": "fake", "description": "fake", "func": lambda: None} + + # Get registry again - should not have fake tool + fresh_tools = get_registered_tools() + assert "fake_tool" not in fresh_tools + assert len(fresh_tools) == 1 + + def test_deleting_from_returned_registry_does_not_affect_internal(self): + """Deleting entries from returned registry should not affect internal registry.""" + from pleiades.mcp.decorators import get_registered_tools, mcp_tool + + @mcp_tool() + def tool_to_keep(): + """Keep me.""" + pass + + # Get registry and delete + tools = get_registered_tools() + del tools["tool_to_keep"] + + # Get registry again - should still have tool + fresh_tools = get_registered_tools() + assert "tool_to_keep" in fresh_tools + + +class TestMcpToolDecoratorWithoutParentheses: + """Test decorator behavior when used without parentheses.""" + + def setup_method(self): + """Clear registry before each test.""" + from pleiades.mcp.decorators import clear_registry + + clear_registry() + + def test_decorator_without_parentheses_works(self): + """@mcp_tool without () should work and register the tool.""" + from pleiades.mcp.decorators import get_registered_tools, mcp_tool + + @mcp_tool + def tool_without_parens(): + """Tool without parentheses.""" + return "works" + + # Should be registered + tools = get_registered_tools() + assert "tool_without_parens" in tools + + # Should be callable + assert tool_without_parens() == "works" + + def test_decorator_without_parentheses_uses_defaults(self): + """@mcp_tool without () should use function name and docstring.""" + from pleiades.mcp.decorators import get_registered_tools, mcp_tool + + @mcp_tool + def my_function(): + """My docstring.""" + pass + + tools = get_registered_tools() + assert tools["my_function"]["name"] == "my_function" + assert "My docstring" in tools["my_function"]["description"] + + +if __name__ == "__main__": + pytest.main(["-v", __file__]) diff --git a/tests/unit/pleiades/test_mcp/test_init.py b/tests/unit/pleiades/test_mcp/test_init.py new file mode 100644 index 00000000..416e1e21 --- /dev/null +++ b/tests/unit/pleiades/test_mcp/test_init.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python +"""Test suite for pleiades.mcp module initialization. + +Tests cover: +- MCP availability detection +- Graceful degradation when MCP dependencies are missing +- Conditional exports +- Error message clarity +""" + +import pytest + +# Detect fastmcp availability at module load (correct pattern) +try: + import fastmcp # noqa: F401 + + HAS_FASTMCP = True +except ImportError: + HAS_FASTMCP = False + + +class TestMCPAvailability: + """Test MCP availability detection and reporting.""" + + def test_mcp_available_flag_is_boolean(self): + """MCP_AVAILABLE should be a boolean.""" + from pleiades.mcp import MCP_AVAILABLE + + assert isinstance(MCP_AVAILABLE, bool) + + def test_mcp_available_matches_fastmcp_installed(self): + """MCP_AVAILABLE should match whether fastmcp is installed.""" + from pleiades.mcp import MCP_AVAILABLE + + assert MCP_AVAILABLE == HAS_FASTMCP + + @pytest.mark.skipif(not HAS_FASTMCP, reason="fastmcp not installed") + def test_fastmcp_exported_when_available(self): + """FastMCP should be importable when available.""" + from pleiades.mcp import FastMCP + + assert FastMCP is not None + + +class TestCheckMCPAvailable: + """Test check_mcp_available function.""" + + def test_check_mcp_available_exists(self): + """check_mcp_available should be importable.""" + from pleiades.mcp import check_mcp_available + + assert callable(check_mcp_available) + + @pytest.mark.skipif(not HAS_FASTMCP, reason="fastmcp not installed") + def test_check_mcp_available_does_not_raise_when_installed(self): + """check_mcp_available should not raise when MCP is installed.""" + from pleiades.mcp import check_mcp_available + + # Should not raise + check_mcp_available() + + @pytest.mark.skipif(HAS_FASTMCP, reason="fastmcp is installed") + def test_check_mcp_available_raises_when_not_installed(self): + """check_mcp_available should raise ImportError when MCP is not installed.""" + from pleiades.mcp import check_mcp_available + + with pytest.raises(ImportError, match="MCP dependencies not installed"): + check_mcp_available() + + +class TestServerModule: + """Test server module components.""" + + @pytest.mark.skipif(not HAS_FASTMCP, reason="fastmcp not installed") + def test_get_server_returns_fastmcp_instance(self): + """get_server should return a FastMCP instance.""" + from pleiades.mcp.server import get_server + + server = get_server() + assert server is not None + assert server.name == "pleiades-mcp" + + @pytest.mark.skipif(not HAS_FASTMCP, reason="fastmcp not installed") + def test_get_server_returns_same_instance(self): + """get_server should return the same instance on multiple calls.""" + from pleiades.mcp.server import get_server + + server1 = get_server() + server2 = get_server() + assert server1 is server2 + + def test_main_function_is_callable(self): + """main function should be importable and callable.""" + from pleiades.mcp.server import main + + assert callable(main) + + +class TestModuleEntry: + """Test module entry point (__main__).""" + + def test_run_function_exists(self): + """run function should be importable from __main__.""" + from pleiades.mcp.__main__ import run + + assert callable(run) + + +if __name__ == "__main__": + pytest.main(["-v", __file__]) diff --git a/tests/unit/pleiades/test_mcp/test_manifest.py b/tests/unit/pleiades/test_mcp/test_manifest.py new file mode 100644 index 00000000..800e5401 --- /dev/null +++ b/tests/unit/pleiades/test_mcp/test_manifest.py @@ -0,0 +1,722 @@ +"""Test suite for manifest parsing functionality. + +Tests cover YAML frontmatter parsing, field validation, and material properties +extraction used by MCP tools for resonance dataset analysis. + +These tests verify the manifest parsing exposed through MCP workflows. +""" + +import pytest + +from pleiades.workflows import extract_manifest + + +class TestManifestFileDiscovery: + """Test manifest file discovery with different naming conventions.""" + + def test_finds_manifest_intermediate_md(self, tmp_path): + """Should find manifest_intermediate.md first.""" + manifest_file = tmp_path / "manifest_intermediate.md" + manifest_file.write_text( + """--- +name: test +description: test dataset +version: 1.0.0 +created: 2024-01-01 +--- +Body content +""" + ) + + result = extract_manifest(tmp_path) + + assert result is not None + assert result.name == "test" + + def test_finds_smcp_manifest_md(self, tmp_path): + """Should find smcp_manifest.md as second priority.""" + manifest_file = tmp_path / "smcp_manifest.md" + manifest_file.write_text( + """--- +name: smcp_test +description: test +version: 1.0.0 +created: 2024-01-01 +--- +Body +""" + ) + + result = extract_manifest(tmp_path) + + assert result is not None + assert result.name == "smcp_test" + + def test_finds_manifest_md(self, tmp_path): + """Should find manifest.md as third priority.""" + manifest_file = tmp_path / "manifest.md" + manifest_file.write_text( + """--- +name: plain_manifest +description: test +version: 1.0.0 +created: 2024-01-01 +--- +Body +""" + ) + + result = extract_manifest(tmp_path) + + assert result is not None + assert result.name == "plain_manifest" + + def test_priority_order(self, tmp_path): + """Should prefer manifest_intermediate.md over others.""" + # Create all three variants + (tmp_path / "manifest.md").write_text( + """--- +name: lowest_priority +description: test +version: 1.0.0 +created: 2024-01-01 +--- +Body +""" + ) + (tmp_path / "smcp_manifest.md").write_text( + """--- +name: medium_priority +description: test +version: 1.0.0 +created: 2024-01-01 +--- +Body +""" + ) + (tmp_path / "manifest_intermediate.md").write_text( + """--- +name: highest_priority +description: test +version: 1.0.0 +created: 2024-01-01 +--- +Body +""" + ) + + result = extract_manifest(tmp_path) + + assert result is not None + assert result.name == "highest_priority" + + def test_returns_none_when_no_manifest(self, tmp_path): + """Should return None when no manifest file exists.""" + result = extract_manifest(tmp_path) + + assert result is None + + def test_handles_string_path(self, tmp_path): + """Should accept string path in addition to Path object.""" + manifest_file = tmp_path / "manifest.md" + manifest_file.write_text( + """--- +name: string_path_test +description: test +version: 1.0.0 +created: 2024-01-01 +--- +Body +""" + ) + + result = extract_manifest(str(tmp_path)) + + assert result is not None + assert result.name == "string_path_test" + + +class TestYamlFrontmatterParsing: + """Test YAML frontmatter parsing.""" + + def test_parses_basic_frontmatter(self, tmp_path): + """Should parse basic YAML frontmatter correctly.""" + manifest_file = tmp_path / "manifest.md" + manifest_file.write_text( + """--- +name: Au197_sample +description: Gold-197 resonance dataset +version: 2.1.0 +created: 2024-06-15T10:30:00 +facility: SNS +beamline: VENUS +--- +# Sample Analysis + +This is the body content. +""" + ) + + result = extract_manifest(tmp_path) + + assert result is not None + assert result.name == "Au197_sample" + assert result.description == "Gold-197 resonance dataset" + assert result.version == "2.1.0" + assert result.facility == "SNS" + assert result.beamline == "VENUS" + + def test_handles_multiline_description(self, tmp_path): + """Should handle multiline YAML values.""" + manifest_file = tmp_path / "manifest.md" + manifest_file.write_text( + """--- +name: test +description: > + This is a very long description + that spans multiple lines. +version: 1.0.0 +created: 2024-01-01 +--- +Body +""" + ) + + result = extract_manifest(tmp_path) + + assert result is not None + assert "long description" in result.description + assert "multiple lines" in result.description + + def test_handles_yaml_list(self, tmp_path): + """Should handle YAML lists in frontmatter.""" + manifest_file = tmp_path / "manifest.md" + manifest_file.write_text( + """--- +name: test +description: test +version: 1.0.0 +created: 2024-01-01 +isotopes: + - Au-197 + - Ag-107 +--- +Body +""" + ) + + result = extract_manifest(tmp_path) + + assert result is not None + assert result.raw_frontmatter.get("isotopes") == ["Au-197", "Ag-107"] + + def test_preserves_raw_frontmatter(self, tmp_path): + """Should preserve all raw frontmatter data.""" + manifest_file = tmp_path / "manifest.md" + manifest_file.write_text( + """--- +name: test +description: test +version: 1.0.0 +created: 2024-01-01 +custom_field: custom_value +nested: + key: value +--- +Body +""" + ) + + result = extract_manifest(tmp_path) + + assert result is not None + assert result.raw_frontmatter["custom_field"] == "custom_value" + assert result.raw_frontmatter["nested"]["key"] == "value" + + def test_raises_for_missing_frontmatter_delimiters(self, tmp_path): + """Should raise ValueError for missing YAML delimiters.""" + manifest_file = tmp_path / "manifest.md" + manifest_file.write_text( + """name: test +description: test +version: 1.0.0 +created: 2024-01-01 + +Body without proper delimiters +""" + ) + + with pytest.raises(ValueError, match="missing YAML frontmatter"): + extract_manifest(tmp_path) + + def test_handles_empty_frontmatter(self, tmp_path): + """Should handle empty YAML frontmatter gracefully.""" + manifest_file = tmp_path / "manifest.md" + manifest_file.write_text( + """--- +--- +Body only, no frontmatter content +""" + ) + + result = extract_manifest(tmp_path) + + assert result is not None + assert result.name == "unknown" # Default value + assert result.description == "" # Default value + + def test_handles_horizontal_rule_in_body(self, tmp_path): + """Should handle --- in body without breaking parsing.""" + manifest_file = tmp_path / "manifest.md" + manifest_file.write_text( + """--- +name: test +description: test +version: 1.0.0 +created: 2024-01-01 +--- +# Body Content + +Some text here. + +--- + +More text after horizontal rule. + +--- + +Even more text. +""" + ) + + result = extract_manifest(tmp_path) + + assert result is not None + assert "horizontal rule" in result.body + assert "Even more text" in result.body + + +class TestFieldValidation: + """Test validation and defaults for manifest fields.""" + + def test_uses_defaults_for_missing_required_fields(self, tmp_path): + """Should use defaults when required fields are missing.""" + manifest_file = tmp_path / "manifest.md" + manifest_file.write_text( + """--- +facility: SNS +--- +Body +""" + ) + + result = extract_manifest(tmp_path) + + assert result is not None + assert result.name == "unknown" + assert result.description == "" + assert result.version == "1.0.0" + assert result.created == "" + + def test_optional_fields_default_to_none(self, tmp_path): + """Should default optional fields to None.""" + manifest_file = tmp_path / "manifest.md" + manifest_file.write_text( + """--- +name: test +description: test +version: 1.0.0 +created: 2024-01-01 +--- +Body +""" + ) + + result = extract_manifest(tmp_path) + + assert result is not None + assert result.facility is None + assert result.beamline is None + assert result.detector is None + assert result.sample_id is None + assert result.isotope is None + assert result.material_properties is None + + def test_handles_all_optional_fields(self, tmp_path): + """Should handle all optional fields when present.""" + manifest_file = tmp_path / "manifest.md" + manifest_file.write_text( + """--- +name: complete_manifest +description: Full manifest +version: 1.0.0 +created: 2024-01-01 +facility: SNS +beamline: VENUS +detector: MCP +sample_id: SAMPLE-001 +isotope: Au-197 +--- +Body +""" + ) + + result = extract_manifest(tmp_path) + + assert result is not None + assert result.facility == "SNS" + assert result.beamline == "VENUS" + assert result.detector == "MCP" + assert result.sample_id == "SAMPLE-001" + assert result.isotope == "Au-197" + + +class TestDatetimeConversion: + """Test datetime handling in manifest parsing.""" + + def test_converts_datetime_to_isoformat(self, tmp_path): + """Should convert datetime objects to ISO format strings.""" + manifest_file = tmp_path / "manifest.md" + # PyYAML auto-parses ISO timestamps as datetime objects + manifest_file.write_text( + """--- +name: test +description: test +version: 1.0.0 +created: 2024-06-15T10:30:45 +--- +Body +""" + ) + + result = extract_manifest(tmp_path) + + assert result is not None + assert isinstance(result.created, str) + assert "2024-06-15" in result.created + + def test_handles_date_only(self, tmp_path): + """Should handle date-only values.""" + manifest_file = tmp_path / "manifest.md" + manifest_file.write_text( + """--- +name: test +description: test +version: 1.0.0 +created: 2024-06-15 +--- +Body +""" + ) + + result = extract_manifest(tmp_path) + + assert result is not None + assert "2024-06-15" in result.created + + def test_handles_string_timestamp(self, tmp_path): + """Should preserve string timestamps as-is.""" + manifest_file = tmp_path / "manifest.md" + manifest_file.write_text( + """--- +name: test +description: test +version: 1.0.0 +created: "2024-06-15T10:30:45Z" +--- +Body +""" + ) + + result = extract_manifest(tmp_path) + + assert result is not None + assert result.created == "2024-06-15T10:30:45Z" + + +class TestMaterialPropertiesExtraction: + """Test material properties parsing.""" + + def test_extracts_material_properties(self, tmp_path): + """Should extract material_properties section.""" + manifest_file = tmp_path / "manifest.md" + manifest_file.write_text( + """--- +name: test +description: test +version: 1.0.0 +created: 2024-01-01 +material_properties: + density_g_cm3: 19.32 + atomic_mass_amu: 196.97 + temperature_k: 300.0 +--- +Body +""" + ) + + result = extract_manifest(tmp_path) + + assert result is not None + assert result.material_properties is not None + assert result.material_properties.density_g_cm3 == 19.32 + assert result.material_properties.atomic_mass_amu == 196.97 + assert result.material_properties.temperature_k == 300.0 + + def test_handles_missing_optional_temperature(self, tmp_path): + """Should handle missing temperature_k (optional field).""" + manifest_file = tmp_path / "manifest.md" + manifest_file.write_text( + """--- +name: test +description: test +version: 1.0.0 +created: 2024-01-01 +material_properties: + density_g_cm3: 19.32 + atomic_mass_amu: 196.97 +--- +Body +""" + ) + + result = extract_manifest(tmp_path) + + assert result is not None + assert result.material_properties is not None + assert result.material_properties.temperature_k is None + + def test_handles_empty_material_properties(self, tmp_path): + """Should handle empty material_properties section.""" + manifest_file = tmp_path / "manifest.md" + manifest_file.write_text( + """--- +name: test +description: test +version: 1.0.0 +created: 2024-01-01 +material_properties: +--- +Body +""" + ) + + result = extract_manifest(tmp_path) + + assert result is not None + assert result.material_properties is None + + def test_handles_invalid_material_properties_gracefully(self, tmp_path): + """Should log warning but not fail for invalid material properties.""" + manifest_file = tmp_path / "manifest.md" + manifest_file.write_text( + """--- +name: test +description: test +version: 1.0.0 +created: 2024-01-01 +material_properties: + density_g_cm3: invalid + atomic_mass_amu: also_invalid +--- +Body +""" + ) + + # Should not raise - invalid material properties are logged but not fatal + result = extract_manifest(tmp_path) + + assert result is not None + assert result.material_properties is None + + +class TestBodyExtraction: + """Test markdown body extraction.""" + + def test_extracts_body_content(self, tmp_path): + """Should extract markdown body after frontmatter.""" + manifest_file = tmp_path / "manifest.md" + manifest_file.write_text( + """--- +name: test +description: test +version: 1.0.0 +created: 2024-01-01 +--- +# Analysis Results + +This is the **body** content. + +- Item 1 +- Item 2 +""" + ) + + result = extract_manifest(tmp_path) + + assert result is not None + assert "# Analysis Results" in result.body + assert "**body**" in result.body + assert "Item 1" in result.body + + def test_strips_body_whitespace(self, tmp_path): + """Should strip leading/trailing whitespace from body.""" + manifest_file = tmp_path / "manifest.md" + manifest_file.write_text( + """--- +name: test +description: test +version: 1.0.0 +created: 2024-01-01 +--- + + +Body with extra whitespace + + +""" + ) + + result = extract_manifest(tmp_path) + + assert result is not None + assert result.body == "Body with extra whitespace" + + def test_handles_empty_body(self, tmp_path): + """Should handle empty body after frontmatter.""" + manifest_file = tmp_path / "manifest.md" + manifest_file.write_text( + """--- +name: test +description: test +version: 1.0.0 +created: 2024-01-01 +--- +""" + ) + + result = extract_manifest(tmp_path) + + assert result is not None + assert result.body == "" + + +class TestErrorHandling: + """Test error handling in manifest parsing.""" + + def test_handles_unreadable_file(self, tmp_path): + """Should raise appropriate error for unreadable files.""" + manifest_file = tmp_path / "manifest.md" + manifest_file.write_text("content") + manifest_file.chmod(0o000) + + try: + with pytest.raises(PermissionError): + extract_manifest(tmp_path) + finally: + manifest_file.chmod(0o644) # Restore permissions for cleanup + + def test_handles_invalid_yaml_syntax(self, tmp_path): + """Should raise error for invalid YAML syntax.""" + import yaml + + manifest_file = tmp_path / "manifest.md" + manifest_file.write_text( + """--- +name: test +description: [invalid yaml + missing closing bracket +version: 1.0.0 +--- +Body +""" + ) + + with pytest.raises(yaml.YAMLError): + extract_manifest(tmp_path) + + def test_handles_binary_content(self, tmp_path): + """Should handle files with binary content gracefully.""" + manifest_file = tmp_path / "manifest.md" + manifest_file.write_bytes(b"\x00\x01\x02---\nname: test\n---\nBody") + + # Binary content should be handled gracefully - returns result without crashing. + # The binary prefix may or may not corrupt the YAML parsing depending on + # encoding handling, but the function should not raise. + result = extract_manifest(tmp_path) + assert result is not None + # Verify we got a ManifestData object back + assert hasattr(result, "name") + assert hasattr(result, "body") + + +class TestMcpToolIntegration: + """Test manifest parsing through MCP tool interface.""" + + def test_mcp_tool_uses_extract_manifest(self, tmp_path): + """Verify MCP tool wrapper calls extract_manifest correctly.""" + from pleiades.mcp.tools import extract_resonance_manifest + + manifest_file = tmp_path / "manifest.md" + manifest_file.write_text( + """--- +name: mcp_test +description: MCP integration test +version: 1.0.0 +created: 2024-01-01 +isotope: Au-197 +--- +Body content +""" + ) + + result = extract_resonance_manifest(str(tmp_path)) + + assert result["status"] == "success" + assert result["data"]["name"] == "mcp_test" + assert result["data"]["isotope"] == "Au-197" + + def test_mcp_tool_returns_error_for_missing_manifest(self, tmp_path): + """MCP tool should return error status for missing manifest.""" + from pleiades.mcp.tools import extract_resonance_manifest + + result = extract_resonance_manifest(str(tmp_path)) + + assert result["status"] == "error" + assert "No manifest found" in result["error"] + + def test_mcp_tool_returns_json_serializable_result(self, tmp_path): + """MCP tool should return JSON-serializable result.""" + import json + + from pleiades.mcp.tools import extract_resonance_manifest + + manifest_file = tmp_path / "manifest.md" + manifest_file.write_text( + """--- +name: json_test +description: JSON serialization test +version: 1.0.0 +created: 2024-06-15T10:30:00 +material_properties: + density_g_cm3: 19.32 + atomic_mass_amu: 196.97 +--- +Body +""" + ) + + result = extract_resonance_manifest(str(tmp_path)) + + # Should be JSON-serializable + json_str = json.dumps(result) + assert isinstance(json_str, str) + + # Verify structure + parsed = json.loads(json_str) + assert parsed["status"] == "success" + assert "data" in parsed + + +if __name__ == "__main__": + pytest.main(["-v", __file__]) diff --git a/tests/unit/pleiades/test_mcp/test_server.py b/tests/unit/pleiades/test_mcp/test_server.py new file mode 100644 index 00000000..35049f65 --- /dev/null +++ b/tests/unit/pleiades/test_mcp/test_server.py @@ -0,0 +1,911 @@ +#!/usr/bin/env python +"""Test suite for pleiades.mcp.server module. + +Tests cover: +- Tool discovery from decorator registry +- Tool registration with FastMCP server +- JSON schema generation from function signatures +- Server initialization and management +- Error handling and edge cases + +These tests define the expected API for Issue #166 (MCP server with auto-discovery). +They are written BEFORE implementation to guide the development process. +""" + +import importlib.util +import inspect +from typing import Any, Optional +from unittest.mock import MagicMock, patch + +import pytest + +# Detect fastmcp availability at module load +HAS_FASTMCP = importlib.util.find_spec("fastmcp") is not None + + +class TestToolDiscovery: + """Test discover_tools() function that reads from decorator registry.""" + + def setup_method(self): + """Clear registry before each test.""" + from pleiades.mcp.decorators import clear_registry + + clear_registry() + + def test_discover_tools_returns_empty_dict_when_no_tools_registered(self): + """discover_tools() should return empty dict when registry is empty.""" + from pleiades.mcp.server import discover_tools + + tools = discover_tools() + assert isinstance(tools, dict) + assert len(tools) == 0 + + def test_discover_tools_finds_single_registered_tool(self): + """discover_tools() should find tools registered with @mcp_tool.""" + from pleiades.mcp.decorators import mcp_tool + from pleiades.mcp.server import discover_tools + + @mcp_tool() + def example_tool(path: str) -> dict: + """Validate a dataset.""" + return {"valid": True} + + tools = discover_tools() + assert "example_tool" in tools + assert tools["example_tool"]["func"] is example_tool + + def test_discover_tools_finds_multiple_registered_tools(self): + """discover_tools() should find all tools in the registry.""" + from pleiades.mcp.decorators import mcp_tool + from pleiades.mcp.server import discover_tools + + @mcp_tool() + def tool_one(): + """First tool.""" + pass + + @mcp_tool() + def tool_two(): + """Second tool.""" + pass + + @mcp_tool() + def tool_three(): + """Third tool.""" + pass + + tools = discover_tools() + assert len(tools) == 3 + assert "tool_one" in tools + assert "tool_two" in tools + assert "tool_three" in tools + + def test_discover_tools_returns_correct_metadata(self): + """discover_tools() should return complete tool metadata.""" + from pleiades.mcp.decorators import mcp_tool + from pleiades.mcp.server import discover_tools + + @mcp_tool( + name="custom_name", + description="Custom description", + parameter_descriptions={ + "x": "First parameter", + "y": "Second parameter", + }, + ) + def my_function(x: int, y: str) -> bool: + """Original docstring.""" + return True + + tools = discover_tools() + tool_info = tools["custom_name"] + + assert tool_info["name"] == "custom_name" + assert tool_info["description"] == "Custom description" + assert tool_info["func"] is my_function + assert tool_info["parameter_descriptions"]["x"] == "First parameter" + assert tool_info["parameter_descriptions"]["y"] == "Second parameter" + + def test_discover_tools_includes_function_without_type_hints(self): + """discover_tools() should handle functions without type hints.""" + from pleiades.mcp.decorators import mcp_tool + from pleiades.mcp.server import discover_tools + + @mcp_tool() + def no_hints(x, y): + """Function without type hints.""" + return x + y + + tools = discover_tools() + assert "no_hints" in tools + assert tools["no_hints"]["func"] is no_hints + + def test_discover_tools_preserves_function_callable(self): + """Discovered functions should remain callable.""" + from pleiades.mcp.decorators import mcp_tool + from pleiades.mcp.server import discover_tools + + @mcp_tool() + def add(a: int, b: int) -> int: + """Add two numbers.""" + return a + b + + tools = discover_tools() + discovered_func = tools["add"]["func"] + + # Should be callable and produce correct result + assert discovered_func(5, 3) == 8 + + +class TestToolRegistration: + """Test register_tools() function that registers tools with FastMCP.""" + + def setup_method(self): + """Clear registry before each test.""" + from pleiades.mcp.decorators import clear_registry + + clear_registry() + + @pytest.mark.skipif(not HAS_FASTMCP, reason="fastmcp not installed") + def test_register_tools_registers_single_tool(self): + """register_tools() should register tools with FastMCP server.""" + from pleiades.mcp.decorators import mcp_tool + from pleiades.mcp.server import register_tools + + @mcp_tool() + def example_tool(path: str) -> dict: + """Validate a dataset.""" + return {"valid": True} + + # Create mock server + mock_server = MagicMock() + + # Register tools + register_tools(mock_server) + + # Verify server.tool() decorator was called + assert mock_server.tool.called + # Should have been called at least once + assert mock_server.tool.call_count >= 1 + + @pytest.mark.skipif(not HAS_FASTMCP, reason="fastmcp not installed") + def test_register_tools_registers_multiple_tools(self): + """register_tools() should register all discovered tools.""" + from pleiades.mcp.decorators import mcp_tool + from pleiades.mcp.server import register_tools + + @mcp_tool() + def tool_a(): + """Tool A.""" + pass + + @mcp_tool() + def tool_b(): + """Tool B.""" + pass + + @mcp_tool() + def tool_c(): + """Tool C.""" + pass + + mock_server = MagicMock() + register_tools(mock_server) + + # Should register all 3 tools + assert mock_server.tool.call_count >= 3 + + @pytest.mark.skipif(not HAS_FASTMCP, reason="fastmcp not installed") + def test_register_tools_handles_empty_registry_gracefully(self): + """register_tools() should not error when registry is empty.""" + from pleiades.mcp.server import register_tools + + mock_server = MagicMock() + + # Should not raise + register_tools(mock_server) + + # Should not have called server.tool() + assert mock_server.tool.call_count == 0 + + @pytest.mark.skipif(not HAS_FASTMCP, reason="fastmcp not installed") + def test_register_tools_preserves_function_signatures(self): + """Registered tools should preserve original function signatures.""" + from pleiades.mcp.decorators import mcp_tool + from pleiades.mcp.server import register_tools + + @mcp_tool() + def typed_function(name: str, age: int, active: bool = True) -> dict: + """Function with typed parameters.""" + return {"name": name, "age": age, "active": active} + + mock_server = MagicMock() + + # Capture the function that gets registered + registered_func = None + + def capture_tool(*args, **kwargs): + """Capture the decorated function.""" + + def decorator(func): + nonlocal registered_func + registered_func = func + return func + + return decorator + + mock_server.tool.side_effect = capture_tool + + register_tools(mock_server) + + # Verify function signature is preserved + assert registered_func is not None + sig = inspect.signature(registered_func) + params = sig.parameters + + assert "name" in params + assert "age" in params + assert "active" in params + assert params["active"].default is True + + @pytest.mark.skipif(not HAS_FASTMCP, reason="fastmcp not installed") + def test_register_tools_uses_custom_names(self): + """register_tools() should use custom tool names when provided.""" + from pleiades.mcp.decorators import mcp_tool + from pleiades.mcp.server import register_tools + + @mcp_tool(name="custom-name") + def original_name(): + """Tool with custom name.""" + pass + + mock_server = MagicMock() + + # Capture the name argument + captured_name = None + + def capture_tool(name=None, **kwargs): + """Capture the tool name.""" + nonlocal captured_name + captured_name = name + + def decorator(func): + return func + + return decorator + + mock_server.tool.side_effect = capture_tool + + register_tools(mock_server) + + # Verify custom name was used + assert captured_name == "custom-name" + + @pytest.mark.skipif(not HAS_FASTMCP, reason="fastmcp not installed") + def test_register_tools_includes_descriptions(self): + """register_tools() should pass tool descriptions to FastMCP.""" + from pleiades.mcp.decorators import mcp_tool + from pleiades.mcp.server import register_tools + + @mcp_tool(description="This is a helpful tool") + def helpful_tool(): + """Original docstring.""" + pass + + mock_server = MagicMock() + + # Capture the description argument + captured_description = None + + def capture_tool(description=None, **kwargs): + """Capture the tool description.""" + nonlocal captured_description + captured_description = description + + def decorator(func): + return func + + return decorator + + mock_server.tool.side_effect = capture_tool + + register_tools(mock_server) + + # Verify description was passed + assert captured_description == "This is a helpful tool" + + +class TestSchemaGeneration: + """Test generate_tool_schema() function for JSON schema creation.""" + + def test_generate_tool_schema_handles_simple_types(self): + """generate_tool_schema() should create schema for str, int, float, bool.""" + from pleiades.mcp.server import generate_tool_schema + + def simple_func(name: str, age: int, height: float, active: bool) -> dict: + """Function with simple types.""" + return {} + + schema = generate_tool_schema(simple_func) + + assert isinstance(schema, dict) + assert "properties" in schema + + # Check each parameter type + props = schema["properties"] + assert "name" in props + assert props["name"]["type"] == "string" + + assert "age" in props + assert props["age"]["type"] == "integer" + + assert "height" in props + assert props["height"]["type"] == "number" + + assert "active" in props + assert props["active"]["type"] == "boolean" + + def test_generate_tool_schema_handles_optional_types(self): + """generate_tool_schema() should handle Optional[T] types.""" + from pleiades.mcp.server import generate_tool_schema + + def optional_func(required: str, optional: Optional[int] = None) -> dict: + """Function with optional parameter.""" + return {} + + schema = generate_tool_schema(optional_func) + + # Optional parameters should not be in required list + # or should allow null + if "required" in schema: + assert "required" in schema["required"] + if "optional" in schema["required"]: + # If optional is in required, it should allow null + props = schema["properties"] + assert "optional" in props + else: + # Schema may handle optionals differently + props = schema["properties"] + assert "optional" in props + + def test_generate_tool_schema_handles_list_types(self): + """generate_tool_schema() should handle list types.""" + from pleiades.mcp.server import generate_tool_schema + + def list_func(items: list[str]) -> dict: + """Function with list parameter.""" + return {} + + schema = generate_tool_schema(list_func) + + props = schema["properties"] + assert "items" in props + assert props["items"]["type"] == "array" + + def test_generate_tool_schema_handles_dict_types(self): + """generate_tool_schema() should handle dict types.""" + from pleiades.mcp.server import generate_tool_schema + + def dict_func(config: dict[str, Any]) -> dict: + """Function with dict parameter.""" + return {} + + schema = generate_tool_schema(dict_func) + + props = schema["properties"] + assert "config" in props + assert props["config"]["type"] == "object" + + def test_generate_tool_schema_includes_parameter_descriptions(self): + """generate_tool_schema() should include parameter descriptions when available.""" + from pleiades.mcp.decorators import mcp_tool + from pleiades.mcp.server import generate_tool_schema + + @mcp_tool( + parameter_descriptions={ + "name": "The user's name", + "age": "The user's age in years", + } + ) + def documented_func(name: str, age: int) -> dict: + """Function with parameter descriptions.""" + return {} + + schema = generate_tool_schema(documented_func) + + props = schema["properties"] + assert "name" in props + assert "description" in props["name"] + assert props["name"]["description"] == "The user's name" + + assert "age" in props + assert "description" in props["age"] + assert props["age"]["description"] == "The user's age in years" + + def test_generate_tool_schema_handles_functions_without_type_hints(self): + """generate_tool_schema() should handle functions without type hints gracefully.""" + from pleiades.mcp.server import generate_tool_schema + + def no_hints(x, y): + """Function without type hints.""" + return x + y + + # Should not raise an error + schema = generate_tool_schema(no_hints) + + # Schema should be valid dict, possibly with generic types + assert isinstance(schema, dict) + assert "properties" in schema + + def test_generate_tool_schema_handles_default_values(self): + """generate_tool_schema() should handle default parameter values.""" + from pleiades.mcp.server import generate_tool_schema + + def with_defaults(required: str, optional: int = 42) -> dict: + """Function with default values.""" + return {} + + schema = generate_tool_schema(with_defaults) + + # Required parameters should be in required list + assert "required" in schema + assert "required" in schema["required"] + + # Optional (has default) should not be required + if "optional" in schema["required"]: + # Some implementations might still list it + pass + else: + # Default is preferred - optional params not in required + assert "optional" not in schema["required"] + + def test_generate_tool_schema_handles_complex_return_types(self): + """generate_tool_schema() should handle complex return types.""" + from pleiades.mcp.server import generate_tool_schema + + def complex_return(x: int) -> dict[str, list[int]]: + """Function with complex return type.""" + return {} + + # Should not raise an error + schema = generate_tool_schema(complex_return) + assert isinstance(schema, dict) + + def test_generate_tool_schema_includes_required_fields(self): + """generate_tool_schema() should mark parameters without defaults as required.""" + from pleiades.mcp.server import generate_tool_schema + + def required_params(a: str, b: int, c: float = 1.0) -> dict: + """Function with required and optional parameters.""" + return {} + + schema = generate_tool_schema(required_params) + + assert "required" in schema + required = schema["required"] + + # a and b should be required + assert "a" in required + assert "b" in required + + # c has a default, should not be required (or handled differently) + # Most schema generators would not mark c as required + + def test_generate_tool_schema_handles_varargs(self): + """generate_tool_schema() should handle *args gracefully.""" + from pleiades.mcp.server import generate_tool_schema + + def varargs_func(*args: int) -> int: + """Function with varargs.""" + return sum(args) + + # Should not raise an error + schema = generate_tool_schema(varargs_func) + assert isinstance(schema, dict) + + def test_generate_tool_schema_handles_kwargs(self): + """generate_tool_schema() should handle **kwargs gracefully.""" + from pleiades.mcp.server import generate_tool_schema + + def kwargs_func(**kwargs: Any) -> dict: + """Function with kwargs.""" + return kwargs + + # Should not raise an error + schema = generate_tool_schema(kwargs_func) + assert isinstance(schema, dict) + + +class TestServerIntegration: + """Test server initialization and integration.""" + + @pytest.mark.skipif(not HAS_FASTMCP, reason="fastmcp not installed") + def test_get_server_returns_fastmcp_instance(self): + """get_server() should return FastMCP instance when MCP available.""" + from pleiades.mcp.server import get_server + + server = get_server() + assert server is not None + # Should be a FastMCP instance + from fastmcp import FastMCP + + assert isinstance(server, FastMCP) + + @pytest.mark.skipif(not HAS_FASTMCP, reason="fastmcp not installed") + def test_get_server_returns_same_instance(self): + """get_server() should return the same instance on multiple calls.""" + from pleiades.mcp.server import get_server + + server1 = get_server() + server2 = get_server() + assert server1 is server2 + + @pytest.mark.skipif(HAS_FASTMCP, reason="fastmcp is installed") + def test_get_server_raises_when_mcp_not_available(self): + """get_server() should raise ImportError when MCP not available.""" + from pleiades.mcp.server import get_server + + with pytest.raises(ImportError, match="MCP dependencies not installed"): + get_server() + + @pytest.mark.skipif(not HAS_FASTMCP, reason="fastmcp not installed") + def test_main_can_be_called_without_error(self): + """main() should be callable (with mocked server.run()).""" + from pleiades.mcp.server import main + + # Mock the server.run() to avoid actually starting the server + with patch("pleiades.mcp.server.get_server") as mock_get_server: + mock_server = MagicMock() + mock_get_server.return_value = mock_server + + # Should not raise + main() + + # Verify server.run() was called + assert mock_server.run.called + + @pytest.mark.skipif(HAS_FASTMCP, reason="fastmcp is installed") + def test_main_raises_when_mcp_not_available(self): + """main() should raise ImportError when MCP not available.""" + from pleiades.mcp.server import main + + with pytest.raises(ImportError, match="MCP dependencies not installed"): + main() + + +class TestErrorHandling: + """Test error handling in server functions.""" + + def setup_method(self): + """Clear registry before each test.""" + from pleiades.mcp.decorators import clear_registry + + clear_registry() + + def test_discover_tools_handles_empty_registry(self): + """discover_tools() should handle empty registry without errors.""" + from pleiades.mcp.server import discover_tools + + # Should not raise + tools = discover_tools() + assert tools == {} + + @pytest.mark.skipif(not HAS_FASTMCP, reason="fastmcp not installed") + def test_register_tools_handles_empty_registry(self): + """register_tools() should handle empty registry gracefully.""" + from pleiades.mcp.server import register_tools + + mock_server = MagicMock() + + # Should not raise + register_tools(mock_server) + + # Should not have registered any tools + assert mock_server.tool.call_count == 0 + + @pytest.mark.skipif(not HAS_FASTMCP, reason="fastmcp not installed") + def test_register_tools_continues_on_tool_error(self): + """register_tools() should continue registering other tools if one fails.""" + from pleiades.mcp.decorators import mcp_tool + from pleiades.mcp.server import register_tools + + @mcp_tool() + def good_tool(): + """A good tool.""" + pass + + @mcp_tool() + def another_good_tool(): + """Another good tool.""" + pass + + mock_server = MagicMock() + + # Make the first call fail, second succeed + call_count = 0 + + def side_effect(*args, **kwargs): + nonlocal call_count + call_count += 1 + if call_count == 1: + raise ValueError("Registration failed") + + def decorator(func): + return func + + return decorator + + mock_server.tool.side_effect = side_effect + + # Should not raise - should continue registering + try: + register_tools(mock_server) + except ValueError: + # If it does raise, that's also acceptable behavior + # The test is mainly checking that we thought about error handling + pass + + # At least attempted to register tools + assert mock_server.tool.call_count >= 1 + + def test_generate_tool_schema_handles_empty_function(self): + """generate_tool_schema() should handle function with no parameters.""" + from pleiades.mcp.server import generate_tool_schema + + def no_params() -> None: + """Function with no parameters.""" + pass + + schema = generate_tool_schema(no_params) + + assert isinstance(schema, dict) + assert "properties" in schema + # Should have empty or minimal properties + assert isinstance(schema["properties"], dict) + + +class TestAutoDiscoveryIntegration: + """Test end-to-end auto-discovery workflow.""" + + def setup_method(self): + """Clear registry before each test.""" + from pleiades.mcp.decorators import clear_registry + + clear_registry() + + @pytest.mark.skipif(not HAS_FASTMCP, reason="fastmcp not installed") + def test_full_workflow_discovers_and_registers_tools(self): + """Complete workflow: decorate -> discover -> register.""" + from pleiades.mcp.decorators import mcp_tool + from pleiades.mcp.server import discover_tools, register_tools + + # Step 1: Decorate functions + @mcp_tool() + def validate_data(path: str) -> dict: + """Validate dataset at path.""" + return {"valid": True} + + @mcp_tool() + def process_image(image_path: str, threshold: float = 0.5) -> dict: + """Process an image with threshold.""" + return {"processed": True} + + # Step 2: Discover tools + tools = discover_tools() + assert len(tools) == 2 + assert "validate_data" in tools + assert "process_image" in tools + + # Step 3: Register with mock server + mock_server = MagicMock() + register_tools(mock_server) + + # Verify both tools were registered + assert mock_server.tool.call_count == 2 + + @pytest.mark.skipif(not HAS_FASTMCP, reason="fastmcp not installed") + def test_registered_tools_preserve_functionality(self): + """Tools should work correctly after registration.""" + from pleiades.mcp.decorators import mcp_tool + from pleiades.mcp.server import discover_tools, register_tools + + @mcp_tool() + def add_numbers(a: int, b: int) -> int: + """Add two numbers.""" + return a + b + + # Discover and register + tools = discover_tools() + mock_server = MagicMock() + register_tools(mock_server) + + # Original function should still work + assert add_numbers(5, 3) == 8 + + # Function in registry should still work + assert tools["add_numbers"]["func"](5, 3) == 8 + + +class TestSchemaGenerationEdgeCases: + """Test edge cases in schema generation.""" + + def test_generate_tool_schema_with_union_types(self): + """generate_tool_schema() should handle Union types.""" + from typing import Union + + from pleiades.mcp.server import generate_tool_schema + + def union_func(value: Union[str, int]) -> dict: + """Function with union type.""" + return {} + + # Should not raise + schema = generate_tool_schema(union_func) + assert isinstance(schema, dict) + + def test_generate_tool_schema_with_nested_types(self): + """generate_tool_schema() should handle nested types.""" + from pleiades.mcp.server import generate_tool_schema + + def nested_func(data: dict[str, list[dict[str, int]]]) -> dict: + """Function with deeply nested types.""" + return {} + + # Should not raise + schema = generate_tool_schema(nested_func) + assert isinstance(schema, dict) + + def test_generate_tool_schema_with_any_type(self): + """generate_tool_schema() should handle Any type.""" + from pleiades.mcp.server import generate_tool_schema + + def any_func(data: Any) -> Any: + """Function with Any type.""" + return data + + # Should not raise + schema = generate_tool_schema(any_func) + assert isinstance(schema, dict) + + def test_generate_tool_schema_preserves_parameter_order(self): + """generate_tool_schema() should preserve parameter order.""" + from pleiades.mcp.server import generate_tool_schema + + def ordered_func(a: str, b: int, c: float, d: bool) -> dict: + """Function with multiple parameters.""" + return {} + + schema = generate_tool_schema(ordered_func) + + # Properties should exist for all parameters + props = schema["properties"] + assert "a" in props + assert "b" in props + assert "c" in props + assert "d" in props + + # Check that they're in order (if the implementation preserves order) + # Note: dict order is preserved in Python 3.7+ + param_names = list(props.keys()) + assert param_names == ["a", "b", "c", "d"] + + def test_generate_tool_schema_with_modern_union_syntax(self): + """generate_tool_schema() should handle Python 3.10+ pipe union syntax.""" + import sys + + if sys.version_info < (3, 10): + pytest.skip("Python 3.10+ required for | union syntax") + + from pleiades.mcp.server import generate_tool_schema + + # Define using exec to avoid syntax error on Python 3.9 + local_ns: dict = {} + exec( + ''' +def modern_union(value: int | str) -> dict: + """Function with modern union.""" + return {} +''', + local_ns, + ) + + schema = generate_tool_schema(local_ns["modern_union"]) + + # Should extract 'int' (first type in union) + assert schema["properties"]["value"]["type"] == "integer" + + def test_generate_tool_schema_with_modern_optional_syntax(self): + """generate_tool_schema() should handle Python 3.10+ pipe optional syntax.""" + import sys + + if sys.version_info < (3, 10): + pytest.skip("Python 3.10+ required for | union syntax") + + from pleiades.mcp.server import generate_tool_schema + + local_ns: dict = {} + exec( + ''' +def modern_optional(value: int | None = None) -> dict: + """Function with modern optional.""" + return {} +''', + local_ns, + ) + + schema = generate_tool_schema(local_ns["modern_optional"]) + + # Should extract 'int' from int | None + assert schema["properties"]["value"]["type"] == "integer" + + +class TestDiscoverToolsReturnFormat: + """Test the exact return format of discover_tools().""" + + def setup_method(self): + """Clear registry before each test.""" + from pleiades.mcp.decorators import clear_registry + + clear_registry() + + def test_discover_tools_returns_dict_of_dicts(self): + """discover_tools() should return dict mapping names to metadata dicts.""" + from pleiades.mcp.decorators import mcp_tool + from pleiades.mcp.server import discover_tools + + @mcp_tool() + def example_tool(): + """Example tool.""" + pass + + tools = discover_tools() + + assert isinstance(tools, dict) + assert isinstance(tools["example_tool"], dict) + + def test_discover_tools_metadata_contains_required_keys(self): + """Each tool metadata dict should contain required keys.""" + from pleiades.mcp.decorators import mcp_tool + from pleiades.mcp.server import discover_tools + + @mcp_tool() + def example_tool(): + """Example tool.""" + pass + + tools = discover_tools() + metadata = tools["example_tool"] + + # Required keys + assert "name" in metadata + assert "description" in metadata + assert "func" in metadata + + # Verify types + assert isinstance(metadata["name"], str) + assert isinstance(metadata["description"], str) + assert callable(metadata["func"]) + + def test_discover_tools_is_independent_copy(self): + """discover_tools() should return independent copy, not reference to registry.""" + from pleiades.mcp.decorators import mcp_tool + from pleiades.mcp.server import discover_tools + + @mcp_tool() + def example_tool(): + """Example tool.""" + pass + + tools1 = discover_tools() + tools2 = discover_tools() + + # Should be separate dicts + assert tools1 is not tools2 + + # Modifying one should not affect the other + tools1["example_tool"]["description"] = "MODIFIED" + assert tools2["example_tool"]["description"] == "Example tool." + + +if __name__ == "__main__": + pytest.main(["-v", __file__]) diff --git a/tests/unit/pleiades/test_mcp/test_tools.py b/tests/unit/pleiades/test_mcp/test_tools.py new file mode 100644 index 00000000..c9320ae7 --- /dev/null +++ b/tests/unit/pleiades/test_mcp/test_tools.py @@ -0,0 +1,1074 @@ +"""Test suite for pleiades.mcp.tools module. + +Tests cover MCP tool wrappers that expose workflow functions. + +These tests define the expected API for Issue #168 (MCP prototype tools). +They are written BEFORE implementation (TDD) to guide development. + +Tests verify: +- Tool registration via @mcp_tool decorator +- Input handling (string paths vs Path objects) +- Output format (JSON-serializable dicts) +- Integration with workflow functions +- Error handling and graceful degradation +""" + +from pathlib import Path +from unittest.mock import MagicMock, patch + +import pytest + + +class TestToolRegistration: + """Test that tools are registered via @mcp_tool decorator.""" + + def setup_method(self): + """Clear and re-register all tools for consistent test isolation.""" + import importlib + + import pleiades.mcp.tools as tools_module + from pleiades.mcp.decorators import clear_registry + + # Always clear and reload for consistent test isolation + clear_registry() + importlib.reload(tools_module) + + def test_validate_resonance_dataset_is_registered(self): + """validate_resonance_dataset should be registered with decorator.""" + from pleiades.mcp.decorators import get_registered_tools + + tools = get_registered_tools() + assert "validate_resonance_dataset" in tools + + def test_extract_resonance_manifest_is_registered(self): + """extract_resonance_manifest should be registered with decorator.""" + from pleiades.mcp.decorators import get_registered_tools + + tools = get_registered_tools() + assert "extract_resonance_manifest" in tools + + def test_analyze_resonance_is_registered(self): + """analyze_resonance should be registered with decorator.""" + from pleiades.mcp.decorators import get_registered_tools + + tools = get_registered_tools() + assert "analyze_resonance" in tools + + def test_all_tools_have_descriptions(self): + """All registered tools should have descriptions.""" + from pleiades.mcp.decorators import get_registered_tools + + tools = get_registered_tools() + + for tool_name in ["validate_resonance_dataset", "extract_resonance_manifest", "analyze_resonance"]: + assert tool_name in tools + assert tools[tool_name]["description"] + assert len(tools[tool_name]["description"]) > 0 + + def test_tools_have_parameter_descriptions(self): + """Tools should have parameter descriptions for better UX.""" + from pleiades.mcp.decorators import get_registered_tools + + tools = get_registered_tools() + + # validate_resonance_dataset should document dataset_path + validate_tool = tools["validate_resonance_dataset"] + if validate_tool.get("parameter_descriptions"): + assert "dataset_path" in validate_tool["parameter_descriptions"] + + # extract_resonance_manifest should document dataset_path + extract_tool = tools["extract_resonance_manifest"] + if extract_tool.get("parameter_descriptions"): + assert "dataset_path" in extract_tool["parameter_descriptions"] + + # analyze_resonance should document dataset_path + analyze_tool = tools["analyze_resonance"] + if analyze_tool.get("parameter_descriptions"): + assert "dataset_path" in analyze_tool["parameter_descriptions"] + + +class TestValidateResonanceDatasetInput: + """Test input handling for validate_resonance_dataset.""" + + def setup_method(self): + """Clear registry before each test.""" + from pleiades.mcp.decorators import clear_registry + + clear_registry() + + def test_accepts_string_path(self): + """Tool should accept string paths (MCP convention).""" + from pleiades.mcp.tools import validate_resonance_dataset + + # Mock the workflow function + with patch("pleiades.mcp.tools.workflows.validate_dataset") as mock_validate: + mock_result = MagicMock() + mock_result.valid = True + mock_result.dataset_path = Path("/fake/path") + mock_result.model_dump.return_value = {"valid": True} + mock_validate.return_value = mock_result + + result = validate_resonance_dataset("/fake/path") + + # Should have called workflow with string converted to Path + assert mock_validate.called + # Result should be a dict + assert isinstance(result, dict) + + def test_accepts_absolute_path(self): + """Tool should accept absolute paths.""" + from pleiades.mcp.tools import validate_resonance_dataset + + with patch("pleiades.mcp.tools.workflows.validate_dataset") as mock_validate: + mock_result = MagicMock() + mock_result.valid = True + mock_result.dataset_path = Path("/absolute/path") + mock_result.model_dump.return_value = {"valid": True} + mock_validate.return_value = mock_result + + result = validate_resonance_dataset("/absolute/path/to/dataset") + + assert mock_validate.called + assert isinstance(result, dict) + + def test_accepts_relative_path(self): + """Tool should accept relative paths.""" + from pleiades.mcp.tools import validate_resonance_dataset + + with patch("pleiades.mcp.tools.workflows.validate_dataset") as mock_validate: + mock_result = MagicMock() + mock_result.valid = True + mock_result.dataset_path = Path("relative/path") + mock_result.model_dump.return_value = {"valid": True} + mock_validate.return_value = mock_result + + result = validate_resonance_dataset("relative/path") + + assert mock_validate.called + assert isinstance(result, dict) + + def test_handles_nonexistent_path_gracefully(self): + """Tool should handle non-existent paths without raising exceptions.""" + from pleiades.mcp.tools import validate_resonance_dataset + + # Real workflow returns ValidationResult with valid=False for missing paths + with patch("pleiades.mcp.tools.workflows.validate_dataset") as mock_validate: + mock_result = MagicMock() + mock_result.valid = False + mock_result.dataset_path = Path("/nonexistent") + mock_result.model_dump.return_value = {"valid": False, "issues": []} + mock_validate.return_value = mock_result + + # Should not raise - should return error result + result = validate_resonance_dataset("/nonexistent/path") + + assert isinstance(result, dict) + assert "status" in result + # Error results should have status='error' or success=False + + +class TestValidateResonanceDatasetOutput: + """Test output format for validate_resonance_dataset.""" + + def setup_method(self): + """Clear registry before each test.""" + from pleiades.mcp.decorators import clear_registry + + clear_registry() + + def test_returns_json_serializable_dict(self): + """Tool should return JSON-serializable dict.""" + import json + + from pleiades.mcp.tools import validate_resonance_dataset + + with patch("pleiades.mcp.tools.workflows.validate_dataset") as mock_validate: + mock_result = MagicMock() + mock_result.valid = True + mock_result.dataset_path = Path("/fake") + mock_result.model_dump.return_value = {"valid": True, "dataset_path": "/fake"} + mock_validate.return_value = mock_result + + result = validate_resonance_dataset("/fake/path") + + # Should be JSON-serializable + json_str = json.dumps(result) + assert isinstance(json_str, str) + + def test_includes_status_field(self): + """Result should include status field (success/error).""" + from pleiades.mcp.tools import validate_resonance_dataset + + with patch("pleiades.mcp.tools.workflows.validate_dataset") as mock_validate: + mock_result = MagicMock() + mock_result.valid = True + mock_result.dataset_path = Path("/fake") + mock_result.model_dump.return_value = {"valid": True} + mock_validate.return_value = mock_result + + result = validate_resonance_dataset("/fake/path") + + # Should have status field + assert "status" in result + assert result["status"] in ["success", "error"] + + def test_success_result_has_data_field(self): + """Successful result should include data field with validation results.""" + from pleiades.mcp.tools import validate_resonance_dataset + + with patch("pleiades.mcp.tools.workflows.validate_dataset") as mock_validate: + mock_validation = MagicMock() + mock_validation.valid = True + mock_validation.dataset_path = Path("/fake") + mock_validation.can_run_full_workflow = False + mock_validation.can_run_simplified_workflow = True + mock_validation.model_dump.return_value = { + "valid": True, + "dataset_path": "/fake", + "can_run_full_workflow": False, + "can_run_simplified_workflow": True, + } + mock_validate.return_value = mock_validation + + result = validate_resonance_dataset("/fake/path") + + assert result["status"] == "success" + assert "data" in result + assert isinstance(result["data"], dict) + assert result["data"]["valid"] is True + + def test_error_result_has_error_field(self): + """Error result should include error message.""" + from pleiades.mcp.tools import validate_resonance_dataset + + with patch("pleiades.mcp.tools.workflows.validate_dataset") as mock_validate: + # Simulate workflow raising exception + mock_validate.side_effect = ValueError("Invalid dataset") + + result = validate_resonance_dataset("/fake/path") + + assert result["status"] == "error" + assert "error" in result + assert "Invalid dataset" in result["error"] + + def test_converts_path_objects_to_strings(self): + """Path objects in result should be converted to strings for JSON.""" + import json + + from pleiades.mcp.tools import validate_resonance_dataset + + with patch("pleiades.mcp.tools.workflows.validate_dataset") as mock_validate: + mock_result = MagicMock() + mock_result.valid = True + mock_result.dataset_path = Path("/fake/path") + mock_result.model_dump.return_value = { + "valid": True, + "dataset_path": "/fake/path", # Already converted + } + mock_validate.return_value = mock_result + + result = validate_resonance_dataset("/fake/path") + + # Should be JSON-serializable (no Path objects) + json_str = json.dumps(result) + assert isinstance(json_str, str) + + # Verify dataset_path is a string + if "data" in result and "dataset_path" in result["data"]: + assert isinstance(result["data"]["dataset_path"], str) + + +class TestExtractResonanceManifestInput: + """Test input handling for extract_resonance_manifest.""" + + def setup_method(self): + """Clear registry before each test.""" + from pleiades.mcp.decorators import clear_registry + + clear_registry() + + def test_accepts_string_path(self): + """Tool should accept string paths.""" + from pleiades.mcp.tools import extract_resonance_manifest + + with patch("pleiades.mcp.tools.workflows.extract_manifest") as mock_extract: + mock_extract.return_value = None + + result = extract_resonance_manifest("/fake/path") + + assert mock_extract.called + assert isinstance(result, dict) + + def test_handles_missing_manifest_gracefully(self): + """Tool should handle missing manifest without raising exceptions.""" + from pleiades.mcp.tools import extract_resonance_manifest + + with patch("pleiades.mcp.tools.workflows.extract_manifest") as mock_extract: + # Workflow returns None for missing manifest + mock_extract.return_value = None + + result = extract_resonance_manifest("/fake/path") + + assert isinstance(result, dict) + assert "status" in result + + +class TestExtractResonanceManifestOutput: + """Test output format for extract_resonance_manifest.""" + + def setup_method(self): + """Clear registry before each test.""" + from pleiades.mcp.decorators import clear_registry + + clear_registry() + + def test_returns_json_serializable_dict(self): + """Tool should return JSON-serializable dict.""" + import json + + from pleiades.mcp.tools import extract_resonance_manifest + + with patch("pleiades.mcp.tools.workflows.extract_manifest") as mock_extract: + mock_manifest = MagicMock() + mock_manifest.model_dump.return_value = { + "name": "test", + "description": "test dataset", + "version": "1.0.0", + "created": "2024-01-01T00:00:00", + } + mock_extract.return_value = mock_manifest + + result = extract_resonance_manifest("/fake/path") + + # Should be JSON-serializable + json_str = json.dumps(result) + assert isinstance(json_str, str) + + def test_success_result_has_manifest_data(self): + """Successful result should include manifest data.""" + from pleiades.mcp.tools import extract_resonance_manifest + + with patch("pleiades.mcp.tools.workflows.extract_manifest") as mock_extract: + mock_manifest = MagicMock() + mock_manifest.model_dump.return_value = { + "name": "Au197_sample", + "description": "Gold sample", + "version": "1.0.0", + "created": "2024-01-01T00:00:00", + "isotope": "Au-197", + } + mock_extract.return_value = mock_manifest + + result = extract_resonance_manifest("/fake/path") + + assert result["status"] == "success" + assert "data" in result + assert result["data"]["name"] == "Au197_sample" + assert result["data"]["isotope"] == "Au-197" + + def test_missing_manifest_returns_error(self): + """Missing manifest should return error status.""" + from pleiades.mcp.tools import extract_resonance_manifest + + with patch("pleiades.mcp.tools.workflows.extract_manifest") as mock_extract: + # Workflow returns None when no manifest found + mock_extract.return_value = None + + result = extract_resonance_manifest("/fake/path") + + assert result["status"] == "error" + assert "error" in result + + def test_invalid_manifest_returns_error(self): + """Invalid manifest should return error result.""" + from pleiades.mcp.tools import extract_resonance_manifest + + with patch("pleiades.mcp.tools.workflows.extract_manifest") as mock_extract: + # Workflow raises ValueError for invalid manifest + mock_extract.side_effect = ValueError("Invalid manifest format") + + result = extract_resonance_manifest("/fake/path") + + assert result["status"] == "error" + assert "error" in result + assert "Invalid manifest" in result["error"] + + +class TestAnalyzeResonanceInput: + """Test input handling for analyze_resonance.""" + + def setup_method(self): + """Clear registry before each test.""" + from pleiades.mcp.decorators import clear_registry + + clear_registry() + + def test_accepts_string_path(self): + """Tool should accept string paths.""" + from pleiades.mcp.tools import analyze_resonance + + with patch("pleiades.mcp.tools.workflows.analyze_resonance") as mock_analyze: + mock_result = MagicMock() + mock_result.success = True + mock_result.model_dump.return_value = {"success": True} + mock_analyze.return_value = mock_result + + result = analyze_resonance("/fake/path") + + assert mock_analyze.called + assert isinstance(result, dict) + + def test_passes_backend_parameter(self): + """Tool should pass backend parameter to workflow.""" + from pleiades.mcp.tools import analyze_resonance + + with patch("pleiades.mcp.tools.workflows.analyze_resonance") as mock_analyze: + mock_result = MagicMock() + mock_result.success = True + mock_result.model_dump.return_value = {"success": True} + mock_analyze.return_value = mock_result + + analyze_resonance("/fake/path", backend="docker") + + # Verify backend was passed + call_kwargs = mock_analyze.call_args[1] + assert call_kwargs["backend"] == "docker" + + def test_backend_defaults_to_auto(self): + """Tool should default backend to 'auto'.""" + from pleiades.mcp.tools import analyze_resonance + + with patch("pleiades.mcp.tools.workflows.analyze_resonance") as mock_analyze: + mock_result = MagicMock() + mock_result.success = True + mock_result.model_dump.return_value = {"success": True} + mock_analyze.return_value = mock_result + + analyze_resonance("/fake/path") + + call_kwargs = mock_analyze.call_args[1] + assert call_kwargs["backend"] == "auto" + + +class TestAnalyzeResonanceOutput: + """Test output format for analyze_resonance.""" + + def setup_method(self): + """Clear registry before each test.""" + from pleiades.mcp.decorators import clear_registry + + clear_registry() + + def test_returns_json_serializable_dict(self): + """Tool should return JSON-serializable dict.""" + import json + + from pleiades.mcp.tools import analyze_resonance + + with patch("pleiades.mcp.tools.workflows.analyze_resonance") as mock_analyze: + mock_result = MagicMock() + mock_result.success = True + mock_result.chi_squared = 1.5 + mock_result.reduced_chi_squared = 1.2 + mock_result.model_dump.return_value = { + "success": True, + "chi_squared": 1.5, + "reduced_chi_squared": 1.2, + } + mock_analyze.return_value = mock_result + + result = analyze_resonance("/fake/path") + + # Should be JSON-serializable + json_str = json.dumps(result) + assert isinstance(json_str, str) + + def test_success_result_has_analysis_data(self): + """Successful result should include analysis data.""" + from pleiades.mcp.tools import analyze_resonance + + with patch("pleiades.mcp.tools.workflows.analyze_resonance") as mock_analyze: + mock_result = MagicMock() + mock_result.success = True + mock_result.chi_squared = 1.5 + mock_result.reduced_chi_squared = 1.2 + mock_result.primary_isotope = "Au-197" + mock_result.model_dump.return_value = { + "success": True, + "chi_squared": 1.5, + "reduced_chi_squared": 1.2, + "primary_isotope": "Au-197", + } + mock_analyze.return_value = mock_result + + result = analyze_resonance("/fake/path") + + assert result["status"] == "success" + assert "data" in result + assert result["data"]["success"] is True + assert result["data"]["primary_isotope"] == "Au-197" + + def test_failed_analysis_returns_error(self): + """Failed analysis should return error status.""" + from pleiades.mcp.tools import analyze_resonance + + with patch("pleiades.mcp.tools.workflows.analyze_resonance") as mock_analyze: + mock_result = MagicMock() + mock_result.success = False + mock_result.error_message = "SAMMY execution failed" + mock_result.model_dump.return_value = { + "success": False, + "error_message": "SAMMY execution failed", + } + mock_analyze.return_value = mock_result + + result = analyze_resonance("/fake/path") + + # Could be success status with success=False in data + # OR error status with error message + # Both are valid designs - test accepts either + assert "status" in result + if result["status"] == "success": + assert result["data"]["success"] is False + else: + assert result["status"] == "error" + + def test_exception_returns_error(self): + """Workflow exception should return error result.""" + from pleiades.mcp.tools import analyze_resonance + + with patch("pleiades.mcp.tools.workflows.analyze_resonance") as mock_analyze: + mock_analyze.side_effect = RuntimeError("SAMMY not available") + + result = analyze_resonance("/fake/path") + + assert result["status"] == "error" + assert "error" in result + assert "SAMMY not available" in result["error"] + + def test_converts_path_objects_to_strings(self): + """Path objects in result should be converted to strings.""" + import json + + from pleiades.mcp.tools import analyze_resonance + + with patch("pleiades.mcp.tools.workflows.analyze_resonance") as mock_analyze: + mock_result = MagicMock() + mock_result.success = True + mock_result.output_dir = Path("/fake/output") + mock_result.model_dump.return_value = { + "success": True, + "output_dir": "/fake/output", # Already converted + } + mock_analyze.return_value = mock_result + + result = analyze_resonance("/fake/path") + + # Should be JSON-serializable + json_str = json.dumps(result) + assert isinstance(json_str, str) + + +class TestWorkflowIntegration: + """Test integration with workflow functions.""" + + def setup_method(self): + """Clear registry before each test.""" + from pleiades.mcp.decorators import clear_registry + + clear_registry() + + def test_validate_dataset_calls_workflow_function(self): + """validate_resonance_dataset should call workflows.validate_dataset.""" + from pleiades.mcp.tools import validate_resonance_dataset + + with patch("pleiades.mcp.tools.workflows.validate_dataset") as mock_validate: + mock_result = MagicMock() + mock_result.valid = True + mock_result.model_dump.return_value = {"valid": True} + mock_validate.return_value = mock_result + + validate_resonance_dataset("/test/path") + + # Should have called the workflow function + assert mock_validate.called + # Should have passed the path (as string or Path) + call_args = mock_validate.call_args[0] + assert len(call_args) == 1 + + def test_extract_manifest_calls_workflow_function(self): + """extract_resonance_manifest should call workflows.extract_manifest.""" + from pleiades.mcp.tools import extract_resonance_manifest + + with patch("pleiades.mcp.tools.workflows.extract_manifest") as mock_extract: + mock_extract.return_value = None + + extract_resonance_manifest("/test/path") + + assert mock_extract.called + + def test_analyze_resonance_calls_workflow_function(self): + """analyze_resonance should call workflows.analyze_resonance.""" + from pleiades.mcp.tools import analyze_resonance + + with patch("pleiades.mcp.tools.workflows.analyze_resonance") as mock_analyze: + mock_result = MagicMock() + mock_result.success = True + mock_result.model_dump.return_value = {"success": True} + mock_analyze.return_value = mock_result + + analyze_resonance("/test/path") + + assert mock_analyze.called + + def test_workflow_result_is_properly_formatted(self): + """Workflow results should be converted to MCP-friendly format.""" + from pleiades.mcp.tools import validate_resonance_dataset + + with patch("pleiades.mcp.tools.workflows.validate_dataset") as mock_validate: + # Create realistic ValidationResult mock + mock_result = MagicMock() + mock_result.valid = True + mock_result.dataset_path = Path("/test") + mock_result.can_run_full_workflow = False + mock_result.can_run_simplified_workflow = True + mock_result.has_manifest = True + mock_result.model_dump.return_value = { + "valid": True, + "dataset_path": "/test", + "can_run_full_workflow": False, + "can_run_simplified_workflow": True, + "has_manifest": True, + } + mock_validate.return_value = mock_result + + result = validate_resonance_dataset("/test") + + # Should be properly formatted + assert "status" in result + assert "data" in result + assert isinstance(result["data"], dict) + + +class TestErrorHandling: + """Test error handling and edge cases.""" + + def setup_method(self): + """Clear registry before each test.""" + from pleiades.mcp.decorators import clear_registry + + clear_registry() + + def test_validate_dataset_handles_permission_error(self): + """validate_resonance_dataset should handle permission errors.""" + from pleiades.mcp.tools import validate_resonance_dataset + + with patch("pleiades.mcp.tools.workflows.validate_dataset") as mock_validate: + mock_validate.side_effect = PermissionError("Access denied") + + result = validate_resonance_dataset("/restricted/path") + + assert result["status"] == "error" + assert "error" in result + + def test_extract_manifest_handles_yaml_error(self): + """extract_resonance_manifest should handle YAML parsing errors.""" + from pleiades.mcp.tools import extract_resonance_manifest + + with patch("pleiades.mcp.tools.workflows.extract_manifest") as mock_extract: + mock_extract.side_effect = ValueError("Invalid YAML format") + + result = extract_resonance_manifest("/test/path") + + assert result["status"] == "error" + assert "error" in result + + def test_analyze_resonance_handles_sammy_error(self): + """analyze_resonance should handle SAMMY execution errors.""" + from pleiades.mcp.tools import analyze_resonance + + with patch("pleiades.mcp.tools.workflows.analyze_resonance") as mock_analyze: + mock_analyze.side_effect = RuntimeError("SAMMY executable not found") + + result = analyze_resonance("/test/path") + + assert result["status"] == "error" + assert "error" in result + + def test_empty_path_handled_gracefully(self): + """Empty path should be handled gracefully.""" + from pleiades.mcp.tools import validate_resonance_dataset + + with patch("pleiades.mcp.tools.workflows.validate_dataset") as mock_validate: + mock_result = MagicMock() + mock_result.valid = False + mock_result.model_dump.return_value = {"valid": False} + mock_validate.return_value = mock_result + + result = validate_resonance_dataset("") + + assert isinstance(result, dict) + assert "status" in result + + def test_none_result_from_workflow_handled(self): + """None result from workflow should be handled.""" + from pleiades.mcp.tools import extract_resonance_manifest + + with patch("pleiades.mcp.tools.workflows.extract_manifest") as mock_extract: + mock_extract.return_value = None + + result = extract_resonance_manifest("/test/path") + + assert isinstance(result, dict) + assert "status" in result + + +class TestOutputConsistency: + """Test consistency of output format across all tools.""" + + def setup_method(self): + """Clear registry before each test.""" + from pleiades.mcp.decorators import clear_registry + + clear_registry() + + def test_all_tools_return_dict(self): + """All tools should return dict.""" + from pleiades.mcp.tools import ( + analyze_resonance, + extract_resonance_manifest, + validate_resonance_dataset, + ) + + with patch("pleiades.mcp.tools.workflows.validate_dataset") as mock_validate: + mock_result = MagicMock() + mock_result.model_dump.return_value = {} + mock_validate.return_value = mock_result + result = validate_resonance_dataset("/test") + assert isinstance(result, dict) + + with patch("pleiades.mcp.tools.workflows.extract_manifest") as mock_extract: + mock_extract.return_value = None + result = extract_resonance_manifest("/test") + assert isinstance(result, dict) + + with patch("pleiades.mcp.tools.workflows.analyze_resonance") as mock_analyze: + mock_result = MagicMock() + mock_result.model_dump.return_value = {} + mock_analyze.return_value = mock_result + result = analyze_resonance("/test") + assert isinstance(result, dict) + + def test_all_tools_include_status_field(self): + """All tools should include status field.""" + from pleiades.mcp.tools import ( + analyze_resonance, + extract_resonance_manifest, + validate_resonance_dataset, + ) + + with patch("pleiades.mcp.tools.workflows.validate_dataset") as mock_validate: + mock_result = MagicMock() + mock_result.model_dump.return_value = {} + mock_validate.return_value = mock_result + result = validate_resonance_dataset("/test") + assert "status" in result + + with patch("pleiades.mcp.tools.workflows.extract_manifest") as mock_extract: + mock_extract.return_value = None + result = extract_resonance_manifest("/test") + assert "status" in result + + with patch("pleiades.mcp.tools.workflows.analyze_resonance") as mock_analyze: + mock_result = MagicMock() + mock_result.model_dump.return_value = {} + mock_analyze.return_value = mock_result + result = analyze_resonance("/test") + assert "status" in result + + def test_success_results_have_data_field(self): + """Successful results should have data field.""" + from pleiades.mcp.tools import validate_resonance_dataset + + with patch("pleiades.mcp.tools.workflows.validate_dataset") as mock_validate: + mock_result = MagicMock() + mock_result.valid = True + mock_result.model_dump.return_value = {"valid": True} + mock_validate.return_value = mock_result + + result = validate_resonance_dataset("/test") + + if result["status"] == "success": + assert "data" in result + + def test_error_results_have_error_field(self): + """Error results should have error field.""" + from pleiades.mcp.tools import validate_resonance_dataset + + with patch("pleiades.mcp.tools.workflows.validate_dataset") as mock_validate: + mock_validate.side_effect = ValueError("Test error") + + result = validate_resonance_dataset("/test") + + if result["status"] == "error": + assert "error" in result + + +class TestParameterDescriptions: + """Test that tools have helpful parameter descriptions.""" + + def setup_method(self): + """Clear and re-register all tools for consistent test isolation.""" + import importlib + + import pleiades.mcp.tools as tools_module + from pleiades.mcp.decorators import clear_registry + + # Always clear and reload for consistent test isolation + clear_registry() + importlib.reload(tools_module) + + def test_validate_dataset_has_parameter_description(self): + """validate_resonance_dataset should document its parameters.""" + from pleiades.mcp.decorators import get_registered_tools + + tools = get_registered_tools() + tool = tools["validate_resonance_dataset"] + + # Parameter descriptions are optional but recommended + if tool.get("parameter_descriptions"): + assert "dataset_path" in tool["parameter_descriptions"] + + def test_extract_manifest_has_parameter_description(self): + """extract_resonance_manifest should document its parameters.""" + from pleiades.mcp.decorators import get_registered_tools + + tools = get_registered_tools() + tool = tools["extract_resonance_manifest"] + + if tool.get("parameter_descriptions"): + assert "dataset_path" in tool["parameter_descriptions"] + + def test_analyze_resonance_has_parameter_descriptions(self): + """analyze_resonance should document its parameters.""" + from pleiades.mcp.decorators import get_registered_tools + + tools = get_registered_tools() + tool = tools["analyze_resonance"] + + if tool.get("parameter_descriptions"): + # Should document at least the main parameter + assert "dataset_path" in tool["parameter_descriptions"] + + +class TestJsonSerializationEdgeCases: + """Test _convert_to_json_serializable handles all edge cases.""" + + def test_circular_reference_protection(self): + """Should detect deeply nested structures that exceed depth limit.""" + from pleiades.mcp.tools import _convert_to_json_serializable + + # Create deeply nested structure exceeding _MAX_SERIALIZATION_DEPTH (100) + deep = {"level": 0} + current = deep + for i in range(150): + current["next"] = {"level": i + 1} + current = current["next"] + + with pytest.raises(ValueError, match="maximum serialization depth"): + _convert_to_json_serializable(deep) + + def test_handles_datetime(self): + """Should convert datetime to ISO format string.""" + import json + from datetime import datetime + + from pleiades.mcp.tools import _convert_to_json_serializable + + dt = datetime(2024, 6, 15, 12, 30, 45) + result = _convert_to_json_serializable(dt) + + assert result == "2024-06-15T12:30:45" + # Verify JSON-serializable + json.dumps(result) + + def test_handles_date(self): + """Should convert date to ISO format string.""" + import json + from datetime import date + + from pleiades.mcp.tools import _convert_to_json_serializable + + d = date(2024, 6, 15) + result = _convert_to_json_serializable(d) + + assert result == "2024-06-15" + json.dumps(result) + + def test_handles_time(self): + """Should convert time to ISO format string.""" + import json + from datetime import time + + from pleiades.mcp.tools import _convert_to_json_serializable + + t = time(12, 30, 45) + result = _convert_to_json_serializable(t) + + assert result == "12:30:45" + json.dumps(result) + + def test_handles_decimal(self): + """Should convert Decimal to float.""" + import json + from decimal import Decimal + + from pleiades.mcp.tools import _convert_to_json_serializable + + d = Decimal("3.14159") + result = _convert_to_json_serializable(d) + + assert isinstance(result, float) + assert abs(result - 3.14159) < 0.00001 + json.dumps(result) + + def test_handles_bytes(self): + """Should convert bytes to UTF-8 string.""" + import json + + from pleiades.mcp.tools import _convert_to_json_serializable + + b = b"hello world" + result = _convert_to_json_serializable(b) + + assert result == "hello world" + json.dumps(result) + + def test_handles_bytes_with_invalid_utf8(self): + """Should handle bytes with invalid UTF-8 using replacement.""" + import json + + from pleiades.mcp.tools import _convert_to_json_serializable + + b = b"hello \xff\xfe world" + result = _convert_to_json_serializable(b) + + assert isinstance(result, str) + assert "hello" in result + assert "world" in result + json.dumps(result) + + def test_handles_set(self): + """Should convert set to sorted list.""" + import json + + from pleiades.mcp.tools import _convert_to_json_serializable + + s = {3, 1, 2} + result = _convert_to_json_serializable(s) + + assert isinstance(result, list) + assert result == [1, 2, 3] + json.dumps(result) + + def test_handles_frozenset(self): + """Should convert frozenset to sorted list.""" + import json + + from pleiades.mcp.tools import _convert_to_json_serializable + + fs = frozenset(["c", "a", "b"]) + result = _convert_to_json_serializable(fs) + + assert isinstance(result, list) + assert result == ["a", "b", "c"] + json.dumps(result) + + def test_handles_enum(self): + """Should convert Enum to its value.""" + import json + from enum import Enum + + from pleiades.mcp.tools import _convert_to_json_serializable + + class Color(Enum): + RED = "red" + GREEN = "green" + + result = _convert_to_json_serializable(Color.RED) + + assert result == "red" + json.dumps(result) + + def test_handles_int_enum(self): + """Should convert IntEnum to its value.""" + import json + from enum import IntEnum + + from pleiades.mcp.tools import _convert_to_json_serializable + + class Priority(IntEnum): + LOW = 1 + HIGH = 2 + + result = _convert_to_json_serializable(Priority.HIGH) + + assert result == 2 + json.dumps(result) + + def test_handles_unknown_type_with_fallback(self): + """Should convert unknown types to string with warning.""" + import json + + from pleiades.mcp.tools import _convert_to_json_serializable + + class CustomClass: + def __str__(self): + return "custom_instance" + + obj = CustomClass() + result = _convert_to_json_serializable(obj) + + assert result == "custom_instance" + json.dumps(result) + + def test_handles_nested_mixed_types(self): + """Should handle complex nested structures with mixed types.""" + import json + from datetime import datetime + from decimal import Decimal + from enum import Enum + + from pleiades.mcp.tools import _convert_to_json_serializable + + class Status(Enum): + ACTIVE = "active" + + data = { + "timestamp": datetime(2024, 1, 1, 12, 0), + "values": {1, 2, 3}, + "price": Decimal("99.99"), + "status": Status.ACTIVE, + "nested": { + "path": Path("/test/path"), + "items": [datetime(2024, 1, 2), b"data"], + }, + } + + result = _convert_to_json_serializable(data) + + # Verify all types converted + assert result["timestamp"] == "2024-01-01T12:00:00" + assert result["values"] == [1, 2, 3] + assert isinstance(result["price"], float) + assert result["status"] == "active" + assert result["nested"]["path"] == "/test/path" + assert result["nested"]["items"][0] == "2024-01-02T00:00:00" + assert result["nested"]["items"][1] == "data" + + # Verify entire result is JSON-serializable + json.dumps(result) + + +if __name__ == "__main__": + pytest.main(["-v", __file__]) diff --git a/tests/unit/pleiades/workflows/__init__.py b/tests/unit/pleiades/workflows/__init__.py new file mode 100644 index 00000000..3a27aa5b --- /dev/null +++ b/tests/unit/pleiades/workflows/__init__.py @@ -0,0 +1 @@ +"""Tests for pleiades.workflows module.""" diff --git a/tests/unit/pleiades/workflows/test_models.py b/tests/unit/pleiades/workflows/test_models.py new file mode 100644 index 00000000..05c6349c --- /dev/null +++ b/tests/unit/pleiades/workflows/test_models.py @@ -0,0 +1,372 @@ +"""Tests for pleiades.workflows.models module.""" + +import math +from pathlib import Path + +import pytest + +from pleiades.workflows.models import ( + FitQuality, + ManifestData, + MaterialProperties, + ResonanceResult, + ValidationIssue, + ValidationResult, + WorkflowType, +) + + +class TestFitQuality: + """Tests for FitQuality enum.""" + + def test_from_chi_squared_excellent(self): + """Chi² < 1.2 should be excellent.""" + assert FitQuality.from_chi_squared(0.9) == FitQuality.EXCELLENT + assert FitQuality.from_chi_squared(1.1) == FitQuality.EXCELLENT + + def test_from_chi_squared_good(self): + """1.2 <= Chi² < 2.0 should be good.""" + assert FitQuality.from_chi_squared(1.2) == FitQuality.GOOD + assert FitQuality.from_chi_squared(1.5) == FitQuality.GOOD + assert FitQuality.from_chi_squared(1.99) == FitQuality.GOOD + + def test_from_chi_squared_acceptable(self): + """2.0 <= Chi² < 5.0 should be acceptable.""" + assert FitQuality.from_chi_squared(2.0) == FitQuality.ACCEPTABLE + assert FitQuality.from_chi_squared(3.5) == FitQuality.ACCEPTABLE + assert FitQuality.from_chi_squared(4.99) == FitQuality.ACCEPTABLE + + def test_from_chi_squared_poor(self): + """Chi² >= 5.0 should be poor.""" + assert FitQuality.from_chi_squared(5.0) == FitQuality.POOR + assert FitQuality.from_chi_squared(10.0) == FitQuality.POOR + + def test_from_chi_squared_invalid_negative(self): + """Negative chi-squared should raise ValueError.""" + with pytest.raises(ValueError, match="Invalid reduced chi-squared"): + FitQuality.from_chi_squared(-1.0) + + def test_from_chi_squared_invalid_nan(self): + """NaN chi-squared should raise ValueError.""" + with pytest.raises(ValueError, match="Invalid reduced chi-squared"): + FitQuality.from_chi_squared(math.nan) + + def test_from_chi_squared_invalid_infinity(self): + """Infinite chi-squared should raise ValueError.""" + with pytest.raises(ValueError, match="Invalid reduced chi-squared"): + FitQuality.from_chi_squared(math.inf) + + +class TestWorkflowType: + """Tests for WorkflowType enum.""" + + def test_workflow_type_values(self): + """WorkflowType should have expected values.""" + assert WorkflowType.FULL.value == "full" + assert WorkflowType.SIMPLIFIED.value == "simplified" + + +class TestMaterialProperties: + """Tests for MaterialProperties model.""" + + def test_create_with_required_fields(self): + """MaterialProperties should require density and atomic mass.""" + mp = MaterialProperties( + density_g_cm3=19.3, + atomic_mass_amu=196.97, + ) + assert mp.density_g_cm3 == 19.3 + assert mp.atomic_mass_amu == 196.97 + assert mp.temperature_k is None + + def test_create_with_all_fields(self): + """MaterialProperties should accept optional temperature.""" + mp = MaterialProperties( + density_g_cm3=19.3, + atomic_mass_amu=196.97, + temperature_k=293.6, + ) + assert mp.temperature_k == 293.6 + + def test_missing_required_field_raises(self): + """Missing required fields should raise ValidationError.""" + with pytest.raises(Exception): # Pydantic ValidationError + MaterialProperties(density_g_cm3=19.3) + + def test_negative_density_raises(self): + """Negative density should raise ValidationError.""" + with pytest.raises(Exception, match="must be positive"): + MaterialProperties(density_g_cm3=-1.0, atomic_mass_amu=196.97) + + def test_zero_density_raises(self): + """Zero density should raise ValidationError.""" + with pytest.raises(Exception, match="must be positive"): + MaterialProperties(density_g_cm3=0.0, atomic_mass_amu=196.97) + + def test_negative_atomic_mass_raises(self): + """Negative atomic mass should raise ValidationError.""" + with pytest.raises(Exception, match="must be positive"): + MaterialProperties(density_g_cm3=19.3, atomic_mass_amu=-10.0) + + def test_negative_temperature_raises(self): + """Negative temperature should raise ValidationError.""" + with pytest.raises(Exception, match="must be positive"): + MaterialProperties(density_g_cm3=19.3, atomic_mass_amu=196.97, temperature_k=-100.0) + + +class TestValidationIssue: + """Tests for ValidationIssue model.""" + + def test_create_error(self): + """ValidationIssue should accept error severity.""" + issue = ValidationIssue( + severity="error", + message="Missing raw data directory", + path=Path("/data/raw"), + ) + assert issue.severity == "error" + assert "raw data" in issue.message + assert issue.path == Path("/data/raw") + + def test_create_warning(self): + """ValidationIssue should accept warning severity.""" + issue = ValidationIssue( + severity="warning", + message="Open beam directory not found", + ) + assert issue.severity == "warning" + assert issue.path is None + + +class TestValidationResult: + """Tests for ValidationResult model.""" + + def test_valid_dataset(self): + """ValidationResult for valid dataset.""" + result = ValidationResult( + valid=True, + dataset_path=Path("/data/sample"), + can_run_simplified_workflow=True, + recommended_workflow=WorkflowType.SIMPLIFIED, + has_sammy_files=True, + ) + assert result.valid + assert result.can_run_simplified_workflow + assert result.recommended_workflow == WorkflowType.SIMPLIFIED + + def test_invalid_dataset_with_errors(self): + """ValidationResult should track errors.""" + result = ValidationResult( + valid=False, + dataset_path=Path("/data/sample"), + issues=[ + ValidationIssue(severity="error", message="No data found"), + ValidationIssue(severity="warning", message="Missing metadata"), + ], + ) + assert not result.valid + assert len(result.errors) == 1 + assert len(result.warnings) == 1 + + def test_errors_property(self): + """errors property should filter to error-level issues.""" + result = ValidationResult( + valid=False, + dataset_path=Path("/data/sample"), + issues=[ + ValidationIssue(severity="error", message="Error 1"), + ValidationIssue(severity="error", message="Error 2"), + ValidationIssue(severity="warning", message="Warning 1"), + ], + ) + assert len(result.errors) == 2 + assert len(result.warnings) == 1 + + +class TestResonanceResult: + """Tests for ResonanceResult model.""" + + def test_successful_result(self): + """ResonanceResult for successful analysis.""" + result = ResonanceResult( + success=True, + workflow_type=WorkflowType.SIMPLIFIED, + primary_isotope="Au-197", + isotopes_analyzed=["Au-197"], + chi_squared=125.5, + reduced_chi_squared=1.05, + degrees_of_freedom=120, + fit_quality=FitQuality.EXCELLENT, + number_density=0.00123, + temperature_k=293.6, + output_dir=Path("/data/output"), + lpt_file=Path("/data/output/SAMMY.LPT"), + lst_file=Path("/data/output/SAMMY.LST"), + runtime_seconds=5.2, + ) + assert result.success + assert result.primary_isotope == "Au-197" + assert result.fit_quality == FitQuality.EXCELLENT + assert result.error_message is None + + def test_failed_result(self): + """ResonanceResult for failed analysis.""" + result = ResonanceResult( + success=False, + workflow_type=WorkflowType.SIMPLIFIED, + primary_isotope="Au-197", + error_message="SAMMY execution failed", + error_step="sammy_execution", + ) + assert not result.success + assert result.error_message == "SAMMY execution failed" + assert result.error_step == "sammy_execution" + assert result.reduced_chi_squared is None + + +class TestManifestData: + """Tests for ManifestData model.""" + + def test_create_minimal(self): + """ManifestData with only required fields.""" + manifest = ManifestData( + name="test_dataset", + description="Test dataset for unit tests", + version="1.0.0", + created="2024-01-01T00:00:00Z", + ) + assert manifest.name == "test_dataset" + assert manifest.isotope is None + assert manifest.material_properties is None + + def test_create_full(self): + """ManifestData with all fields.""" + manifest = ManifestData( + name="Au197_sample", + description="Gold-197 calibration sample", + version="1.0.0", + created="2024-01-01T00:00:00Z", + facility="SNS", + beamline="VENUS", + detector="MCP", + sample_id="Au197-001", + isotope="Au-197", + material_properties=MaterialProperties( + density_g_cm3=19.3, + atomic_mass_amu=196.97, + ), + body="# Analysis Instructions\n\nRun fitting with default parameters.", + ) + assert manifest.isotope == "Au-197" + assert manifest.material_properties.density_g_cm3 == 19.3 + assert "Analysis Instructions" in manifest.body + + def test_default_use_natural_abundance(self): + """ManifestData should default to using natural abundance.""" + manifest = ManifestData( + name="test_dataset", + description="Test", + version="1.0.0", + created="2024-01-01T00:00:00Z", + ) + assert manifest.use_natural_abundance is True + assert manifest.enrichment is None + + def test_enriched_sample_configuration(self): + """ManifestData should support enriched sample configuration.""" + enrichment_data = {"U-235": 0.90, "U-238": 0.10} + manifest = ManifestData( + name="enriched_U235", + description="Enriched uranium sample", + version="1.0.0", + created="2024-01-01T00:00:00Z", + isotope="U-235", + use_natural_abundance=False, + enrichment=enrichment_data, + ) + assert manifest.use_natural_abundance is False + assert manifest.enrichment == enrichment_data + assert manifest.enrichment["U-235"] == 0.90 + + def test_enrichment_without_flag_defaults_to_natural(self): + """Enrichment dict alone doesn't change use_natural_abundance default.""" + manifest = ManifestData( + name="test", + description="Test", + version="1.0.0", + created="2024-01-01T00:00:00Z", + enrichment={"Hf-177": 0.95, "Hf-178": 0.05}, + ) + # Enrichment provided but flag still True - user must explicitly set False + assert manifest.use_natural_abundance is True + + def test_enrichment_values_should_be_fractions(self): + """Enrichment values should be fractions (0-1), not percentages.""" + # This is a design constraint - values should be fractions for consistency + # with IsotopeManager.get_natural_composition() + manifest = ManifestData( + name="test", + description="Test", + version="1.0.0", + created="2024-01-01T00:00:00Z", + use_natural_abundance=False, + enrichment={"Pu-239": 0.94, "Pu-240": 0.06}, + ) + # Verify enrichment values sum to 1.0 (fractions, not percentages) + total = sum(manifest.enrichment.values()) + assert abs(total - 1.0) < 0.01 + + def test_enrichment_sum_not_1_raises(self): + """Enrichment values that don't sum to 1.0 should raise validation error.""" + with pytest.raises(Exception, match="sum to approximately 1.0"): + ManifestData( + name="test", + description="Test", + version="1.0.0", + created="2024-01-01T00:00:00Z", + enrichment={"U-235": 0.95, "U-238": 0.95}, # Sums to 1.9 + ) + + def test_negative_enrichment_values_raises(self): + """Negative enrichment values should raise validation error.""" + with pytest.raises(Exception, match="cannot be negative"): + ManifestData( + name="test", + description="Test", + version="1.0.0", + created="2024-01-01T00:00:00Z", + enrichment={"U-235": -0.5, "U-238": 1.5}, + ) + + def test_enrichment_exceeds_1_raises(self): + """Enrichment values exceeding 1.0 should raise validation error.""" + with pytest.raises(Exception, match="exceeds 1.0"): + ManifestData( + name="test", + description="Test", + version="1.0.0", + created="2024-01-01T00:00:00Z", + enrichment={"U-235": 1.5, "U-238": 0.0}, + ) + + def test_invalid_isotope_key_format_raises(self): + """Malformed isotope keys in enrichment should raise validation error.""" + with pytest.raises(Exception, match="Invalid isotope key"): + ManifestData( + name="test", + description="Test", + version="1.0.0", + created="2024-01-01T00:00:00Z", + enrichment={"Uranium-235": 1.0}, # Full element name instead of symbol + ) + + def test_invalid_isotope_format_raises(self): + """Isotope keys without mass number should raise validation error.""" + with pytest.raises(Exception, match="Invalid isotope key"): + ManifestData( + name="test", + description="Test", + version="1.0.0", + created="2024-01-01T00:00:00Z", + enrichment={"U": 1.0}, # Missing mass number + ) diff --git a/tests/unit/pleiades/workflows/test_resonance.py b/tests/unit/pleiades/workflows/test_resonance.py new file mode 100644 index 00000000..aaf6b2a5 --- /dev/null +++ b/tests/unit/pleiades/workflows/test_resonance.py @@ -0,0 +1,1027 @@ +"""Tests for pleiades.workflows.resonance module.""" + +from types import SimpleNamespace +from unittest.mock import MagicMock, patch + +import pytest + +from pleiades.workflows.models import ManifestData, WorkflowType +from pleiades.workflows.resonance import ( + _get_isotope_composition, + extract_manifest, + validate_dataset, +) + + +class TestValidateDataset: + """Tests for validate_dataset function.""" + + def test_nonexistent_path(self, tmp_path): + """Validation should fail for nonexistent path.""" + result = validate_dataset(tmp_path / "nonexistent") + assert not result.valid + assert len(result.errors) == 1 + assert "does not exist" in result.errors[0].message + + def test_file_not_directory(self, tmp_path): + """Validation should fail if path is a file.""" + test_file = tmp_path / "test.txt" + test_file.write_text("test") + + result = validate_dataset(test_file) + assert not result.valid + assert "not a directory" in result.errors[0].message + + def test_empty_directory(self, tmp_path): + """Validation should fail for empty directory.""" + result = validate_dataset(tmp_path) + assert not result.valid + assert not result.can_run_full_workflow + assert not result.can_run_simplified_workflow + + def test_simplified_workflow_valid(self, tmp_path): + """Dataset with SAMMY files should support simplified workflow.""" + sammy_dir = tmp_path / "sammy_data" + sammy_dir.mkdir() + + # Create SAMMY input files + (sammy_dir / "ex012a.inp").write_text("SAMMY input") + (sammy_dir / "ex012a.par").write_text("SAMMY params") + (sammy_dir / "ex012a.dat").write_text("SAMMY data") + + result = validate_dataset(tmp_path) + assert result.valid + assert result.can_run_simplified_workflow + assert not result.can_run_full_workflow + assert result.recommended_workflow == WorkflowType.SIMPLIFIED + assert result.has_sammy_files + + def test_simplified_workflow_missing_par(self, tmp_path): + """Missing .par file should be an error.""" + sammy_dir = tmp_path / "sammy_data" + sammy_dir.mkdir() + + (sammy_dir / "ex012a.inp").write_text("SAMMY input") + (sammy_dir / "ex012a.dat").write_text("SAMMY data") + + result = validate_dataset(tmp_path) + assert not result.valid + assert any("Missing parameter file" in e.message for e in result.errors) + + def test_full_workflow_valid(self, tmp_path): + """Dataset with imaging data should support full workflow.""" + (tmp_path / "raw").mkdir() + (tmp_path / "open_beam").mkdir() + + result = validate_dataset(tmp_path) + assert result.valid + assert result.can_run_full_workflow + assert not result.can_run_simplified_workflow + assert result.recommended_workflow == WorkflowType.FULL + assert result.has_raw_data + assert result.has_open_beam + + def test_full_workflow_missing_open_beam(self, tmp_path): + """Missing open_beam makes full workflow unavailable (invalid dataset).""" + (tmp_path / "raw").mkdir() + + result = validate_dataset(tmp_path) + # Dataset is invalid because full workflow needs both raw and open_beam + # and no simplified workflow is available either + assert not result.valid + assert result.has_raw_data + assert not result.has_open_beam + assert not result.can_run_full_workflow + # Should have warning about missing open_beam + assert any("Open beam directory" in w.message for w in result.warnings) + + def test_manifest_detection(self, tmp_path): + """Manifest file should be detected.""" + (tmp_path / "sammy_data").mkdir() + (tmp_path / "sammy_data" / "test.inp").write_text("input") + (tmp_path / "sammy_data" / "test.par").write_text("params") + (tmp_path / "sammy_data" / "test.dat").write_text("data") + + # Create manifest + manifest_content = """--- +name: test_dataset +description: Test +tool: pleiades +physics: nri +version: "1.0.0" +created: "2024-01-01T00:00:00Z" +--- +# Instructions +""" + (tmp_path / "manifest_intermediate.md").write_text(manifest_content) + + result = validate_dataset(tmp_path) + assert result.valid + assert result.has_manifest + + def test_both_workflow_types_available(self, tmp_path): + """Dataset with both imaging data and SAMMY files should prefer full workflow.""" + # Create imaging data + (tmp_path / "raw").mkdir() + (tmp_path / "open_beam").mkdir() + + # Create SAMMY files + sammy_dir = tmp_path / "sammy_data" + sammy_dir.mkdir() + (sammy_dir / "test.inp").write_text("input") + (sammy_dir / "test.par").write_text("params") + (sammy_dir / "test.dat").write_text("data") + + result = validate_dataset(tmp_path) + assert result.valid + assert result.can_run_full_workflow + assert result.can_run_simplified_workflow + assert result.recommended_workflow == WorkflowType.FULL + + +class TestExtractManifest: + """Tests for extract_manifest function.""" + + def test_no_manifest(self, tmp_path): + """Should return None if no manifest found.""" + result = extract_manifest(tmp_path) + assert result is None + + def test_valid_manifest(self, tmp_path): + """Should parse valid manifest.""" + manifest_content = """--- +name: Au197_sample +description: Gold calibration sample +tool: pleiades +physics: nri +version: "1.0.0" +created: "2024-01-01T00:00:00Z" +facility: SNS +beamline: VENUS +isotope: Au-197 +--- +# Analysis Instructions + +Run with default parameters. +""" + (tmp_path / "manifest_intermediate.md").write_text(manifest_content) + + result = extract_manifest(tmp_path) + assert result is not None + assert result.name == "Au197_sample" + assert result.isotope == "Au-197" + assert result.facility == "SNS" + assert "Analysis Instructions" in result.body + + def test_manifest_with_material_properties(self, tmp_path): + """Should parse material_properties from manifest.""" + manifest_content = """--- +name: test +description: Test +tool: pleiades +physics: nri +version: "1.0.0" +created: "2024-01-01T00:00:00Z" +material_properties: + density_g_cm3: 19.3 + atomic_mass_amu: 196.97 + temperature_k: 293.6 +--- +Body content +""" + (tmp_path / "manifest_intermediate.md").write_text(manifest_content) + + result = extract_manifest(tmp_path) + assert result is not None + assert result.material_properties is not None + assert result.material_properties.density_g_cm3 == 19.3 + assert result.material_properties.atomic_mass_amu == 196.97 + assert result.material_properties.temperature_k == 293.6 + + def test_invalid_manifest_format(self, tmp_path): + """Should raise ValueError for invalid format.""" + (tmp_path / "manifest_intermediate.md").write_text("No frontmatter here") + + with pytest.raises(ValueError, match="missing YAML frontmatter"): + extract_manifest(tmp_path) + + def test_alternate_manifest_names(self, tmp_path): + """Should find manifest with alternate names.""" + manifest_content = """--- +name: test +description: Test +tool: pleiades +physics: nri +version: "1.0.0" +created: "2024-01-01T00:00:00Z" +--- +Body +""" + # Test smcp_manifest.md + (tmp_path / "smcp_manifest.md").write_text(manifest_content) + + result = extract_manifest(tmp_path) + assert result is not None + assert result.name == "test" + + def test_manifest_with_horizontal_rule_in_body(self, tmp_path): + """Should handle markdown body containing --- (horizontal rule).""" + manifest_content = """--- +name: test +description: Test with horizontal rule +tool: pleiades +physics: nri +version: "1.0.0" +created: "2024-01-01T00:00:00Z" +--- +# Section 1 + +Some content here. + +--- + +# Section 2 + +More content after horizontal rule. + +--- + +Final section. +""" + (tmp_path / "manifest_intermediate.md").write_text(manifest_content) + + result = extract_manifest(tmp_path) + assert result is not None + assert result.name == "test" + # Body should contain the horizontal rules + assert "---" in result.body + assert "Section 1" in result.body + assert "Section 2" in result.body + assert "Final section" in result.body + + +class TestAnalyzeResonance: + """Tests for analyze_resonance function.""" + + def test_invalid_dataset_fails(self, tmp_path): + """Analysis should fail for invalid dataset.""" + from pleiades.workflows.resonance import analyze_resonance + + result = analyze_resonance(tmp_path / "nonexistent") + assert not result.success + assert result.error_step == "validation" + + def test_skip_validation(self, tmp_path): + """skip_validation should bypass validation check.""" + from pleiades.workflows.resonance import analyze_resonance + + # Empty directory, would fail validation + result = analyze_resonance(tmp_path, skip_validation=True) + # Should fail at file discovery, not validation + assert not result.success + assert result.error_step == "file_discovery" + assert "No SAMMY files" in result.error_message + + @patch("pleiades.sammy.results.manager.ResultsManager") + @patch("pleiades.sammy.factory.SammyFactory") + def test_simplified_workflow_success(self, mock_factory, mock_results_manager_class, tmp_path): + """Simplified workflow should succeed with mocked SAMMY.""" + from pleiades.workflows.resonance import analyze_resonance + + # Create SAMMY files + sammy_dir = tmp_path / "sammy_data" + sammy_dir.mkdir() + (sammy_dir / "ex012a.inp").write_text("input") + (sammy_dir / "ex012a.par").write_text("params") + (sammy_dir / "ex012a.dat").write_text("data") + + # Create expected SAMMY output files (these are checked before parsing) + sammy_output = tmp_path / "sammy_output" + sammy_output.mkdir() + (sammy_output / "SAMMY.LPT").write_text("log output") + (sammy_output / "SAMMY.LST").write_text("list output") + + # Mock SAMMY runner + mock_runner = MagicMock() + mock_exec_result = MagicMock() + mock_exec_result.success = True + mock_runner.execute_sammy.return_value = mock_exec_result + mock_factory.auto_select.return_value = mock_runner + + # Mock results manager + mock_fit_result = MagicMock() + mock_fit_result.chi_squared_results.chi_squared = 125.5 + mock_fit_result.chi_squared_results.reduced_chi_squared = 1.05 + mock_fit_result.chi_squared_results.dof = 120 + mock_fit_result.physics_data.broadening_parameters.temp = 293.6 + mock_fit_result.physics_data.broadening_parameters.thick = 0.00123 + + mock_run_results = MagicMock() + mock_run_results.fit_results = [mock_fit_result] + mock_results_manager_class.return_value.run_results = mock_run_results + + result = analyze_resonance(tmp_path) + + assert result.success + assert result.workflow_type == WorkflowType.SIMPLIFIED + assert result.reduced_chi_squared == 1.05 + assert result.temperature_k == 293.6 + + def test_full_workflow_handles_empty_raw_folder(self, tmp_path): + """Full workflow should handle empty raw folder gracefully.""" + from pleiades.workflows.resonance import analyze_resonance + + # Create imaging data structure with empty folders + (tmp_path / "raw").mkdir() + (tmp_path / "open_beam").mkdir() + + result = analyze_resonance(tmp_path) + + assert not result.success + assert result.workflow_type == WorkflowType.FULL + # The normalization step should fail because there are no TIFF files + assert result.error_step == "normalization" + assert "Normalization failed" in result.error_message or "No files found" in result.error_message + + @patch("pleiades.workflows.resonance._execute_full_workflow") + @patch("pleiades.workflows.resonance.extract_manifest", return_value=None) + @patch("pleiades.workflows.resonance.validate_dataset") + def test_analyze_prefers_full_workflow_when_both_available( + self, + mock_validate, + _mock_extract_manifest, + mock_execute_full_workflow, + tmp_path, + ): + """When both workflows are valid, analyze_resonance should run the full workflow.""" + from pleiades.workflows.resonance import analyze_resonance + + mock_validate.return_value = SimpleNamespace( + valid=True, + can_run_full_workflow=True, + can_run_simplified_workflow=True, + ) + sentinel = MagicMock() + mock_execute_full_workflow.return_value = sentinel + + result = analyze_resonance(tmp_path) + + mock_execute_full_workflow.assert_called_once() + assert result is sentinel + + @patch("pleiades.workflows.resonance._execute_simplified_workflow") + @patch("pleiades.workflows.resonance.extract_manifest", return_value=None) + @patch("pleiades.workflows.resonance.validate_dataset") + def test_analyze_rejects_isotopes_in_simplified_workflow( + self, + mock_validate, + _mock_extract_manifest, + mock_execute_simplified_workflow, + tmp_path, + ): + """Supplying isotopes for simplified workflow should return a parameter error.""" + from pleiades.workflows.resonance import analyze_resonance + + mock_validate.return_value = SimpleNamespace( + valid=True, + can_run_full_workflow=False, + can_run_simplified_workflow=True, + ) + + result = analyze_resonance(tmp_path, isotopes=["Hf-177"]) + + assert not result.success + assert result.error_step == "parameter_validation" + assert "isotopes parameter" in result.error_message + mock_execute_simplified_workflow.assert_not_called() + + @patch("pleiades.sammy.results.manager.ResultsManager") + @patch("pleiades.sammy.factory.SammyFactory") + def test_empty_fit_results_handled(self, mock_factory, mock_results_manager_class, tmp_path): + """Empty fit_results list should return error, not crash.""" + from pleiades.workflows.resonance import analyze_resonance + + # Create SAMMY files + sammy_dir = tmp_path / "sammy_data" + sammy_dir.mkdir() + (sammy_dir / "ex012a.inp").write_text("input") + (sammy_dir / "ex012a.par").write_text("params") + (sammy_dir / "ex012a.dat").write_text("data") + + # Create expected SAMMY output files (these are checked before parsing) + sammy_output = tmp_path / "sammy_output" + sammy_output.mkdir() + (sammy_output / "SAMMY.LPT").write_text("log output") + (sammy_output / "SAMMY.LST").write_text("list output") + + # Mock SAMMY runner + mock_runner = MagicMock() + mock_exec_result = MagicMock() + mock_exec_result.success = True + mock_runner.execute_sammy.return_value = mock_exec_result + mock_factory.auto_select.return_value = mock_runner + + # Mock results manager with EMPTY fit_results + mock_run_results = MagicMock() + mock_run_results.fit_results = [] # Empty list + mock_results_manager_class.return_value.run_results = mock_run_results + + result = analyze_resonance(tmp_path) + + # Should fail gracefully, not crash with IndexError + assert not result.success + assert result.error_step == "results_parsing" + assert "No fit results" in result.error_message + + +class TestExtractManifestEnrichment: + """Tests for enrichment parsing in extract_manifest (Issue #204).""" + + def test_manifest_default_natural_abundance(self, tmp_path): + """Should default to use_natural_abundance=True.""" + manifest_content = """--- +name: test +description: Test +version: "1.0.0" +created: "2024-01-01T00:00:00Z" +isotope: Hf-177 +--- +Body +""" + (tmp_path / "manifest_intermediate.md").write_text(manifest_content) + + result = extract_manifest(tmp_path) + assert result is not None + assert result.use_natural_abundance is True + assert result.enrichment is None + + def test_manifest_enriched_sample(self, tmp_path): + """Should parse enrichment for enriched samples.""" + manifest_content = """--- +name: enriched_u235 +description: Enriched uranium sample +version: "1.0.0" +created: "2024-01-01T00:00:00Z" +isotope: U-235 +use_natural_abundance: false +enrichment: + U-235: 0.90 + U-238: 0.10 +--- +Body +""" + (tmp_path / "manifest_intermediate.md").write_text(manifest_content) + + result = extract_manifest(tmp_path) + assert result is not None + assert result.use_natural_abundance is False + assert result.enrichment == {"U-235": 0.90, "U-238": 0.10} + + +class TestGetIsotopeComposition: + """Tests for _get_isotope_composition helper (Issue #204).""" + + def test_user_specified_isotopes_equal_weights(self): + """User-specified isotopes should get equal weights.""" + from pleiades.workflows.resonance import _get_isotope_composition + + isotopes, abundances = _get_isotope_composition( + user_isotopes=["Hf-177", "Hf-178"], + manifest=None, + primary_isotope="Hf-177", + ) + + assert isotopes == ["Hf-177", "Hf-178"] + assert len(abundances) == 2 + assert all(a == 0.5 for a in abundances) + + def test_enriched_sample_uses_manifest_enrichment(self): + """Enriched samples should use manifest enrichment values.""" + from pleiades.workflows.models import ManifestData + from pleiades.workflows.resonance import _get_isotope_composition + + manifest = ManifestData( + name="test", + description="Test", + version="1.0.0", + created="2024-01-01T00:00:00Z", + isotope="U-235", + use_natural_abundance=False, + enrichment={"U-235": 0.90, "U-238": 0.10}, + ) + + isotopes, abundances = _get_isotope_composition( + user_isotopes=None, + manifest=manifest, + primary_isotope="U-235", + ) + + assert set(isotopes) == {"U-235", "U-238"} + # Find U-235's abundance + u235_idx = isotopes.index("U-235") + assert abs(abundances[u235_idx] - 0.90) < 0.001 + + def test_natural_abundance_from_isotope_manager(self): + """Natural abundance should come from IsotopeManager.""" + from pleiades.workflows.models import ManifestData + from pleiades.workflows.resonance import _get_isotope_composition + + manifest = ManifestData( + name="test", + description="Test", + version="1.0.0", + created="2024-01-01T00:00:00Z", + isotope="Hf-177", + use_natural_abundance=True, + ) + + isotopes, abundances = _get_isotope_composition( + user_isotopes=None, + manifest=manifest, + primary_isotope="Hf-177", + ) + + # Should return all 6 natural Hf isotopes + assert len(isotopes) == 6 + assert "Hf-174" in isotopes + assert "Hf-180" in isotopes + + # Abundances should sum to ~1.0 + assert abs(sum(abundances) - 1.0) < 0.01 + + # Hf-180 should be most abundant (~35%) + hf180_idx = isotopes.index("Hf-180") + assert abundances[hf180_idx] > 0.30 + + def test_element_only_isotope_uses_natural_abundance(self): + """Element-only isotope (e.g., 'Hf') should use natural abundance.""" + from pleiades.workflows.resonance import _get_isotope_composition + + isotopes, abundances = _get_isotope_composition( + user_isotopes=None, + manifest=None, + primary_isotope="Hf", + ) + + # Should return all natural Hf isotopes + assert len(isotopes) == 6 + assert abs(sum(abundances) - 1.0) < 0.01 + + def test_element_nat_suffix_uses_natural_abundance(self): + """Element-nat isotope (e.g., 'Hf-nat') should use natural abundance.""" + from pleiades.workflows.resonance import _get_isotope_composition + + isotopes, abundances = _get_isotope_composition( + user_isotopes=None, + manifest=None, + primary_isotope="Hf-nat", + ) + + # Should return all natural Hf isotopes + assert len(isotopes) == 6 + + def test_single_natural_isotope_element_100_percent_abundance(self): + """Elements with only one naturally occurring isotope should return that isotope with 100% abundance.""" + from pleiades.workflows.resonance import _get_isotope_composition + + isotopes, abundances = _get_isotope_composition( + user_isotopes=None, + manifest=None, + primary_isotope="Au-197", + ) + + # Gold has only one natural isotope (Au-197) + assert len(isotopes) == 1 + assert isotopes[0] == "Au-197" + assert abundances[0] == 1.0 + + def test_unknown_element_raises_error(self): + """Unknown element should raise ValueError.""" + from pleiades.workflows.resonance import _get_isotope_composition + + with pytest.raises(ValueError, match="No natural isotopes found"): + _get_isotope_composition( + user_isotopes=None, + manifest=None, + primary_isotope="Xx-999", + ) + + def test_empty_primary_isotope_raises(self): + """Empty primary_isotope should raise ValueError.""" + from pleiades.workflows.resonance import _get_isotope_composition + + with pytest.raises(ValueError, match="cannot be empty"): + _get_isotope_composition( + user_isotopes=None, + manifest=None, + primary_isotope="", + ) + + def test_malformed_primary_isotope_raises(self): + """Malformed primary_isotope should raise ValueError.""" + from pleiades.workflows.resonance import _get_isotope_composition + + # Test various malformed formats + malformed_inputs = [ + "Hf-", # Trailing dash + "-177", # Leading dash + "177-Hf", # Reversed format + "hf177", # Missing dash + "123", # Just numbers + ] + + for bad_input in malformed_inputs: + with pytest.raises(ValueError, match="Invalid primary_isotope format"): + _get_isotope_composition( + user_isotopes=None, + manifest=None, + primary_isotope=bad_input, + ) + + +class TestGetSammyRunner: + """Tests for _get_sammy_runner helper.""" + + @patch("pleiades.sammy.factory.SammyFactory") + def test_auto_backend(self, mock_factory, tmp_path): + """auto backend should call SammyFactory.auto_select.""" + from pleiades.workflows.resonance import _get_sammy_runner + + working = tmp_path / "work" + output = tmp_path / "output" + + _get_sammy_runner("auto", working, output) + + mock_factory.auto_select.assert_called_once() + + @patch("pleiades.sammy.factory.SammyFactory") + def test_specific_backend(self, mock_factory, tmp_path): + """Specific backend should call SammyFactory.create_runner.""" + from pleiades.workflows.resonance import _get_sammy_runner + + working = tmp_path / "work" + output = tmp_path / "output" + + _get_sammy_runner("docker", working, output) + + mock_factory.create_runner.assert_called_once_with( + backend_type="docker", + working_dir=working, + output_dir=output, + ) + + @patch("pleiades.sammy.factory.SammyFactory") + def test_creates_directories(self, mock_factory, tmp_path): + """Should create working and output directories.""" + from pleiades.workflows.resonance import _get_sammy_runner + + working = tmp_path / "work" + output = tmp_path / "output" + + _get_sammy_runner("auto", working, output) + + assert working.exists() + assert output.exists() + + +class TestManifestDataIsotopesField: + """Tests for ManifestData.isotopes field (Issue #206).""" + + def test_manifest_data_isotopes_field_accepts_valid_list(self): + """Valid isotope list should be accepted by ManifestData.""" + manifest = ManifestData( + name="test", + description="Test manifest with isotopes list", + version="1.0.0", + created="2024-01-01T00:00:00Z", + isotope="Hf-177", + isotopes=["Hf-176", "Hf-177", "Hf-178"], + ) + + assert manifest.isotopes == ["Hf-176", "Hf-177", "Hf-178"] + + def test_manifest_data_isotopes_field_accepts_none(self): + """None should be valid for isotopes field (no filtering).""" + manifest = ManifestData( + name="test", + description="Test manifest without isotopes", + version="1.0.0", + created="2024-01-01T00:00:00Z", + isotope="Hf-177", + isotopes=None, + ) + + assert manifest.isotopes is None + + def test_manifest_data_isotopes_field_accepts_empty_list(self): + """Empty list should be valid for isotopes field.""" + manifest = ManifestData( + name="test", + description="Test manifest with empty isotopes list", + version="1.0.0", + created="2024-01-01T00:00:00Z", + isotope="Hf-177", + isotopes=[], + ) + + assert manifest.isotopes == [] + + def test_manifest_data_isotopes_field_rejects_invalid_isotope_format(self): + """Invalid isotope formats should be rejected with validation error.""" + # Test lowercase element symbol + with pytest.raises(ValueError, match="Invalid isotope format"): + ManifestData( + name="test", + description="Test", + version="1.0.0", + created="2024-01-01T00:00:00Z", + isotope="Hf-177", + isotopes=["hf-177", "Hf-178"], # lowercase 'hf' is invalid + ) + + # Test completely invalid format + with pytest.raises(ValueError, match="Invalid isotope format"): + ManifestData( + name="test", + description="Test", + version="1.0.0", + created="2024-01-01T00:00:00Z", + isotope="Hf-177", + isotopes=["Hf-177", "invalid"], + ) + + # Test missing hyphen + with pytest.raises(ValueError, match="Invalid isotope format"): + ManifestData( + name="test", + description="Test", + version="1.0.0", + created="2024-01-01T00:00:00Z", + isotope="Hf-177", + isotopes=["Hf177"], + ) + + def test_manifest_data_isotopes_field_rejects_non_string_elements(self): + """Non-string elements in isotopes list should be rejected.""" + with pytest.raises(ValueError): + ManifestData( + name="test", + description="Test", + version="1.0.0", + created="2024-01-01T00:00:00Z", + isotope="Hf-177", + isotopes=[177, "Hf-177"], # Integer is invalid + ) + + +class TestExtractManifestIsotopesList: + """Tests for extract_manifest parsing of isotopes list (Issue #206).""" + + def test_extract_manifest_parses_isotopes_list(self, tmp_path): + """YAML with isotopes list should be parsed into ManifestData.isotopes.""" + manifest_content = """--- +name: hf_subset +description: Hafnium sample with specific isotopes +version: "1.0.0" +created: "2024-01-01T00:00:00Z" +isotope: Hf-177 +isotopes: + - Hf-176 + - Hf-177 + - Hf-178 +--- +# Analysis Instructions + +Analyze only the specified isotopes. +""" + (tmp_path / "manifest_intermediate.md").write_text(manifest_content) + + result = extract_manifest(tmp_path) + assert result is not None + assert result.isotopes == ["Hf-176", "Hf-177", "Hf-178"] + assert result.isotope == "Hf-177" + + def test_extract_manifest_handles_missing_isotopes(self, tmp_path): + """YAML without isotopes field should result in ManifestData.isotopes=None.""" + manifest_content = """--- +name: test +description: Test without isotopes list +version: "1.0.0" +created: "2024-01-01T00:00:00Z" +isotope: Au-197 +--- +Body content +""" + (tmp_path / "manifest_intermediate.md").write_text(manifest_content) + + result = extract_manifest(tmp_path) + assert result is not None + assert result.isotopes is None + assert result.isotope == "Au-197" + + def test_extract_manifest_handles_empty_isotopes_list(self, tmp_path): + """YAML with empty isotopes list should result in empty list.""" + manifest_content = """--- +name: test +description: Test with empty isotopes +version: "1.0.0" +created: "2024-01-01T00:00:00Z" +isotope: Hf-177 +isotopes: [] +--- +Body content +""" + (tmp_path / "manifest_intermediate.md").write_text(manifest_content) + + result = extract_manifest(tmp_path) + assert result is not None + assert result.isotopes == [] + + def test_extract_manifest_isotopes_list_with_enrichment(self, tmp_path): + """Manifest can have both isotopes list and enrichment (different purposes).""" + manifest_content = """--- +name: enriched_subset +description: Enriched sample analyzing specific isotopes +version: "1.0.0" +created: "2024-01-01T00:00:00Z" +isotope: U-235 +use_natural_abundance: false +enrichment: + U-235: 0.90 + U-238: 0.10 +isotopes: + - U-235 +--- +Body +""" + (tmp_path / "manifest_intermediate.md").write_text(manifest_content) + + result = extract_manifest(tmp_path) + assert result is not None + assert result.isotopes == ["U-235"] + assert result.enrichment == {"U-235": 0.90, "U-238": 0.10} + + +class TestGetIsotopeCompositionWithManifestIsotopes: + """Tests for _get_isotope_composition with manifest isotopes list (Issue #206).""" + + def test_get_isotope_composition_manifest_isotopes_takes_priority_over_natural(self): + """When manifest has isotopes list (but no enrichment), use those with equal weights.""" + manifest = ManifestData( + name="test", + description="Test", + version="1.0.0", + created="2024-01-01T00:00:00Z", + isotope="Hf-177", + isotopes=["Hf-176", "Hf-177", "Hf-178"], + use_natural_abundance=True, # Natural abundance flag, but isotopes list takes priority + ) + + isotopes, abundances = _get_isotope_composition( + user_isotopes=None, + manifest=manifest, + primary_isotope="Hf-177", + ) + + # Should use manifest isotopes, not all natural Hf isotopes + assert isotopes == ["Hf-176", "Hf-177", "Hf-178"] + assert len(abundances) == 3 + # Equal weights for all isotopes + assert all(abs(a - 1.0 / 3) < 0.001 for a in abundances) + + def test_get_isotope_composition_user_isotopes_takes_priority_over_manifest_isotopes(self): + """User parameter should trump manifest isotopes list.""" + manifest = ManifestData( + name="test", + description="Test", + version="1.0.0", + created="2024-01-01T00:00:00Z", + isotope="Hf-177", + isotopes=["Hf-176", "Hf-177", "Hf-178"], + ) + + isotopes, abundances = _get_isotope_composition( + user_isotopes=["Hf-179", "Hf-180"], # User overrides manifest + manifest=manifest, + primary_isotope="Hf-177", + ) + + # Should use user isotopes, not manifest isotopes + assert isotopes == ["Hf-179", "Hf-180"] + assert len(abundances) == 2 + assert all(a == 0.5 for a in abundances) + + def test_get_isotope_composition_isotopes_list_takes_priority_over_enrichment(self): + """When both isotopes list and enrichment are set, isotopes list takes priority (explicit subset selection). + + The isotopes list is a more explicit declaration of which isotopes to include, + so it takes priority over enrichment which is about abundance values. + """ + manifest = ManifestData( + name="test", + description="Test", + version="1.0.0", + created="2024-01-01T00:00:00Z", + isotope="U-235", + isotopes=["U-235"], # Manifest specifies ONLY U-235 + use_natural_abundance=False, + enrichment={"U-235": 0.90, "U-238": 0.10}, # Enrichment has both, but isotopes list wins + ) + + isotopes, abundances = _get_isotope_composition( + user_isotopes=None, + manifest=manifest, + primary_isotope="U-235", + ) + + # Should use isotopes list (only U-235), not enrichment (both U-235 and U-238) + assert isotopes == ["U-235"] + assert len(abundances) == 1 + assert abundances[0] == 1.0 # Equal weight for single isotope + + def test_get_isotope_composition_manifest_isotopes_with_equal_weights(self): + """Verify that manifest isotopes get equal weights (1/n for n isotopes).""" + # Test with 2 isotopes + manifest_2 = ManifestData( + name="test2", + description="Test with 2 isotopes", + version="1.0.0", + created="2024-01-01T00:00:00Z", + isotope="Hf-177", + isotopes=["Hf-177", "Hf-178"], + ) + + isotopes_2, abundances_2 = _get_isotope_composition( + user_isotopes=None, + manifest=manifest_2, + primary_isotope="Hf-177", + ) + + assert len(isotopes_2) == 2 + assert all(abs(a - 0.5) < 0.001 for a in abundances_2) + + # Test with 4 isotopes + manifest_4 = ManifestData( + name="test4", + description="Test with 4 isotopes", + version="1.0.0", + created="2024-01-01T00:00:00Z", + isotope="Hf-177", + isotopes=["Hf-176", "Hf-177", "Hf-178", "Hf-179"], + ) + + isotopes_4, abundances_4 = _get_isotope_composition( + user_isotopes=None, + manifest=manifest_4, + primary_isotope="Hf-177", + ) + + assert len(isotopes_4) == 4 + assert all(abs(a - 0.25) < 0.001 for a in abundances_4) + + def test_get_isotope_composition_empty_manifest_isotopes_uses_natural(self): + """Empty manifest isotopes list should fall back to natural abundance.""" + manifest = ManifestData( + name="test", + description="Test", + version="1.0.0", + created="2024-01-01T00:00:00Z", + isotope="Hf-177", + isotopes=[], # Empty list should be treated as "not specified" + ) + + isotopes, abundances = _get_isotope_composition( + user_isotopes=None, + manifest=manifest, + primary_isotope="Hf-177", + ) + + # Should fall back to natural abundance (all 6 Hf isotopes) + assert len(isotopes) == 6 + assert "Hf-174" in isotopes + assert "Hf-180" in isotopes + assert abs(sum(abundances) - 1.0) < 0.01 + + def test_get_isotope_composition_manifest_isotopes_without_enrichment_flag(self): + """Manifest isotopes should work regardless of use_natural_abundance flag.""" + # Test with use_natural_abundance=False but no enrichment dict + manifest = ManifestData( + name="test", + description="Test", + version="1.0.0", + created="2024-01-01T00:00:00Z", + isotope="Hf-177", + isotopes=["Hf-177", "Hf-178"], + use_natural_abundance=False, # Flag is False + enrichment=None, # But no enrichment dict + ) + + isotopes, abundances = _get_isotope_composition( + user_isotopes=None, + manifest=manifest, + primary_isotope="Hf-177", + ) + + # Should use manifest isotopes since enrichment is None + assert isotopes == ["Hf-177", "Hf-178"] + assert all(abs(a - 0.5) < 0.001 for a in abundances)