From a0aadea7c504def2fd81e1b65457071f66c7190a Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Wed, 19 Nov 2025 12:15:33 +0100 Subject: [PATCH 01/24] Create new easyscience base class and model_base class. --- src/easyscience/base_classes/based_base.py | 15 +++ src/easyscience/base_classes/model_base.py | 112 ++++++++++++++++ src/easyscience/base_classes/new_base.py | 148 +++++++++++++++++++++ src/easyscience/io/serializer_base.py | 29 ++++ 4 files changed, 304 insertions(+) create mode 100644 src/easyscience/base_classes/model_base.py create mode 100644 src/easyscience/base_classes/new_base.py diff --git a/src/easyscience/base_classes/based_base.py b/src/easyscience/base_classes/based_base.py index de72286a..d1820714 100644 --- a/src/easyscience/base_classes/based_base.py +++ b/src/easyscience/base_classes/based_base.py @@ -5,6 +5,8 @@ # © 2021-2025 Contributors to the EasyScience project List[Parameter]: fit_list.append(item) return fit_list + def as_dict(self, skip: Optional[List[str]] = None) -> Dict[str, Any]: + """ + Encode the object as a dictionary. + + :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. + """ + if skip: + skip = skip + ['unique_name'] + else: + skip = ['unique_name'] + return super().as_dict(skip=skip) + def __dir__(self) -> Iterable[str]: """ This creates auto-completion and helps out in iPython notebooks. diff --git a/src/easyscience/base_classes/model_base.py b/src/easyscience/base_classes/model_base.py new file mode 100644 index 00000000..91b7ba7a --- /dev/null +++ b/src/easyscience/base_classes/model_base.py @@ -0,0 +1,112 @@ +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_parameters(self) -> List[DescriptorNumber]: + """ + Get all `Parameters` or `DescriptorNumber` objects as a list. + + :return: List of `DescriptorNumber` or `Parameter` objects. + """ + params = [] + for attr_name in dir(self): + attr = getattr(self, attr_name) + if isinstance(attr, DescriptorNumber): + params.append(attr) + elif hasattr(attr, 'get_all_parameters'): + params += attr.get_all_parameters() + return params + + def get_fit_parameters(self) -> List[Parameter]: + """ + Get all parameters which can be fitted as a list. + + :return: List of `Parameter` objects. + """ + params = [] + for attr_name in dir(self): + attr = getattr(self, attr_name) + if isinstance(attr, Parameter) and attr.independent: + params.append(attr) + elif hasattr(attr, 'get_fit_parameters'): + params += attr.get_fit_parameters() + return params + + 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. + """ + params = [] + for attr_name in dir(self): + attr = getattr(self, attr_name) + if isinstance(attr, Parameter) and not attr.fixed and attr.independent: + params.append(attr) + return params + + @classmethod + def from_dict(cls, obj_dict: Dict[str, Any]) -> None: + """ + 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 isinstance(obj_dict, dict): + if '@module' in obj_dict and obj_dict['@module'].startswith('easy'): + if '@class' in obj_dict and 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(): + temp_param = getattr(cls_instance, key) + setattr(cls_instance, '_'+key, value) + cls_instance._global_object.map.prune(temp_param.unique_name) + return cls_instance + else: + raise ValueError(f'Class name not in dictionary or does not match the expected class: {cls.__name__}.') + else: + raise ValueError('Dictionary does not represent an EasyScience object.') + else: + raise TypeError('Input must be a dictionary.') \ No newline at end of file diff --git a/src/easyscience/base_classes/new_base.py b/src/easyscience/base_classes/new_base.py new file mode 100644 index 00000000..b331d454 --- /dev/null +++ b/src/easyscience/base_classes/new_base.py @@ -0,0 +1,148 @@ +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. + """ + base_cls = getattr(self, '__old_class__', self.__class__) + spec = getfullargspec(base_cls.__init__) + names = set(spec.args[1:]) + return names + + @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) + + @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: + """ + 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 as_dict(self, skip: Optional[List[str]] = None) -> Dict[str, Any]: + """ + Convert an EasyScience 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. + """ + serializer = SerializerBase() + if skip: + skip = skip + ['unique_name'] + else: + skip = ['unique_name'] + return serializer._convert_to_dict(self, skip=skip, full_encode=False) + + @classmethod + def from_dict(cls, obj_dict: Dict[str, Any]) -> None: + """ + 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 isinstance(obj_dict, dict): + if '@module' in obj_dict and obj_dict['@module'].startswith('easy'): + if '@class' in obj_dict and obj_dict['@class'] == cls.__name__: + kwargs = SerializerBase._deserialize_dict(obj_dict) + return cls(**kwargs) + else: + raise ValueError(f'Class name not in dictionary or does not match the expected class: {cls.__name__}.') + else: + raise ValueError('Dictionary does not represent an EasyScience object.') + else: + raise TypeError('Input must be a dictionary.') + + 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.as_dict(skip=['unique_name']) + new_obj = self.__class__.from_dict(temp) + return new_obj + + def __deepcopy__(self, memo): + return self.from_dict(self.as_dict()) + + def __repr__(self) -> str: + return f'{self.__class__.__name__} `{self.unique_name}`' + \ No newline at end of file diff --git a/src/easyscience/io/serializer_base.py b/src/easyscience/io/serializer_base.py index 37a31045..fb014215 100644 --- a/src/easyscience/io/serializer_base.py +++ b/src/easyscience/io/serializer_base.py @@ -260,6 +260,35 @@ 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]) -> None: + """ + Deserialize a dictionary using from_dict for EasyScience objects and SerializerBase otherwise. + :param in_dict: dictionary to deserialize + :return: deserialized dictionary + """ + out_dict = {} + for key, value in in_dict.items(): + if not key.startswith('@'): + if isinstance(value, dict) and "@module" in value and value["@module"].startswith("easy") and '@class' in value: # noqa: E501 + module_name = value['@module'] + class_name = value['@class'] + 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 hasattr(module, class_name): + cls_ = getattr(module, class_name) + if hasattr(cls_, 'from_dict'): + out_dict[key] = cls_.from_dict(value) + else: + out_dict[key] = SerializerBase._convert_from_dict(value) + else: + raise ValueError(f'Class {class_name} not found in module {module_name}.') + else: + out_dict[key] = SerializerBase._convert_from_dict(value) + return out_dict + def _recursive_encoder(self, obj, skip: List[str] = [], encoder=None, full_encode=False, **kwargs): """ Walk through an object encoding it From 952da19a8d81eed650228948cbb3cf899f9c2055 Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Wed, 19 Nov 2025 14:34:07 +0100 Subject: [PATCH 02/24] Constraints serialization (#148) Co-authored-by: Christian Dam Vedel <158568093+damskii9992@users.noreply.github.com> --- .github/workflows/ossar-analysis.yml | 10 - PARAMETER_DEPENDENCY_SERIALIZATION.md | 216 ++++++++ src/easyscience/base_classes/based_base.py | 17 + src/easyscience/global_object/map.py | 2 +- src/easyscience/variable/descriptor_number.py | 12 + src/easyscience/variable/parameter.py | 102 +++- .../variable/parameter_dependency_resolver.py | 147 +++++ .../unit_tests/base_classes/test_obj_base.py | 1 - ...test_parameter_dependency_serialization.py | 500 ++++++++++++++++++ 9 files changed, 992 insertions(+), 15 deletions(-) create mode 100644 PARAMETER_DEPENDENCY_SERIALIZATION.md create mode 100644 src/easyscience/variable/parameter_dependency_resolver.py create mode 100644 tests/unit_tests/variable/test_parameter_dependency_serialization.py 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/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/src/easyscience/base_classes/based_base.py b/src/easyscience/base_classes/based_base.py index de72286a..b575b486 100644 --- a/src/easyscience/base_classes/based_base.py +++ b/src/easyscience/base_classes/based_base.py @@ -5,6 +5,8 @@ # © 2021-2025 Contributors to the EasyScience project BasedBase: temp = self.as_dict(skip=['unique_name']) 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/global_object/map.py b/src/easyscience/global_object/map.py index 53a3aac4..0f29c1bc 100644 --- a/src/easyscience/global_object/map.py +++ b/src/easyscience/global_object/map.py @@ -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/variable/descriptor_number.py b/src/easyscience/variable/descriptor_number.py index d798f0a7..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 @@ -52,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 @@ -67,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: @@ -113,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.""" @@ -326,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 94415aaa..da5dfe59 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 @@ -72,6 +75,11 @@ def __init__( .. 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): @@ -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 @@ -123,7 +132,11 @@ def from_dependency( :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 @@ -554,6 +567,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. @@ -601,6 +633,31 @@ def _process_dependency_unique_names(self, dependency_expression: str): ) # 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() @@ -928,9 +985,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/unit_tests/base_classes/test_obj_base.py b/tests/unit_tests/base_classes/test_obj_base.py index 8a572eef..f004e9af 100644 --- a/tests/unit_tests/base_classes/test_obj_base.py +++ b/tests/unit_tests/base_classes/test_obj_base.py @@ -157,7 +157,6 @@ def test_ObjBase_as_dict(clear, setup_pars: dict): "@class": "ObjBase", "@version": easyscience.__version__, "name": "test", - "unique_name": "ObjBase_0", "par1": { "@module": Parameter.__module__, "@class": Parameter.__name__, 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 + From b5169f5737ef5e40e4b2189f88b3bb5e5d3ee57b Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Wed, 19 Nov 2025 19:50:09 +0100 Subject: [PATCH 03/24] Interface factory unit tests (#151) --- .github/workflows/python-ci.yml | 17 +- pixi.lock | 465 ++++++------ src/easyscience/global_object/map.py | 6 +- .../fitting/calculators/__init__.py | 4 + .../calculators/test_interface_factory.py | 540 ++++++++++++++ .../test_entry_list_comprehensive.py | 320 ++++++++ .../global_object/test_global_object.py | 129 ++++ .../test_integration_comprehensive.py | 461 ++++++++++++ tests/unit_tests/global_object/test_map.py | 408 ++++++++++- .../test_undo_redo_comprehensive.py | 686 ++++++++++++++++++ tests/unit_tests/io/test_serializer_base.py | 610 ++++++++++++++++ tests/unit_tests/models/test_polynomial.py | 165 ++++- tests/unit_tests/variable/test_parameter.py | 18 - 13 files changed, 3581 insertions(+), 248 deletions(-) create mode 100644 tests/unit_tests/fitting/calculators/__init__.py create mode 100644 tests/unit_tests/fitting/calculators/test_interface_factory.py create mode 100644 tests/unit_tests/global_object/test_entry_list_comprehensive.py create mode 100644 tests/unit_tests/global_object/test_integration_comprehensive.py create mode 100644 tests/unit_tests/global_object/test_undo_redo_comprehensive.py create mode 100644 tests/unit_tests/io/test_serializer_base.py diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml index 8ff4b058..80ab2a27 100644 --- a/.github/workflows/python-ci.yml +++ b/.github/workflows/python-ci.yml @@ -65,15 +65,16 @@ jobs: - 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: diff --git a/pixi.lock b/pixi.lock index 60fe0002..a1627563 100644 --- a/pixi.lock +++ b/pixi.lock @@ -11,25 +11,27 @@ environments: - 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.10.5-hbd8a1cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.44-ha97dd6f_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.44-h1aa0949_4.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.1-hecca717_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/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/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.50.4-h0c1763c_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/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.5.4-h26f9b46_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.9-h2b335a9_100_cp313.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_hd72426e_102.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/cc/e6/33d305e6cce0a8daeb79c7d8d6547d6e5f27f4e35fa4883fc9c9eb638596/aiohttp-3.13.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f7/0d/4764669bdf47bd472899b3d3db91fffbe925c8e3038ec591a2fd2ad6a14d/aiohttp-3.13.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl @@ -37,7 +39,7 @@ environments: - 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/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-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 @@ -57,7 +59,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/f5/b7/339b9ed180c28418f3c5c425f341759ce3722b61cc54f8c20918a034a1d5/mpld3-0.5.11-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/13/34/00c7ae8194074ed82b64e0bb7c24220eac5f77ac90c16e23cf0d2cfd2a03/narwhals-2.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/62/cd/9481a199a086ac9f91eaa232b56cff90ca7fdc2cb6658de93825b1007094/narwhals-2.10.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/7e/7d306ff7cb143e6d975cfa7eb98a93e73495c4deabb7d1b5ecf09ea0fd69/numpy-2.3.4-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 @@ -70,10 +72,10 @@ environments: - 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/d7/5b/821733876bad200638237d1cab671b46cfdafb73c0b4094f59c5947155ae/python_socketio-5.14.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/1a/b393a06aa6f2f6ab4a9c5c160a62d488b17d6da5cf93a67bc13a6e3239cd/python_socketio-5.14.3-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/c3/cf/4c3853de982ba47f94493c90fb4bf0a2164ca5b48a769b4facd6c28c9f6b/scipp-25.8.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/da/6a/1a927b14ddc7714111ea51f4e568203b2bb6ed59bdd036d62127c1a360c8/scipy-1.16.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_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/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 @@ -88,20 +90,20 @@ environments: - 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.10.5-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.7.1-h21dd04a_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.4.6-h281671d_1.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.50.4-h39a8b3b_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.5.4-h230baf5_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.13.9-h2bd861f_100_cp313.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_2.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/92/c8/1cf495bac85cf71b80fad5f6d7693e84894f11b9fe876b64b0a1e7cbf32f/aiohttp-3.13.1-cp313-cp313-macosx_10_13_x86_64.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/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl @@ -109,7 +111,7 @@ environments: - 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/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-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 @@ -129,7 +131,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/f5/b7/339b9ed180c28418f3c5c425f341759ce3722b61cc54f8c20918a034a1d5/mpld3-0.5.11-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/13/34/00c7ae8194074ed82b64e0bb7c24220eac5f77ac90c16e23cf0d2cfd2a03/narwhals-2.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/62/cd/9481a199a086ac9f91eaa232b56cff90ca7fdc2cb6658de93825b1007094/narwhals-2.10.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/7e/b72610cc91edf138bc588df5150957a4937221ca6058b825b4725c27be62/numpy-2.3.4-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 @@ -142,10 +144,10 @@ environments: - 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/d7/5b/821733876bad200638237d1cab671b46cfdafb73c0b4094f59c5947155ae/python_socketio-5.14.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/1a/b393a06aa6f2f6ab4a9c5c160a62d488b17d6da5cf93a67bc13a6e3239cd/python_socketio-5.14.3-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/88/3b/1815d8914c70257173ec307d3b7f8016c878a1542a195a18de25c63e2ec7/scipp-25.8.0-cp313-cp313-macosx_11_0_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/c1/27/c5b52f1ee81727a9fc457f5ac1e9bf3d6eab311805ea615c83c27ba06400/scipy-1.16.2-cp313-cp313-macosx_10_14_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/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 @@ -161,20 +163,20 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-75.1-hfee45f7_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.1-hec049ff_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.4.6-h1da3d7d_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/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.50.4-h4237e3c_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.5.4-h5503f6c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.9-h09175d0_100_cp313.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_2.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/a8/19/23c6b81cca587ec96943d977a58d11d05a82837022e65cd5502d665a7d11/aiohttp-3.13.1-cp313-cp313-macosx_11_0_arm64.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/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl @@ -182,7 +184,7 @@ environments: - 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/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-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 @@ -202,7 +204,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/f5/b7/339b9ed180c28418f3c5c425f341759ce3722b61cc54f8c20918a034a1d5/mpld3-0.5.11-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/13/34/00c7ae8194074ed82b64e0bb7c24220eac5f77ac90c16e23cf0d2cfd2a03/narwhals-2.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/62/cd/9481a199a086ac9f91eaa232b56cff90ca7fdc2cb6658de93825b1007094/narwhals-2.10.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3e/46/bdd3370dcea2f95ef14af79dbf81e6927102ddf1cc54adc0024d61252fd9/numpy-2.3.4-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 @@ -215,10 +217,10 @@ environments: - 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/d7/5b/821733876bad200638237d1cab671b46cfdafb73c0b4094f59c5947155ae/python_socketio-5.14.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/1a/b393a06aa6f2f6ab4a9c5c160a62d488b17d6da5cf93a67bc13a6e3239cd/python_socketio-5.14.3-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/9b/70/10c2358156874a645f5556c4bdcddd01cf10c9b73b15434e04512be5101b/scipp-25.8.0-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/32/a9/15c20d08e950b540184caa8ced675ba1128accb0e09c653780ba023a4110/scipy-1.16.2-cp313-cp313-macosx_12_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/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 @@ -233,13 +235,13 @@ environments: - 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.10.5-h4c7d964_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.1-hac47afa_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.4.6-h537db12_1.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.50.4-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.5.4-h725018a_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.9-hdf00ec1_100_cp313.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_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda @@ -248,7 +250,7 @@ environments: - 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/cc/00/f3a92c592a845ebb2f47d102a67f35f0925cb854c5e7386f1a3a1fdff2ab/aiohttp-3.13.1-cp313-cp313-win_amd64.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/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl @@ -256,7 +258,7 @@ environments: - 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/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-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 @@ -277,7 +279,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/f5/b7/339b9ed180c28418f3c5c425f341759ce3722b61cc54f8c20918a034a1d5/mpld3-0.5.11-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/13/34/00c7ae8194074ed82b64e0bb7c24220eac5f77ac90c16e23cf0d2cfd2a03/narwhals-2.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/62/cd/9481a199a086ac9f91eaa232b56cff90ca7fdc2cb6658de93825b1007094/narwhals-2.10.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/97/fd421e8bc50766665ad35536c2bb4ef916533ba1fdd053a62d96cc7c8b95/numpy-2.3.4-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 @@ -290,10 +292,10 @@ environments: - 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/d7/5b/821733876bad200638237d1cab671b46cfdafb73c0b4094f59c5947155ae/python_socketio-5.14.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/1a/b393a06aa6f2f6ab4a9c5c160a62d488b17d6da5cf93a67bc13a6e3239cd/python_socketio-5.14.3-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/ca/3b/8d206694f1dfe8c6305d8c13d9d71fcb3962543b99d6ca8df76fcee5964d/scipp-25.8.0-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/a1/57/0f38e396ad19e41b4c5db66130167eef8ee620a49bc7d0512e3bb67e0cab/scipy-1.16.2-cp313-cp313-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/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 @@ -315,26 +317,28 @@ environments: - 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.10.5-hbd8a1cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.44-ha97dd6f_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.44-h1aa0949_4.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.1-hecca717_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/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/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.50.4-h0c1763c_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/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.5.4-h26f9b46_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.9-h2b335a9_100_cp313.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_hd72426e_102.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.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/cc/e6/33d305e6cce0a8daeb79c7d8d6547d6e5f27f4e35fa4883fc9c9eb638596/aiohttp-3.13.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f7/0d/4764669bdf47bd472899b3d3db91fffbe925c8e3038ec591a2fd2ad6a14d/aiohttp-3.13.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl @@ -349,7 +353,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.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/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-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/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/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl @@ -380,8 +384,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/f5/b7/339b9ed180c28418f3c5c425f341759ce3722b61cc54f8c20918a034a1d5/mpld3-0.5.11-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/13/34/00c7ae8194074ed82b64e0bb7c24220eac5f77ac90c16e23cf0d2cfd2a03/narwhals-2.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/11/0e/16b3886858b3953ef836dea25b951f3ab0c5b5a431da03f675c0e999afb8/nh3-0.3.1-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/62/cd/9481a199a086ac9f91eaa232b56cff90ca7fdc2cb6658de93825b1007094/narwhals-2.10.1-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/9e/7e/7d306ff7cb143e6d975cfa7eb98a93e73495c4deabb7d1b5ecf09ea0fd69/numpy-2.3.4-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 @@ -402,15 +406,15 @@ environments: - 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/d7/5b/821733876bad200638237d1cab671b46cfdafb73c0b4094f59c5947155ae/python_socketio-5.14.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/1a/b393a06aa6f2f6ab4a9c5c160a62d488b17d6da5cf93a67bc13a6e3239cd/python_socketio-5.14.3-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/e1/67/921ec3024056483db83953ae8e48079ad62b92db7880013ca77632921dd0/readme_renderer-44.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/48/9c/6d8035cafa2d2d314f34e6cd9313a299de095b26e96f1c7312878f988eec/restructuredtext_lint-1.4.0.tar.gz - 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/e0/35/266a80d0eb97bd224b3265b9437bd89dde0dcf4faf299db1212e81824e7e/ruff-0.14.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/c3/cf/4c3853de982ba47f94493c90fb4bf0a2164ca5b48a769b4facd6c28c9f6b/scipp-25.8.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/da/6a/1a927b14ddc7714111ea51f4e568203b2bb6ed59bdd036d62127c1a360c8/scipy-1.16.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a7/e7/138b883f0dfe4ad5b76b58bf4ae675f4d2176ac2b24bdd81b4d966b28c61/ruff-0.14.3-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/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 @@ -429,14 +433,14 @@ environments: - pypi: https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/80/c5/0c06759b95747882bb50abda18f5fb48c3e9b0fbfc6ebc0e23550b52415d/stevedore-5.5.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/3e/84/1691aae773dccff72c866ad19af7adb12d4fb8b439c8bfb36ffc429c8c27/tox-4.31.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/40/17/3dd6f646da0c2b5f47950f6a8259082f0e5de1a6a2f2d8e91dae6ff8efb0/tox_gh_actions-3.4.0-py3-none-any.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/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-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/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/27/73/d9a94da0e9d470a543c1b9d3ccbceb0f59455983088e727b8a1824ed90fb/virtualenv-20.35.3-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/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/c9/cd8538dc2e7727095e0c1d867bad1e40c98f37763e6d995c1939f5fdc7b1/yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: ./ @@ -444,21 +448,21 @@ environments: - 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.10.5-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.7.1-h21dd04a_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.4.6-h281671d_1.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.50.4-h39a8b3b_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.5.4-h230baf5_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.13.9-h2bd861f_100_cp313.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_2.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/92/c8/1cf495bac85cf71b80fad5f6d7693e84894f11b9fe876b64b0a1e7cbf32f/aiohttp-3.13.1-cp313-cp313-macosx_10_13_x86_64.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/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl @@ -473,7 +477,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.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/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-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/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/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl @@ -504,8 +508,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/f5/b7/339b9ed180c28418f3c5c425f341759ce3722b61cc54f8c20918a034a1d5/mpld3-0.5.11-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/13/34/00c7ae8194074ed82b64e0bb7c24220eac5f77ac90c16e23cf0d2cfd2a03/narwhals-2.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e1/28/a387fed70438d2810c8ac866e7b24bf1a5b6f30ae65316dfe4de191afa52/nh3-0.3.1-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl + - pypi: https://files.pythonhosted.org/packages/62/cd/9481a199a086ac9f91eaa232b56cff90ca7fdc2cb6658de93825b1007094/narwhals-2.10.1-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/57/7e/b72610cc91edf138bc588df5150957a4937221ca6058b825b4725c27be62/numpy-2.3.4-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 @@ -526,15 +530,15 @@ environments: - 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/d7/5b/821733876bad200638237d1cab671b46cfdafb73c0b4094f59c5947155ae/python_socketio-5.14.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/1a/b393a06aa6f2f6ab4a9c5c160a62d488b17d6da5cf93a67bc13a6e3239cd/python_socketio-5.14.3-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/e1/67/921ec3024056483db83953ae8e48079ad62b92db7880013ca77632921dd0/readme_renderer-44.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/48/9c/6d8035cafa2d2d314f34e6cd9313a299de095b26e96f1c7312878f988eec/restructuredtext_lint-1.4.0.tar.gz - 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/ef/2e/1226961855ccd697255988f5a2474890ac7c5863b080b15bd038df820818/ruff-0.14.1-py3-none-macosx_10_12_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/88/3b/1815d8914c70257173ec307d3b7f8016c878a1542a195a18de25c63e2ec7/scipp-25.8.0-cp313-cp313-macosx_11_0_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/c1/27/c5b52f1ee81727a9fc457f5ac1e9bf3d6eab311805ea615c83c27ba06400/scipy-1.16.2-cp313-cp313-macosx_10_14_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/d3/c8/6724f4634c1daf52409fbf13fefda64aa9c8f81e44727a378b7b73dc590b/ruff-0.14.3-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/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 @@ -553,14 +557,14 @@ environments: - pypi: https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/80/c5/0c06759b95747882bb50abda18f5fb48c3e9b0fbfc6ebc0e23550b52415d/stevedore-5.5.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/3e/84/1691aae773dccff72c866ad19af7adb12d4fb8b439c8bfb36ffc429c8c27/tox-4.31.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/40/17/3dd6f646da0c2b5f47950f6a8259082f0e5de1a6a2f2d8e91dae6ff8efb0/tox_gh_actions-3.4.0-py3-none-any.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/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-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/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/27/73/d9a94da0e9d470a543c1b9d3ccbceb0f59455983088e727b8a1824ed90fb/virtualenv-20.35.3-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/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-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: ./ @@ -569,21 +573,21 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-75.1-hfee45f7_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.1-hec049ff_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.4.6-h1da3d7d_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/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.50.4-h4237e3c_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.5.4-h5503f6c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.9-h09175d0_100_cp313.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_2.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/a8/19/23c6b81cca587ec96943d977a58d11d05a82837022e65cd5502d665a7d11/aiohttp-3.13.1-cp313-cp313-macosx_11_0_arm64.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/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl @@ -598,7 +602,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.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/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-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/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/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl @@ -629,8 +633,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/f5/b7/339b9ed180c28418f3c5c425f341759ce3722b61cc54f8c20918a034a1d5/mpld3-0.5.11-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/13/34/00c7ae8194074ed82b64e0bb7c24220eac5f77ac90c16e23cf0d2cfd2a03/narwhals-2.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e1/28/a387fed70438d2810c8ac866e7b24bf1a5b6f30ae65316dfe4de191afa52/nh3-0.3.1-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl + - pypi: https://files.pythonhosted.org/packages/62/cd/9481a199a086ac9f91eaa232b56cff90ca7fdc2cb6658de93825b1007094/narwhals-2.10.1-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/3e/46/bdd3370dcea2f95ef14af79dbf81e6927102ddf1cc54adc0024d61252fd9/numpy-2.3.4-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 @@ -651,15 +655,15 @@ environments: - 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/d7/5b/821733876bad200638237d1cab671b46cfdafb73c0b4094f59c5947155ae/python_socketio-5.14.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/1a/b393a06aa6f2f6ab4a9c5c160a62d488b17d6da5cf93a67bc13a6e3239cd/python_socketio-5.14.3-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/e1/67/921ec3024056483db83953ae8e48079ad62b92db7880013ca77632921dd0/readme_renderer-44.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/48/9c/6d8035cafa2d2d314f34e6cd9313a299de095b26e96f1c7312878f988eec/restructuredtext_lint-1.4.0.tar.gz - 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/c1/ea/fd9e95863124ed159cd0667ec98449ae461de94acda7101f1acb6066da00/ruff-0.14.1-py3-none-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/9b/70/10c2358156874a645f5556c4bdcddd01cf10c9b73b15434e04512be5101b/scipp-25.8.0-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/32/a9/15c20d08e950b540184caa8ced675ba1128accb0e09c653780ba023a4110/scipy-1.16.2-cp313-cp313-macosx_12_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/de/03/db1bce591d55fd5f8a08bb02517fa0b5097b2ccabd4ea1ee29aa72b67d96/ruff-0.14.3-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/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 @@ -678,14 +682,14 @@ environments: - pypi: https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/80/c5/0c06759b95747882bb50abda18f5fb48c3e9b0fbfc6ebc0e23550b52415d/stevedore-5.5.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/3e/84/1691aae773dccff72c866ad19af7adb12d4fb8b439c8bfb36ffc429c8c27/tox-4.31.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/40/17/3dd6f646da0c2b5f47950f6a8259082f0e5de1a6a2f2d8e91dae6ff8efb0/tox_gh_actions-3.4.0-py3-none-any.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/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-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/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/27/73/d9a94da0e9d470a543c1b9d3ccbceb0f59455983088e727b8a1824ed90fb/virtualenv-20.35.3-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/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/18/91/3274b215fd8442a03975ce6bee5fe6aa57a8326b29b9d3d56234a1dca244/yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: ./ @@ -693,13 +697,13 @@ environments: - 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.10.5-h4c7d964_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.1-hac47afa_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.4.6-h537db12_1.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.50.4-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.5.4-h725018a_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.9-hdf00ec1_100_cp313.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_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda @@ -709,7 +713,7 @@ environments: - 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/cc/00/f3a92c592a845ebb2f47d102a67f35f0925cb854c5e7386f1a3a1fdff2ab/aiohttp-3.13.1-cp313-cp313-win_amd64.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/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl @@ -724,7 +728,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.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/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-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/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/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl @@ -755,8 +759,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/f5/b7/339b9ed180c28418f3c5c425f341759ce3722b61cc54f8c20918a034a1d5/mpld3-0.5.11-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/13/34/00c7ae8194074ed82b64e0bb7c24220eac5f77ac90c16e23cf0d2cfd2a03/narwhals-2.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a5/ca/263eb96b6d32c61a92c1e5480b7f599b60db7d7fbbc0d944be7532d0ac42/nh3-0.3.1-cp38-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/62/cd/9481a199a086ac9f91eaa232b56cff90ca7fdc2cb6658de93825b1007094/narwhals-2.10.1-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/09/97/fd421e8bc50766665ad35536c2bb4ef916533ba1fdd053a62d96cc7c8b95/numpy-2.3.4-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 @@ -777,15 +781,15 @@ environments: - 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/d7/5b/821733876bad200638237d1cab671b46cfdafb73c0b4094f59c5947155ae/python_socketio-5.14.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/1a/b393a06aa6f2f6ab4a9c5c160a62d488b17d6da5cf93a67bc13a6e3239cd/python_socketio-5.14.3-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/e1/67/921ec3024056483db83953ae8e48079ad62b92db7880013ca77632921dd0/readme_renderer-44.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/48/9c/6d8035cafa2d2d314f34e6cd9313a299de095b26e96f1c7312878f988eec/restructuredtext_lint-1.4.0.tar.gz - 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/02/88/0ee4ca507d4aa05f67e292d2e5eb0b3e358fbcfe527554a2eda9ac422d6b/ruff-0.14.1-py3-none-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/ca/3b/8d206694f1dfe8c6305d8c13d9d71fcb3962543b99d6ca8df76fcee5964d/scipp-25.8.0-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/a1/57/0f38e396ad19e41b4c5db66130167eef8ee620a49bc7d0512e3bb67e0cab/scipy-1.16.2-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/03/15/51960ae340823c9859fb60c63301d977308735403e2134e17d1d2858c7fb/ruff-0.14.3-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/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 @@ -804,14 +808,14 @@ environments: - pypi: https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/80/c5/0c06759b95747882bb50abda18f5fb48c3e9b0fbfc6ebc0e23550b52415d/stevedore-5.5.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/3e/84/1691aae773dccff72c866ad19af7adb12d4fb8b439c8bfb36ffc429c8c27/tox-4.31.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/40/17/3dd6f646da0c2b5f47950f6a8259082f0e5de1a6a2f2d8e91dae6ff8efb0/tox_gh_actions-3.4.0-py3-none-any.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/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-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/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/27/73/d9a94da0e9d470a543c1b9d3ccbceb0f59455983088e727b8a1824ed90fb/virtualenv-20.35.3-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/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9a/ee/450914ae11b419eadd067c6183ae08381cfdfcb9798b90b2b713bbebddda/yarl-1.22.0-cp313-cp313-win_amd64.whl - pypi: ./ @@ -857,10 +861,10 @@ packages: version: 2.6.1 sha256: f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/92/c8/1cf495bac85cf71b80fad5f6d7693e84894f11b9fe876b64b0a1e7cbf32f/aiohttp-3.13.1-cp313-cp313-macosx_10_13_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/5d/28/a8a9fc6957b2cee8902414e41816b5ab5536ecf43c3b1843c10e82c559b2/aiohttp-3.13.2-cp313-cp313-win_amd64.whl name: aiohttp - version: 3.13.1 - sha256: 4bef5b83296cebb8167707b4f8d06c1805db0af632f7a72d7c5288a84667e7c3 + version: 3.13.2 + sha256: a88d13e7ca367394908f8a276b89d04a3652044612b9a408a0bb22a5ed976a1a requires_dist: - aiohappyeyeballs>=2.5.0 - aiosignal>=1.4.0 @@ -875,10 +879,10 @@ packages: - 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/a8/19/23c6b81cca587ec96943d977a58d11d05a82837022e65cd5502d665a7d11/aiohttp-3.13.1-cp313-cp313-macosx_11_0_arm64.whl +- 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.1 - sha256: 27af0619c33f9ca52f06069ec05de1a357033449ab101836f431768ecfa63ff5 + version: 3.13.2 + sha256: 5276807b9de9092af38ed23ce120539ab0ac955547b38563a9ba4f5b07b95293 requires_dist: - aiohappyeyeballs>=2.5.0 - aiosignal>=1.4.0 @@ -893,10 +897,10 @@ packages: - 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/cc/00/f3a92c592a845ebb2f47d102a67f35f0925cb854c5e7386f1a3a1fdff2ab/aiohttp-3.13.1-cp313-cp313-win_amd64.whl +- 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.1 - sha256: ef56ffe60e8d97baac123272bde1ab889ee07d3419606fae823c80c2b86c403e + version: 3.13.2 + sha256: 088912a78b4d4f547a1f19c099d5a506df17eacec3c6f4375e2831ec1d995742 requires_dist: - aiohappyeyeballs>=2.5.0 - aiosignal>=1.4.0 @@ -911,10 +915,10 @@ packages: - 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/cc/e6/33d305e6cce0a8daeb79c7d8d6547d6e5f27f4e35fa4883fc9c9eb638596/aiohttp-3.13.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/f7/0d/4764669bdf47bd472899b3d3db91fffbe925c8e3038ec591a2fd2ad6a14d/aiohttp-3.13.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: aiohttp - version: 3.13.1 - sha256: 010dc9b7110f055006acd3648d5d5955bb6473b37c3663ec42a1b4cba7413e6b + version: 3.13.2 + sha256: ac6cde5fba8d7d8c6ac963dbb0256a9854e9fafff52fbcc58fdf819357892c3e requires_dist: - aiohappyeyeballs>=2.5.0 - aiosignal>=1.4.0 @@ -1132,10 +1136,10 @@ packages: version: 3.4.4 sha256: a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894 requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl name: cloudpickle - version: 3.1.1 - sha256: c8c5a44295039331ee9dad40ba100a9c7297b6f988e50e87ccdf3765a668350e + 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 @@ -1642,18 +1646,19 @@ packages: version: 1.4.9 sha256: b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098 requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.44-ha97dd6f_2.conda - sha256: 707dfb8d55d7a5c6f95c772d778ef07a7ca85417d9971796f7d3daad0b615de8 - md5: 14bae321b8127b63cba276bd53fac237 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.44-h1aa0949_4.conda + sha256: 96b6900ca0489d9e5d0318a6b49f8eff43fd85fef6e07cb0c25344ee94cd7a3a + md5: c94ab6ff54ba5172cf1c58267005670f depends: - __glibc >=2.17,<3.0.a0 + - zstd >=1.5.7,<1.6.0a0 constrains: - binutils_impl_linux-64 2.44 license: GPL-3.0-only license_family: GPL purls: [] - size: 747158 - timestamp: 1758810907507 + size: 742501 + timestamp: 1761335175964 - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.1-hecca717_0.conda sha256: da2080da8f0288b95dd86765c801c6e166c4619b910b11f9a8446fb852438dc2 md5: 4211416ecba1866fab0c6470986c22d6 @@ -1705,49 +1710,49 @@ packages: purls: [] size: 141322 timestamp: 1752719767870 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda - sha256: 764432d32db45466e87f10621db5b74363a9f847d2b8b1f9743746cd160f06ab - md5: ede4673863426c0883c0063d853bbd85 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h9ec8514_0.conda + sha256: 25cbdfa65580cfab1b8d15ee90b4c9f1e0d72128f1661449c9a999d341377d54 + md5: 35f29eec58405aaf55e01cb470d8c26a depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 + - libgcc >=14 license: MIT license_family: MIT purls: [] - size: 57433 - timestamp: 1743434498161 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.4.6-h281671d_1.conda - sha256: 6394b1bc67c64a21a5cc73d1736d1d4193a64515152e861785c44d2cfc49edf3 - md5: 4ca9ea59839a9ca8df84170fab4ceb41 + 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: 51216 - timestamp: 1743434595269 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.4.6-h1da3d7d_1.conda - sha256: c6a530924a9b14e193ea9adfe92843de2a806d1b7dbfd341546ece9653129e60 - md5: c215a60c2935b517dcda8cad4705734d + 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: 39839 - timestamp: 1743434670405 -- conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.4.6-h537db12_1.conda - sha256: d3b0b8812eab553d3464bbd68204f007f1ebadf96ce30eb0cbc5159f72e353f5 - md5: 85d8fa5e55ed8f93f874b3b23ed54ec6 + 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.2,<15 - - vc14_runtime >=14.29.30139 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 license: MIT license_family: MIT purls: [] - size: 44978 - timestamp: 1743435053850 + size: 44866 + timestamp: 1760295760649 - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-h767d61c_7.conda sha256: 08f9b87578ab981c7713e4e6a7d935e40766e10691732bba376d4964562bcb45 md5: c0374badb3a5d4b1372db28d19462c53 @@ -1905,6 +1910,19 @@ packages: purls: [] size: 1288499 timestamp: 1753948889360 +- 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/libuuid-2.41.2-he9a06e4_0.conda sha256: e5ec6d2ad7eef538ddcb9ea62ad4346fde70a4736342c4ad87bd713641eb9808 md5: 80c07c68d2f6870250959dcc95b209d1 @@ -2158,10 +2176,10 @@ packages: requires_dist: - typing-extensions>=4.1.0 ; python_full_version < '3.11' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/13/34/00c7ae8194074ed82b64e0bb7c24220eac5f77ac90c16e23cf0d2cfd2a03/narwhals-2.9.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/62/cd/9481a199a086ac9f91eaa232b56cff90ca7fdc2cb6658de93825b1007094/narwhals-2.10.1-py3-none-any.whl name: narwhals - version: 2.9.0 - sha256: c59f7de4763004ae81691ce16df71b4e55aead0ead7ccde8c8f2ef8c9559c765 + version: 2.10.1 + sha256: eed3d9ec8f821963456fef306c1ad11017995982169fca1f38f71c97d6a97b9b requires_dist: - cudf>=24.10.0 ; extra == 'cudf' - dask[dataframe]>=2024.8 ; extra == 'dask' @@ -2206,20 +2224,20 @@ packages: purls: [] size: 797030 timestamp: 1738196177597 -- pypi: https://files.pythonhosted.org/packages/11/0e/16b3886858b3953ef836dea25b951f3ab0c5b5a431da03f675c0e999afb8/nh3-0.3.1-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.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 name: nh3 - version: 0.3.1 - sha256: 94292dd1bd2a2e142fa5bb94c0ee1d84433a5d9034640710132da7e0376fca3a + version: 0.3.2 + sha256: 7bb18403f02b655a1bbe4e3a4696c2ae1d6ae8f5991f7cacb684b1ae27e6c9f7 requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/a5/ca/263eb96b6d32c61a92c1e5480b7f599b60db7d7fbbc0d944be7532d0ac42/nh3-0.3.1-cp38-abi3-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/64/9a/1a1c154f10a575d20dd634e5697805e589bbdb7673a0ad00e8da90044ba7/nh3-0.3.2-cp38-abi3-win_amd64.whl name: nh3 - version: 0.3.1 - sha256: c0acef923a1c3a2df3ee5825ea79c149b6748c6449781c53ab6923dc75e87d26 + version: 0.3.2 + sha256: 562da3dca7a17f9077593214a9781a94b8d76de4f158f8c895e62f09573945fe requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/e1/28/a387fed70438d2810c8ac866e7b24bf1a5b6f30ae65316dfe4de191afa52/nh3-0.3.1-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.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 name: nh3 - version: 0.3.1 - sha256: 1de5c1a35bed19a1b1286bab3c3abfe42e990a8a6c4ce9bb9ab4bde49107ea3b + version: 0.3.2 + sha256: 7064ccf5ace75825bd7bf57859daaaf16ed28660c1c6b306b649a9eda4b54b1e requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/09/97/fd421e8bc50766665ad35536c2bb4ef916533ba1fdd053a62d96cc7c8b95/numpy-2.3.4-cp313-cp313-win_amd64.whl name: numpy @@ -3007,16 +3025,16 @@ packages: - pytest-xdist ; extra == 'testing' - virtualenv ; extra == 'testing' requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.9-h2b335a9_100_cp313.conda - build_number: 100 - sha256: 317ee7a38f4cc97336a2aedf9c79e445adf11daa0d082422afa63babcd5117e4 - md5: 78ba5c3a7aecc68ab3a9f396d3b69d06 +- 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.4.6,<3.5.0a0 + - libffi >=3.5.2,<3.6.0a0 - libgcc >=14 - liblzma >=5.8.1,<6.0a0 - libmpdec >=4.0.0,<5.0a0 @@ -3031,18 +3049,18 @@ packages: - tzdata license: Python-2.0 purls: [] - size: 37323303 - timestamp: 1760612239629 + 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-h2bd861f_100_cp313.conda - build_number: 100 - sha256: c5ae352b7ac8412ed0e9ca8cac2f36d767e96d8e3efb014f47fd103be7447f22 - md5: 9f7e2b7871a35025f30a890492a36578 +- 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.4.6,<3.5.0a0 + - libffi >=3.5.2,<3.6.0a0 - liblzma >=5.8.1,<6.0a0 - libmpdec >=4.0.0,<5.0a0 - libsqlite >=3.50.4,<4.0a0 @@ -3055,18 +3073,18 @@ packages: - tzdata license: Python-2.0 purls: [] - size: 17336745 - timestamp: 1760613619143 + 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-h09175d0_100_cp313.conda - build_number: 100 - sha256: ba1121e96d034129832eff1bde7bba35f186acfc51fce1d7bacd29a544906f1b - md5: a2e4526d795a64fbd9581333482e07ee +- 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.4.6,<3.5.0a0 + - libffi >=3.5.2,<3.6.0a0 - liblzma >=5.8.1,<6.0a0 - libmpdec >=4.0.0,<5.0a0 - libsqlite >=3.50.4,<4.0a0 @@ -3079,17 +3097,17 @@ packages: - tzdata license: Python-2.0 purls: [] - size: 12802912 - timestamp: 1760613485744 + 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-hdf00ec1_100_cp313.conda - build_number: 100 - sha256: 2d6d9d5c641d4a11b5bef148813b83eef4e2dffd68e1033362bad85924837f29 - md5: 89c006f6748c7e0fc7950ae0c80df0d5 +- 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.4.6,<3.5.0a0 + - libffi >=3.5.2,<3.6.0a0 - liblzma >=5.8.1,<6.0a0 - libmpdec >=4.0.0,<5.0a0 - libsqlite >=3.50.4,<4.0a0 @@ -3103,8 +3121,8 @@ packages: - vc14_runtime >=14.44.35208 license: Python-2.0 purls: [] - size: 16503717 - timestamp: 1760610876821 + 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 @@ -3124,10 +3142,10 @@ packages: - aiohttp>=3.4 ; extra == 'asyncio-client' - sphinx ; extra == 'docs' requires_python: '>=3.6' -- pypi: https://files.pythonhosted.org/packages/d7/5b/821733876bad200638237d1cab671b46cfdafb73c0b4094f59c5947155ae/python_socketio-5.14.2-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/c0/1a/b393a06aa6f2f6ab4a9c5c160a62d488b17d6da5cf93a67bc13a6e3239cd/python_socketio-5.14.3-py3-none-any.whl name: python-socketio - version: 5.14.2 - sha256: d7133f040ac7ba540f9a907cb4c36d6c4d1d54eb35e482aad9d45c22fca10654 + version: 5.14.3 + sha256: a5208c1bbf45a8d6328d01ed67e3fa52ec8b186fd3ea44cfcfcbd120f0c71fbe requires_dist: - bidict>=0.21.0 - python-engineio>=4.11.0 @@ -3221,30 +3239,30 @@ packages: - pyright==1.1.394 ; extra == 'lint' - pytest>=8 ; extra == 'test' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/02/88/0ee4ca507d4aa05f67e292d2e5eb0b3e358fbcfe527554a2eda9ac422d6b/ruff-0.14.1-py3-none-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/03/15/51960ae340823c9859fb60c63301d977308735403e2134e17d1d2858c7fb/ruff-0.14.3-py3-none-win_amd64.whl name: ruff - version: 0.14.1 - sha256: 59d599cdff9c7f925a017f6f2c256c908b094e55967f93f2821b1439928746a1 + version: 0.14.3 + sha256: d7b7006ac0756306db212fd37116cce2bd307e1e109375e1c6c106002df0ae5f requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/c1/ea/fd9e95863124ed159cd0667ec98449ae461de94acda7101f1acb6066da00/ruff-0.14.1-py3-none-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/a7/e7/138b883f0dfe4ad5b76b58bf4ae675f4d2176ac2b24bdd81b4d966b28c61/ruff-0.14.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl name: ruff - version: 0.14.1 - sha256: d6191903d39ac156921398e9c86b7354d15e3c93772e7dbf26c9fcae59ceccd5 + version: 0.14.3 + sha256: 0e2f8a0bbcffcfd895df39c9a4ecd59bb80dca03dc43f7fb63e647ed176b741e requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/e0/35/266a80d0eb97bd224b3265b9437bd89dde0dcf4faf299db1212e81824e7e/ruff-0.14.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/d3/c8/6724f4634c1daf52409fbf13fefda64aa9c8f81e44727a378b7b73dc590b/ruff-0.14.3-py3-none-macosx_10_12_x86_64.whl name: ruff - version: 0.14.1 - sha256: cabcaa9ccf8089fb4fdb78d17cc0e28241520f50f4c2e88cb6261ed083d85151 + version: 0.14.3 + sha256: b6fd8c79b457bedd2abf2702b9b472147cd860ed7855c73a5247fa55c9117654 requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/ef/2e/1226961855ccd697255988f5a2474890ac7c5863b080b15bd038df820818/ruff-0.14.1-py3-none-macosx_10_12_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/de/03/db1bce591d55fd5f8a08bb02517fa0b5097b2ccabd4ea1ee29aa72b67d96/ruff-0.14.3-py3-none-macosx_11_0_arm64.whl name: ruff - version: 0.14.1 - sha256: f6fa757cd717f791009f7669fefb09121cc5f7d9bd0ef211371fad68c2b8b224 + version: 0.14.3 + sha256: 71ff6edca490c308f083156938c0c1a66907151263c4abdcb588602c6e696a14 requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/88/3b/1815d8914c70257173ec307d3b7f8016c878a1542a195a18de25c63e2ec7/scipp-25.8.0-cp313-cp313-macosx_11_0_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/64/7c/d8a343b0a622987335a1ac848084079c47c21e44fcc450f9c145b11a56f6/scipp-25.11.0-cp313-cp313-macosx_11_0_arm64.whl name: scipp - version: 25.8.0 - sha256: ca7f360197bde23a21b757c9f6b92a204807927f465caf31452b4899714e2cae + version: 25.11.0 + sha256: 9b082fc7fd29919b4fee007f63db265176becb06c669a4b709feb741b76c612a requires_dist: - numpy>=1.20 - pytest ; extra == 'test' @@ -3267,10 +3285,10 @@ packages: - nodejs ; extra == 'all' - pythreejs ; extra == 'all' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/9b/70/10c2358156874a645f5556c4bdcddd01cf10c9b73b15434e04512be5101b/scipp-25.8.0-cp313-cp313-macosx_11_0_arm64.whl +- 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.8.0 - sha256: 4b0df001f6fdcb697d64f50adfdc5dad1ba01ea30164ad22161d317072ad1c4f + version: 25.11.0 + sha256: fd7788e3ea3dd5334ed7fb53a249413469edc5d836ca5d68b1f271691cf11269 requires_dist: - numpy>=1.20 - pytest ; extra == 'test' @@ -3293,10 +3311,10 @@ packages: - nodejs ; extra == 'all' - pythreejs ; extra == 'all' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/c3/cf/4c3853de982ba47f94493c90fb4bf0a2164ca5b48a769b4facd6c28c9f6b/scipp-25.8.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_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 name: scipp - version: 25.8.0 - sha256: cc9343a1369922197a2e43bc0ce2457639ec7e61b06ec5938db4decdee422188 + version: 25.11.0 + sha256: 3e91c8093dc83d433a2e53708561228a06a7e34d8775a20118d391f5b891a6e6 requires_dist: - numpy>=1.20 - pytest ; extra == 'test' @@ -3319,10 +3337,10 @@ packages: - nodejs ; extra == 'all' - pythreejs ; extra == 'all' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/ca/3b/8d206694f1dfe8c6305d8c13d9d71fcb3962543b99d6ca8df76fcee5964d/scipp-25.8.0-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/f6/f3/9d1bb423a2dc0bbebfc98191095dd410a1268397b9c692d76a3ea971c790/scipp-25.11.0-cp313-cp313-win_amd64.whl name: scipp - version: 25.8.0 - sha256: 7b5934c188a4fadd857cc43e7befb061f7995a3c706bcee5e0013bdf20ce7c4a + version: 25.11.0 + sha256: d44d2aa3f6634ac750a126e50a740c487aa8a2181fcb03764b29ddceeea611cf requires_dist: - numpy>=1.20 - pytest ; extra == 'test' @@ -3345,10 +3363,10 @@ packages: - nodejs ; extra == 'all' - pythreejs ; extra == 'all' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/32/a9/15c20d08e950b540184caa8ced675ba1128accb0e09c653780ba023a4110/scipy-1.16.2-cp313-cp313-macosx_12_0_arm64.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 name: scipy - version: 1.16.2 - sha256: 5c39026d12edc826a1ef2ad35ad1e6d7f087f934bb868fc43fa3049c8b8508f9 + version: 1.16.3 + sha256: 7dc1360c06535ea6116a2220f760ae572db9f661aba2d88074fe30ec2aa1ff88 requires_dist: - numpy>=1.25.2,<2.6 - pytest>=8.0.0 ; extra == 'test' @@ -3389,10 +3407,10 @@ packages: - doit>=0.36.0 ; extra == 'dev' - pydevtool ; extra == 'dev' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/a1/57/0f38e396ad19e41b4c5db66130167eef8ee620a49bc7d0512e3bb67e0cab/scipy-1.16.2-cp313-cp313-win_amd64.whl +- 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.2 - sha256: fda714cf45ba43c9d3bae8f2585c777f64e3f89a2e073b668b32ede412d8f52c + version: 1.16.3 + sha256: 16b8bc35a4cc24db80a0ec836a9286d0e31b2503cb2fd7ff7fb0e0374a97081d requires_dist: - numpy>=1.25.2,<2.6 - pytest>=8.0.0 ; extra == 'test' @@ -3433,10 +3451,10 @@ packages: - doit>=0.36.0 ; extra == 'dev' - pydevtool ; extra == 'dev' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/c1/27/c5b52f1ee81727a9fc457f5ac1e9bf3d6eab311805ea615c83c27ba06400/scipy-1.16.2-cp313-cp313-macosx_10_14_x86_64.whl +- 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.2 - sha256: 84f7bf944b43e20b8a894f5fe593976926744f6c185bacfcbdfbb62736b5cc70 + version: 1.16.3 + sha256: d2ec56337675e61b312179a1ad124f5f570c00f920cc75e1000025451b88241c requires_dist: - numpy>=1.25.2,<2.6 - pytest>=8.0.0 ; extra == 'test' @@ -3477,10 +3495,10 @@ packages: - doit>=0.36.0 ; extra == 'dev' - pydevtool ; extra == 'dev' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/da/6a/1a927b14ddc7714111ea51f4e568203b2bb6ed59bdd036d62127c1a360c8/scipy-1.16.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/cd/01/1204382461fcbfeb05b6161b594f4007e78b6eba9b375382f79153172b4d/scipy-1.16.3-cp313-cp313-win_amd64.whl name: scipy - version: 1.16.2 - sha256: c2275ff105e508942f99d4e3bc56b6ef5e4b3c0af970386ca56b777608ce95b7 + version: 1.16.3 + sha256: 062246acacbe9f8210de8e751b16fc37458213f124bef161a5a02c7a39284304 requires_dist: - numpy>=1.25.2,<2.6 - pytest>=8.0.0 ; extra == 'test' @@ -3863,10 +3881,10 @@ packages: version: 0.10.2 sha256: 806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b requires_python: '>=2.6,!=3.0.*,!=3.1.*,!=3.2.*' -- pypi: https://files.pythonhosted.org/packages/3e/84/1691aae773dccff72c866ad19af7adb12d4fb8b439c8bfb36ffc429c8c27/tox-4.31.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/fc/cc/e09c0d663a004945f82beecd4f147053567910479314e8d01ba71e5d5dea/tox-4.32.0-py3-none-any.whl name: tox - version: 4.31.0 - sha256: 328f392e6567e46cb0f9b625679456744dde940287dd1b39117627dc4b21d5da + version: 4.32.0 + sha256: 451e81dc02ba8d1ed20efd52ee409641ae4b5d5830e008af10fe8823ef1bd551 requires_dist: - cachetools>=6.2 - chardet>=5.2 @@ -3880,10 +3898,10 @@ packages: - typing-extensions>=4.15 ; python_full_version < '3.11' - virtualenv>=20.34 requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/40/17/3dd6f646da0c2b5f47950f6a8259082f0e5de1a6a2f2d8e91dae6ff8efb0/tox_gh_actions-3.4.0-py3-none-any.whl +- 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.4.0 - sha256: 31abb90827472181c17ed07385493e4614fd8c03955e43fef42c06a366712850 + version: 3.5.0 + sha256: 070790114c92f4c94337047515ca5e077ceb269a5cb9366e0a0b3440a024eca1 requires_dist: - tox>=4,<5 - devpi-process ; extra == 'testing' @@ -3988,10 +4006,10 @@ packages: purls: [] size: 114846 timestamp: 1760418593847 -- pypi: https://files.pythonhosted.org/packages/27/73/d9a94da0e9d470a543c1b9d3ccbceb0f59455983088e727b8a1824ed90fb/virtualenv-20.35.3-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl name: virtualenv - version: 20.35.3 - sha256: 63d106565078d8c8d0b206d48080f938a8b25361e19432d2c9db40d2899c810a + version: 20.35.4 + sha256: c21c9cede36c9753eeade68ba7d523529f228a403463376cf821eaae2b650f1b requires_dist: - distlib>=0.3.7,<1 - filelock>=3.12.2,<4 @@ -4061,3 +4079,16 @@ packages: - multidict>=4.0 - propcache>=0.2.1 requires_python: '>=3.9' +- conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda + sha256: a4166e3d8ff4e35932510aaff7aa90772f84b4d07e9f6f83c614cba7ceefe0eb + md5: 6432cb5d4ac0046c3ac0a8a0f95842f9 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libstdcxx >=13 + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 567578 + timestamp: 1742433379869 diff --git a/src/easyscience/global_object/map.py b/src/easyscience/global_object/map.py index 0f29c1bc..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): diff --git a/tests/unit_tests/fitting/calculators/__init__.py b/tests/unit_tests/fitting/calculators/__init__.py new file mode 100644 index 00000000..867a21c4 --- /dev/null +++ b/tests/unit_tests/fitting/calculators/__init__.py @@ -0,0 +1,4 @@ +# 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 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..67d0df5b --- /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 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 \ 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 6b4b5f17..0ca8f85e 100644 --- a/tests/unit_tests/variable/test_parameter.py +++ b/tests/unit_tests/variable/test_parameter.py @@ -635,24 +635,6 @@ def test_set_value_dependent_parameter(self, normal_parameter: Parameter): with pytest.raises(AttributeError): dependent_parameter.value = 3 - @pytest.mark.skip(reason="No longer relevant") - 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 - - @pytest.mark.skip(reason="No longer relevant") - 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): From 66a199e63fdfe93ca2064314c2723f1188ea5dac Mon Sep 17 00:00:00 2001 From: Christian Dam Vedel Date: Thu, 20 Nov 2025 15:15:36 +0100 Subject: [PATCH 04/24] Pr comments --- src/easyscience/base_classes/model_base.py | 52 ++++-------- src/easyscience/base_classes/new_base.py | 5 +- src/easyscience/io/serializer_base.py | 98 ++++++++++++++++------ 3 files changed, 94 insertions(+), 61 deletions(-) diff --git a/src/easyscience/base_classes/model_base.py b/src/easyscience/base_classes/model_base.py index 91b7ba7a..9613a54b 100644 --- a/src/easyscience/base_classes/model_base.py +++ b/src/easyscience/base_classes/model_base.py @@ -59,14 +59,7 @@ def get_fit_parameters(self) -> List[Parameter]: :return: List of `Parameter` objects. """ - params = [] - for attr_name in dir(self): - attr = getattr(self, attr_name) - if isinstance(attr, Parameter) and attr.independent: - params.append(attr) - elif hasattr(attr, 'get_fit_parameters'): - params += attr.get_fit_parameters() - return params + return [param for param in self.get_all_parameters() if isinstance(param, Parameter) and param.independent] def get_free_parameters(self) -> List[Parameter]: """ @@ -74,12 +67,7 @@ def get_free_parameters(self) -> List[Parameter]: :return: List of `Parameter` objects. """ - params = [] - for attr_name in dir(self): - attr = getattr(self, attr_name) - if isinstance(attr, Parameter) and not attr.fixed and attr.independent: - params.append(attr) - return params + return [param for param in self.get_fit_parameters() if not param.fixed] @classmethod def from_dict(cls, obj_dict: Dict[str, Any]) -> None: @@ -89,24 +77,20 @@ def from_dict(cls, obj_dict: Dict[str, Any]) -> None: :param obj_dict: dictionary containing the serialized contents (from `SerializerDict`) of an EasyScience object :return: Reformed EasyScience object """ - if isinstance(obj_dict, dict): - if '@module' in obj_dict and obj_dict['@module'].startswith('easy'): - if '@class' in obj_dict and 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(): - temp_param = getattr(cls_instance, key) - setattr(cls_instance, '_'+key, value) - cls_instance._global_object.map.prune(temp_param.unique_name) - return cls_instance - else: - raise ValueError(f'Class name not in dictionary or does not match the expected class: {cls.__name__}.') - else: - raise ValueError('Dictionary does not represent an 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(): + temp_param = getattr(cls_instance, key) + setattr(cls_instance, '_'+key, value) + cls_instance._global_object.map.prune(temp_param.unique_name) + return cls_instance else: - raise TypeError('Input must be a dictionary.') \ No newline at end of file + raise ValueError(f'Class name in dictionary does not match the expected class: {cls.__name__}.') \ No newline at end of file diff --git a/src/easyscience/base_classes/new_base.py b/src/easyscience/base_classes/new_base.py index b331d454..67e8f87a 100644 --- a/src/easyscience/base_classes/new_base.py +++ b/src/easyscience/base_classes/new_base.py @@ -46,8 +46,7 @@ def _arg_spec(self) -> Set[str]: This method is used by the serializer to determine which arguments are needed by the constructor to deserialize the object. """ - base_cls = getattr(self, '__old_class__', self.__class__) - spec = getfullargspec(base_cls.__init__) + spec = getfullargspec(self.__class__.__init__) names = set(spec.args[1:]) return names @@ -116,7 +115,7 @@ def from_dict(cls, obj_dict: Dict[str, Any]) -> None: if isinstance(obj_dict, dict): if '@module' in obj_dict and obj_dict['@module'].startswith('easy'): if '@class' in obj_dict and obj_dict['@class'] == cls.__name__: - kwargs = SerializerBase._deserialize_dict(obj_dict) + kwargs = SerializerBase.deserialize_dict(obj_dict) return cls(**kwargs) else: raise ValueError(f'Class name not in dictionary or does not match the expected class: {cls.__name__}.') diff --git a/src/easyscience/io/serializer_base.py b/src/easyscience/io/serializer_base.py index fb014215..181df435 100644 --- a/src/easyscience/io/serializer_base.py +++ b/src/easyscience/io/serializer_base.py @@ -261,33 +261,83 @@ def _convert_from_dict(d): return d @staticmethod - def _deserialize_dict(in_dict: Dict[str, Any]) -> None: + def deserialize_dict(in_dict: Dict[str, Any]) -> Dict[str, Any]: """ - Deserialize a dictionary using from_dict for EasyScience objects and SerializerBase otherwise. + 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 + :return: deserialized dictionary with constructor arguments """ - out_dict = {} - for key, value in in_dict.items(): - if not key.startswith('@'): - if isinstance(value, dict) and "@module" in value and value["@module"].startswith("easy") and '@class' in value: # noqa: E501 - module_name = value['@module'] - class_name = value['@class'] - 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 hasattr(module, class_name): - cls_ = getattr(module, class_name) - if hasattr(cls_, 'from_dict'): - out_dict[key] = cls_.from_dict(value) - else: - out_dict[key] = SerializerBase._convert_from_dict(value) - else: - raise ValueError(f'Class {class_name} not found in module {module_name}.') - else: - out_dict[key] = SerializerBase._convert_from_dict(value) - return out_dict + 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) as e: + # 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): """ From 690ce165832ddd4904ffe34973ed4ab6816abe30 Mon Sep 17 00:00:00 2001 From: Christian Dam Vedel Date: Thu, 20 Nov 2025 16:14:31 +0100 Subject: [PATCH 05/24] Simplify NewBase from_dict class using `_is_serialized_easyscience_object` method --- src/easyscience/base_classes/new_base.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/easyscience/base_classes/new_base.py b/src/easyscience/base_classes/new_base.py index 67e8f87a..ef7184a2 100644 --- a/src/easyscience/base_classes/new_base.py +++ b/src/easyscience/base_classes/new_base.py @@ -112,17 +112,13 @@ def from_dict(cls, obj_dict: Dict[str, Any]) -> None: :param obj_dict: dictionary containing the serialized contents (from `SerializerDict`) of an EasyScience object :return: Reformed EasyScience object """ - if isinstance(obj_dict, dict): - if '@module' in obj_dict and obj_dict['@module'].startswith('easy'): - if '@class' in obj_dict and obj_dict['@class'] == cls.__name__: - kwargs = SerializerBase.deserialize_dict(obj_dict) - return cls(**kwargs) - else: - raise ValueError(f'Class name not in dictionary or does not match the expected class: {cls.__name__}.') - else: - raise ValueError('Dictionary does not represent an 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 TypeError('Input must be a dictionary.') + raise ValueError(f'Class name in dictionary does not match the expected class: {cls.__name__}.') def __dir__(self) -> Iterable[str]: """ From 6687c3392ac96c764bb9397aaad2d4c2737e6efc Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Fri, 21 Nov 2025 12:54:57 +0100 Subject: [PATCH 06/24] Sort imports --- src/easyscience/base_classes/model_base.py | 10 ++++++---- src/easyscience/base_classes/new_base.py | 23 ++++++++++------------ 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/easyscience/base_classes/model_base.py b/src/easyscience/base_classes/model_base.py index 91b7ba7a..bbd8b033 100644 --- a/src/easyscience/base_classes/model_base.py +++ b/src/easyscience/base_classes/model_base.py @@ -4,10 +4,12 @@ # 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 Date: Fri, 21 Nov 2025 16:10:08 +0100 Subject: [PATCH 07/24] Test new serializer_base methods except deserialize_dict --- src/easyscience/base_classes/__init__.py | 4 + src/easyscience/base_classes/new_base.py | 5 +- .../unit_tests/base_classes/test_new_base.py | 4 + tests/unit_tests/io/test_serializer_base.py | 106 ++++++++++++++++++ 4 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 tests/unit_tests/base_classes/test_new_base.py create mode 100644 tests/unit_tests/io/test_serializer_base.py 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/new_base.py b/src/easyscience/base_classes/new_base.py index 89e4307d..93bac3b5 100644 --- a/src/easyscience/base_classes/new_base.py +++ b/src/easyscience/base_classes/new_base.py @@ -14,7 +14,8 @@ from typing import Optional from typing import Set -from ..global_object import global_object +from easyscience import global_object + from ..global_object.undo_redo import property_stack from ..io.serializer_base import SerializerBase @@ -23,7 +24,7 @@ class NewBase: """ This is the new base class for easyscience objects. It provides serialization capabilities as well as unique naming and display naming. -""" + """ def __init__(self, unique_name: Optional[str] = None, display_name: Optional[str] = None): self._global_object = global_object diff --git a/tests/unit_tests/base_classes/test_new_base.py b/tests/unit_tests/base_classes/test_new_base.py new file mode 100644 index 00000000..867a21c4 --- /dev/null +++ b/tests/unit_tests/base_classes/test_new_base.py @@ -0,0 +1,4 @@ +# 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 Date: Mon, 24 Nov 2025 09:30:14 +0100 Subject: [PATCH 08/24] Documentation (#152) --- Examples/fitting/README.rst | 6 + docs/src/fitting/introduction.rst | 358 ++++++++++++++++++++++ docs/src/getting-started/installation.rst | 9 +- docs/src/getting-started/overview.rst | 218 +++++++++++++ docs/src/index.rst | 161 ++++++++-- docs/src/reference/base.rst | 323 +++++++++++++++++-- 6 files changed, 1021 insertions(+), 54 deletions(-) create mode 100644 Examples/fitting/README.rst 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/docs/src/fitting/introduction.rst b/docs/src/fitting/introduction.rst index 72c572ac..51155dd9 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..2136bec6 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..4e329b24 100644 --- a/docs/src/index.rst +++ b/docs/src/index.rst @@ -2,68 +2,177 @@ 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 `_. + +* **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 `_ +* **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..f3c277e0 100644 --- a/docs/src/reference/base.rst +++ b/docs/src/reference/base.rst @@ -1,38 +1,315 @@ -====================== -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: -============================= -Super Classes and Collections -============================= + The Parameter class extends DescriptorNumber with fitting capabilities, bounds, and dependency relationships. -Super Classes -============= + **Key Methods:** + + .. automethod:: from_dependency + .. automethod:: make_dependent_on + .. automethod:: make_independent + .. automethod:: resolve_pending_dependencies + +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 + .. automethod:: get_parameters + .. 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:** + + .. automethod:: fit + .. automethod:: switch_minimizer + .. 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.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.Analysis + :members: + :show-inheritance: + +.. autoclass:: easyscience.job.Experiment + :members: + :show-inheritance: + +.. autoclass:: easyscience.job.Job + :members: + :show-inheritance: + +.. autoclass:: easyscience.job.TheoreticalModel + :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. From 0f8b932d3ca721a0b708f06b60be3a02bac55e61 Mon Sep 17 00:00:00 2001 From: Christian Dam Vedel Date: Mon, 24 Nov 2025 09:39:10 +0100 Subject: [PATCH 09/24] Last new SerializerBase test --- src/easyscience/base_classes/based_base.py | 13 --------- tests/unit_tests/io/test_serializer_base.py | 31 +++++++++++++++++++++ 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/easyscience/base_classes/based_base.py b/src/easyscience/base_classes/based_base.py index d1820714..da19b2ca 100644 --- a/src/easyscience/base_classes/based_base.py +++ b/src/easyscience/base_classes/based_base.py @@ -182,19 +182,6 @@ def get_fit_parameters(self) -> List[Parameter]: fit_list.append(item) return fit_list - def as_dict(self, skip: Optional[List[str]] = None) -> Dict[str, Any]: - """ - Encode the object as a dictionary. - - :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. - """ - if skip: - skip = skip + ['unique_name'] - else: - skip = ['unique_name'] - return super().as_dict(skip=skip) - def __dir__(self) -> Iterable[str]: """ This creates auto-completion and helps out in iPython notebooks. diff --git a/tests/unit_tests/io/test_serializer_base.py b/tests/unit_tests/io/test_serializer_base.py index 62e2cf04..bd1dba16 100644 --- a/tests/unit_tests/io/test_serializer_base.py +++ b/tests/unit_tests/io/test_serializer_base.py @@ -104,3 +104,34 @@ def test_deserialize_value_easyscience_import_error(self, monkeypatch): 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 From 8e3ab34ec591c1cdf05f04e4b00008b7ff62e41e Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Wed, 19 Nov 2025 12:15:33 +0100 Subject: [PATCH 10/24] Create new easyscience base class and model_base class. --- src/easyscience/base_classes/based_base.py | 13 ++ src/easyscience/base_classes/model_base.py | 112 ++++++++++++++++ src/easyscience/base_classes/new_base.py | 148 +++++++++++++++++++++ src/easyscience/io/serializer_base.py | 29 ++++ 4 files changed, 302 insertions(+) create mode 100644 src/easyscience/base_classes/model_base.py create mode 100644 src/easyscience/base_classes/new_base.py diff --git a/src/easyscience/base_classes/based_base.py b/src/easyscience/base_classes/based_base.py index b575b486..77bec005 100644 --- a/src/easyscience/base_classes/based_base.py +++ b/src/easyscience/base_classes/based_base.py @@ -182,6 +182,19 @@ def get_fit_parameters(self) -> List[Parameter]: fit_list.append(item) return fit_list + def as_dict(self, skip: Optional[List[str]] = None) -> Dict[str, Any]: + """ + Encode the object as a dictionary. + + :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. + """ + if skip: + skip = skip + ['unique_name'] + else: + skip = ['unique_name'] + return super().as_dict(skip=skip) + def __dir__(self) -> Iterable[str]: """ This creates auto-completion and helps out in iPython notebooks. diff --git a/src/easyscience/base_classes/model_base.py b/src/easyscience/base_classes/model_base.py new file mode 100644 index 00000000..91b7ba7a --- /dev/null +++ b/src/easyscience/base_classes/model_base.py @@ -0,0 +1,112 @@ +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_parameters(self) -> List[DescriptorNumber]: + """ + Get all `Parameters` or `DescriptorNumber` objects as a list. + + :return: List of `DescriptorNumber` or `Parameter` objects. + """ + params = [] + for attr_name in dir(self): + attr = getattr(self, attr_name) + if isinstance(attr, DescriptorNumber): + params.append(attr) + elif hasattr(attr, 'get_all_parameters'): + params += attr.get_all_parameters() + return params + + def get_fit_parameters(self) -> List[Parameter]: + """ + Get all parameters which can be fitted as a list. + + :return: List of `Parameter` objects. + """ + params = [] + for attr_name in dir(self): + attr = getattr(self, attr_name) + if isinstance(attr, Parameter) and attr.independent: + params.append(attr) + elif hasattr(attr, 'get_fit_parameters'): + params += attr.get_fit_parameters() + return params + + 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. + """ + params = [] + for attr_name in dir(self): + attr = getattr(self, attr_name) + if isinstance(attr, Parameter) and not attr.fixed and attr.independent: + params.append(attr) + return params + + @classmethod + def from_dict(cls, obj_dict: Dict[str, Any]) -> None: + """ + 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 isinstance(obj_dict, dict): + if '@module' in obj_dict and obj_dict['@module'].startswith('easy'): + if '@class' in obj_dict and 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(): + temp_param = getattr(cls_instance, key) + setattr(cls_instance, '_'+key, value) + cls_instance._global_object.map.prune(temp_param.unique_name) + return cls_instance + else: + raise ValueError(f'Class name not in dictionary or does not match the expected class: {cls.__name__}.') + else: + raise ValueError('Dictionary does not represent an EasyScience object.') + else: + raise TypeError('Input must be a dictionary.') \ No newline at end of file diff --git a/src/easyscience/base_classes/new_base.py b/src/easyscience/base_classes/new_base.py new file mode 100644 index 00000000..b331d454 --- /dev/null +++ b/src/easyscience/base_classes/new_base.py @@ -0,0 +1,148 @@ +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. + """ + base_cls = getattr(self, '__old_class__', self.__class__) + spec = getfullargspec(base_cls.__init__) + names = set(spec.args[1:]) + return names + + @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) + + @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: + """ + 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 as_dict(self, skip: Optional[List[str]] = None) -> Dict[str, Any]: + """ + Convert an EasyScience 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. + """ + serializer = SerializerBase() + if skip: + skip = skip + ['unique_name'] + else: + skip = ['unique_name'] + return serializer._convert_to_dict(self, skip=skip, full_encode=False) + + @classmethod + def from_dict(cls, obj_dict: Dict[str, Any]) -> None: + """ + 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 isinstance(obj_dict, dict): + if '@module' in obj_dict and obj_dict['@module'].startswith('easy'): + if '@class' in obj_dict and obj_dict['@class'] == cls.__name__: + kwargs = SerializerBase._deserialize_dict(obj_dict) + return cls(**kwargs) + else: + raise ValueError(f'Class name not in dictionary or does not match the expected class: {cls.__name__}.') + else: + raise ValueError('Dictionary does not represent an EasyScience object.') + else: + raise TypeError('Input must be a dictionary.') + + 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.as_dict(skip=['unique_name']) + new_obj = self.__class__.from_dict(temp) + return new_obj + + def __deepcopy__(self, memo): + return self.from_dict(self.as_dict()) + + def __repr__(self) -> str: + return f'{self.__class__.__name__} `{self.unique_name}`' + \ No newline at end of file diff --git a/src/easyscience/io/serializer_base.py b/src/easyscience/io/serializer_base.py index 37a31045..fb014215 100644 --- a/src/easyscience/io/serializer_base.py +++ b/src/easyscience/io/serializer_base.py @@ -260,6 +260,35 @@ 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]) -> None: + """ + Deserialize a dictionary using from_dict for EasyScience objects and SerializerBase otherwise. + :param in_dict: dictionary to deserialize + :return: deserialized dictionary + """ + out_dict = {} + for key, value in in_dict.items(): + if not key.startswith('@'): + if isinstance(value, dict) and "@module" in value and value["@module"].startswith("easy") and '@class' in value: # noqa: E501 + module_name = value['@module'] + class_name = value['@class'] + 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 hasattr(module, class_name): + cls_ = getattr(module, class_name) + if hasattr(cls_, 'from_dict'): + out_dict[key] = cls_.from_dict(value) + else: + out_dict[key] = SerializerBase._convert_from_dict(value) + else: + raise ValueError(f'Class {class_name} not found in module {module_name}.') + else: + out_dict[key] = SerializerBase._convert_from_dict(value) + return out_dict + def _recursive_encoder(self, obj, skip: List[str] = [], encoder=None, full_encode=False, **kwargs): """ Walk through an object encoding it From 51100add9f135b9f15befb6fac9dda0121eeac67 Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Fri, 21 Nov 2025 12:54:57 +0100 Subject: [PATCH 11/24] Sort imports --- src/easyscience/base_classes/model_base.py | 10 ++++++---- src/easyscience/base_classes/new_base.py | 23 ++++++++++------------ 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/easyscience/base_classes/model_base.py b/src/easyscience/base_classes/model_base.py index 91b7ba7a..bbd8b033 100644 --- a/src/easyscience/base_classes/model_base.py +++ b/src/easyscience/base_classes/model_base.py @@ -4,10 +4,12 @@ # 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 Date: Thu, 20 Nov 2025 15:15:36 +0100 Subject: [PATCH 12/24] Pr comments --- src/easyscience/base_classes/model_base.py | 52 ++++-------- src/easyscience/base_classes/new_base.py | 5 +- src/easyscience/io/serializer_base.py | 98 ++++++++++++++++------ 3 files changed, 94 insertions(+), 61 deletions(-) diff --git a/src/easyscience/base_classes/model_base.py b/src/easyscience/base_classes/model_base.py index bbd8b033..d263910b 100644 --- a/src/easyscience/base_classes/model_base.py +++ b/src/easyscience/base_classes/model_base.py @@ -61,14 +61,7 @@ def get_fit_parameters(self) -> List[Parameter]: :return: List of `Parameter` objects. """ - params = [] - for attr_name in dir(self): - attr = getattr(self, attr_name) - if isinstance(attr, Parameter) and attr.independent: - params.append(attr) - elif hasattr(attr, 'get_fit_parameters'): - params += attr.get_fit_parameters() - return params + return [param for param in self.get_all_parameters() if isinstance(param, Parameter) and param.independent] def get_free_parameters(self) -> List[Parameter]: """ @@ -76,12 +69,7 @@ def get_free_parameters(self) -> List[Parameter]: :return: List of `Parameter` objects. """ - params = [] - for attr_name in dir(self): - attr = getattr(self, attr_name) - if isinstance(attr, Parameter) and not attr.fixed and attr.independent: - params.append(attr) - return params + return [param for param in self.get_fit_parameters() if not param.fixed] @classmethod def from_dict(cls, obj_dict: Dict[str, Any]) -> None: @@ -91,24 +79,20 @@ def from_dict(cls, obj_dict: Dict[str, Any]) -> None: :param obj_dict: dictionary containing the serialized contents (from `SerializerDict`) of an EasyScience object :return: Reformed EasyScience object """ - if isinstance(obj_dict, dict): - if '@module' in obj_dict and obj_dict['@module'].startswith('easy'): - if '@class' in obj_dict and 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(): - temp_param = getattr(cls_instance, key) - setattr(cls_instance, '_'+key, value) - cls_instance._global_object.map.prune(temp_param.unique_name) - return cls_instance - else: - raise ValueError(f'Class name not in dictionary or does not match the expected class: {cls.__name__}.') - else: - raise ValueError('Dictionary does not represent an 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(): + temp_param = getattr(cls_instance, key) + setattr(cls_instance, '_'+key, value) + cls_instance._global_object.map.prune(temp_param.unique_name) + return cls_instance else: - raise TypeError('Input must be a dictionary.') \ No newline at end of file + raise ValueError(f'Class name in dictionary does not match the expected class: {cls.__name__}.') \ No newline at end of file diff --git a/src/easyscience/base_classes/new_base.py b/src/easyscience/base_classes/new_base.py index 308cb953..7b556fa0 100644 --- a/src/easyscience/base_classes/new_base.py +++ b/src/easyscience/base_classes/new_base.py @@ -43,8 +43,7 @@ def _arg_spec(self) -> Set[str]: This method is used by the serializer to determine which arguments are needed by the constructor to deserialize the object. """ - base_cls = getattr(self, '__old_class__', self.__class__) - spec = getfullargspec(base_cls.__init__) + spec = getfullargspec(self.__class__.__init__) names = set(spec.args[1:]) return names @@ -113,7 +112,7 @@ def from_dict(cls, obj_dict: Dict[str, Any]) -> None: if isinstance(obj_dict, dict): if '@module' in obj_dict and obj_dict['@module'].startswith('easy'): if '@class' in obj_dict and obj_dict['@class'] == cls.__name__: - kwargs = SerializerBase._deserialize_dict(obj_dict) + kwargs = SerializerBase.deserialize_dict(obj_dict) return cls(**kwargs) else: raise ValueError(f'Class name not in dictionary or does not match the expected class: {cls.__name__}.') diff --git a/src/easyscience/io/serializer_base.py b/src/easyscience/io/serializer_base.py index fb014215..181df435 100644 --- a/src/easyscience/io/serializer_base.py +++ b/src/easyscience/io/serializer_base.py @@ -261,33 +261,83 @@ def _convert_from_dict(d): return d @staticmethod - def _deserialize_dict(in_dict: Dict[str, Any]) -> None: + def deserialize_dict(in_dict: Dict[str, Any]) -> Dict[str, Any]: """ - Deserialize a dictionary using from_dict for EasyScience objects and SerializerBase otherwise. + 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 + :return: deserialized dictionary with constructor arguments """ - out_dict = {} - for key, value in in_dict.items(): - if not key.startswith('@'): - if isinstance(value, dict) and "@module" in value and value["@module"].startswith("easy") and '@class' in value: # noqa: E501 - module_name = value['@module'] - class_name = value['@class'] - 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 hasattr(module, class_name): - cls_ = getattr(module, class_name) - if hasattr(cls_, 'from_dict'): - out_dict[key] = cls_.from_dict(value) - else: - out_dict[key] = SerializerBase._convert_from_dict(value) - else: - raise ValueError(f'Class {class_name} not found in module {module_name}.') - else: - out_dict[key] = SerializerBase._convert_from_dict(value) - return out_dict + 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) as e: + # 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): """ From 64f09c167d9f71c5474c000c25f8127b68c20838 Mon Sep 17 00:00:00 2001 From: Christian Dam Vedel Date: Thu, 20 Nov 2025 16:14:31 +0100 Subject: [PATCH 13/24] Simplify NewBase from_dict class using `_is_serialized_easyscience_object` method --- src/easyscience/base_classes/new_base.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/easyscience/base_classes/new_base.py b/src/easyscience/base_classes/new_base.py index 7b556fa0..89e4307d 100644 --- a/src/easyscience/base_classes/new_base.py +++ b/src/easyscience/base_classes/new_base.py @@ -109,17 +109,13 @@ def from_dict(cls, obj_dict: Dict[str, Any]) -> None: :param obj_dict: dictionary containing the serialized contents (from `SerializerDict`) of an EasyScience object :return: Reformed EasyScience object """ - if isinstance(obj_dict, dict): - if '@module' in obj_dict and obj_dict['@module'].startswith('easy'): - if '@class' in obj_dict and obj_dict['@class'] == cls.__name__: - kwargs = SerializerBase.deserialize_dict(obj_dict) - return cls(**kwargs) - else: - raise ValueError(f'Class name not in dictionary or does not match the expected class: {cls.__name__}.') - else: - raise ValueError('Dictionary does not represent an 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 TypeError('Input must be a dictionary.') + raise ValueError(f'Class name in dictionary does not match the expected class: {cls.__name__}.') def __dir__(self) -> Iterable[str]: """ From 4e26dfc2d8dbf5d59844ccfb8713b90d4b8bc603 Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Fri, 21 Nov 2025 16:10:08 +0100 Subject: [PATCH 14/24] Test new serializer_base methods except deserialize_dict --- src/easyscience/base_classes/__init__.py | 4 + src/easyscience/base_classes/new_base.py | 5 +- .../unit_tests/base_classes/test_new_base.py | 4 + tests/unit_tests/io/test_serializer_base.py | 103 +++++++++++++++++- 4 files changed, 110 insertions(+), 6 deletions(-) create mode 100644 tests/unit_tests/base_classes/test_new_base.py 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/new_base.py b/src/easyscience/base_classes/new_base.py index 89e4307d..93bac3b5 100644 --- a/src/easyscience/base_classes/new_base.py +++ b/src/easyscience/base_classes/new_base.py @@ -14,7 +14,8 @@ from typing import Optional from typing import Set -from ..global_object import global_object +from easyscience import global_object + from ..global_object.undo_redo import property_stack from ..io.serializer_base import SerializerBase @@ -23,7 +24,7 @@ class NewBase: """ This is the new base class for easyscience objects. It provides serialization capabilities as well as unique naming and display naming. -""" + """ def __init__(self, unique_name: Optional[str] = None, display_name: Optional[str] = None): self._global_object = global_object diff --git a/tests/unit_tests/base_classes/test_new_base.py b/tests/unit_tests/base_classes/test_new_base.py new file mode 100644 index 00000000..867a21c4 --- /dev/null +++ b/tests/unit_tests/base_classes/test_new_base.py @@ -0,0 +1,4 @@ +# 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 Date: Mon, 24 Nov 2025 09:39:10 +0100 Subject: [PATCH 15/24] Last new SerializerBase test --- src/easyscience/base_classes/based_base.py | 13 --------- tests/unit_tests/io/test_serializer_base.py | 32 ++++++++++++++++++++- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/src/easyscience/base_classes/based_base.py b/src/easyscience/base_classes/based_base.py index 77bec005..b575b486 100644 --- a/src/easyscience/base_classes/based_base.py +++ b/src/easyscience/base_classes/based_base.py @@ -182,19 +182,6 @@ def get_fit_parameters(self) -> List[Parameter]: fit_list.append(item) return fit_list - def as_dict(self, skip: Optional[List[str]] = None) -> Dict[str, Any]: - """ - Encode the object as a dictionary. - - :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. - """ - if skip: - skip = skip + ['unique_name'] - else: - skip = ['unique_name'] - return super().as_dict(skip=skip) - def __dir__(self) -> Iterable[str]: """ This creates auto-completion and helps out in iPython notebooks. diff --git a/tests/unit_tests/io/test_serializer_base.py b/tests/unit_tests/io/test_serializer_base.py index 88b25403..bc8e8522 100644 --- a/tests/unit_tests/io/test_serializer_base.py +++ b/tests/unit_tests/io/test_serializer_base.py @@ -702,4 +702,34 @@ def test_deserialize_value_easyscience_import_error(self, monkeypatch): # Then obj = SerializerBase._deserialize_value(serialized_dict) # Expect - SerializerBase._convert_from_dict.assert_called_once_with(serialized_dict) \ No newline at end of file + 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 From d19f256059b509e0421ea0f9944f52d40c50cf9d Mon Sep 17 00:00:00 2001 From: Christian Dam Vedel Date: Mon, 24 Nov 2025 14:43:10 +0100 Subject: [PATCH 16/24] Add as_dict tests --- src/easyscience/base_classes/new_base.py | 17 ++- .../unit_tests/base_classes/test_new_base.py | 129 ++++++++++++++++++ tests/unit_tests/io/test_serializer_base.py | 1 + 3 files changed, 141 insertions(+), 6 deletions(-) diff --git a/src/easyscience/base_classes/new_base.py b/src/easyscience/base_classes/new_base.py index 93bac3b5..7d80e31e 100644 --- a/src/easyscience/base_classes/new_base.py +++ b/src/easyscience/base_classes/new_base.py @@ -30,6 +30,9 @@ def __init__(self, unique_name: Optional[str] = None, display_name: Optional[st self._global_object = global_object if unique_name is None: unique_name = self._global_object.generate_unique_name(self.__class__.__name__) + self._default_unique_name = True + else: + self._default_unique_name = False if not isinstance(unique_name, str): raise TypeError('Unique name has to be a string.') self._unique_name = unique_name @@ -62,6 +65,7 @@ def unique_name(self, 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: @@ -89,17 +93,18 @@ def display_name(self, name: str) -> None: def as_dict(self, skip: Optional[List[str]] = None) -> Dict[str, Any]: """ - Convert an EasyScience object into a full dictionary using `SerializerDict`. - This is a shortcut for ```obj.encode(encoder=SerializerDict)``` + 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: - skip = skip + ['unique_name'] - else: - skip = ['unique_name'] + 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 diff --git a/tests/unit_tests/base_classes/test_new_base.py b/tests/unit_tests/base_classes/test_new_base.py index 867a21c4..d6c7489a 100644 --- a/tests/unit_tests/base_classes/test_new_base.py +++ b/tests/unit_tests/base_classes/test_new_base.py @@ -2,3 +2,132 @@ # SPDX-License-Identifier: BSD-3-Clause # © 2021-2025 Contributors to the EasyScience project Date: Mon, 24 Nov 2025 15:06:17 +0100 Subject: [PATCH 17/24] Format and Lint --- src/easyscience/base_classes/model_base.py | 8 ++--- src/easyscience/base_classes/new_base.py | 5 ++- src/easyscience/io/serializer_base.py | 37 ++++++++-------------- 3 files changed, 20 insertions(+), 30 deletions(-) diff --git a/src/easyscience/base_classes/model_base.py b/src/easyscience/base_classes/model_base.py index d263910b..0fbdbd04 100644 --- a/src/easyscience/base_classes/model_base.py +++ b/src/easyscience/base_classes/model_base.py @@ -37,7 +37,7 @@ def my_param(self, new_value: float) -> None: ``` """ - def __init__(self, unique_name: Optional[str] = None, display_name: Optional[str] = None): + 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_parameters(self) -> List[DescriptorNumber]: @@ -70,7 +70,7 @@ def get_free_parameters(self) -> List[Parameter]: :return: List of `Parameter` objects. """ return [param for param in self.get_fit_parameters() if not param.fixed] - + @classmethod def from_dict(cls, obj_dict: Dict[str, Any]) -> None: """ @@ -91,8 +91,8 @@ def from_dict(cls, obj_dict: Dict[str, Any]) -> None: cls_instance = cls(**kwargs) for key, value in parameter_placeholder.items(): temp_param = getattr(cls_instance, key) - setattr(cls_instance, '_'+key, value) + setattr(cls_instance, '_' + key, value) cls_instance._global_object.map.prune(temp_param.unique_name) return cls_instance else: - raise ValueError(f'Class name in dictionary does not match the expected class: {cls.__name__}.') \ No newline at end of file + 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 index 7d80e31e..c4c96feb 100644 --- a/src/easyscience/base_classes/new_base.py +++ b/src/easyscience/base_classes/new_base.py @@ -26,7 +26,7 @@ class NewBase: It provides serialization capabilities as well as unique naming and display naming. """ - def __init__(self, unique_name: Optional[str] = None, display_name: Optional[str] = None): + def __init__(self, unique_name: Optional[str] = None, display_name: Optional[str] = None): self._global_object = global_object if unique_name is None: unique_name = self._global_object.generate_unique_name(self.__class__.__name__) @@ -140,7 +140,6 @@ def __copy__(self) -> NewBase: def __deepcopy__(self, memo): return self.from_dict(self.as_dict()) - + def __repr__(self) -> str: return f'{self.__class__.__name__} `{self.unique_name}`' - \ No newline at end of file diff --git a/src/easyscience/io/serializer_base.py b/src/easyscience/io/serializer_base.py index 181df435..83cc530d 100644 --- a/src/easyscience/io/serializer_base.py +++ b/src/easyscience/io/serializer_base.py @@ -265,41 +265,37 @@ 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('@') - } + 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) as e: + + except (ImportError, ValueError): # Fallback to generic deserialization if class-specific fails return SerializerBase._convert_from_dict(value) @@ -307,22 +303,17 @@ def _deserialize_value(value: Any) -> Any: def _is_serialized_easyscience_object(value: Any) -> bool: """ Check if a value represents a serialized ES object. - - :param value: + + :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 - ) + 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 @@ -333,10 +324,10 @@ def _import_class(module_name: str, class_name: str): 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): From fef80c01c9da307dbc5c725f879b0bdb768d6016 Mon Sep 17 00:00:00 2001 From: Christian Dam Vedel Date: Mon, 24 Nov 2025 17:45:50 +0100 Subject: [PATCH 18/24] Pixi update and final NewBase tests --- pixi.lock | 2292 ++++++++++++++--- pyproject.toml | 3 +- src/easyscience/base_classes/new_base.py | 2 +- .../unit_tests/base_classes/test_new_base.py | 80 +- 4 files changed, 2043 insertions(+), 334 deletions(-) diff --git a/pixi.lock b/pixi.lock index a1627563..00863b1e 100644 --- a/pixi.lock +++ b/pixi.lock @@ -10,30 +10,32 @@ environments: - 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.10.5-hbd8a1cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.44-h1aa0949_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.1-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.11.12-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/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.50.4-h0c1763c_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.5.4-h26f9b46_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.0-h26f9b46_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/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_hd72426e_102.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 - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.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/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-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 @@ -56,23 +58,23 @@ environments: - 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/f5/b7/339b9ed180c28418f3c5c425f341759ce3722b61cc54f8c20918a034a1d5/mpld3-0.5.11-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/62/cd/9481a199a086ac9f91eaa232b56cff90ca7fdc2cb6658de93825b1007094/narwhals-2.10.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9e/7e/7d306ff7cb143e6d975cfa7eb98a93e73495c4deabb7d1b5ecf09ea0fd69/numpy-2.3.4-cp313-cp313-manylinux_2_27_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/3f/93/023955c26b0ce614342d11cc0652f1e45e32393b6ab9d11a664a60e9b7b7/plotly-6.3.1-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/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/c0/1a/b393a06aa6f2f6ab4a9c5c160a62d488b17d6da5cf93a67bc13a6e3239cd/python_socketio-5.14.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 @@ -80,32 +82,32 @@ environments: - 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/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-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/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.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.10.5-hbd8a1cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.7.1-h21dd04a_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.11.12-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/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.50.4-h39a8b3b_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.5.4-h230baf5_0.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_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/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-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 @@ -128,23 +130,23 @@ environments: - 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/f5/b7/339b9ed180c28418f3c5c425f341759ce3722b61cc54f8c20918a034a1d5/mpld3-0.5.11-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/62/cd/9481a199a086ac9f91eaa232b56cff90ca7fdc2cb6658de93825b1007094/narwhals-2.10.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/57/7e/b72610cc91edf138bc588df5150957a4937221ca6058b825b4725c27be62/numpy-2.3.4-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/3f/93/023955c26b0ce614342d11cc0652f1e45e32393b6ab9d11a664a60e9b7b7/plotly-6.3.1-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/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/c0/1a/b393a06aa6f2f6ab4a9c5c160a62d488b17d6da5cf93a67bc13a6e3239cd/python_socketio-5.14.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 @@ -152,33 +154,33 @@ environments: - 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/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-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/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.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.10.5-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.11.12-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-75.1-hfee45f7_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.1-hec049ff_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/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.50.4-h4237e3c_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.5.4-h5503f6c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.0-h5503f6c_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/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_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/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-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 @@ -201,23 +203,23 @@ environments: - 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/f5/b7/339b9ed180c28418f3c5c425f341759ce3722b61cc54f8c20918a034a1d5/mpld3-0.5.11-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/62/cd/9481a199a086ac9f91eaa232b56cff90ca7fdc2cb6658de93825b1007094/narwhals-2.10.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/46/bdd3370dcea2f95ef14af79dbf81e6927102ddf1cc54adc0024d61252fd9/numpy-2.3.4-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/3f/93/023955c26b0ce614342d11cc0652f1e45e32393b6ab9d11a664a60e9b7b7/plotly-6.3.1-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/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/c0/1a/b393a06aa6f2f6ab4a9c5c160a62d488b17d6da5cf93a67bc13a6e3239cd/python_socketio-5.14.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 @@ -225,25 +227,25 @@ environments: - 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/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-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/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.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.10.5-h4c7d964_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.1-hac47afa_0.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.50.4-hf5d6505_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.5.4-h725018a_0.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_2.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 @@ -252,7 +254,7 @@ environments: - 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/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-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 @@ -276,23 +278,23 @@ environments: - 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/f5/b7/339b9ed180c28418f3c5c425f341759ce3722b61cc54f8c20918a034a1d5/mpld3-0.5.11-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/62/cd/9481a199a086ac9f91eaa232b56cff90ca7fdc2cb6658de93825b1007094/narwhals-2.10.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/09/97/fd421e8bc50766665ad35536c2bb4ef916533ba1fdd053a62d96cc7c8b95/numpy-2.3.4-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/3f/93/023955c26b0ce614342d11cc0652f1e45e32393b6ab9d11a664a60e9b7b7/plotly-6.3.1-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/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/c0/1a/b393a06aa6f2f6ab4a9c5c160a62d488b17d6da5cf93a67bc13a6e3239cd/python_socketio-5.14.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 @@ -300,10 +302,10 @@ environments: - 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/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-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/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.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: ./ dev: @@ -316,109 +318,173 @@ environments: - 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.10.5-hbd8a1cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.44-h1aa0949_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.1-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.11.12-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/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.50.4-h0c1763c_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.5.4-h26f9b46_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.0-h26f9b46_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/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_hd72426e_102.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 - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.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/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-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/96/c5/1e741d26306c42e2bf6ab740b2202872727e0f606033c9dd713f8b93f5a8/cachetools-6.2.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-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/65/6c/f7f59c342359a235559d2bc76b0c73cfc4bac7d61bb0df210965cb1ecffd/coverage-7.11.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_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/f5/b7/339b9ed180c28418f3c5c425f341759ce3722b61cc54f8c20918a034a1d5/mpld3-0.5.11-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/62/cd/9481a199a086ac9f91eaa232b56cff90ca7fdc2cb6658de93825b1007094/narwhals-2.10.1-py3-none-any.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/9e/7e/7d306ff7cb143e6d975cfa7eb98a93e73495c4deabb7d1b5ecf09ea0fd69/numpy-2.3.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_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/3f/93/023955c26b0ce614342d11cc0652f1e45e32393b6ab9d11a664a60e9b7b7/plotly-6.3.1-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/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-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/c0/1a/b393a06aa6f2f6ab4a9c5c160a62d488b17d6da5cf93a67bc13a6e3239cd/python_socketio-5.14.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/48/9c/6d8035cafa2d2d314f34e6cd9313a299de095b26e96f1c7312878f988eec/restructuredtext_lint-1.4.0.tar.gz + - 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/a7/e7/138b883f0dfe4ad5b76b58bf4ae675f4d2176ac2b24bdd81b4d966b28c61/ruff-0.14.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.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 @@ -431,118 +497,191 @@ environments: - 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/80/c5/0c06759b95747882bb50abda18f5fb48c3e9b0fbfc6ebc0e23550b52415d/stevedore-5.5.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/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-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/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-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.10.5-hbd8a1cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.7.1-h21dd04a_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.11.12-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/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.50.4-h39a8b3b_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.5.4-h230baf5_0.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_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/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-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/96/c5/1e741d26306c42e2bf6ab740b2202872727e0f606033c9dd713f8b93f5a8/cachetools-6.2.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-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/60/7f/85e4dfe65e400645464b25c036a26ac226cf3a69d4a50c3934c532491cdd/coverage-7.11.0-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/f5/b7/339b9ed180c28418f3c5c425f341759ce3722b61cc54f8c20918a034a1d5/mpld3-0.5.11-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/62/cd/9481a199a086ac9f91eaa232b56cff90ca7fdc2cb6658de93825b1007094/narwhals-2.10.1-py3-none-any.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/57/7e/b72610cc91edf138bc588df5150957a4937221ca6058b825b4725c27be62/numpy-2.3.4-cp313-cp313-macosx_10_13_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/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/3f/93/023955c26b0ce614342d11cc0652f1e45e32393b6ab9d11a664a60e9b7b7/plotly-6.3.1-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/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-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/c0/1a/b393a06aa6f2f6ab4a9c5c160a62d488b17d6da5cf93a67bc13a6e3239cd/python_socketio-5.14.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/48/9c/6d8035cafa2d2d314f34e6cd9313a299de095b26e96f1c7312878f988eec/restructuredtext_lint-1.4.0.tar.gz + - 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/d3/c8/6724f4634c1daf52409fbf13fefda64aa9c8f81e44727a378b7b73dc590b/ruff-0.14.3-py3-none-macosx_10_12_x86_64.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 @@ -555,119 +694,192 @@ environments: - 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/80/c5/0c06759b95747882bb50abda18f5fb48c3e9b0fbfc6ebc0e23550b52415d/stevedore-5.5.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/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-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/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-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.10.5-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.11.12-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-75.1-hfee45f7_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.1-hec049ff_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/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.50.4-h4237e3c_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.5.4-h5503f6c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.0-h5503f6c_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/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_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/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-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/96/c5/1e741d26306c42e2bf6ab740b2202872727e0f606033c9dd713f8b93f5a8/cachetools-6.2.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-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/96/5d/dc5fa98fea3c175caf9d360649cb1aa3715e391ab00dc78c4c66fabd7356/coverage-7.11.0-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/f5/b7/339b9ed180c28418f3c5c425f341759ce3722b61cc54f8c20918a034a1d5/mpld3-0.5.11-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/62/cd/9481a199a086ac9f91eaa232b56cff90ca7fdc2cb6658de93825b1007094/narwhals-2.10.1-py3-none-any.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/3e/46/bdd3370dcea2f95ef14af79dbf81e6927102ddf1cc54adc0024d61252fd9/numpy-2.3.4-cp313-cp313-macosx_11_0_arm64.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/3f/93/023955c26b0ce614342d11cc0652f1e45e32393b6ab9d11a664a60e9b7b7/plotly-6.3.1-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/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-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/c0/1a/b393a06aa6f2f6ab4a9c5c160a62d488b17d6da5cf93a67bc13a6e3239cd/python_socketio-5.14.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/48/9c/6d8035cafa2d2d314f34e6cd9313a299de095b26e96f1c7312878f988eec/restructuredtext_lint-1.4.0.tar.gz + - 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/de/03/db1bce591d55fd5f8a08bb02517fa0b5097b2ccabd4ea1ee29aa72b67d96/ruff-0.14.3-py3-none-macosx_11_0_arm64.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 @@ -680,32 +892,42 @@ environments: - 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/80/c5/0c06759b95747882bb50abda18f5fb48c3e9b0fbfc6ebc0e23550b52415d/stevedore-5.5.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/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-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/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-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.10.5-h4c7d964_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.1-hac47afa_0.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.50.4-hf5d6505_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.5.4-h725018a_0.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_2.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 @@ -716,84 +938,145 @@ environments: - 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/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-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/96/c5/1e741d26306c42e2bf6ab740b2202872727e0f606033c9dd713f8b93f5a8/cachetools-6.2.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-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/ff/d5/226daadfd1bf8ddbccefbd3aa3547d7b960fb48e1bdac124e2dd13a2b71a/coverage-7.11.0-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/f5/b7/339b9ed180c28418f3c5c425f341759ce3722b61cc54f8c20918a034a1d5/mpld3-0.5.11-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/62/cd/9481a199a086ac9f91eaa232b56cff90ca7fdc2cb6658de93825b1007094/narwhals-2.10.1-py3-none-any.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/09/97/fd421e8bc50766665ad35536c2bb4ef916533ba1fdd053a62d96cc7c8b95/numpy-2.3.4-cp313-cp313-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/3f/93/023955c26b0ce614342d11cc0652f1e45e32393b6ab9d11a664a60e9b7b7/plotly-6.3.1-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/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-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/c0/1a/b393a06aa6f2f6ab4a9c5c160a62d488b17d6da5cf93a67bc13a6e3239cd/python_socketio-5.14.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/48/9c/6d8035cafa2d2d314f34e6cd9313a299de095b26e96f1c7312878f988eec/restructuredtext_lint-1.4.0.tar.gz + - 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/03/15/51960ae340823c9859fb60c63301d977308735403e2134e17d1d2858c7fb/ruff-0.14.3-py3-none-win_amd64.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 @@ -806,17 +1089,27 @@ environments: - 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/80/c5/0c06759b95747882bb50abda18f5fb48c3e9b0fbfc6ebc0e23550b52415d/stevedore-5.5.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/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-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/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-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: @@ -946,10 +1239,86 @@ packages: version: 1.0.0 sha256: fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl +- 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.6 - sha256: 5e119ed306e39199fd99c881cea0e306b3f3807f050c9be79829fe274c6378dc + version: 1.0.7 + sha256: d78df08681dfff59031ca624ba7030f9dc576a7a16e2f7a5137c6e7ef3ee60c4 requires_dist: - build ; extra == 'dev' - twine ; extra == 'dev' @@ -958,6 +1327,24 @@ packages: - 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 @@ -997,6 +1384,14 @@ packages: 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 @@ -1088,34 +1483,62 @@ packages: purls: [] size: 55977 timestamp: 1757437738856 -- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-h4c7d964_0.conda - sha256: bfb7f9f242f441fdcd80f1199edd2ecf09acea0f2bcef6f07d7cbb1a8131a345 - md5: e54200a1cd1fe33d61c9df8d3b00b743 +- 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: 156354 - timestamp: 1759649104842 -- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda - sha256: 3b5ad78b8bb61b6cdc0978a6a99f8dfb2cc789a451378d054698441005ecbdb6 - md5: f9e5fbc24009179e8b0409624691758a + 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: 155907 - timestamp: 1759649036195 -- pypi: https://files.pythonhosted.org/packages/96/c5/1e741d26306c42e2bf6ab740b2202872727e0f606033c9dd713f8b93f5a8/cachetools-6.2.1-py3-none-any.whl + 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.1 - sha256: 09868944b6dde876dfd44e1d47e18484541eaf12f26f29b7af91b26cc892d701 + version: 6.2.2 + sha256: 6c09c98183bf58560c97b2abfcedcbaf6a896a490f534b031b661d3723b45ace requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl name: certifi - version: 2025.10.5 - sha256: 0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de + 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 @@ -1154,6 +1577,13 @@ packages: 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 @@ -1254,31 +1684,31 @@ packages: - pytest-xdist ; extra == 'test-no-images' - wurlitzer ; extra == 'test-no-images' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/60/7f/85e4dfe65e400645464b25c036a26ac226cf3a69d4a50c3934c532491cdd/coverage-7.11.0-cp313-cp313-macosx_10_13_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/17/a7/3aa4144d3bcb719bf67b22d2d51c2d577bf801498c13cb08f64173e80497/coverage-7.12.0-cp313-cp313-macosx_11_0_arm64.whl name: coverage - version: 7.11.0 - sha256: cc3f49e65ea6e0d5d9bd60368684fe52a704d46f9e7fc413918f18d046ec40e1 + 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/65/6c/f7f59c342359a235559d2bc76b0c73cfc4bac7d61bb0df210965cb1ecffd/coverage-7.11.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_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 name: coverage - version: 7.11.0 - sha256: 10ad04ac3a122048688387828b4537bc9cf60c0bf4869c1e9989c46e45690b82 + 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/96/5d/dc5fa98fea3c175caf9d360649cb1aa3715e391ab00dc78c4c66fabd7356/coverage-7.11.0-cp313-cp313-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/91/77/dd4aff9af16ff776bf355a24d87eeb48fc6acde54c907cc1ea89b14a8804/coverage-7.12.0-cp313-cp313-win_amd64.whl name: coverage - version: 7.11.0 - sha256: f39ae2f63f37472c17b4990f794035c9890418b1b8cca75c01193f3c8d3e01be + 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/ff/d5/226daadfd1bf8ddbccefbd3aa3547d7b960fb48e1bdac124e2dd13a2b71a/coverage-7.11.0-cp313-cp313-win_amd64.whl +- 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.11.0 - sha256: 2727d47fce3ee2bac648528e41455d1b0c46395a087a229deac75e9f88ba5a05 + version: 7.12.0 + sha256: 47324fffca8d8eae7e185b5bb20c14645f23350f870c1649003618ea91a78941 requires_dist: - tomli ; python_full_version <= '3.11' and extra == 'toml' requires_python: '>=3.10' @@ -1295,6 +1725,26 @@ packages: - 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' @@ -1340,7 +1790,7 @@ packages: - pypi: ./ name: easyscience version: 2.0.0 - sha256: 9bc78e3bef1598da97f578cb356cfd7a68086a4278ac3bf89c3e8dd5275aaf2b + sha256: 3e1433dc7b46ccbb4a5d63a22de63b3113897cd66b602a368db0c08b09b3659c requires_dist: - asteval - bumps @@ -1355,6 +1805,7 @@ packages: - build ; extra == 'dev' - codecov ; extra == 'dev' - flake8 ; extra == 'dev' + - jupyterlab ; extra == 'dev' - matplotlib ; extra == 'dev' - pytest ; extra == 'dev' - pytest-cov ; extra == 'dev' @@ -1373,6 +1824,32 @@ packages: 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 @@ -1523,6 +2000,13 @@ packages: - 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 @@ -1588,6 +2072,48 @@ packages: - 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 @@ -1618,6 +2144,145 @@ packages: 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 @@ -1626,6 +2291,320 @@ packages: - 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 @@ -1646,70 +2625,78 @@ packages: version: 1.4.9 sha256: b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098 requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.44-h1aa0949_4.conda - sha256: 96b6900ca0489d9e5d0318a6b49f8eff43fd85fef6e07cb0c25344ee94cd7a3a - md5: c94ab6ff54ba5172cf1c58267005670f +- 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 - - zstd >=1.5.7,<1.6.0a0 constrains: - - binutils_impl_linux-64 2.44 + - binutils_impl_linux-64 2.45 license: GPL-3.0-only - license_family: GPL purls: [] - size: 742501 - timestamp: 1761335175964 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.1-hecca717_0.conda - sha256: da2080da8f0288b95dd86765c801c6e166c4619b910b11f9a8446fb852438dc2 - md5: 4211416ecba1866fab0c6470986c22d6 + 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.1.* + - expat 2.7.3.* license: MIT license_family: MIT purls: [] - size: 74811 - timestamp: 1752719572741 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.7.1-h21dd04a_0.conda - sha256: 689862313571b62ee77ee01729dc093f2bf25a2f99415fcfe51d3a6cd31cce7b - md5: 9fdeae0b7edda62e989557d645769515 + 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.1.* + - expat 2.7.3.* license: MIT license_family: MIT purls: [] - size: 72450 - timestamp: 1752719744781 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.1-hec049ff_0.conda - sha256: 8fbb17a56f51e7113ed511c5787e0dec0d4b10ef9df921c4fd1cccca0458f648 - md5: b1ca5f21335782f71a8bd69bdc093f67 + 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.1.* + - expat 2.7.3.* license: MIT license_family: MIT purls: [] - size: 65971 - timestamp: 1752719657566 -- conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.1-hac47afa_0.conda - sha256: 8432ca842bdf8073ccecf016ccc9140c41c7114dc4ec77ca754551c01f780845 - md5: 3608ffde260281fa641e70d6e34b1b96 + 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.1.* + - expat 2.7.3.* license: MIT license_family: MIT purls: [] - size: 141322 - timestamp: 1752719767870 + size: 70137 + timestamp: 1763550049107 - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h9ec8514_0.conda sha256: 25cbdfa65580cfab1b8d15ee90b4c9f1e0d72128f1661449c9a999d341377d54 md5: 35f29eec58405aaf55e01cb470d8c26a @@ -1767,6 +2754,16 @@ packages: 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 @@ -1867,49 +2864,50 @@ packages: purls: [] size: 88657 timestamp: 1723861474602 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.50.4-h0c1763c_0.conda - sha256: 6d9c32fc369af5a84875725f7ddfbfc2ace795c28f246dc70055a79f9b2003da - md5: 0b367fad34931cb79e0d6b7e5c06bb1c +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.51.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: 932581 - timestamp: 1753948484112 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.50.4-h39a8b3b_0.conda - sha256: 466366b094c3eb4b1d77320530cbf5400e7a10ab33e4824c200147488eebf7a6 - md5: 156bfb239b6a67ab4a01110e6718cbc4 + 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: 980121 - timestamp: 1753948554003 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.50.4-h4237e3c_0.conda - sha256: 802ebe62e6bc59fc26b26276b793e0542cfff2d03c086440aeaf72fb8bbcec44 - md5: 1dcb0468f5146e38fae99aef9656034b + 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: 902645 - timestamp: 1753948599139 -- conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.50.4-hf5d6505_0.conda - sha256: 5dc4f07b2d6270ac0c874caec53c6984caaaa84bc0d3eb593b0edf3dc8492efa - md5: ccb20d946040f86f0c05b644d5eadeca + 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: 1288499 - timestamp: 1753948889360 + size: 1292710 + timestamp: 1762299749044 - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h8f9b012_7.conda sha256: 1b981647d9775e1cdeb2fab0a4dd9cd75a6b0de2963f6c3953dbd712f78334b3 md5: 5b767048b1b3ee9a954b06f4084f93dc @@ -1923,6 +2921,16 @@ packages: 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 @@ -2116,15 +3124,34 @@ packages: - 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/f5/b7/339b9ed180c28418f3c5c425f341759ce3722b61cc54f8c20918a034a1d5/mpld3-0.5.11-py3-none-any.whl +- 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.11 - sha256: 99c086c51e03372c91620e715031ffae43fa6263207784214a1efbe2254702f6 + version: 0.5.12 + sha256: bea31799a4041029a906f53f2662bbf1c49903e0c0bc712b412354158ec7cf54 requires_dist: - jinja2 - matplotlib @@ -2176,10 +3203,10 @@ packages: requires_dist: - typing-extensions>=4.1.0 ; python_full_version < '3.11' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/62/cd/9481a199a086ac9f91eaa232b56cff90ca7fdc2cb6658de93825b1007094/narwhals-2.10.1-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/0b/9a/c6f79de7ba3a0a8473129936b7b90aa461d3d46fec6f1627672b1dccf4e9/narwhals-2.12.0-py3-none-any.whl name: narwhals - version: 2.10.1 - sha256: eed3d9ec8f821963456fef306c1ad11017995982169fca1f38f71c97d6a97b9b + version: 2.12.0 + sha256: baeba5d448a30b04c299a696bd9ee5ff73e4742143e06c49ca316b46539a7cbb requires_dist: - cudf>=24.10.0 ; extra == 'cudf' - dask[dataframe]>=2024.8 ; extra == 'dask' @@ -2196,6 +3223,112 @@ packages: - 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 @@ -2224,6 +3357,11 @@ packages: 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 @@ -2239,29 +3377,40 @@ packages: version: 0.3.2 sha256: 7064ccf5ace75825bd7bf57859daaaf16ed28660c1c6b306b649a9eda4b54b1e requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/09/97/fd421e8bc50766665ad35536c2bb4ef916533ba1fdd053a62d96cc7c8b95/numpy-2.3.4-cp313-cp313-win_amd64.whl +- 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.4 - sha256: 56209416e81a7893036eea03abcb91c130643eb14233b2515c90dcac963fe99d + version: 2.3.5 + sha256: 00dc4e846108a382c5869e77c6ed514394bdeb3403461d25a829711041217d5b requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/3e/46/bdd3370dcea2f95ef14af79dbf81e6927102ddf1cc54adc0024d61252fd9/numpy-2.3.4-cp313-cp313-macosx_11_0_arm64.whl +- 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.4 - sha256: a13fc473b6db0be619e45f11f9e81260f7302f8d180c49a22b6e6120022596b3 + version: 2.3.5 + sha256: aa5bc7c5d59d831d9773d1170acac7893ce3a5e130540605770ade83280e7188 requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/57/7e/b72610cc91edf138bc588df5150957a4937221ca6058b825b4725c27be62/numpy-2.3.4-cp313-cp313-macosx_10_13_x86_64.whl +- 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.4 - sha256: c090d4860032b857d94144d1a9976b8e36709e40386db289aaf6672de2a81966 + version: 2.3.5 + sha256: d0f23b44f57077c1ede8c5f26b30f706498b4862d3ff0a7298b8411dd2f043ff requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/9e/7e/7d306ff7cb143e6d975cfa7eb98a93e73495c4deabb7d1b5ecf09ea0fd69/numpy-2.3.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.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 name: numpy - version: 2.3.4 - sha256: fc8a63918b04b8571789688b2780ab2b4a33ab44bfe8ccea36d3eba51228c953 + version: 2.3.5 + sha256: 11e06aa0af8c0f05104d56450d6093ee639e15f24ecf62d417329d06e522e017 requires_python: '>=3.11' -- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.4-h26f9b46_0.conda - sha256: e807f3bad09bdf4075dbb4168619e14b0c0360bacb2e12ef18641a834c8c5549 - md5: 14edad12b59ccbfa3910d42c72adc2a0 +- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.0-h26f9b46_0.conda + sha256: a47271202f4518a484956968335b2521409c8173e123ab381e775c358c67fe6d + md5: 9ee58d5c534af06558933af3c845a780 depends: - __glibc >=2.17,<3.0.a0 - ca-certificates @@ -2269,33 +3418,33 @@ packages: license: Apache-2.0 license_family: Apache purls: [] - size: 3119624 - timestamp: 1759324353651 -- conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.5.4-h230baf5_0.conda - sha256: 3ce8467773b2472b2919412fd936413f05a9b10c42e52c27bbddc923ef5da78a - md5: 075eaad78f96bbf5835952afbe44466e + 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: 2747108 - timestamp: 1759326402264 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.5.4-h5503f6c_0.conda - sha256: f0512629f9589392c2fb9733d11e753d0eab8fc7602f96e4d7f3bd95c783eb07 - md5: 71118318f37f717eefe55841adb172fd + 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: 3067808 - timestamp: 1759324763146 -- conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.5.4-h725018a_0.conda - sha256: 5ddc1e39e2a8b72db2431620ad1124016f3df135f87ebde450d235c212a61994 - md5: f28ffa510fe055ab518cbd9d6ddfea23 + 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 @@ -2304,8 +3453,8 @@ packages: license: Apache-2.0 license_family: Apache purls: [] - size: 9218823 - timestamp: 1759326176247 + 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' @@ -2675,11 +3824,33 @@ packages: - 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 @@ -2824,15 +3995,15 @@ packages: - pytest>=8.4.2 ; extra == 'test' - mypy>=1.18.2 ; extra == 'type' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/3f/93/023955c26b0ce614342d11cc0652f1e45e32393b6ab9d11a664a60e9b7b7/plotly-6.3.1-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/e7/c3/3031c931098de393393e1f93a38dc9ed6805d86bb801acc3cf2d5bd1e6b7/plotly-6.5.0-py3-none-any.whl name: plotly - version: 6.3.1 - sha256: 8b4420d1dcf2b040f5983eed433f95732ed24930e496d36eb70d211923532e64 + version: 6.5.0 + sha256: 5ac851e100367735250206788a2b1325412aa4a4917a4fe3e6f0bc5aa6f3d90a requires_dist: - narwhals>=1.15.1 - packaging - numpy ; extra == 'express' - - kaleido>=1.0.0 ; extra == 'kaleido' + - kaleido>=1.1.0 ; extra == 'kaleido' - pytest ; extra == 'dev-core' - requests ; extra == 'dev-core' - ruff==0.11.12 ; extra == 'dev-core' @@ -2875,6 +4046,20 @@ packages: - 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 @@ -2895,11 +4080,202 @@ packages: 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 @@ -2993,15 +4369,15 @@ packages: version: 1.2.0 sha256: 9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913 requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/0b/8b/6300fb80f858cda1c51ffa17075df5d846757081d11ab4aa35cef9e6258b/pytest-9.0.1-py3-none-any.whl name: pytest - version: 8.4.2 - sha256: 872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79 + version: 9.0.1 + sha256: 67be0030d194df2dfa7b556f2e56fb3c3315bd5c8822c6951162b92b32ce7dad requires_dist: - colorama>=0.4 ; sys_platform == 'win32' - exceptiongroup>=1 ; python_full_version < '3.11' - - iniconfig>=1 - - packaging>=20 + - iniconfig>=1.0.1 + - packaging>=22 - pluggy>=1.5,<2 - pygments>=2.7.2 - tomli>=1 ; python_full_version < '3.11' @@ -3012,7 +4388,7 @@ packages: - requests ; extra == 'dev' - setuptools ; extra == 'dev' - xmlschema ; extra == 'dev' - requires_python: '>=3.9' + 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 @@ -3142,10 +4518,36 @@ packages: - aiohttp>=3.4 ; extra == 'asyncio-client' - sphinx ; extra == 'docs' requires_python: '>=3.6' -- pypi: https://files.pythonhosted.org/packages/c0/1a/b393a06aa6f2f6ab4a9c5c160a62d488b17d6da5cf93a67bc13a6e3239cd/python_socketio-5.14.3-py3-none-any.whl +- 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.14.3 - sha256: a5208c1bbf45a8d6328d01ed67e3fa52ec8b186fd3ea44cfcfcbd120f0c71fbe + version: 5.15.0 + sha256: e93363102f4da6d8e7a8872bf4908b866c40f070e716aa27132891e643e2687c requires_dist: - bidict>=0.21.0 - python-engineio>=4.11.0 @@ -3170,6 +4572,52 @@ packages: 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 @@ -3211,6 +4659,15 @@ packages: - 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 @@ -3223,12 +4680,32 @@ packages: - 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/48/9c/6d8035cafa2d2d314f34e6cd9313a299de095b26e96f1c7312878f988eec/restructuredtext_lint-1.4.0.tar.gz +- pypi: https://files.pythonhosted.org/packages/af/63/ac52b32b33ae62f2076ed5c4f6b00e065e3ccbb2063e9a2e813b2bfc95bf/restructuredtext_lint-2.0.2-py3-none-any.whl name: restructuredtext-lint - version: 1.4.0 - sha256: 1b235c0c922341ab6c530390892eb9e92f90b9b75046063e047cacfb0f050c45 + 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 @@ -3239,25 +4716,45 @@ packages: - pyright==1.1.394 ; extra == 'lint' - pytest>=8 ; extra == 'test' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/03/15/51960ae340823c9859fb60c63301d977308735403e2134e17d1d2858c7fb/ruff-0.14.3-py3-none-win_amd64.whl +- 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.3 - sha256: d7b7006ac0756306db212fd37116cce2bd307e1e109375e1c6c106002df0ae5f + version: 0.14.6 + sha256: 167843a6f78680746d7e226f255d920aeed5e4ad9c03258094a2d49d3028b105 requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/a7/e7/138b883f0dfe4ad5b76b58bf4ae675f4d2176ac2b24bdd81b4d966b28c61/ruff-0.14.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- 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.3 - sha256: 0e2f8a0bbcffcfd895df39c9a4ecd59bb80dca03dc43f7fb63e647ed176b741e + version: 0.14.6 + sha256: 9f7539ea257aa4d07b7ce87aed580e485c40143f2473ff2f2b75aee003186004 requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/d3/c8/6724f4634c1daf52409fbf13fefda64aa9c8f81e44727a378b7b73dc590b/ruff-0.14.3-py3-none-macosx_10_12_x86_64.whl +- 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.3 - sha256: b6fd8c79b457bedd2abf2702b9b472147cd860ed7855c73a5247fa55c9117654 + version: 0.14.6 + sha256: 7f6007e55b90a2a7e93083ba48a9f23c3158c433591c33ee2e99a49b889c6332 requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/de/03/db1bce591d55fd5f8a08bb02517fa0b5097b2ccabd4ea1ee29aa72b67d96/ruff-0.14.3-py3-none-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/fb/02/82240553b77fd1341f80ebb3eaae43ba011c7a91b4224a9f317d8e6591af/ruff-0.14.6-py3-none-win_amd64.whl name: ruff - version: 0.14.3 - sha256: 71ff6edca490c308f083156938c0c1a66907151263c4abdcb588602c6e696a14 + 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 @@ -3539,6 +5036,16 @@ packages: - 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 @@ -3622,6 +5129,11 @@ packages: 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 @@ -3825,48 +5337,91 @@ packages: - sphinx>=5 ; extra == 'standalone' - pytest ; extra == 'test' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/80/c5/0c06759b95747882bb50abda18f5fb48c3e9b0fbfc6ebc0e23550b52415d/stevedore-5.5.0-py3-none-any.whl +- 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.5.0 - sha256: 18363d4d268181e8e8452e71a38cd77630f345b2ef6b4a8d5614dac5ee0d18cf - requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_hd72426e_102.conda - sha256: a84ff687119e6d8752346d1d408d5cf360dee0badd487a472aa8ddedfdc219e1 - md5: a0116df4f4ed05c303811a837d5b39d8 + 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: 3285204 - timestamp: 1748387766691 -- conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-hf689a15_2.conda - sha256: b24468006a96b71a5f4372205ea7ec4b399b0f2a543541e86f883de54cd623fc - md5: 9864891a6946c2fe037c02fca7392ab4 + 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: 3259809 - timestamp: 1748387843735 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h892fb3f_2.conda - sha256: cb86c522576fa95c6db4c878849af0bccfd3264daf0cc40dd18e7f4a7bfced0e - md5: 7362396c170252e7b7b0c8fb37fe9c78 + 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: 3125538 - timestamp: 1748388189063 -- conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h2c6b04d_2.conda - sha256: e3614b0eb4abcc70d98eae159db59d9b4059ed743ef402081151a948dce95896 - md5: ebd0e761de9aa879a51d22cc721bd095 + 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 @@ -3874,13 +5429,33 @@ packages: license: TCL license_family: BSD purls: [] - size: 3466348 - timestamp: 1748388121356 + 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 @@ -3912,10 +5487,25 @@ packages: - pytest-mock>=3 ; extra == 'testing' - pytest-randomly>=3 ; extra == 'testing' requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl +- 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.9.11.17 - sha256: 5d392f2d244deb1866556457d6f3516792124a23d1c3a463a2e8668a5d1c15dd + 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 @@ -3958,6 +5548,32 @@ packages: - 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 @@ -4036,13 +5652,40 @@ packages: - 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/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl +- 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.2.0 - sha256: b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736 + version: 1.3.2 + sha256: 61eea322cdf56e8cc904bd3ad7573359a242ba65688716b0710a5eb12beab584 requires_dist: - - h11>=0.9.0,<1 - requires_python: '>=3.7.0' + - 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 @@ -4079,16 +5722,3 @@ packages: - multidict>=4.0 - propcache>=0.2.1 requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda - sha256: a4166e3d8ff4e35932510aaff7aa90772f84b4d07e9f6f83c614cba7ceefe0eb - md5: 6432cb5d4ac0046c3ac0a8a0f95842f9 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - libstdcxx >=13 - - libzlib >=1.3.1,<2.0a0 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 567578 - timestamp: 1742433379869 diff --git a/pyproject.toml b/pyproject.toml index 1cf18064..3cdb977c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,7 +51,8 @@ dev = [ "pytest", "pytest-cov", "ruff", - "tox-gh-actions" + "tox-gh-actions", + "jupyterlab" ] docs = [ "doc8", diff --git a/src/easyscience/base_classes/new_base.py b/src/easyscience/base_classes/new_base.py index c4c96feb..873ab414 100644 --- a/src/easyscience/base_classes/new_base.py +++ b/src/easyscience/base_classes/new_base.py @@ -139,7 +139,7 @@ def __copy__(self) -> NewBase: return new_obj def __deepcopy__(self, memo): - return self.from_dict(self.as_dict()) + return self.__copy__() def __repr__(self) -> str: return f'{self.__class__.__name__} `{self.unique_name}`' diff --git a/tests/unit_tests/base_classes/test_new_base.py b/tests/unit_tests/base_classes/test_new_base.py index d6c7489a..b339cf42 100644 --- a/tests/unit_tests/base_classes/test_new_base.py +++ b/tests/unit_tests/base_classes/test_new_base.py @@ -130,4 +130,82 @@ def test_as_dict_with_skip(self): assert 'display_name' not in obj_dict assert obj_dict['@module'] == 'easyscience.base_classes.new_base' assert obj_dict['@class'] == 'NewBase' - assert '@version' in obj_dict \ No newline at end of file + assert '@version' in obj_dict + + def test_from_dict(self): + # When + obj_dict = { + '@module': 'easyscience.base_classes.new_base', + '@class': 'NewBase', + 'unique_name': 'from_dict_name', + 'display_name': 'From Dict Object' + } + # Then + obj = NewBase.from_dict(obj_dict) + # Expect + assert isinstance(obj, NewBase) + assert obj.unique_name == 'from_dict_name' + assert obj.display_name == 'From Dict Object' + assert global_object.map.get_item_by_key('from_dict_name') is obj + + def test_from_dict_not_easyscience(self): + # When + obj_dict = { + '@module': 'some.other.module', + '@class': 'NewBase', + 'unique_name': 'invalid_from_dict', + 'display_name': 'Invalid From Dict Object' + } + # Then Expect + with pytest.raises(ValueError, match='Input must be a dictionary representing an EasyScience object.'): + NewBase.from_dict(obj_dict) + + def test_from_dict_wrong_class(self): + # When + obj_dict = { + '@module': 'easyscience.base_classes.new_base', + '@class': 'SomeOtherClass', + 'unique_name': 'wrong_class_name', + 'display_name': 'Wrong Class Object' + } + # Then Expect + with pytest.raises(ValueError, match='Class name in dictionary does not match the expected class: NewBase.'): + NewBase.from_dict(obj_dict) + + def test__dir__(self): + # When + obj = NewBase() + dir_list = dir(obj) + # Then + expected_attributes = [ + 'as_dict', + 'unique_name', + 'display_name', + 'from_dict', + ] + # Expect + for attr in expected_attributes: + assert attr in dir_list + + def test_copy(self): + # When + obj = NewBase(unique_name="original_name", display_name="Original Object") + # Then + obj_copy = obj.__copy__() + # Expect + assert isinstance(obj_copy, NewBase) + assert obj_copy.unique_name != obj.unique_name + assert obj_copy.display_name == obj.display_name + assert global_object.map.get_item_by_key(obj_copy.unique_name) is obj_copy + + def test_deepcopy(self): + # When + obj = NewBase(unique_name="deepcopy_name", display_name="Deep Copy Object") + # Then + import copy + obj_deepcopy = copy.deepcopy(obj) + # Expect + assert isinstance(obj_deepcopy, NewBase) + assert obj_deepcopy.unique_name != obj.unique_name + assert obj_deepcopy.display_name == obj.display_name + assert global_object.map.get_item_by_key(obj_deepcopy.unique_name) is obj_deepcopy \ No newline at end of file From 390ad806478df755382a9f12296ea7f2593a48ed Mon Sep 17 00:00:00 2001 From: Christian Dam Vedel Date: Mon, 24 Nov 2025 23:17:26 +0100 Subject: [PATCH 19/24] First ModelBase tests --- .../base_classes/test_model_base.py | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 tests/unit_tests/base_classes/test_model_base.py 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..87ac8cdc --- /dev/null +++ b/tests/unit_tests/base_classes/test_model_base.py @@ -0,0 +1,35 @@ +# SPDX-FileCopyrightText: 2025 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +# © 2021-2025 Contributors to the EasyScience project Date: Tue, 25 Nov 2025 12:05:20 +0100 Subject: [PATCH 20/24] Changes from ADR discussions --- src/easyscience/base_classes/model_base.py | 43 ++++++++---- src/easyscience/base_classes/new_base.py | 4 +- .../base_classes/test_model_base.py | 65 +++++++++++++++---- .../unit_tests/base_classes/test_new_base.py | 14 ++-- 4 files changed, 93 insertions(+), 33 deletions(-) diff --git a/src/easyscience/base_classes/model_base.py b/src/easyscience/base_classes/model_base.py index 0fbdbd04..f6d4ba19 100644 --- a/src/easyscience/base_classes/model_base.py +++ b/src/easyscience/base_classes/model_base.py @@ -5,6 +5,8 @@ # © 2021-2025 Contributors to the EasyScience project None: 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_parameters(self) -> List[DescriptorNumber]: + def get_all_variables(self) -> List[DescriptorBase]: """ - Get all `Parameters` or `DescriptorNumber` objects as a list. + Get all `Descriptor` and `Parameter` objects as a list. - :return: List of `DescriptorNumber` or `Parameter` objects. + :return: List of `Descriptor` and `Parameter` objects. """ - params = [] + vars = [] for attr_name in dir(self): attr = getattr(self, attr_name) - if isinstance(attr, DescriptorNumber): - params.append(attr) - elif hasattr(attr, 'get_all_parameters'): - params += attr.get_all_parameters() - return params + if isinstance(attr, DescriptorBase): + vars.append(attr) + elif hasattr(attr, 'get_all_variables'): + vars += attr.get_all_variables() + return vars - def get_fit_parameters(self) -> List[Parameter]: + 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 isinstance(param, Parameter) and param.independent] + return [param for param in self.get_all_parameters() if param.independent] + + def get_fit_parameters(self) -> List[Parameter]: + """ + This is an alias for `get_fittable_parameters`. + To be removed when fully moved to new base classes and minimizer can be changed. + """ + return self.get_fittable_parameters() def get_free_parameters(self) -> List[Parameter]: """ @@ -69,7 +86,7 @@ def get_free_parameters(self) -> List[Parameter]: :return: List of `Parameter` objects. """ - return [param for param in self.get_fit_parameters() if not param.fixed] + return [param for param in self.get_fittable_parameters() if not param.fixed] @classmethod def from_dict(cls, obj_dict: Dict[str, Any]) -> None: diff --git a/src/easyscience/base_classes/new_base.py b/src/easyscience/base_classes/new_base.py index 873ab414..ba6a31fd 100644 --- a/src/easyscience/base_classes/new_base.py +++ b/src/easyscience/base_classes/new_base.py @@ -91,7 +91,7 @@ def display_name(self, name: str) -> None: raise TypeError('Display name must be a string or None') self._display_name = name - def as_dict(self, skip: Optional[List[str]] = None) -> Dict[str, Any]: + 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. @@ -134,7 +134,7 @@ def __dir__(self) -> Iterable[str]: def __copy__(self) -> NewBase: """Return a copy of the object.""" - temp = self.as_dict(skip=['unique_name']) + temp = self.to_dict(skip=['unique_name']) new_obj = self.__class__.from_dict(temp) return new_obj diff --git a/tests/unit_tests/base_classes/test_model_base.py b/tests/unit_tests/base_classes/test_model_base.py index 87ac8cdc..250a879e 100644 --- a/tests/unit_tests/base_classes/test_model_base.py +++ b/tests/unit_tests/base_classes/test_model_base.py @@ -6,11 +6,15 @@ from easyscience import global_object from easyscience.base_classes import ModelBase from easyscience import Parameter +from easyscience import DescriptorNumber +from easyscience.variable import DescriptorStr -class MockModelBase(ModelBase): - def __init__(self, display_name=None, unique_name=None, temperature=0): +class MockModelComponent(ModelBase): + def __init__(self, display_name=None, unique_name=None, temperature=0, room_temperature=22): super().__init__(display_name=display_name, unique_name=unique_name) self._temperature = Parameter(name="temperature", value=temperature) + self._room_temperature = DescriptorNumber(name="room_temperature", value=room_temperature) + self._status = DescriptorStr(name="status", value="OK") @property def temperature(self): @@ -20,16 +24,55 @@ def temperature(self): def temperature(self, value): self._temperature.value = value -class TestModelBase: + @property + def room_temperature(self): + return self._room_temperature + + @room_temperature.setter + def room_temperature(self, value): + self._room_temperature.value = value + + @property + def status(self): + return self._status + + @status.setter + def status(self, value): + self._status.value = value - @pytest.fixture - def clear(self): - # Clear the global object map before each test - global_object.map._clear() +class MockModelFull(ModelBase): + def __init__(self, display_name=None, unique_name=None, pressure=0): + super().__init__(display_name=display_name, unique_name=unique_name) + self._pressure = Parameter(name="pressure", value=pressure) + self._component = MockModelComponent(temperature=25, room_temperature=22) - def test_model_base_inheritance(self, clear): + @property + def pressure(self): + return self._pressure + + @pressure.setter + def pressure(self, value): + self._pressure.value = value + + @property + def component(self): + return self._component + +class TestModelBase: + + def test_init(self): # When Then - obj = ModelBase() + model = ModelBase(unique_name="test_model", display_name="Test Model") # Expect - assert isinstance(obj, ModelBase) - assert isinstance(obj, NewBase) \ No newline at end of file + assert model.unique_name == "test_model" + assert model.display_name == "Test Model" + + # def test_get_all_parameters_flat(self): + # # When + # model = MockModelComponent(temperature=25, room_temperature=22) + # # Then + # params = model.get_all_parameters() + # # Expect + # assert len(params) == 2 + # assert any(isinstance(p, Parameter) and p.name == "temperature" for p in params) + # assert any(type(p) is DescriptorNumber and p.name == "room_temperature" for p in params) \ No newline at end of file diff --git a/tests/unit_tests/base_classes/test_new_base.py b/tests/unit_tests/base_classes/test_new_base.py index b339cf42..e9ca79d7 100644 --- a/tests/unit_tests/base_classes/test_new_base.py +++ b/tests/unit_tests/base_classes/test_new_base.py @@ -93,11 +93,11 @@ def test_display_name_setter_invalid(self): with pytest.raises(TypeError, match='Display name must be a string or None'): obj.display_name = 101112 - def test_as_dict_full_params(self): + def test_to_dict_full_params(self): # When obj = NewBase(unique_name="test_name", display_name="Test Object") # Then - obj_dict = obj.as_dict() + obj_dict = obj.to_dict() # Expect assert isinstance(obj_dict, dict) assert obj_dict['unique_name'] == "test_name" @@ -106,11 +106,11 @@ def test_as_dict_full_params(self): assert obj_dict['@class'] == 'NewBase' assert '@version' in obj_dict - def test_as_dict_default_params(self): + def test_to_dict_default_params(self): # When obj = NewBase() # Then - obj_dict = obj.as_dict() + obj_dict = obj.to_dict() # Expect assert isinstance(obj_dict, dict) assert obj_dict['@module'] == 'easyscience.base_classes.new_base' @@ -119,11 +119,11 @@ def test_as_dict_default_params(self): assert 'unique_name' not in obj_dict assert 'display_name' not in obj_dict - def test_as_dict_with_skip(self): + def test_to_dict_with_skip(self): # When obj = NewBase(unique_name="skip_test", display_name="Skip Test Object") # Then - obj_dict = obj.as_dict(skip=['display_name']) + obj_dict = obj.to_dict(skip=['display_name']) # Expect assert isinstance(obj_dict, dict) assert obj_dict['unique_name'] == "skip_test" @@ -178,7 +178,7 @@ def test__dir__(self): dir_list = dir(obj) # Then expected_attributes = [ - 'as_dict', + 'to_dict', 'unique_name', 'display_name', 'from_dict', From e10a4fae1a99e415170809eadcb9227821849329 Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Tue, 25 Nov 2025 14:02:02 +0100 Subject: [PATCH 21/24] Test all get_variable methods --- src/easyscience/base_classes/model_base.py | 14 +-- .../base_classes/test_model_base.py | 92 +++++++++++++++++-- 2 files changed, 89 insertions(+), 17 deletions(-) diff --git a/src/easyscience/base_classes/model_base.py b/src/easyscience/base_classes/model_base.py index f6d4ba19..51049f83 100644 --- a/src/easyscience/base_classes/model_base.py +++ b/src/easyscience/base_classes/model_base.py @@ -73,13 +73,6 @@ def get_fittable_parameters(self) -> List[Parameter]: """ return [param for param in self.get_all_parameters() if param.independent] - def get_fit_parameters(self) -> List[Parameter]: - """ - This is an alias for `get_fittable_parameters`. - To be removed when fully moved to new base classes and minimizer can be changed. - """ - return self.get_fittable_parameters() - def get_free_parameters(self) -> List[Parameter]: """ Get all parameters which are currently free to be fitted as a list. @@ -87,6 +80,13 @@ def get_free_parameters(self) -> List[Parameter]: :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]) -> None: diff --git a/tests/unit_tests/base_classes/test_model_base.py b/tests/unit_tests/base_classes/test_model_base.py index 250a879e..849477b0 100644 --- a/tests/unit_tests/base_classes/test_model_base.py +++ b/tests/unit_tests/base_classes/test_model_base.py @@ -8,8 +8,12 @@ from easyscience import Parameter from easyscience import DescriptorNumber from easyscience.variable import DescriptorStr +from unittest.mock import MagicMock class MockModelComponent(ModelBase): + """ + A simple mock model component with some parameters and descriptors. + """ def __init__(self, display_name=None, unique_name=None, temperature=0, room_temperature=22): super().__init__(display_name=display_name, unique_name=unique_name) self._temperature = Parameter(name="temperature", value=temperature) @@ -41,9 +45,13 @@ def status(self, value): self._status.value = value class MockModelFull(ModelBase): - def __init__(self, display_name=None, unique_name=None, pressure=0): + """ + A mock model that contains another model as a component. To test building nested models using the ModelBase class. + """ + def __init__(self, display_name=None, unique_name=None, pressure=0, area=1): super().__init__(display_name=display_name, unique_name=unique_name) self._pressure = Parameter(name="pressure", value=pressure) + self._area = Parameter(name="area", value=area) self._component = MockModelComponent(temperature=25, room_temperature=22) @property @@ -57,8 +65,23 @@ def pressure(self, value): @property def component(self): return self._component + + @property + def area(self): + return self._area + + @area.setter + def area(self, value): + self._area.value = value class TestModelBase: + @pytest.fixture + def nested_model(self, monkeypatch): + model = MockModelFull(pressure=1) + model.pressure.make_dependent_on(dependency_expression="2 * temperature + 5", dependency_map={'temperature': model.component.temperature}) + model.area.fixed = True + monkeypatch.setattr(model.component, 'get_all_variables', MagicMock(wraps=model.component.get_all_variables)) + return model def test_init(self): # When Then @@ -67,12 +90,61 @@ def test_init(self): assert model.unique_name == "test_model" assert model.display_name == "Test Model" - # def test_get_all_parameters_flat(self): - # # When - # model = MockModelComponent(temperature=25, room_temperature=22) - # # Then - # params = model.get_all_parameters() - # # Expect - # assert len(params) == 2 - # assert any(isinstance(p, Parameter) and p.name == "temperature" for p in params) - # assert any(type(p) is DescriptorNumber and p.name == "room_temperature" for p in params) \ No newline at end of file + def test_get_all_variables_flat(self): + # When + model = MockModelComponent(temperature=25, room_temperature=22) + # Then + vars = model.get_all_variables() + # Expect + assert len(vars) == 3 + assert any(isinstance(p, Parameter) for p in vars) + assert any(type(p) is DescriptorNumber for p in vars) + assert any(type(p) is DescriptorStr for p in vars) + + def test_get_all_variables_nested(self, nested_model): + # When Then + vars = nested_model.get_all_variables() + # Expect + assert len(vars) == 5 + assert any(isinstance(p, Parameter) and not p.independent for p in vars) + assert any(isinstance(p, Parameter) and p.fixed for p in vars) + assert any(isinstance(p, Parameter) and p.independent and not p.fixed for p in vars) + assert any(type(p) is DescriptorNumber for p in vars) + assert any(type(p) is DescriptorStr for p in vars) + assert nested_model.component.get_all_variables.call_count == 1 + + def test_get_all_parameters_nested(self, nested_model): + # When Then + params = nested_model.get_all_parameters() + # Expect + assert len(params) == 3 + assert any(isinstance(p, Parameter) and not p.independent for p in params) + assert any(isinstance(p, Parameter) and p.fixed for p in params) + assert any(isinstance(p, Parameter) and p.independent and not p.fixed for p in params) + assert nested_model.component.get_all_variables.call_count == 1 + + def test_get_fittable_parameters_nested(self, nested_model): + # When Then + fittable_params = nested_model.get_fittable_parameters() + # Expect + assert len(fittable_params) == 2 + assert any(isinstance(p, Parameter) and p.fixed for p in fittable_params) + assert any(isinstance(p, Parameter) and p.independent and not p.fixed for p in fittable_params) + assert nested_model.component.get_all_variables.call_count == 1 + + def test_get_fit_parameters_alias(self, monkeypatch): + # When + model = MockModelFull(pressure=1) + monkeypatch.setattr(model, 'get_free_parameters', MagicMock()) + # Then + fit_params = model.get_fit_parameters() + # Expect + assert model.get_free_parameters.call_count == 1 + + def test_get_free_parameters_nested(self, nested_model): + # When Then + free_params = nested_model.get_free_parameters() + # Expect + assert len(free_params) == 1 + assert any(isinstance(p, Parameter) and p.independent and not p.fixed for p in free_params) + assert nested_model.component.get_all_variables.call_count == 1 \ No newline at end of file From 99c83e315b614a138dbf81971746033c765a08ba Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Tue, 25 Nov 2025 16:23:25 +0100 Subject: [PATCH 22/24] Fix to recursive encoder to work with new base classes and nested deserialization tests --- src/easyscience/base_classes/model_base.py | 2 +- src/easyscience/io/serializer_base.py | 4 ++ .../base_classes/test_model_base.py | 68 ++++++++++++++++++- 3 files changed, 70 insertions(+), 4 deletions(-) diff --git a/src/easyscience/base_classes/model_base.py b/src/easyscience/base_classes/model_base.py index 51049f83..9546ed92 100644 --- a/src/easyscience/base_classes/model_base.py +++ b/src/easyscience/base_classes/model_base.py @@ -99,7 +99,7 @@ def from_dict(cls, obj_dict: Dict[str, Any]) -> None: 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) + kwargs = SerializerBase.deserialize_dict(obj_dict) parameter_placeholder = {} for key, value in kwargs.items(): if isinstance(value, DescriptorNumber): diff --git a/src/easyscience/io/serializer_base.py b/src/easyscience/io/serializer_base.py index 83cc530d..b4eeaf2a 100644 --- a/src/easyscience/io/serializer_base.py +++ b/src/easyscience/io/serializer_base.py @@ -341,10 +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/tests/unit_tests/base_classes/test_model_base.py b/tests/unit_tests/base_classes/test_model_base.py index 849477b0..86e8cbdf 100644 --- a/tests/unit_tests/base_classes/test_model_base.py +++ b/tests/unit_tests/base_classes/test_model_base.py @@ -9,6 +9,7 @@ from easyscience import DescriptorNumber from easyscience.variable import DescriptorStr from unittest.mock import MagicMock +from easyscience.io import SerializerBase class MockModelComponent(ModelBase): """ @@ -48,11 +49,14 @@ class MockModelFull(ModelBase): """ A mock model that contains another model as a component. To test building nested models using the ModelBase class. """ - def __init__(self, display_name=None, unique_name=None, pressure=0, area=1): + def __init__(self, component = None, display_name=None, unique_name=None, pressure=0, area=1): super().__init__(display_name=display_name, unique_name=unique_name) self._pressure = Parameter(name="pressure", value=pressure) self._area = Parameter(name="area", value=area) - self._component = MockModelComponent(temperature=25, room_temperature=22) + if component is not None: + self._component = component + else: + self._component = MockModelComponent(temperature=25, room_temperature=22) @property def pressure(self): @@ -83,6 +87,11 @@ def nested_model(self, monkeypatch): monkeypatch.setattr(model.component, 'get_all_variables', MagicMock(wraps=model.component.get_all_variables)) return model + @pytest.fixture + def clear(self): + # Clear the global object map before each test + global_object.map._clear() + def test_init(self): # When Then model = ModelBase(unique_name="test_model", display_name="Test Model") @@ -147,4 +156,57 @@ def test_get_free_parameters_nested(self, nested_model): # Expect assert len(free_params) == 1 assert any(isinstance(p, Parameter) and p.independent and not p.fixed for p in free_params) - assert nested_model.component.get_all_variables.call_count == 1 \ No newline at end of file + assert nested_model.component.get_all_variables.call_count == 1 + + def test_from_dict(self, monkeypatch, clear): + # When + model = MockModelComponent() + obj_dict = model.to_dict() # We only care about deserializing dictorionaries currently created by EasyScience + obj_dict['@module'] = 'easyscience.base_classes.model_base' + monkeypatch.setattr(SerializerBase, '_import_class', MagicMock(side_effect=lambda module_name, class_name: + MockModelComponent if class_name == 'MockModelComponent' + else Parameter if class_name == 'Parameter' + else DescriptorNumber) + ) + global_object.map._clear() + # Then + new_model = MockModelComponent.from_dict(obj_dict) + # Expect + assert isinstance(new_model, MockModelComponent) + assert new_model._temperature.value == 0.0 + assert new_model._room_temperature.value == 22.0 + assert isinstance(new_model._temperature, Parameter) + assert isinstance(new_model._room_temperature, DescriptorNumber) + assert len(global_object.map.vertices()) == 4 + assert global_object.map.get_item_by_key('Parameter_0') is new_model._temperature + assert len([param for param in global_object.map.vertices() if param.startswith('Parameter')]) == 1 + + def test_from_dict_nested(self, monkeypatch, clear): + # When + model = MockModelFull() + model.__class__.__module__ = 'easyscience' # Ensure mock class is seen as easyscience + model.component.__class__.__module__ = 'easyscience' # Ensure nested class is also seen as easyscience + obj_dict = model.to_dict() # We only care about deserializing dictorionaries currently created by EasyScience + monkeypatch.setattr(SerializerBase, '_import_class', MagicMock(side_effect=lambda module_name, class_name: + MockModelFull if class_name == 'MockModelFull' + else MockModelComponent if class_name == 'MockModelComponent' + else Parameter if class_name == 'Parameter' + else DescriptorNumber) + ) + global_object.map._clear() + # Then + new_model = MockModelFull.from_dict(obj_dict) + # Expect + assert isinstance(new_model, MockModelFull) + assert isinstance(new_model.component, MockModelComponent) + assert new_model._pressure.value == 0.0 + assert new_model._area.value == 1.0 + assert new_model.component._temperature.value == 25.0 + assert new_model.component._room_temperature.value == 22.0 + assert isinstance(new_model._pressure, Parameter) + assert isinstance(new_model._area, Parameter) + assert isinstance(new_model.component._temperature, Parameter) + assert isinstance(new_model.component._room_temperature, DescriptorNumber) + assert len(global_object.map.vertices()) == 7 + assert global_object.map.get_item_by_key('Parameter_0') in [new_model._pressure, new_model._area, new_model.component._temperature] + assert len([param for param in global_object.map.vertices() if param.startswith('Parameter')]) == 3 From 5e67822ce67b6a1d4a25919080a95f2a03db7903 Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Tue, 25 Nov 2025 16:32:08 +0100 Subject: [PATCH 23/24] Final ModelBase unit tests --- src/easyscience/base_classes/model_base.py | 2 +- .../base_classes/test_model_base.py | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/easyscience/base_classes/model_base.py b/src/easyscience/base_classes/model_base.py index 9546ed92..905f377d 100644 --- a/src/easyscience/base_classes/model_base.py +++ b/src/easyscience/base_classes/model_base.py @@ -80,7 +80,7 @@ def get_free_parameters(self) -> List[Parameter]: :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`. diff --git a/tests/unit_tests/base_classes/test_model_base.py b/tests/unit_tests/base_classes/test_model_base.py index 86e8cbdf..bcaf4593 100644 --- a/tests/unit_tests/base_classes/test_model_base.py +++ b/tests/unit_tests/base_classes/test_model_base.py @@ -210,3 +210,25 @@ def test_from_dict_nested(self, monkeypatch, clear): assert len(global_object.map.vertices()) == 7 assert global_object.map.get_item_by_key('Parameter_0') in [new_model._pressure, new_model._area, new_model.component._temperature] assert len([param for param in global_object.map.vertices() if param.startswith('Parameter')]) == 3 + + def test_from_dict_not_easyscience(self): + # When + obj_dict = { + '@module': 'some.other.module', + '@class': 'NotAnEasyScienceObject', + 'some_property': 42 + } + # Then / Expect + with pytest.raises(ValueError, match='Input must be a dictionary representing an EasyScience object.'): + MockModelComponent.from_dict(obj_dict) + + def test_from_dict_wrong_class(self): + # When + obj_dict = { + '@module': 'easyscience.base_classes.model_base', + '@class': 'SomeOtherClass', + 'some_property': 42 + } + # Then / Expect + with pytest.raises(ValueError, match='Class name in dictionary does not match the expected class: MockModelComponent.'): + MockModelComponent.from_dict(obj_dict) From f8b081766c7cfa2e78be055fbcaf70c136faffa8 Mon Sep 17 00:00:00 2001 From: Christian Vedel Date: Tue, 25 Nov 2025 16:43:06 +0100 Subject: [PATCH 24/24] Add minimization integration test with ModelBase --- .../integration_tests/fitting/test_fitter.py | 47 ++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) 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