diff --git a/.github/actions/setup-pixi/action.yml b/.github/actions/setup-pixi/action.yml new file mode 100644 index 00000000..8c68f51e --- /dev/null +++ b/.github/actions/setup-pixi/action.yml @@ -0,0 +1,34 @@ +name: 'Setup Pixi Environment' +description: 'Sets up pixi with common configuration' +inputs: + environments: + description: 'Pixi environments to setup' + required: false + default: 'dev' + activate-environment: + description: 'Environment to activate' + required: false + default: 'dev' + run-install: + description: 'Whether to run install' + required: false + default: 'true' + cache: + description: 'Whether to use cache' + required: false + default: 'false' + post-cleanup: + description: 'Whether to run post cleanup' + required: false + default: 'false' + +runs: + using: 'composite' + steps: + - uses: prefix-dev/setup-pixi@v0.9.0 + with: + environments: ${{ inputs.environments }} + activate-environment: ${{ inputs.activate-environment }} + run-install: ${{ inputs.run-install }} + cache: ${{ inputs.cache }} + post-cleanup: ${{ inputs.post-cleanup }} \ No newline at end of file diff --git a/.github/workflows/documentation-build.yml b/.github/workflows/documentation-build.yml index 313857a2..60c73bff 100644 --- a/.github/workflows/documentation-build.yml +++ b/.github/workflows/documentation-build.yml @@ -1,5 +1,5 @@ # This pipeline -# - builds developer documentation +# - builds developer documentation using pixi # - pushes documentation to gh-pages branch of the same repository # # Deployment is handled by pages-build-deployment bot @@ -23,25 +23,22 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@5 + uses: actions/checkout@v5 with: fetch-depth: 0 # otherwise, you will failed to push refs to dest repo - - name: Upgrade pip - run: | - python -m pip install --upgrade pip - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: '3.11' - - name: Install Pandoc, repo and dependencies - run: | - sudo apt install pandoc - pip install . '.[dev,docs]' - - name: Build and Commit - uses: sphinx-notes/pages@master - with: - install_requirements: true - documentation_path: docs/src + + - name: Setup Pixi + uses: ./.github/actions/setup-pixi + + - name: Build documentation + run: pixi run --environment dev docs-build + + # - name: Build and Commit + # uses: sphinx-notes/pages@master + # with: + # install_requirements: true + # documentation_path: docs/src + - name: Push changes uses: ad-m/github-push-action@master continue-on-error: true diff --git a/.github/workflows/nightly-check.yml b/.github/workflows/nightly-check.yml new file mode 100644 index 00000000..133ba4da --- /dev/null +++ b/.github/workflows/nightly-check.yml @@ -0,0 +1,24 @@ +# Nightly Check - install dependencies and run tests after pixi update-lock +name: Nightly Check +on: + schedule: + - cron: '0 0 * * *' # Runs every day at midnight UTC + workflow_dispatch: + +jobs: + nightly-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - name: Setup Pixi + uses: ./.github/actions/setup-pixi + + - name: Update lock file + run: pixi run update-lock + + - name: Build package + run: pixi run build + + - name: Run tox tests + run: pixi run tox diff --git a/.github/workflows/ossar-analysis.yml b/.github/workflows/ossar-analysis.yml index c33f318d..1d4435f4 100644 --- a/.github/workflows/ossar-analysis.yml +++ b/.github/workflows/ossar-analysis.yml @@ -26,16 +26,6 @@ jobs: - run: git checkout HEAD^2 if: ${{ github.event_name == 'pull_request' }} - # Ensure a compatible version of dotnet is installed. - # The [Microsoft Security Code Analysis CLI](https://aka.ms/mscadocs) is built with dotnet v3.1.201. - # A version greater than or equal to v3.1.201 of dotnet must be installed on the agent in order to run this action. - # Remote agents already have a compatible version of dotnet installed and this step may be skipped. - # For local agents, ensure dotnet version 3.1.201 or later is installed by including this action: - # - name: Install .NET - # uses: actions/setup-dotnet@v1 - # with: - # dotnet-version: '3.1.x' - # Run open source static analysis tools - name: Run OSSAR uses: github/ossar-action@v1 diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml index 0f87a455..80ab2a27 100644 --- a/.github/workflows/python-ci.yml +++ b/.github/workflows/python-ci.yml @@ -1,5 +1,5 @@ # This workflow will for a variety of Python versions -# - install the code base +# - install the code base with pixi # - lint the code base # - test the code base # - upload the test coverage to codecov @@ -10,7 +10,7 @@ # # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries -name: CI using pip +name: CI using pixi on: [push, pull_request] @@ -19,11 +19,24 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - - uses: astral-sh/ruff-action@v3 - - name: Suggestion to fix issues + - name: Setup Pixi + uses: ./.github/actions/setup-pixi + - name: Run linting + run: pixi run lint + + - name: Suggestion to fix lint issues if: ${{ failure() }} run: | - echo "::notice::In project root run 'python.exe -m ruff . --fix' and commit changes to fix issues." + echo "::notice::In project root run 'pixi run lint-fix' and commit changes to fix issues." + exit 1 + + - name: Run formatting + run: pixi run format-check + + - name: Suggestion to fix format issues + if: ${{ failure() }} + run: | + echo "::notice::In project root run 'pixi run format' and commit changes to fix issues." exit 1 Code_Testing: @@ -31,7 +44,7 @@ jobs: max-parallel: 4 matrix: python-version: ['3.11', '3.12', '3.13'] - os: [ubuntu-latest, macos-latest, windows-latest] + os: [ubuntu-latest, macos-latest, macos-15-intel, windows-latest] runs-on: ${{ matrix.os }} if: "!contains(github.event.head_commit.message, '[ci skip]')" @@ -39,27 +52,29 @@ jobs: steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v6 + - name: Setup Pixi + uses: prefix-dev/setup-pixi@v0.9.0 with: - python-version: ${{ matrix.python-version }} + run-install: false + cache: false + post-cleanup: false - - name: Install dependencies - run: pip install -e '.[dev]' + - name: Set Python version + run: pixi add python=${{ matrix.python-version }} - - name: Test with tox - run: | - pip install tox tox-gh-actions coverage - tox + - name: Install dependencies and run tests + run: pixi run -e dev test - - name: Upload coverage - uses: codecov/codecov-action@v4.0.1 + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v5 with: - token: ${{ secrets.CODECOV_TOKEN_NEW }} - name: Pytest coverage - env_vars: OS,PYTHON,GITHUB_ACTIONS,GITHUB_ACTION,GITHUB_REF,GITHUB_REPOSITORY,GITHUB_HEAD_REF,GITHUB_RUN_ID,GITHUB_SHA,COVERAGE_FILE - env: - OS: ${{ matrix.os }} - PYTHON: ${{ matrix.python-version }} + name: unit-tests-job + flags: unittests + files: ./coverage-unit.xml + fail_ci_if_error: true + verbose: true + token: ${{ secrets.CODECOV_TOKEN }} + slug: EasyScience/corelib Package_Testing: @@ -69,16 +84,11 @@ jobs: steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v6 - with: - python-version: '3.11' + - name: Setup Pixi + uses: ./.github/actions/setup-pixi - - name: Install dependencies and build - run: | - pip install -e '.[dev]' - python -m build + - name: Build package + run: pixi run build - - name: Check Build - run: | - pip install tox tox-gh-actions coverage - tox + - name: Run tox tests + run: pixi run tox diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 759f8ffb..02d39381 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -1,11 +1,11 @@ # This workflow will for a variety of Python versions -# - build a python package +# - build a python package using pixi # - run tests on the produced package # - upload the package as an artifact # # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions -name: Create Python Package +name: Create Python Package using pixi on: @@ -26,18 +26,19 @@ jobs: steps: - uses: actions/checkout@v5 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v6 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies and build - run: | - pip install -e '.[dev]' - python -m build - - name: Test with pytest - run: | - pip install tox tox-gh-actions coverage - tox + + - name: Setup Pixi + uses: ./.github/actions/setup-pixi + + - name: Set Python version + run: pixi add python=${{ matrix.python-version }} + + - name: Build package + run: pixi run build + + - name: Run tests with tox + run: pixi run tox + - uses: actions/upload-artifact@v4 with: name: EasyScience - Python ${{ matrix.python-version }} diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 8ab59313..2187d301 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -1,5 +1,5 @@ # This workflow will -# - build distribution package, pure python wheel +# - build distribution package, pure python wheel using pixi # - publish produced distribution package to PyPI # # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries @@ -26,14 +26,11 @@ jobs: steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v6 - with: - python-version: '3.11' + - name: Setup Pixi + uses: ./.github/actions/setup-pixi - - name: Install dependencies and build - run: | - pip install -e '.[dev]' - python -m build + - name: Build package + run: pixi run build - name: Publish distribution 📦 to PyPI uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/release-drafter-verify_pr_labels.yml b/.github/workflows/release-drafter-verify_pr_labels.yml deleted file mode 100644 index cbd9497c..00000000 --- a/.github/workflows/release-drafter-verify_pr_labels.yml +++ /dev/null @@ -1,22 +0,0 @@ -# This workflow will be verify that all PRs have at -# least on the label: 'bugs', 'enhancement' before -# they can be merged. - -name: Verify PR labels -on: - pull_request_target: - types: [opened, labeled, unlabeled, synchronize] - -jobs: - check_pr_labels: - runs-on: ubuntu-latest - name: Verify that the PR has a valid label - steps: - - name: Verify PR label action - uses: jesusvasquez333/verify-pr-label-action@v1.4.0 - id: verify-pr-label - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - valid-labels: chore, fix, bugfix, bug, enhancement, feature, dependencies, documentation - pull-request-number: ${{ github.event.pull_request.number }} - disable-reviews: false diff --git a/.github/workflows/verify_issue_labels.yml b/.github/workflows/verify_issue_labels.yml new file mode 100644 index 00000000..69ec3a1f --- /dev/null +++ b/.github/workflows/verify_issue_labels.yml @@ -0,0 +1,31 @@ +# This workflow will verify that all issues have at +# least one of the [scope] labels and one of the [priority] labels + +name: Verify issue labels +on: + issues: + types: [opened, labeled, unlabeled] + +jobs: + check_issue_labels: + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - name: Require [scope] label + uses: trstringer/require-label-prefix@v1 + with: + secret: ${{ github.TOKEN }} + prefix: "[scope]" + addLabel: true + defaultLabel: "[scope] ⚠️ label needed" + + - name: Require [priority] label + if: always() + uses: trstringer/require-label-prefix@v1 + with: + secret: ${{ github.TOKEN }} + prefix: "[priority]" + addLabel: true + defaultLabel: "[priority] ⚠️ label needed" + diff --git a/.github/workflows/verify_pr_labels.yml b/.github/workflows/verify_pr_labels.yml index cbd9497c..e7eb0f5c 100644 --- a/.github/workflows/verify_pr_labels.yml +++ b/.github/workflows/verify_pr_labels.yml @@ -1,6 +1,5 @@ -# This workflow will be verify that all PRs have at -# least on the label: 'bugs', 'enhancement' before -# they can be merged. +# This workflow will verify that all PRs have at +# least one of the [scope] labels before they can be merged. name: Verify PR labels on: @@ -8,15 +7,17 @@ on: types: [opened, labeled, unlabeled, synchronize] jobs: - check_pr_labels: + check_pr_label: runs-on: ubuntu-latest + permissions: + pull-requests: write name: Verify that the PR has a valid label steps: - name: Verify PR label action uses: jesusvasquez333/verify-pr-label-action@v1.4.0 id: verify-pr-label with: - github-token: ${{ secrets.GITHUB_TOKEN }} - valid-labels: chore, fix, bugfix, bug, enhancement, feature, dependencies, documentation + github-token: ${{ github.TOKEN }} + valid-labels: '[scope] bug, [scope] enhancement, [scope] documentation, [scope] significant, [scope] maintenance' pull-request-number: ${{ github.event.pull_request.number }} disable-reviews: false diff --git a/.gitignore b/.gitignore index ff5a50d4..5d0e0e91 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,9 @@ dist poetry.lock *.egg-info +# Pixi +.pixi/ + # PyInstaller build *.spec diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ebf49062..cb8357ab 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,6 +5,52 @@ email, or any other method with the owners of this repository before making a ch Please note we have a code of conduct, please follow it in all your interactions with the project. +## Development Setup + +We use [pixi](https://pixi.sh) for dependency management and development workflow automation. This ensures consistent environments across different platforms and simplifies the development process. + +### Prerequisites + +1. Install pixi following the [official installation guide](https://pixi.sh/latest/#installation) + +### Getting Started + +1. Fork and clone the repository: +```bash +git clone https://github.com/your-username/EasyScience.git +cd EasyScience +``` + +2. Set up the development environment: +```bash +pixi install +``` + +3. Run tests to verify everything is working: +```bash +pixi run test +``` + +### Development Workflow + +- **Run tests**: `pixi run test` (full test suite with coverage) +- **Run unit tests only**: `pixi run test-unit` +- **Check code style**: `pixi run lint-check` +- **Fix code style**: `pixi run lint` +- **Check code formatting**: `pixi run format-check` +- **Format code**: `pixi run format` +- **Build package**: `pixi run build` +- **Build documentation**: `pixi run docs-build` + +### Before Submitting a Pull Request + +1. Update the lock file to ensure dependencies are up to date: `pixi run update-lock` +2. Ensure all tests pass: `pixi run test` +3. Check code style: `pixi run lint-check` +4. Format your code: `pixi run format` +5. Update documentation if necessary +6. Add tests for new functionality + ## Pull Request Process 1. Ensure any install or build dependencies are removed before the end of the layer when doing a diff --git a/Examples/fitting/README.rst b/Examples/fitting/README.rst new file mode 100644 index 00000000..614960cb --- /dev/null +++ b/Examples/fitting/README.rst @@ -0,0 +1,6 @@ +.. _fitting_examples: + +Fitting Examples +---------------- + +This section gathers examples which demonstrate fitting functionality using EasyScience's fitting capabilities. diff --git a/PARAMETER_DEPENDENCY_SERIALIZATION.md b/PARAMETER_DEPENDENCY_SERIALIZATION.md new file mode 100644 index 00000000..82bf0383 --- /dev/null +++ b/PARAMETER_DEPENDENCY_SERIALIZATION.md @@ -0,0 +1,216 @@ +# Parameter Dependency Serialization + +This document explains how to serialize and deserialize `Parameter` objects that have dependencies. + +## Overview + +Parameters with dependencies can now be serialized to dictionaries (and JSON) while preserving their dependency relationships. After deserialization, the dependencies are automatically reconstructed using the `serializer_id` attribute to match parameters, with `unique_name` attribute being used as a fallback. + +## Key Features + +- **Automatic dependency serialization**: Dependency expressions and maps are automatically saved during serialization +- **Reliable dependency resolution**: Dependencies are resolved using stable `serializer_id` attributes with `unique_name` as fallback after deserialization +- **Order-independent loading**: Parameters can be loaded in any order thanks to the reliable ID system +- **Bulk dependency resolution**: Utility functions help resolve all dependencies at once +- **JSON compatibility**: Full support for JSON serialization/deserialization +- **Backward compatibility**: Existing code using `unique_name` continues to work as fallback + +## Usage + +### Basic Serialization/Deserialization + +```python +import json +from easyscience import Parameter, global_object +from easyscience.variable.parameter_dependency_resolver import resolve_all_parameter_dependencies + +# Create parameters with dependencies +a = Parameter(name="a", value=2.0, unit="m", min=0, max=10) +b = Parameter.from_dependency( + name="b", + dependency_expression="2 * a", + dependency_map={"a": a}, + unit="m" +) + +# Serialize to dictionary and save to file +params_dict = {"a": a.as_dict(), "b": b.as_dict()} +with open("parameters.json", "w") as f: + json.dump(params_dict, f, indent=2, default=str) + +print("Parameters saved to parameters.json") +``` + +In a new Python session: + +```python +import json +from easyscience import Parameter, global_object +from easyscience.variable.parameter_dependency_resolver import resolve_all_parameter_dependencies + +# Load parameters from file +with open("parameters.json", "r") as f: + params_dict = json.load(f) + +# Clear global map (simulate new environment) +global_object.map._clear() + +# Deserialize parameters +new_a = Parameter.from_dict(params_dict["a"]) +new_b = Parameter.from_dict(params_dict["b"]) + +# Resolve dependencies +resolve_all_parameter_dependencies({"a": new_a, "b": new_b}) + +# Dependencies are now working +new_a.value = 5.0 +print(new_b.value) # Will be 10.0 (2 * 5.0) +``` + +### JSON Serialization + +```python +import json + +# Serialize to JSON +param_dict = parameter.as_dict() +json_str = json.dumps(param_dict, default=str) + +# Deserialize from JSON +loaded_dict = json.loads(json_str) +new_param = Parameter.from_dict(loaded_dict) + +# Resolve dependencies +resolve_all_parameter_dependencies(new_param) +``` + +### Bulk Operations + +```python +from easyscience.variable.parameter_dependency_resolver import get_parameters_with_pending_dependencies + +# Create multiple parameters with dependencies +params = create_parameter_hierarchy() # Your function + +# Serialize all +serialized = {name: param.as_dict() for name, param in params.items()} + +# Clear and deserialize +global_object.map._clear() +new_params = {name: Parameter.from_dict(d) for name, d in serialized.items()} + +# Check which parameters have pending dependencies +pending = get_parameters_with_pending_dependencies(new_params) +print(f"Found {len(pending)} parameters with pending dependencies") + +# Resolve all at once +resolve_all_parameter_dependencies(new_params) +``` + +## Implementation Details + +### Serialization + +During serialization, the following additional fields are added to dependent parameters: + +- `_dependency_string`: The original dependency expression +- `_dependency_map_serializer_ids`: A mapping of dependency keys to stable dependency IDs (preferred) +- `_dependency_map_unique_names`: A mapping of dependency keys to unique names (fallback) +- `__serializer_id`: The parameter's own unique dependency ID +- `_independent`: Boolean flag indicating if the parameter is dependent + +### Deserialization + +During deserialization: + +1. Parameters are created normally but marked as independent temporarily +2. Dependency information is stored in `_pending_dependency_string`, `_pending_dependency_map_serializer_ids`, and `_pending_dependency_map_unique_names` attributes +3. The parameter's own `__serializer_id` is restored from serialized data +4. After all parameters are loaded, `resolve_all_parameter_dependencies()` establishes the dependency relationships using dependency IDs first, then unique names as fallback + +### Dependency Resolution + +The dependency resolution process: + +1. Scans for parameters with pending dependencies +2. First attempts to look up dependency objects by their stable `serializer_id` +3. Falls back to `unique_name` lookup in the global map if serializer_id is not available +4. Calls `make_dependent_on()` to establish the dependency relationship +5. Cleans up temporary attributes + +This dual-strategy approach ensures reliable dependency resolution regardless of parameter loading order while maintaining backward compatibility. + +## Error Handling + +The system provides detailed error messages for common issues: + +- Missing dependencies (parameter with required unique_name not found) +- Invalid dependency expressions +- Circular dependency detection + +## Utility Functions + +### `resolve_all_parameter_dependencies(obj)` + +Recursively finds all Parameter objects with pending dependencies and resolves them. + +**Parameters:** +- `obj`: Object to search for Parameters (can be Parameter, list, dict, or complex object) + +**Returns:** +- None (modifies parameters in place) + +**Raises:** +- `ValueError`: If dependency resolution fails + +### `get_parameters_with_pending_dependencies(obj)` + +Finds all Parameter objects that have pending dependencies. + +**Parameters:** +- `obj`: Object to search for Parameters + +**Returns:** +- `List[Parameter]`: List of parameters with pending dependencies + +## Best Practices + +1. **Always resolve dependencies after deserialization**: Use `resolve_all_parameter_dependencies()` after loading serialized parameters + +2. **Handle the global map carefully**: The global map must contain all referenced parameters for dependency resolution to work + +3. **Use unique names for cross-references**: When creating dependency expressions that reference other parameters, consider using unique names with quotes: `'Parameter_0'` + +4. **Error handling**: Wrap dependency resolution in try-catch blocks for robust error handling + +5. **Bulk operations**: For complex object hierarchies, use the utility functions to handle all parameters at once + +6. **Reliable ordering**: With the new dependency ID system, parameters can be loaded in any order without affecting dependency resolution + +7. **Access dependency ID**: Use `parameter.serializer_id` to access the stable ID for debugging or manual cross-referencing + +## Example: Complex Hierarchy + +```python +def save_model(model): + \"\"\"Save a model with parameter dependencies to JSON.\"\"\" + model_dict = model.as_dict() + with open('model.json', 'w') as f: + json.dump(model_dict, f, indent=2, default=str) + +def load_model(filename): + \"\"\"Load a model from JSON and resolve dependencies.\"\"\" + global_object.map._clear() # Start fresh + + with open(filename) as f: + model_dict = json.load(f) + + model = Model.from_dict(model_dict) + + # Resolve all parameter dependencies + resolve_all_parameter_dependencies(model) + + return model +``` + +This system ensures that complex parameter hierarchies with dependencies can be reliably serialized and reconstructed while maintaining their behavioral relationships. \ No newline at end of file diff --git a/README.md b/README.md index c993b0b4..56e2ceb0 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,10 @@ Or direct from the repository: ```pip install https://github.com/easyScience/EasyScience``` +### Development + +For development setup and workflow instructions, please see [CONTRIBUTING.md](CONTRIBUTING.md). + ## Test After installation, launch the test suite: diff --git a/docs/src/conf.py b/docs/src/conf.py index 9bf23650..f8445631 100644 --- a/docs/src/conf.py +++ b/docs/src/conf.py @@ -111,7 +111,7 @@ html_theme = 'sphinx_book_theme' html_logo = os.path.join('_static', 'ec_sidebar_w.png') html_favicon = os.path.join('_static', 'favicon.ico') -html_theme_options = {'logo_only': True} +html_theme_options = {} # html_theme_options = { # 'logo': os.path.join('ec_logo_single.png'), # 'github_user': project_info['tool']['github']['info']['organization'], diff --git a/docs/src/fitting/introduction.rst b/docs/src/fitting/introduction.rst index 72c572ac..0a694090 100644 --- a/docs/src/fitting/introduction.rst +++ b/docs/src/fitting/introduction.rst @@ -2,3 +2,361 @@ Fitting in EasyScience ====================== +EasyScience provides a flexible and powerful fitting framework that supports multiple optimization backends. +This guide covers both basic usage for users wanting to fit their data, and advanced patterns for developers building scientific components. + +Overview +-------- + +The EasyScience fitting system consists of: + +* **Parameters**: Scientific values with units, bounds, and fitting capabilities +* **Models**: Objects containing parameters, inheriting from ``ObjBase`` +* **Fitter**: The main fitting engine supporting multiple minimizers +* **Minimizers**: Backend optimization engines (LMFit, Bumps, DFO-LS) + +Quick Start +----------- + +Basic Parameter and Model Setup +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + import numpy as np + from easyscience import ObjBase, Parameter, Fitter + + # Create a simple model with fittable parameters + class SineModel(ObjBase): + def __init__(self, amplitude_val=1.0, frequency_val=1.0, phase_val=0.0): + amplitude = Parameter("amplitude", amplitude_val, min=0, max=10) + frequency = Parameter("frequency", frequency_val, min=0.1, max=5) + phase = Parameter("phase", phase_val, min=-np.pi, max=np.pi) + super().__init__("sine_model", amplitude=amplitude, frequency=frequency, phase=phase) + + def __call__(self, x): + return self.amplitude.value * np.sin(2 * np.pi * self.frequency.value * x + self.phase.value) + +Basic Fitting Example +~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + # Create test data + x_data = np.linspace(0, 2, 100) + true_model = SineModel(amplitude_val=2.5, frequency_val=1.5, phase_val=0.5) + y_data = true_model(x_data) + 0.1 * np.random.normal(size=len(x_data)) + + # Create model to fit with initial guesses + fit_model = SineModel(amplitude_val=1.0, frequency_val=1.0, phase_val=0.0) + + # Set which parameters to fit (unfix them) + fit_model.amplitude.fixed = False + fit_model.frequency.fixed = False + fit_model.phase.fixed = False + + # Create fitter and perform fit + fitter = Fitter(fit_model, fit_model) + result = fitter.fit(x=x_data, y=y_data) + + # Access results + print(f"Chi-squared: {result.chi2}") + print(f"Fitted amplitude: {fit_model.amplitude.value} ± {fit_model.amplitude.error}") + print(f"Fitted frequency: {fit_model.frequency.value} ± {fit_model.frequency.error}") + +Available Minimizers +-------------------- + +EasyScience supports multiple optimization backends: + +.. code-block:: python + + from easyscience import AvailableMinimizers + + # View all available minimizers + fitter = Fitter(model, model) + print(fitter.available_minimizers) + # Output: ['LMFit', 'LMFit_leastsq', 'LMFit_powell', 'Bumps', 'Bumps_simplex', 'DFO', 'DFO_leastsq'] + +Switching Minimizers +~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + # Use LMFit (default) + fitter.switch_minimizer(AvailableMinimizers.LMFit) + result1 = fitter.fit(x=x_data, y=y_data) + + # Switch to Bumps + fitter.switch_minimizer(AvailableMinimizers.Bumps) + result2 = fitter.fit(x=x_data, y=y_data) + + # Use DFO for derivative-free optimization + fitter.switch_minimizer(AvailableMinimizers.DFO) + result3 = fitter.fit(x=x_data, y=y_data) + +Parameter Management +-------------------- + +Setting Bounds and Constraints +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + # Parameter with bounds + param = Parameter(name="amplitude", value=1.0, min=0.0, max=10.0, unit="m") + + # Fix parameter (exclude from fitting) + param.fixed = True + + # Unfix parameter (include in fitting) + param.fixed = False + + # Change bounds dynamically + param.min = 0.5 + param.max = 8.0 + +Parameter Dependencies +~~~~~~~~~~~~~~~~~~~~~~~ + +Parameters can depend on other parameters through expressions: + +.. code-block:: python + + # Create independent parameters + length = Parameter("length", 10.0, unit="m", min=1, max=100) + width = Parameter("width", 5.0, unit="m", min=1, max=50) + + # Create dependent parameter + area = Parameter.from_dependency( + name="area", + dependency_expression="length * width", + dependency_map={"length": length, "width": width} + ) + + # When length or width changes, area updates automatically + length.value = 15.0 + print(area.value) # Will be 75.0 (15 * 5) + +Using make_dependent_on() Method +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can also make an existing parameter dependent on other parameters using the ``make_dependent_on()`` method. This is useful when you want to convert an independent parameter into a dependent one: + +.. code-block:: python + + # Create independent parameters + radius = Parameter("radius", 5.0, unit="m", min=1, max=20) + height = Parameter("height", 10.0, unit="m", min=1, max=50) + volume = Parameter("volume", 100.0, unit="m³") # Initially independent + pi = Parameter("pi", 3.14159, fixed=True) # Constant parameter + + # Make volume dependent on radius and height + volume.make_dependent_on( + dependency_expression="pi * radius**2 * height", + dependency_map={"radius": radius, "height": height, "pi": pi} + ) + + # Now volume automatically updates when radius or height changes + radius.value = 8.0 + print(f"New volume: {volume.value:.2f} m³") # Automatically calculated + + # The parameter becomes dependent and cannot be set directly + try: + volume.value = 200.0 # This will raise an AttributeError + except AttributeError: + print("Cannot set value of dependent parameter directly") + +**What to expect:** + +- The parameter becomes **dependent** and its ``independent`` property becomes ``False`` +- You **cannot directly set** the value, bounds, or variance of a dependent parameter +- The parameter's value is **automatically recalculated** whenever any of its dependencies change +- Dependent parameters **cannot be fitted** (they are automatically fixed) +- The original value, unit, variance, min, and max are **overwritten** by the dependency calculation +- You can **revert to independence** using the ``make_independent()`` method if needed + +Advanced Fitting Options +------------------------ + +Setting Tolerances and Limits +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + fitter = Fitter(model, model) + + # Set convergence tolerance + fitter.tolerance = 1e-8 + + # Limit maximum function evaluations + fitter.max_evaluations = 1000 + + # Perform fit with custom settings + result = fitter.fit(x=x_data, y=y_data) + +Using Weights +~~~~~~~~~~~~~ + +.. code-block:: python + + # Define weights (inverse variance) + weights = 1.0 / errors**2 # where errors are your data uncertainties + + # Fit with weights + result = fitter.fit(x=x_data, y=y_data, weights=weights) + +Multidimensional Fitting +~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + class AbsSin2D(ObjBase): + def __init__(self, offset_val=0.0, phase_val=0.0): + offset = Parameter("offset", offset_val) + phase = Parameter("phase", phase_val) + super().__init__("sin2D", offset=offset, phase=phase) + + def __call__(self, x): + X, Y = x[:, 0], x[:, 1] # x is 2D array + return np.abs(np.sin(self.phase.value * X + self.offset.value)) * \ + np.abs(np.sin(self.phase.value * Y + self.offset.value)) + + # Create 2D data + x_2d = np.column_stack([x_grid.ravel(), y_grid.ravel()]) + + # Fit 2D model + model_2d = AbsSin2D(offset_val=0.1, phase_val=1.0) + model_2d.offset.fixed = False + model_2d.phase.fixed = False + + fitter = Fitter(model_2d, model_2d) + result = fitter.fit(x=x_2d, y=z_data.ravel()) + +Accessing Fit Results +--------------------- + +The ``FitResults`` object contains comprehensive information about the fit: + +.. code-block:: python + + result = fitter.fit(x=x_data, y=y_data) + + # Fit statistics + print(f"Chi-squared: {result.chi2}") + print(f"Reduced chi-squared: {result.reduced_chi}") + print(f"Number of parameters: {result.n_pars}") + print(f"Success: {result.success}") + + # Parameter values and uncertainties + for param_name, value in result.p.items(): + error = result.errors.get(param_name, 0.0) + print(f"{param_name}: {value} ± {error}") + + # Calculated values and residuals + y_calculated = result.y_calc + residuals = result.residual + + # Plot results + import matplotlib.pyplot as plt + plt.figure(figsize=(10, 4)) + plt.subplot(121) + plt.plot(x_data, y_data, 'o', label='Data') + plt.plot(x_data, y_calculated, '-', label='Fit') + plt.legend() + plt.subplot(122) + plt.plot(x_data, residuals, 'o') + plt.axhline(0, color='k', linestyle='--') + plt.ylabel('Residuals') + +Developer Guidelines +--------------------- + +Creating Custom Models +~~~~~~~~~~~~~~~~~~~~~~ + +For developers building scientific components: + +.. code-block:: python + + from easyscience import ObjBase, Parameter + + class CustomModel(ObjBase): + def __init__(self, param1_val=1.0, param2_val=0.0): + # Always create Parameters with appropriate bounds and units + param1 = Parameter("param1", param1_val, min=-10, max=10, unit="m/s") + param2 = Parameter("param2", param2_val, min=0, max=1, fixed=True) + + # Call parent constructor with named parameters + super().__init__("custom_model", param1=param1, param2=param2) + + def __call__(self, x): + # Implement your model calculation + return self.param1.value * x + self.param2.value + + def get_fit_parameters(self): + # This is automatically implemented by ObjBase + # Returns only non-fixed parameters + return super().get_fit_parameters() + +Best Practices +~~~~~~~~~~~~~~ + +1. **Always set appropriate bounds** on parameters to constrain the search space +2. **Use meaningful units** for physical parameters +3. **Fix parameters** that shouldn't be optimized +4. **Test with different minimizers** for robustness +5. **Validate results** by checking chi-squared and residuals + +Error Handling +~~~~~~~~~~~~~~ + +.. code-block:: python + + from easyscience.fitting.minimizers import FitError + + try: + result = fitter.fit(x=x_data, y=y_data) + if not result.success: + print(f"Fit failed: {result.message}") + except FitError as e: + print(f"Fitting error: {e}") + except Exception as e: + print(f"Unexpected error: {e}") + +Testing Patterns +~~~~~~~~~~~~~~~~ + +When writing tests for fitting code: + +.. code-block:: python + + import pytest + from easyscience import global_object + + @pytest.fixture + def clear_global_map(): + """Clear global map before each test""" + global_object.map._clear() + yield + global_object.map._clear() + + def test_model_fitting(clear_global_map): + # Create model and test fitting + model = CustomModel() + model.param1.fixed = False + + # Generate test data + x_test = np.linspace(0, 10, 50) + y_test = 2.5 * x_test + 0.1 * np.random.normal(size=len(x_test)) + + # Fit and verify + fitter = Fitter(model, model) + result = fitter.fit(x=x_test, y=y_test) + + assert result.success + assert model.param1.value == pytest.approx(2.5, abs=0.1) + +This comprehensive guide covers the essential aspects of fitting in EasyScience, from basic usage to advanced developer patterns. +The examples are drawn from the actual test suite and demonstrate real-world usage patterns. + diff --git a/docs/src/getting-started/installation.rst b/docs/src/getting-started/installation.rst index 048f2cd4..4d0cef44 100644 --- a/docs/src/getting-started/installation.rst +++ b/docs/src/getting-started/installation.rst @@ -2,7 +2,7 @@ Installation ************ -**EasyScience** requires Python 3.7 or above. +**EasyScience** requires Python 3.11 or above. Install via ``pip`` ------------------- @@ -17,18 +17,17 @@ Install as an EasyScience developer ----------------------------------- You can obtain the latest development source from our `Github repository -`_.: +`_.: .. code-block:: console - $ git clone https://github.com/easyScience/EasyScience - $ cd EasyScience + $ git clone https://github.com/easyscience/corelib + $ cd corelib And install via pip: .. code-block:: console - $ pip install -r requirements.txt $ pip install -e . .. installation-end-content \ No newline at end of file diff --git a/docs/src/getting-started/overview.rst b/docs/src/getting-started/overview.rst index 2fb0dbd7..d5516a1c 100644 --- a/docs/src/getting-started/overview.rst +++ b/docs/src/getting-started/overview.rst @@ -3,4 +3,222 @@ Overview ======== +EasyScience is a foundational Python library that provides the building blocks for scientific data simulation, analysis, and fitting. +It implements a descriptor-based object system with global state management, making it easy to create scientific models with parameters +that have units, bounds, and dependencies. + +What is EasyScience? +-------------------- + +EasyScience serves as the core foundation for the EasyScience family of projects, offering: + +* **Scientific Parameters**: Values with units, uncertainties, bounds, and fitting capabilities +* **Model Building**: Base classes for creating complex scientific models +* **Multi-backend Fitting**: Support for LMFit, Bumps, and DFO-LS optimization engines +* **Parameter Dependencies**: Express relationships between parameters through mathematical expressions +* **Serialization**: Save and load complete model states including parameter relationships +* **Undo/Redo System**: Track and revert changes to model parameters +* **Global State Management**: Unified tracking of all objects and their relationships + +Key Concepts +------------ + +Descriptor-Based Architecture +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +EasyScience uses a hierarchical descriptor system: + +.. code-block:: python + + from easyscience import Parameter, ObjBase + + # Scientific parameter with units and bounds + temperature = Parameter( + name="temperature", + value=300.0, + unit="K", + min=0, + max=1000, + description="Sample temperature" + ) + + # Model containing parameters + class ThermalModel(ObjBase): + def __init__(self, temp_val=300.0, coeff_val=1.0): + temperature = Parameter("temperature", temp_val, unit="K", min=0, max=1000) + coefficient = Parameter("coefficient", coeff_val, min=0, max=10) + super().__init__("thermal_model", temperature=temperature, coefficient=coefficient) + +The hierarchy flows from: + +* ``DescriptorBase`` → ``DescriptorNumber`` → ``Parameter`` (fittable scientific values) +* ``BasedBase`` → ``ObjBase`` (containers for parameters and scientific models) +* ``CollectionBase`` (mutable sequences of scientific objects) + +Units and Physical Quantities +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +EasyScience integrates with `scipp `_ for robust unit handling: + +.. code-block:: python + + # Parameters automatically handle units + length = Parameter("length", 100, unit="cm", min=0, max=1000) + + # Unit conversions are automatic + length.convert_unit("m") + print(length.value) # 1.0 + print(length.unit) # m + + # Arithmetic operations preserve units + area = length * length # Results in m^2 + +Parameter Dependencies +~~~~~~~~~~~~~~~~~~~~~~~ + +Parameters can depend on other parameters through mathematical expressions: + +.. code-block:: python + + # Independent parameters + radius = Parameter("radius", 5.0, unit="m", min=0, max=100) + height = Parameter("height", 10.0, unit="m", min=0, max=200) + + # Dependent parameter using mathematical expression + volume = Parameter.from_dependency( + name="volume", + dependency_expression="3.14159 * radius**2 * height", + dependency_map={"radius": radius, "height": height} + ) + + # Automatic updates + radius.value = 10.0 + print(volume.value) # Automatically recalculated + +Global State Management +~~~~~~~~~~~~~~~~~~~~~~~ + +All EasyScience objects register with a global map for dependency tracking: + +.. code-block:: python + + from easyscience import global_object + + # All objects are automatically tracked + param = Parameter("test", 1.0) + print(param.unique_name) # Automatically generated unique identifier + + # Access global registry + all_objects = global_object.map.vertices() + + # Clear for testing (important in unit tests) + global_object.map._clear() + +Fitting and Optimization +~~~~~~~~~~~~~~~~~~~~~~~~~ + +EasyScience provides a unified interface to multiple optimization backends: + +.. code-block:: python + + from easyscience import Fitter, AvailableMinimizers + + # Create fitter with model + fitter = Fitter(model, model) # model serves as both object and function + + # Switch between different optimizers + fitter.switch_minimizer(AvailableMinimizers.LMFit) # Levenberg-Marquardt + fitter.switch_minimizer(AvailableMinimizers.Bumps) # Bayesian inference + fitter.switch_minimizer(AvailableMinimizers.DFO) # Derivative-free + + # Perform fit + result = fitter.fit(x=x_data, y=y_data, weights=weights) + +Serialization and Persistence +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Complete model states can be saved and restored: + +.. code-block:: python + + # Save model to dictionary + model_dict = model.as_dict() + + # Save to JSON + import json + with open('model.json', 'w') as f: + json.dump(model_dict, f, indent=2, default=str) + + # Restore model + with open('model.json', 'r') as f: + loaded_dict = json.load(f) + + new_model = Model.from_dict(loaded_dict) + + # Resolve parameter dependencies after loading + from easyscience.variable.parameter_dependency_resolver import resolve_all_parameter_dependencies + resolve_all_parameter_dependencies(new_model) + +Use Cases +--------- + +EasyScience is designed for: + +Scientific Modeling +~~~~~~~~~~~~~~~~~~~~ + +* Creating physics-based models with parameters that have physical meaning +* Handling units consistently throughout calculations +* Managing complex parameter relationships and constraints + +Data Fitting and Analysis +~~~~~~~~~~~~~~~~~~~~~~~~~ + +* Fitting experimental data to theoretical models +* Comparing different optimization algorithms +* Uncertainty quantification and error propagation + +Software Development +~~~~~~~~~~~~~~~~~~~~ + +* Building domain-specific scientific applications +* Creating reusable model components +* Implementing complex scientific workflows + +Research and Education +~~~~~~~~~~~~~~~~~~~~~~ + +* Reproducible scientific computing +* Teaching scientific programming concepts +* Collaborative model development + +Architecture Benefits +--------------------- + +**Type Safety**: Strong typing with unit checking prevents common errors + +**Flexibility**: Multiple optimization backends allow algorithm comparison + +**Extensibility**: Descriptor pattern makes it easy to add new parameter types + +**Reproducibility**: Complete serialization enables exact state restoration + +**Performance**: Efficient observer pattern minimizes unnecessary recalculations + +**Testing**: Global state management with cleanup utilities supports robust testing + +Getting Started +--------------- + +The best way to learn EasyScience is through examples: + +1. **Basic Usage**: Start with simple parameters and models +2. **Fitting Tutorial**: Learn the fitting system with real data +3. **Advanced Features**: Explore parameter dependencies and serialization +4. **Development Guide**: Build your own scientific components + +See the :doc:`installation` guide to get started, then explore the :doc:`../fitting/introduction` for practical examples. + +EasyScience forms the foundation for more specialized packages in the EasyScience ecosystem, providing the core abstractions that make scientific computing more accessible and reliable. + diff --git a/docs/src/index.rst b/docs/src/index.rst index ca99dda5..10f795a8 100644 --- a/docs/src/index.rst +++ b/docs/src/index.rst @@ -1,69 +1,178 @@ -========================================= +======================================= Welcome to EasyScience's documentation! -========================================= +======================================= -**EasyScience** is the foundation of the EasyScience universe, providing the building blocks for libraries and applications which aim to make scientific data simulation and analysis easier. +**EasyScience** is a foundational Python library that provides the building blocks for scientific data simulation, analysis, and fitting. +It implements a descriptor-based object system with global state management, making it easier to create scientific models with parameters +that have units, bounds, and dependencies. +.. code-block:: python -Features of EasyScience -========================= + from easyscience import Parameter, ObjBase, Fitter + + # Create a model with scientific parameters + class SineModel(ObjBase): + def __init__(self, amplitude=1.0, frequency=1.0, phase=0.0): + amp = Parameter("amplitude", amplitude, min=0, max=10, unit="V") + freq = Parameter("frequency", frequency, min=0.1, max=5, unit="Hz") + phase = Parameter("phase", phase, min=-3.14, max=3.14, unit="rad") + super().__init__("sine_model", amplitude=amp, frequency=freq, phase=phase) + + def __call__(self, x): + return self.amplitude.value * np.sin(2*np.pi*self.frequency.value*x + self.phase.value) + + # Fit to experimental data + model = SineModel() + model.amplitude.fixed = False # Allow fitting + fitter = Fitter(model, model) + result = fitter.fit(x=x_data, y=y_data) -Free and open-source -Anyone is free to use EasyScience and the source code is openly shared on GitHub. +Key Features +============ -* *Cross-platform* - EasyScience is written in Python and available for all platforms. +**Scientific Parameters with Units** + Parameters automatically handle physical units, bounds, and uncertainties using `scipp `_ integration. -* *Various techniques* - EasyScience has been used to build various libraries such as easyDiffraction and easyReflectometry. +**Parameter Dependencies** + Express mathematical relationships between parameters that update automatically when dependencies change. -* *Advanced built-in features* - EasyScience provides features such as model minimization, automatic script generation, undo/redo, and more. +**Multi-Backend Fitting** + Unified interface to LMFit, Bumps, and DFO-LS optimization engines with easy algorithm comparison. +**Complete Serialization** + Save and restore entire model states including parameter relationships and dependencies. -Projects using EasyScience +**Global State Management** + Automatic tracking of all objects and their relationships with built-in undo/redo capabilities. + +**Developer-Friendly** + Clean APIs, comprehensive testing utilities, and extensive documentation for building scientific applications. + +Why EasyScience? +================ + +**Type Safety & Units** + Prevent common scientific computing errors with automatic unit checking and strong typing. + +**Reproducible Research** + Complete state serialization ensures exact reproducibility of scientific analyses. + +**Algorithm Flexibility** + Compare different optimization approaches without changing your model code. + +**Extensible Architecture** + Descriptor pattern makes it easy to create new parameter types and model components. + +Open Source & Cross-Platform ============================ -EasyScience is currently being used in the following projects: +EasyScience is free and open-source software with the source code openly shared on `GitHub repository `_. + +* **Cross-platform** - Written in Python and available for Windows, macOS, and Linux +* **Well-tested** - Comprehensive test suite ensuring reliability across platforms +* **Community-driven** - Open to contributions and feature requests +* **Production-ready** - Used in multiple scientific applications worldwide + + +Projects Built with EasyScience +=============================== + +EasyScience serves as the foundation for several scientific applications: + +**easyDiffraction** + .. image:: https://raw.githubusercontent.com/easyScience/easyDiffractionWww/master/assets/img/card.png + :target: https://easydiffraction.org + :width: 300px + + Scientific software for modeling and analysis of neutron diffraction data, providing an intuitive interface for crystallographic refinement. + +**easyReflectometry** + .. image:: https://raw.githubusercontent.com/easyScience/easyReflectometryWww/master/assets/img/card.png + :target: https://easyreflectometry.org + :width: 300px + + Scientific software for modeling and analysis of neutron reflectometry data, enabling detailed study of thin film structures. + +**Your Project Here** + EasyScience's flexible architecture makes it ideal for building domain-specific scientific applications. The comprehensive API and documentation help you get started quickly. + +Quick Start +=========== + +Ready to begin? Here's how to get started: -.. image:: https://raw.githubusercontent.com/easyScience/easyDiffractionWww/master/assets/img/card.png - :target: https://easydiffraction.org +1. **Install EasyScience**: ``pip install easyscience`` +2. **Read the Overview**: Understand the core concepts and architecture +3. **Try the Examples**: Work through practical fitting examples +4. **Explore the API**: Dive into the comprehensive reference documentation -Scientific software for modelling and analysis of neutron diffraction data. +.. code-block:: bash -.. image:: https://raw.githubusercontent.com/easyScience/easyReflectometryWww/master/assets/img/card.png - :target: https://easyreflectometry.org + pip install easyscience -Scientific software for modelling and analysis of neutron reflectometry data. +Then explore the tutorials and examples to learn the key concepts! -Documentation ------------------------------------------- +Documentation Guide +=================== .. toctree:: :caption: Getting Started - :maxdepth: 3 + :maxdepth: 2 + :titlesonly: getting-started/overview getting-started/installation +New to EasyScience? Start with the :doc:`getting-started/overview` to understand the core concepts, then follow the :doc:`getting-started/installation` guide. .. toctree:: - :caption: Base Classes - :maxdepth: 3 + :caption: User Guides + :maxdepth: 2 + :titlesonly: - reference/base + fitting/introduction +Learn how to use EasyScience for scientific modeling and data fitting with comprehensive examples and best practices. .. toctree:: - :caption: Fitting - :maxdepth: 3 + :caption: API Reference + :maxdepth: 2 + :titlesonly: - fitting/introduction + reference/base + +Complete API documentation for all classes, methods, and functions in EasyScience. .. toctree:: + :caption: Examples :maxdepth: 2 - :caption: Example galleries + :titlesonly: base_examples/index fitting_examples/index +Practical examples and tutorials demonstrating real-world usage patterns. + +Need Help? +========== + +* **GitHub Issues**: Report bugs or request features on `GitHub Issues `_ +* **Discussions**: Ask questions in `GitHub Discussions `_ +* **API Reference**: Complete documentation of all classes and methods +* **Examples**: Practical tutorials and code samples + +Contributing +============ + +EasyScience is developed openly and welcomes contributions! Whether you're fixing bugs, adding features, improving documentation, or sharing usage examples, your contributions help make scientific computing more accessible. + +Visit our `GitHub repository `_ to: + +* Report issues or suggest features +* Submit pull requests +* Join discussions about development +* Help improve documentation + Indices and tables ================== diff --git a/docs/src/reference/base.rst b/docs/src/reference/base.rst index ed3d05de..1d91803c 100644 --- a/docs/src/reference/base.rst +++ b/docs/src/reference/base.rst @@ -1,38 +1,323 @@ -====================== -Parameters and Objects -====================== +============== +API Reference +============== -Descriptors -=========== +This reference provides detailed documentation for all EasyScience classes and functions. -.. autoclass:: easyscience.variable.Descriptor - :members: +Core Variables and Descriptors +============================== + +Descriptor Base Classes +----------------------- + +.. autoclass:: easyscience.variable.DescriptorBase + :members: + :inherited-members: + :show-inheritance: + +.. autoclass:: easyscience.variable.DescriptorNumber + :members: + :inherited-members: + :show-inheritance: + +.. autoclass:: easyscience.variable.DescriptorArray + :members: + :inherited-members: + :show-inheritance: + +.. autoclass:: easyscience.variable.DescriptorStr + :members: + :inherited-members: + :show-inheritance: + +.. autoclass:: easyscience.variable.DescriptorBool + :members: + :inherited-members: + :show-inheritance: + +.. autoclass:: easyscience.variable.DescriptorAnyType + :members: + :inherited-members: + :show-inheritance: Parameters -========== +---------- .. autoclass:: easyscience.variable.Parameter - :members: - :inherited-members: + :members: + :inherited-members: + :show-inheritance: + + The Parameter class extends DescriptorNumber with fitting capabilities, bounds, and dependency relationships. + + **Key Methods:** -============================= -Super Classes and Collections -============================= + .. automethod:: from_dependency + :noindex: + .. automethod:: make_dependent_on + :noindex: + .. automethod:: make_independent + :noindex: + .. automethod:: resolve_pending_dependencies + :noindex: -Super Classes -============= +Base Classes for Models +======================= + +BasedBase +--------- .. autoclass:: easyscience.base_classes.BasedBase - :members: - :inherited-members: + :members: + :inherited-members: + :show-inheritance: + + Base class providing serialization, global object registration, and interface management. + +ObjBase +------- .. autoclass:: easyscience.base_classes.ObjBase - :members: +_add_component - :inherited-members: + :members: + :inherited-members: + :show-inheritance: + + Container class for creating scientific models with parameters. All user-defined models should inherit from this class. + + **Key Methods:** + + .. automethod:: get_fit_parameters + :noindex: + .. automethod:: get_parameters + :noindex: + .. automethod:: _add_component Collections -=========== +----------- + +.. autoclass:: easyscience.base_classes.CollectionBase + :members: + :inherited-members: + :show-inheritance: + + Mutable sequence container for scientific objects with automatic parameter tracking. + +Fitting and Optimization +========================= + +Fitter +------- + +.. autoclass:: easyscience.fitting.Fitter + :members: + :show-inheritance: + + Main fitting engine supporting multiple optimization backends. + + **Key Methods:** + + .. autoproperty:: fit + :noindex: + .. automethod:: switch_minimizer + :noindex: + .. automethod:: make_model + .. automethod:: evaluate + +Available Minimizers +-------------------- + +.. autoclass:: easyscience.fitting.AvailableMinimizers + :members: + :show-inheritance: + + Enumeration of available optimization backends. + +Fit Results +----------- + +.. autoclass:: easyscience.fitting.FitResults + :members: + :show-inheritance: + + Container for fitting results including parameters, statistics, and diagnostics. + +Minimizer Base Classes +---------------------- + +.. autoclass:: easyscience.fitting.minimizers.MinimizerBase + :members: + :show-inheritance: + + Abstract base class for all minimizer implementations. + +.. autoclass:: easyscience.fitting.minimizers.LMFit + :members: + :show-inheritance: + + LMFit-based minimizer implementation. + +.. autoclass:: easyscience.fitting.minimizers.Bumps + :members: + :show-inheritance: + + Bumps-based minimizer implementation. + +.. autoclass:: easyscience.fitting.minimizers.DFO + :members: + :show-inheritance: + + DFO-LS-based minimizer implementation. + +Global State Management +======================== + +Global Object +------------- + +.. autoclass:: easyscience.global_object.GlobalObject + :members: + :show-inheritance: + + Singleton managing global state, logging, and object tracking. + +Object Map +---------- + +.. autoclass:: easyscience.global_object.Map + :members: + :show-inheritance: + + Graph-based registry for tracking object relationships and dependencies. + +Undo/Redo System +---------------- + +.. autoclass:: easyscience.global_object.undo_redo.UndoStack + :members: + :show-inheritance: + + Stack-based undo/redo system for parameter changes. + +Serialization and I/O +===================== + +Serializer Components +--------------------- + +.. autoclass:: easyscience.io.SerializerComponent + :members: + :show-inheritance: + + Base class providing serialization capabilities. + +.. autoclass:: easyscience.io.SerializerDict + :members: + :show-inheritance: + + Dictionary-based serialization implementation. + +.. autoclass:: easyscience.io.SerializerBase + :members: + :show-inheritance: + + Base serialization functionality. + +Models and Examples +=================== + +Polynomial Model +---------------- + +.. autoclass:: easyscience.models.Polynomial + :members: + :show-inheritance: + + Built-in polynomial model for demonstration and testing. + +Job Management +============== + +Analysis and Experiments +------------------------ + +.. autoclass:: easyscience.job.AnalysisBase + :members: + :show-inheritance: + +.. autoclass:: easyscience.job.ExperimentBase + :members: + :show-inheritance: + +.. autoclass:: easyscience.job.JobBase + :members: + :show-inheritance: + +.. autoclass:: easyscience.job.TheoreticalModelBase + :members: + :show-inheritance: + +Utility Functions +================= + +Decorators +---------- + +.. autofunction:: easyscience.global_object.undo_redo.property_stack + + Decorator for properties that should be tracked in the undo/redo system. + +Class Tools +----------- + +.. autofunction:: easyscience.utils.classTools.addLoggedProp + + Utility for adding logged properties to classes. + +String Utilities +---------------- + +.. automodule:: easyscience.utils.string + :members: + +Parameter Dependencies +----------------------- + +.. autofunction:: easyscience.variable.parameter_dependency_resolver.resolve_all_parameter_dependencies + + Resolve all pending parameter dependencies after deserialization. + +.. autofunction:: easyscience.variable.parameter_dependency_resolver.get_parameters_with_pending_dependencies + + Find parameters that have unresolved dependencies. + +Constants and Enumerations +=========================== + +.. autodata:: easyscience.global_object + :annotation: GlobalObject + + Global singleton instance managing application state. + +Exception Classes +================= + +.. autoclass:: easyscience.fitting.minimizers.FitError + :show-inheritance: + + Exception raised when fitting operations fail. + +.. autoclass:: scipp.UnitError + :show-inheritance: + + Exception raised for unit-related errors (from scipp dependency). + +Usage Examples +============== + +For practical usage examples and tutorials, see: + +* :doc:`../getting-started/overview` - Introduction and key concepts +* :doc:`../fitting/introduction` - Comprehensive fitting guide +* :doc:`../getting-started/installation` - Installation instructions -.. autoclass:: easyscience.CollectionBase - :members: - :inherited-members: +The API reference covers all public classes and methods. For implementation details and advanced usage patterns, refer to the source code and test suites in the repository. diff --git a/pixi.lock b/pixi.lock new file mode 100644 index 00000000..00863b1e --- /dev/null +++ b/pixi.lock @@ -0,0 +1,5724 @@ +version: 6 +environments: + default: + 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/linux-64/bzip2-1.0.8-hda65f42_8.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.11.12-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45-bootstrap_ha15bf96_3.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/libgcc-15.2.0-h767d61c_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-h767d61c_7.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/libmpdec-4.0.0-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.51.0-hee844dc_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h8f9b012_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-h4852527_7.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/libzlib-1.3.1-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.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/python-3.13.9-hc97d973_101_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.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/tzdata-2025b-h78e105d_0.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/b7/da/875925db2ed80dc7b919b2817da555848b608620be9662c5f835670d5d8d/asteval-1.0.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6b/be/0f2f4a5e8adc114a02b63d92bf8edbfa24db6fc602fca83c885af2479e0e/editables-0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2d/8b/371ab3cec97ee3fe1126b3406b7abd60c8fec8975fd79a3c75cdea0c3d83/fonttools-4.60.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_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 + - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d9/69/4402ea66272dacc10b298cca18ed73e1c0791ff2ae9ed218d3859f9698ac/h5py-3.15.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/04/35/aa8738d6674aba09d3f0c77a1c40aee1dbf10e1b26d03cbd987aa6642e86/hatchling-1.21.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e9/e9/f218a2cb3a9ffbe324ca29a9e399fa2d2866d7f348ec3a88df87fc248fc5/kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/22/ff/6425bf5c20d79aa5b959d1ce9e65f599632345391381c9a104133fe0b171/matplotlib-3.10.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5d/ba/459f18c16f2b3fc1a1ca871f72f07d70c07bf768ad0a507a698b8052ac58/msgpack-1.1.2-cp313-cp313-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/0b/9a/c6f79de7ba3a0a8473129936b7b90aa461d3d46fec6f1627672b1dccf4e9/narwhals-2.12.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/10/ca162f45a102738958dcec8023062dad0cbc17d1ab99d68c4e4a6c45fb2b/numpy-2.3.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/57/755dbd06530a27a5ed74f8cb0a7a44a21722ebf318edbe67ddbd7fb28f88/pillow-12.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e7/c3/3031c931098de393393e1f93a38dc9ed6805d86bb801acc3cf2d5bd1e6b7/plotly-6.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-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/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/f0/c5aa0a69fd9326f013110653543f36ece4913c17921f3e1dbd78e1b423ee/python_engineio-4.12.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cd/fa/1ef2f8537272a2f383d72b9301c3ef66a49710b3bb7dcb2bd138cf2920d1/python_socketio-5.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/77/34/1956aed61c4abb91926f9513330afac4654cc2a711ecb73085dc4e2e5c1d/scipp-25.11.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/21/f6/4bfb5695d8941e5c570a04d9fcd0d36bce7511b7d78e6e75c8f9791f82d0/scipy-1.16.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/ba/daf16c2d1965bf6237fb696639e3e93645ac6801f7dcaf9ec694a74e9326/setuptools_git_versioning-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/49/f6/73c4aa003d1237ee9bea8a46f49dc38c45dfe95af4f0da7e60678d388011/trove_classifiers-2025.11.14.15-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-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-64: + - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_8.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.11.12-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.7.3-heffb93a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.5.2-h750e83c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.1-hd471939_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libmpdec-4.0.0-h6e16a3a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.51.0-h86bffb9_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-hd23fc13_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-h0622a9a_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.6.0-h230baf5_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.13.9-h17c18a5_101_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.2-h7cca4af_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-hf689a15_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/db/ed/1f59215ab6853fbaa5c8495fa6cbc39edfc93553426152b75d82a5f32b76/aiohttp-3.13.2-cp313-cp313-macosx_10_13_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/b7/da/875925db2ed80dc7b919b2817da555848b608620be9662c5f835670d5d8d/asteval-1.0.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6b/be/0f2f4a5e8adc114a02b63d92bf8edbfa24db6fc602fca83c885af2479e0e/editables-0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d6/8a/de9cc0540f542963ba5e8f3a1f6ad48fa211badc3177783b9d5cadf79b5d/fonttools-4.60.1-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/b3/40207e0192415cbff7ea1d37b9f24b33f6d38a5a2f5d18a678de78f967ae/h5py-3.15.1-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/04/35/aa8738d6674aba09d3f0c77a1c40aee1dbf10e1b26d03cbd987aa6642e86/hatchling-1.21.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/f0/f44f50c9f5b1a1860261092e3bc91ecdc9acda848a8b8c6abfda4a24dd5c/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/02/9c/207547916a02c78f6bdd83448d9b21afbc42f6379ed887ecf610984f3b4e/matplotlib-3.10.7-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6b/31/b46518ecc604d7edf3a4f94cb3bf021fc62aa301f0cb849936968164ef23/msgpack-1.1.2-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/91/1c/eb97db117a1ebe46d457a3d235a7b9d2e6dcab174f42d1b67663dd9e5371/multidict-6.7.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/0b/9a/c6f79de7ba3a0a8473129936b7b90aa461d3d46fec6f1627672b1dccf4e9/narwhals-2.12.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/db/69/9cde09f36da4b5a505341180a3f2e6fadc352fd4d2b7096ce9778db83f1a/numpy-2.3.5-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a4/a4/a0a31467e3f83b94d37568294b01d22b43ae3c5d85f2811769b9c66389dd/pillow-12.0.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e7/c3/3031c931098de393393e1f93a38dc9ed6805d86bb801acc3cf2d5bd1e6b7/plotly-6.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/f0/c5aa0a69fd9326f013110653543f36ece4913c17921f3e1dbd78e1b423ee/python_engineio-4.12.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cd/fa/1ef2f8537272a2f383d72b9301c3ef66a49710b3bb7dcb2bd138cf2920d1/python_socketio-5.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/f2/18d5be10ac890ce7490451eb55d41bf9d96e481751362a30ed1a8bc176fa/scipp-25.11.0-cp313-cp313-macosx_11_0_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/72/f1/57e8327ab1508272029e27eeef34f2302ffc156b69e7e233e906c2a5c379/scipy-1.16.3-cp313-cp313-macosx_10_14_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/ba/daf16c2d1965bf6237fb696639e3e93645ac6801f7dcaf9ec694a74e9326/setuptools_git_versioning-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/49/f6/73c4aa003d1237ee9bea8a46f49dc38c45dfe95af4f0da7e60678d388011/trove_classifiers-2025.11.14.15-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/01/88/04d98af0b47e0ef42597b9b28863b9060bb515524da0a65d5f4db160b2d5/yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: ./ + osx-arm64: + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_8.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.11.12-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-75.1-hfee45f7_0.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/liblzma-5.8.1-h39f12f2_2.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/libsqlite-3.51.0-h8adb53f_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/ncurses-6.5-h5e97a16_3.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/python-3.13.9-hfc2f54d_101_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.2-h1d1bf99_2.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/tzdata-2025b-h78e105d_0.conda + - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/68/7b/fe0fe0f5e05e13629d893c760465173a15ad0039c0a5b0d0040995c8075e/aiohttp-3.13.2-cp313-cp313-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/b7/da/875925db2ed80dc7b919b2817da555848b608620be9662c5f835670d5d8d/asteval-1.0.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6b/be/0f2f4a5e8adc114a02b63d92bf8edbfa24db6fc602fca83c885af2479e0e/editables-0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7c/5b/cdd2c612277b7ac7ec8c0c9bc41812c43dc7b2d5f2b0897e15fdf5a1f915/fonttools-4.60.1-cp313-cp313-macosx_10_13_universal2.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/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/96/ba99a003c763998035b0de4c299598125df5fc6c9ccf834f152ddd60e0fb/h5py-3.15.1-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/04/35/aa8738d6674aba09d3f0c77a1c40aee1dbf10e1b26d03cbd987aa6642e86/hatchling-1.21.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2d/7a/9d90a151f558e29c3936b8a47ac770235f436f2120aca41a6d5f3d62ae8d/kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/bc/d0/b3d3338d467d3fc937f0bb7f256711395cae6f78e22cef0656159950adf0/matplotlib-3.10.7-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/92/dc/c385f38f2c2433333345a82926c6bfa5ecfff3ef787201614317b58dd8be/msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.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/0b/9a/c6f79de7ba3a0a8473129936b7b90aa461d3d46fec6f1627672b1dccf4e9/narwhals-2.12.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/fb/f505c95ceddd7027347b067689db71ca80bd5ecc926f913f1a23e65cf09b/numpy-2.3.5-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/83/06/48eab21dd561de2914242711434c0c0eb992ed08ff3f6107a5f44527f5e9/pillow-12.0.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/e7/c3/3031c931098de393393e1f93a38dc9ed6805d86bb801acc3cf2d5bd1e6b7/plotly-6.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/f0/c5aa0a69fd9326f013110653543f36ece4913c17921f3e1dbd78e1b423ee/python_engineio-4.12.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cd/fa/1ef2f8537272a2f383d72b9301c3ef66a49710b3bb7dcb2bd138cf2920d1/python_socketio-5.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/64/7c/d8a343b0a622987335a1ac848084079c47c21e44fcc450f9c145b11a56f6/scipp-25.11.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/44/13/7e63cfba8a7452eb756306aa2fd9b37a29a323b672b964b4fdeded9a3f21/scipy-1.16.3-cp313-cp313-macosx_12_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/ba/daf16c2d1965bf6237fb696639e3e93645ac6801f7dcaf9ec694a74e9326/setuptools_git_versioning-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/49/f6/73c4aa003d1237ee9bea8a46f49dc38c45dfe95af4f0da7e60678d388011/trove_classifiers-2025.11.14.15-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-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: ./ + win-64: + - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_8.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.11.12-h4c7d964_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.3-hac47afa_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h52bdfb6_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.1-h2466b09_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libmpdec-4.0.0-h2466b09_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.51.0-hf5d6505_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.0-h725018a_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.9-h09917c8_101_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h2c6b04d_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h2b53caa_32.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_32.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_32.conda + - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5d/28/a8a9fc6957b2cee8902414e41816b5ab5536ecf43c3b1843c10e82c559b2/aiohttp-3.13.2-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/da/875925db2ed80dc7b919b2817da555848b608620be9662c5f835670d5d8d/asteval-1.0.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6b/be/0f2f4a5e8adc114a02b63d92bf8edbfa24db6fc602fca83c885af2479e0e/editables-0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/75/4d/b022c1577807ce8b31ffe055306ec13a866f2337ecee96e75b24b9b753ea/fonttools-4.60.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/ea/fbb258a98863f99befb10ed727152b4ae659f322e1d9c0576f8a62754e81/h5py-3.15.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/04/35/aa8738d6674aba09d3f0c77a1c40aee1dbf10e1b26d03cbd987aa6642e86/hatchling-1.21.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/75/bd/f1a5d894000941739f2ae1b65a32892349423ad49c2e6d0771d0bad3fae4/kiwisolver-1.4.9-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/e1/b6/23064a96308b9aeceeffa65e96bcde459a2ea4934d311dee20afde7407a0/matplotlib-3.10.7-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/32/31/75c59e7d3b4205075b4c183fa4ca398a2daf2303ddf616b04ae6ef55cffe/multidict-6.7.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/0b/9a/c6f79de7ba3a0a8473129936b7b90aa461d3d46fec6f1627672b1dccf4e9/narwhals-2.12.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0c/88/e2eaa6cffb115b85ed7c7c87775cb8bcf0816816bc98ca8dbfa2ee33fe6e/numpy-2.3.5-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/2a/dd43dcfd6dae9b6a49ee28a8eedb98c7d5ff2de94a5d834565164667b97b/pillow-12.0.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/e7/c3/3031c931098de393393e1f93a38dc9ed6805d86bb801acc3cf2d5bd1e6b7/plotly-6.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/f0/c5aa0a69fd9326f013110653543f36ece4913c17921f3e1dbd78e1b423ee/python_engineio-4.12.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cd/fa/1ef2f8537272a2f383d72b9301c3ef66a49710b3bb7dcb2bd138cf2920d1/python_socketio-5.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f6/f3/9d1bb423a2dc0bbebfc98191095dd410a1268397b9c692d76a3ea971c790/scipp-25.11.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/cd/01/1204382461fcbfeb05b6161b594f4007e78b6eba9b375382f79153172b4d/scipy-1.16.3-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/ba/daf16c2d1965bf6237fb696639e3e93645ac6801f7dcaf9ec694a74e9326/setuptools_git_versioning-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/49/f6/73c4aa003d1237ee9bea8a46f49dc38c45dfe95af4f0da7e60678d388011/trove_classifiers-2025.11.14.15-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9a/ee/450914ae11b419eadd067c6183ae08381cfdfcb9798b90b2b713bbebddda/yarl-1.22.0-cp313-cp313-win_amd64.whl + - pypi: ./ + dev: + 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/linux-64/bzip2-1.0.8-hda65f42_8.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.11.12-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45-bootstrap_ha15bf96_3.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/libgcc-15.2.0-h767d61c_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-h767d61c_7.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/libmpdec-4.0.0-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.51.0-hee844dc_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h8f9b012_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-h4852527_7.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/libzlib-1.3.1-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.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/python-3.13.9-hc97d973_101_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.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/tzdata-2025b-h78e105d_0.conda + - pypi: https://files.pythonhosted.org/packages/8d/3f/95338030883d8c8b91223b4e21744b04d11b161a3ef117295d8241f50ab4/accessible_pygments-0.0.5-py3-none-any.whl + - 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/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/da/875925db2ed80dc7b919b2817da555848b608620be9662c5f835670d5d8d/asteval-1.0.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-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/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/af/02/18785edcdf6266cdd6c6dc7635f1cbeefd9a5b4c3bb8aff8bd681e9dd095/codecov-2.1.13-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/76/b6/67d7c0e1f400b32c883e9342de4a8c2ae7c1a0b57c5de87622b7262e2309/coverage-7.12.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b0/d0/89247ec250369fc76db477720a26b2fce7ba079ff1380e4ab4529d2fe233/debugpy-1.8.17-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a2/e9/90b7d243364d3dce38c8c2a1b8c103d7a8d1383c2b24c735fae0eee038dd/doc8-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6b/be/0f2f4a5e8adc114a02b63d92bf8edbfa24db6fc602fca83c885af2479e0e/editables-0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9f/56/13ab06b4f93ca7cac71078fbe37fcea175d3216f31f85c3168a6bbd0bb9a/flake8-7.3.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2d/8b/371ab3cec97ee3fe1126b3406b7abd60c8fec8975fd79a3c75cdea0c3d83/fonttools-4.60.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-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/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d9/69/4402ea66272dacc10b298cca18ed73e1c0791ff2ae9ed218d3859f9698ac/h5py-3.15.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/04/35/aa8738d6674aba09d3f0c77a1c40aee1dbf10e1b26d03cbd987aa6642e86/hatchling-1.21.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a3/17/20c2552266728ceba271967b87919664ecc0e33efca29c3efc6baf88c5f9/ipykernel-7.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/05/aa/62893d6a591d337aa59dcc4c6f6c842f1fe20cd72c8c5c1f980255243252/ipython-9.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/85/e2/05328bd2621be49a6fed9e3030b1e51a2d04537d3f816d211b9cc53c5262/json5-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/1e/5a4d5498eba382fee667ed797cf64ae5d1b13b04356df62f067f48bb0f61/jupyterlab-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e9/e9/f218a2cb3a9ffbe324ca29a9e399fa2d2866d7f348ec3a88df87fc248fc5/kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/22/ff/6425bf5c20d79aa5b959d1ce9e65f599632345391381c9a104133fe0b171/matplotlib-3.10.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7a/f0/8282d9641415e9e33df173516226b404d367a0fc55e1a60424a152913abc/mistune-3.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5d/ba/459f18c16f2b3fc1a1ca871f72f07d70c07bf768ad0a507a698b8052ac58/msgpack-1.1.2-cp313-cp313-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/0b/9a/c6f79de7ba3a0a8473129936b7b90aa461d3d46fec6f1627672b1dccf4e9/narwhals-2.12.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/42/0f/c76bf3dba22c73c38e9b1113b017cf163f7696f50e003404ec5ecdb1e8a6/nh3-0.3.2-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/10/ca162f45a102738958dcec8023062dad0cbc17d1ab99d68c4e4a6c45fb2b/numpy-2.3.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/57/755dbd06530a27a5ed74f8cb0a7a44a21722ebf318edbe67ddbd7fb28f88/pillow-12.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/c3/3031c931098de393393e1f93a38dc9ed6805d86bb801acc3cf2d5bd1e6b7/plotly-6.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-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/ce/b1/5f49af514f76431ba4eea935b8ad3725cdeb397e9245ab919dbc1d1dc20f/psutil-7.1.3-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/d3/c622950d87a2ffd1654208733b5bd1c5645930014abed8f4c0d74863988b/pydata_sphinx_theme-0.15.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c2/2f/81d580a0fb83baeb066698975cb14a618bdbed7720678566f1b046a95fe8/pyflakes-3.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/cc/cecf97be298bee2b2a37dd360618c819a2a7fd95251d8e480c1f0eb88f3b/pyproject_api-1.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0b/8b/6300fb80f858cda1c51ffa17075df5d846757081d11ab4aa35cef9e6258b/pytest-9.0.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/f0/c5aa0a69fd9326f013110653543f36ece4913c17921f3e1dbd78e1b423ee/python_engineio-4.12.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cd/fa/1ef2f8537272a2f383d72b9301c3ef66a49710b3bb7dcb2bd138cf2920d1/python_socketio-5.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e1/67/921ec3024056483db83953ae8e48079ad62b92db7880013ca77632921dd0/readme_renderer-44.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/af/63/ac52b32b33ae62f2076ed5c4f6b00e065e3ccbb2063e9a2e813b2bfc95bf/restructuredtext_lint-2.0.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/53/97/d2cbbaa10c9b826af0e10fdf836e1bf344d9f0abb873ebc34d1f49642d3f/roman_numerals_py-3.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b9/42/555b4ee17508beafac135c8b450816ace5a96194ce97fefc49d58e5652ea/rpds_py-0.29.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/13/ac/9b9fe63716af8bdfddfacd0882bc1586f29985d3b988b3c62ddce2e202c3/ruff-0.14.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/77/34/1956aed61c4abb91926f9513330afac4654cc2a711ecb73085dc4e2e5c1d/scipp-25.11.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/21/f6/4bfb5695d8941e5c570a04d9fcd0d36bce7511b7d78e6e75c8f9791f82d0/scipy-1.16.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/ba/daf16c2d1965bf6237fb696639e3e93645ac6801f7dcaf9ec694a74e9326/setuptools_git_versioning-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/53/136e9eca6e0b9dc0e1962e2c908fbea2e5ac000c2a2fbd9a35797958c48b/sphinx-8.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/05/f2/9657c98a66973b7c35bfd48ba65d1922860de9598fbb535cd96e3f58a908/sphinx_autodoc_typehints-3.5.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/9e/c41d68be04eef5b6202b468e0f90faf0c469f3a03353f2a218fd78279710/sphinx_book_theme-1.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/77/c7/52b48aec16b26c52aba854d03a3a31e0681150301dac1bea2243645a69e7/sphinx_gallery-0.19.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/40/8561ce06dc46fd17242c7724ab25b257a2ac1b35f4ebf551b40ce6105cfa/stevedore-5.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f9/41/fb15f06e33d7430ca89420283a8762a4e6b8025b800ea51796ab5e6d9559/tornado-6.5.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/fc/cc/e09c0d663a004945f82beecd4f147053567910479314e8d01ba71e5d5dea/tox-4.32.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fd/9e/8d50f3b3fc4af8c73154f64d4a2293bfa2d517a19000e70ef2d614254084/tox_gh_actions-3.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/49/f6/73c4aa003d1237ee9bea8a46f49dc38c45dfe95af4f0da7e60678d388011/trove_classifiers-2025.11.14.15-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-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-64: + - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_8.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.11.12-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.7.3-heffb93a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.5.2-h750e83c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.1-hd471939_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libmpdec-4.0.0-h6e16a3a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.51.0-h86bffb9_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-hd23fc13_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-h0622a9a_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.6.0-h230baf5_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.13.9-h17c18a5_101_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.2-h7cca4af_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-hf689a15_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + - pypi: https://files.pythonhosted.org/packages/8d/3f/95338030883d8c8b91223b4e21744b04d11b161a3ef117295d8241f50ab4/accessible_pygments-0.0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/db/ed/1f59215ab6853fbaa5c8495fa6cbc39edfc93553426152b75d82a5f32b76/aiohttp-3.13.2-cp313-cp313-macosx_10_13_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/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/da/875925db2ed80dc7b919b2817da555848b608620be9662c5f835670d5d8d/asteval-1.0.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-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/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/af/02/18785edcdf6266cdd6c6dc7635f1cbeefd9a5b4c3bb8aff8bd681e9dd095/codecov-2.1.13-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/b8/14/771700b4048774e48d2c54ed0c674273702713c9ee7acdfede40c2666747/coverage-7.12.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b0/d0/89247ec250369fc76db477720a26b2fce7ba079ff1380e4ab4529d2fe233/debugpy-1.8.17-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a2/e9/90b7d243364d3dce38c8c2a1b8c103d7a8d1383c2b24c735fae0eee038dd/doc8-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6b/be/0f2f4a5e8adc114a02b63d92bf8edbfa24db6fc602fca83c885af2479e0e/editables-0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9f/56/13ab06b4f93ca7cac71078fbe37fcea175d3216f31f85c3168a6bbd0bb9a/flake8-7.3.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d6/8a/de9cc0540f542963ba5e8f3a1f6ad48fa211badc3177783b9d5cadf79b5d/fonttools-4.60.1-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/b3/40207e0192415cbff7ea1d37b9f24b33f6d38a5a2f5d18a678de78f967ae/h5py-3.15.1-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/04/35/aa8738d6674aba09d3f0c77a1c40aee1dbf10e1b26d03cbd987aa6642e86/hatchling-1.21.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a3/17/20c2552266728ceba271967b87919664ecc0e33efca29c3efc6baf88c5f9/ipykernel-7.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/05/aa/62893d6a591d337aa59dcc4c6f6c842f1fe20cd72c8c5c1f980255243252/ipython-9.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/85/e2/05328bd2621be49a6fed9e3030b1e51a2d04537d3f816d211b9cc53c5262/json5-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/1e/5a4d5498eba382fee667ed797cf64ae5d1b13b04356df62f067f48bb0f61/jupyterlab-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/f0/f44f50c9f5b1a1860261092e3bc91ecdc9acda848a8b8c6abfda4a24dd5c/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/02/9c/207547916a02c78f6bdd83448d9b21afbc42f6379ed887ecf610984f3b4e/matplotlib-3.10.7-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7a/f0/8282d9641415e9e33df173516226b404d367a0fc55e1a60424a152913abc/mistune-3.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6b/31/b46518ecc604d7edf3a4f94cb3bf021fc62aa301f0cb849936968164ef23/msgpack-1.1.2-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/91/1c/eb97db117a1ebe46d457a3d235a7b9d2e6dcab174f42d1b67663dd9e5371/multidict-6.7.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/0b/9a/c6f79de7ba3a0a8473129936b7b90aa461d3d46fec6f1627672b1dccf4e9/narwhals-2.12.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b6/3e/f5a5cc2885c24be13e9b937441bd16a012ac34a657fe05e58927e8af8b7a/nh3-0.3.2-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl + - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/db/69/9cde09f36da4b5a505341180a3f2e6fadc352fd4d2b7096ce9778db83f1a/numpy-2.3.5-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a4/a4/a0a31467e3f83b94d37568294b01d22b43ae3c5d85f2811769b9c66389dd/pillow-12.0.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/c3/3031c931098de393393e1f93a38dc9ed6805d86bb801acc3cf2d5bd1e6b7/plotly-6.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ef/94/46b9154a800253e7ecff5aaacdf8ebf43db99de4a2dfa18575b02548654e/psutil-7.1.3-cp36-abi3-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/d3/c622950d87a2ffd1654208733b5bd1c5645930014abed8f4c0d74863988b/pydata_sphinx_theme-0.15.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c2/2f/81d580a0fb83baeb066698975cb14a618bdbed7720678566f1b046a95fe8/pyflakes-3.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/cc/cecf97be298bee2b2a37dd360618c819a2a7fd95251d8e480c1f0eb88f3b/pyproject_api-1.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0b/8b/6300fb80f858cda1c51ffa17075df5d846757081d11ab4aa35cef9e6258b/pytest-9.0.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/f0/c5aa0a69fd9326f013110653543f36ece4913c17921f3e1dbd78e1b423ee/python_engineio-4.12.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cd/fa/1ef2f8537272a2f383d72b9301c3ef66a49710b3bb7dcb2bd138cf2920d1/python_socketio-5.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl + - pypi: https://files.pythonhosted.org/packages/e1/67/921ec3024056483db83953ae8e48079ad62b92db7880013ca77632921dd0/readme_renderer-44.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/af/63/ac52b32b33ae62f2076ed5c4f6b00e065e3ccbb2063e9a2e813b2bfc95bf/restructuredtext_lint-2.0.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/53/97/d2cbbaa10c9b826af0e10fdf836e1bf344d9f0abb873ebc34d1f49642d3f/roman_numerals_py-3.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fd/d9/c5de60d9d371bbb186c3e9bf75f4fc5665e11117a25a06a6b2e0afb7380e/rpds_py-0.29.0-cp313-cp313-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/36/6a/ad66d0a3315d6327ed6b01f759d83df3c4d5f86c30462121024361137b6a/ruff-0.14.6-py3-none-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/71/f2/18d5be10ac890ce7490451eb55d41bf9d96e481751362a30ed1a8bc176fa/scipp-25.11.0-cp313-cp313-macosx_11_0_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/72/f1/57e8327ab1508272029e27eeef34f2302ffc156b69e7e233e906c2a5c379/scipy-1.16.3-cp313-cp313-macosx_10_14_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/ba/daf16c2d1965bf6237fb696639e3e93645ac6801f7dcaf9ec694a74e9326/setuptools_git_versioning-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/53/136e9eca6e0b9dc0e1962e2c908fbea2e5ac000c2a2fbd9a35797958c48b/sphinx-8.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/05/f2/9657c98a66973b7c35bfd48ba65d1922860de9598fbb535cd96e3f58a908/sphinx_autodoc_typehints-3.5.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/9e/c41d68be04eef5b6202b468e0f90faf0c469f3a03353f2a218fd78279710/sphinx_book_theme-1.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/77/c7/52b48aec16b26c52aba854d03a3a31e0681150301dac1bea2243645a69e7/sphinx_gallery-0.19.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/40/8561ce06dc46fd17242c7724ab25b257a2ac1b35f4ebf551b40ce6105cfa/stevedore-5.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f2/b5/9b575a0ed3e50b00c40b08cbce82eb618229091d09f6d14bce80fc01cb0b/tornado-6.5.2-cp39-abi3-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/fc/cc/e09c0d663a004945f82beecd4f147053567910479314e8d01ba71e5d5dea/tox-4.32.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fd/9e/8d50f3b3fc4af8c73154f64d4a2293bfa2d517a19000e70ef2d614254084/tox_gh_actions-3.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/49/f6/73c4aa003d1237ee9bea8a46f49dc38c45dfe95af4f0da7e60678d388011/trove_classifiers-2025.11.14.15-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/01/88/04d98af0b47e0ef42597b9b28863b9060bb515524da0a65d5f4db160b2d5/yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: ./ + osx-arm64: + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_8.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.11.12-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-75.1-hfee45f7_0.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/liblzma-5.8.1-h39f12f2_2.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/libsqlite-3.51.0-h8adb53f_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/ncurses-6.5-h5e97a16_3.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/python-3.13.9-hfc2f54d_101_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.2-h1d1bf99_2.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/tzdata-2025b-h78e105d_0.conda + - pypi: https://files.pythonhosted.org/packages/8d/3f/95338030883d8c8b91223b4e21744b04d11b161a3ef117295d8241f50ab4/accessible_pygments-0.0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/68/7b/fe0fe0f5e05e13629d893c760465173a15ad0039c0a5b0d0040995c8075e/aiohttp-3.13.2-cp313-cp313-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/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/da/875925db2ed80dc7b919b2817da555848b608620be9662c5f835670d5d8d/asteval-1.0.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-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/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/af/02/18785edcdf6266cdd6c6dc7635f1cbeefd9a5b4c3bb8aff8bd681e9dd095/codecov-2.1.13-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/17/a7/3aa4144d3bcb719bf67b22d2d51c2d577bf801498c13cb08f64173e80497/coverage-7.12.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b0/d0/89247ec250369fc76db477720a26b2fce7ba079ff1380e4ab4529d2fe233/debugpy-1.8.17-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a2/e9/90b7d243364d3dce38c8c2a1b8c103d7a8d1383c2b24c735fae0eee038dd/doc8-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6b/be/0f2f4a5e8adc114a02b63d92bf8edbfa24db6fc602fca83c885af2479e0e/editables-0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9f/56/13ab06b4f93ca7cac71078fbe37fcea175d3216f31f85c3168a6bbd0bb9a/flake8-7.3.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7c/5b/cdd2c612277b7ac7ec8c0c9bc41812c43dc7b2d5f2b0897e15fdf5a1f915/fonttools-4.60.1-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-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/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/96/ba99a003c763998035b0de4c299598125df5fc6c9ccf834f152ddd60e0fb/h5py-3.15.1-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/04/35/aa8738d6674aba09d3f0c77a1c40aee1dbf10e1b26d03cbd987aa6642e86/hatchling-1.21.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a3/17/20c2552266728ceba271967b87919664ecc0e33efca29c3efc6baf88c5f9/ipykernel-7.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/05/aa/62893d6a591d337aa59dcc4c6f6c842f1fe20cd72c8c5c1f980255243252/ipython-9.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/85/e2/05328bd2621be49a6fed9e3030b1e51a2d04537d3f816d211b9cc53c5262/json5-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/1e/5a4d5498eba382fee667ed797cf64ae5d1b13b04356df62f067f48bb0f61/jupyterlab-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2d/7a/9d90a151f558e29c3936b8a47ac770235f436f2120aca41a6d5f3d62ae8d/kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/bc/d0/b3d3338d467d3fc937f0bb7f256711395cae6f78e22cef0656159950adf0/matplotlib-3.10.7-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7a/f0/8282d9641415e9e33df173516226b404d367a0fc55e1a60424a152913abc/mistune-3.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/92/dc/c385f38f2c2433333345a82926c6bfa5ecfff3ef787201614317b58dd8be/msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.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/0b/9a/c6f79de7ba3a0a8473129936b7b90aa461d3d46fec6f1627672b1dccf4e9/narwhals-2.12.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b6/3e/f5a5cc2885c24be13e9b937441bd16a012ac34a657fe05e58927e8af8b7a/nh3-0.3.2-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl + - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/fb/f505c95ceddd7027347b067689db71ca80bd5ecc926f913f1a23e65cf09b/numpy-2.3.5-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/83/06/48eab21dd561de2914242711434c0c0eb992ed08ff3f6107a5f44527f5e9/pillow-12.0.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/c3/3031c931098de393393e1f93a38dc9ed6805d86bb801acc3cf2d5bd1e6b7/plotly-6.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/68/3a/9f93cff5c025029a36d9a92fef47220ab4692ee7f2be0fba9f92813d0cb8/psutil-7.1.3-cp36-abi3-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/d3/c622950d87a2ffd1654208733b5bd1c5645930014abed8f4c0d74863988b/pydata_sphinx_theme-0.15.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c2/2f/81d580a0fb83baeb066698975cb14a618bdbed7720678566f1b046a95fe8/pyflakes-3.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/cc/cecf97be298bee2b2a37dd360618c819a2a7fd95251d8e480c1f0eb88f3b/pyproject_api-1.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0b/8b/6300fb80f858cda1c51ffa17075df5d846757081d11ab4aa35cef9e6258b/pytest-9.0.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/f0/c5aa0a69fd9326f013110653543f36ece4913c17921f3e1dbd78e1b423ee/python_engineio-4.12.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cd/fa/1ef2f8537272a2f383d72b9301c3ef66a49710b3bb7dcb2bd138cf2920d1/python_socketio-5.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl + - pypi: https://files.pythonhosted.org/packages/e1/67/921ec3024056483db83953ae8e48079ad62b92db7880013ca77632921dd0/readme_renderer-44.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/af/63/ac52b32b33ae62f2076ed5c4f6b00e065e3ccbb2063e9a2e813b2bfc95bf/restructuredtext_lint-2.0.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/53/97/d2cbbaa10c9b826af0e10fdf836e1bf344d9f0abb873ebc34d1f49642d3f/roman_numerals_py-3.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/b3/0860cdd012291dc21272895ce107f1e98e335509ba986dd83d72658b82b9/rpds_py-0.29.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/a3/9d/dae6db96df28e0a15dea8e986ee393af70fc97fd57669808728080529c37/ruff-0.14.6-py3-none-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/64/7c/d8a343b0a622987335a1ac848084079c47c21e44fcc450f9c145b11a56f6/scipp-25.11.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/44/13/7e63cfba8a7452eb756306aa2fd9b37a29a323b672b964b4fdeded9a3f21/scipy-1.16.3-cp313-cp313-macosx_12_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/ba/daf16c2d1965bf6237fb696639e3e93645ac6801f7dcaf9ec694a74e9326/setuptools_git_versioning-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/53/136e9eca6e0b9dc0e1962e2c908fbea2e5ac000c2a2fbd9a35797958c48b/sphinx-8.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/05/f2/9657c98a66973b7c35bfd48ba65d1922860de9598fbb535cd96e3f58a908/sphinx_autodoc_typehints-3.5.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/9e/c41d68be04eef5b6202b468e0f90faf0c469f3a03353f2a218fd78279710/sphinx_book_theme-1.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/77/c7/52b48aec16b26c52aba854d03a3a31e0681150301dac1bea2243645a69e7/sphinx_gallery-0.19.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/40/8561ce06dc46fd17242c7724ab25b257a2ac1b35f4ebf551b40ce6105cfa/stevedore-5.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f6/48/6a7529df2c9cc12efd2e8f5dd219516184d703b34c06786809670df5b3bd/tornado-6.5.2-cp39-abi3-macosx_10_9_universal2.whl + - pypi: https://files.pythonhosted.org/packages/fc/cc/e09c0d663a004945f82beecd4f147053567910479314e8d01ba71e5d5dea/tox-4.32.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fd/9e/8d50f3b3fc4af8c73154f64d4a2293bfa2d517a19000e70ef2d614254084/tox_gh_actions-3.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/49/f6/73c4aa003d1237ee9bea8a46f49dc38c45dfe95af4f0da7e60678d388011/trove_classifiers-2025.11.14.15-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-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: ./ + win-64: + - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_8.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.11.12-h4c7d964_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.3-hac47afa_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h52bdfb6_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.1-h2466b09_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libmpdec-4.0.0-h2466b09_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.51.0-hf5d6505_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.0-h725018a_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.9-h09917c8_101_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h2c6b04d_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h2b53caa_32.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_32.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_32.conda + - pypi: https://files.pythonhosted.org/packages/8d/3f/95338030883d8c8b91223b4e21744b04d11b161a3ef117295d8241f50ab4/accessible_pygments-0.0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5d/28/a8a9fc6957b2cee8902414e41816b5ab5536ecf43c3b1843c10e82c559b2/aiohttp-3.13.2-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/da/875925db2ed80dc7b919b2817da555848b608620be9662c5f835670d5d8d/asteval-1.0.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-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/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/af/02/18785edcdf6266cdd6c6dc7635f1cbeefd9a5b4c3bb8aff8bd681e9dd095/codecov-2.1.13-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/91/77/dd4aff9af16ff776bf355a24d87eeb48fc6acde54c907cc1ea89b14a8804/coverage-7.12.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/46/11/18c79a1cee5ff539a94ec4aa290c1c069a5580fd5cfd2fb2e282f8e905da/debugpy-1.8.17-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a2/e9/90b7d243364d3dce38c8c2a1b8c103d7a8d1383c2b24c735fae0eee038dd/doc8-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6b/be/0f2f4a5e8adc114a02b63d92bf8edbfa24db6fc602fca83c885af2479e0e/editables-0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9f/56/13ab06b4f93ca7cac71078fbe37fcea175d3216f31f85c3168a6bbd0bb9a/flake8-7.3.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/75/4d/b022c1577807ce8b31ffe055306ec13a866f2337ecee96e75b24b9b753ea/fonttools-4.60.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/ea/fbb258a98863f99befb10ed727152b4ae659f322e1d9c0576f8a62754e81/h5py-3.15.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/04/35/aa8738d6674aba09d3f0c77a1c40aee1dbf10e1b26d03cbd987aa6642e86/hatchling-1.21.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a3/17/20c2552266728ceba271967b87919664ecc0e33efca29c3efc6baf88c5f9/ipykernel-7.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/05/aa/62893d6a591d337aa59dcc4c6f6c842f1fe20cd72c8c5c1f980255243252/ipython-9.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/85/e2/05328bd2621be49a6fed9e3030b1e51a2d04537d3f816d211b9cc53c5262/json5-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/1e/5a4d5498eba382fee667ed797cf64ae5d1b13b04356df62f067f48bb0f61/jupyterlab-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/75/bd/f1a5d894000941739f2ae1b65a32892349423ad49c2e6d0771d0bad3fae4/kiwisolver-1.4.9-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/e1/b6/23064a96308b9aeceeffa65e96bcde459a2ea4934d311dee20afde7407a0/matplotlib-3.10.7-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7a/f0/8282d9641415e9e33df173516226b404d367a0fc55e1a60424a152913abc/mistune-3.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/32/31/75c59e7d3b4205075b4c183fa4ca398a2daf2303ddf616b04ae6ef55cffe/multidict-6.7.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/0b/9a/c6f79de7ba3a0a8473129936b7b90aa461d3d46fec6f1627672b1dccf4e9/narwhals-2.12.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/64/9a/1a1c154f10a575d20dd634e5697805e589bbdb7673a0ad00e8da90044ba7/nh3-0.3.2-cp38-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0c/88/e2eaa6cffb115b85ed7c7c87775cb8bcf0816816bc98ca8dbfa2ee33fe6e/numpy-2.3.5-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/2a/dd43dcfd6dae9b6a49ee28a8eedb98c7d5ff2de94a5d834565164667b97b/pillow-12.0.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/c3/3031c931098de393393e1f93a38dc9ed6805d86bb801acc3cf2d5bd1e6b7/plotly-6.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/55/4c/c3ed1a622b6ae2fd3c945a366e64eb35247a31e4db16cf5095e269e8eb3c/psutil-7.1.3-cp37-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/d3/c622950d87a2ffd1654208733b5bd1c5645930014abed8f4c0d74863988b/pydata_sphinx_theme-0.15.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c2/2f/81d580a0fb83baeb066698975cb14a618bdbed7720678566f1b046a95fe8/pyflakes-3.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/cc/cecf97be298bee2b2a37dd360618c819a2a7fd95251d8e480c1f0eb88f3b/pyproject_api-1.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0b/8b/6300fb80f858cda1c51ffa17075df5d846757081d11ab4aa35cef9e6258b/pytest-9.0.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/f0/c5aa0a69fd9326f013110653543f36ece4913c17921f3e1dbd78e1b423ee/python_engineio-4.12.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cd/fa/1ef2f8537272a2f383d72b9301c3ef66a49710b3bb7dcb2bd138cf2920d1/python_socketio-5.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/19/b757fe28008236a4a713e813283721b8a40aa60cd7d3f83549f2e25a3155/pywinpty-3.0.2-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/e1/67/921ec3024056483db83953ae8e48079ad62b92db7880013ca77632921dd0/readme_renderer-44.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/af/63/ac52b32b33ae62f2076ed5c4f6b00e065e3ccbb2063e9a2e813b2bfc95bf/restructuredtext_lint-2.0.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/53/97/d2cbbaa10c9b826af0e10fdf836e1bf344d9f0abb873ebc34d1f49642d3f/roman_numerals_py-3.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3c/6b/0229d3bed4ddaa409e6d90b0ae967ed4380e4bdd0dad6e59b92c17d42457/rpds_py-0.29.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/fb/02/82240553b77fd1341f80ebb3eaae43ba011c7a91b4224a9f317d8e6591af/ruff-0.14.6-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/f6/f3/9d1bb423a2dc0bbebfc98191095dd410a1268397b9c692d76a3ea971c790/scipp-25.11.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/cd/01/1204382461fcbfeb05b6161b594f4007e78b6eba9b375382f79153172b4d/scipy-1.16.3-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/ba/daf16c2d1965bf6237fb696639e3e93645ac6801f7dcaf9ec694a74e9326/setuptools_git_versioning-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/53/136e9eca6e0b9dc0e1962e2c908fbea2e5ac000c2a2fbd9a35797958c48b/sphinx-8.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/05/f2/9657c98a66973b7c35bfd48ba65d1922860de9598fbb535cd96e3f58a908/sphinx_autodoc_typehints-3.5.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/9e/c41d68be04eef5b6202b468e0f90faf0c469f3a03353f2a218fd78279710/sphinx_book_theme-1.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/77/c7/52b48aec16b26c52aba854d03a3a31e0681150301dac1bea2243645a69e7/sphinx_gallery-0.19.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/40/8561ce06dc46fd17242c7724ab25b257a2ac1b35f4ebf551b40ce6105cfa/stevedore-5.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/2a/f609b420c2f564a748a2d80ebfb2ee02a73ca80223af712fca591386cafb/tornado-6.5.2-cp39-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/fc/cc/e09c0d663a004945f82beecd4f147053567910479314e8d01ba71e5d5dea/tox-4.32.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fd/9e/8d50f3b3fc4af8c73154f64d4a2293bfa2d517a19000e70ef2d614254084/tox_gh_actions-3.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/49/f6/73c4aa003d1237ee9bea8a46f49dc38c45dfe95af4f0da7e60678d388011/trove_classifiers-2025.11.14.15-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9a/ee/450914ae11b419eadd067c6183ae08381cfdfcb9798b90b2b713bbebddda/yarl-1.22.0-cp313-cp313-win_amd64.whl + - pypi: ./ +packages: +- conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 + sha256: fe51de6107f9edc7aa4f786a70f4a883943bc9d39b3bb7307c04c41410990726 + md5: d7c89558ba9fa0495403155b64376d81 + license: None + purls: [] + size: 2562 + timestamp: 1578324546067 +- conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 + build_number: 16 + sha256: fbe2c5e56a653bebb982eda4876a9178aedfc2b545f25d0ce9c4c0b508253d22 + md5: 73aaf86a425cc6e73fcf236a5a46396d + depends: + - _libgcc_mutex 0.1 conda_forge + - libgomp >=7.5.0 + constrains: + - openmp_impl 9999 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 23621 + timestamp: 1650670423406 +- pypi: https://files.pythonhosted.org/packages/8d/3f/95338030883d8c8b91223b4e21744b04d11b161a3ef117295d8241f50ab4/accessible_pygments-0.0.5-py3-none-any.whl + name: accessible-pygments + version: 0.0.5 + sha256: 88ae3211e68a1d0b011504b2ffc1691feafce124b845bd072ab6f9f66f34d4b7 + requires_dist: + - pygments>=1.5 + - pillow ; extra == 'dev' + - pkginfo>=1.10 ; extra == 'dev' + - playwright ; extra == 'dev' + - pre-commit ; extra == 'dev' + - setuptools ; extra == 'dev' + - twine>=5.0 ; extra == 'dev' + - hypothesis ; extra == 'tests' + - pytest ; extra == 'tests' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl + name: aiohappyeyeballs + version: 2.6.1 + sha256: f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/5d/28/a8a9fc6957b2cee8902414e41816b5ab5536ecf43c3b1843c10e82c559b2/aiohttp-3.13.2-cp313-cp313-win_amd64.whl + name: aiohttp + version: 3.13.2 + sha256: a88d13e7ca367394908f8a276b89d04a3652044612b9a408a0bb22a5ed976a1a + requires_dist: + - aiohappyeyeballs>=2.5.0 + - aiosignal>=1.4.0 + - async-timeout>=4.0,<6.0 ; python_full_version < '3.11' + - attrs>=17.3.0 + - frozenlist>=1.1.1 + - multidict>=4.5,<7.0 + - propcache>=0.2.0 + - yarl>=1.17.0,<2.0 + - aiodns>=3.3.0 ; extra == 'speedups' + - brotli ; platform_python_implementation == 'CPython' and extra == 'speedups' + - brotlicffi ; 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/68/7b/fe0fe0f5e05e13629d893c760465173a15ad0039c0a5b0d0040995c8075e/aiohttp-3.13.2-cp313-cp313-macosx_11_0_arm64.whl + name: aiohttp + version: 3.13.2 + sha256: 5276807b9de9092af38ed23ce120539ab0ac955547b38563a9ba4f5b07b95293 + requires_dist: + - aiohappyeyeballs>=2.5.0 + - aiosignal>=1.4.0 + - async-timeout>=4.0,<6.0 ; python_full_version < '3.11' + - attrs>=17.3.0 + - frozenlist>=1.1.1 + - multidict>=4.5,<7.0 + - propcache>=0.2.0 + - yarl>=1.17.0,<2.0 + - aiodns>=3.3.0 ; extra == 'speedups' + - brotli ; platform_python_implementation == 'CPython' and extra == 'speedups' + - brotlicffi ; 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/db/ed/1f59215ab6853fbaa5c8495fa6cbc39edfc93553426152b75d82a5f32b76/aiohttp-3.13.2-cp313-cp313-macosx_10_13_x86_64.whl + name: aiohttp + version: 3.13.2 + sha256: 088912a78b4d4f547a1f19c099d5a506df17eacec3c6f4375e2831ec1d995742 + requires_dist: + - aiohappyeyeballs>=2.5.0 + - aiosignal>=1.4.0 + - async-timeout>=4.0,<6.0 ; python_full_version < '3.11' + - attrs>=17.3.0 + - frozenlist>=1.1.1 + - multidict>=4.5,<7.0 + - propcache>=0.2.0 + - yarl>=1.17.0,<2.0 + - aiodns>=3.3.0 ; extra == 'speedups' + - brotli ; platform_python_implementation == 'CPython' and extra == 'speedups' + - brotlicffi ; 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/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.2 + sha256: ac6cde5fba8d7d8c6ac963dbb0256a9854e9fafff52fbcc58fdf819357892c3e + requires_dist: + - aiohappyeyeballs>=2.5.0 + - aiosignal>=1.4.0 + - async-timeout>=4.0,<6.0 ; python_full_version < '3.11' + - attrs>=17.3.0 + - frozenlist>=1.1.1 + - multidict>=4.5,<7.0 + - propcache>=0.2.0 + - yarl>=1.17.0,<2.0 + - aiodns>=3.3.0 ; extra == 'speedups' + - brotli ; platform_python_implementation == 'CPython' and extra == 'speedups' + - brotlicffi ; 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 + version: 1.4.0 + sha256: 053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e + requires_dist: + - frozenlist>=1.1.0 + - typing-extensions>=4.2 ; python_full_version < '3.13' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl + name: alabaster + version: 1.0.0 + sha256: fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl + name: anyio + version: 4.11.0 + sha256: 0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc + requires_dist: + - exceptiongroup>=1.0.2 ; python_full_version < '3.11' + - idna>=2.8 + - sniffio>=1.1 + - typing-extensions>=4.5 ; python_full_version < '3.13' + - trio>=0.31.0 ; extra == 'trio' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl + name: appnope + version: 0.1.4 + sha256: 502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl + name: argon2-cffi + version: 25.1.0 + sha256: fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741 + requires_dist: + - argon2-cffi-bindings + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl + name: argon2-cffi-bindings + version: 25.1.0 + sha256: d3e924cfc503018a714f94a49a149fdc0b644eaead5d1f089330399134fa028a + requires_dist: + - cffi>=1.0.1 ; python_full_version < '3.14' + - cffi>=2.0.0b1 ; python_full_version >= '3.14' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl + name: argon2-cffi-bindings + version: 25.1.0 + sha256: 2630b6240b495dfab90aebe159ff784d08ea999aa4b0d17efa734055a07d2f44 + requires_dist: + - cffi>=1.0.1 ; python_full_version < '3.14' + - cffi>=2.0.0b1 ; python_full_version >= '3.14' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl + name: argon2-cffi-bindings + version: 25.1.0 + sha256: 7aef0c91e2c0fbca6fc68e7555aa60ef7008a739cbe045541e438373bc54d2b0 + requires_dist: + - cffi>=1.0.1 ; python_full_version < '3.14' + - cffi>=2.0.0b1 ; python_full_version >= '3.14' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl + name: argon2-cffi-bindings + version: 25.1.0 + sha256: a98cd7d17e9f7ce244c0803cad3c23a7d379c301ba618a5fa76a67d116618b98 + requires_dist: + - cffi>=1.0.1 ; python_full_version < '3.14' + - cffi>=2.0.0b1 ; python_full_version >= '3.14' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl + name: arrow + version: 1.4.0 + sha256: 749f0769958ebdc79c173ff0b0670d59051a535fa26e8eba02953dc19eb43205 + requires_dist: + - python-dateutil>=2.7.0 + - backports-zoneinfo==0.2.1 ; python_full_version < '3.9' + - tzdata ; python_full_version >= '3.9' + - doc8 ; extra == 'doc' + - sphinx>=7.0.0 ; extra == 'doc' + - sphinx-autobuild ; extra == 'doc' + - sphinx-autodoc-typehints ; extra == 'doc' + - sphinx-rtd-theme>=1.3.0 ; extra == 'doc' + - dateparser==1.* ; extra == 'test' + - pre-commit ; extra == 'test' + - pytest ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-mock ; extra == 'test' + - pytz==2025.2 ; extra == 'test' + - simplejson==3.* ; extra == 'test' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/b7/da/875925db2ed80dc7b919b2817da555848b608620be9662c5f835670d5d8d/asteval-1.0.7-py3-none-any.whl + name: asteval + version: 1.0.7 + sha256: d78df08681dfff59031ca624ba7030f9dc576a7a16e2f7a5137c6e7ef3ee60c4 + requires_dist: + - build ; extra == 'dev' + - twine ; extra == 'dev' + - sphinx ; extra == 'doc' + - pytest ; extra == 'test' + - pytest-cov ; extra == 'test' + - coverage ; extra == 'test' + - asteval[dev,doc,test] ; extra == 'all' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl + name: asttokens + version: 3.0.1 + sha256: 15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a + requires_dist: + - astroid>=2,<5 ; extra == 'astroid' + - astroid>=2,<5 ; extra == 'test' + - pytest<9.0 ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-xdist ; extra == 'test' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-py3-none-any.whl + name: async-lru + version: 2.0.5 + sha256: ab95404d8d2605310d345932697371a5f40def0487c03d6d0ad9138de52c9943 + requires_dist: + - typing-extensions>=4.0.0 ; python_full_version < '3.11' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + name: attrs + version: 25.4.0 + sha256: adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl + name: babel + version: 2.17.0 + sha256: 4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2 + requires_dist: + - pytz>=2015.7 ; python_full_version < '3.9' + - tzdata ; sys_platform == 'win32' and extra == 'dev' + - backports-zoneinfo ; python_full_version < '3.9' and extra == 'dev' + - freezegun~=1.0 ; extra == 'dev' + - jinja2>=3.0 ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - pytest>=6.0 ; extra == 'dev' + - pytz ; extra == 'dev' + - setuptools ; extra == 'dev' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl + name: beautifulsoup4 + version: 4.14.2 + sha256: 5ef6fa3a8cbece8488d66985560f97ed091e22bbc4e9c2338508a9d5de6d4515 + requires_dist: + - soupsieve>1.2 + - typing-extensions>=4.0.0 + - cchardet ; extra == 'cchardet' + - chardet ; extra == 'chardet' + - charset-normalizer ; extra == 'charset-normalizer' + - html5lib ; extra == 'html5lib' + - lxml ; extra == 'lxml' + requires_python: '>=3.7.0' +- pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl + name: bidict + version: 0.23.1 + sha256: 5dae8d4d79b552a71cbabc7deb25dfe8ce710b17ff41711e13010ead2abfc3e5 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl + name: bleach + version: 6.3.0 + sha256: fe10ec77c93ddf3d13a73b035abaac7a9f5e436513864ccdad516693213c65d6 + requires_dist: + - webencodings + - tinycss2>=1.1.0,<1.5 ; extra == 'css' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl + name: blinker + version: 1.9.0 + sha256: ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl + name: build + version: 1.3.0 + sha256: 7145f0b5061ba90a1500d60bd1b13ca0a8a4cebdd0cc16ed8adf1c0e739f43b4 + requires_dist: + - packaging>=19.1 + - pyproject-hooks + - colorama ; os_name == 'nt' + - importlib-metadata>=4.6 ; python_full_version < '3.10.2' + - tomli>=1.1.0 ; python_full_version < '3.11' + - uv>=0.1.18 ; extra == 'uv' + - virtualenv>=20.11 ; python_full_version < '3.10' and extra == 'virtualenv' + - virtualenv>=20.17 ; python_full_version >= '3.10' and python_full_version < '3.14' and extra == 'virtualenv' + - virtualenv>=20.31 ; python_full_version >= '3.14' and extra == 'virtualenv' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl + name: bumps + version: 1.0.3 + sha256: 4f503c814b9ddd2cda760b2e35aaa6285651434fc2e64ccac55b1a666bca17f6 + requires_dist: + - numpy + - scipy + - h5py + - dill + - cloudpickle + - matplotlib + - blinker + - aiohttp + - python-socketio + - plotly + - mpld3 + - msgpack + - graphlib-backport ; python_full_version < '3.9' + - build ; extra == 'dev' + - pre-commit ; extra == 'dev' + - pytest ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - ruff ; extra == 'dev' + - wheel ; extra == 'dev' + - setuptools ; extra == 'dev' + - sphinx ; extra == 'dev' + - versioningit ; extra == 'dev' + requires_python: '>=3.9' +- conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_8.conda + sha256: c30daba32ddebbb7ded490f0e371eae90f51e72db620554089103b4a6934b0d5 + md5: 51a19bba1b8ebfb60df25cde030b7ebc + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: bzip2-1.0.6 + license_family: BSD + purls: [] + size: 260341 + timestamp: 1757437258798 +- conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_8.conda + sha256: 8f50b58efb29c710f3cecf2027a8d7325ba769ab10c746eff75cea3ac050b10c + md5: 97c4b3bd8a90722104798175a1bdddbf + depends: + - __osx >=10.13 + license: bzip2-1.0.6 + license_family: BSD + purls: [] + size: 132607 + timestamp: 1757437730085 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_8.conda + sha256: b456200636bd5fecb2bec63f7e0985ad2097cf1b83d60ce0b6968dffa6d02aa1 + md5: 58fd217444c2a5701a44244faf518206 + depends: + - __osx >=11.0 + license: bzip2-1.0.6 + license_family: BSD + purls: [] + size: 125061 + timestamp: 1757437486465 +- conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_8.conda + sha256: d882712855624641f48aa9dc3f5feea2ed6b4e6004585d3616386a18186fe692 + md5: 1077e9333c41ff0be8edd1a5ec0ddace + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: bzip2-1.0.6 + license_family: BSD + purls: [] + size: 55977 + timestamp: 1757437738856 +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.11.12-h4c7d964_0.conda + sha256: 686a13bd2d4024fc99a22c1e0e68a7356af3ed3304a8d3ff6bb56249ad4e82f0 + md5: f98fb7db808b94bc1ec5b0e62f9f1069 + depends: + - __win + license: ISC + purls: [] + size: 152827 + timestamp: 1762967310929 +- 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: 152432 + timestamp: 1762967197890 +- 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' +- pypi: https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl + name: certifi + version: 2025.11.12 + sha256: 97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl + name: cffi + version: 2.0.0 + sha256: 19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75 + requires_dist: + - pycparser ; implementation_name != 'PyPy' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl + name: cffi + version: 2.0.0 + sha256: 45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca + requires_dist: + - pycparser ; implementation_name != 'PyPy' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl + name: cffi + version: 2.0.0 + sha256: 00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb + requires_dist: + - pycparser ; implementation_name != 'PyPy' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + name: cffi + version: 2.0.0 + sha256: c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26 + requires_dist: + - pycparser ; implementation_name != 'PyPy' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl + name: chardet + version: 5.2.0 + sha256: e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl + name: charset-normalizer + version: 3.4.4 + sha256: e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl + name: charset-normalizer + version: 3.4.4 + sha256: b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: charset-normalizer + version: 3.4.4 + sha256: a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl + name: cloudpickle + version: 3.1.2 + sha256: 9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/af/02/18785edcdf6266cdd6c6dc7635f1cbeefd9a5b4c3bb8aff8bd681e9dd095/codecov-2.1.13-py2.py3-none-any.whl + name: codecov + version: 2.1.13 + sha256: c2ca5e51bba9ebb43644c43d0690148a55086f7f5e6fd36170858fa4206744d5 + requires_dist: + - requests>=2.7.9 + - coverage + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*' +- pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl + name: colorama + version: 0.4.6 + sha256: 4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*' +- pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl + name: comm + version: 0.2.3 + sha256: c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417 + requires_dist: + - pytest ; extra == 'test' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl + name: contourpy + version: 1.3.3 + sha256: 1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263 + requires_dist: + - numpy>=1.25 + - furo ; extra == 'docs' + - sphinx>=7.2 ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - bokeh ; extra == 'bokeh' + - selenium ; extra == 'bokeh' + - contourpy[bokeh,docs] ; extra == 'mypy' + - bokeh ; extra == 'mypy' + - docutils-stubs ; extra == 'mypy' + - mypy==1.17.0 ; extra == 'mypy' + - types-pillow ; extra == 'mypy' + - contourpy[test-no-images] ; extra == 'test' + - matplotlib ; extra == 'test' + - pillow ; extra == 'test' + - pytest ; extra == 'test-no-images' + - pytest-cov ; extra == 'test-no-images' + - pytest-rerunfailures ; extra == 'test-no-images' + - pytest-xdist ; extra == 'test-no-images' + - wurlitzer ; extra == 'test-no-images' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: contourpy + version: 1.3.3 + sha256: 4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9 + requires_dist: + - numpy>=1.25 + - furo ; extra == 'docs' + - sphinx>=7.2 ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - bokeh ; extra == 'bokeh' + - selenium ; extra == 'bokeh' + - contourpy[bokeh,docs] ; extra == 'mypy' + - bokeh ; extra == 'mypy' + - docutils-stubs ; extra == 'mypy' + - mypy==1.17.0 ; extra == 'mypy' + - types-pillow ; extra == 'mypy' + - contourpy[test-no-images] ; extra == 'test' + - matplotlib ; extra == 'test' + - pillow ; extra == 'test' + - pytest ; extra == 'test-no-images' + - pytest-cov ; extra == 'test-no-images' + - pytest-rerunfailures ; extra == 'test-no-images' + - pytest-xdist ; extra == 'test-no-images' + - wurlitzer ; extra == 'test-no-images' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl + name: contourpy + version: 1.3.3 + sha256: 177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5 + requires_dist: + - numpy>=1.25 + - furo ; extra == 'docs' + - sphinx>=7.2 ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - bokeh ; extra == 'bokeh' + - selenium ; extra == 'bokeh' + - contourpy[bokeh,docs] ; extra == 'mypy' + - bokeh ; extra == 'mypy' + - docutils-stubs ; extra == 'mypy' + - mypy==1.17.0 ; extra == 'mypy' + - types-pillow ; extra == 'mypy' + - contourpy[test-no-images] ; extra == 'test' + - matplotlib ; extra == 'test' + - pillow ; extra == 'test' + - pytest ; extra == 'test-no-images' + - pytest-cov ; extra == 'test-no-images' + - pytest-rerunfailures ; extra == 'test-no-images' + - pytest-xdist ; extra == 'test-no-images' + - wurlitzer ; extra == 'test-no-images' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl + name: contourpy + version: 1.3.3 + sha256: d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1 + requires_dist: + - numpy>=1.25 + - furo ; extra == 'docs' + - sphinx>=7.2 ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - bokeh ; extra == 'bokeh' + - selenium ; extra == 'bokeh' + - contourpy[bokeh,docs] ; extra == 'mypy' + - bokeh ; extra == 'mypy' + - docutils-stubs ; extra == 'mypy' + - mypy==1.17.0 ; extra == 'mypy' + - types-pillow ; extra == 'mypy' + - contourpy[test-no-images] ; extra == 'test' + - matplotlib ; extra == 'test' + - pillow ; extra == 'test' + - pytest ; extra == 'test-no-images' + - pytest-cov ; extra == 'test-no-images' + - pytest-rerunfailures ; extra == 'test-no-images' + - pytest-xdist ; extra == 'test-no-images' + - wurlitzer ; extra == 'test-no-images' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/17/a7/3aa4144d3bcb719bf67b22d2d51c2d577bf801498c13cb08f64173e80497/coverage-7.12.0-cp313-cp313-macosx_11_0_arm64.whl + name: coverage + version: 7.12.0 + sha256: ccf3b2ede91decd2fb53ec73c1f949c3e034129d1e0b07798ff1d02ea0c8fa4a + requires_dist: + - tomli ; python_full_version <= '3.11' and extra == 'toml' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/76/b6/67d7c0e1f400b32c883e9342de4a8c2ae7c1a0b57c5de87622b7262e2309/coverage-7.12.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + name: coverage + version: 7.12.0 + sha256: bc13baf85cd8a4cfcf4a35c7bc9d795837ad809775f782f697bf630b7e200211 + requires_dist: + - tomli ; python_full_version <= '3.11' and extra == 'toml' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/91/77/dd4aff9af16ff776bf355a24d87eeb48fc6acde54c907cc1ea89b14a8804/coverage-7.12.0-cp313-cp313-win_amd64.whl + name: coverage + version: 7.12.0 + sha256: ce61969812d6a98a981d147d9ac583a36ac7db7766f2e64a9d4d059c2fe29d07 + requires_dist: + - tomli ; python_full_version <= '3.11' and extra == 'toml' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/b8/14/771700b4048774e48d2c54ed0c674273702713c9ee7acdfede40c2666747/coverage-7.12.0-cp313-cp313-macosx_10_13_x86_64.whl + name: coverage + version: 7.12.0 + sha256: 47324fffca8d8eae7e185b5bb20c14645f23350f870c1649003618ea91a78941 + requires_dist: + - tomli ; python_full_version <= '3.11' and extra == 'toml' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl + name: cycler + version: 0.12.1 + sha256: 85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30 + requires_dist: + - ipython ; extra == 'docs' + - matplotlib ; extra == 'docs' + - numpydoc ; extra == 'docs' + - sphinx ; extra == 'docs' + - pytest ; extra == 'tests' + - pytest-cov ; extra == 'tests' + - pytest-xdist ; extra == 'tests' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/46/11/18c79a1cee5ff539a94ec4aa290c1c069a5580fd5cfd2fb2e282f8e905da/debugpy-1.8.17-cp313-cp313-win_amd64.whl + name: debugpy + version: 1.8.17 + sha256: 6c5cd6f009ad4fca8e33e5238210dc1e5f42db07d4b6ab21ac7ffa904a196420 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/b0/d0/89247ec250369fc76db477720a26b2fce7ba079ff1380e4ab4529d2fe233/debugpy-1.8.17-py2.py3-none-any.whl + name: debugpy + version: 1.8.17 + sha256: 60c7dca6571efe660ccb7a9508d73ca14b8796c4ed484c2002abba714226cfef + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl + name: decorator + version: 5.2.1 + sha256: d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl + name: defusedxml + version: 0.7.1 + sha256: a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61 + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*' +- pypi: https://files.pythonhosted.org/packages/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl + name: dfo-ls + version: '1.6' + sha256: 416edce5537237fa417bd27aef5ba91201e856f3daae52ffd44cfacb10f5b771 + requires_dist: + - setuptools + - numpy + - scipy>=1.11 + - pandas + - pytest ; extra == 'dev' + - sphinx ; extra == 'dev' + - sphinx-rtd-theme ; extra == 'dev' + - trustregion>=1.1 ; extra == 'trustregion' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl + name: dill + version: 0.4.0 + sha256: 44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049 + requires_dist: + - objgraph>=1.7.2 ; extra == 'graph' + - gprof2dot>=2022.7.29 ; extra == 'profile' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + name: distlib + version: 0.4.0 + sha256: 9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16 +- pypi: https://files.pythonhosted.org/packages/a2/e9/90b7d243364d3dce38c8c2a1b8c103d7a8d1383c2b24c735fae0eee038dd/doc8-2.0.0-py3-none-any.whl + name: doc8 + version: 2.0.0 + sha256: 9862710027f793c25f9b1899150660e4bf1d4c9a6738742e71f32011e2e3f590 + requires_dist: + - docutils>=0.19,<=0.21.2 + - restructuredtext-lint>=0.7 + - stevedore + - tomli ; python_full_version < '3.11' + - pygments + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl + name: docutils + version: 0.21.2 + sha256: dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2 + requires_python: '>=3.9' +- pypi: ./ + name: easyscience + version: 2.0.0 + sha256: 3e1433dc7b46ccbb4a5d63a22de63b3113897cd66b602a368db0c08b09b3659c + requires_dist: + - asteval + - bumps + - dfo-ls + - lmfit + - numpy + - scipp + - uncertainties + - build ; extra == 'build' + - hatchling<=1.21.0 ; extra == 'build' + - setuptools-git-versioning ; extra == 'build' + - build ; extra == 'dev' + - codecov ; extra == 'dev' + - flake8 ; extra == 'dev' + - jupyterlab ; extra == 'dev' + - matplotlib ; extra == 'dev' + - pytest ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - ruff ; extra == 'dev' + - tox-gh-actions ; extra == 'dev' + - doc8 ; extra == 'docs' + - readme-renderer ; extra == 'docs' + - sphinx-autodoc-typehints ; extra == 'docs' + - sphinx-book-theme ; extra == 'docs' + - sphinx-gallery ; extra == 'docs' + - toml ; extra == 'docs' + requires_python: '>=3.11' + editable: true +- pypi: https://files.pythonhosted.org/packages/6b/be/0f2f4a5e8adc114a02b63d92bf8edbfa24db6fc602fca83c885af2479e0e/editables-0.5-py3-none-any.whl + name: editables + version: '0.5' + sha256: 61e5ffa82629e0d8bfe09bc44a07db3c1ab8ed1ce78a6980732870f19b5e7d4c + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl + name: executing + version: 2.2.1 + sha256: 760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017 + requires_dist: + - asttokens>=2.1.0 ; extra == 'tests' + - ipython ; extra == 'tests' + - pytest ; extra == 'tests' + - coverage ; extra == 'tests' + - coverage-enable-subprocess ; extra == 'tests' + - littleutils ; extra == 'tests' + - rich ; python_full_version >= '3.11' and extra == 'tests' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl + name: fastjsonschema + version: 2.21.2 + sha256: 1c797122d0a86c5cace2e54bf4e819c36223b552017172f32c5c024a6b77e463 + requires_dist: + - colorama ; extra == 'devel' + - jsonschema ; extra == 'devel' + - json-spec ; extra == 'devel' + - pylint ; extra == 'devel' + - pytest ; extra == 'devel' + - pytest-benchmark ; extra == 'devel' + - pytest-cache ; extra == 'devel' + - validictory ; extra == 'devel' +- pypi: https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl + name: filelock + version: 3.20.0 + sha256: 339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/9f/56/13ab06b4f93ca7cac71078fbe37fcea175d3216f31f85c3168a6bbd0bb9a/flake8-7.3.0-py2.py3-none-any.whl + name: flake8 + version: 7.3.0 + sha256: b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e + requires_dist: + - mccabe>=0.7.0,<0.8.0 + - pycodestyle>=2.14.0,<2.15.0 + - pyflakes>=3.4.0,<3.5.0 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/2d/8b/371ab3cec97ee3fe1126b3406b7abd60c8fec8975fd79a3c75cdea0c3d83/fonttools-4.60.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl + name: fonttools + version: 4.60.1 + sha256: b33a7884fabd72bdf5f910d0cf46be50dce86a0362a65cfc746a4168c67eb96c + requires_dist: + - lxml>=4.0 ; extra == 'lxml' + - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' + - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'woff' + - zopfli>=0.1.4 ; extra == 'woff' + - unicodedata2>=15.1.0 ; python_full_version < '3.13' and extra == 'unicode' + - lz4>=1.7.4.2 ; extra == 'graphite' + - scipy ; platform_python_implementation != 'PyPy' and extra == 'interpolatable' + - munkres ; platform_python_implementation == 'PyPy' and extra == 'interpolatable' + - pycairo ; extra == 'interpolatable' + - matplotlib ; extra == 'plot' + - sympy ; extra == 'symfont' + - xattr ; sys_platform == 'darwin' and extra == 'type1' + - skia-pathops>=0.5.0 ; extra == 'pathops' + - uharfbuzz>=0.23.0 ; extra == 'repacker' + - lxml>=4.0 ; extra == 'all' + - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'all' + - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'all' + - zopfli>=0.1.4 ; extra == 'all' + - unicodedata2>=15.1.0 ; python_full_version < '3.13' and extra == 'all' + - lz4>=1.7.4.2 ; extra == 'all' + - scipy ; platform_python_implementation != 'PyPy' and extra == 'all' + - munkres ; platform_python_implementation == 'PyPy' and extra == 'all' + - pycairo ; extra == 'all' + - matplotlib ; extra == 'all' + - sympy ; extra == 'all' + - xattr ; sys_platform == 'darwin' and extra == 'all' + - skia-pathops>=0.5.0 ; extra == 'all' + - uharfbuzz>=0.23.0 ; extra == 'all' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/75/4d/b022c1577807ce8b31ffe055306ec13a866f2337ecee96e75b24b9b753ea/fonttools-4.60.1-cp313-cp313-win_amd64.whl + name: fonttools + version: 4.60.1 + sha256: a3db56f153bd4c5c2b619ab02c5db5192e222150ce5a1bc10f16164714bc39ac + requires_dist: + - lxml>=4.0 ; extra == 'lxml' + - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' + - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'woff' + - zopfli>=0.1.4 ; extra == 'woff' + - unicodedata2>=15.1.0 ; python_full_version < '3.13' and extra == 'unicode' + - lz4>=1.7.4.2 ; extra == 'graphite' + - scipy ; platform_python_implementation != 'PyPy' and extra == 'interpolatable' + - munkres ; platform_python_implementation == 'PyPy' and extra == 'interpolatable' + - pycairo ; extra == 'interpolatable' + - matplotlib ; extra == 'plot' + - sympy ; extra == 'symfont' + - xattr ; sys_platform == 'darwin' and extra == 'type1' + - skia-pathops>=0.5.0 ; extra == 'pathops' + - uharfbuzz>=0.23.0 ; extra == 'repacker' + - lxml>=4.0 ; extra == 'all' + - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'all' + - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'all' + - zopfli>=0.1.4 ; extra == 'all' + - unicodedata2>=15.1.0 ; python_full_version < '3.13' and extra == 'all' + - lz4>=1.7.4.2 ; extra == 'all' + - scipy ; platform_python_implementation != 'PyPy' and extra == 'all' + - munkres ; platform_python_implementation == 'PyPy' and extra == 'all' + - pycairo ; extra == 'all' + - matplotlib ; extra == 'all' + - sympy ; extra == 'all' + - xattr ; sys_platform == 'darwin' and extra == 'all' + - skia-pathops>=0.5.0 ; extra == 'all' + - uharfbuzz>=0.23.0 ; extra == 'all' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/7c/5b/cdd2c612277b7ac7ec8c0c9bc41812c43dc7b2d5f2b0897e15fdf5a1f915/fonttools-4.60.1-cp313-cp313-macosx_10_13_universal2.whl + name: fonttools + version: 4.60.1 + sha256: 6f68576bb4bbf6060c7ab047b1574a1ebe5c50a17de62830079967b211059ebb + requires_dist: + - lxml>=4.0 ; extra == 'lxml' + - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' + - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'woff' + - zopfli>=0.1.4 ; extra == 'woff' + - unicodedata2>=15.1.0 ; python_full_version < '3.13' and extra == 'unicode' + - lz4>=1.7.4.2 ; extra == 'graphite' + - scipy ; platform_python_implementation != 'PyPy' and extra == 'interpolatable' + - munkres ; platform_python_implementation == 'PyPy' and extra == 'interpolatable' + - pycairo ; extra == 'interpolatable' + - matplotlib ; extra == 'plot' + - sympy ; extra == 'symfont' + - xattr ; sys_platform == 'darwin' and extra == 'type1' + - skia-pathops>=0.5.0 ; extra == 'pathops' + - uharfbuzz>=0.23.0 ; extra == 'repacker' + - lxml>=4.0 ; extra == 'all' + - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'all' + - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'all' + - zopfli>=0.1.4 ; extra == 'all' + - unicodedata2>=15.1.0 ; python_full_version < '3.13' and extra == 'all' + - lz4>=1.7.4.2 ; extra == 'all' + - scipy ; platform_python_implementation != 'PyPy' and extra == 'all' + - munkres ; platform_python_implementation == 'PyPy' and extra == 'all' + - pycairo ; extra == 'all' + - matplotlib ; extra == 'all' + - sympy ; extra == 'all' + - xattr ; sys_platform == 'darwin' and extra == 'all' + - skia-pathops>=0.5.0 ; extra == 'all' + - uharfbuzz>=0.23.0 ; extra == 'all' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/d6/8a/de9cc0540f542963ba5e8f3a1f6ad48fa211badc3177783b9d5cadf79b5d/fonttools-4.60.1-cp313-cp313-macosx_10_13_x86_64.whl + name: fonttools + version: 4.60.1 + sha256: eedacb5c5d22b7097482fa834bda0dafa3d914a4e829ec83cdea2a01f8c813c4 + requires_dist: + - lxml>=4.0 ; extra == 'lxml' + - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' + - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'woff' + - zopfli>=0.1.4 ; extra == 'woff' + - unicodedata2>=15.1.0 ; python_full_version < '3.13' and extra == 'unicode' + - lz4>=1.7.4.2 ; extra == 'graphite' + - scipy ; platform_python_implementation != 'PyPy' and extra == 'interpolatable' + - munkres ; platform_python_implementation == 'PyPy' and extra == 'interpolatable' + - pycairo ; extra == 'interpolatable' + - matplotlib ; extra == 'plot' + - sympy ; extra == 'symfont' + - xattr ; sys_platform == 'darwin' and extra == 'type1' + - skia-pathops>=0.5.0 ; extra == 'pathops' + - uharfbuzz>=0.23.0 ; extra == 'repacker' + - lxml>=4.0 ; extra == 'all' + - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'all' + - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'all' + - zopfli>=0.1.4 ; extra == 'all' + - unicodedata2>=15.1.0 ; python_full_version < '3.13' and extra == 'all' + - lz4>=1.7.4.2 ; extra == 'all' + - scipy ; platform_python_implementation != 'PyPy' and extra == 'all' + - munkres ; platform_python_implementation == 'PyPy' and extra == 'all' + - pycairo ; extra == 'all' + - matplotlib ; extra == 'all' + - sympy ; extra == 'all' + - xattr ; sys_platform == 'darwin' and extra == 'all' + - skia-pathops>=0.5.0 ; extra == 'all' + - uharfbuzz>=0.23.0 ; extra == 'all' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl + name: fqdn + version: 1.5.1 + sha256: 3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014 + requires_dist: + - cached-property>=1.3.0 ; python_full_version < '3.8' + requires_python: '>=2.7,!=3.0,!=3.1,!=3.2,!=3.3,!=3.4,<4' +- pypi: https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl + name: frozenlist + version: 1.8.0 + sha256: f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl + name: frozenlist + version: 1.8.0 + sha256: 96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7 + requires_python: '>=3.9' +- 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: fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl + name: frozenlist + version: 1.8.0 + sha256: 878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl + name: h11 + version: 0.16.0 + sha256: 63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/31/96/ba99a003c763998035b0de4c299598125df5fc6c9ccf834f152ddd60e0fb/h5py-3.15.1-cp313-cp313-macosx_11_0_arm64.whl + name: h5py + version: 3.15.1 + sha256: ab2219dbc6fcdb6932f76b548e2b16f34a1f52b7666e998157a4dfc02e2c4123 + requires_dist: + - numpy>=1.21.2 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/88/b3/40207e0192415cbff7ea1d37b9f24b33f6d38a5a2f5d18a678de78f967ae/h5py-3.15.1-cp313-cp313-macosx_10_13_x86_64.whl + name: h5py + version: 3.15.1 + sha256: c8440fd8bee9500c235ecb7aa1917a0389a2adb80c209fa1cc485bd70e0d94a5 + requires_dist: + - numpy>=1.21.2 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/d9/69/4402ea66272dacc10b298cca18ed73e1c0791ff2ae9ed218d3859f9698ac/h5py-3.15.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: h5py + version: 3.15.1 + sha256: 121b2b7a4c1915d63737483b7bff14ef253020f617c2fb2811f67a4bed9ac5e8 + requires_dist: + - numpy>=1.21.2 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/e5/ea/fbb258a98863f99befb10ed727152b4ae659f322e1d9c0576f8a62754e81/h5py-3.15.1-cp313-cp313-win_amd64.whl + name: h5py + version: 3.15.1 + sha256: dea78b092fd80a083563ed79a3171258d4a4d307492e7cf8b2313d464c82ba52 + requires_dist: + - numpy>=1.21.2 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/04/35/aa8738d6674aba09d3f0c77a1c40aee1dbf10e1b26d03cbd987aa6642e86/hatchling-1.21.0-py3-none-any.whl + name: hatchling + version: 1.21.0 + sha256: b33ef0ecdee6dbfd28c21ca30df459ba1d566050d033f8b5a1d0e26e5606d26b + requires_dist: + - editables>=0.3 + - packaging>=21.3 + - pathspec>=0.10.1 + - pluggy>=1.0.0 + - tomli>=1.2.2 ; python_full_version < '3.11' + - trove-classifiers + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl + name: httpcore + version: 1.0.9 + sha256: 2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55 + requires_dist: + - certifi + - h11>=0.16 + - anyio>=4.0,<5.0 ; extra == 'asyncio' + - h2>=3,<5 ; extra == 'http2' + - socksio==1.* ; extra == 'socks' + - trio>=0.22.0,<1.0 ; extra == 'trio' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl + name: httpx + version: 0.28.1 + sha256: d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad + requires_dist: + - anyio + - certifi + - httpcore==1.* + - idna + - brotli ; platform_python_implementation == 'CPython' and extra == 'brotli' + - brotlicffi ; platform_python_implementation != 'CPython' and extra == 'brotli' + - click==8.* ; extra == 'cli' + - pygments==2.* ; extra == 'cli' + - rich>=10,<14 ; extra == 'cli' + - h2>=3,<5 ; extra == 'http2' + - socksio==1.* ; extra == 'socks' + - zstandard>=0.18.0 ; extra == 'zstd' + requires_python: '>=3.8' +- conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda + sha256: 71e750d509f5fa3421087ba88ef9a7b9be11c53174af3aa4d06aff4c18b38e8e + md5: 8b189310083baabfb622af68fd9d3ae3 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc-ng >=12 + - libstdcxx-ng >=12 + license: MIT + license_family: MIT + 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 +- pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl + name: idna + version: '3.11' + sha256: 771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea + requires_dist: + - ruff>=0.6.2 ; extra == 'all' + - mypy>=1.11.2 ; extra == 'all' + - pytest>=8.3.2 ; extra == 'all' + - flake8>=7.1.1 ; extra == 'all' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl + name: imagesize + version: 1.4.1 + sha256: 0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*' +- pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl + name: iniconfig + version: 2.3.0 + sha256: f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/a3/17/20c2552266728ceba271967b87919664ecc0e33efca29c3efc6baf88c5f9/ipykernel-7.1.0-py3-none-any.whl + name: ipykernel + version: 7.1.0 + sha256: 763b5ec6c5b7776f6a8d7ce09b267693b4e5ce75cb50ae696aaefb3c85e1ea4c + requires_dist: + - appnope>=0.1.2 ; sys_platform == 'darwin' + - comm>=0.1.1 + - debugpy>=1.6.5 + - ipython>=7.23.1 + - jupyter-client>=8.0.0 + - jupyter-core>=4.12,!=5.0.* + - matplotlib-inline>=0.1 + - nest-asyncio>=1.4 + - packaging>=22 + - psutil>=5.7 + - pyzmq>=25 + - tornado>=6.2 + - traitlets>=5.4.0 + - coverage[toml] ; extra == 'cov' + - matplotlib ; extra == 'cov' + - pytest-cov ; extra == 'cov' + - trio ; extra == 'cov' + - intersphinx-registry ; extra == 'docs' + - myst-parser ; extra == 'docs' + - pydata-sphinx-theme ; extra == 'docs' + - sphinx-autodoc-typehints ; extra == 'docs' + - sphinx<8.2.0 ; extra == 'docs' + - sphinxcontrib-github-alt ; extra == 'docs' + - sphinxcontrib-spelling ; extra == 'docs' + - trio ; extra == 'docs' + - pyqt5 ; extra == 'pyqt5' + - pyside6 ; extra == 'pyside6' + - flaky ; extra == 'test' + - ipyparallel ; extra == 'test' + - pre-commit ; extra == 'test' + - pytest-asyncio>=0.23.5 ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest>=7.0,<9 ; extra == 'test' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/05/aa/62893d6a591d337aa59dcc4c6f6c842f1fe20cd72c8c5c1f980255243252/ipython-9.7.0-py3-none-any.whl + name: ipython + version: 9.7.0 + sha256: bce8ac85eb9521adc94e1845b4c03d88365fd6ac2f4908ec4ed1eb1b0a065f9f + requires_dist: + - colorama>=0.4.4 ; sys_platform == 'win32' + - decorator>=4.3.2 + - ipython-pygments-lexers>=1.0.0 + - jedi>=0.18.1 + - matplotlib-inline>=0.1.5 + - pexpect>4.3 ; sys_platform != 'emscripten' and sys_platform != 'win32' + - prompt-toolkit>=3.0.41,<3.1.0 + - pygments>=2.11.0 + - stack-data>=0.6.0 + - traitlets>=5.13.0 + - typing-extensions>=4.6 ; python_full_version < '3.12' + - black ; extra == 'black' + - docrepr ; extra == 'doc' + - exceptiongroup ; extra == 'doc' + - intersphinx-registry ; extra == 'doc' + - ipykernel ; extra == 'doc' + - ipython[matplotlib,test] ; extra == 'doc' + - setuptools>=70.0 ; extra == 'doc' + - sphinx-toml==0.0.4 ; extra == 'doc' + - sphinx-rtd-theme>=0.1.8 ; extra == 'doc' + - sphinx>=8.0 ; extra == 'doc' + - typing-extensions ; extra == 'doc' + - pytest>=7.0.0 ; extra == 'test' + - pytest-asyncio>=1.0.0 ; extra == 'test' + - testpath>=0.2 ; extra == 'test' + - packaging>=20.1.0 ; extra == 'test' + - setuptools>=61.2 ; extra == 'test' + - ipython[test] ; extra == 'test-extra' + - curio ; extra == 'test-extra' + - jupyter-ai ; extra == 'test-extra' + - ipython[matplotlib] ; extra == 'test-extra' + - nbformat ; extra == 'test-extra' + - nbclient ; extra == 'test-extra' + - ipykernel>6.30 ; extra == 'test-extra' + - numpy>=1.27 ; extra == 'test-extra' + - pandas>2.1 ; extra == 'test-extra' + - trio>=0.1.0 ; extra == 'test-extra' + - matplotlib>3.9 ; extra == 'matplotlib' + - ipython[doc,matplotlib,test,test-extra] ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl + name: ipython-pygments-lexers + version: 1.1.1 + sha256: a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c + requires_dist: + - pygments + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl + name: isoduration + version: 20.11.0 + sha256: b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042 + requires_dist: + - arrow>=0.15.0 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl + name: jedi + version: 0.19.2 + sha256: a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9 + requires_dist: + - parso>=0.8.4,<0.9.0 + - jinja2==2.11.3 ; extra == 'docs' + - markupsafe==1.1.1 ; extra == 'docs' + - pygments==2.8.1 ; extra == 'docs' + - alabaster==0.7.12 ; extra == 'docs' + - babel==2.9.1 ; extra == 'docs' + - chardet==4.0.0 ; extra == 'docs' + - commonmark==0.8.1 ; extra == 'docs' + - docutils==0.17.1 ; extra == 'docs' + - future==0.18.2 ; extra == 'docs' + - idna==2.10 ; extra == 'docs' + - imagesize==1.2.0 ; extra == 'docs' + - mock==1.0.1 ; extra == 'docs' + - packaging==20.9 ; extra == 'docs' + - pyparsing==2.4.7 ; extra == 'docs' + - pytz==2021.1 ; extra == 'docs' + - readthedocs-sphinx-ext==2.1.4 ; extra == 'docs' + - recommonmark==0.5.0 ; extra == 'docs' + - requests==2.25.1 ; extra == 'docs' + - six==1.15.0 ; extra == 'docs' + - snowballstemmer==2.1.0 ; extra == 'docs' + - sphinx-rtd-theme==0.4.3 ; extra == 'docs' + - sphinx==1.8.5 ; extra == 'docs' + - sphinxcontrib-serializinghtml==1.1.4 ; extra == 'docs' + - sphinxcontrib-websupport==1.2.4 ; extra == 'docs' + - urllib3==1.26.4 ; extra == 'docs' + - flake8==5.0.4 ; extra == 'qa' + - mypy==0.971 ; extra == 'qa' + - types-setuptools==67.2.0.1 ; extra == 'qa' + - django ; extra == 'testing' + - attrs ; extra == 'testing' + - colorama ; extra == 'testing' + - docopt ; extra == 'testing' + - pytest<9.0.0 ; extra == 'testing' + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + name: jinja2 + version: 3.1.6 + sha256: 85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67 + requires_dist: + - markupsafe>=2.0 + - babel>=2.7 ; extra == 'i18n' + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/85/e2/05328bd2621be49a6fed9e3030b1e51a2d04537d3f816d211b9cc53c5262/json5-0.12.1-py3-none-any.whl + name: json5 + version: 0.12.1 + sha256: d9c9b3bc34a5f54d43c35e11ef7cb87d8bdd098c6ace87117a7b7e83e705c1d5 + requires_dist: + - build==1.2.2.post1 ; extra == 'dev' + - coverage==7.5.4 ; python_full_version < '3.9' and extra == 'dev' + - coverage==7.8.0 ; python_full_version >= '3.9' and extra == 'dev' + - mypy==1.14.1 ; python_full_version < '3.9' and extra == 'dev' + - mypy==1.15.0 ; python_full_version >= '3.9' and extra == 'dev' + - pip==25.0.1 ; extra == 'dev' + - pylint==3.2.7 ; python_full_version < '3.9' and extra == 'dev' + - pylint==3.3.6 ; python_full_version >= '3.9' and extra == 'dev' + - ruff==0.11.2 ; extra == 'dev' + - twine==6.1.0 ; extra == 'dev' + - uv==0.6.11 ; extra == 'dev' + requires_python: '>=3.8.0' +- pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + name: jsonpointer + version: 3.0.0 + sha256: 13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl + name: jsonschema + version: 4.25.1 + sha256: 3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63 + requires_dist: + - attrs>=22.2.0 + - jsonschema-specifications>=2023.3.6 + - referencing>=0.28.4 + - rpds-py>=0.7.1 + - fqdn ; extra == 'format' + - idna ; extra == 'format' + - isoduration ; extra == 'format' + - jsonpointer>1.13 ; extra == 'format' + - rfc3339-validator ; extra == 'format' + - rfc3987 ; extra == 'format' + - uri-template ; extra == 'format' + - webcolors>=1.11 ; extra == 'format' + - fqdn ; extra == 'format-nongpl' + - idna ; extra == 'format-nongpl' + - isoduration ; extra == 'format-nongpl' + - jsonpointer>1.13 ; extra == 'format-nongpl' + - rfc3339-validator ; extra == 'format-nongpl' + - rfc3986-validator>0.1.0 ; extra == 'format-nongpl' + - rfc3987-syntax>=1.1.0 ; extra == 'format-nongpl' + - uri-template ; extra == 'format-nongpl' + - webcolors>=24.6.0 ; extra == 'format-nongpl' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl + name: jsonschema-specifications + version: 2025.9.1 + sha256: 98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe + requires_dist: + - referencing>=0.31.0 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl + name: jupyter-client + version: 8.6.3 + sha256: e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f + requires_dist: + - importlib-metadata>=4.8.3 ; python_full_version < '3.10' + - jupyter-core>=4.12,!=5.0.* + - python-dateutil>=2.8.2 + - pyzmq>=23.0 + - tornado>=6.2 + - traitlets>=5.3 + - ipykernel ; extra == 'docs' + - myst-parser ; extra == 'docs' + - pydata-sphinx-theme ; extra == 'docs' + - sphinx-autodoc-typehints ; extra == 'docs' + - sphinx>=4 ; extra == 'docs' + - sphinxcontrib-github-alt ; extra == 'docs' + - sphinxcontrib-spelling ; extra == 'docs' + - coverage ; extra == 'test' + - ipykernel>=6.14 ; extra == 'test' + - mypy ; extra == 'test' + - paramiko ; sys_platform == 'win32' and extra == 'test' + - pre-commit ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-jupyter[client]>=0.4.1 ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest<8.2.0 ; extra == 'test' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl + name: jupyter-core + version: 5.9.1 + sha256: ebf87fdc6073d142e114c72c9e29a9d7ca03fad818c5d300ce2adc1fb0743407 + requires_dist: + - platformdirs>=2.5 + - traitlets>=5.3 + - intersphinx-registry ; extra == 'docs' + - myst-parser ; extra == 'docs' + - pydata-sphinx-theme ; extra == 'docs' + - sphinx-autodoc-typehints ; extra == 'docs' + - sphinxcontrib-spelling ; extra == 'docs' + - traitlets ; extra == 'docs' + - ipykernel ; extra == 'test' + - pre-commit ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest<9 ; extra == 'test' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl + name: jupyter-events + version: 0.12.0 + sha256: 6464b2fa5ad10451c3d35fabc75eab39556ae1e2853ad0c0cc31b656731a97fb + requires_dist: + - jsonschema[format-nongpl]>=4.18.0 + - packaging + - python-json-logger>=2.0.4 + - pyyaml>=5.3 + - referencing + - rfc3339-validator + - rfc3986-validator>=0.1.1 + - traitlets>=5.3 + - click ; extra == 'cli' + - rich ; extra == 'cli' + - jupyterlite-sphinx ; extra == 'docs' + - myst-parser ; extra == 'docs' + - pydata-sphinx-theme>=0.16 ; extra == 'docs' + - sphinx>=8 ; extra == 'docs' + - sphinxcontrib-spelling ; extra == 'docs' + - click ; extra == 'test' + - pre-commit ; extra == 'test' + - pytest-asyncio>=0.19.0 ; extra == 'test' + - pytest-console-scripts ; extra == 'test' + - pytest>=7.0 ; extra == 'test' + - rich ; extra == 'test' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + name: jupyter-lsp + version: 2.3.0 + sha256: e914a3cb2addf48b1c7710914771aaf1819d46b2e5a79b0f917b5478ec93f34f + requires_dist: + - jupyter-server>=1.1.2 + - importlib-metadata>=4.8.3 ; python_full_version < '3.10' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl + name: jupyter-server + version: 2.17.0 + sha256: e8cb9c7db4251f51ed307e329b81b72ccf2056ff82d50524debde1ee1870e13f + requires_dist: + - anyio>=3.1.0 + - argon2-cffi>=21.1 + - jinja2>=3.0.3 + - jupyter-client>=7.4.4 + - jupyter-core>=4.12,!=5.0.* + - jupyter-events>=0.11.0 + - jupyter-server-terminals>=0.4.4 + - nbconvert>=6.4.4 + - nbformat>=5.3.0 + - overrides>=5.0 ; python_full_version < '3.12' + - packaging>=22.0 + - prometheus-client>=0.9 + - pywinpty>=2.0.1 ; os_name == 'nt' + - pyzmq>=24 + - send2trash>=1.8.2 + - terminado>=0.8.3 + - tornado>=6.2.0 + - traitlets>=5.6.0 + - websocket-client>=1.7 + - ipykernel ; extra == 'docs' + - jinja2 ; extra == 'docs' + - jupyter-client ; extra == 'docs' + - myst-parser ; extra == 'docs' + - nbformat ; extra == 'docs' + - prometheus-client ; extra == 'docs' + - pydata-sphinx-theme ; extra == 'docs' + - send2trash ; extra == 'docs' + - sphinx-autodoc-typehints ; extra == 'docs' + - sphinxcontrib-github-alt ; extra == 'docs' + - sphinxcontrib-openapi>=0.8.0 ; extra == 'docs' + - sphinxcontrib-spelling ; extra == 'docs' + - sphinxemoji ; extra == 'docs' + - tornado ; extra == 'docs' + - typing-extensions ; extra == 'docs' + - flaky ; extra == 'test' + - ipykernel ; extra == 'test' + - pre-commit ; extra == 'test' + - pytest-console-scripts ; extra == 'test' + - pytest-jupyter[server]>=0.7 ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest>=7.0,<9 ; extra == 'test' + - requests ; extra == 'test' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl + name: jupyter-server-terminals + version: 0.5.3 + sha256: 41ee0d7dc0ebf2809c668e0fc726dfaf258fcd3e769568996ca731b6194ae9aa + requires_dist: + - pywinpty>=2.0.3 ; os_name == 'nt' + - terminado>=0.8.3 + - jinja2 ; extra == 'docs' + - jupyter-server ; extra == 'docs' + - mistune<4.0 ; extra == 'docs' + - myst-parser ; extra == 'docs' + - nbformat ; extra == 'docs' + - packaging ; extra == 'docs' + - pydata-sphinx-theme ; extra == 'docs' + - sphinxcontrib-github-alt ; extra == 'docs' + - sphinxcontrib-openapi ; extra == 'docs' + - sphinxcontrib-spelling ; extra == 'docs' + - sphinxemoji ; extra == 'docs' + - tornado ; extra == 'docs' + - jupyter-server>=2.0.0 ; extra == 'test' + - pytest-jupyter[server]>=0.5.3 ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest>=7.0 ; extra == 'test' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/6c/1e/5a4d5498eba382fee667ed797cf64ae5d1b13b04356df62f067f48bb0f61/jupyterlab-4.5.0-py3-none-any.whl + name: jupyterlab + version: 4.5.0 + sha256: 88e157c75c1afff64c7dc4b801ec471450b922a4eae4305211ddd40da8201c8a + requires_dist: + - async-lru>=1.0.0 + - httpx>=0.25.0,<1 + - importlib-metadata>=4.8.3 ; python_full_version < '3.10' + - ipykernel>=6.5.0,!=6.30.0 + - jinja2>=3.0.3 + - jupyter-core + - jupyter-lsp>=2.0.0 + - jupyter-server>=2.4.0,<3 + - jupyterlab-server>=2.28.0,<3 + - notebook-shim>=0.2 + - packaging + - setuptools>=41.1.0 + - tomli>=1.2.2 ; python_full_version < '3.11' + - tornado>=6.2.0 + - traitlets + - build ; extra == 'dev' + - bump2version ; extra == 'dev' + - coverage ; extra == 'dev' + - hatch ; extra == 'dev' + - pre-commit ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - ruff==0.11.12 ; extra == 'dev' + - jsx-lexer ; extra == 'docs' + - myst-parser ; extra == 'docs' + - pydata-sphinx-theme>=0.13.0 ; extra == 'docs' + - pytest ; extra == 'docs' + - pytest-check-links ; extra == 'docs' + - pytest-jupyter ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - sphinx>=1.8,<8.2.0 ; extra == 'docs' + - altair==6.0.0 ; extra == 'docs-screenshots' + - ipython==8.16.1 ; extra == 'docs-screenshots' + - ipywidgets==8.1.5 ; extra == 'docs-screenshots' + - jupyterlab-geojson==3.4.0 ; extra == 'docs-screenshots' + - jupyterlab-language-pack-zh-cn==4.3.post1 ; extra == 'docs-screenshots' + - matplotlib==3.10.0 ; extra == 'docs-screenshots' + - nbconvert>=7.0.0 ; extra == 'docs-screenshots' + - pandas==2.2.3 ; extra == 'docs-screenshots' + - scipy==1.15.1 ; extra == 'docs-screenshots' + - coverage ; extra == 'test' + - pytest-check-links>=0.7 ; extra == 'test' + - pytest-console-scripts ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-jupyter>=0.5.3 ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest-tornasync ; extra == 'test' + - pytest>=7.0 ; extra == 'test' + - requests ; extra == 'test' + - requests-cache ; extra == 'test' + - virtualenv ; extra == 'test' + - copier>=9,<10 ; extra == 'upgrade-extension' + - jinja2-time<0.3 ; extra == 'upgrade-extension' + - pydantic<3.0 ; extra == 'upgrade-extension' + - pyyaml-include<3.0 ; extra == 'upgrade-extension' + - tomli-w<2.0 ; extra == 'upgrade-extension' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl + name: jupyterlab-pygments + version: 0.3.0 + sha256: 841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl + name: jupyterlab-server + version: 2.28.0 + sha256: e4355b148fdcf34d312bbbc80f22467d6d20460e8b8736bf235577dd18506968 + requires_dist: + - babel>=2.10 + - importlib-metadata>=4.8.3 ; python_full_version < '3.10' + - jinja2>=3.0.3 + - json5>=0.9.0 + - jsonschema>=4.18.0 + - jupyter-server>=1.21,<3 + - packaging>=21.3 + - requests>=2.31 + - autodoc-traits ; extra == 'docs' + - jinja2<3.2.0 ; extra == 'docs' + - mistune<4 ; extra == 'docs' + - myst-parser ; extra == 'docs' + - pydata-sphinx-theme ; extra == 'docs' + - sphinx ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - sphinxcontrib-openapi>0.8 ; extra == 'docs' + - openapi-core~=0.18.0 ; extra == 'openapi' + - ruamel-yaml ; extra == 'openapi' + - hatch ; extra == 'test' + - ipykernel ; extra == 'test' + - openapi-core~=0.18.0 ; extra == 'test' + - openapi-spec-validator>=0.6.0,<0.8.0 ; extra == 'test' + - pytest-console-scripts ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-jupyter[server]>=0.6.2 ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest>=7.0,<8 ; extra == 'test' + - requests-mock ; extra == 'test' + - ruamel-yaml ; extra == 'test' + - sphinxcontrib-spelling ; extra == 'test' + - strict-rfc3339 ; extra == 'test' + - werkzeug ; extra == 'test' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/2d/7a/9d90a151f558e29c3936b8a47ac770235f436f2120aca41a6d5f3d62ae8d/kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl + name: kiwisolver + version: 1.4.9 + sha256: 1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/75/bd/f1a5d894000941739f2ae1b65a32892349423ad49c2e6d0771d0bad3fae4/kiwisolver-1.4.9-cp313-cp313-win_amd64.whl + name: kiwisolver + version: 1.4.9 + sha256: dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/ca/f0/f44f50c9f5b1a1860261092e3bc91ecdc9acda848a8b8c6abfda4a24dd5c/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl + name: kiwisolver + version: 1.4.9 + sha256: efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/e9/e9/f218a2cb3a9ffbe324ca29a9e399fa2d2866d7f348ec3a88df87fc248fc5/kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + name: kiwisolver + version: 1.4.9 + sha256: b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl + name: lark + version: 1.3.1 + sha256: c629b661023a014c37da873b4ff58a817398d12635d3bbb2c5a03be7fe5d1e12 + requires_dist: + - regex ; extra == 'regex' + - js2py ; extra == 'nearley' + - atomicwrites ; extra == 'atomic-cache' + - interegular>=0.3.1,<0.4.0 ; extra == 'interegular' + requires_python: '>=3.8' +- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45-bootstrap_ha15bf96_3.conda + sha256: 7dfdf5500318521827c590fbda0fce5d6bda1d15dfee11b677d420580d18ccc0 + md5: 3036ca5b895b7f5146c5a25486234a68 + depends: + - __glibc >=2.17,<3.0.a0 + constrains: + - binutils_impl_linux-64 2.45 + license: GPL-3.0-only + purls: [] + size: 729222 + timestamp: 1763768158810 +- 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.3.* + license: MIT + license_family: MIT + purls: [] + size: 76643 + timestamp: 1763549731408 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.7.3-heffb93a_0.conda + sha256: d11b3a6ce5b2e832f430fd112084533a01220597221bee16d6c7dc3947dffba6 + md5: 222e0732a1d0780a622926265bee14ef + depends: + - __osx >=10.13 + constrains: + - expat 2.7.3.* + license: MIT + license_family: MIT + purls: [] + size: 74058 + timestamp: 1763549886493 +- 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.3.* + license: MIT + license_family: MIT + purls: [] + size: 67800 + timestamp: 1763549994166 +- conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.3-hac47afa_0.conda + sha256: 844ab708594bdfbd7b35e1a67c379861bcd180d6efe57b654f482ae2f7f5c21e + md5: 8c9e4f1a0e688eef2e95711178061a0f + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - expat 2.7.3.* + license: MIT + license_family: MIT + purls: [] + size: 70137 + timestamp: 1763550049107 +- 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 >=14 + license: MIT + license_family: MIT + purls: [] + size: 57821 + timestamp: 1760295480630 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.5.2-h750e83c_0.conda + sha256: 277dc89950f5d97f1683f26e362d6dca3c2efa16cb2f6fdb73d109effa1cd3d0 + md5: d214916b24c625bcc459b245d509f22e + depends: + - __osx >=10.13 + license: MIT + license_family: MIT + purls: [] + size: 52573 + timestamp: 1760295626449 +- 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: 40251 + timestamp: 1760295839166 +- conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h52bdfb6_0.conda + sha256: ddff25aaa4f0aa535413f5d831b04073789522890a4d8626366e43ecde1534a3 + md5: ba4ad812d2afc22b9a34ce8327a0930f + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: [] + size: 44866 + timestamp: 1760295760649 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-h767d61c_7.conda + sha256: 08f9b87578ab981c7713e4e6a7d935e40766e10691732bba376d4964562bcb45 + md5: c0374badb3a5d4b1372db28d19462c53 + depends: + - __glibc >=2.17,<3.0.a0 + - _openmp_mutex >=4.5 + constrains: + - libgomp 15.2.0 h767d61c_7 + - libgcc-ng ==15.2.0=*_7 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 822552 + timestamp: 1759968052178 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_7.conda + sha256: 2045066dd8e6e58aaf5ae2b722fb6dfdbb57c862b5f34ac7bfb58c40ef39b6ad + md5: 280ea6eee9e2ddefde25ff799c4f0363 + depends: + - libgcc 15.2.0 h767d61c_7 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 29313 + timestamp: 1759968065504 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-h767d61c_7.conda + sha256: e9fb1c258c8e66ee278397b5822692527c5f5786d372fe7a869b900853f3f5ca + md5: f7b4d76975aac7e5d9e6ad13845f92fe + depends: + - __glibc >=2.17,<3.0.a0 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 447919 + timestamp: 1759967942498 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_2.conda + sha256: f2591c0069447bbe28d4d696b7fcb0c5bd0b4ac582769b89addbcf26fb3430d8 + md5: 1a580f7796c7bf6393fddb8bbbde58dc + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + constrains: + - xz 5.8.1.* + license: 0BSD + purls: [] + size: 112894 + timestamp: 1749230047870 +- conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.1-hd471939_2.conda + sha256: 7e22fd1bdb8bf4c2be93de2d4e718db5c548aa082af47a7430eb23192de6bb36 + md5: 8468beea04b9065b9807fc8b9cdc5894 + depends: + - __osx >=10.13 + constrains: + - xz 5.8.1.* + license: 0BSD + purls: [] + size: 104826 + timestamp: 1749230155443 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.1-h39f12f2_2.conda + sha256: 0cb92a9e026e7bd4842f410a5c5c665c89b2eb97794ffddba519a626b8ce7285 + md5: d6df911d4564d77c4374b02552cb17d1 + depends: + - __osx >=11.0 + constrains: + - xz 5.8.1.* + license: 0BSD + purls: [] + size: 92286 + timestamp: 1749230283517 +- conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.1-h2466b09_2.conda + sha256: 55764956eb9179b98de7cc0e55696f2eff8f7b83fc3ebff5e696ca358bca28cc + md5: c15148b2e18da456f5108ccb5e411446 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + constrains: + - xz 5.8.1.* + license: 0BSD + purls: [] + size: 104935 + timestamp: 1749230611612 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb9d3cd8_0.conda + sha256: 3aa92d4074d4063f2a162cd8ecb45dccac93e543e565c01a787e16a43501f7ee + md5: c7e925f37e3b40d893459e625f6a53f1 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 91183 + timestamp: 1748393666725 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libmpdec-4.0.0-h6e16a3a_0.conda + sha256: 98299c73c7a93cd4f5ff8bb7f43cd80389f08b5a27a296d806bdef7841cc9b9e + md5: 18b81186a6adb43f000ad19ed7b70381 + depends: + - __osx >=10.13 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 77667 + timestamp: 1748393757154 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h5505292_0.conda + sha256: 0a1875fc1642324ebd6c4ac864604f3f18f57fbcf558a8264f6ced028a3c75b2 + md5: 85ccccb47823dd9f7a99d2c7f530342f + depends: + - __osx >=11.0 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 71829 + timestamp: 1748393749336 +- conda: https://conda.anaconda.org/conda-forge/win-64/libmpdec-4.0.0-h2466b09_0.conda + sha256: fc529fc82c7caf51202cc5cec5bb1c2e8d90edbac6d0a4602c966366efe3c7bf + md5: 74860100b2029e2523cf480804c76b9b + depends: + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 88657 + timestamp: 1723861474602 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.51.0-hee844dc_0.conda + sha256: 4c992dcd0e34b68f843e75406f7f303b1b97c248d18f3c7c330bdc0bc26ae0b3 + md5: 729a572a3ebb8c43933b30edcc628ceb + depends: + - __glibc >=2.17,<3.0.a0 + - icu >=75.1,<76.0a0 + - libgcc >=14 + - libzlib >=1.3.1,<2.0a0 + license: blessing + purls: [] + size: 945576 + timestamp: 1762299687230 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.51.0-h86bffb9_0.conda + sha256: ad151af8192c17591fad0b68c9ffb7849ad9f4be9da2020b38b8befd2c5f6f02 + md5: 1ee9b74571acd6dd87e6a0f783989426 + depends: + - __osx >=10.13 + - libzlib >=1.3.1,<2.0a0 + license: blessing + purls: [] + size: 986898 + timestamp: 1762300146976 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.51.0-h8adb53f_0.conda + sha256: b43d198f147f46866e5336c4a6b91668beef698bfba69d1706158460eadb2c1b + md5: 5fb1945dbc6380e6fe7e939a62267772 + depends: + - __osx >=11.0 + - icu >=75.1,<76.0a0 + - libzlib >=1.3.1,<2.0a0 + license: blessing + purls: [] + size: 909508 + timestamp: 1762300078624 +- conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.51.0-hf5d6505_0.conda + sha256: 2373bd7450693bd0f624966e1bee2f49b0bf0ffbc114275ed0a43cf35aec5b21 + md5: d2c9300ebd2848862929b18c264d1b1e + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: blessing + purls: [] + size: 1292710 + timestamp: 1762299749044 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h8f9b012_7.conda + sha256: 1b981647d9775e1cdeb2fab0a4dd9cd75a6b0de2963f6c3953dbd712f78334b3 + md5: 5b767048b1b3ee9a954b06f4084f93dc + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc 15.2.0 h767d61c_7 + constrains: + - libstdcxx-ng ==15.2.0=*_7 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 3898269 + timestamp: 1759968103436 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-h4852527_7.conda + sha256: 024fd46ac3ea8032a5ec3ea7b91c4c235701a8bf0e6520fe5e6539992a6bd05f + md5: f627678cf829bd70bccf141a19c3ad3e + depends: + - libstdcxx 15.2.0 h8f9b012_7 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 29343 + timestamp: 1759968157195 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.2-he9a06e4_0.conda + sha256: e5ec6d2ad7eef538ddcb9ea62ad4346fde70a4736342c4ad87bd713641eb9808 + md5: 80c07c68d2f6870250959dcc95b209d1 + 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/libzlib-1.3.1-hb9d3cd8_2.conda + sha256: d4bfe88d7cb447768e31650f06257995601f89076080e76df55e3112d4e47dc4 + md5: edb0dca6bc32e4f4789199455a1dbeb8 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + constrains: + - zlib 1.3.1 *_2 + license: Zlib + license_family: Other + purls: [] + size: 60963 + timestamp: 1727963148474 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-hd23fc13_2.conda + sha256: 8412f96504fc5993a63edf1e211d042a1fd5b1d51dedec755d2058948fcced09 + md5: 003a54a4e32b02f7355b50a837e699da + depends: + - __osx >=10.13 + constrains: + - zlib 1.3.1 *_2 + license: Zlib + license_family: Other + purls: [] + size: 57133 + timestamp: 1727963183990 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda + sha256: ce34669eadaba351cd54910743e6a2261b67009624dbc7daeeafdef93616711b + md5: 369964e85dc26bfe78f41399b366c435 + depends: + - __osx >=11.0 + constrains: + - zlib 1.3.1 *_2 + license: Zlib + license_family: Other + purls: [] + size: 46438 + timestamp: 1727963202283 +- conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda + sha256: ba945c6493449bed0e6e29883c4943817f7c79cbff52b83360f7b341277c6402 + md5: 41fbfac52c601159df6c01f875de31b9 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + constrains: + - zlib 1.3.1 *_2 + license: Zlib + license_family: Other + purls: [] + size: 55476 + timestamp: 1727963768015 +- pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + name: lmfit + version: 1.3.4 + sha256: afce1593b42324d37ae2908249b0c55445e2f4c1a0474ff706a8e2f7b5d949fa + requires_dist: + - asteval>=1.0 + - numpy>=1.24 + - scipy>=1.10.0 + - uncertainties>=3.2.2 + - dill>=0.3.4 + - build ; extra == 'dev' + - check-wheel-contents ; extra == 'dev' + - flake8-pyproject ; extra == 'dev' + - pre-commit ; extra == 'dev' + - twine ; extra == 'dev' + - cairosvg ; extra == 'doc' + - corner ; extra == 'doc' + - emcee>=3.0.0 ; extra == 'doc' + - ipykernel ; extra == 'doc' + - jupyter-sphinx>=0.2.4 ; extra == 'doc' + - matplotlib ; extra == 'doc' + - numdifftools ; extra == 'doc' + - pandas ; extra == 'doc' + - pillow ; extra == 'doc' + - pycairo ; sys_platform == 'win32' and extra == 'doc' + - sphinx ; extra == 'doc' + - sphinx-gallery>=0.10 ; extra == 'doc' + - sphinxcontrib-svg2pdfconverter ; extra == 'doc' + - sympy ; extra == 'doc' + - coverage ; extra == 'test' + - flaky ; extra == 'test' + - pytest ; extra == 'test' + - pytest-cov ; extra == 'test' + - lmfit[dev,doc,test] ; extra == 'all' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl + name: markupsafe + version: 3.0.3 + sha256: 9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl + name: markupsafe + version: 3.0.3 + sha256: e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl + name: markupsafe + version: 3.0.3 + sha256: 116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: markupsafe + version: 3.0.3 + sha256: ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/02/9c/207547916a02c78f6bdd83448d9b21afbc42f6379ed887ecf610984f3b4e/matplotlib-3.10.7-cp313-cp313-macosx_10_13_x86_64.whl + name: matplotlib + version: 3.10.7 + sha256: 1d9d3713a237970569156cfb4de7533b7c4eacdd61789726f444f96a0d28f57f + requires_dist: + - contourpy>=1.0.1 + - cycler>=0.10 + - fonttools>=4.22.0 + - kiwisolver>=1.3.1 + - numpy>=1.23 + - packaging>=20.0 + - pillow>=8 + - pyparsing>=3 + - python-dateutil>=2.7 + - meson-python>=0.13.1,<0.17.0 ; extra == 'dev' + - pybind11>=2.13.2,!=2.13.3 ; extra == 'dev' + - setuptools-scm>=7 ; extra == 'dev' + - setuptools>=64 ; extra == 'dev' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/22/ff/6425bf5c20d79aa5b959d1ce9e65f599632345391381c9a104133fe0b171/matplotlib-3.10.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + name: matplotlib + version: 3.10.7 + sha256: b3c4ea4948d93c9c29dc01c0c23eef66f2101bf75158c291b88de6525c55c3d1 + requires_dist: + - contourpy>=1.0.1 + - cycler>=0.10 + - fonttools>=4.22.0 + - kiwisolver>=1.3.1 + - numpy>=1.23 + - packaging>=20.0 + - pillow>=8 + - pyparsing>=3 + - python-dateutil>=2.7 + - meson-python>=0.13.1,<0.17.0 ; extra == 'dev' + - pybind11>=2.13.2,!=2.13.3 ; extra == 'dev' + - setuptools-scm>=7 ; extra == 'dev' + - setuptools>=64 ; extra == 'dev' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/bc/d0/b3d3338d467d3fc937f0bb7f256711395cae6f78e22cef0656159950adf0/matplotlib-3.10.7-cp313-cp313-macosx_11_0_arm64.whl + name: matplotlib + version: 3.10.7 + sha256: 37a1fea41153dd6ee061d21ab69c9cf2cf543160b1b85d89cd3d2e2a7902ca4c + requires_dist: + - contourpy>=1.0.1 + - cycler>=0.10 + - fonttools>=4.22.0 + - kiwisolver>=1.3.1 + - numpy>=1.23 + - packaging>=20.0 + - pillow>=8 + - pyparsing>=3 + - python-dateutil>=2.7 + - meson-python>=0.13.1,<0.17.0 ; extra == 'dev' + - pybind11>=2.13.2,!=2.13.3 ; extra == 'dev' + - setuptools-scm>=7 ; extra == 'dev' + - setuptools>=64 ; extra == 'dev' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/e1/b6/23064a96308b9aeceeffa65e96bcde459a2ea4934d311dee20afde7407a0/matplotlib-3.10.7-cp313-cp313-win_amd64.whl + name: matplotlib + version: 3.10.7 + sha256: 744991e0cc863dd669c8dc9136ca4e6e0082be2070b9d793cbd64bec872a6815 + requires_dist: + - contourpy>=1.0.1 + - cycler>=0.10 + - fonttools>=4.22.0 + - kiwisolver>=1.3.1 + - numpy>=1.23 + - packaging>=20.0 + - pillow>=8 + - pyparsing>=3 + - python-dateutil>=2.7 + - meson-python>=0.13.1,<0.17.0 ; extra == 'dev' + - pybind11>=2.13.2,!=2.13.3 ; extra == 'dev' + - setuptools-scm>=7 ; extra == 'dev' + - setuptools>=64 ; extra == 'dev' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl + name: matplotlib-inline + version: 0.2.1 + sha256: d56ce5156ba6085e00a9d54fead6ed29a9c47e215cd1bba2e976ef39f5710a76 + requires_dist: + - traitlets + - flake8 ; extra == 'test' + - nbdime ; extra == 'test' + - nbval ; extra == 'test' + - notebook ; extra == 'test' + - pytest ; extra == 'test' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl + name: mccabe + version: 0.7.0 + sha256: 6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/7a/f0/8282d9641415e9e33df173516226b404d367a0fc55e1a60424a152913abc/mistune-3.1.4-py3-none-any.whl + name: mistune + version: 3.1.4 + sha256: 93691da911e5d9d2e23bc54472892aff676df27a75274962ff9edc210364266d + requires_dist: + - typing-extensions ; python_full_version < '3.11' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl + name: mpld3 + version: 0.5.12 + sha256: bea31799a4041029a906f53f2662bbf1c49903e0c0bc712b412354158ec7cf54 + requires_dist: + - jinja2 + - matplotlib +- pypi: https://files.pythonhosted.org/packages/5d/ba/459f18c16f2b3fc1a1ca871f72f07d70c07bf768ad0a507a698b8052ac58/msgpack-1.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: msgpack + version: 1.1.2 + sha256: fac4be746328f90caa3cd4bc67e6fe36ca2bf61d5c6eb6d895b6527e3f05071e + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/6b/31/b46518ecc604d7edf3a4f94cb3bf021fc62aa301f0cb849936968164ef23/msgpack-1.1.2-cp313-cp313-macosx_10_13_x86_64.whl + name: msgpack + version: 1.1.2 + sha256: 4efd7b5979ccb539c221a4c4e16aac1a533efc97f3b759bb5a5ac9f6d10383bf + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl + name: msgpack + version: 1.1.2 + sha256: a465f0dceb8e13a487e54c07d04ae3ba131c7c5b95e2612596eafde1dccf64a9 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/92/dc/c385f38f2c2433333345a82926c6bfa5ecfff3ef787201614317b58dd8be/msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.whl + name: msgpack + version: 1.1.2 + sha256: 42eefe2c3e2af97ed470eec850facbe1b5ad1d6eacdbadc42ec98e7dcf68b4b7 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/32/31/75c59e7d3b4205075b4c183fa4ca398a2daf2303ddf616b04ae6ef55cffe/multidict-6.7.0-cp313-cp313-win_amd64.whl + name: multidict + version: 6.7.0 + sha256: 30d193c6cc6d559db42b6bcec8a5d395d34d60c9877a0b71ecd7c204fcf15390 + requires_dist: + - typing-extensions>=4.1.0 ; python_full_version < '3.11' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/91/1c/eb97db117a1ebe46d457a3d235a7b9d2e6dcab174f42d1b67663dd9e5371/multidict-6.7.0-cp313-cp313-macosx_10_13_x86_64.whl + name: multidict + version: 6.7.0 + sha256: 7ef6b61cad77091056ce0e7ce69814ef72afacb150b7ac6a3e9470def2198159 + requires_dist: + - typing-extensions>=4.1.0 ; python_full_version < '3.11' + requires_python: '>=3.9' +- 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: 9ff96e8815eecacc6645da76c413eb3b3d34cfca256c70b16b286a687d013c32 + 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 + name: multidict + version: 6.7.0 + sha256: 9c0359b1ec12b1d6849c59f9d319610b7f20ef990a6d454ab151aa0e3b9f78ca + requires_dist: + - typing-extensions>=4.1.0 ; python_full_version < '3.11' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/0b/9a/c6f79de7ba3a0a8473129936b7b90aa461d3d46fec6f1627672b1dccf4e9/narwhals-2.12.0-py3-none-any.whl + name: narwhals + version: 2.12.0 + sha256: baeba5d448a30b04c299a696bd9ee5ff73e4742143e06c49ca316b46539a7cbb + requires_dist: + - cudf>=24.10.0 ; extra == 'cudf' + - dask[dataframe]>=2024.8 ; extra == 'dask' + - duckdb>=1.1 ; extra == 'duckdb' + - ibis-framework>=6.0.0 ; extra == 'ibis' + - packaging ; extra == 'ibis' + - pyarrow-hotfix ; extra == 'ibis' + - rich ; extra == 'ibis' + - modin ; extra == 'modin' + - pandas>=1.1.3 ; extra == 'pandas' + - polars>=0.20.4 ; extra == 'polars' + - pyarrow>=13.0.0 ; extra == 'pyarrow' + - pyspark>=3.5.0 ; extra == 'pyspark' + - pyspark[connect]>=3.5.0 ; extra == 'pyspark-connect' + - sqlframe>=3.22.0,!=3.39.3 ; extra == 'sqlframe' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl + name: nbclient + version: 0.10.2 + sha256: 4ffee11e788b4a27fabeb7955547e4318a5298f34342a4bfd01f2e1faaeadc3d + requires_dist: + - jupyter-client>=6.1.12 + - jupyter-core>=4.12,!=5.0.* + - nbformat>=5.1 + - traitlets>=5.4 + - pre-commit ; extra == 'dev' + - autodoc-traits ; extra == 'docs' + - flaky ; extra == 'docs' + - ipykernel>=6.19.3 ; extra == 'docs' + - ipython ; extra == 'docs' + - ipywidgets ; extra == 'docs' + - mock ; extra == 'docs' + - moto ; extra == 'docs' + - myst-parser ; extra == 'docs' + - nbconvert>=7.1.0 ; extra == 'docs' + - pytest-asyncio ; extra == 'docs' + - pytest-cov>=4.0 ; extra == 'docs' + - pytest>=7.0,<8 ; extra == 'docs' + - sphinx-book-theme ; extra == 'docs' + - sphinx>=1.7 ; extra == 'docs' + - sphinxcontrib-spelling ; extra == 'docs' + - testpath ; extra == 'docs' + - xmltodict ; extra == 'docs' + - flaky ; extra == 'test' + - ipykernel>=6.19.3 ; extra == 'test' + - ipython ; extra == 'test' + - ipywidgets ; extra == 'test' + - nbconvert>=7.1.0 ; extra == 'test' + - pytest-asyncio ; extra == 'test' + - pytest-cov>=4.0 ; extra == 'test' + - pytest>=7.0,<8 ; extra == 'test' + - testpath ; extra == 'test' + - xmltodict ; extra == 'test' + requires_python: '>=3.9.0' +- pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl + name: nbconvert + version: 7.16.6 + sha256: 1375a7b67e0c2883678c48e506dc320febb57685e5ee67faa51b18a90f3a712b + requires_dist: + - beautifulsoup4 + - bleach[css]!=5.0.0 + - defusedxml + - importlib-metadata>=3.6 ; python_full_version < '3.10' + - jinja2>=3.0 + - jupyter-core>=4.7 + - jupyterlab-pygments + - markupsafe>=2.0 + - mistune>=2.0.3,<4 + - nbclient>=0.5.0 + - nbformat>=5.7 + - packaging + - pandocfilters>=1.4.1 + - pygments>=2.4.1 + - traitlets>=5.1 + - flaky ; extra == 'all' + - ipykernel ; extra == 'all' + - ipython ; extra == 'all' + - ipywidgets>=7.5 ; extra == 'all' + - myst-parser ; extra == 'all' + - nbsphinx>=0.2.12 ; extra == 'all' + - playwright ; extra == 'all' + - pydata-sphinx-theme ; extra == 'all' + - pyqtwebengine>=5.15 ; extra == 'all' + - pytest>=7 ; extra == 'all' + - sphinx==5.0.2 ; extra == 'all' + - sphinxcontrib-spelling ; extra == 'all' + - tornado>=6.1 ; extra == 'all' + - ipykernel ; extra == 'docs' + - ipython ; extra == 'docs' + - myst-parser ; extra == 'docs' + - nbsphinx>=0.2.12 ; extra == 'docs' + - pydata-sphinx-theme ; extra == 'docs' + - sphinx==5.0.2 ; extra == 'docs' + - sphinxcontrib-spelling ; extra == 'docs' + - pyqtwebengine>=5.15 ; extra == 'qtpdf' + - pyqtwebengine>=5.15 ; extra == 'qtpng' + - tornado>=6.1 ; extra == 'serve' + - flaky ; extra == 'test' + - ipykernel ; extra == 'test' + - ipywidgets>=7.5 ; extra == 'test' + - pytest>=7 ; extra == 'test' + - playwright ; extra == 'webpdf' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl + name: nbformat + version: 5.10.4 + sha256: 3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b + requires_dist: + - fastjsonschema>=2.15 + - jsonschema>=2.6 + - jupyter-core>=4.12,!=5.0.* + - traitlets>=5.1 + - myst-parser ; extra == 'docs' + - pydata-sphinx-theme ; extra == 'docs' + - sphinx ; extra == 'docs' + - sphinxcontrib-github-alt ; extra == 'docs' + - sphinxcontrib-spelling ; extra == 'docs' + - pep440 ; extra == 'test' + - pre-commit ; extra == 'test' + - pytest ; extra == 'test' + - testpath ; extra == 'test' + requires_python: '>=3.8' +- conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda + sha256: 3fde293232fa3fca98635e1167de6b7c7fda83caf24b9d6c91ec9eefb4f4d586 + md5: 47e340acb35de30501a76c7c799c41d7 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: X11 AND BSD-3-Clause + purls: [] + size: 891641 + timestamp: 1738195959188 +- conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-h0622a9a_3.conda + sha256: ea4a5d27ded18443749aefa49dc79f6356da8506d508b5296f60b8d51e0c4bd9 + md5: ced34dd9929f491ca6dab6a2927aff25 + depends: + - __osx >=10.13 + license: X11 AND BSD-3-Clause + purls: [] + size: 822259 + timestamp: 1738196181298 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda + sha256: 2827ada40e8d9ca69a153a45f7fd14f32b2ead7045d3bbb5d10964898fe65733 + md5: 068d497125e4bf8a66bf707254fff5ae + depends: + - __osx >=11.0 + license: X11 AND BSD-3-Clause + purls: [] + size: 797030 + timestamp: 1738196177597 +- pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl + name: nest-asyncio + version: 1.6.0 + sha256: 87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c + requires_python: '>=3.5' +- pypi: https://files.pythonhosted.org/packages/42/0f/c76bf3dba22c73c38e9b1113b017cf163f7696f50e003404ec5ecdb1e8a6/nh3-0.3.2-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + name: nh3 + version: 0.3.2 + sha256: 7bb18403f02b655a1bbe4e3a4696c2ae1d6ae8f5991f7cacb684b1ae27e6c9f7 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/64/9a/1a1c154f10a575d20dd634e5697805e589bbdb7673a0ad00e8da90044ba7/nh3-0.3.2-cp38-abi3-win_amd64.whl + name: nh3 + version: 0.3.2 + sha256: 562da3dca7a17f9077593214a9781a94b8d76de4f158f8c895e62f09573945fe + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/b6/3e/f5a5cc2885c24be13e9b937441bd16a012ac34a657fe05e58927e8af8b7a/nh3-0.3.2-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl + name: nh3 + version: 0.3.2 + sha256: 7064ccf5ace75825bd7bf57859daaaf16ed28660c1c6b306b649a9eda4b54b1e + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl + name: notebook-shim + version: 0.2.4 + sha256: 411a5be4e9dc882a074ccbcae671eda64cceb068767e9a3419096986560e1cef + requires_dist: + - jupyter-server>=1.8,<3 + - pytest ; extra == 'test' + - pytest-console-scripts ; extra == 'test' + - pytest-jupyter ; extra == 'test' + - pytest-tornasync ; extra == 'test' + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/0c/88/e2eaa6cffb115b85ed7c7c87775cb8bcf0816816bc98ca8dbfa2ee33fe6e/numpy-2.3.5-cp313-cp313-win_amd64.whl + name: numpy + version: 2.3.5 + sha256: 00dc4e846108a382c5869e77c6ed514394bdeb3403461d25a829711041217d5b + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/79/fb/f505c95ceddd7027347b067689db71ca80bd5ecc926f913f1a23e65cf09b/numpy-2.3.5-cp313-cp313-macosx_11_0_arm64.whl + name: numpy + version: 2.3.5 + sha256: aa5bc7c5d59d831d9773d1170acac7893ce3a5e130540605770ade83280e7188 + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/db/69/9cde09f36da4b5a505341180a3f2e6fadc352fd4d2b7096ce9778db83f1a/numpy-2.3.5-cp313-cp313-macosx_10_13_x86_64.whl + name: numpy + version: 2.3.5 + sha256: d0f23b44f57077c1ede8c5f26b30f706498b4862d3ff0a7298b8411dd2f043ff + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/f5/10/ca162f45a102738958dcec8023062dad0cbc17d1ab99d68c4e4a6c45fb2b/numpy-2.3.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: numpy + version: 2.3.5 + sha256: 11e06aa0af8c0f05104d56450d6093ee639e15f24ecf62d417329d06e522e017 + requires_python: '>=3.11' +- 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 + - libgcc >=14 + license: Apache-2.0 + license_family: Apache + purls: [] + size: 3165399 + timestamp: 1762839186699 +- conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.6.0-h230baf5_0.conda + sha256: 36fe9fb316be22fcfb46d5fa3e2e85eec5ef84f908b7745f68f768917235b2d5 + md5: 3f50cdf9a97d0280655758b735781096 + depends: + - __osx >=10.13 + - ca-certificates + license: Apache-2.0 + license_family: Apache + purls: [] + size: 2778996 + timestamp: 1762840724922 +- 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: 3108371 + timestamp: 1762839712322 +- conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.0-h725018a_0.conda + sha256: 6d72d6f766293d4f2aa60c28c244c8efed6946c430814175f959ffe8cab899b3 + md5: 84f8fb4afd1157f59098f618cd2437e4 + depends: + - ca-certificates + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Apache-2.0 + license_family: Apache + purls: [] + size: 9440812 + timestamp: 1762841722179 +- pypi: https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl + name: packaging + version: '25.0' + sha256: 29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + name: pandas + version: 2.3.3 + sha256: 318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac + requires_dist: + - numpy>=1.22.4 ; python_full_version < '3.11' + - numpy>=1.23.2 ; python_full_version == '3.11.*' + - numpy>=1.26.0 ; python_full_version >= '3.12' + - python-dateutil>=2.8.2 + - pytz>=2020.1 + - tzdata>=2022.7 + - hypothesis>=6.46.1 ; extra == 'test' + - pytest>=7.3.2 ; extra == 'test' + - pytest-xdist>=2.2.0 ; extra == 'test' + - pyarrow>=10.0.1 ; extra == 'pyarrow' + - bottleneck>=1.3.6 ; extra == 'performance' + - numba>=0.56.4 ; extra == 'performance' + - numexpr>=2.8.4 ; extra == 'performance' + - scipy>=1.10.0 ; extra == 'computation' + - xarray>=2022.12.0 ; extra == 'computation' + - fsspec>=2022.11.0 ; extra == 'fss' + - s3fs>=2022.11.0 ; extra == 'aws' + - gcsfs>=2022.11.0 ; extra == 'gcp' + - pandas-gbq>=0.19.0 ; extra == 'gcp' + - odfpy>=1.4.1 ; extra == 'excel' + - openpyxl>=3.1.0 ; extra == 'excel' + - python-calamine>=0.1.7 ; extra == 'excel' + - pyxlsb>=1.0.10 ; extra == 'excel' + - xlrd>=2.0.1 ; extra == 'excel' + - xlsxwriter>=3.0.5 ; extra == 'excel' + - pyarrow>=10.0.1 ; extra == 'parquet' + - pyarrow>=10.0.1 ; extra == 'feather' + - tables>=3.8.0 ; extra == 'hdf5' + - pyreadstat>=1.2.0 ; extra == 'spss' + - sqlalchemy>=2.0.0 ; extra == 'postgresql' + - psycopg2>=2.9.6 ; extra == 'postgresql' + - adbc-driver-postgresql>=0.8.0 ; extra == 'postgresql' + - sqlalchemy>=2.0.0 ; extra == 'mysql' + - pymysql>=1.0.2 ; extra == 'mysql' + - sqlalchemy>=2.0.0 ; extra == 'sql-other' + - adbc-driver-postgresql>=0.8.0 ; extra == 'sql-other' + - adbc-driver-sqlite>=0.8.0 ; extra == 'sql-other' + - beautifulsoup4>=4.11.2 ; extra == 'html' + - html5lib>=1.1 ; extra == 'html' + - lxml>=4.9.2 ; extra == 'html' + - lxml>=4.9.2 ; extra == 'xml' + - matplotlib>=3.6.3 ; extra == 'plot' + - jinja2>=3.1.2 ; extra == 'output-formatting' + - tabulate>=0.9.0 ; extra == 'output-formatting' + - pyqt5>=5.15.9 ; extra == 'clipboard' + - qtpy>=2.3.0 ; extra == 'clipboard' + - zstandard>=0.19.0 ; extra == 'compression' + - dataframe-api-compat>=0.1.7 ; extra == 'consortium-standard' + - adbc-driver-postgresql>=0.8.0 ; extra == 'all' + - adbc-driver-sqlite>=0.8.0 ; extra == 'all' + - beautifulsoup4>=4.11.2 ; extra == 'all' + - bottleneck>=1.3.6 ; extra == 'all' + - dataframe-api-compat>=0.1.7 ; extra == 'all' + - fastparquet>=2022.12.0 ; extra == 'all' + - fsspec>=2022.11.0 ; extra == 'all' + - gcsfs>=2022.11.0 ; extra == 'all' + - html5lib>=1.1 ; extra == 'all' + - hypothesis>=6.46.1 ; extra == 'all' + - jinja2>=3.1.2 ; extra == 'all' + - lxml>=4.9.2 ; extra == 'all' + - matplotlib>=3.6.3 ; extra == 'all' + - numba>=0.56.4 ; extra == 'all' + - numexpr>=2.8.4 ; extra == 'all' + - odfpy>=1.4.1 ; extra == 'all' + - openpyxl>=3.1.0 ; extra == 'all' + - pandas-gbq>=0.19.0 ; extra == 'all' + - psycopg2>=2.9.6 ; extra == 'all' + - pyarrow>=10.0.1 ; extra == 'all' + - pymysql>=1.0.2 ; extra == 'all' + - pyqt5>=5.15.9 ; extra == 'all' + - pyreadstat>=1.2.0 ; extra == 'all' + - pytest>=7.3.2 ; extra == 'all' + - pytest-xdist>=2.2.0 ; extra == 'all' + - python-calamine>=0.1.7 ; extra == 'all' + - pyxlsb>=1.0.10 ; extra == 'all' + - qtpy>=2.3.0 ; extra == 'all' + - scipy>=1.10.0 ; extra == 'all' + - s3fs>=2022.11.0 ; extra == 'all' + - sqlalchemy>=2.0.0 ; extra == 'all' + - tables>=3.8.0 ; extra == 'all' + - tabulate>=0.9.0 ; extra == 'all' + - xarray>=2022.12.0 ; extra == 'all' + - xlrd>=2.0.1 ; extra == 'all' + - xlsxwriter>=3.0.5 ; extra == 'all' + - zstandard>=0.19.0 ; extra == 'all' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl + name: pandas + version: 2.3.3 + sha256: bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8 + requires_dist: + - numpy>=1.22.4 ; python_full_version < '3.11' + - numpy>=1.23.2 ; python_full_version == '3.11.*' + - numpy>=1.26.0 ; python_full_version >= '3.12' + - python-dateutil>=2.8.2 + - pytz>=2020.1 + - tzdata>=2022.7 + - hypothesis>=6.46.1 ; extra == 'test' + - pytest>=7.3.2 ; extra == 'test' + - pytest-xdist>=2.2.0 ; extra == 'test' + - pyarrow>=10.0.1 ; extra == 'pyarrow' + - bottleneck>=1.3.6 ; extra == 'performance' + - numba>=0.56.4 ; extra == 'performance' + - numexpr>=2.8.4 ; extra == 'performance' + - scipy>=1.10.0 ; extra == 'computation' + - xarray>=2022.12.0 ; extra == 'computation' + - fsspec>=2022.11.0 ; extra == 'fss' + - s3fs>=2022.11.0 ; extra == 'aws' + - gcsfs>=2022.11.0 ; extra == 'gcp' + - pandas-gbq>=0.19.0 ; extra == 'gcp' + - odfpy>=1.4.1 ; extra == 'excel' + - openpyxl>=3.1.0 ; extra == 'excel' + - python-calamine>=0.1.7 ; extra == 'excel' + - pyxlsb>=1.0.10 ; extra == 'excel' + - xlrd>=2.0.1 ; extra == 'excel' + - xlsxwriter>=3.0.5 ; extra == 'excel' + - pyarrow>=10.0.1 ; extra == 'parquet' + - pyarrow>=10.0.1 ; extra == 'feather' + - tables>=3.8.0 ; extra == 'hdf5' + - pyreadstat>=1.2.0 ; extra == 'spss' + - sqlalchemy>=2.0.0 ; extra == 'postgresql' + - psycopg2>=2.9.6 ; extra == 'postgresql' + - adbc-driver-postgresql>=0.8.0 ; extra == 'postgresql' + - sqlalchemy>=2.0.0 ; extra == 'mysql' + - pymysql>=1.0.2 ; extra == 'mysql' + - sqlalchemy>=2.0.0 ; extra == 'sql-other' + - adbc-driver-postgresql>=0.8.0 ; extra == 'sql-other' + - adbc-driver-sqlite>=0.8.0 ; extra == 'sql-other' + - beautifulsoup4>=4.11.2 ; extra == 'html' + - html5lib>=1.1 ; extra == 'html' + - lxml>=4.9.2 ; extra == 'html' + - lxml>=4.9.2 ; extra == 'xml' + - matplotlib>=3.6.3 ; extra == 'plot' + - jinja2>=3.1.2 ; extra == 'output-formatting' + - tabulate>=0.9.0 ; extra == 'output-formatting' + - pyqt5>=5.15.9 ; extra == 'clipboard' + - qtpy>=2.3.0 ; extra == 'clipboard' + - zstandard>=0.19.0 ; extra == 'compression' + - dataframe-api-compat>=0.1.7 ; extra == 'consortium-standard' + - adbc-driver-postgresql>=0.8.0 ; extra == 'all' + - adbc-driver-sqlite>=0.8.0 ; extra == 'all' + - beautifulsoup4>=4.11.2 ; extra == 'all' + - bottleneck>=1.3.6 ; extra == 'all' + - dataframe-api-compat>=0.1.7 ; extra == 'all' + - fastparquet>=2022.12.0 ; extra == 'all' + - fsspec>=2022.11.0 ; extra == 'all' + - gcsfs>=2022.11.0 ; extra == 'all' + - html5lib>=1.1 ; extra == 'all' + - hypothesis>=6.46.1 ; extra == 'all' + - jinja2>=3.1.2 ; extra == 'all' + - lxml>=4.9.2 ; extra == 'all' + - matplotlib>=3.6.3 ; extra == 'all' + - numba>=0.56.4 ; extra == 'all' + - numexpr>=2.8.4 ; extra == 'all' + - odfpy>=1.4.1 ; extra == 'all' + - openpyxl>=3.1.0 ; extra == 'all' + - pandas-gbq>=0.19.0 ; extra == 'all' + - psycopg2>=2.9.6 ; extra == 'all' + - pyarrow>=10.0.1 ; extra == 'all' + - pymysql>=1.0.2 ; extra == 'all' + - pyqt5>=5.15.9 ; extra == 'all' + - pyreadstat>=1.2.0 ; extra == 'all' + - pytest>=7.3.2 ; extra == 'all' + - pytest-xdist>=2.2.0 ; extra == 'all' + - python-calamine>=0.1.7 ; extra == 'all' + - pyxlsb>=1.0.10 ; extra == 'all' + - qtpy>=2.3.0 ; extra == 'all' + - scipy>=1.10.0 ; extra == 'all' + - s3fs>=2022.11.0 ; extra == 'all' + - sqlalchemy>=2.0.0 ; extra == 'all' + - tables>=3.8.0 ; extra == 'all' + - tabulate>=0.9.0 ; extra == 'all' + - xarray>=2022.12.0 ; extra == 'all' + - xlrd>=2.0.1 ; extra == 'all' + - xlsxwriter>=3.0.5 ; extra == 'all' + - zstandard>=0.19.0 ; extra == 'all' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl + name: pandas + version: 2.3.3 + sha256: f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee + requires_dist: + - numpy>=1.22.4 ; python_full_version < '3.11' + - numpy>=1.23.2 ; python_full_version == '3.11.*' + - numpy>=1.26.0 ; python_full_version >= '3.12' + - python-dateutil>=2.8.2 + - pytz>=2020.1 + - tzdata>=2022.7 + - hypothesis>=6.46.1 ; extra == 'test' + - pytest>=7.3.2 ; extra == 'test' + - pytest-xdist>=2.2.0 ; extra == 'test' + - pyarrow>=10.0.1 ; extra == 'pyarrow' + - bottleneck>=1.3.6 ; extra == 'performance' + - numba>=0.56.4 ; extra == 'performance' + - numexpr>=2.8.4 ; extra == 'performance' + - scipy>=1.10.0 ; extra == 'computation' + - xarray>=2022.12.0 ; extra == 'computation' + - fsspec>=2022.11.0 ; extra == 'fss' + - s3fs>=2022.11.0 ; extra == 'aws' + - gcsfs>=2022.11.0 ; extra == 'gcp' + - pandas-gbq>=0.19.0 ; extra == 'gcp' + - odfpy>=1.4.1 ; extra == 'excel' + - openpyxl>=3.1.0 ; extra == 'excel' + - python-calamine>=0.1.7 ; extra == 'excel' + - pyxlsb>=1.0.10 ; extra == 'excel' + - xlrd>=2.0.1 ; extra == 'excel' + - xlsxwriter>=3.0.5 ; extra == 'excel' + - pyarrow>=10.0.1 ; extra == 'parquet' + - pyarrow>=10.0.1 ; extra == 'feather' + - tables>=3.8.0 ; extra == 'hdf5' + - pyreadstat>=1.2.0 ; extra == 'spss' + - sqlalchemy>=2.0.0 ; extra == 'postgresql' + - psycopg2>=2.9.6 ; extra == 'postgresql' + - adbc-driver-postgresql>=0.8.0 ; extra == 'postgresql' + - sqlalchemy>=2.0.0 ; extra == 'mysql' + - pymysql>=1.0.2 ; extra == 'mysql' + - sqlalchemy>=2.0.0 ; extra == 'sql-other' + - adbc-driver-postgresql>=0.8.0 ; extra == 'sql-other' + - adbc-driver-sqlite>=0.8.0 ; extra == 'sql-other' + - beautifulsoup4>=4.11.2 ; extra == 'html' + - html5lib>=1.1 ; extra == 'html' + - lxml>=4.9.2 ; extra == 'html' + - lxml>=4.9.2 ; extra == 'xml' + - matplotlib>=3.6.3 ; extra == 'plot' + - jinja2>=3.1.2 ; extra == 'output-formatting' + - tabulate>=0.9.0 ; extra == 'output-formatting' + - pyqt5>=5.15.9 ; extra == 'clipboard' + - qtpy>=2.3.0 ; extra == 'clipboard' + - zstandard>=0.19.0 ; extra == 'compression' + - dataframe-api-compat>=0.1.7 ; extra == 'consortium-standard' + - adbc-driver-postgresql>=0.8.0 ; extra == 'all' + - adbc-driver-sqlite>=0.8.0 ; extra == 'all' + - beautifulsoup4>=4.11.2 ; extra == 'all' + - bottleneck>=1.3.6 ; extra == 'all' + - dataframe-api-compat>=0.1.7 ; extra == 'all' + - fastparquet>=2022.12.0 ; extra == 'all' + - fsspec>=2022.11.0 ; extra == 'all' + - gcsfs>=2022.11.0 ; extra == 'all' + - html5lib>=1.1 ; extra == 'all' + - hypothesis>=6.46.1 ; extra == 'all' + - jinja2>=3.1.2 ; extra == 'all' + - lxml>=4.9.2 ; extra == 'all' + - matplotlib>=3.6.3 ; extra == 'all' + - numba>=0.56.4 ; extra == 'all' + - numexpr>=2.8.4 ; extra == 'all' + - odfpy>=1.4.1 ; extra == 'all' + - openpyxl>=3.1.0 ; extra == 'all' + - pandas-gbq>=0.19.0 ; extra == 'all' + - psycopg2>=2.9.6 ; extra == 'all' + - pyarrow>=10.0.1 ; extra == 'all' + - pymysql>=1.0.2 ; extra == 'all' + - pyqt5>=5.15.9 ; extra == 'all' + - pyreadstat>=1.2.0 ; extra == 'all' + - pytest>=7.3.2 ; extra == 'all' + - pytest-xdist>=2.2.0 ; extra == 'all' + - python-calamine>=0.1.7 ; extra == 'all' + - pyxlsb>=1.0.10 ; extra == 'all' + - qtpy>=2.3.0 ; extra == 'all' + - scipy>=1.10.0 ; extra == 'all' + - s3fs>=2022.11.0 ; extra == 'all' + - sqlalchemy>=2.0.0 ; extra == 'all' + - tables>=3.8.0 ; extra == 'all' + - tabulate>=0.9.0 ; extra == 'all' + - xarray>=2022.12.0 ; extra == 'all' + - xlrd>=2.0.1 ; extra == 'all' + - xlsxwriter>=3.0.5 ; extra == 'all' + - zstandard>=0.19.0 ; extra == 'all' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl + name: pandas + version: 2.3.3 + sha256: 56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713 + requires_dist: + - numpy>=1.22.4 ; python_full_version < '3.11' + - numpy>=1.23.2 ; python_full_version == '3.11.*' + - numpy>=1.26.0 ; python_full_version >= '3.12' + - python-dateutil>=2.8.2 + - pytz>=2020.1 + - tzdata>=2022.7 + - hypothesis>=6.46.1 ; extra == 'test' + - pytest>=7.3.2 ; extra == 'test' + - pytest-xdist>=2.2.0 ; extra == 'test' + - pyarrow>=10.0.1 ; extra == 'pyarrow' + - bottleneck>=1.3.6 ; extra == 'performance' + - numba>=0.56.4 ; extra == 'performance' + - numexpr>=2.8.4 ; extra == 'performance' + - scipy>=1.10.0 ; extra == 'computation' + - xarray>=2022.12.0 ; extra == 'computation' + - fsspec>=2022.11.0 ; extra == 'fss' + - s3fs>=2022.11.0 ; extra == 'aws' + - gcsfs>=2022.11.0 ; extra == 'gcp' + - pandas-gbq>=0.19.0 ; extra == 'gcp' + - odfpy>=1.4.1 ; extra == 'excel' + - openpyxl>=3.1.0 ; extra == 'excel' + - python-calamine>=0.1.7 ; extra == 'excel' + - pyxlsb>=1.0.10 ; extra == 'excel' + - xlrd>=2.0.1 ; extra == 'excel' + - xlsxwriter>=3.0.5 ; extra == 'excel' + - pyarrow>=10.0.1 ; extra == 'parquet' + - pyarrow>=10.0.1 ; extra == 'feather' + - tables>=3.8.0 ; extra == 'hdf5' + - pyreadstat>=1.2.0 ; extra == 'spss' + - sqlalchemy>=2.0.0 ; extra == 'postgresql' + - psycopg2>=2.9.6 ; extra == 'postgresql' + - adbc-driver-postgresql>=0.8.0 ; extra == 'postgresql' + - sqlalchemy>=2.0.0 ; extra == 'mysql' + - pymysql>=1.0.2 ; extra == 'mysql' + - sqlalchemy>=2.0.0 ; extra == 'sql-other' + - adbc-driver-postgresql>=0.8.0 ; extra == 'sql-other' + - adbc-driver-sqlite>=0.8.0 ; extra == 'sql-other' + - beautifulsoup4>=4.11.2 ; extra == 'html' + - html5lib>=1.1 ; extra == 'html' + - lxml>=4.9.2 ; extra == 'html' + - lxml>=4.9.2 ; extra == 'xml' + - matplotlib>=3.6.3 ; extra == 'plot' + - jinja2>=3.1.2 ; extra == 'output-formatting' + - tabulate>=0.9.0 ; extra == 'output-formatting' + - pyqt5>=5.15.9 ; extra == 'clipboard' + - qtpy>=2.3.0 ; extra == 'clipboard' + - zstandard>=0.19.0 ; extra == 'compression' + - dataframe-api-compat>=0.1.7 ; extra == 'consortium-standard' + - adbc-driver-postgresql>=0.8.0 ; extra == 'all' + - adbc-driver-sqlite>=0.8.0 ; extra == 'all' + - beautifulsoup4>=4.11.2 ; extra == 'all' + - bottleneck>=1.3.6 ; extra == 'all' + - dataframe-api-compat>=0.1.7 ; extra == 'all' + - fastparquet>=2022.12.0 ; extra == 'all' + - fsspec>=2022.11.0 ; extra == 'all' + - gcsfs>=2022.11.0 ; extra == 'all' + - html5lib>=1.1 ; extra == 'all' + - hypothesis>=6.46.1 ; extra == 'all' + - jinja2>=3.1.2 ; extra == 'all' + - lxml>=4.9.2 ; extra == 'all' + - matplotlib>=3.6.3 ; extra == 'all' + - numba>=0.56.4 ; extra == 'all' + - numexpr>=2.8.4 ; extra == 'all' + - odfpy>=1.4.1 ; extra == 'all' + - openpyxl>=3.1.0 ; extra == 'all' + - pandas-gbq>=0.19.0 ; extra == 'all' + - psycopg2>=2.9.6 ; extra == 'all' + - pyarrow>=10.0.1 ; extra == 'all' + - pymysql>=1.0.2 ; extra == 'all' + - pyqt5>=5.15.9 ; extra == 'all' + - pyreadstat>=1.2.0 ; extra == 'all' + - pytest>=7.3.2 ; extra == 'all' + - pytest-xdist>=2.2.0 ; extra == 'all' + - python-calamine>=0.1.7 ; extra == 'all' + - pyxlsb>=1.0.10 ; extra == 'all' + - qtpy>=2.3.0 ; extra == 'all' + - scipy>=1.10.0 ; extra == 'all' + - s3fs>=2022.11.0 ; extra == 'all' + - sqlalchemy>=2.0.0 ; extra == 'all' + - tables>=3.8.0 ; extra == 'all' + - tabulate>=0.9.0 ; extra == 'all' + - xarray>=2022.12.0 ; extra == 'all' + - xlrd>=2.0.1 ; extra == 'all' + - xlsxwriter>=3.0.5 ; extra == 'all' + - zstandard>=0.19.0 ; extra == 'all' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl + name: pandocfilters + version: 1.5.1 + sha256: 93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*' +- pypi: https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl + name: parso + version: 0.8.5 + sha256: 646204b5ee239c396d040b90f9e272e9a8017c630092bf59980beb62fd033887 + requires_dist: + - pytest ; extra == 'testing' + - docopt ; extra == 'testing' + - flake8==5.0.4 ; extra == 'qa' + - mypy==0.971 ; extra == 'qa' + - types-setuptools==67.2.0.1 ; extra == 'qa' + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl + name: pathspec + version: 0.12.1 + sha256: a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl + name: pexpect + version: 4.9.0 + sha256: 7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523 + requires_dist: + - ptyprocess>=0.5 +- pypi: https://files.pythonhosted.org/packages/38/57/755dbd06530a27a5ed74f8cb0a7a44a21722ebf318edbe67ddbd7fb28f88/pillow-12.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: pillow + version: 12.0.0 + sha256: f4f1231b7dec408e8670264ce63e9c71409d9583dd21d32c163e25213ee2a344 + requires_dist: + - furo ; extra == 'docs' + - olefile ; extra == 'docs' + - sphinx>=8.2 ; extra == 'docs' + - sphinx-autobuild ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - sphinx-inline-tabs ; extra == 'docs' + - sphinxext-opengraph ; extra == 'docs' + - olefile ; extra == 'fpx' + - olefile ; extra == 'mic' + - arro3-compute ; extra == 'test-arrow' + - arro3-core ; extra == 'test-arrow' + - nanoarrow ; extra == 'test-arrow' + - pyarrow ; extra == 'test-arrow' + - check-manifest ; extra == 'tests' + - coverage>=7.4.2 ; extra == 'tests' + - defusedxml ; extra == 'tests' + - markdown2 ; extra == 'tests' + - olefile ; extra == 'tests' + - packaging ; extra == 'tests' + - pyroma>=5 ; extra == 'tests' + - pytest ; extra == 'tests' + - pytest-cov ; extra == 'tests' + - pytest-timeout ; extra == 'tests' + - pytest-xdist ; extra == 'tests' + - trove-classifiers>=2024.10.12 ; extra == 'tests' + - defusedxml ; extra == 'xmp' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/6d/2a/dd43dcfd6dae9b6a49ee28a8eedb98c7d5ff2de94a5d834565164667b97b/pillow-12.0.0-cp313-cp313-win_amd64.whl + name: pillow + version: 12.0.0 + sha256: 4cf7fed4b4580601c4345ceb5d4cbf5a980d030fd5ad07c4d2ec589f95f09905 + requires_dist: + - furo ; extra == 'docs' + - olefile ; extra == 'docs' + - sphinx>=8.2 ; extra == 'docs' + - sphinx-autobuild ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - sphinx-inline-tabs ; extra == 'docs' + - sphinxext-opengraph ; extra == 'docs' + - olefile ; extra == 'fpx' + - olefile ; extra == 'mic' + - arro3-compute ; extra == 'test-arrow' + - arro3-core ; extra == 'test-arrow' + - nanoarrow ; extra == 'test-arrow' + - pyarrow ; extra == 'test-arrow' + - check-manifest ; extra == 'tests' + - coverage>=7.4.2 ; extra == 'tests' + - defusedxml ; extra == 'tests' + - markdown2 ; extra == 'tests' + - olefile ; extra == 'tests' + - packaging ; extra == 'tests' + - pyroma>=5 ; extra == 'tests' + - pytest ; extra == 'tests' + - pytest-cov ; extra == 'tests' + - pytest-timeout ; extra == 'tests' + - pytest-xdist ; extra == 'tests' + - trove-classifiers>=2024.10.12 ; extra == 'tests' + - defusedxml ; extra == 'xmp' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/83/06/48eab21dd561de2914242711434c0c0eb992ed08ff3f6107a5f44527f5e9/pillow-12.0.0-cp313-cp313-macosx_11_0_arm64.whl + name: pillow + version: 12.0.0 + sha256: 5193fde9a5f23c331ea26d0cf171fbf67e3f247585f50c08b3e205c7aeb4589b + requires_dist: + - furo ; extra == 'docs' + - olefile ; extra == 'docs' + - sphinx>=8.2 ; extra == 'docs' + - sphinx-autobuild ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - sphinx-inline-tabs ; extra == 'docs' + - sphinxext-opengraph ; extra == 'docs' + - olefile ; extra == 'fpx' + - olefile ; extra == 'mic' + - arro3-compute ; extra == 'test-arrow' + - arro3-core ; extra == 'test-arrow' + - nanoarrow ; extra == 'test-arrow' + - pyarrow ; extra == 'test-arrow' + - check-manifest ; extra == 'tests' + - coverage>=7.4.2 ; extra == 'tests' + - defusedxml ; extra == 'tests' + - markdown2 ; extra == 'tests' + - olefile ; extra == 'tests' + - packaging ; extra == 'tests' + - pyroma>=5 ; extra == 'tests' + - pytest ; extra == 'tests' + - pytest-cov ; extra == 'tests' + - pytest-timeout ; extra == 'tests' + - pytest-xdist ; extra == 'tests' + - trove-classifiers>=2024.10.12 ; extra == 'tests' + - defusedxml ; extra == 'xmp' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/a4/a4/a0a31467e3f83b94d37568294b01d22b43ae3c5d85f2811769b9c66389dd/pillow-12.0.0-cp313-cp313-macosx_10_13_x86_64.whl + name: pillow + version: 12.0.0 + sha256: c50f36a62a22d350c96e49ad02d0da41dbd17ddc2e29750dbdba4323f85eb4a5 + requires_dist: + - furo ; extra == 'docs' + - olefile ; extra == 'docs' + - sphinx>=8.2 ; extra == 'docs' + - sphinx-autobuild ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - sphinx-inline-tabs ; extra == 'docs' + - sphinxext-opengraph ; extra == 'docs' + - olefile ; extra == 'fpx' + - olefile ; extra == 'mic' + - arro3-compute ; extra == 'test-arrow' + - arro3-core ; extra == 'test-arrow' + - nanoarrow ; extra == 'test-arrow' + - pyarrow ; extra == 'test-arrow' + - check-manifest ; extra == 'tests' + - coverage>=7.4.2 ; extra == 'tests' + - defusedxml ; extra == 'tests' + - markdown2 ; extra == 'tests' + - olefile ; extra == 'tests' + - packaging ; extra == 'tests' + - pyroma>=5 ; extra == 'tests' + - pytest ; extra == 'tests' + - pytest-cov ; extra == 'tests' + - pytest-timeout ; extra == 'tests' + - pytest-xdist ; extra == 'tests' + - trove-classifiers>=2024.10.12 ; extra == 'tests' + - defusedxml ; extra == 'xmp' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl + name: platformdirs + version: 4.5.0 + sha256: e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3 + requires_dist: + - furo>=2025.9.25 ; extra == 'docs' + - proselint>=0.14 ; extra == 'docs' + - sphinx-autodoc-typehints>=3.2 ; extra == 'docs' + - sphinx>=8.2.3 ; extra == 'docs' + - appdirs==1.4.4 ; extra == 'test' + - covdefaults>=2.3 ; extra == 'test' + - pytest-cov>=7 ; extra == 'test' + - pytest-mock>=3.15.1 ; extra == 'test' + - pytest>=8.4.2 ; extra == 'test' + - mypy>=1.18.2 ; extra == 'type' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/e7/c3/3031c931098de393393e1f93a38dc9ed6805d86bb801acc3cf2d5bd1e6b7/plotly-6.5.0-py3-none-any.whl + name: plotly + version: 6.5.0 + sha256: 5ac851e100367735250206788a2b1325412aa4a4917a4fe3e6f0bc5aa6f3d90a + requires_dist: + - narwhals>=1.15.1 + - packaging + - numpy ; extra == 'express' + - kaleido>=1.1.0 ; extra == 'kaleido' + - pytest ; extra == 'dev-core' + - requests ; extra == 'dev-core' + - ruff==0.11.12 ; extra == 'dev-core' + - plotly[dev-core] ; extra == 'dev-build' + - build ; extra == 'dev-build' + - jupyter ; extra == 'dev-build' + - plotly[dev-build] ; extra == 'dev-optional' + - plotly[kaleido] ; extra == 'dev-optional' + - anywidget ; extra == 'dev-optional' + - colorcet ; extra == 'dev-optional' + - fiona<=1.9.6 ; python_full_version < '3.9' and extra == 'dev-optional' + - geopandas ; extra == 'dev-optional' + - inflect ; extra == 'dev-optional' + - numpy ; extra == 'dev-optional' + - orjson ; extra == 'dev-optional' + - pandas ; extra == 'dev-optional' + - pdfrw ; extra == 'dev-optional' + - pillow ; extra == 'dev-optional' + - plotly-geo ; extra == 'dev-optional' + - polars[timezone] ; extra == 'dev-optional' + - pyarrow ; extra == 'dev-optional' + - pyshp ; extra == 'dev-optional' + - pytz ; extra == 'dev-optional' + - scikit-image ; extra == 'dev-optional' + - scipy ; extra == 'dev-optional' + - shapely ; extra == 'dev-optional' + - statsmodels ; extra == 'dev-optional' + - vaex ; python_full_version < '3.10' and extra == 'dev-optional' + - xarray ; extra == 'dev-optional' + - plotly[dev-optional] ; extra == 'dev' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + name: pluggy + version: 1.6.0 + sha256: e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 + requires_dist: + - pre-commit ; extra == 'dev' + - tox ; extra == 'dev' + - pytest ; extra == 'testing' + - pytest-benchmark ; extra == 'testing' + - coverage ; extra == 'testing' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl + name: prometheus-client + version: 0.23.1 + sha256: dd1913e6e76b59cfe44e7a4b83e01afc9873c1bdfd2ed8739f1e76aeca115f99 + requires_dist: + - twisted ; extra == 'twisted' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl + name: prompt-toolkit + version: 3.0.52 + sha256: 9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955 + requires_dist: + - wcwidth + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl + name: propcache + version: 0.4.1 + sha256: cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl + name: propcache + version: 0.4.1 + sha256: d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311 + requires_python: '>=3.9' +- 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.1 + sha256: d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl + name: propcache + version: 0.4.1 + sha256: 381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/55/4c/c3ed1a622b6ae2fd3c945a366e64eb35247a31e4db16cf5095e269e8eb3c/psutil-7.1.3-cp37-abi3-win_amd64.whl + name: psutil + version: 7.1.3 + sha256: f39c2c19fe824b47484b96f9692932248a54c43799a84282cfe58d05a6449efd + requires_dist: + - pytest ; extra == 'dev' + - pytest-instafail ; extra == 'dev' + - pytest-subtests ; extra == 'dev' + - pytest-xdist ; extra == 'dev' + - setuptools ; extra == 'dev' + - abi3audit ; extra == 'dev' + - black ; extra == 'dev' + - check-manifest ; extra == 'dev' + - coverage ; extra == 'dev' + - packaging ; extra == 'dev' + - pylint ; extra == 'dev' + - pyperf ; extra == 'dev' + - pypinfo ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - requests ; extra == 'dev' + - rstcheck ; extra == 'dev' + - ruff ; extra == 'dev' + - sphinx ; extra == 'dev' + - sphinx-rtd-theme ; extra == 'dev' + - toml-sort ; extra == 'dev' + - twine ; extra == 'dev' + - validate-pyproject[all] ; extra == 'dev' + - virtualenv ; extra == 'dev' + - vulture ; extra == 'dev' + - wheel ; extra == 'dev' + - colorama ; os_name == 'nt' and extra == 'dev' + - pyreadline ; os_name == 'nt' and extra == 'dev' + - pywin32 ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'dev' + - wheel ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'dev' + - wmi ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'dev' + - pytest ; extra == 'test' + - pytest-instafail ; extra == 'test' + - pytest-subtests ; extra == 'test' + - pytest-xdist ; extra == 'test' + - setuptools ; extra == 'test' + - pywin32 ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' + - wheel ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' + - wmi ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/68/3a/9f93cff5c025029a36d9a92fef47220ab4692ee7f2be0fba9f92813d0cb8/psutil-7.1.3-cp36-abi3-macosx_11_0_arm64.whl + name: psutil + version: 7.1.3 + sha256: bc31fa00f1fbc3c3802141eede66f3a2d51d89716a194bf2cd6fc68310a19880 + requires_dist: + - pytest ; extra == 'dev' + - pytest-instafail ; extra == 'dev' + - pytest-subtests ; extra == 'dev' + - pytest-xdist ; extra == 'dev' + - setuptools ; extra == 'dev' + - abi3audit ; extra == 'dev' + - black ; extra == 'dev' + - check-manifest ; extra == 'dev' + - coverage ; extra == 'dev' + - packaging ; extra == 'dev' + - pylint ; extra == 'dev' + - pyperf ; extra == 'dev' + - pypinfo ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - requests ; extra == 'dev' + - rstcheck ; extra == 'dev' + - ruff ; extra == 'dev' + - sphinx ; extra == 'dev' + - sphinx-rtd-theme ; extra == 'dev' + - toml-sort ; extra == 'dev' + - twine ; extra == 'dev' + - validate-pyproject[all] ; extra == 'dev' + - virtualenv ; extra == 'dev' + - vulture ; extra == 'dev' + - wheel ; extra == 'dev' + - colorama ; os_name == 'nt' and extra == 'dev' + - pyreadline ; os_name == 'nt' and extra == 'dev' + - pywin32 ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'dev' + - wheel ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'dev' + - wmi ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'dev' + - pytest ; extra == 'test' + - pytest-instafail ; extra == 'test' + - pytest-subtests ; extra == 'test' + - pytest-xdist ; extra == 'test' + - setuptools ; extra == 'test' + - pywin32 ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' + - wheel ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' + - wmi ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/ce/b1/5f49af514f76431ba4eea935b8ad3725cdeb397e9245ab919dbc1d1dc20f/psutil-7.1.3-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl + name: psutil + version: 7.1.3 + sha256: 3bb428f9f05c1225a558f53e30ccbad9930b11c3fc206836242de1091d3e7dd3 + requires_dist: + - pytest ; extra == 'dev' + - pytest-instafail ; extra == 'dev' + - pytest-subtests ; extra == 'dev' + - pytest-xdist ; extra == 'dev' + - setuptools ; extra == 'dev' + - abi3audit ; extra == 'dev' + - black ; extra == 'dev' + - check-manifest ; extra == 'dev' + - coverage ; extra == 'dev' + - packaging ; extra == 'dev' + - pylint ; extra == 'dev' + - pyperf ; extra == 'dev' + - pypinfo ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - requests ; extra == 'dev' + - rstcheck ; extra == 'dev' + - ruff ; extra == 'dev' + - sphinx ; extra == 'dev' + - sphinx-rtd-theme ; extra == 'dev' + - toml-sort ; extra == 'dev' + - twine ; extra == 'dev' + - validate-pyproject[all] ; extra == 'dev' + - virtualenv ; extra == 'dev' + - vulture ; extra == 'dev' + - wheel ; extra == 'dev' + - colorama ; os_name == 'nt' and extra == 'dev' + - pyreadline ; os_name == 'nt' and extra == 'dev' + - pywin32 ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'dev' + - wheel ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'dev' + - wmi ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'dev' + - pytest ; extra == 'test' + - pytest-instafail ; extra == 'test' + - pytest-subtests ; extra == 'test' + - pytest-xdist ; extra == 'test' + - setuptools ; extra == 'test' + - pywin32 ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' + - wheel ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' + - wmi ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/ef/94/46b9154a800253e7ecff5aaacdf8ebf43db99de4a2dfa18575b02548654e/psutil-7.1.3-cp36-abi3-macosx_10_9_x86_64.whl + name: psutil + version: 7.1.3 + sha256: 2bdbcd0e58ca14996a42adf3621a6244f1bb2e2e528886959c72cf1e326677ab + requires_dist: + - pytest ; extra == 'dev' + - pytest-instafail ; extra == 'dev' + - pytest-subtests ; extra == 'dev' + - pytest-xdist ; extra == 'dev' + - setuptools ; extra == 'dev' + - abi3audit ; extra == 'dev' + - black ; extra == 'dev' + - check-manifest ; extra == 'dev' + - coverage ; extra == 'dev' + - packaging ; extra == 'dev' + - pylint ; extra == 'dev' + - pyperf ; extra == 'dev' + - pypinfo ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - requests ; extra == 'dev' + - rstcheck ; extra == 'dev' + - ruff ; extra == 'dev' + - sphinx ; extra == 'dev' + - sphinx-rtd-theme ; extra == 'dev' + - toml-sort ; extra == 'dev' + - twine ; extra == 'dev' + - validate-pyproject[all] ; extra == 'dev' + - virtualenv ; extra == 'dev' + - vulture ; extra == 'dev' + - wheel ; extra == 'dev' + - colorama ; os_name == 'nt' and extra == 'dev' + - pyreadline ; os_name == 'nt' and extra == 'dev' + - pywin32 ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'dev' + - wheel ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'dev' + - wmi ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'dev' + - pytest ; extra == 'test' + - pytest-instafail ; extra == 'test' + - pytest-subtests ; extra == 'test' + - pytest-xdist ; extra == 'test' + - setuptools ; extra == 'test' + - pywin32 ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' + - wheel ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' + - wmi ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl + name: ptyprocess + version: 0.7.0 + sha256: 4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35 +- pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl + name: pure-eval + version: 0.2.3 + sha256: 1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0 + requires_dist: + - pytest ; extra == 'tests' +- pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl + name: pycodestyle + version: 2.14.0 + sha256: dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl + name: pycparser + version: '2.23' + sha256: e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/e7/d3/c622950d87a2ffd1654208733b5bd1c5645930014abed8f4c0d74863988b/pydata_sphinx_theme-0.15.4-py3-none-any.whl + name: pydata-sphinx-theme + version: 0.15.4 + sha256: 2136ad0e9500d0949f96167e63f3e298620040aea8f9c74621959eda5d4cf8e6 + requires_dist: + - sphinx>=5 + - beautifulsoup4 + - docutils!=0.17.0 + - packaging + - babel + - pygments>=2.7 + - accessible-pygments + - typing-extensions + - numpydoc ; extra == 'doc' + - linkify-it-py ; extra == 'doc' + - rich ; extra == 'doc' + - sphinxext-rediraffe ; extra == 'doc' + - sphinx-sitemap ; extra == 'doc' + - sphinx-autoapi>=3.0.0 ; extra == 'doc' + - myst-parser ; extra == 'doc' + - ablog>=0.11.8 ; extra == 'doc' + - jupyter-sphinx ; extra == 'doc' + - pandas ; extra == 'doc' + - plotly ; extra == 'doc' + - matplotlib ; extra == 'doc' + - numpy ; extra == 'doc' + - xarray ; extra == 'doc' + - sphinx-copybutton ; extra == 'doc' + - sphinx-design ; extra == 'doc' + - sphinx-togglebutton ; extra == 'doc' + - jupyterlite-sphinx ; extra == 'doc' + - sphinxcontrib-youtube>=1.4.1 ; extra == 'doc' + - sphinx-favicon>=1.0.1 ; extra == 'doc' + - ipykernel ; extra == 'doc' + - nbsphinx ; extra == 'doc' + - ipyleaflet ; extra == 'doc' + - colorama ; extra == 'doc' + - ipywidgets ; extra == 'doc' + - graphviz ; extra == 'doc' + - pytest ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-regressions ; extra == 'test' + - sphinx[test] ; extra == 'test' + - pyyaml ; extra == 'dev' + - pre-commit ; extra == 'dev' + - pydata-sphinx-theme[doc,test] ; extra == 'dev' + - tox ; extra == 'dev' + - pandoc ; extra == 'dev' + - sphinx-theme-builder[cli] ; extra == 'dev' + - pytest-playwright ; extra == 'a11y' + - babel ; extra == 'i18n' + - jinja2 ; extra == 'i18n' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/c2/2f/81d580a0fb83baeb066698975cb14a618bdbed7720678566f1b046a95fe8/pyflakes-3.4.0-py2.py3-none-any.whl + name: pyflakes + version: 3.4.0 + sha256: f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl + name: pygments + version: 2.19.2 + sha256: 86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b + requires_dist: + - colorama>=0.4.6 ; extra == 'windows-terminal' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl + name: pyparsing + version: 3.2.5 + sha256: e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e + requires_dist: + - railroad-diagrams ; extra == 'diagrams' + - jinja2 ; extra == 'diagrams' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/54/cc/cecf97be298bee2b2a37dd360618c819a2a7fd95251d8e480c1f0eb88f3b/pyproject_api-1.10.0-py3-none-any.whl + name: pyproject-api + version: 1.10.0 + sha256: 8757c41a79c0f4ab71b99abed52b97ecf66bd20b04fa59da43b5840bac105a09 + requires_dist: + - packaging>=25 + - tomli>=2.3 ; python_full_version < '3.11' + - furo>=2025.9.25 ; extra == 'docs' + - sphinx-autodoc-typehints>=3.5.1 ; extra == 'docs' + - covdefaults>=2.3 ; extra == 'testing' + - pytest-cov>=7 ; extra == 'testing' + - pytest-mock>=3.15.1 ; extra == 'testing' + - pytest>=8.4.2 ; extra == 'testing' + - setuptools>=80.9 ; extra == 'testing' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + name: pyproject-hooks + version: 1.2.0 + sha256: 9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/0b/8b/6300fb80f858cda1c51ffa17075df5d846757081d11ab4aa35cef9e6258b/pytest-9.0.1-py3-none-any.whl + name: pytest + version: 9.0.1 + sha256: 67be0030d194df2dfa7b556f2e56fb3c3315bd5c8822c6951162b92b32ce7dad + requires_dist: + - colorama>=0.4 ; sys_platform == 'win32' + - exceptiongroup>=1 ; python_full_version < '3.11' + - iniconfig>=1.0.1 + - packaging>=22 + - pluggy>=1.5,<2 + - pygments>=2.7.2 + - tomli>=1 ; python_full_version < '3.11' + - argcomplete ; extra == 'dev' + - attrs>=19.2 ; extra == 'dev' + - hypothesis>=3.56 ; extra == 'dev' + - mock ; extra == 'dev' + - requests ; extra == 'dev' + - setuptools ; extra == 'dev' + - xmlschema ; extra == 'dev' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + name: pytest-cov + version: 7.0.0 + sha256: 3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861 + requires_dist: + - coverage[toml]>=7.10.6 + - pluggy>=1.2 + - pytest>=7 + - process-tests ; extra == 'testing' + - pytest-xdist ; extra == 'testing' + - virtualenv ; extra == 'testing' + requires_python: '>=3.9' +- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.9-hc97d973_101_cp313.conda + build_number: 101 + sha256: e89da062abd0d3e76c8d3b35d3cafc5f0d05914339dcb238f9e3675f2a58d883 + md5: 4780fe896e961722d0623fa91d0d3378 + depends: + - __glibc >=2.17,<3.0.a0 + - bzip2 >=1.0.8,<2.0a0 + - ld_impl_linux-64 >=2.36.1 + - libexpat >=2.7.1,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - libgcc >=14 + - liblzma >=5.8.1,<6.0a0 + - libmpdec >=4.0.0,<5.0a0 + - libsqlite >=3.50.4,<4.0a0 + - libuuid >=2.41.2,<3.0a0 + - libzlib >=1.3.1,<2.0a0 + - ncurses >=6.5,<7.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 + license: Python-2.0 + purls: [] + size: 37174029 + timestamp: 1761178179147 + python_site_packages_path: lib/python3.13/site-packages +- conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.13.9-h17c18a5_101_cp313.conda + build_number: 101 + sha256: b56484229cf83f6c84e8b138dc53f7f2fa9ee850f42bf1f6d6fa1c03c044c2d3 + md5: fb1e51574ce30d2a4d5e4facb9b2cbd5 + depends: + - __osx >=10.13 + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.7.1,<3.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.4,<4.0a0 + - python_abi 3.13.* *_cp313 + - readline >=8.2,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + license: Python-2.0 + purls: [] + size: 17521522 + timestamp: 1761177097697 + python_site_packages_path: lib/python3.13/site-packages +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.9-hfc2f54d_101_cp313.conda + build_number: 101 + sha256: 516229f780b98783a5ef4112a5a4b5e5647d4f0177c4621e98aa60bb9bc32f98 + md5: a4241bce59eecc74d4d2396e108c93b8 + depends: + - __osx >=11.0 + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.7.1,<3.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.4,<4.0a0 + - python_abi 3.13.* *_cp313 + - readline >=8.2,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + license: Python-2.0 + purls: [] + size: 11915380 + timestamp: 1761176793936 + python_site_packages_path: lib/python3.13/site-packages +- conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.9-h09917c8_101_cp313.conda + build_number: 101 + sha256: bc855b513197637c2083988d5cbdcc407a23151cdecff381bd677df33d516a01 + md5: 89d992b9d4b9e88ed54346c9c4a24c1c + depends: + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.7.1,<3.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 + - openssl >=3.5.4,<4.0a0 + - python_abi 3.13.* *_cp313 + - tk >=8.6.13,<8.7.0a0 + - tzdata + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Python-2.0 + purls: [] + size: 16613183 + timestamp: 1761175050438 + python_site_packages_path: Lib/site-packages +- pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl + name: python-dateutil + version: 2.9.0.post0 + sha256: a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 + requires_dist: + - six>=1.5 + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*' +- pypi: https://files.pythonhosted.org/packages/d8/f0/c5aa0a69fd9326f013110653543f36ece4913c17921f3e1dbd78e1b423ee/python_engineio-4.12.3-py3-none-any.whl + name: python-engineio + version: 4.12.3 + sha256: 7c099abb2a27ea7ab429c04da86ab2d82698cdd6c52406cb73766fe454feb7e1 + requires_dist: + - simple-websocket>=0.10.0 + - requests>=2.21.0 ; extra == 'client' + - websocket-client>=0.54.0 ; extra == 'client' + - aiohttp>=3.4 ; extra == 'asyncio-client' + - sphinx ; extra == 'docs' + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl + name: python-json-logger + version: 4.0.0 + sha256: af09c9daf6a813aa4cc7180395f50f2a9e5fa056034c9953aec92e381c5ba1e2 + requires_dist: + - typing-extensions ; python_full_version < '3.10' + - orjson ; implementation_name != 'pypy' and extra == 'dev' + - msgspec ; implementation_name != 'pypy' and extra == 'dev' + - validate-pyproject[all] ; extra == 'dev' + - black ; extra == 'dev' + - pylint ; extra == 'dev' + - mypy ; extra == 'dev' + - pytest ; extra == 'dev' + - freezegun ; extra == 'dev' + - backports-zoneinfo ; python_full_version < '3.9' and extra == 'dev' + - tzdata ; extra == 'dev' + - build ; extra == 'dev' + - mkdocs ; extra == 'dev' + - mkdocs-material>=8.5 ; extra == 'dev' + - mkdocs-awesome-pages-plugin ; extra == 'dev' + - mdx-truly-sane-lists ; extra == 'dev' + - mkdocstrings[python] ; extra == 'dev' + - mkdocs-gen-files ; extra == 'dev' + - mkdocs-literate-nav ; extra == 'dev' + - mike ; extra == 'dev' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/cd/fa/1ef2f8537272a2f383d72b9301c3ef66a49710b3bb7dcb2bd138cf2920d1/python_socketio-5.15.0-py3-none-any.whl + name: python-socketio + version: 5.15.0 + sha256: e93363102f4da6d8e7a8872bf4908b866c40f070e716aa27132891e643e2687c + requires_dist: + - bidict>=0.21.0 + - python-engineio>=4.11.0 + - requests>=2.21.0 ; extra == 'client' + - websocket-client>=0.54.0 ; extra == 'client' + - aiohttp>=3.4 ; extra == 'asyncio-client' + - tox ; extra == 'dev' + - sphinx ; extra == 'docs' + requires_python: '>=3.8' +- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda + build_number: 8 + sha256: 210bffe7b121e651419cb196a2a63687b087497595c9be9d20ebe97dd06060a7 + md5: 94305520c52a4aa3f6c2b1ff6008d9f8 + constrains: + - python 3.13.* *_cp313 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 7002 + timestamp: 1752805902938 +- pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl + name: pytz + version: '2025.2' + sha256: 5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00 +- pypi: https://files.pythonhosted.org/packages/fc/19/b757fe28008236a4a713e813283721b8a40aa60cd7d3f83549f2e25a3155/pywinpty-3.0.2-cp313-cp313-win_amd64.whl + name: pywinpty + version: 3.0.2 + sha256: 18f78b81e4cfee6aabe7ea8688441d30247b73e52cd9657138015c5f4ee13a51 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: pyyaml + version: 6.0.3 + sha256: 0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl + name: pyyaml + version: 6.0.3 + sha256: 79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl + name: pyyaml + version: 6.0.3 + sha256: 2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl + name: pyyaml + version: 6.0.3 + sha256: 8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl + name: pyzmq + version: 27.1.0 + sha256: 452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc + requires_dist: + - cffi ; implementation_name == 'pypy' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl + name: pyzmq + version: 27.1.0 + sha256: 43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31 + requires_dist: + - cffi ; implementation_name == 'pypy' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl + name: pyzmq + version: 27.1.0 + sha256: 9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf + requires_dist: + - cffi ; implementation_name == 'pypy' + requires_python: '>=3.8' +- conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda + sha256: 2d6d0c026902561ed77cd646b5021aef2d4db22e57a5b0178dfc669231e06d2c + md5: 283b96675859b20a825f8fa30f311446 + depends: + - libgcc >=13 + - ncurses >=6.5,<7.0a0 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 282480 + timestamp: 1740379431762 +- conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.2-h7cca4af_2.conda + sha256: 53017e80453c4c1d97aaf78369040418dea14cf8f46a2fa999f31bd70b36c877 + md5: 342570f8e02f2f022147a7f841475784 + depends: + - ncurses >=6.5,<7.0a0 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 256712 + timestamp: 1740379577668 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.2-h1d1bf99_2.conda + sha256: 7db04684d3904f6151eff8673270922d31da1eea7fa73254d01c437f49702e34 + md5: 63ef3f6e6d6d5c589e64f11263dc5676 + depends: + - ncurses >=6.5,<7.0a0 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 252359 + timestamp: 1740379663071 +- pypi: https://files.pythonhosted.org/packages/e1/67/921ec3024056483db83953ae8e48079ad62b92db7880013ca77632921dd0/readme_renderer-44.0-py3-none-any.whl + name: readme-renderer + version: '44.0' + sha256: 2fbca89b81a08526aadf1357a8c2ae889ec05fb03f5da67f9769c9a592166151 + requires_dist: + - nh3>=0.2.14 + - docutils>=0.21.2 + - pygments>=2.5.1 + - cmarkgfm>=0.8.0 ; extra == 'md' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl + name: referencing + version: 0.37.0 + sha256: 381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231 + requires_dist: + - attrs>=22.2.0 + - rpds-py>=0.7.0 + - typing-extensions>=4.4.0 ; python_full_version < '3.13' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + name: requests + version: 2.32.5 + sha256: 2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6 + requires_dist: + - charset-normalizer>=2,<4 + - idna>=2.5,<4 + - urllib3>=1.21.1,<3 + - certifi>=2017.4.17 + - pysocks>=1.5.6,!=1.5.7 ; extra == 'socks' + - chardet>=3.0.2,<6 ; extra == 'use-chardet-on-py3' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/af/63/ac52b32b33ae62f2076ed5c4f6b00e065e3ccbb2063e9a2e813b2bfc95bf/restructuredtext_lint-2.0.2-py3-none-any.whl + name: restructuredtext-lint + version: 2.0.2 + sha256: 374c0d3e7e0867b2335146a145343ac619400623716b211b9a010c94426bbed7 + requires_dist: + - docutils>=0.11,<1.0 +- pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl + name: rfc3339-validator + version: 0.1.4 + sha256: 24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa + requires_dist: + - six + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*' +- pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl + name: rfc3986-validator + version: 0.1.1 + sha256: 2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9 + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*' +- pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl + name: rfc3987-syntax + version: 1.1.0 + sha256: 6c3d97604e4c5ce9f714898e05401a0445a641cfa276432b0a648c80856f6a3f + requires_dist: + - lark>=1.2.2 + - pytest>=8.3.5 ; extra == 'testing' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/53/97/d2cbbaa10c9b826af0e10fdf836e1bf344d9f0abb873ebc34d1f49642d3f/roman_numerals_py-3.1.0-py3-none-any.whl + name: roman-numerals-py + version: 3.1.0 + sha256: 9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c + requires_dist: + - mypy==1.15.0 ; extra == 'lint' + - ruff==0.9.7 ; extra == 'lint' + - pyright==1.1.394 ; extra == 'lint' + - pytest>=8 ; extra == 'test' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/3c/6b/0229d3bed4ddaa409e6d90b0ae967ed4380e4bdd0dad6e59b92c17d42457/rpds_py-0.29.0-cp313-cp313-win_amd64.whl + name: rpds-py + version: 0.29.0 + sha256: 6410e66f02803600edb0b1889541f4b5cc298a5ccda0ad789cc50ef23b54813e + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/b3/b3/0860cdd012291dc21272895ce107f1e98e335509ba986dd83d72658b82b9/rpds_py-0.29.0-cp313-cp313-macosx_11_0_arm64.whl + name: rpds-py + version: 0.29.0 + sha256: 521807963971a23996ddaf764c682b3e46459b3c58ccd79fefbe16718db43154 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/b9/42/555b4ee17508beafac135c8b450816ace5a96194ce97fefc49d58e5652ea/rpds_py-0.29.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + name: rpds-py + version: 0.29.0 + sha256: de73e40ebc04dd5d9556f50180395322193a78ec247e637e741c1b954810f295 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/fd/d9/c5de60d9d371bbb186c3e9bf75f4fc5665e11117a25a06a6b2e0afb7380e/rpds_py-0.29.0-cp313-cp313-macosx_10_12_x86_64.whl + name: rpds-py + version: 0.29.0 + sha256: 1585648d0760b88292eecab5181f5651111a69d90eff35d6b78aa32998886a61 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/13/ac/9b9fe63716af8bdfddfacd0882bc1586f29985d3b988b3c62ddce2e202c3/ruff-0.14.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + name: ruff + version: 0.14.6 + sha256: 167843a6f78680746d7e226f255d920aeed5e4ad9c03258094a2d49d3028b105 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/36/6a/ad66d0a3315d6327ed6b01f759d83df3c4d5f86c30462121024361137b6a/ruff-0.14.6-py3-none-macosx_10_12_x86_64.whl + name: ruff + version: 0.14.6 + sha256: 9f7539ea257aa4d07b7ce87aed580e485c40143f2473ff2f2b75aee003186004 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/a3/9d/dae6db96df28e0a15dea8e986ee393af70fc97fd57669808728080529c37/ruff-0.14.6-py3-none-macosx_11_0_arm64.whl + name: ruff + version: 0.14.6 + sha256: 7f6007e55b90a2a7e93083ba48a9f23c3158c433591c33ee2e99a49b889c6332 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/fb/02/82240553b77fd1341f80ebb3eaae43ba011c7a91b4224a9f317d8e6591af/ruff-0.14.6-py3-none-win_amd64.whl + name: ruff + version: 0.14.6 + sha256: 390e6480c5e3659f8a4c8d6a0373027820419ac14fa0d2713bd8e6c3e125b8b9 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/64/7c/d8a343b0a622987335a1ac848084079c47c21e44fcc450f9c145b11a56f6/scipp-25.11.0-cp313-cp313-macosx_11_0_arm64.whl + name: scipp + version: 25.11.0 + sha256: 9b082fc7fd29919b4fee007f63db265176becb06c669a4b709feb741b76c612a + requires_dist: + - numpy>=1.20 + - pytest ; extra == 'test' + - matplotlib ; extra == 'test' + - beautifulsoup4 ; extra == 'test' + - ipython ; extra == 'test' + - h5py ; extra == 'extra' + - scipy>=1.7.0 ; extra == 'extra' + - graphviz ; extra == 'extra' + - pooch ; extra == 'extra' + - plopp ; extra == 'extra' + - matplotlib ; extra == 'extra' + - scipp[extra] ; extra == 'all' + - ipympl ; extra == 'all' + - ipython ; extra == 'all' + - ipywidgets ; extra == 'all' + - jupyterlab ; extra == 'all' + - jupyterlab-widgets ; extra == 'all' + - jupyter-nbextensions-configurator ; extra == 'all' + - nodejs ; extra == 'all' + - pythreejs ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/71/f2/18d5be10ac890ce7490451eb55d41bf9d96e481751362a30ed1a8bc176fa/scipp-25.11.0-cp313-cp313-macosx_11_0_x86_64.whl + name: scipp + version: 25.11.0 + sha256: fd7788e3ea3dd5334ed7fb53a249413469edc5d836ca5d68b1f271691cf11269 + requires_dist: + - numpy>=1.20 + - pytest ; extra == 'test' + - matplotlib ; extra == 'test' + - beautifulsoup4 ; extra == 'test' + - ipython ; extra == 'test' + - h5py ; extra == 'extra' + - scipy>=1.7.0 ; extra == 'extra' + - graphviz ; extra == 'extra' + - pooch ; extra == 'extra' + - plopp ; extra == 'extra' + - matplotlib ; extra == 'extra' + - scipp[extra] ; extra == 'all' + - ipympl ; extra == 'all' + - ipython ; extra == 'all' + - ipywidgets ; extra == 'all' + - jupyterlab ; extra == 'all' + - jupyterlab-widgets ; extra == 'all' + - jupyter-nbextensions-configurator ; extra == 'all' + - nodejs ; extra == 'all' + - pythreejs ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/77/34/1956aed61c4abb91926f9513330afac4654cc2a711ecb73085dc4e2e5c1d/scipp-25.11.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: scipp + version: 25.11.0 + sha256: 3e91c8093dc83d433a2e53708561228a06a7e34d8775a20118d391f5b891a6e6 + requires_dist: + - numpy>=1.20 + - pytest ; extra == 'test' + - matplotlib ; extra == 'test' + - beautifulsoup4 ; extra == 'test' + - ipython ; extra == 'test' + - h5py ; extra == 'extra' + - scipy>=1.7.0 ; extra == 'extra' + - graphviz ; extra == 'extra' + - pooch ; extra == 'extra' + - plopp ; extra == 'extra' + - matplotlib ; extra == 'extra' + - scipp[extra] ; extra == 'all' + - ipympl ; extra == 'all' + - ipython ; extra == 'all' + - ipywidgets ; extra == 'all' + - jupyterlab ; extra == 'all' + - jupyterlab-widgets ; extra == 'all' + - jupyter-nbextensions-configurator ; extra == 'all' + - nodejs ; extra == 'all' + - pythreejs ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/f6/f3/9d1bb423a2dc0bbebfc98191095dd410a1268397b9c692d76a3ea971c790/scipp-25.11.0-cp313-cp313-win_amd64.whl + name: scipp + version: 25.11.0 + sha256: d44d2aa3f6634ac750a126e50a740c487aa8a2181fcb03764b29ddceeea611cf + requires_dist: + - numpy>=1.20 + - pytest ; extra == 'test' + - matplotlib ; extra == 'test' + - beautifulsoup4 ; extra == 'test' + - ipython ; extra == 'test' + - h5py ; extra == 'extra' + - scipy>=1.7.0 ; extra == 'extra' + - graphviz ; extra == 'extra' + - pooch ; extra == 'extra' + - plopp ; extra == 'extra' + - matplotlib ; extra == 'extra' + - scipp[extra] ; extra == 'all' + - ipympl ; extra == 'all' + - ipython ; extra == 'all' + - ipywidgets ; extra == 'all' + - jupyterlab ; extra == 'all' + - jupyterlab-widgets ; extra == 'all' + - jupyter-nbextensions-configurator ; extra == 'all' + - nodejs ; extra == 'all' + - pythreejs ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/21/f6/4bfb5695d8941e5c570a04d9fcd0d36bce7511b7d78e6e75c8f9791f82d0/scipy-1.16.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + name: scipy + version: 1.16.3 + sha256: 7dc1360c06535ea6116a2220f760ae572db9f661aba2d88074fe30ec2aa1ff88 + requires_dist: + - numpy>=1.25.2,<2.6 + - pytest>=8.0.0 ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest-xdist ; extra == 'test' + - asv ; extra == 'test' + - mpmath ; extra == 'test' + - gmpy2 ; extra == 'test' + - threadpoolctl ; extra == 'test' + - scikit-umfpack ; extra == 'test' + - pooch ; extra == 'test' + - hypothesis>=6.30 ; extra == 'test' + - array-api-strict>=2.3.1 ; extra == 'test' + - cython ; extra == 'test' + - meson ; extra == 'test' + - ninja ; sys_platform != 'emscripten' and extra == 'test' + - sphinx>=5.0.0,<8.2.0 ; extra == 'doc' + - intersphinx-registry ; extra == 'doc' + - pydata-sphinx-theme>=0.15.2 ; extra == 'doc' + - sphinx-copybutton ; extra == 'doc' + - sphinx-design>=0.4.0 ; extra == 'doc' + - matplotlib>=3.5 ; extra == 'doc' + - numpydoc ; extra == 'doc' + - jupytext ; extra == 'doc' + - myst-nb>=1.2.0 ; extra == 'doc' + - pooch ; extra == 'doc' + - jupyterlite-sphinx>=0.19.1 ; extra == 'doc' + - jupyterlite-pyodide-kernel ; extra == 'doc' + - linkify-it-py ; extra == 'doc' + - mypy==1.10.0 ; extra == 'dev' + - typing-extensions ; extra == 'dev' + - types-psutil ; extra == 'dev' + - pycodestyle ; extra == 'dev' + - ruff>=0.0.292 ; extra == 'dev' + - cython-lint>=0.12.2 ; extra == 'dev' + - rich-click ; extra == 'dev' + - doit>=0.36.0 ; extra == 'dev' + - pydevtool ; extra == 'dev' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/44/13/7e63cfba8a7452eb756306aa2fd9b37a29a323b672b964b4fdeded9a3f21/scipy-1.16.3-cp313-cp313-macosx_12_0_arm64.whl + name: scipy + version: 1.16.3 + sha256: 16b8bc35a4cc24db80a0ec836a9286d0e31b2503cb2fd7ff7fb0e0374a97081d + requires_dist: + - numpy>=1.25.2,<2.6 + - pytest>=8.0.0 ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest-xdist ; extra == 'test' + - asv ; extra == 'test' + - mpmath ; extra == 'test' + - gmpy2 ; extra == 'test' + - threadpoolctl ; extra == 'test' + - scikit-umfpack ; extra == 'test' + - pooch ; extra == 'test' + - hypothesis>=6.30 ; extra == 'test' + - array-api-strict>=2.3.1 ; extra == 'test' + - cython ; extra == 'test' + - meson ; extra == 'test' + - ninja ; sys_platform != 'emscripten' and extra == 'test' + - sphinx>=5.0.0,<8.2.0 ; extra == 'doc' + - intersphinx-registry ; extra == 'doc' + - pydata-sphinx-theme>=0.15.2 ; extra == 'doc' + - sphinx-copybutton ; extra == 'doc' + - sphinx-design>=0.4.0 ; extra == 'doc' + - matplotlib>=3.5 ; extra == 'doc' + - numpydoc ; extra == 'doc' + - jupytext ; extra == 'doc' + - myst-nb>=1.2.0 ; extra == 'doc' + - pooch ; extra == 'doc' + - jupyterlite-sphinx>=0.19.1 ; extra == 'doc' + - jupyterlite-pyodide-kernel ; extra == 'doc' + - linkify-it-py ; extra == 'doc' + - mypy==1.10.0 ; extra == 'dev' + - typing-extensions ; extra == 'dev' + - types-psutil ; extra == 'dev' + - pycodestyle ; extra == 'dev' + - ruff>=0.0.292 ; extra == 'dev' + - cython-lint>=0.12.2 ; extra == 'dev' + - rich-click ; extra == 'dev' + - doit>=0.36.0 ; extra == 'dev' + - pydevtool ; extra == 'dev' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/72/f1/57e8327ab1508272029e27eeef34f2302ffc156b69e7e233e906c2a5c379/scipy-1.16.3-cp313-cp313-macosx_10_14_x86_64.whl + name: scipy + version: 1.16.3 + sha256: d2ec56337675e61b312179a1ad124f5f570c00f920cc75e1000025451b88241c + requires_dist: + - numpy>=1.25.2,<2.6 + - pytest>=8.0.0 ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest-xdist ; extra == 'test' + - asv ; extra == 'test' + - mpmath ; extra == 'test' + - gmpy2 ; extra == 'test' + - threadpoolctl ; extra == 'test' + - scikit-umfpack ; extra == 'test' + - pooch ; extra == 'test' + - hypothesis>=6.30 ; extra == 'test' + - array-api-strict>=2.3.1 ; extra == 'test' + - cython ; extra == 'test' + - meson ; extra == 'test' + - ninja ; sys_platform != 'emscripten' and extra == 'test' + - sphinx>=5.0.0,<8.2.0 ; extra == 'doc' + - intersphinx-registry ; extra == 'doc' + - pydata-sphinx-theme>=0.15.2 ; extra == 'doc' + - sphinx-copybutton ; extra == 'doc' + - sphinx-design>=0.4.0 ; extra == 'doc' + - matplotlib>=3.5 ; extra == 'doc' + - numpydoc ; extra == 'doc' + - jupytext ; extra == 'doc' + - myst-nb>=1.2.0 ; extra == 'doc' + - pooch ; extra == 'doc' + - jupyterlite-sphinx>=0.19.1 ; extra == 'doc' + - jupyterlite-pyodide-kernel ; extra == 'doc' + - linkify-it-py ; extra == 'doc' + - mypy==1.10.0 ; extra == 'dev' + - typing-extensions ; extra == 'dev' + - types-psutil ; extra == 'dev' + - pycodestyle ; extra == 'dev' + - ruff>=0.0.292 ; extra == 'dev' + - cython-lint>=0.12.2 ; extra == 'dev' + - rich-click ; extra == 'dev' + - doit>=0.36.0 ; extra == 'dev' + - pydevtool ; extra == 'dev' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/cd/01/1204382461fcbfeb05b6161b594f4007e78b6eba9b375382f79153172b4d/scipy-1.16.3-cp313-cp313-win_amd64.whl + name: scipy + version: 1.16.3 + sha256: 062246acacbe9f8210de8e751b16fc37458213f124bef161a5a02c7a39284304 + requires_dist: + - numpy>=1.25.2,<2.6 + - pytest>=8.0.0 ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest-xdist ; extra == 'test' + - asv ; extra == 'test' + - mpmath ; extra == 'test' + - gmpy2 ; extra == 'test' + - threadpoolctl ; extra == 'test' + - scikit-umfpack ; extra == 'test' + - pooch ; extra == 'test' + - hypothesis>=6.30 ; extra == 'test' + - array-api-strict>=2.3.1 ; extra == 'test' + - cython ; extra == 'test' + - meson ; extra == 'test' + - ninja ; sys_platform != 'emscripten' and extra == 'test' + - sphinx>=5.0.0,<8.2.0 ; extra == 'doc' + - intersphinx-registry ; extra == 'doc' + - pydata-sphinx-theme>=0.15.2 ; extra == 'doc' + - sphinx-copybutton ; extra == 'doc' + - sphinx-design>=0.4.0 ; extra == 'doc' + - matplotlib>=3.5 ; extra == 'doc' + - numpydoc ; extra == 'doc' + - jupytext ; extra == 'doc' + - myst-nb>=1.2.0 ; extra == 'doc' + - pooch ; extra == 'doc' + - jupyterlite-sphinx>=0.19.1 ; extra == 'doc' + - jupyterlite-pyodide-kernel ; extra == 'doc' + - linkify-it-py ; extra == 'doc' + - mypy==1.10.0 ; extra == 'dev' + - typing-extensions ; extra == 'dev' + - types-psutil ; extra == 'dev' + - pycodestyle ; extra == 'dev' + - ruff>=0.0.292 ; extra == 'dev' + - cython-lint>=0.12.2 ; extra == 'dev' + - rich-click ; extra == 'dev' + - doit>=0.36.0 ; extra == 'dev' + - pydevtool ; extra == 'dev' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl + name: send2trash + version: 1.8.3 + sha256: 0c31227e0bd08961c7665474a3d1ef7193929fedda4233843689baa056be46c9 + requires_dist: + - pyobjc-framework-cocoa ; sys_platform == 'darwin' and extra == 'nativelib' + - pywin32 ; sys_platform == 'win32' and extra == 'nativelib' + - pyobjc-framework-cocoa ; sys_platform == 'darwin' and extra == 'objc' + - pywin32 ; sys_platform == 'win32' and extra == 'win32' + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*' +- pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl + name: setuptools + version: 80.9.0 + sha256: 062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922 + requires_dist: + - pytest>=6,!=8.1.* ; extra == 'test' + - virtualenv>=13.0.0 ; extra == 'test' + - wheel>=0.44.0 ; extra == 'test' + - pip>=19.1 ; extra == 'test' + - packaging>=24.2 ; extra == 'test' + - jaraco-envs>=2.2 ; extra == 'test' + - pytest-xdist>=3 ; extra == 'test' + - jaraco-path>=3.7.2 ; extra == 'test' + - build[virtualenv]>=1.0.3 ; extra == 'test' + - filelock>=3.4.0 ; extra == 'test' + - ini2toml[lite]>=0.14 ; extra == 'test' + - tomli-w>=1.0.0 ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest-perf ; sys_platform != 'cygwin' and extra == 'test' + - jaraco-develop>=7.21 ; python_full_version >= '3.9' and sys_platform != 'cygwin' and extra == 'test' + - pytest-home>=0.5 ; extra == 'test' + - pytest-subprocess ; extra == 'test' + - pyproject-hooks!=1.1 ; extra == 'test' + - jaraco-test>=5.5 ; extra == 'test' + - sphinx>=3.5 ; extra == 'doc' + - jaraco-packaging>=9.3 ; extra == 'doc' + - rst-linker>=1.9 ; extra == 'doc' + - furo ; extra == 'doc' + - sphinx-lint ; extra == 'doc' + - jaraco-tidelift>=1.4 ; extra == 'doc' + - pygments-github-lexers==0.0.5 ; extra == 'doc' + - sphinx-favicon ; extra == 'doc' + - sphinx-inline-tabs ; extra == 'doc' + - sphinx-reredirects ; extra == 'doc' + - sphinxcontrib-towncrier ; extra == 'doc' + - sphinx-notfound-page>=1,<2 ; extra == 'doc' + - pyproject-hooks!=1.1 ; extra == 'doc' + - towncrier<24.7 ; extra == 'doc' + - packaging>=24.2 ; extra == 'core' + - more-itertools>=8.8 ; extra == 'core' + - jaraco-text>=3.7 ; extra == 'core' + - importlib-metadata>=6 ; python_full_version < '3.10' and extra == 'core' + - tomli>=2.0.1 ; python_full_version < '3.11' and extra == 'core' + - wheel>=0.43.0 ; extra == 'core' + - platformdirs>=4.2.2 ; extra == 'core' + - jaraco-functools>=4 ; extra == 'core' + - more-itertools ; extra == 'core' + - pytest-checkdocs>=2.4 ; extra == 'check' + - pytest-ruff>=0.2.1 ; sys_platform != 'cygwin' and extra == 'check' + - ruff>=0.8.0 ; sys_platform != 'cygwin' and extra == 'check' + - pytest-cov ; extra == 'cover' + - pytest-enabler>=2.2 ; extra == 'enabler' + - pytest-mypy ; extra == 'type' + - mypy==1.14.* ; extra == 'type' + - importlib-metadata>=7.0.2 ; python_full_version < '3.10' and extra == 'type' + - jaraco-develop>=7.21 ; sys_platform != 'cygwin' and extra == 'type' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/c0/ba/daf16c2d1965bf6237fb696639e3e93645ac6801f7dcaf9ec694a74e9326/setuptools_git_versioning-2.1.0-py3-none-any.whl + name: setuptools-git-versioning + version: 2.1.0 + sha256: 09a15cbb9a00884e91a3591a4c9ec1ff93c24b1b4a40de39a44815196beb7ebf + requires_dist: + - packaging + - setuptools + - tomli>=2.0.1 ; python_full_version < '3.11' + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl + name: simple-websocket + version: 1.1.0 + sha256: 4af6069630a38ed6c561010f0e11a5bc0d4ca569b36306eb257cd9a192497c8c + requires_dist: + - wsproto + - tox ; extra == 'dev' + - flake8 ; extra == 'dev' + - pytest ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - sphinx ; extra == 'docs' + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + name: six + version: 1.17.0 + sha256: 4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*' +- pypi: https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl + name: sniffio + version: 1.3.1 + sha256: 2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl + name: snowballstemmer + version: 3.0.1 + sha256: 6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064 + requires_python: '!=3.0.*,!=3.1.*,!=3.2.*' +- pypi: https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl + name: soupsieve + version: '2.8' + sha256: 0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/31/53/136e9eca6e0b9dc0e1962e2c908fbea2e5ac000c2a2fbd9a35797958c48b/sphinx-8.2.3-py3-none-any.whl + name: sphinx + version: 8.2.3 + sha256: 4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3 + requires_dist: + - sphinxcontrib-applehelp>=1.0.7 + - sphinxcontrib-devhelp>=1.0.6 + - sphinxcontrib-htmlhelp>=2.0.6 + - sphinxcontrib-jsmath>=1.0.1 + - sphinxcontrib-qthelp>=1.0.6 + - sphinxcontrib-serializinghtml>=1.1.9 + - jinja2>=3.1 + - pygments>=2.17 + - docutils>=0.20,<0.22 + - snowballstemmer>=2.2 + - babel>=2.13 + - alabaster>=0.7.14 + - imagesize>=1.3 + - requests>=2.30.0 + - roman-numerals-py>=1.0.0 + - packaging>=23.0 + - colorama>=0.4.6 ; sys_platform == 'win32' + - sphinxcontrib-websupport ; extra == 'docs' + - ruff==0.9.9 ; extra == 'lint' + - mypy==1.15.0 ; extra == 'lint' + - sphinx-lint>=0.9 ; extra == 'lint' + - types-colorama==0.4.15.20240311 ; extra == 'lint' + - types-defusedxml==0.7.0.20240218 ; extra == 'lint' + - types-docutils==0.21.0.20241128 ; extra == 'lint' + - types-pillow==10.2.0.20240822 ; extra == 'lint' + - types-pygments==2.19.0.20250219 ; extra == 'lint' + - types-requests==2.32.0.20241016 ; extra == 'lint' + - types-urllib3==1.26.25.14 ; extra == 'lint' + - pyright==1.1.395 ; extra == 'lint' + - pytest>=8.0 ; extra == 'lint' + - pypi-attestations==0.0.21 ; extra == 'lint' + - betterproto==2.0.0b6 ; extra == 'lint' + - pytest>=8.0 ; extra == 'test' + - pytest-xdist[psutil]>=3.4 ; extra == 'test' + - defusedxml>=0.7.1 ; extra == 'test' + - cython>=3.0 ; extra == 'test' + - setuptools>=70.0 ; extra == 'test' + - typing-extensions>=4.9 ; extra == 'test' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/05/f2/9657c98a66973b7c35bfd48ba65d1922860de9598fbb535cd96e3f58a908/sphinx_autodoc_typehints-3.5.2-py3-none-any.whl + name: sphinx-autodoc-typehints + version: 3.5.2 + sha256: 0accd043619f53c86705958e323b419e41667917045ac9215d7be1b493648d8c + requires_dist: + - sphinx>=8.2.3 + - furo>=2025.9.25 ; extra == 'docs' + - covdefaults>=2.3 ; extra == 'testing' + - coverage>=7.10.7 ; extra == 'testing' + - defusedxml>=0.7.1 ; extra == 'testing' + - diff-cover>=9.7.1 ; extra == 'testing' + - pytest-cov>=7 ; extra == 'testing' + - pytest>=8.4.2 ; extra == 'testing' + - sphobjinv>=2.3.1.3 ; extra == 'testing' + - typing-extensions>=4.15 ; extra == 'testing' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/51/9e/c41d68be04eef5b6202b468e0f90faf0c469f3a03353f2a218fd78279710/sphinx_book_theme-1.1.4-py3-none-any.whl + name: sphinx-book-theme + version: 1.1.4 + sha256: 843b3f5c8684640f4a2d01abd298beb66452d1b2394cd9ef5be5ebd5640ea0e1 + requires_dist: + - sphinx>=6.1 + - pydata-sphinx-theme==0.15.4 + - pre-commit ; extra == 'code-style' + - ablog ; extra == 'doc' + - ipywidgets ; extra == 'doc' + - folium ; extra == 'doc' + - numpy ; extra == 'doc' + - matplotlib ; extra == 'doc' + - numpydoc ; extra == 'doc' + - myst-nb ; extra == 'doc' + - nbclient ; extra == 'doc' + - pandas ; extra == 'doc' + - plotly ; extra == 'doc' + - sphinx-design ; extra == 'doc' + - sphinx-examples ; extra == 'doc' + - sphinx-copybutton ; extra == 'doc' + - sphinx-tabs ; extra == 'doc' + - sphinx-togglebutton ; extra == 'doc' + - sphinx-thebe ; extra == 'doc' + - sphinxcontrib-bibtex ; extra == 'doc' + - sphinxcontrib-youtube ; extra == 'doc' + - sphinxext-opengraph ; extra == 'doc' + - beautifulsoup4 ; extra == 'test' + - coverage ; extra == 'test' + - defusedxml ; extra == 'test' + - myst-nb ; extra == 'test' + - pytest ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-regressions ; extra == 'test' + - sphinx-thebe ; extra == 'test' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/77/c7/52b48aec16b26c52aba854d03a3a31e0681150301dac1bea2243645a69e7/sphinx_gallery-0.19.0-py3-none-any.whl + name: sphinx-gallery + version: 0.19.0 + sha256: 4c28751973f81769d5bbbf5e4ebaa0dc49dff8c48eb7f11131eb5f6e4aa25f0e + requires_dist: + - pillow + - sphinx>=5 + - sphinxcontrib-video ; extra == 'animations' + - absl-py ; extra == 'dev' + - graphviz ; extra == 'dev' + - intersphinx-registry ; extra == 'dev' + - ipython ; extra == 'dev' + - joblib ; extra == 'dev' + - jupyterlite-sphinx>=0.17.1 ; extra == 'dev' + - lxml ; extra == 'dev' + - matplotlib ; extra == 'dev' + - numpy ; extra == 'dev' + - packaging ; extra == 'dev' + - plotly ; extra == 'dev' + - pydata-sphinx-theme ; extra == 'dev' + - pytest ; extra == 'dev' + - pytest-coverage ; extra == 'dev' + - seaborn ; extra == 'dev' + - sphinxcontrib-video ; extra == 'dev' + - statsmodels ; extra == 'dev' + - jupyterlite-sphinx ; extra == 'jupyterlite' + - joblib ; extra == 'parallel' + - numpy ; extra == 'recommender' + - graphviz ; extra == 'show-api-usage' + - memory-profiler ; extra == 'show-memory' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl + name: sphinxcontrib-applehelp + version: 2.0.0 + sha256: 4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5 + requires_dist: + - ruff==0.5.5 ; extra == 'lint' + - mypy ; extra == 'lint' + - types-docutils ; extra == 'lint' + - sphinx>=5 ; extra == 'standalone' + - pytest ; extra == 'test' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl + name: sphinxcontrib-devhelp + version: 2.0.0 + sha256: aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2 + requires_dist: + - ruff==0.5.5 ; extra == 'lint' + - mypy ; extra == 'lint' + - types-docutils ; extra == 'lint' + - sphinx>=5 ; extra == 'standalone' + - pytest ; extra == 'test' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl + name: sphinxcontrib-htmlhelp + version: 2.1.0 + sha256: 166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8 + requires_dist: + - ruff==0.5.5 ; extra == 'lint' + - mypy ; extra == 'lint' + - types-docutils ; extra == 'lint' + - sphinx>=5 ; extra == 'standalone' + - pytest ; extra == 'test' + - html5lib ; extra == 'test' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl + name: sphinxcontrib-jsmath + version: 1.0.1 + sha256: 2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178 + requires_dist: + - pytest ; extra == 'test' + - flake8 ; extra == 'test' + - mypy ; extra == 'test' + requires_python: '>=3.5' +- pypi: https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl + name: sphinxcontrib-qthelp + version: 2.0.0 + sha256: b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb + requires_dist: + - ruff==0.5.5 ; extra == 'lint' + - mypy ; extra == 'lint' + - types-docutils ; extra == 'lint' + - sphinx>=5 ; extra == 'standalone' + - pytest ; extra == 'test' + - defusedxml>=0.7.1 ; extra == 'test' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl + name: sphinxcontrib-serializinghtml + version: 2.0.0 + sha256: 6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331 + requires_dist: + - ruff==0.5.5 ; extra == 'lint' + - mypy ; extra == 'lint' + - types-docutils ; extra == 'lint' + - sphinx>=5 ; extra == 'standalone' + - pytest ; extra == 'test' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl + name: stack-data + version: 0.6.3 + sha256: d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695 + requires_dist: + - executing>=1.2.0 + - asttokens>=2.1.0 + - pure-eval + - pytest ; extra == 'tests' + - typeguard ; extra == 'tests' + - pygments ; extra == 'tests' + - littleutils ; extra == 'tests' + - cython ; extra == 'tests' +- pypi: https://files.pythonhosted.org/packages/f4/40/8561ce06dc46fd17242c7724ab25b257a2ac1b35f4ebf551b40ce6105cfa/stevedore-5.6.0-py3-none-any.whl + name: stevedore + version: 5.6.0 + sha256: 4a36dccefd7aeea0c70135526cecb7766c4c84c473b1af68db23d541b6dc1820 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl + name: terminado + version: 0.18.1 + sha256: a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0 + requires_dist: + - ptyprocess ; os_name != 'nt' + - pywinpty>=1.1.0 ; os_name == 'nt' + - tornado>=6.1.0 + - myst-parser ; extra == 'docs' + - pydata-sphinx-theme ; extra == 'docs' + - sphinx ; extra == 'docs' + - pre-commit ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest>=7.0 ; extra == 'test' + - mypy~=1.6 ; extra == 'typing' + - traitlets>=5.11.1 ; extra == 'typing' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl + name: tinycss2 + version: 1.4.0 + sha256: 3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289 + requires_dist: + - webencodings>=0.4 + - sphinx ; extra == 'doc' + - sphinx-rtd-theme ; extra == 'doc' + - pytest ; extra == 'test' + - ruff ; extra == 'test' + requires_python: '>=3.8' +- 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: 3284905 + timestamp: 1763054914403 +- conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-hf689a15_3.conda + sha256: 0d0b6cef83fec41bc0eb4f3b761c4621b7adfb14378051a8177bd9bb73d26779 + md5: bd9f1de651dbd80b51281c694827f78f + depends: + - __osx >=10.13 + - libzlib >=1.3.1,<2.0a0 + license: TCL + license_family: BSD + purls: [] + size: 3262702 + timestamp: 1763055085507 +- 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: 3125484 + timestamp: 1763055028377 +- conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h2c6b04d_3.conda + sha256: 4581f4ffb432fefa1ac4f85c5682cc27014bcd66e7beaa0ee330e927a7858790 + md5: 7cb36e506a7dba4817970f8adb6396f9 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + license: TCL + license_family: BSD + purls: [] + size: 3472313 + timestamp: 1763055164278 +- pypi: https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl + name: toml + version: 0.10.2 + sha256: 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b + requires_python: '>=2.6,!=3.0.*,!=3.1.*,!=3.2.*' +- pypi: https://files.pythonhosted.org/packages/c7/2a/f609b420c2f564a748a2d80ebfb2ee02a73ca80223af712fca591386cafb/tornado-6.5.2-cp39-abi3-win_amd64.whl + name: tornado + version: 6.5.2 + sha256: e56a5af51cc30dd2cae649429af65ca2f6571da29504a07995175df14c18f35f + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/f2/b5/9b575a0ed3e50b00c40b08cbce82eb618229091d09f6d14bce80fc01cb0b/tornado-6.5.2-cp39-abi3-macosx_10_9_x86_64.whl + name: tornado + version: 6.5.2 + sha256: 583a52c7aa94ee046854ba81d9ebb6c81ec0fd30386d96f7640c96dad45a03ef + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/f6/48/6a7529df2c9cc12efd2e8f5dd219516184d703b34c06786809670df5b3bd/tornado-6.5.2-cp39-abi3-macosx_10_9_universal2.whl + name: tornado + version: 6.5.2 + sha256: 2436822940d37cde62771cff8774f4f00b3c8024fe482e16ca8387b8a2724db6 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/f9/41/fb15f06e33d7430ca89420283a8762a4e6b8025b800ea51796ab5e6d9559/tornado-6.5.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl + name: tornado + version: 6.5.2 + sha256: e792706668c87709709c18b353da1f7662317b563ff69f00bab83595940c7108 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/fc/cc/e09c0d663a004945f82beecd4f147053567910479314e8d01ba71e5d5dea/tox-4.32.0-py3-none-any.whl + name: tox + version: 4.32.0 + sha256: 451e81dc02ba8d1ed20efd52ee409641ae4b5d5830e008af10fe8823ef1bd551 + requires_dist: + - cachetools>=6.2 + - chardet>=5.2 + - colorama>=0.4.6 + - filelock>=3.20 + - packaging>=25 + - platformdirs>=4.5 + - pluggy>=1.6 + - pyproject-api>=1.9.1 + - tomli>=2.3 ; python_full_version < '3.11' + - typing-extensions>=4.15 ; python_full_version < '3.11' + - virtualenv>=20.34 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/fd/9e/8d50f3b3fc4af8c73154f64d4a2293bfa2d517a19000e70ef2d614254084/tox_gh_actions-3.5.0-py3-none-any.whl + name: tox-gh-actions + version: 3.5.0 + sha256: 070790114c92f4c94337047515ca5e077ceb269a5cb9366e0a0b3440a024eca1 + requires_dist: + - tox>=4,<5 + - devpi-process ; extra == 'testing' + - mypy ; platform_python_implementation == 'CPython' and extra == 'testing' + - pre-commit ; extra == 'testing' + - pytest>=7 ; extra == 'testing' + - pytest-cov>=4 ; extra == 'testing' + - pytest-mock>=3 ; extra == 'testing' + - pytest-randomly>=3 ; extra == 'testing' + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl + name: traitlets + version: 5.14.3 + sha256: b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f + requires_dist: + - myst-parser ; extra == 'docs' + - pydata-sphinx-theme ; extra == 'docs' + - sphinx ; extra == 'docs' + - argcomplete>=3.0.3 ; extra == 'test' + - mypy>=1.7.0 ; extra == 'test' + - pre-commit ; extra == 'test' + - pytest-mock ; extra == 'test' + - pytest-mypy-testing ; extra == 'test' + - pytest>=7.0,<8.2 ; extra == 'test' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/49/f6/73c4aa003d1237ee9bea8a46f49dc38c45dfe95af4f0da7e60678d388011/trove_classifiers-2025.11.14.15-py3-none-any.whl + name: trove-classifiers + version: 2025.11.14.15 + sha256: d1dac259c1e908939862e3331177931c6df0a37af2c1a8debcc603d9115fcdd9 +- pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl + name: typing-extensions + version: 4.15.0 + sha256: f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl + name: tzdata + version: '2025.2' + sha256: 1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8 + requires_python: '>=2' +- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + sha256: 5aaa366385d716557e365f0a4e9c3fca43ba196872abbbe3d56bb610d131e192 + md5: 4222072737ccff51314b5ece9c7d6f5a + license: LicenseRef-Public-Domain + purls: [] + size: 122968 + timestamp: 1742727099393 +- conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda + sha256: 3005729dce6f3d3f5ec91dfc49fc75a0095f9cd23bab49efb899657297ac91a5 + md5: 71b24316859acd00bdb8b38f5e2ce328 + constrains: + - vc14_runtime >=14.29.30037 + - vs2015_runtime >=14.29.30037 + license: LicenseRef-MicrosoftWindowsSDK10 + purls: [] + size: 694692 + timestamp: 1756385147981 +- pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl + name: uncertainties + version: 3.2.3 + sha256: 313353900d8f88b283c9bad81e7d2b2d3d4bcc330cbace35403faaed7e78890a + requires_dist: + - numpy ; extra == 'arrays' + - pytest ; extra == 'test' + - pytest-codspeed ; extra == 'test' + - pytest-cov ; extra == 'test' + - scipy ; extra == 'test' + - sphinx ; extra == 'doc' + - sphinx-copybutton ; extra == 'doc' + - python-docs-theme ; extra == 'doc' + - uncertainties[arrays,doc,test] ; extra == 'all' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl + name: uri-template + version: 1.3.0 + sha256: a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363 + requires_dist: + - types-pyyaml ; extra == 'dev' + - mypy ; extra == 'dev' + - flake8 ; extra == 'dev' + - flake8-annotations ; extra == 'dev' + - flake8-bandit ; extra == 'dev' + - flake8-bugbear ; extra == 'dev' + - flake8-commas ; extra == 'dev' + - flake8-comprehensions ; extra == 'dev' + - flake8-continuation ; extra == 'dev' + - flake8-datetimez ; extra == 'dev' + - flake8-docstrings ; extra == 'dev' + - flake8-import-order ; extra == 'dev' + - flake8-literal ; extra == 'dev' + - flake8-modern-annotations ; extra == 'dev' + - flake8-noqa ; extra == 'dev' + - flake8-pyproject ; extra == 'dev' + - flake8-requirements ; extra == 'dev' + - flake8-typechecking-import ; extra == 'dev' + - flake8-use-fstring ; extra == 'dev' + - pep8-naming ; extra == 'dev' + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl + name: urllib3 + version: 2.5.0 + sha256: e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc + requires_dist: + - brotli>=1.0.9 ; platform_python_implementation == 'CPython' and extra == 'brotli' + - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'brotli' + - h2>=4,<5 ; extra == 'h2' + - pysocks>=1.5.6,!=1.5.7,<2.0 ; extra == 'socks' + - zstandard>=0.18.0 ; extra == 'zstd' + requires_python: '>=3.9' +- conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h2b53caa_32.conda + sha256: 82250af59af9ff3c6a635dd4c4764c631d854feb334d6747d356d949af44d7cf + md5: ef02bbe151253a72b8eda264a935db66 + depends: + - vc14_runtime >=14.42.34433 + track_features: + - vc14 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 18861 + timestamp: 1760418772353 +- conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_32.conda + sha256: e3a3656b70d1202e0d042811ceb743bd0d9f7e00e2acdf824d231b044ef6c0fd + md5: 378d5dcec45eaea8d303da6f00447ac0 + depends: + - ucrt >=10.0.20348.0 + - vcomp14 14.44.35208 h818238b_32 + constrains: + - vs2015_runtime 14.44.35208.* *_32 + license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime + license_family: Proprietary + purls: [] + size: 682706 + timestamp: 1760418629729 +- conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_32.conda + sha256: f3790c88fbbdc55874f41de81a4237b1b91eab75e05d0e58661518ff04d2a8a1 + md5: 58f67b437acbf2764317ba273d731f1d + depends: + - ucrt >=10.0.20348.0 + constrains: + - vs2015_runtime 14.44.35208.* *_32 + license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime + license_family: Proprietary + purls: [] + size: 114846 + timestamp: 1760418593847 +- pypi: https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl + name: virtualenv + version: 20.35.4 + sha256: c21c9cede36c9753eeade68ba7d523529f228a403463376cf821eaae2b650f1b + requires_dist: + - distlib>=0.3.7,<1 + - filelock>=3.12.2,<4 + - importlib-metadata>=6.6 ; python_full_version < '3.8' + - platformdirs>=3.9.1,<5 + - typing-extensions>=4.13.2 ; python_full_version < '3.11' + - furo>=2023.7.26 ; extra == 'docs' + - proselint>=0.13 ; extra == 'docs' + - sphinx>=7.1.2,!=7.3 ; extra == 'docs' + - sphinx-argparse>=0.4 ; extra == 'docs' + - sphinxcontrib-towncrier>=0.2.1a0 ; extra == 'docs' + - towncrier>=23.6 ; extra == 'docs' + - covdefaults>=2.3 ; extra == 'test' + - coverage-enable-subprocess>=1 ; extra == 'test' + - coverage>=7.2.7 ; extra == 'test' + - flaky>=3.7 ; extra == 'test' + - packaging>=23.1 ; extra == 'test' + - pytest-env>=0.8.2 ; extra == 'test' + - pytest-freezer>=0.4.8 ; (python_full_version >= '3.13' and platform_python_implementation == 'CPython' and sys_platform == 'win32' and extra == 'test') or (platform_python_implementation == 'GraalVM' and extra == 'test') or (platform_python_implementation == 'PyPy' and extra == 'test') + - pytest-mock>=3.11.1 ; extra == 'test' + - pytest-randomly>=3.12 ; extra == 'test' + - pytest-timeout>=2.1 ; extra == 'test' + - pytest>=7.4 ; extra == 'test' + - setuptools>=68 ; extra == 'test' + - time-machine>=2.10 ; platform_python_implementation == 'CPython' and extra == 'test' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl + name: wcwidth + version: 0.2.14 + sha256: a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1 + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl + name: webcolors + version: 25.10.0 + sha256: 032c727334856fc0b968f63daa252a1ac93d33db2f5267756623c210e57a4f1d + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl + name: webencodings + version: 0.5.1 + sha256: a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 +- pypi: https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl + name: websocket-client + version: 1.9.0 + sha256: af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef + requires_dist: + - pytest ; extra == 'test' + - websockets ; extra == 'test' + - python-socks ; extra == 'optional' + - wsaccel ; extra == 'optional' + - sphinx>=6.0 ; extra == 'docs' + - sphinx-rtd-theme>=1.1.0 ; extra == 'docs' + - myst-parser>=2.0.0 ; extra == 'docs' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl + name: wsproto + version: 1.3.2 + sha256: 61eea322cdf56e8cc904bd3ad7573359a242ba65688716b0710a5eb12beab584 + requires_dist: + - h11>=0.16.0,<1 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/01/88/04d98af0b47e0ef42597b9b28863b9060bb515524da0a65d5f4db160b2d5/yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl + name: yarl + version: 1.22.0 + sha256: 01e73b85a5434f89fc4fe27dcda2aff08ddf35e4d47bbbea3bdcd25321af538a + requires_dist: + - idna>=2.0 + - multidict>=4.0 + - propcache>=0.2.1 + requires_python: '>=3.9' +- 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: bec03d0d388060058f5d291a813f21c011041938a441c593374da6077fe21b1b + requires_dist: + - idna>=2.0 + - multidict>=4.0 + - propcache>=0.2.1 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/18/91/3274b215fd8442a03975ce6bee5fe6aa57a8326b29b9d3d56234a1dca244/yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl + name: yarl + version: 1.22.0 + sha256: 22965c2af250d20c873cdbee8ff958fb809940aeb2e74ba5f20aaf6b7ac8c70c + requires_dist: + - idna>=2.0 + - multidict>=4.0 + - propcache>=0.2.1 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/9a/ee/450914ae11b419eadd067c6183ae08381cfdfcb9798b90b2b713bbebddda/yarl-1.22.0-cp313-cp313-win_amd64.whl + name: yarl + version: 1.22.0 + sha256: 47743b82b76d89a1d20b83e60d5c20314cbd5ba2befc9cda8f28300c4a08ed4d + requires_dist: + - idna>=2.0 + - multidict>=4.0 + - propcache>=0.2.1 + requires_python: '>=3.9' diff --git a/pixi.toml b/pixi.toml new file mode 100644 index 00000000..a7a8897b --- /dev/null +++ b/pixi.toml @@ -0,0 +1,54 @@ +[workspace] +name = "easyscience" +channels = ["conda-forge"] +platforms = ["linux-64", "osx-64", "osx-arm64", "win-64"] + +[dependencies] +python = ">=3.11,<3.14" + +[feature.dev.pypi-dependencies] +# Development dependencies +easyscience = { path = ".", extras = ["dev"], editable = true } + +[feature.docs.pypi-dependencies] +# Documentation dependencies +easyscience = { path = ".", extras = ["docs"], editable = true } + +[feature.build.pypi-dependencies] +# build dependencies +easyscience = { path = ".", extras = ["build"], editable = true } + +[environments] +# Default environment with basic build setup +default = ["build"] +# Development environment with build, dev, and docs features +dev = ["build", "dev", "docs"] + +[tasks] +# Development tasks +test = "pytest --cov=src/easyscience tests --cov-branch --cov-report=xml:coverage-unit.xml" +test-unit = "pytest tests/unit_tests" +test-integration = "pytest tests/integration_tests" + +# Code quality tasks +lint-check = "ruff check ." +lint = "ruff check . --fix" +format-check = "ruff format . --check" +format = "ruff format ." + +# Build tasks +build = "python -m build" +clean = "rm -rf dist/ build/ *.egg-info/" +update-lock = "pixi update" + +# Tox tasks +tox = "tox" + +# Documentation tasks +docs-build = "sphinx-build -b html docs/src docs/_build/html" +docs-clean = "rm -rf docs/_build/" + +[pypi-dependencies] # == [feature.default.pypi-dependencies] +# Editable install of the package itself +easyscience = { path = ".", editable = true } + diff --git a/pyproject.toml b/pyproject.toml index 6ce612e9..3cdb977c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,6 +38,11 @@ dependencies = [ ] [project.optional-dependencies] +build = [ + "hatchling <= 1.21.0", + "setuptools-git-versioning", + "build" +] dev = [ "build", "codecov", @@ -46,7 +51,8 @@ dev = [ "pytest", "pytest-cov", "ruff", - "tox-gh-actions" + "tox-gh-actions", + "jupyterlab" ] docs = [ "doc8", @@ -136,6 +142,7 @@ python = PLATFORM = ubuntu-latest: linux macos-latest: macos + macos-15-intel: macos-intel windows-latest: windows [testenv] passenv = diff --git a/resources/scripts/generate_html.py b/resources/scripts/generate_html.py index 0a399639..8f1a4cc6 100644 --- a/resources/scripts/generate_html.py +++ b/resources/scripts/generate_html.py @@ -1,4 +1,3 @@ - import sys import requests diff --git a/src/easyscience/__version__.py b/src/easyscience/__version__.py index afced147..a33997dd 100644 --- a/src/easyscience/__version__.py +++ b/src/easyscience/__version__.py @@ -1 +1 @@ -__version__ = '2.0.0' +__version__ = '2.1.0' diff --git a/src/easyscience/base_classes/__init__.py b/src/easyscience/base_classes/__init__.py index 7e4b2819..9f3ba080 100644 --- a/src/easyscience/base_classes/__init__.py +++ b/src/easyscience/base_classes/__init__.py @@ -1,9 +1,13 @@ from .based_base import BasedBase from .collection_base import CollectionBase +from .model_base import ModelBase +from .new_base import NewBase from .obj_base import ObjBase __all__ = [ BasedBase, CollectionBase, ObjBase, + ModelBase, + NewBase, ] diff --git a/src/easyscience/base_classes/based_base.py b/src/easyscience/base_classes/based_base.py index b401d049..a2427c7f 100644 --- a/src/easyscience/base_classes/based_base.py +++ b/src/easyscience/base_classes/based_base.py @@ -3,8 +3,10 @@ # SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause # © 2021-2025 Contributors to the EasyScience project Set[str]: base_cls = getattr(self, '__old_class__', self.__class__) - spec = getfullargspec(base_cls.__init__) - names = set(spec.args[1:]) - return names + sign = signature(base_cls.__init__) + names = [param.name for param in sign.parameters.values() if param.kind == param.POSITIONAL_OR_KEYWORD] + return set(names[1:]) def __reduce__(self): """ @@ -195,4 +197,17 @@ def __copy__(self) -> BasedBase: new_obj = self.__class__.from_dict(temp) return new_obj + def as_dict(self, skip: Optional[List[str]] = None) -> Dict[str, Any]: + """ + Convert an object into a full dictionary using `SerializerDict`. + This is a shortcut for ```obj.encode(encoder=SerializerDict)``` + :param skip: List of field names as strings to skip when forming the dictionary + :return: encoded object containing all information to reform an EasyScience object. + """ + # extend skip to include unique_name by default + if skip is None: + skip = [] + if 'unique_name' not in skip: + skip.append('unique_name') + return super().as_dict(skip=skip) diff --git a/src/easyscience/base_classes/collection_base.py b/src/easyscience/base_classes/collection_base.py index 45d6f39c..3cc0586a 100644 --- a/src/easyscience/base_classes/collection_base.py +++ b/src/easyscience/base_classes/collection_base.py @@ -23,7 +23,6 @@ from ..fitting.calculators import InterfaceFactoryTemplate - class CollectionBase(BasedBase, MutableSequence): """ This is the base class for which all higher level classes are built off of. @@ -232,7 +231,7 @@ def data(self) -> Tuple: return tuple(self._kwargs.values()) def __repr__(self) -> str: - return f"{self.__class__.__name__} `{getattr(self, 'name')}` of length {len(self)}" + return f'{self.__class__.__name__} `{getattr(self, "name")}` of length {len(self)}' def sort(self, mapping: Callable[[Union[BasedBase, DescriptorBase]], Any], reverse: bool = False) -> None: """ diff --git a/src/easyscience/base_classes/model_base.py b/src/easyscience/base_classes/model_base.py new file mode 100644 index 00000000..3a6a4061 --- /dev/null +++ b/src/easyscience/base_classes/model_base.py @@ -0,0 +1,119 @@ +from __future__ import annotations + +# SPDX-FileCopyrightText: 2025 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +# © 2021-2025 Contributors to the EasyScience project Parameter: + return self._my_param + + @my_param.setter + def my_param(self, new_value: float) -> None: + self._my_param.value = new_value + ``` + """ + + def __init__(self, unique_name: Optional[str] = None, display_name: Optional[str] = None): + super().__init__(unique_name=unique_name, display_name=display_name) + + def get_all_variables(self) -> List[DescriptorBase]: + """ + Get all `Descriptor` and `Parameter` objects as a list. + + :return: List of `Descriptor` and `Parameter` objects. + """ + vars = [] + for attr_name in dir(self): + attr = getattr(self, attr_name) + if isinstance(attr, DescriptorBase): + vars.append(attr) + elif hasattr(attr, 'get_all_variables'): + vars += attr.get_all_variables() + return vars + + def get_all_parameters(self) -> List[Parameter]: + """ + Get all `Parameter` objects as a list. + + :return: List of `Parameter` objects. + """ + return [param for param in self.get_all_variables() if isinstance(param, Parameter)] + + def get_fittable_parameters(self) -> List[Parameter]: + """ + Get all parameters which can be fitted as a list. + + :return: List of `Parameter` objects. + """ + return [param for param in self.get_all_parameters() if param.independent] + + def get_free_parameters(self) -> List[Parameter]: + """ + Get all parameters which are currently free to be fitted as a list. + + :return: List of `Parameter` objects. + """ + return [param for param in self.get_fittable_parameters() if not param.fixed] + + def get_fit_parameters(self) -> List[Parameter]: + """ + This is an alias for `get_free_parameters`. + To be removed when fully moved to new base classes and minimizer can be changed. + """ + return self.get_free_parameters() + + @classmethod + def from_dict(cls, obj_dict: Dict[str, Any]) -> ModelBase: + """ + Re-create an EasyScience object with DescriptorNumber attributes from a full encoded dictionary. + + :param obj_dict: dictionary containing the serialized contents (from `SerializerDict`) of an EasyScience object + :return: Reformed EasyScience object + """ + if not SerializerBase._is_serialized_easyscience_object(obj_dict): + raise ValueError('Input must be a dictionary representing an EasyScience object.') + if obj_dict['@class'] == cls.__name__: + kwargs = SerializerBase.deserialize_dict(obj_dict) + parameter_placeholder = {} + for key, value in kwargs.items(): + if isinstance(value, DescriptorNumber): + parameter_placeholder[key] = value + kwargs[key] = value.value + cls_instance = cls(**kwargs) + for key, value in parameter_placeholder.items(): + try: + temp_param = getattr(cls_instance, key) + setattr(cls_instance, '_' + key, value) + cls_instance._global_object.map.prune(temp_param.unique_name) + except Exception as e: + raise SyntaxError(f"""Could not set parameter {key} during `from_dict` with full deserialized variable. \n' + This should be fixed in the class definition. Error: {e}""") from e + return cls_instance + else: + raise ValueError(f'Class name in dictionary does not match the expected class: {cls.__name__}.') diff --git a/src/easyscience/base_classes/new_base.py b/src/easyscience/base_classes/new_base.py new file mode 100644 index 00000000..604233bd --- /dev/null +++ b/src/easyscience/base_classes/new_base.py @@ -0,0 +1,145 @@ +from __future__ import annotations + +# SPDX-FileCopyrightText: 2025 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +# © 2021-2025 Contributors to the EasyScience project Set[str]: + """ + This method is used by the serializer to determine which arguments are needed + by the constructor to deserialize the object. + """ + sign = signature(self.__class__.__init__) + names = [param.name for param in sign.parameters.values() if param.kind == param.POSITIONAL_OR_KEYWORD] + return set(names[1:]) + + @property + def unique_name(self) -> str: + """Get the unique name of the object.""" + return self._unique_name + + @unique_name.setter + def unique_name(self, new_unique_name: str): + """Set a new unique name for the object. The old name is still kept in the map. + + :param new_unique_name: New unique name for the object""" + if not isinstance(new_unique_name, str): + raise TypeError('Unique name has to be a string.') + self._unique_name = new_unique_name + self._global_object.map.add_vertex(self) + self._default_unique_name = False + + @property + def display_name(self) -> str: + """ + Get a pretty display name. + + :return: The pretty display name. + """ + display_name = self._display_name + if display_name is None: + display_name = self.unique_name + return display_name + + @display_name.setter + @property_stack + def display_name(self, name: str | None) -> None: + """ + Set the pretty display name. + + :param name: Pretty display name of the object. + """ + if name is not None and not isinstance(name, str): + raise TypeError('Display name must be a string or None') + self._display_name = name + + def to_dict(self, skip: Optional[List[str]] = None) -> Dict[str, Any]: + """ + Convert an EasyScience object into a full dictionary using `SerializerBase`s generic `convert_to_dict` method. + + :param skip: List of field names as strings to skip when forming the dictionary + :return: encoded object containing all information to reform an EasyScience object. + """ + serializer = SerializerBase() + if skip is None: + skip = [] + if self._default_unique_name and 'unique_name' not in skip: + skip.append('unique_name') + if self._display_name is None: + skip.append('display_name') + return serializer._convert_to_dict(self, skip=skip, full_encode=False) + + @classmethod + def from_dict(cls, obj_dict: Dict[str, Any]) -> NewBase: + """ + Re-create an EasyScience object from a full encoded dictionary. + + :param obj_dict: dictionary containing the serialized contents (from `SerializerDict`) of an EasyScience object + :return: Reformed EasyScience object + """ + if not SerializerBase._is_serialized_easyscience_object(obj_dict): + raise ValueError('Input must be a dictionary representing an EasyScience object.') + if obj_dict['@class'] == cls.__name__: + kwargs = SerializerBase.deserialize_dict(obj_dict) + return cls(**kwargs) + else: + raise ValueError(f'Class name in dictionary does not match the expected class: {cls.__name__}.') + + def __dir__(self) -> Iterable[str]: + """ + This creates auto-completion and helps out in iPython notebooks. + + :return: list of function and parameter names for auto-completion + """ + new_class_objs = list(k for k in dir(self.__class__) if not k.startswith('_')) + return sorted(new_class_objs) + + def __copy__(self) -> NewBase: + """Return a copy of the object.""" + temp = self.to_dict(skip=['unique_name']) + new_obj = self.__class__.from_dict(temp) + return new_obj + + def __deepcopy__(self, memo): + return self.__copy__() + + def __repr__(self) -> str: + return f'{self.__class__.__name__} `{self.unique_name}`' diff --git a/src/easyscience/base_classes/obj_base.py b/src/easyscience/base_classes/obj_base.py index 33316259..b42991f8 100644 --- a/src/easyscience/base_classes/obj_base.py +++ b/src/easyscience/base_classes/obj_base.py @@ -15,7 +15,6 @@ from ..io import SerializerComponent - class ObjBase(BasedBase): """ This is the base class for which all higher level classes are built off of. @@ -69,17 +68,12 @@ def _add_component(self, key: str, component: SerializerComponent) -> None: Dynamically add a component to the class. This is an internal method, though can be called remotely. The recommended alternative is to use typing, i.e. - class Foo(Bar): - def __init__(self, foo: Parameter, bar: Parameter): - super(Foo, self).__init__(bar=bar) - self._add_component("foo", foo) + .. code-block:: python - Goes to: - class Foo(Bar): - foo: ClassVar[Parameter] - def __init__(self, foo: Parameter, bar: Parameter): - super(Foo, self).__init__(bar=bar) - self.foo = foo + class Foo(Bar): + def __init__(self, foo: Parameter, bar: Parameter): + super(Foo, self).__init__(bar=bar) + self._add_component("foo", foo) :param key: Name of component to be added :param component: Component to be added @@ -127,7 +121,7 @@ def __setattr__(self, key: str, value: SerializerComponent) -> None: self.generate_bindings() def __repr__(self) -> str: - return f"{self.__class__.__name__} `{getattr(self, 'name')}`" + return f'{self.__class__.__name__} `{getattr(self, "name")}`' @staticmethod def __getter(key: str) -> Callable[[SerializerComponent], SerializerComponent]: @@ -139,9 +133,7 @@ def getter(obj: SerializerComponent) -> SerializerComponent: @staticmethod def __setter(key: str) -> Callable[[SerializerComponent], None]: def setter(obj: SerializerComponent, value: float) -> None: - if issubclass(obj._kwargs[key].__class__, (DescriptorBase)) and not issubclass( - value.__class__, (DescriptorBase) - ): + if issubclass(obj._kwargs[key].__class__, (DescriptorBase)) and not issubclass(value.__class__, (DescriptorBase)): obj._kwargs[key].value = value else: obj._kwargs[key] = value diff --git a/src/easyscience/fitting/fitter.py b/src/easyscience/fitting/fitter.py index 0cb67016..1ccacbe1 100644 --- a/src/easyscience/fitting/fitter.py +++ b/src/easyscience/fitting/fitter.py @@ -69,6 +69,7 @@ def create(self, minimizer_enum: Union[AvailableMinimizers, str] = DEFAULT_MINIM def switch_minimizer(self, minimizer_enum: Union[AvailableMinimizers, str]) -> None: """ Switch minimizer and initialize. + :param minimizer_enum: The enum of the minimizer to create and instantiate. """ if isinstance(minimizer_enum, str): diff --git a/src/easyscience/fitting/minimizers/__init__.py b/src/easyscience/fitting/minimizers/__init__.py index b4de4c38..5ca5ee06 100644 --- a/src/easyscience/fitting/minimizers/__init__.py +++ b/src/easyscience/fitting/minimizers/__init__.py @@ -3,7 +3,10 @@ # © 2021-2025 Contributors to the EasyScience project Callable: def _outer(obj: DFO): def _make_func(x, y, weights): - dfo_pars = {} if not parameters: for name, par in obj._cached_pars.items(): diff --git a/src/easyscience/fitting/minimizers/minimizer_lmfit.py b/src/easyscience/fitting/minimizers/minimizer_lmfit.py index ed0ab397..4a10993c 100644 --- a/src/easyscience/fitting/minimizers/minimizer_lmfit.py +++ b/src/easyscience/fitting/minimizers/minimizer_lmfit.py @@ -116,13 +116,13 @@ def fit( if y.shape != x.shape: raise ValueError('x and y must have the same shape.') - + if weights.shape != x.shape: raise ValueError('Weights must have the same shape as x and y.') - + if not np.isfinite(weights).all(): raise ValueError('Weights cannot be NaN or infinite.') - + if (weights <= 0).any(): raise ValueError('Weights must be strictly positive and non-zero.') diff --git a/src/easyscience/fitting/minimizers/utils.py b/src/easyscience/fitting/minimizers/utils.py index 2e99f6ca..60ab00a6 100644 --- a/src/easyscience/fitting/minimizers/utils.py +++ b/src/easyscience/fitting/minimizers/utils.py @@ -51,6 +51,7 @@ def chi2(self): def reduced_chi(self): return self.chi2 / (len(self.x) - self.n_pars) + class FitError(Exception): def __init__(self, e: Exception = None): self.e = e diff --git a/src/easyscience/global_object/__init__.py b/src/easyscience/global_object/__init__.py index 0bea5e61..2adc1bc2 100644 --- a/src/easyscience/global_object/__init__.py +++ b/src/easyscience/global_object/__init__.py @@ -2,3 +2,4 @@ from .hugger.hugger import ScriptManager # noqa: F401 from .logger import Logger # noqa: F401 from .map import Map # noqa: F401 +from .undo_redo import UndoStack # noqa: F401 diff --git a/src/easyscience/global_object/hugger/hugger.py b/src/easyscience/global_object/hugger/hugger.py index 637f9ad6..193482c8 100644 --- a/src/easyscience/global_object/hugger/hugger.py +++ b/src/easyscience/global_object/hugger/hugger.py @@ -15,8 +15,8 @@ @singleton class Store: __log = [] - __var_ident = "var_" - __ret_ident = "ret_" + __var_ident = 'var_' + __ret_ident = 'ret_' def __init__(self): self.log = self.__log # TODO Async problem? @@ -26,12 +26,12 @@ def __init__(self): @staticmethod def get_defaults() -> dict: return { - "log": Store.__log, - "create_list": Store.__create_list, - "unique_args": Store.__unique_args, - "unique_rets": Store.__unique_rets, - "var_ident": Store.__var_ident, - "ret_ident": Store.__ret_ident, + 'log': Store.__log, + 'create_list': Store.__create_list, + 'unique_args': Store.__unique_args, + 'unique_rets': Store.__unique_rets, + 'var_ident': Store.__var_ident, + 'ret_ident': Store.__ret_ident, } def append_log(self, log_entry: str): @@ -87,9 +87,7 @@ def __init__(self): @staticmethod def is_mutable(arg) -> bool: ret = True - if isinstance( - arg, (int, float, complex, str, tuple, frozenset, bytes, property) - ): + if isinstance(arg, (int, float, complex, str, tuple, frozenset, bytes, property)): ret = False return ret @@ -112,7 +110,7 @@ def stack_(frame): stack = stack_(sys._getframe(1)) start = 0 + skip if len(stack) < start + 1: - return "" + return '' parentframe = stack[start] name = [] @@ -122,16 +120,16 @@ def stack_(frame): if module: name.append(module.__name__) # detect classname - if "self" in parentframe.f_locals: + if 'self' in parentframe.f_locals: # I don't know any way to detect call from the object method # XXX: there seems to be no way to detect static method call - it will # be just a function call - name.append(parentframe.f_locals["self"].__class__.__name__) + name.append(parentframe.f_locals['self'].__class__.__name__) codename = parentframe.f_code.co_name - if codename != "": # top level usually + if codename != '': # top level usually name.append(codename) # function or a method del parentframe - return ".".join(name) + return '.'.join(name) def _append_args(self, *args, **kwargs): def check(res): @@ -184,9 +182,9 @@ def _append_log(self, log_entry: str): def __options(self, item) -> Tuple[int, dict]: this_id = id(item) option = { - "create_list": self._store.create_list, - "return_list": self._store.unique_rets, - "input_list": self._store.unique_args, + 'create_list': self._store.create_list, + 'return_list': self._store.unique_rets, + 'input_list': self._store.unique_args, } return this_id, option @@ -210,9 +208,7 @@ def _get_class_that_defined_method(method_in) -> classmethod: return cls method_in = method_in.__func__ # fallback to __qualname__ parsing if inspect.isfunction(method_in): - class_name = method_in.__qualname__.split(".", 1)[0].rsplit(".", 1)[ - 0 - ] + class_name = method_in.__qualname__.split('.', 1)[0].rsplit('.', 1)[0] try: cls = getattr(inspect.getmodule(method_in), class_name) except AttributeError: diff --git a/src/easyscience/global_object/hugger/property.py b/src/easyscience/global_object/hugger/property.py index fdf19333..a18f913b 100644 --- a/src/easyscience/global_object/hugger/property.py +++ b/src/easyscience/global_object/hugger/property.py @@ -42,11 +42,11 @@ def stack_(frame): stack: List[Any] = stack_(sys._getframe(1)) start = 0 + skip if len(stack) < start + 1: - return "" + return '' parent_frame = stack[start] test = False - if "self" in parent_frame.f_locals: - test = issubclass(parent_frame.f_locals["self"].__class__, test_class) + if 'self' in parent_frame.f_locals: + test = issubclass(parent_frame.f_locals['self'].__class__, test_class) return test def __get__(self, instance, owner=None): @@ -59,9 +59,9 @@ def result_item(item_to_be_resulted): if item_to_be_resulted is None: return None if global_object.map.is_known(item_to_be_resulted): - global_object.map.change_type(item_to_be_resulted, "returned") + global_object.map.change_type(item_to_be_resulted, 'returned') else: - global_object.map.add_vertex(item_to_be_resulted, obj_type="returned") + global_object.map.add_vertex(item_to_be_resulted, obj_type='returned') if not test and self._get_id is not None and self._my_self is not None: if not isinstance(res, list): @@ -69,11 +69,9 @@ def result_item(item_to_be_resulted): else: for item in res: result_item(item) - Store().append_log(self.makeEntry("get", res)) + Store().append_log(self.makeEntry('get', res)) if global_object.debug: # noqa: S1006 - print( - f"I'm {self._my_self} and {self._get_id} has been called from the outside!" - ) + print(f"I'm {self._my_self} and {self._get_id} has been called from the outside!") return res def __set__(self, instance, value): @@ -81,74 +79,56 @@ def __set__(self, instance, value): return super().__set__(instance, value) test = self._caller_class(self.test_class) if not test and self._get_id is not None and self._my_self is not None: - Store().append_log(self.makeEntry("set", value)) + Store().append_log(self.makeEntry('set', value)) if global_object.debug: # noqa: S1006 - print( - f"I'm {self._my_self} and {self._get_id} has been set to {value} from the outside!" - ) + print(f"I'm {self._my_self} and {self._get_id} has been set to {value} from the outside!") return super().__set__(instance, value) def makeEntry(self, log_type, returns, *args, **kwargs) -> str: - temp = "" + temp = '' if returns is None: returns = [] if not isinstance(returns, list): returns = [returns] - if log_type == "get": + if log_type == 'get': for var in returns: if var.unique_name in global_object.map.returned_objs: - index = global_object.map.returned_objs.index( - var.unique_name - ) - temp += f"{Store().var_ident}{index}, " + index = global_object.map.returned_objs.index(var.unique_name) + temp += f'{Store().var_ident}{index}, ' if len(returns) > 0: temp = temp[:-2] - temp += " = " + temp += ' = ' if self._my_self.unique_name in global_object.map.created_objs: # for edge in route[::-1]: - index = global_object.map.created_objs.index( - self._my_self.unique_name - ) - temp += ( - f"{self._my_self.__class__.__name__.lower()}_{index}.{self._get_id}" - ) + index = global_object.map.created_objs.index(self._my_self.unique_name) + temp += f'{self._my_self.__class__.__name__.lower()}_{index}.{self._get_id}' if self._my_self.unique_name in global_object.map.created_internal: # We now have to trace.... route = global_object.map.reverse_route(self._my_self) # noqa: F841 - index = global_object.map.created_internal.index( - self._my_self.unique_name - ) - temp += ( - f"{self._my_self.__class__.__name__.lower()}_{index}.{self._get_id}" - ) - elif log_type == "set": + index = global_object.map.created_internal.index(self._my_self.unique_name) + temp += f'{self._my_self.__class__.__name__.lower()}_{index}.{self._get_id}' + elif log_type == 'set': if self._my_self.unique_name in global_object.map.created_objs: - index = global_object.map.created_objs.index( - self._my_self.unique_name - ) - temp += f"{self._my_self.__class__.__name__.lower()}_{index}.{self._get_id} = " + index = global_object.map.created_objs.index(self._my_self.unique_name) + temp += f'{self._my_self.__class__.__name__.lower()}_{index}.{self._get_id} = ' args = args[1:] for var in args: if var.unique_name in global_object.map.argument_objs: - index = global_object.map.argument_objs.index( - var.unique_name - ) - temp += f"{Store().var_ident}{index}" + index = global_object.map.argument_objs.index(var.unique_name) + temp += f'{Store().var_ident}{index}' elif var.unique_name in global_object.map.returned_objs: - index = global_object.map.returned_objs.index( - var.unique_name - ) - temp += f"{Store().var_ident}{index}" + index = global_object.map.returned_objs.index(var.unique_name) + temp += f'{Store().var_ident}{index}' elif var.unique_name in global_object.map.created_objs: index = global_object.map.created_objs.index(var.unique_name) - temp += f"{self._my_self.__class__.__name__.lower()}_{index}" + temp += f'{self._my_self.__class__.__name__.lower()}_{index}' else: if isinstance(var, str): var = '"' + var + '"' - temp += f"{var}" + temp += f'{var}' else: - print(f"{log_type} is not implemented yet. Sorry") - temp += "\n" + print(f'{log_type} is not implemented yet. Sorry') + temp += '\n' return temp @@ -168,9 +148,9 @@ def __init__(self, klass, prop_name): self.prop_name = prop_name self.property = klass.__dict__.get(prop_name) self.__patch_ref = { - "fget": {"old": self.property.fget, "patcher": self.patch_get}, - "fset": {"old": self.property.fset, "patcher": self.patch_set}, - "fdel": {"old": self.property.fdel, "patcher": self.patch_del}, + 'fget': {'old': self.property.fget, 'patcher': self.patch_get}, + 'fset': {'old': self.property.fset, 'patcher': self.patch_set}, + 'fdel': {'old': self.property.fdel, 'patcher': self.patch_del}, } def patch(self): @@ -179,28 +159,26 @@ def patch(self): func = getattr(self.property, key) if func is not None: if global_object.debug: - print(f"Patching property {self.klass.__name__}.{self.prop_name}") - patch_function: Callable = item.get("patcher") + print(f'Patching property {self.klass.__name__}.{self.prop_name}') + patch_function: Callable = item.get('patcher') new_func = patch_function(func) option[key] = new_func setattr(self.klass, self.prop_name, property(**option)) def restore(self): if global_object.debug: - print(f"Restoring property {self.klass.__name__}.{self.prop_name}") + print(f'Restoring property {self.klass.__name__}.{self.prop_name}') setattr(self.klass, self.prop_name, self.property) def patch_get(self, func: Callable) -> Callable: @wraps(func) def inner(*args, **kwargs): if global_object.debug: - print( - f"{self.klass.__name__}.{self.prop_name} has been called with {args[1:]}, {kwargs}" - ) + print(f'{self.klass.__name__}.{self.prop_name} has been called with {args[1:]}, {kwargs}') res = func(*args, **kwargs) self._append_args(*args, **kwargs) self._append_result(res) - self._append_log(self.makeEntry("get", res, *args, **kwargs)) + self._append_log(self.makeEntry('get', res, *args, **kwargs)) return res return inner @@ -209,11 +187,9 @@ def patch_set(self, func: Callable) -> Callable: @wraps(func) def inner(*args, **kwargs): if global_object.debug: - print( - f"{self.klass.__name__}.{self.prop_name} has been set with {args[1:]}, {kwargs}" - ) + print(f'{self.klass.__name__}.{self.prop_name} has been set with {args[1:]}, {kwargs}') self._append_args(*args, **kwargs) - self._append_log(self.makeEntry("set", None, *args, **kwargs)) + self._append_log(self.makeEntry('set', None, *args, **kwargs)) return func(*args, **kwargs) return inner @@ -222,49 +198,49 @@ def patch_del(self, func: Callable) -> Callable: @wraps(func) def inner(*args, **kwargs): if global_object.debug: - print(f"{self.klass.__name__}.{self.prop_name} has been deleted.") - self._append_log(self.makeEntry("del", None, *args, **kwargs)) + print(f'{self.klass.__name__}.{self.prop_name} has been deleted.') + self._append_log(self.makeEntry('del', None, *args, **kwargs)) return func(*args, **kwargs) return inner def makeEntry(self, log_type, returns, *args, **kwargs) -> str: - temp = "" + temp = '' if returns is None: returns = [] if not isinstance(returns, list): returns = [returns] - if log_type == "get": + if log_type == 'get': for var in returns: - if self._in_list("return_list", var): - index = self._get_position("return_list", var) - temp += f"{self._store.var_ident}{index}, " + if self._in_list('return_list', var): + index = self._get_position('return_list', var) + temp += f'{self._store.var_ident}{index}, ' if len(returns) > 0: temp = temp[:-2] - temp += " = " - if self._in_list("create_list", args[0]): - index = self._get_position("create_list", args[0]) - temp += f"{self.klass.__name__.lower()}_{index}.{self.prop_name}" - elif log_type == "set": - if self._in_list("create_list", args[0]): - index = self._get_position("create_list", args[0]) - temp += f"{self.klass.__name__.lower()}_{index}.{self.prop_name} = " + temp += ' = ' + if self._in_list('create_list', args[0]): + index = self._get_position('create_list', args[0]) + temp += f'{self.klass.__name__.lower()}_{index}.{self.prop_name}' + elif log_type == 'set': + if self._in_list('create_list', args[0]): + index = self._get_position('create_list', args[0]) + temp += f'{self.klass.__name__.lower()}_{index}.{self.prop_name} = ' args = args[1:] for var in args: - if self._in_list("input_list", var): - index = self._get_position("input_list", var) - temp += f"{self._store.var_ident}{index}" - elif self._in_list("return_list", var): - index = self._get_position("return_list", var) - temp += f"{self._store.var_ident}{index}" - elif self._in_list("create_list", var): - index = self._get_position("create_list", var) - temp += f"{self.klass.__name__.lower()}_{index}" + if self._in_list('input_list', var): + index = self._get_position('input_list', var) + temp += f'{self._store.var_ident}{index}' + elif self._in_list('return_list', var): + index = self._get_position('return_list', var) + temp += f'{self._store.var_ident}{index}' + elif self._in_list('create_list', var): + index = self._get_position('create_list', var) + temp += f'{self.klass.__name__.lower()}_{index}' else: if isinstance(var, str): var = '"' + var + '"' - temp += f"{var}" + temp += f'{var}' else: - print(f"{log_type} is not implemented yet. Sorry") - temp += "\n" + print(f'{log_type} is not implemented yet. Sorry') + temp += '\n' return temp diff --git a/src/easyscience/global_object/logger.py b/src/easyscience/global_object/logger.py index 6e537d26..f4fa3a42 100644 --- a/src/easyscience/global_object/logger.py +++ b/src/easyscience/global_object/logger.py @@ -11,9 +11,7 @@ def __init__(self, log_level: int = logging.INFO): self.level = log_level self.logger.setLevel(self.level) - def getLogger( - self, logger_name, color: str = "32", defaults: bool = True - ) -> logging: + def getLogger(self, logger_name, color: str = '32', defaults: bool = True) -> logging: """ Create a logger :param color: diff --git a/src/easyscience/global_object/map.py b/src/easyscience/global_object/map.py index 53a3aac4..c64bfaff 100644 --- a/src/easyscience/global_object/map.py +++ b/src/easyscience/global_object/map.py @@ -24,10 +24,10 @@ def __repr__(self) -> str: s += ', '.join(self._type) else: s += 'Undefined' - s += '. With' if self.finalizer is None: - s += 'out' - s += 'a finalizer.' + s += '. Without a finalizer.' + else: + s += '. With a finalizer.' return s def __delitem__(self, key): @@ -261,7 +261,7 @@ def is_connected(self, vertices_encountered=None, start_vertex=None) -> bool: return False def _clear(self): - """Reset the map to an empty state.""" + """Reset the map to an empty state. Only to be used for testing""" for vertex in self.vertices(): self.prune(vertex) gc.collect() diff --git a/src/easyscience/global_object/undo_redo.py b/src/easyscience/global_object/undo_redo.py index a93d89fc..7317ec23 100644 --- a/src/easyscience/global_object/undo_redo.py +++ b/src/easyscience/global_object/undo_redo.py @@ -15,7 +15,7 @@ import numpy as np -from easyscience import global_object +# Removed: from easyscience import global_object # Avoid circular import class UndoCommand(metaclass=abc.ABCMeta): @@ -53,6 +53,8 @@ def text(self, text: str) -> NoReturn: def dict_stack_deco(func: Callable) -> Callable: def inner(obj, *args, **kwargs): + from easyscience import global_object # Local import to avoid circular dependency + # Only do the work to a NotarizedDict. if hasattr(obj, '_stack_enabled') and obj._stack_enabled: if not kwargs: @@ -71,6 +73,8 @@ class NotarizedDict(UserDict): """ def __init__(self, **kwargs): + from easyscience import global_object # Local import to avoid circular dependency + super().__init__(**kwargs) self._global_object = global_object self._stack_enabled = False @@ -455,6 +459,8 @@ def func() def make_wrapper(func: Callable, name: str, **kwargs) -> Callable: def wrapper(obj, *args) -> NoReturn: + from easyscience import global_object # Local import to avoid circular dependency + old_value = getattr(obj, name) new_value = args[0] if issubclass(type(old_value), Iterable) or issubclass(type(new_value), Iterable): diff --git a/src/easyscience/io/serializer_base.py b/src/easyscience/io/serializer_base.py index b50cedf2..b4eeaf2a 100644 --- a/src/easyscience/io/serializer_base.py +++ b/src/easyscience/io/serializer_base.py @@ -260,6 +260,76 @@ def _convert_from_dict(d): return [SerializerBase._convert_from_dict(x) for x in d] return d + @staticmethod + def deserialize_dict(in_dict: Dict[str, Any]) -> Dict[str, Any]: + """ + Deserialize a dictionary using from_dict for ES objects and SerializerBase otherwise. + This method processes constructor arguments, skipping metadata keys starting with '@'. + + :param in_dict: dictionary to deserialize + :return: deserialized dictionary with constructor arguments + """ + d = {key: SerializerBase._deserialize_value(value) for key, value in in_dict.items() if not key.startswith('@')} + return d + + @staticmethod + def _deserialize_value(value: Any) -> Any: + """ + Deserialize a single value, using specialized handling for ES objects. + + :param value: + :return: deserialized value + """ + if not SerializerBase._is_serialized_easyscience_object(value): + return SerializerBase._convert_from_dict(value) + + module_name = value['@module'] + class_name = value['@class'] + + try: + cls = SerializerBase._import_class(module_name, class_name) + + # Prefer from_dict() method for ES objects + if hasattr(cls, 'from_dict'): + return cls.from_dict(value) + else: + return SerializerBase._convert_from_dict(value) + + except (ImportError, ValueError): + # Fallback to generic deserialization if class-specific fails + return SerializerBase._convert_from_dict(value) + + @staticmethod + def _is_serialized_easyscience_object(value: Any) -> bool: + """ + Check if a value represents a serialized ES object. + + :param value: + :return: True if this is a serialized ES object + """ + return isinstance(value, dict) and '@module' in value and value['@module'].startswith('easy') and '@class' in value + + @staticmethod + def _import_class(module_name: str, class_name: str): + """ + Import a class from a module name and class name. + + :param module_name: name of the module + :param class_name: name of the class + :return: the imported class + :raises ImportError: if module cannot be imported + :raises ValueError: if class is not found in module + """ + try: + module = __import__(module_name, globals(), locals(), [class_name], 0) + except ImportError as e: + raise ImportError(f'Could not import module {module_name}') from e + + if not hasattr(module, class_name): + raise ValueError(f'Class {class_name} not found in module {module_name}.') + + return getattr(module, class_name) + def _recursive_encoder(self, obj, skip: List[str] = [], encoder=None, full_encode=False, **kwargs): """ Walk through an object encoding it @@ -271,11 +341,14 @@ def _recursive_encoder(self, obj, skip: List[str] = [], encoder=None, full_encod # Is it a core MutableSequence? if hasattr(obj, 'encode') and obj.__class__.__module__ != 'builtins': # strings have encode return encoder._convert_to_dict(obj, skip, full_encode, **kwargs) + elif hasattr(obj, 'to_dict') and obj.__class__.__module__.startswith('easy'): + return encoder._convert_to_dict(obj, skip, full_encode, **kwargs) else: return [self._recursive_encoder(it, skip, encoder, full_encode, **kwargs) for it in obj] if isinstance(obj, dict): return {kk: self._recursive_encoder(vv, skip, encoder, full_encode, **kwargs) for kk, vv in obj.items()} if hasattr(obj, 'encode') and obj.__class__.__module__ != 'builtins': # strings have encode return encoder._convert_to_dict(obj, skip, full_encode, **kwargs) + elif hasattr(obj, 'to_dict') and obj.__class__.__module__.startswith('easy'): + return encoder._convert_to_dict(obj, skip, full_encode, **kwargs) return obj - diff --git a/src/easyscience/io/serializer_component.py b/src/easyscience/io/serializer_component.py index 15995412..85da9961 100644 --- a/src/easyscience/io/serializer_component.py +++ b/src/easyscience/io/serializer_component.py @@ -18,14 +18,13 @@ class SerializerComponent: """ - This base class adds the capability of saving and loading (encoding/decoding, serializing/deserializing) easyscience - objects via the `encode` and `decode` methods. + This base class adds the capability of saving and loading (encoding/decoding, serializing/deserializing) easyscience + objects via the `encode` and `decode` methods. The default encoder is `SerializerDict`, which converts the object to a dictionary. Shortcuts for dictionary and encoding is also present. """ - def __deepcopy__(self, memo): return self.from_dict(self.as_dict()) diff --git a/src/easyscience/io/serializer_dict.py b/src/easyscience/io/serializer_dict.py index 95b28f09..8fe423c1 100644 --- a/src/easyscience/io/serializer_dict.py +++ b/src/easyscience/io/serializer_dict.py @@ -1,7 +1,7 @@ from __future__ import annotations -__author__ = "https://github.com/materialsvirtuallab/monty/blob/master/monty/json.py" -__version__ = "3.0.0" +__author__ = 'https://github.com/materialsvirtuallab/monty/blob/master/monty/json.py' +__version__ = '3.0.0' # SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause # © 2021-2025 Contributors to the EasyScience project SerializerComponent: :return: EasyScience object. """ - return SerializerBase._convert_from_dict(d) \ No newline at end of file + return SerializerBase._convert_from_dict(d) diff --git a/src/easyscience/job/__init__.py b/src/easyscience/job/__init__.py index e69de29b..5a1770f5 100644 --- a/src/easyscience/job/__init__.py +++ b/src/easyscience/job/__init__.py @@ -0,0 +1,8 @@ +# SPDX-FileCopyrightText: 2025 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +# © 2021-2025 Contributors to the EasyScience project np.ndarray: - raise NotImplementedError("calculate_theory not implemented") + def calculate_theory(self, x: np.ndarray, **kwargs) -> np.ndarray: + raise NotImplementedError('calculate_theory not implemented') @abstractmethod - def fit(self, - x: np.ndarray, - y: np.ndarray, - e: np.ndarray, - **kwargs) -> None: - raise NotImplementedError("fit not implemented") + def fit(self, x: np.ndarray, y: np.ndarray, e: np.ndarray, **kwargs) -> None: + raise NotImplementedError('fit not implemented') @property def calculator(self) -> str: @@ -57,6 +52,4 @@ def minimizer(self, minimizer: MinimizerBase) -> None: # required dunder methods def __str__(self): - return f"Analysis: {self.name}" - - \ No newline at end of file + return f'Analysis: {self.name}' diff --git a/src/easyscience/job/experiment.py b/src/easyscience/job/experiment.py index 807e0572..cc356614 100644 --- a/src/easyscience/job/experiment.py +++ b/src/easyscience/job/experiment.py @@ -10,12 +10,11 @@ class ExperimentBase(ObjBase): """ This virtual class allows for the creation of technique-specific Experiment objects. """ + def __init__(self, name: str, *args, **kwargs): super(ExperimentBase, self).__init__(name, *args, **kwargs) self._name = name # required dunder methods def __str__(self): - return f"Experiment: {self._name}" - - \ No newline at end of file + return f'Experiment: {self._name}' diff --git a/src/easyscience/job/job.py b/src/easyscience/job/job.py index dca21b60..0a1df5e9 100644 --- a/src/easyscience/job/job.py +++ b/src/easyscience/job/job.py @@ -15,6 +15,7 @@ class JobBase(ObjBase, metaclass=ABCMeta): """ This virtual class allows for the creation of technique-specific Job objects. """ + def __init__(self, name: str, *args, **kwargs): super(JobBase, self).__init__(name, *args, **kwargs) self.name = name @@ -28,6 +29,7 @@ def __init__(self, name: str, *args, **kwargs): JobBase consists of Theory, Experiment, Analysis virtual classes. Summary and Info classes are included to store additional information. """ + @property def theorerical_model(self): return self._theory @@ -35,8 +37,8 @@ def theorerical_model(self): @theorerical_model.setter @abstractmethod def theoretical_model(self, theory: TheoreticalModelBase): - raise NotImplementedError("theory setter not implemented") - + raise NotImplementedError('theory setter not implemented') + @property def experiment(self): return self._experiment @@ -44,7 +46,7 @@ def experiment(self): @experiment.setter @abstractmethod def experiment(self, experiment: ExperimentBase): - raise NotImplementedError("experiment setter not implemented") + raise NotImplementedError('experiment setter not implemented') @property def analysis(self): @@ -53,7 +55,7 @@ def analysis(self): @analysis.setter @abstractmethod def analysis(self, analysis: AnalysisBase): - raise NotImplementedError("analysis setter not implemented") + raise NotImplementedError('analysis setter not implemented') # TODO: extend derived classes to include Summary and Info # @property @@ -64,7 +66,7 @@ def analysis(self, analysis: AnalysisBase): # @abstractmethod # def summary(self, summary: SummaryBase): # raise NotImplementedError("summary setter not implemented") - + # @property # def info(self): # return self._info @@ -76,9 +78,8 @@ def analysis(self, analysis: AnalysisBase): @abstractmethod def calculate_theory(self, *args, **kwargs): - raise NotImplementedError("calculate_theory not implemented") + raise NotImplementedError('calculate_theory not implemented') @abstractmethod def fit(self, *args, **kwargs): - raise NotImplementedError("fit not implemented") - \ No newline at end of file + raise NotImplementedError('fit not implemented') diff --git a/src/easyscience/job/theoreticalmodel.py b/src/easyscience/job/theoreticalmodel.py index 0a7d5699..ac0fd432 100644 --- a/src/easyscience/job/theoreticalmodel.py +++ b/src/easyscience/job/theoreticalmodel.py @@ -10,15 +10,15 @@ class TheoreticalModelBase(ObjBase): """ This virtual class allows for the creation of technique-specific Theory objects. """ + def __init__(self, name: str, *args, **kwargs): self._name = name super().__init__(name, *args, **kwargs) # required dunder methods def __str__(self): - raise NotImplementedError("Copy not implemented") - + raise NotImplementedError('Copy not implemented') + def as_dict(self, skip: list = []) -> dict: this_dict = super().as_dict(skip=skip) return this_dict - diff --git a/src/easyscience/legacy/dict.py b/src/easyscience/legacy/dict.py index e1339707..9ae29397 100644 --- a/src/easyscience/legacy/dict.py +++ b/src/easyscience/legacy/dict.py @@ -1,7 +1,7 @@ from __future__ import annotations -__author__ = "https://github.com/materialsvirtuallab/monty/blob/master/monty/json.py" -__version__ = "3.0.0" +__author__ = 'https://github.com/materialsvirtuallab/monty/blob/master/monty/json.py' +__version__ = '3.0.0' # SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause # © 2021-2025 Contributors to the EasyScience project ComponentSerializer: EasyScience object. """ - raise NotImplementedError( - "It is not possible to reconstitute objects from data only dictionary." - ) + raise NotImplementedError('It is not possible to reconstitute objects from data only dictionary.') @staticmethod def _parse_dict(in_dict: Dict[str, Any]) -> Dict[str, Any]: @@ -113,16 +111,13 @@ def _parse_dict(in_dict: Dict[str, Any]) -> Dict[str, Any]: out_dict = dict() for key in in_dict.keys(): - if key[0] == "@": - if key == "@class" and in_dict[key] not in _KNOWN_CORE_TYPES: - out_dict["name"] = in_dict[key] + if key[0] == '@': + if key == '@class' and in_dict[key] not in _KNOWN_CORE_TYPES: + out_dict['name'] = in_dict[key] continue out_dict[key] = in_dict[key] if isinstance(in_dict[key], dict): out_dict[key] = DataDictSerializer._parse_dict(in_dict[key]) elif isinstance(in_dict[key], list): - out_dict[key] = [ - DataDictSerializer._parse_dict(x) if isinstance(x, dict) else x - for x in in_dict[key] - ] + out_dict[key] = [DataDictSerializer._parse_dict(x) if isinstance(x, dict) else x for x in in_dict[key]] return out_dict diff --git a/src/easyscience/models/__init__.py b/src/easyscience/models/__init__.py index de175a27..a9aed973 100644 --- a/src/easyscience/models/__init__.py +++ b/src/easyscience/models/__init__.py @@ -1,3 +1,5 @@ # SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -# © 2021-2025 Contributors to the EasyScience project = 2: s += [f'{self.coefficients[1].value}x'] if len(self.coefficients) >= 3: - s += [f'{c.value}x^{i+2}' for i, c in enumerate(self.coefficients[2:]) if c.value != 0] + s += [f'{c.value}x^{i + 2}' for i, c in enumerate(self.coefficients[2:]) if c.value != 0] s.reverse() s = ' + '.join(s) return 'Polynomial({}, {})'.format(self.name, s) - diff --git a/src/easyscience/utils/__init__.py b/src/easyscience/utils/__init__.py index de175a27..462d95af 100644 --- a/src/easyscience/utils/__init__.py +++ b/src/easyscience/utils/__init__.py @@ -1,3 +1,3 @@ # SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -# © 2021-2025 Contributors to the EasyScience project 0 else 0} ms\033[0m" - ) + time_logger.debug(f'\033[1;34;49mExecution time: {end_ if end_ > 0 else 0} ms\033[0m') return _time_it @@ -90,7 +88,7 @@ def deprecated(func): @functools.wraps(func) def new_func(*args, **kwargs): warnings.warn_explicit( - "Call to deprecated function {}.".format(func.__name__), + 'Call to deprecated function {}.'.format(func.__name__), category=DeprecationWarning, filename=func.__code__.co_filename, lineno=func.__code__.co_firstlineno + 1, diff --git a/src/easyscience/variable/__init__.py b/src/easyscience/variable/__init__.py index 8af6ee67..96094cab 100644 --- a/src/easyscience/variable/__init__.py +++ b/src/easyscience/variable/__init__.py @@ -1,5 +1,6 @@ from .descriptor_any_type import DescriptorAnyType from .descriptor_array import DescriptorArray +from .descriptor_base import DescriptorBase from .descriptor_bool import DescriptorBool from .descriptor_number import DescriptorNumber from .descriptor_str import DescriptorStr @@ -8,6 +9,7 @@ __all__ = [ DescriptorAnyType, DescriptorArray, + DescriptorBase, DescriptorBool, DescriptorNumber, DescriptorStr, diff --git a/src/easyscience/variable/descriptor_any_type.py b/src/easyscience/variable/descriptor_any_type.py index 93745d97..c00ccb01 100644 --- a/src/easyscience/variable/descriptor_any_type.py +++ b/src/easyscience/variable/descriptor_any_type.py @@ -16,7 +16,7 @@ class DescriptorAnyType(DescriptorBase): """ - A `Descriptor` for any type that does not fit the other Descriptors. Should be avoided when possible. + A `Descriptor` for any type that does not fit the other Descriptors. Should be avoided when possible. It was created to hold the symmetry operations used in the SpaceGroup class of EasyCrystallography. """ @@ -41,8 +41,8 @@ def __init__( .. note:: Undo/Redo functionality is implemented for the attributes `variance`, `error`, `unit` and `value`. """ - self._value=value - + self._value = value + super().__init__( name=name, unique_name=unique_name, @@ -55,7 +55,7 @@ def __init__( @property def value(self) -> numbers.Number: """ - Get the value. + Get the value. :return: Value of self. """ @@ -65,7 +65,7 @@ def value(self) -> numbers.Number: @property_stack def value(self, value: Union[list, np.ndarray]) -> None: """ - Set the value of self. + Set the value of self. :param value: New value for the DescriptorAnyType. """ @@ -90,4 +90,3 @@ def as_dict(self, skip: Optional[List[str]] = None) -> Dict[str, Any]: raw_dict = super().as_dict(skip=skip) raw_dict['value'] = self._value return raw_dict - diff --git a/src/easyscience/variable/descriptor_array.py b/src/easyscience/variable/descriptor_array.py index c7a1d8ca..9e9b8e6f 100644 --- a/src/easyscience/variable/descriptor_array.py +++ b/src/easyscience/variable/descriptor_array.py @@ -38,7 +38,7 @@ def __init__( url: Optional[str] = None, display_name: Optional[str] = None, parent: Optional[Any] = None, - dimensions: Optional[list] = None + dimensions: Optional[list] = None, ): """Constructor for the DescriptorArray class @@ -53,46 +53,42 @@ def __init__( param dimensions: List of dimensions to pass to scipp. Will be autogenerated if not supplied. .. note:: Undo/Redo functionality is implemented for the attributes `variance`, `error`, `unit` and `value`. """ - + if not isinstance(value, (list, np.ndarray)): - raise TypeError(f"{value=} must be a list or numpy array.") + raise TypeError(f'{value=} must be a list or numpy array.') if isinstance(value, list): value = np.array(value) # Convert to numpy array for consistent handling. value = np.astype(value, 'float') if variance is not None: if not isinstance(variance, (list, np.ndarray)): - raise TypeError(f"{variance=} must be a list or numpy array if provided.") + raise TypeError(f'{variance=} must be a list or numpy array if provided.') if isinstance(variance, list): variance = np.array(variance) # Convert to numpy array for consistent handling. if variance.shape != value.shape: - raise ValueError(f"{variance=} must have the same shape as {value=}.") + raise ValueError(f'{variance=} must have the same shape as {value=}.') if not np.all(variance >= 0): - raise ValueError(f"{variance=} must only contain non-negative values.") + raise ValueError(f'{variance=} must only contain non-negative values.') variance = np.astype(variance, 'float') - + if not isinstance(unit, sc.Unit) and not isinstance(unit, str): raise TypeError(f'{unit=} must be a scipp unit or a string representing a valid scipp unit') if dimensions is None: # Autogenerate dimensions if not supplied - dimensions = ['dim'+str(i) for i in range(len(value.shape))] + dimensions = ['dim' + str(i) for i in range(len(value.shape))] if not len(dimensions) == len(value.shape): - raise ValueError(f"Length of dimensions ({dimensions=}) does not match length of value {value=}.") + raise ValueError(f'Length of dimensions ({dimensions=}) does not match length of value {value=}.') self._dimensions = dimensions - try: # Convert value and variance to floats # for optimization everything must be floats - self._array = sc.array(dims=dimensions, - values=value, - unit=unit, - variances=variance) + self._array = sc.array(dims=dimensions, values=value, unit=unit, variances=variance) except Exception as message: raise UnitError(message) - # TODO: handle 1xn and nx1 arrays - + # TODO: handle 1xn and nx1 arrays + super().__init__( name=name, unique_name=unique_name, @@ -118,12 +114,14 @@ def from_scipp(cls, name: str, full_value: Variable, **kwargs) -> DescriptorArra """ if not isinstance(full_value, Variable): raise TypeError(f'{full_value=} must be a scipp array') - return cls(name=name, - value=full_value.values, - unit=full_value.unit, - variance=full_value.variances, - dimensions=full_value.dims, - **kwargs) + return cls( + name=name, + value=full_value.values, + unit=full_value.unit, + variance=full_value.variances, + dimensions=full_value.dims, + **kwargs, + ) @property def full_value(self) -> Variable: @@ -159,16 +157,16 @@ def value(self, value: Union[list, np.ndarray]) -> None: :param value: New value for the DescriptorArray, must be a list or numpy array. """ if not isinstance(value, (list, np.ndarray)): - raise TypeError(f"{value=} must be a list or numpy array.") + raise TypeError(f'{value=} must be a list or numpy array.') if isinstance(value, list): value = np.array(value) # Convert lists to numpy arrays for consistent handling. if value.shape != self._array.values.shape: - raise ValueError(f"{value=} must have the same shape as the existing array values.") - + raise ValueError(f'{value=} must have the same shape as the existing array values.') + # Values must be floats for optimization self._array.values = value.astype('float') - + @property def dimensions(self) -> list: """ @@ -186,14 +184,14 @@ def dimensions(self, dimensions: Union[list]) -> None: :param value: list of dimensions. """ if not isinstance(dimensions, (list, np.ndarray)): - raise TypeError(f"{dimensions=} must be a list or numpy array.") + raise TypeError(f'{dimensions=} must be a list or numpy array.') if len(dimensions) != len(self._dimensions): - raise ValueError(f"{dimensions=} must have the same shape as the existing dims") + raise ValueError(f'{dimensions=} must have the same shape as the existing dims') self._dimensions = dimensions # Also rename the dims of the scipp array - rename_dict = { old_dim: new_dim for (old_dim, new_dim) in zip(self.full_value.dims, dimensions) } + rename_dict = {old_dim: new_dim for (old_dim, new_dim) in zip(self.full_value.dims, dimensions)} renamed_array = self._array.rename_dims(rename_dict) self._array = renamed_array @@ -234,19 +232,19 @@ def variance(self, variance: Union[list, np.ndarray]) -> None: """ if variance is not None: if not isinstance(variance, (list, np.ndarray)): - raise TypeError(f"{variance=} must be a list or numpy array.") + raise TypeError(f'{variance=} must be a list or numpy array.') if isinstance(variance, list): variance = np.array(variance) # Convert lists to numpy arrays for consistent handling. if variance.shape != self._array.shape: - raise ValueError(f"{variance=} must have the same shape as the array values.") + raise ValueError(f'{variance=} must have the same shape as the array values.') if not np.all(variance >= 0): - raise ValueError(f"{variance=} must only contain non-negative values.") + raise ValueError(f'{variance=} must only contain non-negative values.') # Values must be floats for optimization self._array.variances = variance.astype('float') - + @property def error(self) -> Optional[np.ndarray]: """ @@ -268,15 +266,15 @@ def error(self, error: Union[list, np.ndarray]) -> None: """ if error is not None: if not isinstance(error, (list, np.ndarray)): - raise TypeError(f"{error=} must be a list or numpy array.") + raise TypeError(f'{error=} must be a list or numpy array.') if isinstance(error, list): error = np.array(error) # Convert lists to numpy arrays for consistent handling. if error.shape != self._array.values.shape: - raise ValueError(f"{error=} must have the same shape as the array values.") + raise ValueError(f'{error=} must have the same shape as the array values.') if not np.all(error >= 0): - raise ValueError(f"{error=} must only contain non-negative values.") + raise ValueError(f'{error=} must only contain non-negative values.') # Update variances as the square of the errors self._array.variances = error**2 @@ -300,7 +298,7 @@ def convert_unit(self, unit_str: str) -> None: try: new_array = self._array.to(unit=new_unit) except Exception as e: - raise UnitError(f"Failed to convert unit: {e}") from e + raise UnitError(f'Failed to convert unit: {e}') from e # Define the setter function for the undo stack def set_array(obj, scalar): @@ -308,7 +306,7 @@ def set_array(obj, scalar): # Push to undo stack self._global_object.stack.push( - PropertyStack(self, set_array, old_array, new_array, text=f"Convert unit to {unit_str}") + PropertyStack(self, set_array, old_array, new_array, text=f'Convert unit to {unit_str}') ) # Update the array @@ -330,32 +328,31 @@ def __repr__(self) -> str: # Summarize array values values_summary = np.array2string( - self._array.values, - precision=4, + self._array.values, + precision=4, threshold=10, # Show full array if <=10 elements, else summarize - edgeitems=3, # Show first and last 3 elements for large arrays + edgeitems=3, # Show first and last 3 elements for large arrays ) - string += f"values={values_summary}" + string += f'values={values_summary}' # Add errors if they exists if self._array.variances is not None: errors_summary = np.array2string( - self.error, - precision=4, - threshold=10, + self.error, + precision=4, + threshold=10, edgeitems=3, ) - string += f", errors={errors_summary}" + string += f', errors={errors_summary}' # Add unit obj_unit = str(self._array.unit) - if obj_unit and obj_unit != "dimensionless": - string += f", unit={obj_unit}" - - string += ">" - string=string.replace('\n', ',') - return string + if obj_unit and obj_unit != 'dimensionless': + string += f', unit={obj_unit}' + string += '>' + string = string.replace('\n', ',') + return string def as_dict(self, skip: Optional[List[str]] = None) -> Dict[str, Any]: """ @@ -368,11 +365,13 @@ def as_dict(self, skip: Optional[List[str]] = None) -> Dict[str, Any]: raw_dict['variance'] = self._array.variances raw_dict['dimensions'] = self._array.dims return raw_dict - - def _apply_operation(self, - other: Union[DescriptorArray, DescriptorNumber, list, numbers.Number], - operation: Callable, - units_must_match: bool = True) -> DescriptorArray: + + def _apply_operation( + self, + other: Union[DescriptorArray, DescriptorNumber, list, numbers.Number], + operation: Callable, + units_must_match: bool = True, + ) -> DescriptorArray: """ Perform element-wise operations with another DescriptorNumber, DescriptorArray, list, or number. @@ -383,42 +382,43 @@ def _apply_operation(self, """ if isinstance(other, numbers.Number): # Does not need to be dimensionless for multiplication and division - if self.unit not in [None, "dimensionless"] and units_must_match: - raise UnitError("Numbers can only be used together with dimensionless values") + if self.unit not in [None, 'dimensionless'] and units_must_match: + raise UnitError('Numbers can only be used together with dimensionless values') new_full_value = operation(self.full_value, other) elif isinstance(other, list): - if self.unit not in [None, "dimensionless"] and units_must_match: - raise UnitError("Operations with lists are only allowed for dimensionless values") - + if self.unit not in [None, 'dimensionless'] and units_must_match: + raise UnitError('Operations with lists are only allowed for dimensionless values') + # Ensure dimensions match if np.shape(other) != self._array.values.shape: - raise ValueError(f"Shape of {other=} must match the shape of DescriptorArray values") - + raise ValueError(f'Shape of {other=} must match the shape of DescriptorArray values') + other = sc.array(dims=self._array.dims, values=other) new_full_value = operation(self._array, other) # Let scipp handle operation for uncertainty propagation - + elif isinstance(other, DescriptorNumber): try: other_converted = other.__copy__() other_converted.convert_unit(self.unit) except UnitError: if units_must_match: - raise UnitError(f"Values with units {self.unit} and {other.unit} are not compatible") from None + raise UnitError(f'Values with units {self.unit} and {other.unit} are not compatible') from None # Operations with a DescriptorNumber that has a variance WILL introduce # correlations between the elements of the DescriptorArray. # See, https://content.iospress.com/articles/journal-of-neutron-research/jnr220049 # However, DescriptorArray does not consider the covariance between # elements of the array. Hence, the broadcasting is "manually" # performed to work around `scipp` and a warning raised to the end user. - if (self._array.variances is not None or other.variance is not None): - warn('Correlations introduced by this operation will not be considered.\ + if self._array.variances is not None or other.variance is not None: + warn( + 'Correlations introduced by this operation will not be considered.\ See https://content.iospress.com/articles/journal-of-neutron-research/jnr220049\ - for further details', UserWarning) + for further details', + UserWarning, + ) # Cheeky copy() of broadcasted scipp array to force scipp to perform the broadcast here - broadcasted = sc.broadcast(other_converted.full_value, - dims=self._array.dims, - shape=self._array.shape).copy() + broadcasted = sc.broadcast(other_converted.full_value, dims=self._array.dims, shape=self._array.shape).copy() new_full_value = operation(self.full_value, broadcasted) elif isinstance(other, DescriptorArray): @@ -427,12 +427,14 @@ def _apply_operation(self, other_converted.convert_unit(self.unit) except UnitError: if units_must_match: - raise UnitError(f"Values with units {self.unit} and {other.unit} are incompatible") from None + raise UnitError(f'Values with units {self.unit} and {other.unit} are incompatible') from None # Ensure dimensions match if self.full_value.dims != other_converted.full_value.dims: - raise ValueError(f"Dimensions of the DescriptorArrays do not match: " - f"{self.full_value.dims} vs {other_converted.full_value.dims}") + raise ValueError( + f'Dimensions of the DescriptorArrays do not match: ' + f'{self.full_value.dims} vs {other_converted.full_value.dims}' + ) new_full_value = operation(self.full_value, other_converted.full_value) @@ -443,14 +445,17 @@ def _apply_operation(self, descriptor_array.name = descriptor_array.unique_name return descriptor_array - def _rapply_operation(self, - other: Union[DescriptorArray, DescriptorNumber, list, numbers.Number], - operation: Callable, - units_must_match: bool = True) -> DescriptorArray: + def _rapply_operation( + self, + other: Union[DescriptorArray, DescriptorNumber, list, numbers.Number], + operation: Callable, + units_must_match: bool = True, + ) -> DescriptorArray: """ Handle reverse operations for DescriptorArrays, DescriptorNumbers, lists, and scalars. Ensures unit compatibility when `other` is a DescriptorNumber. """ + def reversed_operation(a, b): return operation(b, a) @@ -465,7 +470,7 @@ def reversed_operation(a, b): # the units for mul/div, but if the conversion # fails it's no big deal. if units_must_match: - raise UnitError(f"Values with units {self.unit} and {other.unit} are incompatible") from None + raise UnitError(f'Values with units {self.unit} and {other.unit} are incompatible') from None result = self._apply_operation(other, reversed_operation, units_must_match) # Revert `self` to its original unit self.convert_unit(original_unit) @@ -473,8 +478,8 @@ def reversed_operation(a, b): else: # Delegate to operation to __self__ for other types (e.g., list, scalar) return self._apply_operation(other, reversed_operation, units_must_match) - - def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): + + def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): """ DescriptorArray does not generally support Numpy array functions. For example, `np.argwhere(descriptorArray: DescriptorArray)` should fail. @@ -489,7 +494,7 @@ def __array_function__(self, func, types, args, kwargs): Modify this function if you want to add such functionality. """ return NotImplemented - + def __add__(self, other: Union[DescriptorArray, DescriptorNumber, list, numbers.Number]) -> DescriptorArray: """ Perform element-wise addition with another DescriptorNumber, DescriptorArray, list, or number. @@ -506,7 +511,7 @@ def __radd__(self, other: Union[DescriptorNumber, list, numbers.Number]) -> Desc Ensures unit compatibility when `other` is a DescriptorNumber. """ return self._rapply_operation(other, operator.add) - + def __sub__(self, other: Union[DescriptorArray, list, np.ndarray, numbers.Number]) -> DescriptorArray: """ Perform element-wise subtraction with another DescriptorArray, list, or number. @@ -525,7 +530,7 @@ def __sub__(self, other: Union[DescriptorArray, list, np.ndarray, numbers.Number return self.__add__(value) else: return NotImplemented - + def __rsub__(self, other: Union[DescriptorNumber, list, numbers.Number]) -> DescriptorArray: """ Perform element-wise subtraction with another DescriptorNumber, list, or number. @@ -543,7 +548,7 @@ def __rsub__(self, other: Union[DescriptorNumber, list, numbers.Number]) -> Desc return -(self.__radd__(value)) else: return NotImplemented - + def __mul__(self, other: Union[DescriptorArray, DescriptorNumber, list, numbers.Number]) -> DescriptorArray: """ Perform element-wise multiplication with another DescriptorNumber, DescriptorArray, list, or number. @@ -555,7 +560,7 @@ def __mul__(self, other: Union[DescriptorArray, DescriptorNumber, list, numbers. if not isinstance(other, (DescriptorArray, DescriptorNumber, list, numbers.Number)): return NotImplemented return self._apply_operation(other, operator.mul, units_must_match=False) - + def __rmul__(self, other: Union[DescriptorNumber, list, numbers.Number]) -> DescriptorArray: """ Handle reverse multiplication for DescriptorNumbers, lists, and scalars. @@ -564,7 +569,7 @@ def __rmul__(self, other: Union[DescriptorNumber, list, numbers.Number]) -> Desc if not isinstance(other, (DescriptorNumber, list, numbers.Number)): return NotImplemented return self._rapply_operation(other, operator.mul, units_must_match=False) - + def __truediv__(self, other: Union[DescriptorArray, DescriptorNumber, list, numbers.Number]) -> DescriptorArray: """ Perform element-wise division with another DescriptorNumber, DescriptorArray, list, or number. @@ -586,7 +591,7 @@ def __truediv__(self, other: Union[DescriptorArray, DescriptorNumber, list, numb if np.any(original_other == 0): raise ZeroDivisionError('Cannot divide by zero') return self._apply_operation(other, operator.truediv, units_must_match=False) - + def __rtruediv__(self, other: Union[DescriptorNumber, list, numbers.Number]) -> DescriptorArray: """ Handle reverse division for DescriptorNumbers, lists, and scalars. @@ -597,12 +602,12 @@ def __rtruediv__(self, other: Union[DescriptorNumber, list, numbers.Number]) -> if np.any(self.full_value.values == 0): raise ZeroDivisionError('Cannot divide by zero') - + # First use __div__ to compute `self / other` # but first converting to the units of other inverse_result = self._rapply_operation(other, operator.truediv, units_must_match=False) return inverse_result - + def __pow__(self, other: Union[DescriptorNumber, numbers.Number]) -> DescriptorArray: """ Perform element-wise exponentiation with another DescriptorNumber or number. @@ -677,11 +682,11 @@ def __delitem__(self, a): This should fail, since scipp does not support __delitem__. """ return self.full_value.__delitem__(a) - + def __setitem__(self, a, b: Union[numbers.Number, list, DescriptorNumber, DescriptorArray]): """ __setitem via slice is not allowed, since we currently do not give back a - view to the DescriptorArray upon calling __getitem__. + view to the DescriptorArray upon calling __getitem__. """ raise AttributeError( f'{self.__class__.__name__} cannot be edited via slicing. Edit the underlying scipp\ @@ -689,9 +694,9 @@ def __setitem__(self, a, b: Union[numbers.Number, list, DescriptorNumber, Descri new {self.__class__.__name__}.' ) - def trace(self, - dimension1: Optional[str] = None, - dimension2: Optional[str]= None) -> Union[DescriptorArray, DescriptorNumber]: + def trace( + self, dimension1: Optional[str] = None, dimension2: Optional[str] = None + ) -> Union[DescriptorArray, DescriptorNumber]: """ Computes the trace over the descriptor array. The submatrix defined `dimension1` and `dimension2` must be square. For a rank `k` tensor, the trace will run over the firs two dimensions, resulting in a rank `k-2` tensor. @@ -701,11 +706,11 @@ def trace(self, """ if (dimension1 is not None and dimension2 is None) or (dimension1 is None and dimension2 is not None): raise ValueError('Either both or none of `dimension1` and `dimension2` must be set.') - + if dimension1 is not None and dimension2 is not None: if dimension1 == dimension2: raise ValueError(f'`{dimension1=}` and `{dimension2=}` must be different.') - + axes = [] for dim in (dimension1, dimension2): if dim not in self.dimensions: @@ -741,7 +746,7 @@ def sum(self, dim: Optional[Union[str, list]] = None) -> Union[DescriptorArray, :param dim: The dim(s) in the scipp array to sum over. If `None`, will sum over all dims. """ new_full_value = self.full_value.sum(dim=dim) - + # If fully reduced the result will be a DescriptorNumber, # otherwise a DescriptorArray if dim is None: @@ -752,7 +757,7 @@ def sum(self, dim: Optional[Union[str, list]] = None) -> Union[DescriptorArray, descriptor = constructor(name=self.name, full_value=new_full_value) descriptor.name = descriptor.unique_name return descriptor - + # This is to be implemented at a later time # def __matmul__(self, other: [DescriptorArray, list]) -> DescriptorArray: # """ @@ -773,11 +778,10 @@ def sum(self, dim: Optional[Union[str, list]] = None) -> Union[DescriptorArray, # # Dimensions must match for matrix multiplication # if shape[0] != self._array.values.shape[-1]: # raise ValueError(f"Last dimension of {other=} must match the first dimension of DescriptorArray values") - # + # # other = sc.array(dims=self._array.dims, values=other) # new_full_value = operation(self._array, other) # Let scipp handle operation for uncertainty propagation - def _base_unit(self) -> str: """ Returns the base unit of the current array. diff --git a/src/easyscience/variable/descriptor_base.py b/src/easyscience/variable/descriptor_base.py index ab30a9f7..4bb54e4a 100644 --- a/src/easyscience/variable/descriptor_base.py +++ b/src/easyscience/variable/descriptor_base.py @@ -23,11 +23,11 @@ class DescriptorBase(SerializerComponent, metaclass=abc.ABCMeta): A `Descriptor` is typically something which describes part of a model and is non-fittable and generally changes the state of an object. """ + _global_object = global_object # Used by serializer _REDIRECT = {'parent': None} - def __init__( self, name: str, diff --git a/src/easyscience/variable/descriptor_bool.py b/src/easyscience/variable/descriptor_bool.py index 23869172..72c41cd8 100644 --- a/src/easyscience/variable/descriptor_bool.py +++ b/src/easyscience/variable/descriptor_bool.py @@ -12,6 +12,7 @@ class DescriptorBool(DescriptorBase): """ A `Descriptor` for boolean values. """ + def __init__( self, name: str, diff --git a/src/easyscience/variable/descriptor_number.py b/src/easyscience/variable/descriptor_number.py index 66f0e7ab..7d35ca0b 100644 --- a/src/easyscience/variable/descriptor_number.py +++ b/src/easyscience/variable/descriptor_number.py @@ -1,6 +1,7 @@ from __future__ import annotations import numbers +import uuid from typing import Any from typing import Dict from typing import List @@ -27,6 +28,7 @@ def notify_observers(func): :param func: Function to be decorated :return: Decorated function """ + def wrapper(self, *args, **kwargs): result = func(self, *args, **kwargs) self._notify_observers() @@ -34,6 +36,7 @@ def wrapper(self, *args, **kwargs): return wrapper + class DescriptorNumber(DescriptorBase): """ A `Descriptor` for Number values with units. The internal representation is a scipp scalar. @@ -50,6 +53,7 @@ def __init__( url: Optional[str] = None, display_name: Optional[str] = None, parent: Optional[Any] = None, + **kwargs: Any, # Additional keyword arguments (used for (de)serialization) ): """Constructor for the DescriptorNumber class @@ -65,6 +69,10 @@ def __init__( """ self._observers: List[DescriptorNumber] = [] + # Extract serializer_id if provided during deserialization + if '__serializer_id' in kwargs: + self.__serializer_id = kwargs.pop('__serializer_id') + if not isinstance(value, numbers.Number) or isinstance(value, bool): raise TypeError(f'{value=} must be a number') if variance is not None: @@ -92,7 +100,6 @@ def __init__( if self.unit is not None: self._convert_unit(self._base_unit()) - @classmethod def from_scipp(cls, name: str, full_value: Variable, **kwargs) -> DescriptorNumber: """ @@ -112,10 +119,14 @@ def from_scipp(cls, name: str, full_value: Variable, **kwargs) -> DescriptorNumb def _attach_observer(self, observer: DescriptorNumber) -> None: """Attach an observer to the descriptor.""" self._observers.append(observer) + if not hasattr(self, '_DescriptorNumber__serializer_id'): + self.__serializer_id = str(uuid.uuid4()) def _detach_observer(self, observer: DescriptorNumber) -> None: """Detach an observer from the descriptor.""" self._observers.remove(observer) + if not self._observers: + del self.__serializer_id def _notify_observers(self) -> None: """Notify all observers of a change.""" @@ -128,9 +139,11 @@ def _validate_dependencies(self, origin=None) -> None: :param origin: Unique_name of the origin of this validation check. Used to avoid cyclic depenencies. """ if origin == self.unique_name: - raise RuntimeError('\n Cyclic dependency detected!\n' + - f'An update of {self.unique_name} leads to it updating itself.\n' + - 'Please check your dependencies.') + raise RuntimeError( + '\n Cyclic dependency detected!\n' + + f'An update of {self.unique_name} leads to it updating itself.\n' + + 'Please check your dependencies.' + ) if origin is None: origin = self.unique_name for observer in self._observers: @@ -266,7 +279,7 @@ def _convert_unit(self, unit_str: str) -> None: try: new_scalar = self._scalar.to(unit=new_unit) except Exception as e: - raise UnitError(f"Failed to convert unit: {e}") from e + raise UnitError(f'Failed to convert unit: {e}') from e # Define the setter function for the undo stack def set_scalar(obj, scalar): @@ -274,7 +287,7 @@ def set_scalar(obj, scalar): # Push to undo stack self._global_object.stack.push( - PropertyStack(self, set_scalar, old_scalar, new_scalar, text=f"Convert unit to {unit_str}") + PropertyStack(self, set_scalar, old_scalar, new_scalar, text=f'Convert unit to {unit_str}') ) # Update the scalar @@ -299,7 +312,7 @@ def __repr__(self) -> str: string = '<' string += self.__class__.__name__ + ' ' string += f"'{self._name}': " - if np.abs(self._scalar.value)>1e4 or (np.abs(self._scalar.value)<1e-4 and self._scalar.value != 0): + if np.abs(self._scalar.value) > 1e4 or (np.abs(self._scalar.value) < 1e-4 and self._scalar.value != 0): # Use scientific notation for large or small values string += f'{self._scalar.value:.3e}' if self.variance: @@ -323,6 +336,8 @@ def as_dict(self, skip: Optional[List[str]] = None) -> Dict[str, Any]: raw_dict['value'] = self._scalar.value raw_dict['unit'] = str(self._scalar.unit) raw_dict['variance'] = self._scalar.variance + if hasattr(self, '_DescriptorNumber__serializer_id'): + raw_dict['__serializer_id'] = self.__serializer_id return raw_dict def __add__(self, other: Union[DescriptorNumber, numbers.Number]) -> DescriptorNumber: diff --git a/src/easyscience/variable/parameter.py b/src/easyscience/variable/parameter.py index c459946c..55787ad4 100644 --- a/src/easyscience/variable/parameter.py +++ b/src/easyscience/variable/parameter.py @@ -11,6 +11,7 @@ import weakref from typing import Any from typing import Dict +from typing import List from typing import Optional from typing import Union @@ -33,7 +34,8 @@ class Parameter(DescriptorNumber): """ # Used by serializer - _REDIRECT = DescriptorNumber._REDIRECT + # We copy the parent's _REDIRECT and modify it to avoid altering the parent's class dict + _REDIRECT = DescriptorNumber._REDIRECT.copy() _REDIRECT['callback'] = None def __init__( @@ -51,6 +53,7 @@ def __init__( display_name: Optional[str] = None, callback: property = property(), parent: Optional[Any] = None, + **kwargs: Any, # Additional keyword arguments (used for (de)serialization) ): """ This class is an extension of a `DescriptorNumber`. Where the descriptor was for static @@ -69,9 +72,14 @@ def __init__( :param display_name: The name of the object as it should be displayed :param parent: The object which is the parent to this one - .. note:: + Note: Undo/Redo functionality is implemented for the attributes `value`, `variance`, `error`, `min`, `max`, `bounds`, `fixed`, `unit` """ # noqa: E501 + # Extract and ignore serialization-specific fields from kwargs + kwargs.pop('_dependency_string', None) + kwargs.pop('_dependency_map_serializer_ids', None) + kwargs.pop('_independent', None) + if not isinstance(min, numbers.Number): raise TypeError('`min` must be a number') if not isinstance(max, numbers.Number): @@ -87,7 +95,7 @@ def __init__( if not isinstance(fixed, bool): raise TypeError('`fixed` must be either True or False') self._independent = True - self._fixed = fixed # For fitting, but must be initialized before super().__init__ + self._fixed = fixed # For fitting, but must be initialized before super().__init__ self._min = sc.scalar(float(min), unit=unit) self._max = sc.scalar(float(max), unit=unit) @@ -101,6 +109,7 @@ def __init__( url=url, display_name=display_name, parent=parent, + **kwargs, # Additional keyword arguments (used for (de)serialization) ) self._callback = callback # Callback is used by interface to link to model @@ -111,17 +120,23 @@ def __init__( self._initial_scalar = copy.deepcopy(self._scalar) @classmethod - def from_dependency(cls, name: str, dependency_expression: str, dependency_map: Optional[dict] = None, **kwargs) -> Parameter: # noqa: E501 + def from_dependency( + cls, name: str, dependency_expression: str, dependency_map: Optional[dict] = None, **kwargs + ) -> Parameter: # noqa: E501 """ Create a dependent Parameter directly from a dependency expression. - + :param name: The name of the parameter :param dependency_expression: The dependency expression to evaluate. This should be a string which can be evaluated by the ASTEval interpreter. - :param dependency_map: A dictionary of dependency expression symbol name and dependency object pairs. This is inserted into the asteval interpreter to resolve dependencies. + :param dependency_map: A dictionary of dependency expression symbol name and dependency object pairs. This is inserted into the asteval interpreter to resolve dependencies. :param kwargs: Additional keyword arguments to pass to the Parameter constructor. :return: A new dependent Parameter object. """ # noqa: E501 - parameter = cls(name=name, value=0.0, unit='', variance=0.0, min=-np.inf, max=np.inf, **kwargs) + # Set default values for required parameters for the constructor, they get overwritten by the dependency anyways + default_kwargs = {'value': 0.0, 'unit': '', 'variance': 0.0, 'min': -np.inf, 'max': np.inf} + # Update with user-provided kwargs, to avoid errors. + default_kwargs.update(kwargs) + parameter = cls(name=name, **default_kwargs) parameter.make_dependent_on(dependency_expression=dependency_expression, dependency_map=dependency_map) return parameter @@ -135,8 +150,12 @@ def _update(self) -> None: self._scalar.value = temporary_parameter.value self._scalar.unit = temporary_parameter.unit self._scalar.variance = temporary_parameter.variance - self._min.value = temporary_parameter.min if isinstance(temporary_parameter, Parameter) else temporary_parameter.value # noqa: E501 - self._max.value = temporary_parameter.max if isinstance(temporary_parameter, Parameter) else temporary_parameter.value # noqa: E501 + self._min.value = ( + temporary_parameter.min if isinstance(temporary_parameter, Parameter) else temporary_parameter.value + ) # noqa: E501 + self._max.value = ( + temporary_parameter.max if isinstance(temporary_parameter, Parameter) else temporary_parameter.value + ) # noqa: E501 self._min.unit = temporary_parameter.unit self._max.unit = temporary_parameter.unit self._notify_observers() @@ -153,21 +172,36 @@ def make_dependent_on(self, dependency_expression: str, dependency_map: Optional I.e. the values are the actual objects, whereas the keys are how they are represented in the dependency expression. The dependency map is not needed if the dependency expression uses the unique names of the parameters. - Unique names in dependency expressions are defined by quotes, e.g. 'Parameter_0' or "Parameter_0" depending on the quotes used for the expression. + Unique names in dependency expressions are defined by quotes, e.g. 'Parameter_0' or "Parameter_0" depending on + the quotes used for the expression. + + :param dependency_expression: + The dependency expression to evaluate. This should be a string which + can be evaluated by a python interpreter. + + :param dependency_map: + A dictionary of dependency expression symbol name and dependency object pairs. + This is inserted into the asteval interpreter to resolve dependencies. - :param dependency_expression: The dependency expression to evaluate. This should be a string which can be evaluated by a python interpreter. - :param dependency_map: A dictionary of dependency expression symbol name and dependency object pairs. This is inserted into the asteval interpreter to resolve dependencies. """ # noqa: E501 if not isinstance(dependency_expression, str): raise TypeError('`dependency_expression` must be a string representing a valid dependency expression.') if not (isinstance(dependency_map, dict) or dependency_map is None): - raise TypeError('`dependency_map` must be a dictionary of dependencies and their corresponding names in the dependecy expression.') # noqa: E501 + raise TypeError( + '`dependency_map` must be a dictionary of dependencies and their' + 'corresponding names in the dependecy expression.' + ) # noqa: E501 if isinstance(dependency_map, dict): for key, value in dependency_map.items(): if not isinstance(key, str): - raise TypeError('`dependency_map` keys must be strings representing the names of the dependencies in the dependency expression.') # noqa: E501 + raise TypeError( + '`dependency_map` keys must be strings representing the names of' + 'the dependencies in the dependency expression.' + ) # noqa: E501 if not isinstance(value, DescriptorNumber): - raise TypeError(f'`dependency_map` values must be DescriptorNumbers or Parameters. Got {type(value)} for {key}.') # noqa: E501 + raise TypeError( + f'`dependency_map` values must be DescriptorNumbers or Parameters. Got {type(value)} for {key}.' + ) # noqa: E501 # If we're overwriting the dependency, store the old attributes # in case we need to revert back to the old dependency @@ -186,12 +220,26 @@ def make_dependent_on(self, dependency_expression: str, dependency_map: Optional self._dependency_string = dependency_expression self._dependency_map = dependency_map if dependency_map is not None else {} # List of allowed python constructs for the asteval interpreter - asteval_config = {'import': False, 'importfrom': False, 'assert': False, - 'augassign': False, 'delete': False, 'if': True, - 'ifexp': True, 'for': False, 'formattedvalue': False, - 'functiondef': False, 'print': False, 'raise': False, - 'listcomp': False, 'dictcomp': False, 'setcomp': False, - 'try': False, 'while': False, 'with': False} + asteval_config = { + 'import': False, + 'importfrom': False, + 'assert': False, + 'augassign': False, + 'delete': False, + 'if': True, + 'ifexp': True, + 'for': False, + 'formattedvalue': False, + 'functiondef': False, + 'print': False, + 'raise': False, + 'listcomp': False, + 'dictcomp': False, + 'setcomp': False, + 'try': False, + 'while': False, + 'with': False, + } self._dependency_interpreter = Interpreter(config=asteval_config) # Process the dependency expression for unique names @@ -202,26 +250,37 @@ def make_dependent_on(self, dependency_expression: str, dependency_map: Optional raise error for key, value in self._dependency_map.items(): - self._dependency_interpreter.symtable[key] = value - self._dependency_interpreter.readonly_symbols.add(key) # Dont allow overwriting of the dependencies in the dependency expression # noqa: E501 - value._attach_observer(self) + self._dependency_interpreter.symtable[key] = value + self._dependency_interpreter.readonly_symbols.add( + key + ) # Dont allow overwriting of the dependencies in the dependency expression # noqa: E501 + value._attach_observer(self) # Check the dependency expression for errors try: dependency_result = self._dependency_interpreter.eval(self._clean_dependency_string, raise_errors=True) except NameError as message: self._revert_dependency() - raise NameError('\nUnknown name encountered in dependecy expression:'+ - '\n'+'\n'.join(str(message).split("\n")[1:])+ - '\nPlease check your expression or add the name to the `dependency_map`') from None + raise NameError( + '\nUnknown name encountered in dependecy expression:' + + '\n' + + '\n'.join(str(message).split('\n')[1:]) + + '\nPlease check your expression or add the name to the `dependency_map`' + ) from None except Exception as message: self._revert_dependency() - raise SyntaxError('\nError encountered in dependecy expression:'+ - '\n'+'\n'.join(str(message).split("\n")[1:])+ - '\nPlease check your expression') from None + raise SyntaxError( + '\nError encountered in dependecy expression:' + + '\n' + + '\n'.join(str(message).split('\n')[1:]) + + '\nPlease check your expression' + ) from None if not isinstance(dependency_result, DescriptorNumber): error_string = self._dependency_string self._revert_dependency() - raise TypeError(f'The dependency expression: "{error_string}" returned a {type(dependency_result)}, it should return a Parameter or DescriptorNumber.') # noqa: E501 + raise TypeError( + f'The dependency expression: "{error_string}" returned a {type(dependency_result)},' + 'it should return a Parameter or DescriptorNumber.' + ) # noqa: E501 # Check for cyclic dependencies try: self._validate_dependencies() @@ -258,10 +317,12 @@ def independent(self) -> bool: :return: True = independent, False = dependent """ return self._independent - + @independent.setter def independent(self, value: bool) -> None: - raise AttributeError('This property is read-only. Use `make_independent` and `make_dependent_on` to change the state of the parameter.') # noqa: E501 + raise AttributeError( + 'This property is read-only. Use `make_independent` and `make_dependent_on` to change the state of the parameter.' + ) # noqa: E501 @property def dependency_expression(self) -> str: @@ -274,10 +335,12 @@ def dependency_expression(self) -> str: return self._dependency_string else: raise AttributeError('This parameter is independent. It has no dependency expression.') - + @dependency_expression.setter def dependency_expression(self, new_expression: str) -> None: - raise AttributeError('Dependency expression is read-only. Use `make_dependent_on` to change the dependency expression.') # noqa: E501 + raise AttributeError( + 'Dependency expression is read-only. Use `make_dependent_on` to change the dependency expression.' + ) # noqa: E501 @property def dependency_map(self) -> Dict[str, DescriptorNumber]: @@ -290,7 +353,7 @@ def dependency_map(self) -> Dict[str, DescriptorNumber]: return self._dependency_map else: raise AttributeError('This parameter is independent. It has no dependency map.') - + @dependency_map.setter def dependency_map(self, new_map: Dict[str, DescriptorNumber]) -> None: raise AttributeError('Dependency map is read-only. Use `make_dependent_on` to change the dependency map.') @@ -312,10 +375,6 @@ def full_value(self) -> Variable: :return: Value of self with unit and variance. """ - if self._callback.fget is not None: - scalar = self._callback.fget() - if scalar != self._scalar: - self._scalar = scalar return self._scalar @full_value.setter @@ -348,7 +407,7 @@ def value(self, value: numbers.Number) -> None: if self._independent: if not isinstance(value, numbers.Number): raise TypeError(f'{value=} must be a number') - + value = float(value) if value < self._min.value: value = self._min.value @@ -363,7 +422,7 @@ def value(self, value: numbers.Number) -> None: # Notify observers of the change self._notify_observers() else: - raise AttributeError("This is a dependent parameter, its value cannot be set directly.") + raise AttributeError('This is a dependent parameter, its value cannot be set directly.') @DescriptorNumber.variance.setter def variance(self, variance_float: float) -> None: @@ -375,7 +434,7 @@ def variance(self, variance_float: float) -> None: if self._independent: DescriptorNumber.variance.fset(self, variance_float) else: - raise AttributeError("This is a dependent parameter, its variance cannot be set directly.") + raise AttributeError('This is a dependent parameter, its variance cannot be set directly.') @DescriptorNumber.error.setter def error(self, value: float) -> None: @@ -387,7 +446,7 @@ def error(self, value: float) -> None: if self._independent: DescriptorNumber.error.fset(self, value) else: - raise AttributeError("This is a dependent parameter, its error cannot be set directly.") + raise AttributeError('This is a dependent parameter, its error cannot be set directly.') def _convert_unit(self, unit_str: str) -> None: """ @@ -441,7 +500,7 @@ def min(self, min_value: numbers.Number) -> None: raise ValueError(f'The current value ({self.value}) is smaller than the desired min value ({min_value}).') self._notify_observers() else: - raise AttributeError("This is a dependent parameter, its minimum value cannot be set directly.") + raise AttributeError('This is a dependent parameter, its minimum value cannot be set directly.') @property def max(self) -> numbers.Number: @@ -473,7 +532,7 @@ def max(self, max_value: numbers.Number) -> None: raise ValueError(f'The current value ({self.value}) is greater than the desired max value ({max_value}).') self._notify_observers() else: - raise AttributeError("This is a dependent parameter, its maximum value cannot be set directly.") + raise AttributeError('This is a dependent parameter, its maximum value cannot be set directly.') @property def fixed(self) -> bool: @@ -501,7 +560,7 @@ def fixed(self, fixed: bool) -> None: if self._global_object.stack.enabled: # Remove the recorded change from the stack global_object.stack.pop() - raise AttributeError("This is a dependent parameter, dependent parameters cannot be fixed.") + raise AttributeError('This is a dependent parameter, dependent parameters cannot be fixed.') # Is this alias really needed? @property @@ -512,6 +571,25 @@ def free(self) -> bool: def free(self, value: bool) -> None: self.fixed = not value + def as_dict(self, skip: Optional[List[str]] = None) -> Dict[str, Any]: + """Overwrite the as_dict method to handle dependency information.""" + raw_dict = super().as_dict(skip=skip) + + # Add dependency information for dependent parameters + if not self._independent: + # Save the dependency expression + raw_dict['_dependency_string'] = self._clean_dependency_string + + # Mark that this parameter is dependent + raw_dict['_independent'] = self._independent + + # Convert dependency_map to use serializer_ids + raw_dict['_dependency_map_serializer_ids'] = {} + for key, obj in self._dependency_map.items(): + raw_dict['_dependency_map_serializer_ids'][key] = obj._DescriptorNumber__serializer_id + + return raw_dict + def _revert_dependency(self, skip_detach=False) -> None: """ Revert the dependency to the old dependency. This is used when an error is raised during setting the dependency. @@ -536,24 +614,54 @@ def _process_dependency_unique_names(self, dependency_expression: str): :param dependency_expression: The dependency expression to be evaluated """ # Get the unique_names from the expression string regardless of the quotes used - inputted_unique_names = re.findall("(\'.+?\')", dependency_expression) - inputted_unique_names += re.findall('(\".+?\")', dependency_expression) + inputted_unique_names = re.findall("('.+?')", dependency_expression) + inputted_unique_names += re.findall('(".+?")', dependency_expression) clean_dependency_string = dependency_expression existing_unique_names = self._global_object.map.vertices() # Add the unique names of the parameters to the ASTEVAL interpreter for name in inputted_unique_names: - stripped_name = name.strip("'\"") + stripped_name = name.strip('\'"') if stripped_name not in existing_unique_names: - raise ValueError(f'A Parameter with unique_name {stripped_name} does not exist. Please check your dependency expression.') # noqa: E501 + raise ValueError( + f'A Parameter with unique_name {stripped_name} does not exist. Please check your dependency expression.' + ) # noqa: E501 dependent_parameter = self._global_object.map.get_item_by_key(stripped_name) if isinstance(dependent_parameter, DescriptorNumber): - self._dependency_map['__'+stripped_name+'__'] = dependent_parameter - clean_dependency_string = clean_dependency_string.replace(name, '__'+stripped_name+'__') + self._dependency_map['__' + stripped_name + '__'] = dependent_parameter + clean_dependency_string = clean_dependency_string.replace(name, '__' + stripped_name + '__') else: - raise ValueError(f'The object with unique_name {stripped_name} is not a Parameter or DescriptorNumber. Please check your dependency expression.') # noqa: E501 + raise ValueError( + f'The object with unique_name {stripped_name} is not a Parameter or DescriptorNumber. ' + 'Please check your dependency expression.' + ) # noqa: E501 self._clean_dependency_string = clean_dependency_string + @classmethod + def from_dict(cls, obj_dict: dict) -> 'Parameter': + """ + Custom deserialization to handle parameter dependencies. + Override the parent method to handle dependency information. + """ + # Extract dependency information before creating the parameter + raw_dict = obj_dict.copy() # Don't modify the original dict + dependency_string = raw_dict.pop('_dependency_string', None) + dependency_map_serializer_ids = raw_dict.pop('_dependency_map_serializer_ids', None) + is_independent = raw_dict.pop('_independent', True) + # Note: Keep _serializer_id in the dict so it gets passed to __init__ + + # Create the parameter using the base class method (serializer_id is now handled in __init__) + param = super().from_dict(raw_dict) + + # Store dependency information for later resolution + if not is_independent: + param._pending_dependency_string = dependency_string + param._pending_dependency_map_serializer_ids = dependency_map_serializer_ids + # Keep parameter as independent initially - will be made dependent after all objects are loaded + param._independent = True + + return param + def __copy__(self) -> Parameter: new_obj = super().__copy__() new_obj._callback = property() @@ -881,9 +989,48 @@ def __abs__(self) -> Parameter: new_full_value = abs(self.full_value) combinations = [abs(self.min), abs(self.max)] if self.min < 0 and self.max > 0: - combinations.append(0) + combinations.append(0.0) min_value = min(combinations) max_value = max(combinations) parameter = Parameter.from_scipp(name=self.name, full_value=new_full_value, min=min_value, max=max_value) parameter.name = parameter.unique_name return parameter + + def resolve_pending_dependencies(self) -> None: + """Resolve pending dependencies after deserialization. + + This method should be called after all parameters have been deserialized + to establish dependency relationships using serializer_ids. + """ + if hasattr(self, '_pending_dependency_string'): + dependency_string = self._pending_dependency_string + dependency_map = {} + + if hasattr(self, '_pending_dependency_map_serializer_ids'): + dependency_map_serializer_ids = self._pending_dependency_map_serializer_ids + + # Build dependency_map by looking up objects by serializer_id + for key, serializer_id in dependency_map_serializer_ids.items(): + dep_obj = self._find_parameter_by_serializer_id(serializer_id) + if dep_obj is not None: + dependency_map[key] = dep_obj + else: + raise ValueError(f"Cannot find parameter with serializer_id '{serializer_id}'") + + # Establish the dependency relationship + try: + self.make_dependent_on(dependency_expression=dependency_string, dependency_map=dependency_map) + except Exception as e: + raise ValueError(f"Error establishing dependency '{dependency_string}': {e}") + + # Clean up temporary attributes + delattr(self, '_pending_dependency_string') + delattr(self, '_pending_dependency_map_serializer_ids') + + def _find_parameter_by_serializer_id(self, serializer_id: str) -> Optional['DescriptorNumber']: + """Find a parameter by its serializer_id from all parameters in the global map.""" + for obj in self._global_object.map._store.values(): + if isinstance(obj, DescriptorNumber) and hasattr(obj, '_DescriptorNumber__serializer_id'): + if obj._DescriptorNumber__serializer_id == serializer_id: + return obj + return None diff --git a/src/easyscience/variable/parameter_dependency_resolver.py b/src/easyscience/variable/parameter_dependency_resolver.py new file mode 100644 index 00000000..e6b7cbd6 --- /dev/null +++ b/src/easyscience/variable/parameter_dependency_resolver.py @@ -0,0 +1,147 @@ +# SPDX-FileCopyrightText: 2025 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +# © 2021-2025 Contributors to the EasyScience project None: + """ + Recursively find all Parameter objects in an object hierarchy and resolve their pending dependencies. + + This function should be called after deserializing a complex object that contains Parameters + with dependencies to ensure all dependency relationships are properly established. + + :param obj: The object to search for Parameters (can be a single Parameter, list, dict, or complex object) + """ + + def _collect_parameters(item: Any, parameters: List[Parameter]) -> None: + """Recursively collect all Parameter objects from an item.""" + if isinstance(item, Parameter): + parameters.append(item) + elif isinstance(item, dict): + for value in item.values(): + _collect_parameters(value, parameters) + elif isinstance(item, (list, tuple)): + for element in item: + _collect_parameters(element, parameters) + elif hasattr(item, '__dict__'): + # Check instance attributes + for attr_name, attr_value in item.__dict__.items(): + if not attr_name.startswith('_'): # Skip private attributes + _collect_parameters(attr_value, parameters) + + # Check class properties (descriptors like Parameter instances) + for attr_name in dir(type(item)): + if not attr_name.startswith('_'): # Skip private attributes + class_attr = getattr(type(item), attr_name, None) + if isinstance(class_attr, property): + try: + attr_value = getattr(item, attr_name) + _collect_parameters(attr_value, parameters) + except (AttributeError, Exception): + # log the exception + print(f"Error accessing property '{attr_name}' of {item}") + # Skip properties that can't be accessed + continue + + # Collect all parameters + all_parameters = [] + _collect_parameters(obj, all_parameters) + + # Resolve dependencies for all parameters that have pending dependencies + resolved_count = 0 + error_count = 0 + errors = [] + + for param in all_parameters: + if hasattr(param, '_pending_dependency_string'): + try: + param.resolve_pending_dependencies() + resolved_count += 1 + except Exception as e: + error_count += 1 + serializer_id = getattr(param, '_DescriptorNumber__serializer_id', 'unknown') + errors.append( + f"Failed to resolve dependencies for parameter '{param.name}'" + f" (unique_name: '{param.unique_name}', serializer_id: '{serializer_id}'): {e}" + ) + + # Report results + if resolved_count > 0: + print(f'Successfully resolved dependencies for {resolved_count} parameter(s).') + + if error_count > 0: + error_message = f'Failed to resolve dependencies for {error_count} parameter(s):\n' + '\n'.join(errors) + raise ValueError(error_message) + + +def get_parameters_with_pending_dependencies(obj: Any) -> List[Parameter]: + """ + Find all Parameter objects in an object hierarchy that have pending dependencies. + + :param obj: The object to search for Parameters + :return: List of Parameters with pending dependencies + """ + parameters_with_pending = [] + + def _collect_pending_parameters(item: Any) -> None: + """Recursively collect all Parameter objects with pending dependencies.""" + if isinstance(item, Parameter): + if hasattr(item, '_pending_dependency_string'): + parameters_with_pending.append(item) + elif isinstance(item, dict): + for value in item.values(): + _collect_pending_parameters(value) + elif isinstance(item, (list, tuple)): + for element in item: + _collect_pending_parameters(element) + elif hasattr(item, '__dict__'): + # Check instance attributes + for attr_name, attr_value in item.__dict__.items(): + if not attr_name.startswith('_'): # Skip private attributes + _collect_pending_parameters(attr_value) + + # Check class properties (descriptors like Parameter instances) + for attr_name in dir(type(item)): + if not attr_name.startswith('_'): # Skip private attributes + class_attr = getattr(type(item), attr_name, None) + if isinstance(class_attr, property): + try: + attr_value = getattr(item, attr_name) + _collect_pending_parameters(attr_value) + except (AttributeError, Exception): + # log the exception + print(f"Error accessing property '{attr_name}' of {item}") + # Skip properties that can't be accessed + continue + + _collect_pending_parameters(obj) + return parameters_with_pending + + +def deserialize_and_resolve_parameters(params_data: Dict[str, Dict[str, Any]]) -> Dict[str, Parameter]: + """ + Deserialize parameters from a dictionary and resolve their dependencies. + + This is a convenience function that combines Parameter.from_dict() deserialization + with dependency resolution in a single call. + + :param params_data: Dictionary mapping parameter names to their serialized data + :return: Dictionary mapping parameter names to deserialized Parameters with resolved dependencies + """ + # Deserialize all parameters first + new_params = {} + for name, data in params_data.items(): + new_params[name] = Parameter.from_dict(data) + + # Resolve all dependencies + resolve_all_parameter_dependencies(new_params) + + return new_params diff --git a/tests/integration_tests/fitting/test_fitter.py b/tests/integration_tests/fitting/test_fitter.py index 601f249d..bfa3f237 100644 --- a/tests/integration_tests/fitting/test_fitter.py +++ b/tests/integration_tests/fitting/test_fitter.py @@ -10,6 +10,7 @@ from easyscience import ObjBase from easyscience import Parameter from easyscience.fitting.minimizers import FitError +from easyscience.base_classes import ModelBase # Model and container of parameters for tests class AbsSin(ObjBase): @@ -50,6 +51,31 @@ def __call__(self, x): np.sin(self.phase.value * X + self.offset.value) ) * np.abs(np.sin(self.phase.value * Y + self.offset.value)) +class StraightLine(ModelBase): + def __init__(self, slope: float, intercept: float): + super().__init__() + self._slope = Parameter("slope", slope) + self._intercept = Parameter("intercept", intercept) + + @property + def slope(self) -> Parameter: + return self._slope + + @slope.setter + def slope(self, value: float) -> None: + self._slope.value = value + + @property + def intercept(self) -> Parameter: + return self._intercept + + @intercept.setter + def intercept(self, value: float) -> None: + self._intercept.value = value + + def __call__(self, x: np.ndarray) -> np.ndarray: + return self.slope.value * x + self.intercept.value + def check_fit_results(result, sp_sin, ref_sin, x, **kwargs): assert result.n_pars == len(sp_sin.get_fit_parameters()) @@ -343,4 +369,23 @@ def test_fixed_parameter_does_not_change(fit_engine): # Offset should remain unchanged assert sp_sin.offset.value == pytest.approx(fixed_offset_before, abs=1e-12) # Phase should be optimized - assert sp_sin.phase.value != pytest.approx(ref_sin.phase.value, rel=1e-3) \ No newline at end of file + assert sp_sin.phase.value != pytest.approx(ref_sin.phase.value, rel=1e-3) + +def test_fitter_new_model_base_integration(): + # WHEN + ground_truth = StraightLine(slope=2.0, intercept=1.0) + model = StraightLine(slope=0.5, intercept=0.0) + + x = np.linspace(0, 10, 100) + weights = np.ones_like(x) + y = ground_truth(x) + + # THEN + model.slope.fixed = False + model.intercept.fixed = False + fitter = Fitter(model, model) + result = fitter.fit(x=x, y=y, weights=weights) + + # EXPECT + assert model.slope.value == pytest.approx(ground_truth.slope.value, rel=1e-3) + assert model.intercept.value == pytest.approx(ground_truth.intercept.value, rel=1e-3) \ No newline at end of file diff --git a/tests/unit_tests/base_classes/test_model_base.py b/tests/unit_tests/base_classes/test_model_base.py new file mode 100644 index 00000000..a6d85768 --- /dev/null +++ b/tests/unit_tests/base_classes/test_model_base.py @@ -0,0 +1,252 @@ +# SPDX-FileCopyrightText: 2025 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +# © 2021-2025 Contributors to the EasyScience project +# SPDX-License-Identifier: BSD-3-Clause +# © 2021-2025 Contributors to the EasyScience project +# SPDX-License-Identifier: BSD-3-Clause +# © 2021-2025 Contributors to the EasyScience project +# SPDX-License-Identifier: BSD-3-Clause +# © 2021-2025 Contributors to the EasyScience project +# SPDX-License-Identifier: BSD-3-Clause +# © 2021-2025 Contributors to the EasyScience project +# SPDX-License-Identifier: BSD-3-Clause +# © 2021-2025 Contributors to the EasyScience project sub_container -> parameter + param = Parameter(name="value", value=42.0) + sub_container = ObjBase(name="sub", value=param) + main_container = ObjBase(name="main", sub=sub_container) + + # When - Find path from main to parameter + path = global_obj.map.find_path( + main_container.unique_name, + param.unique_name + ) + + # Then - Should find the path through the hierarchy + assert len(path) >= 2 + assert path[0] == main_container.unique_name + assert path[-1] == param.unique_name + + # When - Find reverse path + reverse_path = global_obj.map.reverse_route( + param.unique_name, + main_container.unique_name + ) + + # Then - Should be the reverse + assert reverse_path[0] == param.unique_name + assert reverse_path[-1] == main_container.unique_name + + def test_map_connectivity_with_isolated_objects(self, clear_all): + """Test map connectivity detection""" + # Given + global_obj = GlobalObject() + + # When - Create connected objects + param1 = Parameter(name="connected1", value=1.0) + param2 = Parameter(name="connected2", value=2.0) + container = ObjBase(name="container", p1=param1, p2=param2) + + # Then - Map should be connected + # TODO: Depending on implementation, connectivity might vary + # assert global_obj.map.is_connected() + + # When - Create isolated object + isolated = Parameter(name="isolated", value=99.0) + # Remove its automatic connection by clearing edges + # (In real usage, isolated objects would be rare) + + # Then - Map might still be connected depending on implementation + # This tests the connectivity algorithm + connectivity = global_obj.map.is_connected() + assert isinstance(connectivity, bool) + + def test_error_handling_integration(self, clear_all): + """Test error handling across the global object system""" + # Given + global_obj = GlobalObject() + + # When - Try to get non-existent object + with pytest.raises(ValueError, match="Item not in map"): + global_obj.map.get_item_by_key("non_existent") + + # When - Try to add object with duplicate name + param1 = Parameter(name="test", value=1.0) + param1_name = param1.unique_name + + # Create another with same unique name (should fail in add_vertex) + with pytest.raises(ValueError, match="already exists"): + # This simulates what would happen if we tried to add duplicate + global_obj.map.add_vertex(param1) # param1 already added during creation + + def test_memory_pressure_simulation(self, clear_all): + """Test system behavior under memory pressure""" + # Given + global_obj = GlobalObject() + + # When - Create many objects + objects = [] + for i in range(100): + param = Parameter(name=f"param_{i}", value=float(i)) + obj = ObjBase(name=f"obj_{i}", param=param) + objects.append((param, obj)) + + initial_vertex_count = len(global_obj.map.vertices()) + assert initial_vertex_count == 200 # 100 params + 100 objs + + # When - Delete half the objects + objects_to_delete = [objects[i] for i in range(0, 100, 2)] + for item in objects_to_delete: + objects.remove(item) + del item + + # Force garbage collection + gc.collect() + + # Then - Map should have fewer vertices + current_vertex_count = len(global_obj.map.vertices()) + assert current_vertex_count <= initial_vertex_count + + # When - Delete all remaining objects + del objects + gc.collect() + + # Then - Map should be mostly empty (might have some remaining references) + final_vertex_count = len(global_obj.map.vertices()) + assert final_vertex_count < initial_vertex_count + + def test_serialization_integration_with_global_state(self, clear_all): + """Test serialization integration with global state management""" + # Given + global_obj = GlobalObject() + + # Create objects + param = Parameter(name="test_param", value=123.45, unit="kg") + obj = ObjBase(name="test_obj", param=param) + + original_vertex_count = len(global_obj.map.vertices()) + + # When - Serialize objects + param_dict = param.as_dict() + obj_dict = obj.as_dict() + + # Clear global state + global_obj.map._clear() + assert len(global_obj.map.vertices()) == 0 + + # When - Deserialize objects + new_param = Parameter.from_dict(param_dict) + new_obj = ObjBase.from_dict(obj_dict) + + # Then - Should be registered in global map again + assert len(global_obj.map.vertices()) >= 2 + assert global_obj.map.is_known(new_param) + assert global_obj.map.is_known(new_obj) + + # Objects should be functionally equivalent + assert new_param.name == param.name + assert new_param.value == param.value + assert new_param.unit == param.unit + + def test_debug_mode_integration(self, clear_all): + """Test debug mode behavior across the system""" + # Given + global_obj = GlobalObject() + original_debug = global_obj.debug + + try: + # When - Enable debug mode + global_obj.debug = True + + if not global_obj.stack: + global_obj.instantiate_stack() + global_obj.stack.enabled = True + + # Create and modify objects + param = Parameter(name="debug_test", value=1.0) + + # This should trigger debug output in property_stack decorator + with patch('builtins.print') as mock_print: + param.value = 2.0 + + # Should have printed debug info (if debug mode works) + # Note: This depends on the debug implementation details + + # Test that operations still work normally + assert param.value == 2.0 + assert global_obj.stack.canUndo() + + finally: + # Restore original debug state + global_obj.debug = original_debug + + def test_concurrent_access_simulation(self, clear_all): + """Simulate concurrent access patterns""" + import threading + import time + + # Given + global_obj = GlobalObject() + results = [] + errors = [] + + def create_objects(thread_id, count=10): + """Create objects in a thread""" + try: + for i in range(count): + param = Parameter(name=f"thread_{thread_id}_param_{i}", value=float(i)) + results.append(param.unique_name) + time.sleep(0.001) # Small delay to encourage race conditions + except Exception as e: + errors.append(f"Thread {thread_id}: {e}") + + # When - Create objects concurrently + threads = [] + for thread_id in range(3): + thread = threading.Thread(target=create_objects, args=(thread_id, 5)) + threads.append(thread) + + for thread in threads: + thread.start() + + for thread in threads: + thread.join() + + # Then - Should not have errors and all objects should be created + assert len(errors) == 0, f"Errors occurred: {errors}" + assert len(results) == 15 # 3 threads * 5 objects each + assert len(set(results)) == 15 # All unique names should be unique + + # All objects should be in the map + for unique_name in results: + assert unique_name in global_obj.map.vertices() \ No newline at end of file diff --git a/tests/unit_tests/global_object/test_map.py b/tests/unit_tests/global_object/test_map.py index df344aed..05eec1a8 100644 --- a/tests/unit_tests/global_object/test_map.py +++ b/tests/unit_tests/global_object/test_map.py @@ -6,7 +6,127 @@ from easyscience import ObjBase import pytest import gc +import weakref +import numpy as np +from unittest.mock import MagicMock, patch from easyscience import global_object +from easyscience.global_object.map import _EntryList, Map + +class TestEntryList: + """Test the _EntryList helper class""" + + def test_init_default(self): + """Test _EntryList initialization with defaults""" + entry = _EntryList() + assert entry._type == [] + assert entry.finalizer is None + + def test_init_with_known_type(self): + """Test _EntryList initialization with known type""" + entry = _EntryList(my_type='created') + assert 'created' in entry._type + + def test_init_with_unknown_type(self): + """Test _EntryList initialization with unknown type""" + entry = _EntryList(my_type='unknown') + assert entry._type == [] + + def test_repr(self): + """Test string representation""" + entry = _EntryList() + repr_str = str(entry) + assert 'Undefined' in repr_str + assert 'finalizer' in repr_str + + entry.type = 'created' + repr_str = str(entry) + assert 'created' in repr_str + assert 'finalizer' in repr_str + + def test_type_property_getter(self): + """Test type property getter""" + entry = _EntryList() + assert entry.type == [] + + def test_type_property_setter_valid(self): + """Test type property setter with valid types""" + entry = _EntryList() + entry.type = 'created' + assert 'created' in entry.type + + entry.type = 'argument' + assert 'argument' in entry.type + assert 'created' in entry.type # Should be additive + + def test_type_property_setter_invalid(self): + """Test type property setter with invalid type""" + entry = _EntryList() + entry.type = 'invalid' + assert entry.type == [] + + def test_type_property_setter_duplicate(self): + """Test type property setter doesn't add duplicates""" + entry = _EntryList() + entry.type = 'created' + entry.type = 'created' # Try to add again + assert entry.type.count('created') == 1 + + def test_remove_type(self): + """Test removing a type""" + entry = _EntryList() + entry.type = 'created' + entry.type = 'argument' + + entry.remove_type('created') + assert 'created' not in entry.type + assert 'argument' in entry.type + + def test_remove_type_not_present(self): + """Test removing a type that's not present""" + entry = _EntryList() + entry.type = 'created' + entry.remove_type('argument') # Not present + assert 'created' in entry.type + + def test_reset_type(self): + """Test resetting types""" + entry = _EntryList() + entry.type = 'created' + entry.type = 'argument' + + entry.reset_type('returned') + assert entry.type == ['returned'] + + def test_reset_type_default_none(self): + """Test resetting types with no default""" + entry = _EntryList() + entry.type = 'created' + + entry.reset_type() + assert entry.type == [] + + def test_boolean_properties(self): + """Test boolean property helpers""" + entry = _EntryList() + + # Test all false initially + assert not entry.is_argument + assert not entry.is_created + assert not entry.is_created_internal + assert not entry.is_returned + + # Test each property + entry.type = 'argument' + assert entry.is_argument + + entry.type = 'created' + assert entry.is_created + + entry.type = 'created_internal' + assert entry.is_created_internal + + entry.type = 'returned' + assert entry.is_returned class TestMap: @pytest.fixture @@ -19,7 +139,7 @@ def base_object(self): @pytest.fixture def parameter_object(self): - return Parameter(value=2.0, name="test2") + return Parameter(name="test2", value=2) def test_add_vertex(self, clear, base_object, parameter_object): # When Then Expect @@ -78,3 +198,289 @@ def test_unique_name_change_still_in_map(self, clear, base_object, parameter_obj assert global_object.map.get_item_by_key("test3") == base_object assert global_object.map.get_item_by_key("test4") == parameter_object + def test_add_vertex_duplicate_name_error(self, clear, base_object): + """Test that adding vertex with duplicate name raises error""" + # When/Then + with pytest.raises(ValueError, match="already exists"): + # Try to add another object with same unique_name + duplicate_obj = ObjBase(name="duplicate", unique_name=base_object.unique_name) + + def test_add_vertex_with_object_type(self, clear): + """Test adding vertex with specific object type""" + # Given + obj = ObjBase(name="test") + + # When - Object is automatically added during construction + # Then + assert global_object.map.is_known(obj) + assert 'created' in global_object.map.find_type(obj) + + def test_is_known_object(self, clear, base_object): + """Test is_known method""" + # When/Then + assert global_object.map.is_known(base_object) is True + + # Test with unknown object + unknown_obj = MagicMock() + unknown_obj.unique_name = "unknown" + assert global_object.map.is_known(unknown_obj) is False + + def test_find_type_known_object(self, clear, base_object): + """Test find_type method""" + # When/Then + types = global_object.map.find_type(base_object) + assert isinstance(types, list) + assert 'created' in types + + def test_find_type_unknown_object(self, clear): + """Test find_type with unknown object""" + # Given + unknown_obj = MagicMock() + unknown_obj.unique_name = "unknown" + + # When/Then + result = global_object.map.find_type(unknown_obj) + assert result is None + + def test_reset_type(self, clear, base_object): + """Test resetting object type""" + # Given + original_types = global_object.map.find_type(base_object) + + # When + global_object.map.reset_type(base_object, 'argument') + + # Then + new_types = global_object.map.find_type(base_object) + assert new_types == ['argument'] + assert new_types != original_types + + def test_change_type(self, clear, base_object): + """Test changing object type""" + # Given + original_types = global_object.map.find_type(base_object) + original_count = len(original_types) + + # When + global_object.map.change_type(base_object, 'argument') + + # Then + new_types = global_object.map.find_type(base_object) + assert 'argument' in new_types + assert len(new_types) >= original_count # Should be at least the same or more + + def test_add_edge(self, clear, base_object, parameter_object): + """Test adding edges between objects""" + # When + global_object.map.add_edge(base_object, parameter_object) + + # Then + edges = global_object.map.get_edges(base_object) + assert parameter_object.unique_name in edges + + def test_add_edge_unknown_start_object(self, clear, parameter_object): + """Test adding edge with unknown start object""" + # Given + unknown_obj = MagicMock() + unknown_obj.unique_name = "unknown" + + # When/Then + with pytest.raises(AttributeError, match="Start object not in map"): + global_object.map.add_edge(unknown_obj, parameter_object) + + def test_get_edges(self, clear, base_object, parameter_object): + """Test getting edges for an object""" + # Given + global_object.map.add_edge(base_object, parameter_object) + + # When + edges = global_object.map.get_edges(base_object) + + # Then + assert isinstance(edges, list) + assert parameter_object.unique_name in edges + + def test_get_edges_unknown_object(self, clear): + """Test getting edges for unknown object""" + # Given + unknown_obj = MagicMock() + unknown_obj.unique_name = "unknown" + + # When/Then + with pytest.raises(AttributeError): + global_object.map.get_edges(unknown_obj) + + def test_prune_vertex_from_edge(self, clear, base_object, parameter_object): + """Test removing edge between objects""" + # Given + global_object.map.add_edge(base_object, parameter_object) + assert parameter_object.unique_name in global_object.map.get_edges(base_object) + + # When + global_object.map.prune_vertex_from_edge(base_object, parameter_object) + + # Then + edges = global_object.map.get_edges(base_object) + assert parameter_object.unique_name not in edges + + def test_prune_vertex_from_edge_none_child(self, clear, base_object): + """Test pruning edge with None child""" + # When/Then - Should not raise error + global_object.map.prune_vertex_from_edge(base_object, None) + + def test_prune_vertex(self, clear, base_object): + """Test pruning a vertex completely""" + # Given + unique_name = base_object.unique_name + assert global_object.map.is_known(base_object) + + # When + global_object.map.prune(unique_name) + + # Then + assert not global_object.map.is_known(base_object) + assert unique_name not in global_object.map.vertices() + + def test_edges_generation(self, clear, base_object, parameter_object): + """Test edge generation""" + # Given + global_object.map.add_edge(base_object, parameter_object) + + # When + edges = global_object.map.edges() + + # Then + assert isinstance(edges, list) + expected_edge = {base_object.unique_name, parameter_object.unique_name} + assert expected_edge in edges + + def test_type_filtering_properties(self, clear): + """Test type filtering properties""" + # Given + obj1 = ObjBase(name="obj1") # 'created' type + obj2 = Parameter(name="obj2", value=1) # 'created' type + + global_object.map.change_type(obj1, 'argument') + global_object.map.change_type(obj2, 'returned') + + # When/Then + argument_objs = global_object.map.argument_objs + created_objs = global_object.map.created_objs + returned_objs = global_object.map.returned_objs + + assert obj1.unique_name in argument_objs + assert obj1.unique_name in created_objs # Should still be there + assert obj2.unique_name in created_objs + assert obj2.unique_name in returned_objs + + def test_find_path_simple(self, clear, base_object, parameter_object): + """Test finding path between objects""" + # Given + global_object.map.add_edge(base_object, parameter_object) + + # When + path = global_object.map.find_path(base_object.unique_name, parameter_object.unique_name) + + # Then + assert path == [base_object.unique_name, parameter_object.unique_name] + + def test_find_path_same_vertex(self, clear, base_object): + """Test finding path to same vertex""" + # When + path = global_object.map.find_path(base_object.unique_name, base_object.unique_name) + + # Then + assert path == [base_object.unique_name] + + def test_find_path_no_path(self, clear, base_object, parameter_object): + """Test finding path when no path exists""" + # When - No edge added + path = global_object.map.find_path(base_object.unique_name, parameter_object.unique_name) + + # Then + assert path == [] + + def test_find_all_paths(self, clear, base_object, parameter_object): + """Test finding all paths between objects""" + # Given + global_object.map.add_edge(base_object, parameter_object) + + # When + paths = global_object.map.find_all_paths(base_object.unique_name, parameter_object.unique_name) + + # Then + assert len(paths) == 1 + assert paths[0] == [base_object.unique_name, parameter_object.unique_name] + + def test_reverse_route_with_start(self, clear, base_object, parameter_object): + """Test reverse route with specified start""" + # Given + global_object.map.add_edge(base_object, parameter_object) + + # When + route = global_object.map.reverse_route(parameter_object.unique_name, base_object.unique_name) + + # Then + assert route == [parameter_object.unique_name, base_object.unique_name] + + def test_reverse_route_without_start(self, clear, base_object, parameter_object): + """Test reverse route without specified start""" + # Given + global_object.map.add_edge(base_object, parameter_object) + + # When + route = global_object.map.reverse_route(parameter_object.unique_name) + + # Then + assert len(route) >= 1 + assert route[0] == parameter_object.unique_name + + def test_is_connected_single_vertex(self, clear, base_object): + """Test connectivity with single vertex""" + # When/Then + assert global_object.map.is_connected() is True + + def test_is_connected_multiple_vertices(self, clear, base_object, parameter_object): + """Test connectivity with multiple connected vertices""" + # Given + global_object.map.add_edge(base_object, parameter_object) + + # When/Then + assert global_object.map.is_connected() is True + + def test_map_repr(self, clear, base_object, parameter_object): + """Test map string representation""" + # When + repr_str = str(global_object.map) + + # Then + assert "Map object" in repr_str + assert "2" in repr_str # Should show vertex count + + def test_get_item_by_key_not_found(self, clear): + """Test getting item by non-existent key""" + # When/Then + with pytest.raises(ValueError, match="Item not in map"): + global_object.map.get_item_by_key("non_existent") + + def test_clear_with_finalizers(self, clear): + """Test clearing map properly calls finalizers""" + # Given + obj = ObjBase(name="test") + original_count = len(global_object.map.vertices()) + + # When + global_object.map._clear() + + # Then + assert len(global_object.map.vertices()) == 0 + + def test_map_initialization(self): + """Test Map initialization""" + # When + test_map = Map() + + # Then + assert len(test_map.vertices()) == 0 + assert test_map.edges() == [] + diff --git a/tests/unit_tests/global_object/test_undo_redo_comprehensive.py b/tests/unit_tests/global_object/test_undo_redo_comprehensive.py new file mode 100644 index 00000000..024731a7 --- /dev/null +++ b/tests/unit_tests/global_object/test_undo_redo_comprehensive.py @@ -0,0 +1,686 @@ +# SPDX-FileCopyrightText: 2025 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +# © 2021-2025 Contributors to the EasyScience project +# SPDX-License-Identifier: BSD-3-Clause +# © 2021-2025 Contributors to the EasyScience project Any: + return self._convert_to_dict(obj, skip=skip, **kwargs) + + @classmethod + def decode(cls, obj: Any) -> Any: + return cls._convert_from_dict(obj) + + +class TestSerializerBase: + + @pytest.fixture + def clear(self): + """Clear everything before and after each test""" + global_object.map._clear() + if global_object.stack: + global_object.stack.clear() + yield + global_object.map._clear() + if global_object.stack: + global_object.stack.clear() + + @pytest.fixture + def serializer(self): + return ConcreteSerializer() + + @pytest.fixture + def mock_obj(self): + return MockSerializerComponent("test_obj", 42, "optional_value") + + def test_abstract_methods_are_placeholders(self): + """Test that SerializerBase can be instantiated but abstract methods just pass""" + # SerializerBase can actually be instantiated (abstract methods just pass) + base = SerializerBase() + + # Abstract methods don't raise exceptions - they just pass/return None + result = base.encode(Mock()) + assert result is None + + result = SerializerBase.decode({}) + assert result is None + + def test_get_arg_spec(self): + """Test get_arg_spec static method""" + def example_func(self, arg1, arg2, arg3="default"): + pass + + spec, args = SerializerBase.get_arg_spec(example_func) + + assert args == ['arg1', 'arg2', 'arg3'] + assert hasattr(spec, 'args') + assert hasattr(spec, 'defaults') + + def test_encode_objs_datetime(self): + """Test _encode_objs with datetime objects""" + dt = datetime.datetime(2023, 10, 17, 14, 30, 45, 123456) + result = SerializerBase._encode_objs(dt) + + expected = { + '@module': 'datetime', + '@class': 'datetime', + 'string': '2023-10-17 14:30:45.123456' + } + assert result == expected + + def test_encode_objs_numpy_array_real(self): + """Test _encode_objs with real numpy arrays""" + arr = np.array([1, 2, 3], dtype=np.int32) + result = SerializerBase._encode_objs(arr) + + expected = { + '@module': 'numpy', + '@class': 'array', + 'dtype': 'int32', + 'data': [1, 2, 3] + } + assert result == expected + + def test_encode_objs_numpy_array_complex(self): + """Test _encode_objs with complex numpy arrays""" + arr = np.array([1+2j, 3+4j], dtype=np.complex128) + result = SerializerBase._encode_objs(arr) + + expected = { + '@module': 'numpy', + '@class': 'array', + 'dtype': 'complex128', + 'data': [[1.0, 3.0], [2.0, 4.0]] # [real, imag] + } + assert result == expected + + def test_encode_objs_numpy_scalar(self): + """Test _encode_objs with numpy scalars""" + scalar = np.int32(42) + result = SerializerBase._encode_objs(scalar) + + assert result == 42 + assert isinstance(result, int) + + def test_encode_objs_json_serializable(self): + """Test _encode_objs with JSON serializable objects""" + # Should return the object unchanged if it's JSON serializable + data = {"key": "value", "number": 42} + result = SerializerBase._encode_objs(data) + assert result == data + + def test_encode_objs_non_serializable(self): + """Test _encode_objs with non-serializable objects""" + class NonSerializable: + def __init__(self): + self.value = "test" + + obj = NonSerializable() + result = SerializerBase._encode_objs(obj) + # Should return the object unchanged if not serializable + assert result is obj + + def test_convert_from_dict_datetime(self): + """Test _convert_from_dict with datetime objects""" + dt_dict = { + '@module': 'datetime', + '@class': 'datetime', + 'string': '2023-10-17 14:30:45.123456' + } + + result = SerializerBase._convert_from_dict(dt_dict) + expected = datetime.datetime(2023, 10, 17, 14, 30, 45, 123456) + assert result == expected + + def test_convert_from_dict_datetime_no_microseconds(self): + """Test _convert_from_dict with datetime without microseconds""" + dt_dict = { + '@module': 'datetime', + '@class': 'datetime', + 'string': '2023-10-17 14:30:45' + } + + result = SerializerBase._convert_from_dict(dt_dict) + expected = datetime.datetime(2023, 10, 17, 14, 30, 45) + assert result == expected + + def test_convert_from_dict_numpy_array_real(self): + """Test _convert_from_dict with real numpy arrays""" + arr_dict = { + '@module': 'numpy', + '@class': 'array', + 'dtype': 'int32', + 'data': [1, 2, 3] + } + + result = SerializerBase._convert_from_dict(arr_dict) + expected = np.array([1, 2, 3], dtype=np.int32) + np.testing.assert_array_equal(result, expected) + assert result.dtype == expected.dtype + + def test_convert_from_dict_numpy_array_complex(self): + """Test _convert_from_dict with complex numpy arrays""" + arr_dict = { + '@module': 'numpy', + '@class': 'array', + 'dtype': 'complex128', + 'data': [[1.0, 3.0], [2.0, 4.0]] + } + + result = SerializerBase._convert_from_dict(arr_dict) + expected = np.array([1+2j, 3+4j], dtype=np.complex128) + np.testing.assert_array_equal(result, expected) + assert result.dtype == expected.dtype + + def test_convert_from_dict_easyscience_object(self, clear): + """Test _convert_from_dict with EasyScience objects""" + param_dict = { + '@module': 'easyscience.variable.parameter', + '@class': 'Parameter', + '@version': '0.6.0', + 'name': 'test_param', + 'value': 5.0, + 'unit': 'm', + 'variance': 0.1, + 'min': 0.0, + 'max': 10.0, + 'fixed': False, + 'url': '', + 'description': '', + 'display_name': 'test_param' + } + + result = SerializerBase._convert_from_dict(param_dict) + assert isinstance(result, Parameter) + assert result.name == 'test_param' + assert result.value == 5.0 + assert str(result.unit) == 'm' + + def test_convert_from_dict_list(self): + """Test _convert_from_dict with lists""" + data = [ + {'@module': 'datetime', '@class': 'datetime', 'string': '2023-10-17 14:30:45'}, + {'key': 'value'}, + 42 + ] + + result = SerializerBase._convert_from_dict(data) + assert isinstance(result, list) + assert len(result) == 3 + assert isinstance(result[0], datetime.datetime) + assert result[1] == {'key': 'value'} + assert result[2] == 42 + + def test_convert_from_dict_regular_dict(self): + """Test _convert_from_dict with regular dictionaries""" + data = {'key': 'value', 'number': 42} + result = SerializerBase._convert_from_dict(data) + assert result == data + + def test_convert_to_dict_basic(self, serializer, mock_obj, clear): + """Test _convert_to_dict with basic object""" + result = serializer._convert_to_dict(mock_obj) + + assert '@module' in result + assert '@class' in result + assert '@version' in result + assert result['name'] == 'test_obj' + assert result['value'] == 42 + assert result['optional_param'] == 'optional_value' + assert result['unique_name'] == 'mock_test_obj' + + def test_convert_to_dict_with_skip(self, serializer, mock_obj, clear): + """Test _convert_to_dict with skip parameter""" + result = serializer._convert_to_dict(mock_obj, skip=['value', 'optional_param']) + + assert 'name' in result + assert 'value' not in result + assert 'optional_param' not in result + + def test_convert_to_dict_with_redirect(self, serializer, clear): + """Test _convert_to_dict with _REDIRECT""" + obj = MockSerializerWithRedirect("redirect_test", 10) + result = serializer._convert_to_dict(obj) + + assert result['special_attr'] == 20 # 10 * 2 from redirect + assert 'none_attr' not in result # Should be skipped due to None redirect + + def test_convert_to_dict_with_custom_convert_to_dict(self, serializer, clear): + """Test _convert_to_dict with custom _convert_to_dict method""" + obj = MockSerializerWithConvertToDict("custom_test", 5) + result = serializer._convert_to_dict(obj) + + assert result['custom_field'] == 'added_by_convert_to_dict' + assert result['name'] == 'custom_test' + assert result['value'] == 5 + + def test_convert_to_dict_with_enum_object(self, serializer, clear): + """Test _convert_to_dict when the object itself is an enum""" + + # Test that enum values in objects remain as enums without full_encode + class MockObjWithEnum(SerializerComponent): + def __init__(self, name: str, enum_val: TestEnum): + self.name = name + self.enum_val = enum_val + self.unique_name = f"obj_{name}" + self._global_object = True + + obj = MockObjWithEnum("test", TestEnum.TEST_VALUE) + result = serializer._convert_to_dict(obj) + + # The enum field should remain as an enum object (not encoded as dict) + assert isinstance(result['enum_val'], TestEnum) + assert result['enum_val'] == TestEnum.TEST_VALUE + + def test_convert_to_dict_full_encode(self, serializer, clear): + """Test _convert_to_dict with full_encode=True""" + dt = datetime.datetime(2023, 10, 17, 14, 30, 45) + + class MockObjWithDateTime(SerializerComponent): + def __init__(self, name: str, dt: datetime.datetime): + self.name = name + self.dt = dt + self.unique_name = f"dt_{name}" + self._global_object = True + + obj = MockObjWithDateTime("test", dt) + result = serializer._convert_to_dict(obj, full_encode=True) + + assert isinstance(result['dt'], dict) + assert result['dt']['@module'] == 'datetime' + assert result['dt']['@class'] == 'datetime' + + def test_convert_to_dict_without_global_object(self, serializer): + """Test _convert_to_dict with object without _global_object""" + + class MockObjNoGlobal(SerializerComponent): + def __init__(self, name: str): + self.name = name + + obj = MockObjNoGlobal("test") + result = serializer._convert_to_dict(obj) + + assert result['name'] == 'test' + assert 'unique_name' not in result + + def test_convert_to_dict_with_arg_spec(self, serializer, clear): + """Test _convert_to_dict with custom _arg_spec""" + + class MockObjCustomArgSpec(SerializerComponent): + def __init__(self, name: str, value: int, extra: str = "default"): + self.name = name + self.value = value + self.extra = extra + self._arg_spec = ['name', 'value'] # Skip 'extra' + self.unique_name = f"custom_spec_{name}" + self._global_object = True + + obj = MockObjCustomArgSpec("test", 42, "not_default") + result = serializer._convert_to_dict(obj) + + assert 'name' in result + assert 'value' in result + assert 'extra' not in result + + def test_recursive_encoder_with_lists(self, serializer, clear): + """Test _recursive_encoder with lists""" + mock_obj = MockSerializerComponent("list_test", 1) + data = [mock_obj, "string", 42, {"key": "value"}] + + result = serializer._recursive_encoder(data) + + assert isinstance(result, list) + assert len(result) == 4 + assert isinstance(result[0], dict) # Encoded mock object + assert result[0]['name'] == 'list_test' + assert result[1] == "string" + assert result[2] == 42 + assert result[3] == {"key": "value"} + + def test_recursive_encoder_with_dicts(self, serializer, clear): + """Test _recursive_encoder with dictionaries""" + mock_obj = MockSerializerComponent("dict_test", 2) + data = {"obj": mock_obj, "simple": "value"} + + result = serializer._recursive_encoder(data) + + assert isinstance(result, dict) + assert isinstance(result['obj'], dict) # Encoded mock object + assert result['obj']['name'] == 'dict_test' + assert result['simple'] == "value" + + def test_recursive_encoder_with_tuples(self, serializer, clear): + """Test _recursive_encoder with tuples""" + mock_obj = MockSerializerComponent("tuple_test", 3) + data = (mock_obj, "string", 42) + + result = serializer._recursive_encoder(data) + + assert isinstance(result, list) # Tuples become lists + assert len(result) == 3 + assert isinstance(result[0], dict) # Encoded mock object + assert result[0]['name'] == 'tuple_test' + + def test_recursive_encoder_builtin_encode_method(self, serializer): + """Test _recursive_encoder doesn't encode builtin objects with encode method""" + # Strings have an encode method but are builtin + data = ["test_string", b"bytes"] + + result = serializer._recursive_encoder(data) + + assert result == ["test_string", b"bytes"] + + def test_recursive_encoder_with_mutable_sequence(self, serializer, clear): + """Test _recursive_encoder with MutableSequence objects""" + from easyscience.base_classes import CollectionBase + + d0 = DescriptorNumber("a", 0) # type: ignore + d1 = DescriptorNumber("b", 1) # type: ignore + collection = CollectionBase("test_collection", d0, d1) + + result = serializer._recursive_encoder(collection) + + assert isinstance(result, dict) + assert result['@class'] == 'CollectionBase' + assert 'data' in result + + @patch('easyscience.io.serializer_base.import_module') + def test_convert_to_dict_no_version(self, mock_import, serializer, clear): + """Test _convert_to_dict when module has no __version__""" + mock_module = Mock() + del mock_module.__version__ # Remove version attribute + mock_import.return_value = mock_module + + mock_obj = MockSerializerComponent("no_version", 1) + result = serializer._convert_to_dict(mock_obj) + + assert result['@version'] is None + + @patch('easyscience.io.serializer_base.import_module') + def test_convert_to_dict_import_error(self, mock_import, serializer, clear): + """Test _convert_to_dict when import_module raises ImportError""" + mock_import.side_effect = ImportError("Module not found") + + mock_obj = MockSerializerComponent("import_error", 1) + result = serializer._convert_to_dict(mock_obj) + + assert result['@version'] is None + + def test_convert_to_dict_attribute_error_handling(self, serializer, clear): + """Test _convert_to_dict handles AttributeError for missing attributes""" + + class MockObjMissingAttrs(SerializerComponent): + def __init__(self, name: str, missing_param: str = "default"): + self.name = name + self.unique_name = f"missing_{name}" + self._global_object = True + # Don't set missing_param attribute to trigger AttributeError + + obj = MockObjMissingAttrs("test") + + with pytest.raises(NotImplementedError, match="Unable to automatically determine as_dict"): + serializer._convert_to_dict(obj) + + def test_convert_to_dict_with_kwargs_attribute(self, serializer, clear): + """Test _convert_to_dict with _kwargs attribute handling""" + + class MockObjWithKwargs(SerializerComponent): + def __init__(self, name: str, value: int): + self.name = name + self.value = value + self.unique_name = f"kwargs_{name}" + self._global_object = True + # Set up _kwargs to test the kwargs handling path + self._kwargs = {'extra_param': 'extra_value'} + + obj = MockObjWithKwargs("test", 42) + result = serializer._convert_to_dict(obj) + + # The extra_param from _kwargs should be included + assert result['extra_param'] == 'extra_value' + + def test_convert_to_dict_varargs_handling(self, serializer, clear): + """Test _convert_to_dict with varargs (*args) handling""" + + class MockObjWithVarargs(SerializerComponent): + def __init__(self, name: str, *args): + self.name = name + self.args = args + self.unique_name = f"varargs_{name}" + self._global_object = True + + obj = MockObjWithVarargs("test", "arg1", "arg2", "arg3") + result = serializer._convert_to_dict(obj) + + assert result['name'] == 'test' + if 'args' in result: + assert result['args'] == ("arg1", "arg2", "arg3") + + def test_encode_objs_edge_cases(self): + """Test _encode_objs with edge cases""" + # Test with None + assert SerializerBase._encode_objs(None) is None + + # Test with empty numpy array + empty_arr = np.array([]) + result = SerializerBase._encode_objs(empty_arr) + assert result['@module'] == 'numpy' + assert result['data'] == [] + + def test_convert_from_dict_edge_cases(self): + """Test _convert_from_dict with edge cases""" + # Test with None + assert SerializerBase._convert_from_dict(None) is None + + # Test with empty dict + assert SerializerBase._convert_from_dict({}) == {} + + # Test with empty list + assert SerializerBase._convert_from_dict([]) == [] + + # Test with bson.objectid (should not be processed) + bson_dict = { + '@module': 'bson.objectid', + '@class': 'ObjectId', + 'value': 'some_id' + } + result = SerializerBase._convert_from_dict(bson_dict) + assert result == bson_dict + + def test_concrete_serializer_implementation(self, clear): + """Test that ConcreteSerializer works correctly""" + serializer = ConcreteSerializer() + mock_obj = MockSerializerComponent("concrete_test", 100) + + # Test encode + encoded = serializer.encode(mock_obj) + assert isinstance(encoded, dict) + assert encoded['name'] == 'concrete_test' + assert encoded['value'] == 100 + + # Test decode + global_object.map._clear() # Clear before decode + decoded = ConcreteSerializer.decode(encoded) + assert isinstance(decoded, MockSerializerComponent) + assert decoded.name == 'concrete_test' + assert decoded.value == 100 + + def test_get_arg_spec_with_complex_function(self): + """Test get_arg_spec with more complex function signatures""" + def complex_func(self, required_arg, optional_arg="default", *args, **kwargs): + pass + + spec, args = SerializerBase.get_arg_spec(complex_func) + + assert args == ['required_arg', 'optional_arg'] + assert spec.varargs == 'args' + assert spec.varkw == 'kwargs' + assert spec.defaults == ("default",) + + @patch('easyscience.io.serializer_base.np', None) + def test_encode_objs_without_numpy(self): + """Test _encode_objs when numpy is not available""" + # This test patches np to None to simulate numpy not being installed + dt = datetime.datetime(2023, 10, 17, 14, 30, 45) + result = SerializerBase._encode_objs(dt) + + expected = { + '@module': 'datetime', + '@class': 'datetime', + 'string': '2023-10-17 14:30:45' + } + assert result == expected + + def test_recursive_encoder_with_nested_structures(self, serializer, clear): + """Test _recursive_encoder with deeply nested structures""" + mock_obj = MockSerializerComponent("nested", 1) + + data = { + "level1": { + "level2": [mock_obj, {"level3": mock_obj}] + } + } + + result = serializer._recursive_encoder(data) + + assert isinstance(result, dict) + assert isinstance(result["level1"], dict) + assert isinstance(result["level1"]["level2"], list) + assert isinstance(result["level1"]["level2"][0], dict) # Encoded object + assert result["level1"]["level2"][0]["name"] == "nested" + assert isinstance(result["level1"]["level2"][1]["level3"], dict) # Encoded object + + def test_import_class(self): + # When Then + cls = SerializerBase._import_class(module_name="easyscience", class_name="Parameter") + # Expect + assert cls is Parameter + + def test_import_class_missing_module(self): + # When Then Expect + with pytest.raises(ImportError): + SerializerBase._import_class(module_name="non_existent_module", class_name="Parameter") + + def test_import_class_missing_class(self): + # When Then Expect + with pytest.raises(ValueError): + SerializerBase._import_class(module_name="easyscience", class_name="NonExistentClass") + + @pytest.mark.parametrize("dict, expected", [ + ({ '@module': 'easyscience', + '@class': 'Parameter', + 'name': 'param1', + 'value': 10.0 + }, + True + ), + ({ + '@module': 'numpy', + '@class': 'array', + 'unique_name': 'unique1', + 'display_name': 'Display 1' + }, False + )], ids=['valid_easyscience_dict', 'invalid_numpy_dict']) + def test_is_serialized_easyscience_object_false(self, dict, expected): + # When Then + result = SerializerBase._is_serialized_easyscience_object(dict) + # Expect + assert result is expected + + def test_deserialize_value_non_easyscience(self): + # When + serialized_dict = { + '@module': 'numpy', + '@class': 'array', + 'dtype': 'int64', + 'data': [0, 1] + } + # Then + obj = SerializerBase._deserialize_value(serialized_dict) + # Expect + assert isinstance(obj, np.ndarray) + assert obj.dtype == np.int64 + + def test_deserialize_value_easyscience_uses_from_dict(self, monkeypatch): + # When + serialized_dict = { + '@module': 'easyscience.base_classes', + '@class': 'NewBase', + 'display_name': 'test', + } + monkeypatch.setattr(NewBase, 'from_dict', MagicMock()) + # Then + obj = SerializerBase._deserialize_value(serialized_dict) + # Expect + NewBase.from_dict.assert_called_once_with(serialized_dict) + + def test_deserialize_value_easyscience_no_from_dict(self, monkeypatch): + # When + serialized_dict = { + '@module': 'easyscience.base_classes', + '@class': 'NewBase', + 'display_name': 'test', + } + monkeypatch.delattr(NewBase, 'from_dict') + monkeypatch.setattr(SerializerBase, '_convert_from_dict', MagicMock()) + # Then + obj = SerializerBase._deserialize_value(serialized_dict) + # Expect + SerializerBase._convert_from_dict.assert_called_once_with(serialized_dict) + + def test_deserialize_value_easyscience_import_error(self, monkeypatch): + # When + serialized_dict = { + '@module': 'easyscience.base_classes', + '@class': 'NewBase', + 'display_name': 'test', + } + monkeypatch.setattr(SerializerBase, '_import_class', MagicMock(side_effect=ImportError())) + monkeypatch.setattr(SerializerBase, '_convert_from_dict', MagicMock()) + # Then + obj = SerializerBase._deserialize_value(serialized_dict) + # Expect + SerializerBase._convert_from_dict.assert_called_once_with(serialized_dict) + + def test_deserialize_dict(self, monkeypatch): + # When + serialized_dict = { + '@module': 'easyscience', + '@class': 'ParameterContainer', + 'param1': { + '@module': 'easyscience', + '@class': 'Parameter', + 'name': 'param1', + 'value': 10.0 + }, + 'array1': { + '@module': 'numpy', + '@class': 'array', + 'dtype': 'int64', + 'data': [0, 1] + } + } + monkeypatch.setattr(SerializerBase, '_deserialize_value', MagicMock(side_effect=[ + Parameter(name='param1', value=10.0), + np.array([0, 1], dtype=np.int64) + ])) + # Then + result = SerializerBase.deserialize_dict(serialized_dict) + # Expect + SerializerBase._deserialize_value.assert_any_call(serialized_dict['param1']) + SerializerBase._deserialize_value.assert_any_call(serialized_dict['array1']) + assert isinstance(result['param1'], Parameter) + assert isinstance(result['array1'], np.ndarray) + assert result['array1'].dtype == np.int64 \ No newline at end of file diff --git a/tests/unit_tests/models/test_polynomial.py b/tests/unit_tests/models/test_polynomial.py index 799a917a..2493cd11 100644 --- a/tests/unit_tests/models/test_polynomial.py +++ b/tests/unit_tests/models/test_polynomial.py @@ -6,7 +6,17 @@ import numpy as np import pytest +from easyscience import global_object +from easyscience.base_classes import CollectionBase from easyscience.models.polynomial import Polynomial +from easyscience.variable import Parameter + + +@pytest.fixture +def clear(): + """Clear global object map before each test.""" + global_object.map._clear() + poly_test_cases = ( (1.,), @@ -19,8 +29,9 @@ (0.72, 6.48, -0.48), ) + @pytest.mark.parametrize("coo", poly_test_cases) -def test_Polynomial_pars(coo): +def test_Polynomial_pars(clear, coo): poly = Polynomial(coefficients=coo) vals = {coo.value for coo in poly.coefficients} @@ -29,3 +40,155 @@ def test_Polynomial_pars(coo): x = np.linspace(0, 10, 100) y = np.polyval(coo, x) assert np.allclose(poly(x), y) + + +def test_Polynomial_default_initialization(clear): + """Test Polynomial with no coefficients.""" + poly = Polynomial(name='test_poly') + + assert poly.name == 'test_poly' + assert len(poly.coefficients) == 0 + + # Test that calling the polynomial with empty coefficients works + x = np.array([1, 2, 3]) + result = poly(x) + assert len(result) == len(x) + + +def test_Polynomial_with_Parameter_objects(clear): + """Test Polynomial with Parameter objects as coefficients.""" + p0 = Parameter('c0', value=1.0) + p1 = Parameter('c1', value=2.0) + p2 = Parameter('c2', value=3.0) + + poly = Polynomial(coefficients=[p0, p1, p2]) + + assert len(poly.coefficients) == 3 + assert poly.coefficients[0].name == 'c0' + assert poly.coefficients[1].name == 'c1' + assert poly.coefficients[2].name == 'c2' + assert poly.coefficients[0].value == 1.0 + assert poly.coefficients[1].value == 2.0 + assert poly.coefficients[2].value == 3.0 + + # Test evaluation - coefficients passed directly to polyval + # polyval([1.0, 2.0, 3.0], x) = 1.0*x^2 + 2.0*x + 3.0 + x = np.array([0, 1, 2]) + expected = np.polyval([1.0, 2.0, 3.0], x) + assert np.allclose(poly(x), expected) + + +def test_Polynomial_with_mixed_coefficients(clear): + """Test Polynomial with mixed float and Parameter coefficients.""" + p0 = Parameter('c0', value=5.0) + + poly = Polynomial(coefficients=[p0, 2.0, 1.0]) + + assert len(poly.coefficients) == 3 + assert poly.coefficients[0].name == 'c0' + assert poly.coefficients[1].name == 'c1' + assert poly.coefficients[2].name == 'c2' + + # polyval([5.0, 2.0, 1.0], x) = 5.0*x^2 + 2.0*x + 1.0 + x = np.array([1, 2, 3]) + expected = np.polyval([5.0, 2.0, 1.0], x) + assert np.allclose(poly(x), expected) + + +def test_Polynomial_with_CollectionBase(clear): + """Test Polynomial initialized with a CollectionBase.""" + collection = CollectionBase('coeffs') + collection.append(Parameter('c0', value=1.0)) + collection.append(Parameter('c1', value=2.0)) + collection.append(Parameter('c2', value=3.0)) + + poly = Polynomial(coefficients=collection) + + assert poly.coefficients is collection + assert len(poly.coefficients) == 3 + + # polyval([1.0, 2.0, 3.0], x) = 1.0*x^2 + 2.0*x + 3.0 + x = np.array([0, 1, 2]) + expected = np.polyval([1.0, 2.0, 3.0], x) + assert np.allclose(poly(x), expected) + + +def test_Polynomial_invalid_coefficient_type(clear): + """Test that invalid coefficient types raise TypeError.""" + with pytest.raises(TypeError, match='Coefficients must be floats or Parameters'): + Polynomial(coefficients=[1.0, 'invalid', 3.0]) + + with pytest.raises(TypeError, match='Coefficients must be floats or Parameters'): + Polynomial(coefficients=[1, 2, 3]) # integers, not floats + + +def test_Polynomial_invalid_coefficients_type(clear): + """Test that invalid coefficients argument type raises TypeError.""" + # String is iterable, so it will iterate over characters and fail with first error + with pytest.raises(TypeError, match='Coefficients must be floats or Parameters'): + Polynomial(coefficients="invalid") + + # Integer is not iterable, so it will fail with second error + with pytest.raises(TypeError, match='coefficients must be a list or a CollectionBase'): + Polynomial(coefficients=42) + + +def test_Polynomial_repr_no_coefficients(clear): + """Test __repr__ with no coefficients.""" + poly = Polynomial(name='empty') + + repr_str = repr(poly) + assert 'Polynomial(empty, )' == repr_str + + +def test_Polynomial_repr_one_coefficient(clear): + """Test __repr__ with one coefficient.""" + poly = Polynomial(coefficients=[5.0]) + + repr_str = repr(poly) + assert 'Polynomial(polynomial, 5.0)' == repr_str + + +def test_Polynomial_repr_two_coefficients(clear): + """Test __repr__ with two coefficients.""" + poly = Polynomial(coefficients=[3.0, 2.0]) + + repr_str = repr(poly) + assert 'Polynomial(polynomial, 2.0x + 3.0)' == repr_str + + +def test_Polynomial_repr_three_coefficients(clear): + """Test __repr__ with three coefficients.""" + poly = Polynomial(coefficients=[1.0, 2.0, 3.0]) + + repr_str = repr(poly) + assert 'Polynomial(polynomial, 3.0x^2 + 2.0x + 1.0)' == repr_str + + +def test_Polynomial_repr_with_zero_coefficients(clear): + """Test __repr__ with some zero coefficients.""" + poly = Polynomial(coefficients=[1.0, 0.0, 3.0, 0.0, 5.0]) + + repr_str = repr(poly) + # Zero coefficients for higher powers should be excluded + assert 'Polynomial(polynomial, 5.0x^4 + 3.0x^2 + 0.0x + 1.0)' == repr_str + + +def test_Polynomial_repr_negative_coefficients(clear): + """Test __repr__ with negative coefficients.""" + poly = Polynomial(coefficients=[-1.0, -2.0, -3.0]) + + repr_str = repr(poly) + assert 'Polynomial(polynomial, -3.0x^2 + -2.0x + -1.0)' == repr_str + + +def test_Polynomial_call_with_args_kwargs(clear): + """Test that __call__ accepts *args and **kwargs.""" + poly = Polynomial(coefficients=[1.0, 2.0, 3.0]) + + x = np.array([0, 1, 2]) + # These additional args/kwargs should be ignored + result = poly(x, "extra_arg", extra_kwarg="value") + # polyval([1.0, 2.0, 3.0], x) = 1.0*x^2 + 2.0*x + 3.0 + expected = np.polyval([1.0, 2.0, 3.0], x) + assert np.allclose(result, expected) diff --git a/tests/unit_tests/variable/test_parameter.py b/tests/unit_tests/variable/test_parameter.py index 356ed83d..0ca8f85e 100644 --- a/tests/unit_tests/variable/test_parameter.py +++ b/tests/unit_tests/variable/test_parameter.py @@ -635,22 +635,6 @@ def test_set_value_dependent_parameter(self, normal_parameter: Parameter): with pytest.raises(AttributeError): dependent_parameter.value = 3 - def test_full_value_match_callback(self, parameter: Parameter): - # When - self.mock_callback.fget.return_value = sc.scalar(1, unit='m') - - # Then Expect - assert parameter.full_value == sc.scalar(1, unit='m') - assert parameter._callback.fget.call_count == 1 - - def test_full_value_no_match_callback(self, parameter: Parameter): - # When - self.mock_callback.fget.return_value = sc.scalar(2, unit='m') - - # Then Expect - assert parameter.full_value == sc.scalar(2, unit='m') - assert parameter._callback.fget.call_count == 1 - def test_set_full_value(self, parameter: Parameter): # When Then Expect with pytest.raises(AttributeError): diff --git a/tests/unit_tests/variable/test_parameter_dependency_serialization.py b/tests/unit_tests/variable/test_parameter_dependency_serialization.py new file mode 100644 index 00000000..8cf7abc0 --- /dev/null +++ b/tests/unit_tests/variable/test_parameter_dependency_serialization.py @@ -0,0 +1,500 @@ +import pytest +import json +from copy import deepcopy +from unittest.mock import Mock + +from easyscience import Parameter, global_object +from easyscience.variable.parameter_dependency_resolver import resolve_all_parameter_dependencies +from easyscience.variable.parameter_dependency_resolver import get_parameters_with_pending_dependencies +from easyscience.variable.parameter_dependency_resolver import deserialize_and_resolve_parameters + + +class TestParameterDependencySerialization: + + @pytest.fixture + def clear_global_map(self): + """This fixture pattern: + - Clears the map before each test (clean slate) + - Yields control to the test + - Clears the map after each test (cleanup) + + Dependency serialization tests require more robust + setup-yield-cleanup pattern because they involve complex + object lifecycles with serialization, deserialization, + and dependency resolution that are particularly sensitive + to global state contamination. + """ + # The global map uses weakref.WeakValueDictionary() for object storage, + # but also maintains strong references in __type_dict that need explicit cleanup. + global_object.map._clear() + yield + # final cleanup after test + global_object.map._clear() + + def test_independent_parameter_serialization(self, clear_global_map): + """Test that independent parameters serialize normally without dependency info.""" + param = Parameter(name="test", value=5.0, unit="m", min=0, max=10) + + # Serialize + serialized = param.as_dict() + + # Should not contain dependency fields + assert '_dependency_string' not in serialized + assert '_dependency_map_serializer_ids' not in serialized + assert '_independent' not in serialized + + # Deserialize + global_object.map._clear() + new_param = Parameter.from_dict(serialized) + + # Should be identical + assert new_param.name == param.name + assert new_param.value == param.value + assert new_param.unit == param.unit + assert new_param.independent is True + + def test_dependent_parameter_serialization(self, clear_global_map): + """Test serialization of parameters with dependencies.""" + # Create independent parameter + a = Parameter(name="a", value=2.0, unit="m", min=0, max=10) + + # Create dependent parameter + b = Parameter.from_dependency( + name="b", + dependency_expression="2 * a", + dependency_map={"a": a}, + unit="m" + ) + + # Serialize dependent parameter + serialized = b.as_dict() + + # Should contain dependency information + assert serialized['_dependency_string'] == "2 * a" + assert serialized['_dependency_map_serializer_ids'] == {"a": a._DescriptorNumber__serializer_id} + assert serialized['_independent'] is False + + # Deserialize + global_object.map._clear() + new_b = Parameter.from_dict(serialized) + + # Should have pending dependency info + assert hasattr(new_b, '_pending_dependency_string') + assert new_b._pending_dependency_string == "2 * a" + assert new_b._pending_dependency_map_serializer_ids == {"a": a._DescriptorNumber__serializer_id} + assert new_b.independent is True # Initially independent until dependencies resolved + + def test_dependency_resolution_after_deserialization(self, clear_global_map): + """Test that dependencies are properly resolved after deserialization.""" + # Create test parameters with dependencies + a = Parameter(name="a", value=2.0, unit="m", min=0, max=10) + b = Parameter(name="b", value=3.0, unit="m", min=0, max=10) + + c = Parameter.from_dependency( + name="c", + dependency_expression="a + b", + dependency_map={"a": a, "b": b}, + unit="m" + ) + + # Verify original dependency works + assert c.value == 5.0 # 2 + 3 + + # Serialize all parameters + params_data = { + "a": a.as_dict(), + "b": b.as_dict(), + "c": c.as_dict() + } + + # Clear and deserialize (manual approach) + global_object.map._clear() + new_params = {} + for name, data in params_data.items(): + new_params[name] = Parameter.from_dict(data) + + # Before resolution, c should be independent with pending dependency + assert new_params["c"].independent is True + assert hasattr(new_params["c"], '_pending_dependency_string') + + # Resolve dependencies + resolve_all_parameter_dependencies(new_params) + + # Alternative simplified approach using the helper function: + # global_object.map._clear() + # new_params = deserialize_and_resolve_parameters(params_data) + + # After resolution, c should be dependent and functional + assert new_params["c"].independent is False + assert new_params["c"].value == 5.0 # Still 2 + 3 + + # Test that dependency still works + new_params["a"].value = 10.0 + assert new_params["c"].value == 13.0 # 10 + 3 + + def test_unique_name_dependency_serialization(self, clear_global_map): + """Test serialization of dependencies using unique names.""" + a = Parameter(name="a", value=3.0, unit="m", min=0, max=10) + + # Create dependent parameter using unique name + b = Parameter.from_dependency( + name="b", + dependency_expression='2 * "Parameter_0"', # Using unique name + unit="m" + ) + + # Serialize both parameters + a_serialized = a.as_dict() + b_serialized = b.as_dict() + + # Should contain unique name mapping + assert b_serialized['_dependency_string'] == '2 * __Parameter_0__' + assert "__Parameter_0__" in b_serialized['_dependency_map_serializer_ids'] + assert b_serialized['_dependency_map_serializer_ids']["__Parameter_0__"] == a._DescriptorNumber__serializer_id + + # Deserialize both and resolve + global_object.map._clear() + c = Parameter(name='c', value=0.0) # Dummy to occupy unique name, to force new unique_names + + # Remove unique_name from serialized data to force generation of new unique names + a_serialized.pop('unique_name', None) + b_serialized.pop('unique_name', None) + + new_b = Parameter.from_dict(b_serialized) + new_a = Parameter.from_dict(a_serialized) + resolve_all_parameter_dependencies({"a": new_a, "b": new_b}) + + # Should work correctly + assert new_b.independent is False + new_a.value = 4.0 + assert new_b.value == 8.0 # 2 * 4 + + def test_json_serialization_roundtrip(self, clear_global_map): + """Test that parameter dependencies survive JSON serialization.""" + # Create parameters with dependencies + length = Parameter(name="length", value=10.0, unit="m", min=0, max=100) + width = Parameter(name="width", value=5.0, unit="m", min=0, max=50) + + area = Parameter.from_dependency( + name="area", + dependency_expression="length * width", + dependency_map={"length": length, "width": width}, + unit="m^2" + ) + + # Serialize to JSON + params_data = { + "length": length.as_dict(), + "width": width.as_dict(), + "area": area.as_dict() + } + json_str = json.dumps(params_data, default=str) + + # Deserialize from JSON + global_object.map._clear() + loaded_data = json.loads(json_str) + new_params = {} + for name, data in loaded_data.items(): + new_params[name] = Parameter.from_dict(data) + + # Resolve dependencies + resolve_all_parameter_dependencies(new_params) + + # Test functionality + assert new_params["area"].value == 50.0 # 10 * 5 + + # Test dependency updates + new_params["length"].value = 20.0 + assert new_params["area"].value == 100.0 # 20 * 5 + + def test_multiple_dependent_parameters(self, clear_global_map): + """Test serialization with multiple dependent parameters.""" + # Create a chain of dependencies + x = Parameter(name="x", value=2.0, unit="m", min=0, max=10) + + y = Parameter.from_dependency( + name="y", + dependency_expression="2 * x", + dependency_map={"x": x}, + unit="m" + ) + + z = Parameter.from_dependency( + name="z", + dependency_expression="y + x", + dependency_map={"y": y, "x": x}, + unit="m" + ) + + # Verify original chain works + assert y.value == 4.0 # 2 * 2 + assert z.value == 6.0 # 4 + 2 + + # Serialize all + params_data = { + "x": x.as_dict(), + "y": y.as_dict(), + "z": z.as_dict() + } + + # Deserialize and resolve + global_object.map._clear() + new_params = {} + for name, data in params_data.items(): + new_params[name] = Parameter.from_dict(data) + + resolve_all_parameter_dependencies(new_params) + + # Test chain still works + assert new_params["y"].value == 4.0 + assert new_params["z"].value == 6.0 + + # Test cascade updates + new_params["x"].value = 5.0 + assert new_params["y"].value == 10.0 # 2 * 5 + assert new_params["z"].value == 15.0 # 10 + 5 + + def test_dependency_with_descriptor_number(self, clear_global_map): + """Test that dependencies involving DescriptorNumber serialize correctly.""" + from easyscience.variable import DescriptorNumber + # When + + x = DescriptorNumber(name="x", value=3.0, unit="m") + y = Parameter(name="y", value=4.0, unit="m") + z = Parameter.from_dependency( + name="z", + dependency_expression="x + y", + dependency_map={"x": x, "y": y}, + ) + + # Verify original functionality + assert z.value == 7.0 # 3 + 4 + + # Then + # Serialize all + params_data = { + "x": x.as_dict(), + "y": y.as_dict(), + "z": z.as_dict() + } + # Deserialize and resolve + global_object.map._clear() + new_params = {} + for name, data in params_data.items(): + if name == "x": + new_params[name] = DescriptorNumber.from_dict(data) + else: + new_params[name] = Parameter.from_dict(data) + + resolve_all_parameter_dependencies(new_params) + + # Expect + # Test that functionality still works + assert new_params["z"].value == 7.0 # 3 + 4 + new_x = new_params["x"] + new_y = new_params["y"] + new_x.value = 4.0 + assert new_params["z"].value == 8.0 # 4 + 4 + new_y.value = 6.0 + assert new_params["z"].value == 10.0 # 4 + 6 + + def test_get_parameters_with_pending_dependencies(self, clear_global_map): + """Test utility function for finding parameters with pending dependencies.""" + # Create parameters + a = Parameter(name="a", value=1.0, unit="m") + b = Parameter.from_dependency( + name="b", + dependency_expression="2 * a", + dependency_map={"a": a}, + unit="m" + ) + + # Serialize and deserialize + params_data = {"a": a.as_dict(), "b": b.as_dict()} + global_object.map._clear() + new_params = {} + for name, data in params_data.items(): + new_params[name] = Parameter.from_dict(data) + + # Find pending dependencies + pending = get_parameters_with_pending_dependencies(new_params) + + assert len(pending) == 1 + assert pending[0].name == "b" + assert hasattr(pending[0], '_pending_dependency_string') + + # After resolution, should be empty + resolve_all_parameter_dependencies(new_params) + pending_after = get_parameters_with_pending_dependencies(new_params) + assert len(pending_after) == 0 + + def test_error_handling_missing_dependency(self, clear_global_map): + """Test error handling when dependency cannot be resolved.""" + a = Parameter(name="a", value=1.0, unit="m") + b = Parameter.from_dependency( + name="b", + dependency_expression="2 * a", + dependency_map={"a": a}, + unit="m" + ) + + # Serialize b but not a + b_data = b.as_dict() + + # Deserialize without a in the global map + global_object.map._clear() + new_b = Parameter.from_dict(b_data) + + # Should raise error when trying to resolve + with pytest.raises(ValueError, match="Cannot find parameter with serializer_id"): + new_b.resolve_pending_dependencies() + + def test_backward_compatibility_base_deserializer(self, clear_global_map): + """Test that the base deserializer path still works for dependent parameters.""" + from easyscience.io.serializer_dict import SerializerDict + + # Create dependent parameter + a = Parameter(name="a", value=2.0, unit="m") + b = Parameter.from_dependency( + name="b", + dependency_expression="3 * a", + dependency_map={"a": a}, + unit="m" + ) + + # Use base serializer path (SerializerDict.decode) + serialized = b.encode(encoder=SerializerDict) + global_object.map._clear() + + # This should not raise the "_independent" error anymore + deserialized = SerializerDict.decode(serialized) + + # Should be a valid Parameter (but without dependency resolution) + assert isinstance(deserialized, Parameter) + assert deserialized.name == "b" + assert deserialized.independent is True # Base path doesn't handle dependencies + + @pytest.mark.parametrize("order", [ + ["x", "y", "z"], + ["z", "x", "y"], + ["y", "z", "x"], + ["z", "y", "x"] + ], ids=['normal_order', 'dependent_first', 'mixed_order', 'dependent_first_reverse']) + def test_serializer_id_system_order_independence(self, clear_global_map, order): + """Test that dependency IDs allow parameters to be loaded in any order.""" + # WHEN + # Create parameters with dependencies + x = Parameter(name="x", value=5.0, unit="m", min=0, max=20) + y = Parameter(name="y", value=10.0, unit="m", min=0, max=30) + + z = Parameter.from_dependency( + name="z", + dependency_expression="x * y", + dependency_map={"x": x, "y": y}, + unit="m^2" + ) + + # Verify original functionality + assert z.value == 50.0 # 5 * 10 + + # Get dependency IDs + x_dep_id = x._DescriptorNumber__serializer_id + y_dep_id = y._DescriptorNumber__serializer_id + + # Serialize all parameters + params_data = { + "x": x.as_dict(), + "y": y.as_dict(), + "z": z.as_dict() + } + + # Verify dependency IDs are in serialized data + assert params_data["x"]["__serializer_id"] == x_dep_id + assert params_data["y"]["__serializer_id"] == y_dep_id + assert "__serializer_id" not in params_data["z"] + assert "_dependency_map_serializer_ids" in params_data["z"] + + # THEN + global_object.map._clear() + new_params = {} + + # Load in the specified order + for name in order: + new_params[name] = Parameter.from_dict(params_data[name]) + + # EXPECT + # Verify dependency IDs are preserved + assert new_params["x"]._DescriptorNumber__serializer_id == x_dep_id + assert new_params["y"]._DescriptorNumber__serializer_id == y_dep_id + + # Resolve dependencies + resolve_all_parameter_dependencies(new_params) + + # Verify functionality regardless of loading order + assert new_params["z"].independent is False + assert new_params["z"].value == 50.0 + + # Test dependency updates still work + new_params["x"].value = 6.0 + assert new_params["z"].value == 60.0 # 6 * 10 + + new_params["y"].value = 8.0 + assert new_params["z"].value == 48.0 # 6 * 8 + + def test_deserialize_and_resolve_parameters_helper(self, clear_global_map): + """Test the convenience helper function for deserialization and dependency resolution.""" + # Create test parameters with dependencies + a = Parameter(name="a", value=2.0, unit="m", min=0, max=10) + b = Parameter(name="b", value=3.0, unit="m", min=0, max=10) + + c = Parameter.from_dependency( + name="c", + dependency_expression="a + b", + dependency_map={"a": a, "b": b}, + unit="m" + ) + + # Verify original dependency works + assert c.value == 5.0 # 2 + 3 + + # Serialize all parameters + params_data = { + "a": a.as_dict(), + "b": b.as_dict(), + "c": c.as_dict() + } + + # Clear global map + global_object.map._clear() + + # Use the helper function instead of manual deserialization + resolution + new_params = deserialize_and_resolve_parameters(params_data) + + # Verify all parameters are correctly deserialized and dependencies resolved + assert len(new_params) == 3 + assert "a" in new_params + assert "b" in new_params + assert "c" in new_params + + # Check that independent parameters work + assert new_params["a"].name == "a" + assert new_params["a"].value == 2.0 + assert new_params["a"].independent is True + + assert new_params["b"].name == "b" + assert new_params["b"].value == 3.0 + assert new_params["b"].independent is True + + # Check that dependent parameter is properly resolved + assert new_params["c"].name == "c" + assert new_params["c"].value == 5.0 # 2 + 3 + assert new_params["c"].independent is False + + # Verify dependency still works after helper function + new_params["a"].value = 10.0 + assert new_params["c"].value == 13.0 # 10 + 3 + + # Verify no pending dependencies remain + pending = get_parameters_with_pending_dependencies(new_params) + assert len(pending) == 0 +