diff --git a/.gitignore b/.gitignore index 579ee93..6056115 100644 --- a/.gitignore +++ b/.gitignore @@ -9,5 +9,5 @@ dist pid logs .secop-ophyd - +tests/testgen/* _api diff --git a/cfg/ophyd_secop_test_cfg.py b/cfg/ophyd_secop_test_cfg.py index a97de52..e249aa8 100644 --- a/cfg/ophyd_secop_test_cfg.py +++ b/cfg/ophyd_secop_test_cfg.py @@ -4,6 +4,8 @@ import os import sys +from frappy.core import EnumType + # Get project root from environment variable project_root = os.environ.get("FRAPPY_PROJECT_ROOT") @@ -102,3 +104,35 @@ # pollinterval = Param(export=False), # value = Param(unit = 'K', test = 'customized value'), ) + + +class GasEnum(EnumType): + def __init__(self, gas_names): + gas_dict = {name: idx for idx, name in enumerate(gas_names)} + super().__init__(**gas_dict) + + +Mod( + "enum1", + "frappy_modules.ophyd_secop_test_modules.Test_Enum", + "test module for enum codegen testing", + group="test", + value=1, + gas_type=Param( + description="gaslist of MFC", + datatype=GasEnum(["AR", "N2", "H2"]), + ), +) + + +Mod( + "enum2", + "frappy_modules.ophyd_secop_test_modules.Test_Enum", + "test module for enum codegen testing", + group="test", + value=1, + gas_type=Param( + description="gaslist of MFC", + datatype=GasEnum(["AR", "N2", "H2", "CO2"]), + ), +) diff --git a/frappy_modules/ophyd_secop_test_modules.py b/frappy_modules/ophyd_secop_test_modules.py index 0114637..52e7f79 100644 --- a/frappy_modules/ophyd_secop_test_modules.py +++ b/frappy_modules/ophyd_secop_test_modules.py @@ -43,6 +43,22 @@ def read_status(self): return self.status +class Test_Enum(Readable): + value = Parameter("dummy val", IntRange()) + + gas_type = Parameter("gas type", EnumType(ZERO=0, ONE=1, THREE=3), readonly=False) + + def read_value(self): + return random.choice([1, 2, 3]) + + def read_gas_type(self): + return self.gas_type + + def write_gas_type(self, val): + self.gas_type = val + return val + + class Test_ND_arrays(Readable): Status = Enum(Readable.Status) diff --git a/pyproject.toml b/pyproject.toml index bd68e92..eb53838 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,8 @@ dev = [ 'isort', 'pytest', 'black', + 'jinja2', + 'autoflake', 'pep8-naming', 'mypy', 'wheel', diff --git a/src/secop_ophyd/AsyncFrappyClient.py b/src/secop_ophyd/AsyncFrappyClient.py index 4873abd..86ca54f 100644 --- a/src/secop_ophyd/AsyncFrappyClient.py +++ b/src/secop_ophyd/AsyncFrappyClient.py @@ -8,19 +8,19 @@ class AsyncFrappyClient: - def __init__(self, host: str, port: str, loop) -> None: + def __init__(self, host: str, port: str, log=Logger) -> None: self.host: str = host self.port: str = port - self.client: SecopClient = None - - self.loop = loop + self.loop: asyncio.AbstractEventLoop self.external: bool = False self.conn_timestamp: float - self.log = None + self.client: SecopClient = SecopClient(uri=host + ":" + port, log=log) + + self.log = self.client.log @property def state(self): @@ -46,18 +46,10 @@ def uri(self): def nodename(self): return self.client.nodename - @classmethod - async def create(cls, host, port, loop, log=Logger): - self = AsyncFrappyClient(host=host, port=port, loop=loop) - self.client = SecopClient(uri=host + ":" + port, log=log) - - self.log = self.client.log - - await self.connect(3) + async def connect(self, try_period=0): - return self + self.loop = asyncio.get_running_loop() - async def connect(self, try_period=0): await asyncio.to_thread(self.client.connect, try_period) self.conn_timestamp = time.time() diff --git a/src/secop_ophyd/GenNodeCode.py b/src/secop_ophyd/GenNodeCode.py index 401ac98..1890a06 100644 --- a/src/secop_ophyd/GenNodeCode.py +++ b/src/secop_ophyd/GenNodeCode.py @@ -1,278 +1,1144 @@ +"""Code generation for annotated ophyd device classes using Jinja2 templates.""" + +import ast import inspect +import io +import json +import linecache import re +import sys +import textwrap +import tokenize +from dataclasses import dataclass, field +from enum import StrEnum from importlib import import_module, reload from inspect import Signature +from logging import Logger from pathlib import Path +from types import ModuleType +from typing import get_type_hints + +import autoflake +import black +from frappy.client import get_datatype +from frappy.datatypes import DataType +from jinja2 import Environment, PackageLoader, select_autoescape +from ophyd_async.core import Signal, SignalR, SignalRW, StandardReadable +from ophyd_async.core import StandardReadableFormat as Format +from ophyd_async.core._utils import get_origin_class + +from secop_ophyd.SECoPDevices import ( + IGNORED_PROPS, + ParameterType, + PropertyType, + class_from_interface, + secop_enum_name_to_python, +) +from secop_ophyd.SECoPSignal import secop_dtype_obj_from_json +from secop_ophyd.util import SECoPdtype + + +def internalize_name(name: str) -> str: + """how to create internal names""" + if name.startswith("_"): + return name[1:] + return name + + +@dataclass +class EnumMember: + """Represents an enum member with name and value.""" + + name: str # Python identifier (e.g., "LOW") + value: str # Original SECoP string (e.g., "Low Energy") + description: str | None = None # Optional description + + +@dataclass +class EnumClass: + """Represents an enum class definition.""" + + name: str # Enum class name (e.g., "TemperatureRegulatorModeEnum") + members: list[EnumMember] + description: str | None = None # Optional enum description + base_enum_class: str = "StrictEnum" # "StrictEnum" or "SupersetEnum" + + +@dataclass +class ModuleAttribute: + """Represents a module attribute with name, type, and optional description.""" + + name: str + type: str + + +@dataclass +class PropertyAttribute: + """Represents a module property attribute with name, type""" + + name: str + type_param: str | None = ( + None # Optional type parameter like float for SignalRW[float] + ) + path_annotation: str | None = ( + None # Annotation like ParamPath(...) or PropPath(...) + ) + + type: str = "SignalR" # Default to SignalR for properties + + +@dataclass +class ParameterAttribute: + """Represents a module parameter attribute with name, type, and + optional description.""" + + name: str + type: str + type_param: str | None = ( + None # Optional type parameter like float for SignalRW[float] + ) + description: str | None = None # Optional description from SECoP or docstrings + + path_annotation: str | None = ( + None # Annotation like ParamPath(...) or PropPath(...) + ) + format_annotation: str | None = None # StandardReadableFormat.CONFIG_SIGNAL, etc. class Method: + """Represents a class method with signature and description. + + This class supports both old-style initialization (for backward compatibility) + and new-style dataclass initialization. + """ + def __init__(self, cmd_name: str, description: str, cmd_sign: Signature) -> None: - self.sig_str: str + """Initialize Method (backward compatibility constructor). + Args: + cmd_name: Name of the command + description: Description of the command + cmd_sign: Signature of the command + """ raw_sig_str: str = str(cmd_sign) - raw_sig_str = raw_sig_str.replace("typing.", "") if "self" in raw_sig_str: - self.sig_str = raw_sig_str + sig_str = raw_sig_str else: - self.sig_str = "(self, " + raw_sig_str[1:] + sig_str = "(self, " + raw_sig_str[1:] + self.name: str = cmd_name + self.signature: str = sig_str self.description: str = description - def __str__(self) -> str: - code = "" - code += " @abstractmethod \n" - code += f" def {self.name}{self.sig_str}:\n" - code += f' """{self.description}"""' - return code +@dataclass +class ModuleClass: + """Represents a module class to be generated.""" + + name: str + bases: list[str] + parameters: list[ParameterAttribute] = field(default_factory=list) + properties: list[PropertyAttribute] = field(default_factory=list) + methods: list[Method] = field(default_factory=list) + description: str = "" + enums: list[EnumClass] = field(default_factory=list) # Enum classes for this module + + +@dataclass +class NodeClass: + """Represents a node class to be generated.""" + + name: str + bases: list[str] + properties: list[PropertyAttribute] = field(default_factory=list) + modules: list[ModuleAttribute] = field(default_factory=list) + description: str = "" class GenNodeCode: - """Generates A Python Class for a given SECoP_Node_Device instance. This allows - autocompletiion and type hinting in IDEs, this is needed since the attributes of - the generated Ophyd devices are only known at runtime. + """Generates annotated Python classes for SECoP ophyd devices. + + This class can generate Python code in two ways: + 1. By introspecting a fully instantiated SECoP ophyd device + 2. From a SECoP JSON describe message (future feature) + + The generated code uses Jinja2 templates and is formatted with Black. """ ModName: str = "genNodeClass" - node_mod = None - module_folder_path: Path | None = None def __init__(self, path: str | None = None, log=None): - """Instantiates GenNodeCode, internally all atrribues on a node and module level - are collected. Additionally all the needed imports are collected in a dict - """ - # prevent circular import - from secop_ophyd.SECoPDevices import ( - SECoPBaseDevice, - SECoPCommunicatorDevice, - SECoPMoveableDevice, - SECoPNodeDevice, - SECoPReadableDevice, - SECoPTriggerableDevice, - SECoPWritableDevice, - ) - - self.dimport: dict[str, set[str]] = {} - self.dmodule: dict = {} - self.dnode: dict = {} - - self.import_string: str = "" - self.mod_cls_string: str = "" - self.node_cls_string: str = "" - - self.log = log + """Initialize the code generator. + Args: + path: Optional path to the module folder + log: Optional logger instance + """ + self.log: Logger | None = log + self.module_folder_path: Path | None = None if path is not None: self.module_folder_path = Path(path) - # resulting Class is supposed to be abstract - self.add_import("abc", "ABC") + # Data structures for classes and imports + self.imports: dict[str, set[str] | None] = {} + self.module_classes: list[ModuleClass] = [] + self.node_classes: list[NodeClass] = [] + self.enum_classes: list[EnumClass] = [] + self.node_mod: ModuleType | None = None + self.inline_comment_threshold: int = 120 + self.comment_wrap_width: int = 100 + + # Required imports for abstract classes self.add_import("abc", "abstractmethod") + self.add_import("typing", "Annotated as A") + self.add_import("ophyd_async.core", "SignalR") + self.add_import("ophyd_async.core", "SignalRW") + self.add_import("ophyd_async.core", "SignalX") + self.add_import("ophyd_async.core", "StandardReadableFormat as Format") + self.add_import("ophyd_async.core", "StrictEnum") + self.add_import("ophyd_async.core", "SupersetEnum") self.add_import("typing", "Any") + self.add_import("numpy", "ndarray") + self.add_import("secop_ophyd.SECoPDevices", "ParameterType") + self.add_import("secop_ophyd.SECoPDevices", "PropertyType") + # Add necessary Device imports + self.add_import("secop_ophyd.SECoPDevices", "SECoPDevice") + self.add_import("secop_ophyd.SECoPDevices", "SECoPCommunicatorDevice") + self.add_import("secop_ophyd.SECoPDevices", "SECoPReadableDevice") + self.add_import("secop_ophyd.SECoPDevices", "SECoPTriggerableDevice") + self.add_import("secop_ophyd.SECoPDevices", "SECoPWritableDevice") + self.add_import("secop_ophyd.SECoPDevices", "SECoPMoveableDevice") + self.add_import("secop_ophyd.SECoPDevices", "SECoPNodeDevice") + + # Setup Jinja2 environment + self.jinja_env = Environment( + loader=PackageLoader("secop_ophyd", "templates"), + autoescape=select_autoescape(), + trim_blocks=False, + lstrip_blocks=False, + keep_trailing_newline=True, + ) + + # Try to load existing generated module + self._load_existing_module() + + def _load_existing_module(self): + """Load existing generated module if present.""" mod_path = self.ModName if self.module_folder_path is not None: - str_path = str(self.module_folder_path) - rep_slash = str_path.replace("/", ".").replace("\\", ".") - mod_path = f"{rep_slash}.{self.ModName}" + # For absolute paths, we need to add to sys.path and import just the module + # name + if self.module_folder_path.is_absolute(): + str_path = str(self.module_folder_path) + if str_path not in sys.path: + sys.path.insert(0, str_path) + # Just use the module name when the folder is in sys.path + mod_path = self.ModName + else: + # For relative paths, construct the module path with dots + str_path = str(self.module_folder_path) + rep_slash = str_path.replace("/", ".").replace("\\", ".") + mod_path = f"{rep_slash}.{self.ModName}" + + # Remove cached module to ensure fresh import (important when module file + # has been modified or recreated between imports) + if mod_path in sys.modules: + del sys.modules[mod_path] + + # Clear linecache for the module file to ensure inspect.getsource() works + if self.module_folder_path is not None: + module_file = self.module_folder_path / f"{self.ModName}.py" + linecache.checkcache(str(module_file)) try: self.node_mod = import_module(mod_path) + self._parse_existing_module() except ModuleNotFoundError: if self.log is None: - print("no code generated yet, building from scratch") + print("No code generated yet, building from scratch") else: - self.log.info("no code generated yet, building from scratch") + self.log.info("No code generated yet, building from scratch") + + def _parse_existing_module(self): + """Parse an existing generated module to extract class definitions.""" + # Prevent circular import + + from secop_ophyd.SECoPDevices import ( + SECoPDevice, + SECoPNodeDevice, + ) + + if self.node_mod is None: return modules = inspect.getmembers(self.node_mod) + # Filter to only classes defined in this module, not imported ones + class_members = [ + m + for m in modules + if inspect.isclass(m[1]) and m[1].__module__ == self.node_mod.__name__ + ] + + enum_classes = [m for m in class_members if issubclass(m[1], StrEnum)] + node_classes = [m for m in class_members if issubclass(m[1], SECoPNodeDevice)] + module_classes = [ + m + for m in class_members + if issubclass(m[1], SECoPDevice) and not issubclass(m[1], SECoPNodeDevice) + ] - results = filter(lambda m: inspect.isclass(m[1]), modules) - for class_symbol, class_obj in results: - module = class_obj.__module__ + for class_symbol, class_obj in enum_classes: + self._parse_enum_class(class_symbol, class_obj) - if module == self.ModName: - # Node Classes + for class_symbol, class_obj in node_classes: + self._parse_node_class(class_symbol, class_obj) - def get_attrs(source: str) -> list[tuple[str, str]]: - source_list: list[str] = source.split("\n") - # remove first line - source_list.pop(0) - source_list.pop() + for class_symbol, class_obj in module_classes: + self._parse_module_class(class_symbol, class_obj) - # remove whitespace - source_list = [attr.replace(" ", "") for attr in source_list] - # split at colon - attrs: list[tuple[str, str]] = [] - for attr in source_list: - parts = attr.split(":", maxsplit=1) - if len(parts) == 2: - attrs.append((parts[0], parts[1])) + def _parse_node_class(self, class_symbol: str, class_obj: type): + """Parse a node class from existing module. - return attrs + Args: + class_symbol: Name of the class + class_obj: The class object + """ + # attrs = self._extract_attrs_from_source(inspect.getsource(class_obj)) + + bases = [base.__name__ for base in class_obj.__bases__] + + # Extract description from docstring + description = inspect.getdoc(class_obj) or "" + + _, properties, modules = self._get_attr_list(class_obj) - if issubclass(class_obj, SECoPNodeDevice): - attrs = get_attrs(inspect.getsource(class_obj)) + node_cls = NodeClass( + name=class_symbol, + bases=bases, + properties=properties, + modules=modules, + description=description, + ) + self.node_classes.append(node_cls) + + def _extract_descriptions_from_source(self, class_obj: type) -> dict[str, str]: + """Extract trailing comment descriptions from class source code. - bases = [base.__name__ for base in class_obj.__bases__] + Uses ``ast`` to find class-level annotated attributes and ``tokenize`` to + read actual Python comment tokens. This avoids false positives from ``#`` + inside strings and ignores non-attribute annotations. - self.add_node_class(class_symbol, bases, attrs) + Args: + class_obj: The class object to extract descriptions from + + Returns: + Dictionary mapping attribute names to their descriptions + """ + descriptions: dict[str, str] = {} + try: + source = textwrap.dedent(inspect.getsource(class_obj)) + source_lines = source.splitlines() + module_ast = ast.parse(source) + + class_nodes = [ + node for node in module_ast.body if isinstance(node, ast.ClassDef) + ] + if not class_nodes: + return descriptions + + class_node = class_nodes[0] + + comments_by_line: dict[int, list[str]] = {} + for token_info in tokenize.generate_tokens(io.StringIO(source).readline): + if token_info.type != tokenize.COMMENT: continue - if issubclass( - class_obj, - ( - SECoPBaseDevice, - SECoPCommunicatorDevice, - SECoPMoveableDevice, - SECoPReadableDevice, - SECoPWritableDevice, - SECoPTriggerableDevice, - ), - ): + comment_text = token_info.string[1:].lstrip().rstrip() + comments_by_line.setdefault(token_info.start[0], []).append( + comment_text + ) + + for node in class_node.body: + if not isinstance(node, ast.AnnAssign): + continue - attributes = [] + if not isinstance(node.target, ast.Name): + continue - for attr_name, attr in class_obj.__annotations__.items(): - attributes.append((attr_name, attr.__name__)) + attr_name = node.target.id + annotation_end_line = getattr(node, "end_lineno", node.lineno) + description_lines: list[str] = [] - methods = [] + # Inline comment on the annotation line. + description_lines.extend(comments_by_line.get(annotation_end_line, [])) - for method_name, method in class_obj.__dict__.items(): - if callable(method) and not method_name.startswith("__"): + # Multiline trailing comment block directly below the annotation. + next_line_no = annotation_end_line + 1 + while next_line_no <= len(source_lines): + stripped_line = source_lines[next_line_no - 1].lstrip() + if not stripped_line.startswith("#"): + break - method_source = inspect.getsource(method) + description_lines.extend(comments_by_line.get(next_line_no, [])) + next_line_no += 1 - match = re.search( - r"\s*def\s+\w+\s*\(.*\).*:\s*", method_source - ) - if match: - function_body = method_source[match.end() :] - description_list = function_body.split('"""', 2) - description = description_list[1] - else: - raise Exception( - "could not extract description function body" - ) + description = "\n".join(description_lines).rstrip() + if description: + descriptions[attr_name] = description + except Exception as e: + if self.log: + self.log.debug(f"Could not extract descriptions from source: {e}") - methods.append( - Method( - method_name, description, inspect.signature(method) - ) + return descriptions + + def _normalize_description(self, description: str | None) -> str: + """Normalize description text for generated comments. + + - Trim trailing whitespace/newlines + - Preserve intentional internal newlines + """ + if description is None: + return "" + + normalized = description.rstrip() + return normalized if normalized else "" + + def _get_attr_list( + self, class_obj: type + ) -> tuple[ + list[ParameterAttribute], list[PropertyAttribute], list[ModuleAttribute] + ]: + hints = get_type_hints(class_obj) + # Get hints with Annotated for wrapping signals and backends + extra_hints = get_type_hints(class_obj, include_extras=True) + + # Extract description comments from source code + descriptions = self._extract_descriptions_from_source(class_obj) + + modules = [] + properties = [] + parameters = [] + + for attr_name, annotation in hints.items(): + extras = getattr(extra_hints[attr_name], "__metadata__", ()) + + origin = get_origin_class(annotation) + + if issubclass(origin, Signal): + + sig_type = annotation.__args__[0] + # Get the module name + module = sig_type.__module__ + + type_param = ( + sig_type.__name__ if module == "builtins" else sig_type.__name__ + ) + + path_annotation = next( + (e for e in extras if isinstance(e, (ParameterType, PropertyType))), + None, + ) + category = ( + "property" + if isinstance(path_annotation, PropertyType) + else "parameter" + ) + format_annotation = next( + (e for e in extras if isinstance(e, Format)), None + ) + if format_annotation is not None: + format_annotation = f"Format.{format_annotation.name}" + + # Get description from comments + description = descriptions.get(attr_name) + + match category: + case "property": + properties.append( + PropertyAttribute( + name=attr_name, + type=origin.__name__, + type_param=type_param, + path_annotation=str(path_annotation), ) + ) + case "parameter": + parameters.append( + ParameterAttribute( + name=attr_name, + type=origin.__name__, + type_param=type_param, + description=description, + path_annotation=str(path_annotation), + format_annotation=format_annotation, + ) + ) - bases = [base.__name__ for base in class_obj.__bases__] + if issubclass(origin, StandardReadable): + modules.append(ModuleAttribute(name=attr_name, type=origin.__name__)) - self.add_mod_class(class_symbol, bases, attributes, methods) - continue + return parameters, properties, modules - else: - self.add_import(module, class_symbol) + def _parse_enum_class(self, class_symbol: str, class_obj: type): + """Parse an enum class from existing module. + + Args: + class_symbol: Name of the class + class_obj: The class object + + """ + # Extract description from docstring + description = inspect.getdoc(class_obj) or "" + + # Extract enum members from class attributes + members = [] + + for attr_name, attr_value in class_obj.__dict__.items(): + # Skip private/magic attributes and methods + if attr_name.startswith("_") or callable(attr_value): + continue + + # Create an EnumMember for each enum value + # attr_name is the member name (e.g., "RAMP") + # attr_value is the member value (e.g., "ramp") + member = EnumMember(name=attr_name, value=attr_value, description=None) + members.append(member) + + bases = [base.__name__ for base in class_obj.__bases__] + + # Create and return the EnumClass + self.enum_classes.append( + EnumClass( + name=class_symbol, + members=members, + description=description, + base_enum_class=bases[0] if bases else "StrictEnum", + ) + ) + + def _parse_module_class(self, class_symbol: str, class_obj: type): + """Parse a module class from existing module. + + Args: + class_symbol: Name of the class + class_obj: The class object + """ + # Extract attributes from source code to get proper type annotations + + parameters, properties, _ = self._get_attr_list(class_obj) + + methods = [] + for method_name, method in class_obj.__dict__.items(): + if callable(method) and not method_name.startswith("__"): + method_source = inspect.getsource(method) + description = self._extract_method_description(method_source) + methods.append( + Method(method_name, description, inspect.signature(method)) + ) + + bases = [base.__name__ for base in class_obj.__bases__] + + # Extract description from docstring + description = inspect.getdoc(class_obj) or "" + + mod_enums: list[EnumClass] = [] + enums = {enum_class.name: enum_class for enum_class in self.enum_classes} + + for param in parameters: + if param.type_param in enums: + enum_class = enums[param.type_param] + if enum_class not in mod_enums: + mod_enums.append(enum_class) + + mod_cls = ModuleClass( + name=class_symbol, + bases=bases, + parameters=parameters, + properties=properties, + methods=methods, + description=description, + enums=mod_enums, + ) + self.module_classes.append(mod_cls) + + def _extract_method_description(self, method_source: str) -> str: + """Extract description from method docstring. - def add_import(self, module: str, class_str: str): - """adds an Import to the import dict + Args: + method_source: Source code of the method - :param module: Python Module of the dict - :type module: str - :param class_str: Class that is to be imported - :type class_str: str + Returns: + Description string """ - if self.dimport.get(module): - self.dimport[module].add(class_str) + match = re.search(r"\s*def\s+\w+\s*\(.*\).*:\s*", method_source) + if match: + function_body = method_source[match.end() :] + description_list = function_body.split('"""', 2) + if len(description_list) > 1: + return description_list[1] + return "" + + def add_import(self, module: str, class_str: str | None = None): + """Add an import to the import dictionary. + + Args: + module: Python module to import from + class_str: Class/symbol to import. If None or empty, imports the module + directly. + """ + if class_str is None or class_str == "": + # For module-only imports (import module), use None as value + if module not in self.imports: + self.imports[module] = None else: - self.dimport[module] = {class_str} + existing = self.imports.get(module) + if existing is None: + # Convert from module-only import to specific imports + self.imports[module] = {class_str} + elif isinstance(existing, set): + existing.add(class_str) + else: + self.imports[module] = {class_str} def add_mod_class( self, module_cls: str, bases: list[str], - attrs: list[tuple[str, str]], + parameters: list[ParameterAttribute], + properties: list[PropertyAttribute], cmd_plans: list[Method], + description: str = "", + enum_classes: list[EnumClass] | None = None, ): - """adds module class to the module dict - - :param module_cls: name of the new Module class - :type module_cls: str - :param bases: bases the new class is derived from - :type bases: list[str] - :param attrs: list of attributes of the class - :type attrs: tuple[str, str] + """Add a module class to be generated. + + Args: + module_cls: Name of the module class + bases: Base classes + parameters: List of parameter attributes + properties: List of property attributes + cmd_plans: List of method definitions + description: Optional class description """ - self.dmodule[module_cls] = {"bases": bases, "attrs": attrs, "plans": cmd_plans} + # Check if class already exists (loaded from file) + existing_class = next( + (cls for cls in self.module_classes if cls.name == module_cls), None + ) + if existing_class: + # Class already exists - merge enums if provided + if enum_classes: + existing_class.enums.extend(enum_classes) + if self.log: + self.log.info( + f"Module class {module_cls} already exists, " + f"merged {len(enum_classes)} enum(s)" + ) + return + + mod_cls = ModuleClass( + name=module_cls, + bases=bases, + parameters=parameters, + properties=properties, + methods=cmd_plans, + description=description, + enums=enum_classes or [], + ) + self.module_classes.append(mod_cls) def add_node_class( - self, node_cls: str, bases: list[str], attrs: list[tuple[str, str]] + self, + node_cls: str, + bases: list[str], + properties: list[PropertyAttribute], + modules: list[ModuleAttribute], + description: str = "", ): - self.dnode[node_cls] = {"bases": bases, "attrs": attrs} + """Add a node class to be generated. - def _write_imports_string(self): - self.import_string = "" + Args: + node_cls: Name of the node class + bases: Base classes + attrs: List of attribute tuples. Supported formats: + - (name, type) + - (name, type, type_param) + - (name, type, type_param, description, category) + """ + # Check if class already exists (loaded from file) + existing_class = next( + (cls for cls in self.node_classes if cls.name == node_cls), None + ) + if existing_class: + # Class already exists, skip adding it + if self.log: + self.log.info(f"Node class {node_cls} already exists, skipping") + return - # Collect imports required for type hints (Node) - for mod, cls_set in self.dimport.items(): - if len(cls_set) == 1 and list(cls_set)[0] == "": - self.import_string += f"import {mod} \n" - continue + node_class = NodeClass( + name=node_cls, + bases=bases, + properties=properties, + modules=modules, + description=description, + ) + self.node_classes.append(node_class) + + def _parse_command_signature( + self, cmd_name: str, datainfo: dict, description: str + ) -> Method: + """Parse command datainfo to create Method signature. + + Args: + cmd_name: Name of the command + datainfo: Command datainfo with argument/result types + description: Command description + + Returns: + Method object with signature + """ + # Extract argument and result types + arg_type = datainfo.get("argument") - cls_string = ", ".join(cls_set) + # Create a basic signature object + sig = Signature.from_callable(lambda self, wait_for_idle=False: None) + if arg_type is not None: + sig = Signature.from_callable(lambda self, arg, wait_for_idle=False: None) - self.import_string += f"from {mod} import {cls_string} \n" - self.import_string += "\n\n" + return Method(cmd_name=cmd_name, description=description, cmd_sign=sig) - def _write_mod_cls_string(self): - self.mod_cls_string = "" + def from_json_describe(self, json_data: str | dict): + """Generate classes from a SECoP JSON describe message. - for mod_cls, cls_dict in self.dmodule.items(): - # Generate the Python code for each Module class - bases = ", ".join(cls_dict["bases"]) + Args: + json_data: JSON string or dict containing SECoP describe data + """ + # Parse JSON if string + if isinstance(json_data, str): + describe_data = json.loads(json_data) + else: + describe_data = json_data - self.mod_cls_string += f"class {mod_cls}({bases}):\n" - # write Attributes - for attr_name, attr_type in cls_dict["attrs"]: - self.mod_cls_string += f" {attr_name}: {attr_type}\n" - self.mod_cls_string += "\n" - # write abstract methods - for plan in cls_dict["plans"]: - self.mod_cls_string += str(plan) + modules: dict[str, dict] = describe_data.get("modules", {}) + node_properties = {k: v for k, v in describe_data.items() if k != "modules"} - self.mod_cls_string += "\n\n" + # Parse modules + node_module_attrs: list[ModuleAttribute] = [] + node_property_attrs: list[PropertyAttribute] = [] - def _write_node_cls_string(self): - self.node_cls_string = "" + for modname, moddescr in modules.items(): + # separate accessibles into command and parameters + parameters = {} + commands = {} + accessibles = moddescr["accessibles"] + for aname, aentry in accessibles.items(): + iname = internalize_name(aname) + datatype = get_datatype(aentry["datainfo"], iname) - for node_cls, cls_dict in self.dnode.items(): - # Generate the Python code for each Module class - bases = ", ".join(cls_dict["bases"]) + aentry = dict(aentry, datatype=datatype) - self.node_cls_string += f"class {node_cls}({bases}):\n" - for attr_name, attr_type in cls_dict["attrs"]: - self.node_cls_string += f" {attr_name}: {attr_type}\n" + if datatype.IS_COMMAND: + commands[iname] = aentry + else: + parameters[iname] = aentry - self.node_cls_string += "\n\n" + properties = {k: v for k, v in moddescr.items() if k != "accessibles"} - def write_gen_node_class_file(self): - self._write_imports_string() - self._write_mod_cls_string() - self._write_node_cls_string() + # Add module class (highest secop interface class) that the actual + # module class is derived from + secop_ophyd_modclass = class_from_interface(properties) + module_bases = [secop_ophyd_modclass.__name__] + + # Add the module class, use self reported "implementation" module property, + # if not present use the module name + module_class = modname + if properties.get("implementation", ""): + module_class = properties.get("implementation", "").split(".").pop() + + module_class_list = ( + module_class.replace(" ", "_").replace("-", "_").split("_") + ) + + module_class = "".join(word.capitalize() for word in module_class_list) + + # Module enum classes + module_enum_classes = [] + + # Prepare attributes + + # Module Commands + command_plans = [] + + for command, command_data in commands.items(): + # Stop is already an ophyd native operation + if command == "stop": + continue + + argument = command_data["datainfo"].get("argument") + result = command_data["datainfo"].get("result") + + description: str = "" + description += f"{command_data['description']}\n" + + if argument: + description += ( + f" argument: {command_data['datainfo'].get('argument')}\n" + ) + if result: + description += ( + f" result: {command_data['datainfo'].get('result')}" + ) + + def command_plan(self, arg, wait_for_idle: bool = False): + pass + + def command_plan_no_arg(self, wait_for_idle: bool = False): + pass + + plan = Method( + cmd_name=command, + description=description, + cmd_sign=inspect.signature( + command_plan if argument else command_plan_no_arg + ), + ) + + command_plans.append(plan) + + mod_parameters: list[ParameterAttribute] = [] + + for param_name, param_data in parameters.items(): + + descr = self._normalize_description(param_data.get("description", "")) + unit = param_data["datainfo"].get("unit") + + if unit: + param_descr = ( + f"{descr}; Unit: ({unit})" if descr else f"Unit: ({unit})" + ) + else: + param_descr = descr + signal_base = SignalR if param_data["readonly"] else SignalRW + + format = None + + # infer format from parameter property + match param_data.get("_signal_format", None): + case "HINTED_SIGNAL": + format = Format.HINTED_SIGNAL + case "HINTED_UNCACHED_SIGNAL": + format = Format.HINTED_UNCACHED_SIGNAL + case "UNCACHED_SIGNAL": + format = Format.UNCACHED_SIGNAL + case _: + format = None + + # depending on the Interface class other parameter need to be declared + # as readsignals as well + if param_name in secop_ophyd_modclass.hinted_signals: + format = format or Format.HINTED_SIGNAL + + # Remove "StandardReadable" prefix from format for cleaner annotation + format = ( + str(format).removeprefix("StandardReadable") if format else None + ) + + datainfo = param_data.get("datainfo", {}) + + # infer the ophyd type from secop datatype + type_param = get_type_param(param_data["datatype"]) + + # Handle StrictEnum types - generate enum class + if type_param and "StrictEnum" in type_param: + # Generate unique enum class name: + # ModuleClass + ParamName + Enum + param_name_list = ( + param_name.replace(" ", "_").replace("-", "_").split("_") + ) + + param_name_camel = "".join( + word.capitalize() for word in param_name_list + ) + + enum_class_name = f"{module_class}_{param_name_camel}_Enum" + + # Extract enum members from datainfo + enum_members_dict = datainfo.get("members", {}) + if enum_members_dict: + from secop_ophyd.GenNodeCode import EnumClass, EnumMember + + enum_members = [] + for member_value, _ in enum_members_dict.items(): + # Convert member name to Python identifier + python_name = secop_enum_name_to_python(member_value) + enum_members.append( + EnumMember( + name=python_name, + value=member_value, + description=None, + ) + ) + + # Create enum class definition + enum_descr = f"{param_name} enum for `{module_class}`." + + enum_cls = EnumClass( + name=enum_class_name, + members=enum_members, + description=enum_descr, + ) + + module_enum_classes.append(enum_cls) - code = "" + # Use the specific enum class name instead of generic + # StrictEnum + type_param = enum_class_name - code += self.import_string - code += self.mod_cls_string - code += self.node_cls_string + # Default format for parameters is CONFIG_SIGNAL - # Write the generated code to a .py file + mod_parameters.append( + ParameterAttribute( + name=param_name, + type=signal_base.__name__, + type_param=type_param, + description=param_descr, + path_annotation=str(ParameterType()), + format_annotation=format, + ) + ) + # Module properties + module_properties: list[PropertyAttribute] = [] + + # Process module properties + for prop_name, property_value in properties.items(): + + if prop_name in IGNORED_PROPS: + continue + + type_param = get_type_param(secop_dtype_obj_from_json(property_value)) + + module_properties.append( + PropertyAttribute( + name=prop_name, + type=SignalR.__name__, + type_param=type_param, + path_annotation=str(PropertyType()), + ) + ) + + self.add_mod_class( + module_cls=module_class, + bases=module_bases, + parameters=mod_parameters, + properties=module_properties, + cmd_plans=command_plans, + description=properties.get("description", ""), + enum_classes=module_enum_classes, + ) + + # Add to node attributes + # Type the None explicitly as str | None to match other entries + node_module_attrs.append(ModuleAttribute(name=modname, type=module_class)) + + # Process module properties + for prop_name, property_value in node_properties.items(): + type_param = get_type_param(secop_dtype_obj_from_json(property_value)) + + # Generate PropPath annotation for node-level properties + + node_property_attrs.append( + PropertyAttribute( + name=prop_name, + type=SignalR.__name__, + type_param=type_param, + path_annotation=str(PropertyType()), + ) + ) + + # Add node class + node_bases = ["SECoPNodeDevice"] + + equipment_id: str = node_properties["equipment_id"] + + # format node class accordingly + node_class_name = equipment_id.replace(".", "_").replace("-", "_").capitalize() + + self.add_node_class( + node_cls=node_class_name, + bases=node_bases, + modules=node_module_attrs, + properties=node_property_attrs, + description=node_properties.get("description", ""), + ) + + # Add required imports + self.add_import("secop_ophyd.SECoPDevices", "SECoPNodeDevice") + self.add_import("secop_ophyd.SECoPDevices", "SECoPBaseDevice") + self.add_import("secop_ophyd.SECoPDevices", "SECoPCommunicatorDevice") + self.add_import("secop_ophyd.SECoPDevices", "SECoPReadableDevice") + self.add_import("secop_ophyd.SECoPDevices", "SECoPWritableDevice") + self.add_import("secop_ophyd.SECoPDevices", "SECoPMoveableDevice") + self.add_import("secop_ophyd.SECoPDevices", "SECoPTriggerableDevice") + + def generate_code(self) -> str: + """Generate Python code using Jinja2 template. + + Returns: + Generated Python code as string + """ + template = self.jinja_env.get_template("generated_classes.py.jinja2") + + # Prepare template context + context = { + "imports": self.imports, + "module_classes": self.module_classes, + "node_classes": self.node_classes, + "enum_classes": self._collect_all_enums(), + "inline_comment_threshold": self.inline_comment_threshold, + "comment_wrap_width": self.comment_wrap_width, + } + + # Render template + code = template.render(**context) + + # Remove unused imports with autoflake + try: + + code = autoflake.fix_code( + code, + remove_all_unused_imports=True, + remove_unused_variables=False, + remove_duplicate_keys=True, + ) + except Exception as e: + if self.log: + self.log.warning(f"Autoflake processing failed: {e}") + else: + print(f"Warning: Autoflake processing failed: {e}") + + # Format with Black + try: + code = black.format_str(code, mode=black.Mode(line_length=200)) + except Exception as e: + if self.log: + self.log.warning(f"Black formatting failed: {e}") + else: + print(f"Warning: Black formatting failed: {e}") + + return code + + def _collect_all_enums(self) -> list[EnumClass]: + """Collect and merge enum definitions from all module classes. + + When multiple module classes have enums with the same base name but different + members, they are merged into a single SupersetEnum containing the union of all + members. + + Returns: + List of deduplicated EnumClass definitions + """ + from collections import defaultdict + + # Group enums by their base name (ModuleClass + ParamName + Enum) + # We need to track which module classes use each enum + enum_groups = defaultdict(list) # base_name -> [(module_class, enum)] + + for mod_cls in self.module_classes: + for enum in mod_cls.enums: + # Extract base enum name by removing module class prefix + # e.g., "MassflowController1Gastype_selectEnum" -> need module class + # name + enum_groups[enum.name].append((mod_cls.name, enum)) + + # Process each enum group + merged_enums = [] + for enum_name, enum_list in enum_groups.items(): + if len(enum_list) == 1: + # Single enum definition - use StrictEnum + _, enum = enum_list[0] + + # an already merged enum read in from a file + if enum.base_enum_class != "StrictEnum": + merged_enums.append(enum) + continue + + enum.base_enum_class = "StrictEnum" + merged_enums.append(enum) + else: + # Multiple definitions - need to check if members are identical + member_sets = [ + frozenset((m.name, m.value) for m in enum.members) + for _, enum in enum_list + ] + + if len(set(member_sets)) == 1: + # All enums have identical members - use StrictEnum + _, enum = enum_list[0] + enum.base_enum_class = "StrictEnum" + merged_enums.append(enum) + else: + # Different members - merge into SupersetEnum + all_members_dict = {} # (name, value) -> EnumMember + + for _, enum in enum_list: + for member in enum.members: + key = (member.name, member.value) + if key not in all_members_dict: + all_members_dict[key] = member + + # Create merged enum with all unique members + _, base_enum = enum_list[0] + merged_enum = EnumClass( + name=enum_name, + members=list(all_members_dict.values()), + description=base_enum.description, + base_enum_class="SupersetEnum", + ) + merged_enums.append(merged_enum) + + return merged_enums + + def write_gen_node_class_file(self): + """Generate and write the class file to disk.""" + code = self.generate_code() + + # Determine file path if self.module_folder_path is None: filep = Path(f"{self.ModName}.py") else: filep = self.module_folder_path / f"{self.ModName}.py" + + # Write to file with open(filep, "w") as file: file.write(code) - # Reload the Module after its source has been edited + if self.log: + self.log.info(f"Generated class file: {filep}") + else: + print(f"Generated class file: {filep}") + + # Reload the module if self.node_mod is not None: reload(self.node_mod) + + +def get_type_param(secop_dtype: DataType) -> str | None: + sig_type = SECoPdtype(secop_dtype).np_datatype + + # Get the module name + module = sig_type.__module__ + + # For builtins, just return the name without module prefix + if module == "builtins": + return sig_type.__name__ + + return sig_type.__name__ + + +def get_type_prop(prop_value) -> str | None: + secop_dtype: DataType = secop_dtype_obj_from_json(prop_value) + return get_type_param(secop_dtype) diff --git a/src/secop_ophyd/SECoPDevices.py b/src/secop_ophyd/SECoPDevices.py index 829c9d2..36441f5 100644 --- a/src/secop_ophyd/SECoPDevices.py +++ b/src/secop_ophyd/SECoPDevices.py @@ -1,8 +1,9 @@ import asyncio -import inspect import logging import re import time as ttime +import warnings +from abc import abstractmethod from logging import Logger from types import MethodType from typing import Any, Dict, Iterator, Optional, Type @@ -33,7 +34,11 @@ from ophyd_async.core import ( DEFAULT_TIMEOUT, AsyncStatus, + Device, + DeviceConnector, + DeviceFiller, LazyMock, + Signal, SignalR, SignalRW, SignalX, @@ -44,13 +49,12 @@ from ophyd_async.core._utils import Callback from secop_ophyd.AsyncFrappyClient import AsyncFrappyClient -from secop_ophyd.GenNodeCode import GenNodeCode, Method -from secop_ophyd.logs import LOG_LEVELS, setup_logging +from secop_ophyd.logs import setup_logging from secop_ophyd.propertykeys import DATAINFO, EQUIPMENT_ID, INTERFACE_CLASSES from secop_ophyd.SECoPSignal import ( + AttributeType, LocalBackend, - PropertyBackend, - SECoPParamBackend, + SECoPBackend, SECoPXBackend, ) from secop_ophyd.util import Path @@ -78,10 +82,324 @@ UNKNOWN = 401 # not in SECoP standard (yet) +IGNORED_PROPS = ["meaning", "_plotly"] + + def clean_identifier(anystring): return str(re.sub(r"\W+|^(?=\d)", "_", anystring)) +def secop_enum_name_to_python(member_name: str) -> str: + """Convert SECoP enum member name to Python identifier. + + Examples: + 'Low Energy' -> 'LOW_ENERGY' + 'high-power' -> 'HIGH_POWER' + 'Mode 1' -> 'MODE_1' + + :param member_name: Original SECoP enum member name + :return: Python-compatible identifier in UPPER_CASE + """ + # Replace spaces and hyphens with underscores, remove other special chars + cleaned = re.sub(r"[\s-]+", "_", member_name) + cleaned = re.sub(r"[^a-zA-Z0-9_]", "", cleaned) + # Convert to uppercase + cleaned = cleaned.upper() + # Ensure it doesn't start with a digit + if cleaned and cleaned[0].isdigit(): + cleaned = "_" + cleaned + return cleaned + + +def format_assigned(device: StandardReadable, signal: SignalR) -> bool: + if ( + signal.describe in device._describe_funcs + or signal.describe in device._describe_config_funcs + ): + # Standard readable format already assigned + return True + + return False + + +def is_read_signal(device: StandardReadable, signal: SignalR | SignalRW) -> bool: + if signal.describe in device._describe_funcs: + return True + + return False + + +def is_config_signal(device: StandardReadable, signal: SignalR | SignalRW) -> bool: + if signal.describe in device._describe_config_funcs: + return True + + return False + + +class ParameterType: + """Annotation for Parameter Signals, defines the path to the parameter + in the secclient module dict""" + + def __repr__(self) -> str: + """Return repr suitable for code generation in annotations.""" + return "ParameterType()" + + def __call__(self, parent: Device, child: Device): + if not isinstance(child, Signal): + return + + backend = child._connector.backend + + if not isinstance(backend, SECoPBackend): + return + + backend.attribute_type = AttributeType.PARAMETER + backend._secclient = parent._client + + +class PropertyType: + """Annotation for Module Property Signals, defines the path to the property""" + + def __repr__(self) -> str: + """Return repr suitable for code generation in annotations.""" + return "PropertyType()" + + def __call__(self, parent: Device, child: Device): + if not isinstance(child, Signal): + return + + backend = child._connector.backend + + if not isinstance(backend, SECoPBackend): + return + + backend.attribute_type = AttributeType.PROPERTY + backend._secclient = parent._client + + +class SECoPDeviceConnector(DeviceConnector): + + sri: str + module: str | None + node_id: str + _auto_fill_signals: bool + + def __init__( + self, + sri: str, + auto_fill_signals: bool = True, + loglevel=logging.INFO, + logdir: str | None = None, + ) -> None: + + self.sri = sri + self.node_id = sri.split(":")[0] + ":" + sri.split(":")[1] + self._auto_fill_signals = auto_fill_signals + self.loglevel = loglevel + self.logdir = logdir + + if sri.count(":") == 2: + self.module = sri.split(":")[2] + elif sri.count(":") == 1: + self.module = None + else: + raise RuntimeError(f"Invalid SECoP resource identifier: {sri}") + + if SECoPDevice.clients.get(self.node_id) is None: + raise RuntimeError(f"No AsyncFrappyClient for URI {sri} exists") + + self.client: AsyncFrappyClient = SECoPDevice.clients[self.node_id] + + def set_module(self, module_name: str): + if self.sri.count(":") != 1: + raise RuntimeError( + "Module can only be set if SRI does not already contain module" + ) + self.module = module_name + self.sri = self.sri + ":" + module_name + + def create_children_from_annotations(self, device: Device): + if not hasattr(self, "filler"): + self.filler = DeviceFiller( + device=device, + signal_backend_factory=SECoPBackend, + device_connector_factory=lambda: SECoPDeviceConnector( + self.sri, self._auto_fill_signals, self.loglevel, self.logdir + ), + ) + + list(self.filler.create_signals_from_annotations()) + list(self.filler.create_devices_from_annotations(filled=False)) + + self.filler.check_created() + + def fill_backend_with_path(self, backend: SECoPBackend, annotations: list[Any]): + unhandled = [] + while annotations: + annotation = annotations.pop(0) + + if isinstance(annotation, StandardReadableFormat): + backend.format = annotation + + else: + unhandled.append(annotation) + + annotations.extend(unhandled) + # These leftover annotations will now be handled by the iterator + + async def connect_mock(self, device: Device, mock: LazyMock): + # Make 2 entries for each DeviceVector + self.filler.create_device_vector_entries_to_mock(2) + # Set the name of the device to name all children + device.set_name(device.name) + await super().connect_mock(device, mock) + + async def connect_real(self, device: Device, timeout: float, force_reconnect: bool): + if not self.sri: + raise RuntimeError(f"Could not connect to SEC node: {self.sri}") + + # Establish connection to SEC Node + await self.client.connect(3) + + # Module Device: fill Parameters & Pproperties + # (commands are done via annotated plans) + if self.module: + + # Fill Parmeters + parameter_dict = self.client.modules[self.module]["parameters"] + # remove ignored signals + parameters = [ + child + for child in parameter_dict.keys() + if child not in self.filler.ignored_signals + ] + + # Dertermine children that are declared but not yet filled + not_filled = {unfilled for unfilled, _ in device.children()} + + for param_name in parameters: + if self._auto_fill_signals or param_name in not_filled: + signal_type = ( + SignalR if parameter_dict[param_name]["readonly"] else SignalRW + ) + + backend = self.filler.fill_child_signal(param_name, signal_type) + + from secop_ophyd.GenNodeCode import get_type_param + + datatype = get_type_param(parameter_dict[param_name]["datatype"]) + backend.init_parameter_from_introspection( + datatype=datatype, + path=self.module + ":" + param_name, + secclient=self.client, + ) + + # Fill Properties + module_property_dict = self.client.modules[self.module]["properties"] + + # remove ignored signals + module_properties = [ + child + for child in module_property_dict.keys() + if child not in self.filler.ignored_signals + ] + + for mod_property_name in module_properties: + if self._auto_fill_signals or mod_property_name in not_filled: + + # properties are always read only + backend = self.filler.fill_child_signal(mod_property_name, SignalR) + + from secop_ophyd.GenNodeCode import get_type_prop + + datatype = get_type_prop(module_property_dict[mod_property_name]) + + backend.init_property_from_introspection( + datatype=datatype, + path=self.module + ":" + mod_property_name, + secclient=self.client, + ) + + # Node Device: fill child devices (modules) + else: + + # Fill Module devices + modules = self.client.modules + + not_filled = {unfilled for unfilled, _ in device.children()} + + for module_name in modules.keys(): + if self._auto_fill_signals or module_name in not_filled: + module_properties = modules[module_name]["properties"] + device_sub_class = class_from_interface(module_properties) + + self.filler.fill_child_device(module_name, device_sub_class) + + mod_dev: SECoPDevice = getattr(device, module_name) + mod_dev.set_module(module_name) + + # Fill Node properties + node_property_dict = self.client.properties + + # remove ignored signals + node_properties = [ + child + for child in node_property_dict.keys() + if child not in self.filler.ignored_signals + ] + + for node_property_name in node_properties: + if self._auto_fill_signals or node_property_name in not_filled: + + # properties are always read only + backend = self.filler.fill_child_signal(node_property_name, SignalR) + + from secop_ophyd.GenNodeCode import get_type_prop + + datatype = get_type_prop(node_property_dict[node_property_name]) + + backend.init_property_from_introspection( + datatype=datatype, + path=node_property_name, + secclient=self.client, + ) + + self.filler.check_filled(f"{self.node_id}") + + # Set the name of the device to name all children + device.set_name(device.name) + await super().connect_real(device, timeout, force_reconnect) + + # All Signals and child devs should be filled and connected now, in the next + # all signals and child devices need to be added to the according + # StandardReadableFormat with the hierarchiy: + # 1. Format given in Annotation + # --> these will already have been set by the DeviceFiller + # 2. Module Interface Class definition (value, target,...) + # --> these are set at the end of a .connect() method of the according + # SECoPDevice subclass skipping any signals that have already been + # set by annotations (should emit warning if there is a conflict + # config vs read sig) + # 3. & 4. Definition in Parameter property "_signal_format" + Defaults + # - _signal_format property + default CONFIG_SIGNAL for all other Signals + # --> these are set here at the end of SECoPDeviceConnector.connect_real() + # for all Signals that have not yet been set to a format + # - CHILD format for all child devices (SECoPDevice instances) + # --> these are set at the end of SECoPNodeDevice.connect() method + # the device tree has only a depth of 2 levels (Node -> Modules) + # + + # device has to be standard readable for this to make sense + if not isinstance(device, SECoPDevice): + return + + # 2. Module Interface Class definition (value, target,...) + await device._assign_interface_formats() + + # 3. & 4. Definition in Parameter property "_signal_format" + Defaults + await device._assign_default_formats() + + class SECoPCMDDevice(StandardReadable, Flyable, Triggerable): """ Command devices that have Signals for command args, return values and a signal @@ -212,200 +530,119 @@ def collect(self) -> Iterator[PartialEvent]: ) -class SECoPBaseDevice(StandardReadable): - """Base Class for generating Opyd devices from SEC Node modules, - objects of type SECoPBaseDevice are not supposed to be instanciated +class SECoPDevice(StandardReadable): - """ + clients: Dict[str, AsyncFrappyClient] = {} + + node_id: str + sri: str + host: str + port: str + module: str | None + mod_prop_devices: Dict[str, SignalR] + param_devices: Dict[str, Any] + logger: Logger + + hinted_signals: list[str] = [] def __init__( self, - secclient: AsyncFrappyClient, - module_name: str, + sri: str = "", # SECoP resource identifier host:port:optional[module] + name: str = "", + connector: SECoPDeviceConnector | None = None, loglevel=logging.INFO, logdir: str | None = None, ) -> None: - """Initiate A SECoPBaseDevice - - :param secclient: SECoP client providing communication to the SEC Node - :type secclient: AsyncFrappyClient - """ - # config_params is default - self._hinted_params: list[str] = ["value", "target"] - self._uncached_params: list[str] = [] - self._hinted_uncached_params: list[str] = [] - - self._secclient: AsyncFrappyClient = secclient - self.impl: str | None = None - - self._module = module_name - module_desc = secclient.modules[module_name] - self.plans: list[Method] = [] - self.mod_prop_devices: Dict[str, SignalR] = {} - self.param_devices: Dict[str, Any] = {} - - name = self._secclient.properties[EQUIPMENT_ID].replace(".", "-") - - for parameter, properties in module_desc["parameters"].items(): - match properties.get("_signal_format", None): - case "HINTED_SIGNAL": - if parameter not in self._hinted_params: - self._hinted_params.append(parameter) - - case "HINTED_UNCACHED_SIGNAL": - if parameter not in self._hinted_uncached_params: - self._hinted_uncached_params.append(parameter) - case "UNCACHED_SIGNAL": - if parameter not in self._uncached_params: - self._uncached_params.append(parameter) - case _: - continue - - self.logger: Logger = setup_logging( - name=f"secop-ophyd:{name}:{module_name}", level=loglevel, log_dir=logdir - ) + if connector and sri: + raise RuntimeError("Provide either sri or connector, not both") - self.logger.info(f"Initializing SECoPBaseDevice for module {module_name}") - # Add configuration Signals - with self.add_children_as_readables( - format=StandardReadableFormat.CONFIG_SIGNAL - ): - # generate Signals from Module Properties - for property in module_desc["properties"]: + if connector: + sri = connector.sri + loglevel = connector.loglevel + logdir = connector.logdir - if property == "implementation": - self.impl = module_desc["properties"]["implementation"] + self.sri = sri + self.host = sri.split(":")[0] + self.port = sri.split(":")[1] + self.mod_prop_devices = {} + self.param_devices = {} + self.node_id = sri.split(":")[0] + ":" + sri.split(":")[1] - if property in ["meaning", "_plotly"]: - continue - - propb = PropertyBackend(property, module_desc["properties"], secclient) - - setattr(self, property, SignalR(backend=propb)) - self.mod_prop_devices[property] = getattr(self, property) - - # generate Signals from Module parameters eiter r or rw - for parameter, properties in module_desc["parameters"].items(): - if ( - parameter - in self._hinted_params - + self._uncached_params - + self._hinted_uncached_params - ): - continue - # generate new root path - param_path = Path(parameter_name=parameter, module_name=module_name) - - # readonly propertyns to plans and plan stubs. - readonly: bool = properties.get("readonly", None) - - # Normal types + (struct and tuple as JSON object Strings) - self._signal_from_parameter( - path=param_path, - sig_name=parameter, - readonly=readonly, - ) - self.param_devices[parameter] = getattr(self, parameter) - - self.add_signals_by_format( - format=StandardReadableFormat.HINTED_SIGNAL, - format_params=self._hinted_params, - module_name=module_name, - module_desc=module_desc, - ) - - self.add_signals_by_format( - format=StandardReadableFormat.UNCACHED_SIGNAL, - format_params=self._uncached_params, - module_name=module_name, - module_desc=module_desc, + self.logger = setup_logging( + name=f"frappy:{self.host}:{self.port}", + level=loglevel, + log_dir=logdir, ) - self.add_signals_by_format( - format=StandardReadableFormat.HINTED_UNCACHED_SIGNAL, - format_params=self._hinted_uncached_params, - module_name=module_name, - module_desc=module_desc, - ) + self.module = None + if len(sri.split(":")) > 2: + self.module = sri.split(":")[2] - # Initialize Command Devices - for command, properties in module_desc["commands"].items(): - # generate new root path - cmd_path = Path(parameter_name=command, module_name=module_name) - cmd_dev_name = command + "_CMD" - setattr( - self, - cmd_dev_name, - SECoPCMDDevice(path=cmd_path, secclient=secclient), + if SECoPDevice.clients.get(self.node_id) is None: + SECoPDevice.clients[self.node_id] = AsyncFrappyClient( + host=self.host, port=self.port, log=self.logger ) - cmd_dev: SECoPCMDDevice = getattr(self, cmd_dev_name) - # Add Bluesky Plan Methods + connector = connector or SECoPDeviceConnector(sri=sri) - # Stop is already an ophyd native operation - if command == "stop": - continue + self._client: AsyncFrappyClient = SECoPDevice.clients[self.node_id] - cmd_plan = self.generate_cmd_plan( - cmd_dev, cmd_dev.arg_dtype, cmd_dev.res_dtype - ) + super().__init__(name=name, connector=connector) - setattr(self, command, MethodType(cmd_plan, self)) + def set_module(self, module_name: str): + if self.module is not None: + raise RuntimeError("Module can only be set if it was not already set") - description: str = "" - description += f"{cmd_dev.description}\n" - description += f" argument: {str(cmd_dev.arg_dtype)}\n" - description += f" result: {str(cmd_dev.res_dtype)}" + self.module = module_name + self.sri = self.sri + ":" + module_name - plan = Method( - cmd_name=command, - description=description, - cmd_sign=inspect.signature(getattr(self, command)), - ) + self._connector.set_module(module_name) - self.plans.append(plan) + async def connect( + self, + mock: bool | LazyMock = False, + timeout: float = DEFAULT_TIMEOUT, + force_reconnect: bool = False, + ): + if not self._client.online or force_reconnect: + # Establish connection to SEC Node + await self._client.connect(3) - self.set_name(module_name) + if self.module: + module_desc = self._client.modules[self.module] - # Add status Signal AFTER set_name() to avoid auto-registration as config/hinted - # This is only needed as long as Tiled can't handle structured numpy arrays - if "status" in module_desc["parameters"].keys(): - properties = module_desc["parameters"]["status"] - param_path = Path(parameter_name="status", module_name=module_name) - readonly = properties.get("readonly", None) + # Initialize Command Devices + for command, _ in module_desc["commands"].items(): + # generate new root path + cmd_path = Path(parameter_name=command, module_name=self.module) + cmd_dev_name = command + "_CMD" + setattr( + self, + cmd_dev_name, + SECoPCMDDevice(path=cmd_path, secclient=self._client), + ) - # Create signal without adding to readables - self._signal_from_parameter( - path=param_path, - sig_name="status", - readonly=readonly, - ) - self.param_devices["status"] = getattr(self, "status") + cmd_dev: SECoPCMDDevice = getattr(self, cmd_dev_name) + # Add Bluesky Plan Methods - def add_signals_by_format( - self, format, format_params: list, module_name, module_desc: dict - ): - # Add hinted readable Signals - with self.add_children_as_readables(format=format): - for parameter in format_params: - if parameter not in module_desc["parameters"].keys(): + # Stop is already an ophyd native operation + if command == "stop": continue - properties = module_desc["parameters"][parameter] - # generate new root path - param_path = Path(parameter_name=parameter, module_name=module_name) + cmd_plan = self.generate_cmd_plan( + cmd_dev, cmd_dev.arg_dtype, cmd_dev.res_dtype + ) - # readonly propertyns to plans and plan stubs. - readonly = properties.get("readonly", None) + setattr(self, command, MethodType(cmd_plan, self)) - # Normal types + (struct and tuple as JSON object Strings) - self._signal_from_parameter( - path=param_path, - sig_name=parameter, - readonly=readonly, - ) - self.param_devices[parameter] = getattr(self, parameter) + await super().connect(mock, timeout, force_reconnect) + + if self.module is None: + # set device name from equipment id property + self.set_name(self._client.properties[EQUIPMENT_ID].replace(".", "-")) + else: + self.set_name(self.module) def generate_cmd_plan( self, @@ -482,61 +719,173 @@ def wait_for_idle_factory(): return cmd_meth - def _signal_from_parameter(self, path: Path, sig_name: str, readonly: bool): - """Generates an Ophyd Signal from a Module Parameter + @abstractmethod + async def _assign_interface_formats(self): + """Assign signal formats specific to this device's interface class. + Subclasses override this to assign formats before default fallback.""" - :param path: Path to the Parameter in the secclient module dict - :type path: Path - :param sig_name: Name of the new Signal - :type sig_name: str - :param readonly: Signal is R or RW - :type readonly: bool - """ - # Normal types + (struct and tuple as JSON object Strings) - paramb = SECoPParamBackend(path=path, secclient=self._secclient) + async def _assign_default_formats(self): + config_signals = [] + hinted_signals = [] + uncached_signals = [] + hinted_uncached_signals = [] - # construct signal - if readonly: - setattr(self, sig_name, SignalR(paramb)) - else: - setattr(self, sig_name, SignalRW(paramb)) + def assert_device_is_signalr(device: Device) -> SignalR: + if not isinstance(device, SignalR): + raise TypeError(f"{device} is not a SignalR") + return device + + for _, child in self.children(): + + if not isinstance(child, Signal): + continue + + backend = child._connector.backend + + if not isinstance(backend, SECoPBackend): + continue + + param_name = backend.path_str.split(":")[-1] + if param_name == "status": + # status signals should not be assigned a format, + # but a SignalR children (this can be removed once tiled can + # hanlde composite dtypes) + continue + + # child is a Signal with SECoPParamBackend + + # check if signal already has a format assigned + signalr_device = assert_device_is_signalr(child) + + if format_assigned(self, signalr_device): + # format already assigned by annotation or module IF class + continue + + match backend.format: + case StandardReadableFormat.CHILD: + raise RuntimeError("Signal cannot have CHILD format") + case StandardReadableFormat.CONFIG_SIGNAL: + config_signals.append(signalr_device) + case StandardReadableFormat.HINTED_SIGNAL: + hinted_signals.append(signalr_device) + case StandardReadableFormat.UNCACHED_SIGNAL: + uncached_signals.append(signalr_device) + case StandardReadableFormat.HINTED_UNCACHED_SIGNAL: + hinted_uncached_signals.append(signalr_device) + + # add signals to device in the order of their priority + self.add_readables(config_signals, StandardReadableFormat.CONFIG_SIGNAL) + self.add_readables(hinted_signals, StandardReadableFormat.HINTED_SIGNAL) + + self.add_readables(uncached_signals, StandardReadableFormat.UNCACHED_SIGNAL) + + self.add_readables( + hinted_uncached_signals, StandardReadableFormat.HINTED_UNCACHED_SIGNAL + ) -class SECoPCommunicatorDevice(SECoPBaseDevice): + +class SECoPNodeDevice(SECoPDevice): + + hinted_signals: list[str] = [] def __init__( self, - secclient: AsyncFrappyClient, - module_name: str, + sec_node_uri: str = "", # SECoP resource identifier host:port:optional[module] + name: str = "", loglevel=logging.INFO, logdir: str | None = None, ): - """Initializes the SECoPCommunicatorDevice + # ensure sec_node_uri only contains host:port + if sec_node_uri.count(":") != 1: + raise RuntimeError( + f"SECoPNodeDevice SRI must only contain host:port {sec_node_uri}" + ) - :param secclient: SECoP client providing communication to the SEC Node - :type secclient: AsyncFrappyClient - :param module_name: Name of the SEC Node module that is represented by - this device - :type module_name: str""" + super().__init__(sri=sec_node_uri, name=name, loglevel=loglevel, logdir=logdir) + + async def connect(self, mock=False, timeout=DEFAULT_TIMEOUT, force_reconnect=False): + await super().connect(mock, timeout, force_reconnect) + + moddevs = [] + for _, moddev in self.children(): + if isinstance(moddev, SECoPDevice): + moddevs.append(moddev) + + self.add_readables(moddevs, StandardReadableFormat.CHILD) + + # register secclient callbacks (these are useful if sec node description + # changes after a reconnect) + self._client.register_callback( + None, self.descriptiveDataChange, self.nodeStateChange + ) + + def descriptiveDataChange(self, module, description): # noqa: N802 + raise RuntimeError( + "The descriptive data has changed upon reconnect. Descriptive data changes" + "are not supported: reinstantiate device" + ) + + def nodeStateChange(self, online, state): # noqa: N802 + """called when the state of the connection changes + + 'online' is True when connected or reconnecting, False when disconnected + or connecting 'state' is the connection state as a string + """ + if state == "connected" and online is True: + self._client.conn_timestamp = ttime.time() + + async def _assign_interface_formats(self): + # Node device has no specific interface class formats + pass + + def class_from_instance(self, path_to_module: str | None = None): + from secop_ophyd.GenNodeCode import GenNodeCode + + description = self._client.client.request("describe")[2] + + # parse genClass file if already present + genCode = GenNodeCode(path=path_to_module, log=self.logger) + + genCode.from_json_describe(description) + + genCode.write_gen_node_class_file() + +class SECoPCommunicatorDevice(SECoPDevice): + + hinted_signals: list[str] = [] + + def __init__( + self, + sri: str = "", # SECoP resource identifier host:port:optional[module] + name: str = "", + connector: SECoPDeviceConnector | None = None, + loglevel=logging.INFO, + logdir: str | None = None, + ) -> None: super().__init__( - secclient=secclient, - module_name=module_name, - loglevel=loglevel, - logdir=logdir, + sri=sri, name=name, connector=connector, loglevel=loglevel, logdir=logdir ) + async def _assign_interface_formats(self): + # Communicator has no specific interface class formats + pass + -class SECoPReadableDevice(SECoPCommunicatorDevice, Triggerable, Subscribable): +class SECoPReadableDevice(SECoPDevice, Triggerable, Subscribable): """ Standard readable SECoP device, corresponding to a SECoP module with the interface class "Readable" """ + hinted_signals: list[str] = ["value"] + def __init__( self, - secclient: AsyncFrappyClient, - module_name: str, + sri: str = "", # SECoP resource identifier host:port:optional[module] + name: str = "", + connector: SECoPDeviceConnector | None = None, loglevel=logging.INFO, logdir: str | None = None, ): @@ -553,12 +902,12 @@ def __init__( self.status: SignalR super().__init__( - secclient=secclient, - module_name=module_name, - loglevel=loglevel, - logdir=logdir, + sri=sri, name=name, connector=connector, loglevel=loglevel, logdir=logdir ) + async def connect(self, mock=False, timeout=DEFAULT_TIMEOUT, force_reconnect=False): + await super().connect(mock, timeout, force_reconnect) + if not hasattr(self, "value"): raise AttributeError( "Attribute 'value' has not been assigned," @@ -571,6 +920,19 @@ def __init__( + "but is needed for Readable interface class" ) + async def _assign_interface_formats(self): + + if format_assigned(self, self.value): + if not is_read_signal(self, self.value): + warnings.warn( + f"Signal 'value' of device {self.name} has format assigned " + + "that is not compatible with Readable interface class" + ) + else: + self.add_readables([self.value], StandardReadableFormat.HINTED_SIGNAL) + + # TODO ensure status signal must be neither config nor read format + async def wait_for_idle(self): """asynchronously waits until module is IDLE again. this is helpful, for running commands that are not done immediately @@ -597,7 +959,7 @@ async def wait_for_idle(self): break if hasattr(self, "_stopped"): - self.logger.info(f"Module {self.name} was stopped STOPPED") + # self.logger.info(f"Module {self.name} was stopped STOPPED") if self._stopped is True: break @@ -629,9 +991,7 @@ def trigger(self) -> AsyncStatus: self.logger.info(f"Triggering {self.name}: read fresh data from device") # get fresh reading of the value Parameter from the SEC Node return AsyncStatus( - awaitable=self._secclient.get_parameter( - self._module, "value", trycache=False - ) + awaitable=self._client.get_parameter(self.module, "value", trycache=False) ) def subscribe(self, function: Callback[dict[str, Reading]]) -> None: @@ -649,11 +1009,14 @@ class SECoPTriggerableDevice(SECoPReadableDevice, Stoppable): interface class "Triggerable" """ + hinted_signals: list[str] = ["value"] + def __init__( self, - secclient: AsyncFrappyClient, - module_name: str, - loglevel=logging.info, + sri: str = "", # SECoP resource identifier host:port:optional[module] + name: str = "", + connector: SECoPDeviceConnector | None = None, + loglevel=logging.INFO, logdir: str | None = None, ): """Initialize SECoPTriggerableDevice @@ -670,70 +1033,30 @@ def __init__( self._success = True self._stopped = False - super().__init__(secclient, module_name, loglevel=loglevel, logdir=logdir) - - async def __go_coro(self, wait_for_idle: bool): - await self._secclient.exec_command(module=self._module, command="go") - - self._success = True - self._stopped = False - await asyncio.sleep(0.2) - - if wait_for_idle: - await self.wait_for_idle() - - def wait_for_prepared(self): - yield from self.observe_status_change(IDLE) - yield from self.observe_status_change(PREPARING) - - def trigger(self) -> AsyncStatus: - - self.logger.info(f"Triggering {self.name} go command") - - async def go_or_read_on_busy(): - module_status = await self.status.get_value(False) - stat_code = module_status["f0"] - - if BUSY <= stat_code <= ERROR: - return - - await self.__go_coro(True) - - return AsyncStatus(awaitable=go_or_read_on_busy()) - - async def stop(self, success=True): - """Calls stop command on the SEC Node module - - :param success: - True: device is stopped as planned - False: something has gone wrong - (defaults to True) - :type success: bool, optional - """ - self._success = success - - self.logger.info(f"Stopping {self.name} success={success}") - - await self._secclient.exec_command(self._module, "stop") - self._stopped = True + super().__init__( + sri=sri, name=name, connector=connector, loglevel=loglevel, logdir=logdir + ) class SECoPWritableDevice(SECoPReadableDevice): - """Fast settable device target""" + hinted_signals: list[str] = ["target", "value"] pass -class SECoPMoveableDevice(SECoPWritableDevice, Locatable, Stoppable): +class SECoPMoveableDevice(SECoPReadableDevice, Locatable, Stoppable): """ Standard movable SECoP device, corresponding to a SECoP module with the interface class "Drivable" """ + hinted_signals: list[str] = ["target", "value"] + def __init__( self, - secclient: AsyncFrappyClient, - module_name: str, + sri: str = "", # SECoP resource identifier host:port:optional[module] + name: str = "", + connector: SECoPDeviceConnector | None = None, loglevel=logging.INFO, logdir: str | None = None, ): @@ -748,7 +1071,16 @@ def __init__( self.target: SignalRW - super().__init__(secclient, module_name, loglevel=loglevel, logdir=logdir) + super().__init__( + sri=sri, name=name, connector=connector, loglevel=loglevel, logdir=logdir + ) + + self._success = True + self._stopped = False + + async def connect(self, mock=False, timeout=DEFAULT_TIMEOUT, force_reconnect=False): + + await super().connect(mock, timeout, force_reconnect) if not hasattr(self, "target"): raise AttributeError( @@ -756,9 +1088,6 @@ def __init__( + "but is needed for 'Drivable' interface class!" ) - self._success = True - self._stopped = False - def set(self, new_target, timeout: Optional[float] = None) -> AsyncStatus: """Sends new target to SEC Nonde and waits until module is IDLE again @@ -787,6 +1116,9 @@ async def _move(self, new_target): stat_code = current_stat["f0"] if self._stopped is True: + self.logger.info( + f"Move of {self.name} to {new_target} was stopped STOPPED" + ) break # Error State or DISABLED @@ -803,7 +1135,7 @@ async def _move(self, new_target): # TODO other status transitions if not self._success: - raise RuntimeError("Module was stopped") + self.logger.error(f"Move of {self.name} to {new_target} was not successful") async def stop(self, success=True): """Calls stop command on the SEC Node module @@ -818,14 +1150,14 @@ async def stop(self, success=True): if not success: self.logger.info(f"Stopping {self.name} success={success}") - await self._secclient.exec_command(self._module, "stop") + await self._client.exec_command(self.module, "stop") self._stopped = True async def locate(self) -> Location: # return current location of the device (setpoint and readback). # Only locally cached values are returned - setpoint = await self._secclient.get_parameter(self._module, "target", True) - readback = await self._secclient.get_parameter(self._module, "value", True) + setpoint = await self._client.get_parameter(self.module, "target", True) + readback = await self._client.get_parameter(self.module, "value", True) location: Location = { "setpoint": setpoint.value, @@ -833,277 +1165,35 @@ async def locate(self) -> Location: } return location + async def _assign_interface_formats(self): + await super()._assign_interface_formats() -class SECoPNodeDevice(StandardReadable): - """ - Generates the root ophyd device from a Sec-node. Signals of this Device correspond - to the Sec-node properties - """ - - name: str = "" - - def __init__( - self, - sec_node_uri: str, - # `prefix` not used, it's just that the device connecter requires it for - # some reason. - prefix: str = "", - name: str = "", - loglevel: str = "INFO", - logdir: str | None = None, - ): - """Initializes the node device and generates all node signals and subdevices - corresponding to the SECoP-modules of the secnode - - :param secclient: SECoP client providing communication to the SEC Node - :type secclient: AsyncFrappyClient - """ - - self.host, self.port = sec_node_uri.rsplit(":", maxsplit=1) - - self.logger: Logger = setup_logging( - name=f"frappy:{self.host}:{self.port}", - level=LOG_LEVELS[loglevel], - log_dir=logdir, - ) - self.logdir = logdir - - self.name = name - self.prefix = prefix - - async def connect( - self, - mock: bool | LazyMock = False, - timeout: float = DEFAULT_TIMEOUT, - force_reconnect: bool = False, - ): - if not hasattr(self, "_secclient"): - secclient: AsyncFrappyClient - - secclient = await AsyncFrappyClient.create( - host=self.host, - port=self.port, - loop=asyncio.get_running_loop(), - log=self.logger, - ) - - self.equipment_id: SignalR - self.description: SignalR - self.version: SignalR - - self._secclient: AsyncFrappyClient = secclient - - self._module_name: str = "" - self._node_cls_name: str = "" - self.mod_devices: Dict[str, SECoPReadableDevice] = {} - self.node_prop_devices: Dict[str, SignalR] = {} - - self.genCode: GenNodeCode - - if self.name == "": - self.name = self._secclient.properties[EQUIPMENT_ID].replace(".", "-") - - self.name = self.prefix + self.name - - config = [] - - self.logger.info( - "Initializing SECoPNodeDevice " - + f"({self._secclient.host}:{self._secclient.port})" - ) - - with self.add_children_as_readables( - format=StandardReadableFormat.CONFIG_SIGNAL - ): - for property in self._secclient.properties: - propb = PropertyBackend( - property, self._secclient.properties, secclient - ) - setattr(self, property, SignalR(backend=propb)) - config.append(getattr(self, property)) - self.node_prop_devices[property] = getattr(self, property) - - with self.add_children_as_readables(format=StandardReadableFormat.CHILD): - for module, module_desc in self._secclient.modules.items(): - - secop_dev_class = self.class_from_interface( - module_desc["properties"] - ) - - if secop_dev_class is not None: - setattr( - self, - module, - secop_dev_class( - self._secclient, - module, - loglevel=self.logger.level, - logdir=self.logdir, - ), - ) - self.mod_devices[module] = getattr(self, module) - - # register secclient callbacks (these are useful if sec node description - # changes after a reconnect) - secclient.client.register_callback( - None, self.descriptiveDataChange, self.nodeStateChange - ) - - super().__init__(name=self.name) - - elif force_reconnect or self._secclient.client.online is False: - await self._secclient.disconnect(True) - await self._secclient.connect(try_period=DEFAULT_TIMEOUT) - - def class_from_instance(self, path_to_module: str | None = None): - """Dynamically generate python class file for the SECoP_Node_Device, this - allows autocompletion in IDEs and eases working with the generated Ophyd - devices - """ - - # parse genClass file if already present - self.genCode = GenNodeCode(path=path_to_module, log=self._secclient.log) - - self.genCode.add_import(self.__module__, self.__class__.__name__) - - node_dict = self.__dict__ - - # NodeClass Name - self._node_cls_name = self.name.replace("-", "_").capitalize() - - node_bases = [self.__class__.__name__, "ABC"] - - node_class_attrs = [] - - for attr_name, attr_value in node_dict.items(): - # Modules - if isinstance( - attr_value, - ( - SECoPBaseDevice, - SECoPCommunicatorDevice, - SECoPReadableDevice, - SECoPWritableDevice, - SECoPMoveableDevice, - SECoPTriggerableDevice, - ), - ): - attr_type = type(attr_value) - module = str(getattr(attr_type, "__module__", None)) - - # add imports for module attributes - self.genCode.add_import(module, attr_type.__name__) - - module_dict = attr_value.__dict__ - - # modclass is baseclass of derived class - mod_bases = [attr_value.__class__.__name__, "ABC"] - - module_class_attrs = [] - - # Name for derived class - module_class_name = attr_name - if attr_value.impl is not None: - module_class_name = attr_value.impl.split(".").pop() - - # Module:Acessibles - for module_attr_name, module_attr_value in module_dict.items(): - if isinstance( - module_attr_value, - (SignalR, SignalX, SignalRW, SignalR, SECoPCMDDevice), - ): - # add imports for module attributes - self.genCode.add_import( - module_attr_value.__module__, - type(module_attr_value).__name__, - ) - - module_class_attrs.append( - (module_attr_name, type(module_attr_value).__name__) - ) - self.genCode.add_mod_class( - module_class_name, mod_bases, module_class_attrs, attr_value.plans - ) - - node_class_attrs.append((attr_name, module_class_name)) - - # Poperty Signals - if isinstance(attr_value, (SignalR)): - self.genCode.add_import( - attr_value.__module__, type(attr_value).__name__ - ) - node_class_attrs.append((attr_name, attr_value.__class__.__name__)) - - self.genCode.add_node_class(self._node_cls_name, node_bases, node_class_attrs) - - self.genCode.write_gen_node_class_file() - - def descriptiveDataChange(self, module, description): # noqa: N802 - """called when the description has changed - - this callback is called on the node with module=None - and on every changed module with module== - - :param module: module name of the module that has changes - :type module: _type_ - :param description: new Node description string - :type description: _type_ - """ - # TODO this functionality is untested and will probably break the generated - # ophyd device since a changed module description would lead a newly - # instanciated module object while references to the old one are broken - # mitigation: alway call methods via: - # - # 'node_obj.module_obj.method()' - - self._secclient.conn_timestamp = ttime.time() - - if module is None: - # Refresh signals that correspond to Node Properties - config = [] - for property in self._secclient.properties: - propb = PropertyBackend( - property, self._secclient.properties, self._secclient + if format_assigned(self, self.target): + if not is_read_signal(self, self.target): + warnings.warn( + f"Signal 'target' of device {self.name} has format assigned " + + "that is not compatible with Movable interface class" ) - - setattr(self, property, SignalR(backend=propb)) - config.append(getattr(self, property)) - - self.add_readables(config, format=StandardReadableFormat.CONFIG_SIGNAL) else: - # Refresh changed modules - module_desc = self._secclient.modules[module] - secop_dev_class = self.class_from_interface(module_desc["properties"]) + self.add_readables([self.target], StandardReadableFormat.HINTED_SIGNAL) - setattr(self, module, secop_dev_class(self._secclient, module)) - # TODO what about removing Modules during disconn +def class_from_interface(mod_properties: dict): + ophyd_class = None - def nodeStateChange(self, online, state): # noqa: N802 - """called when the state of the connection changes + # infer highest level IF class + module_interface_classes: dict = mod_properties[INTERFACE_CLASSES] + for interface_class in IF_CLASSES.keys(): + if interface_class in module_interface_classes: + ophyd_class = IF_CLASSES[interface_class] + break - 'online' is True when connected or reconnecting, False when disconnected - or connecting 'state' is the connection state as a string - """ - if state == "connected" and online is True: - self._secclient.conn_timestamp = ttime.time() - - def class_from_interface(self, mod_properties: dict): - ophyd_class = None - - # infer highest level IF class - module_interface_classes: dict = mod_properties[INTERFACE_CLASSES] - for interface_class in IF_CLASSES.keys(): - if interface_class in module_interface_classes: - ophyd_class = IF_CLASSES[interface_class] - break - - # No predefined IF class was a match --> use base class (loose collection of - # accessibles) - if ophyd_class is None: - ophyd_class = SECoPBaseDevice # type: ignore + # No predefined IF class was a match --> use base class (loose collection of + # accessibles) + if ophyd_class is None: + ophyd_class = SECoPDevice # type: ignore - return ophyd_class + return ophyd_class IF_CLASSES = { @@ -1113,18 +1203,3 @@ def class_from_interface(self, mod_properties: dict): "Readable": SECoPReadableDevice, "Communicator": SECoPCommunicatorDevice, } - -SECOP_TO_NEXUS_TYPE = { - "double": "NX_FLOAT64", - "int": "NX_INT64", - "scaled": "NX_FLOAT64", -} - - -ALL_IF_CLASSES = set(IF_CLASSES.values()) - -# TODO -# FEATURES = { -# 'HasLimits': SecopHasLimits, -# 'HasOffset': SecopHasOffset, -# } diff --git a/src/secop_ophyd/SECoPSignal.py b/src/secop_ophyd/SECoPSignal.py index de28639..3b68096 100644 --- a/src/secop_ophyd/SECoPSignal.py +++ b/src/secop_ophyd/SECoPSignal.py @@ -1,7 +1,7 @@ import asyncio import warnings from functools import wraps -from typing import Any, Callable, Dict +from typing import Any, Callable from bluesky.protocols import DataKey, Reading from frappy.client import CacheItem @@ -17,7 +17,13 @@ StructOf, TupleOf, ) -from ophyd_async.core import Callback, SignalBackend, SignalDatatypeT +from ophyd_async.core import ( + Callback, + SignalBackend, + SignalDatatypeT, + StandardReadableFormat, + StrictEnum, +) from secop_ophyd.AsyncFrappyClient import AsyncFrappyClient from secop_ophyd.util import Path, SECoPDataKey, SECoPdtype, SECoPReading, deep_get @@ -37,6 +43,11 @@ MAX_DEPTH = 1 +class AttributeType(StrictEnum): + PARAMETER = "parameter" + PROPERTY = "property" + + class LocalBackend(SignalBackend): """Class for the 'argument' and 'result' Signal backends of a SECoP_CMD_Device. These Signals act as a local cache for storing the command argument and result. @@ -120,7 +131,6 @@ def set_callback(self, callback: Callback[Reading[SignalDatatypeT]] | None) -> N self.callback = callback # type: ignore[assignment] -# TODO add return of Asyncstatus class SECoPXBackend(SignalBackend): """ Signal backend for SignalX of a SECoP_CMD_Device, that handles command execution @@ -216,69 +226,180 @@ async def get_setpoint(self) -> SignalDatatypeT: ) -class SECoPParamBackend(SignalBackend): - """Standard backend for a Signal that represents SECoP Parameter""" +class SECoPBackend(SignalBackend[SignalDatatypeT]): + """Unified backend for SECoP Parameters and Properties. - def __init__(self, path: Path, secclient: AsyncFrappyClient) -> None: - """_summary_ - :param path: Path to the parameter in the secclient module dict - :type path: Path - :param secclient: SECoP client providing communication to the SEC Node - :type secclient: AsyncFrappyClient + This allows a single backend type to be used in signal_backend_factory, + with deferred initialization based on annotation metadata. + """ + + format: StandardReadableFormat + attribute_type: str | None + _module_name: str | None + _attribute_name: str | None # parameter or property name + _secclient: AsyncFrappyClient + path_str: str + SECoPdtype_obj: DataType + SECoP_type_info: SECoPdtype + describe_dict: dict + + def __init__( + self, + datatype: type[SignalDatatypeT] | None, + path: str | None = None, + attribute_type: str | None = None, + secclient: AsyncFrappyClient | None = None, + ): + """Initialize backend (supports deferred initialization). + + Args: + datatype: Optional datatype for the signal + path: Optional path for immediate initialization (module:param or prop_key) + secclient: Optional SECoP client for immediate initialization """ + self._module_name = None + self._attribute_name = None - # secclient - self._secclient: AsyncFrappyClient = secclient + self.attribute_type = attribute_type - # module:acessible Path for reading/writing (module,accessible) - self.path: Path = path + if secclient: + self._secclient = secclient - self._param_description: dict = self._get_param_desc() + self.path_str = path or "" - # Root datainfo or memberinfo for nested datatypes - self.datainfo: dict = deep_get( - self._param_description["datainfo"], self.path.get_memberinfo_path() - ) + if path and secclient: - self.readonly = self._param_description.get("readonly") + if path.count(":") == 0: + self._module_name = None + self._attribute_name = path + else: + self._module_name, self._attribute_name = path.split(":", maxsplit=1) + + super().__init__(datatype) + + def init_parameter_from_introspection( + self, + datatype: type[SignalDatatypeT], + path: str, + secclient: AsyncFrappyClient, + ): + if self.attribute_type is not None: + + if secclient != self._secclient: + raise RuntimeError( + "Backend already initialized with a different SECoP client, cannot " + "re-initialize" + ) + + if self.attribute_type != AttributeType.PARAMETER: + raise RuntimeError( + f"Backend already initialized as {self.attribute_type}, " + f"cannot re-initialize as PARAMETER" + ) + + self.attribute_type = AttributeType.PARAMETER + + module_name, parameter_name = path.split(":", maxsplit=1) + + self._module_name = module_name + self._attribute_name = parameter_name + self._secclient = secclient + + self.datatype = datatype + + self.path_str = path + + def init_property_from_introspection( + self, datatype: type[SignalDatatypeT], path: str, secclient: AsyncFrappyClient + ): + if self.attribute_type is not None: + + if secclient != self._secclient: + raise RuntimeError( + "Backend already initialized with a different SECoP client, cannot " + "re-initialize" + ) + + if self.attribute_type != AttributeType.PROPERTY: + raise RuntimeError( + f"Backend already initialized as {self.attribute_type}, cannot " + f"re-initialize as PROPERTY" + ) + + self.attribute_type = AttributeType.PROPERTY + if path.count(":") == 0: + module_name = None + property_name = path + else: + module_name, property_name = path.split(":", maxsplit=1) + + self._module_name = module_name + self._attribute_name = property_name - self.SECoPdtype_str: str - self.SECoPdtype_obj: DataType = self._param_description["datatype"] + self._secclient = secclient + self.datatype = datatype + + self.path_str = path + + def source(self, name: str, read: bool) -> str: + return self._secclient.host + ":" + self._secclient.port + ":" + self.path_str + + async def connect(self, timeout: float): + """Connect and initialize backend (handles both parameters and properties).""" + await self._secclient.connect() + + match self.attribute_type: + case AttributeType.PROPERTY: + await self._init_property() + case AttributeType.PARAMETER: + await self._init_parameter() + + async def _init_parameter(self): + """Initialize as a parameter signal.""" + self._param_description: dict = self._get_param_desc() - self.SECoP_type_info: SECoPdtype = SECoPdtype(self.SECoPdtype_obj) + if not hasattr(self, "format"): + match self._param_description.get("_signal_format", None): + case "HINTED_SIGNAL": + self.format = StandardReadableFormat.HINTED_SIGNAL + case "HINTED_UNCACHED_SIGNAL": + self.format = StandardReadableFormat.HINTED_UNCACHED_SIGNAL + case "UNCACHED_SIGNAL": + self.format = StandardReadableFormat.UNCACHED_SIGNAL + case _: + self.format = StandardReadableFormat.CONFIG_SIGNAL + + # Root datainfo or memberinfo for nested datatypes + self.datainfo: dict = self._param_description["datainfo"] + self.readonly = self._param_description.get("readonly") + self.SECoPdtype_obj = self._param_description["datatype"] + self.SECoP_type_info = SECoPdtype(self.SECoPdtype_obj) if self.SECoP_type_info.max_depth > MAX_DEPTH: warnings.warn( - f"The datatype of parameter '{path._accessible_name}' has a maximum " + f"The datatype of parameter '{self._attribute_name}' has a maximum " f"depth of {self.SECoP_type_info.max_depth}. Tiled & Databroker only " f"support a Depth upto {MAX_DEPTH} " f"dtype_descr: {self.SECoP_type_info.dtype_descr}" ) - self.describe_dict: dict = {} - self.source_name = ( - secclient.uri + self._secclient.uri + ":" - + secclient.nodename + + self._secclient.nodename + ":" - + self.path._module_name + + self._module_name + ":" - + self.path._accessible_name + + self._attribute_name ) - # SECoP metadata is static and can only change when connection is reset self.describe_dict = {} - self.describe_dict["source"] = self.source_name - - # add gathered keys from SECoPdtype: self.describe_dict.update(self.SECoP_type_info.get_datakey()) for property_name, prop_val in self._param_description.items(): - # skip datainfo (treated seperately) - if property_name == "datainfo" or property_name == "datatype": + if property_name in ("datainfo", "datatype"): continue self.describe_dict[property_name] = prop_val @@ -289,15 +410,47 @@ def __init__(self, path: Path, secclient: AsyncFrappyClient) -> None: property_name = "units" self.describe_dict[property_name] = prop_val - super().__init__(datatype=self.SECoP_type_info.np_datatype) + self.datatype = self.SECoP_type_info.np_datatype - def source(self, name: str, read: bool) -> str: - return self.source_name + async def _init_property(self): + """Initialize as a property signal.""" - async def connect(self, timeout: float): - pass + if self._module_name: + module_desc = self._secclient.modules[self._module_name] + self._property_dict = module_desc["properties"] + else: + self._property_dict = self._secclient.properties + + self._prop_value = self._property_dict[self._attribute_name] + self.SECoPdtype_obj = secop_dtype_obj_from_json(self._prop_value) + self.SECoP_type_info = SECoPdtype(self.SECoPdtype_obj) + + if self.SECoP_type_info.max_depth > MAX_DEPTH: + warnings.warn( + f"The datatype of property '{self._attribute_name}' has a maximum " + f"depth of {self.SECoP_type_info.max_depth}. Tiled & Databroker only " + f"support a Depth upto {MAX_DEPTH} " + f"dtype_descr: {self.SECoP_type_info.dtype_descr}" + ) + + self.describe_dict = {} + self.describe_dict["source"] = self.path_str + self.describe_dict.update(self.SECoP_type_info.get_datakey()) + + # Properties are always readonly + self.format = StandardReadableFormat.CONFIG_SIGNAL + self.readonly = True + self.datatype = self.SECoP_type_info.np_datatype async def put(self, value: Any | None, wait=True): + """Put a value to the parameter. Properties are readonly.""" + + if self.attribute_type == AttributeType.PROPERTY: + # Properties are readonly + raise RuntimeError( + f"Cannot set property '{self._attribute_name}', properties are readonly" + ) + # convert to frappy compatible Format secop_val = self.SECoP_type_info.val2secop(value) @@ -310,6 +463,9 @@ async def put(self, value: Any | None, wait=True): async def get_datakey(self, source: str) -> DataKey: """Metadata like source, dtype, shape, precision, units""" + if self.attribute_type == AttributeType.PROPERTY: + # Properties have static metadata + return describedict_to_datakey(self.describe_dict) if self.SECoP_type_info._is_composite or isinstance( self.SECoPdtype_obj, ArrayOf @@ -326,23 +482,35 @@ async def get_datakey(self, source: str) -> DataKey: return describedict_to_datakey(self.describe_dict) async def get_reading(self) -> Reading[SignalDatatypeT]: - dataset = await self._secclient.get_parameter( - **self.get_param_path(), trycache=True - ) - - sec_reading = SECoPReading(entry=dataset, secop_dt=self.SECoP_type_info) + """Get reading, handling both parameters and properties.""" + if self.attribute_type == AttributeType.PROPERTY: + # Properties have static values + dataset = CacheItem( + value=self._prop_value, timestamp=self._secclient.conn_timestamp + ) + sec_reading = SECoPReading(entry=dataset, secop_dt=self.SECoP_type_info) + return sec_reading.get_reading() - return sec_reading.get_reading() + else: + # Parameters are fetched from SECoP + dataset = await self._secclient.get_parameter( + **self.get_param_path(), trycache=True + ) + sec_reading = SECoPReading(entry=dataset, secop_dt=self.SECoP_type_info) + return sec_reading.get_reading() async def get_value(self) -> SignalDatatypeT: dataset: Reading = await self.get_reading() - return dataset["value"] # type: ignore async def get_setpoint(self) -> SignalDatatypeT: return await self.get_value() def set_callback(self, callback: Callback[Reading[SignalDatatypeT]] | None) -> None: + if self.attribute_type == AttributeType.PROPERTY: + # Properties are static, no callbacks + return + def awaitify(sync_func): """Wrap a synchronous callable to allow ``await``'ing it""" @@ -367,106 +535,16 @@ def updateItem(module, parameter, entry: CacheItem): # noqa: N802 self._secclient.unregister_callback(self.get_path_tuple(), updateItem) def _get_param_desc(self) -> dict: - return deep_get(self._secclient.modules, self.path.get_param_desc_path()) + return deep_get( + self._secclient.modules, + [self._module_name, "parameters", self._attribute_name], + ) def get_param_path(self): - return self.path.get_param_path() + return {"module": self._module_name, "parameter": self._attribute_name} def get_path_tuple(self): - return self.path.get_path_tuple() - - def get_unit(self): - return self.describe_dict.get("units", None) - - def is_number(self) -> bool: - if ( - self.describe_dict["dtype"] == "number" - or self.describe_dict["dtype"] == "integer" - ): - return True - - return False - - -class PropertyBackend(SignalBackend): - """Readonly backend for static SECoP Properties of Nodes/Modules""" - - def __init__( - self, prop_key: str, property_dict: Dict[str, Any], secclient: AsyncFrappyClient - ) -> None: - """Initializes PropertyBackend - - :param prop_key: Name of Property - :type prop_key: str - :param propertyDict: Dicitonary containing all properties of Node/Module - :type propertyDict: Dict[str, T] - :param secclient: SECoP client providing communication to the SEC Node - :type secclient: AsyncFrappyClient - """ - # secclient - - self._property_dict = property_dict - self._prop_key = prop_key - self._prop_value = self._property_dict[self._prop_key] - self.SECoPdtype_obj: DataType = secop_dtype_obj_from_json(self._prop_value) - self.SECoP_type_info: SECoPdtype = SECoPdtype(self.SECoPdtype_obj) - - if self.SECoP_type_info.max_depth > MAX_DEPTH: - warnings.warn( - f"The datatype of parameter '{prop_key}' has a maximum" - f"depth of {self.SECoP_type_info.max_depth}. Tiled & Databroker only" - f"support a Depth upto {MAX_DEPTH}" - f"dtype_descr: {self.SECoP_type_info.dtype_descr}" - ) - - # SECoP metadata is static and can only change when connection is reset - self.describe_dict = {} - self.source_name = prop_key - self.describe_dict["source"] = self.source_name - - # add gathered keys from SECoPdtype: - self.describe_dict.update(self.SECoP_type_info.get_datakey()) - - self._secclient: AsyncFrappyClient = secclient - # TODO full property path - - super().__init__(datatype=self.SECoP_type_info.np_datatype) - - def source(self, name: str, read: bool) -> str: - return str(self.source_name) - - async def connect(self, timeout: float): - """Connect to underlying hardware""" - pass - - async def put(self, value: SignalDatatypeT | None, wait=True): - """Put a value to the PV, if wait then wait for completion for up to timeout""" - # Properties are readonly - pass - - async def get_datakey(self, source: str) -> DataKey: - """Metadata like source, dtype, shape, precision, units""" - return describedict_to_datakey(self.describe_dict) - - async def get_reading(self) -> Reading[SignalDatatypeT]: - dataset = CacheItem( - value=self._prop_value, timestamp=self._secclient.conn_timestamp - ) - - sec_reading = SECoPReading(entry=dataset, secop_dt=self.SECoP_type_info) - - return sec_reading.get_reading() - - async def get_value(self) -> SignalDatatypeT: - dataset: Reading = await self.get_reading() - - return dataset["value"] # type: ignore - - async def get_setpoint(self) -> SignalDatatypeT: - return await self.get_value() - - def set_callback(self, callback: Callback[Reading[SignalDatatypeT]] | None) -> None: - pass + return (self._module_name, self._attribute_name) def secop_dtype_obj_from_json(prop_val): diff --git a/src/secop_ophyd/logs.py b/src/secop_ophyd/logs.py index 0195f4e..a41e6cb 100644 --- a/src/secop_ophyd/logs.py +++ b/src/secop_ophyd/logs.py @@ -12,16 +12,6 @@ DEFAULT_ROTATION_INTERVAL = 1 # Every 1 hour DEFAULT_BACKUP_COUNT = 48 # Keep logs for 48 hours -# Create a dictionary of log level names to their values -LOG_LEVELS = { - "DEBUG": logging.DEBUG, - "INFO": logging.INFO, - "WARNING": logging.WARNING, - "ERROR": logging.ERROR, - "CRITICAL": logging.CRITICAL, -} - - log_file_handlers: Dict[str, TimedRotatingFileHandler] = {} console_handler: logging.StreamHandler | None = None diff --git a/src/secop_ophyd/templates/generated_classes.py.jinja2 b/src/secop_ophyd/templates/generated_classes.py.jinja2 new file mode 100644 index 0000000..9911c92 --- /dev/null +++ b/src/secop_ophyd/templates/generated_classes.py.jinja2 @@ -0,0 +1,105 @@ +{# Main template for generating ophyd device classes #} +{# Imports section #} +{% for module, classes in imports.items() %} +{%- if classes is none %} +import {{ module }} +{%- elif classes %} +from {{ module }} import {{ classes | sort | join(', ') }} +{%- else %} +import {{ module }} +{%- endif %} +{% endfor %} + + +{# Enum classes section #} +{% for enum_cls in enum_classes %} +class {{ enum_cls.name }}({{ enum_cls.base_enum_class }}): + """{{ enum_cls.description or 'Generated enum class' }}""" +{%- for member in enum_cls.members %} + {{ member.name }} = "{{ member.value }}" +{%- endfor %} + + +{% endfor %} + +{# Module classes section #} +{% for module_cls in module_classes %} +class {{ module_cls.name }}({{ module_cls.bases | join(', ') }}): + """{{ module_cls.description or 'Generated module class' }}""" +{%- if module_cls.properties %} + + # Module Properties +{%- for attr in module_cls.properties %} +{%- if attr.path_annotation and attr.format_annotation %} + {{ attr.name }}: A[{{ attr.type }}{% if attr.type_param %}[{{ attr.type_param }}]{% endif %}, {{ attr.path_annotation }}, {{ attr.format_annotation }}] +{%- elif attr.path_annotation %} + {{ attr.name }}: A[{{ attr.type }}{% if attr.type_param %}[{{ attr.type_param }}]{% endif %}, {{ attr.path_annotation }}] +{%- else %} + {{ attr.name }}: {{ attr.type }}{% if attr.type_param %}[{{ attr.type_param }}]{% endif %} +{%- endif %} +{%- if attr.description %} # {{ attr.description }}{% endif %} +{%- endfor %} +{%- endif %} +{%- if module_cls.parameters %} + + # Module Parameters +{%- for attr in module_cls.parameters %} +{%- if attr.path_annotation and attr.format_annotation %} + {{ attr.name }}: A[{{ attr.type }}{% if attr.type_param %}[{{ attr.type_param }}]{% endif %}, {{ attr.path_annotation }}, {{ attr.format_annotation }}] +{%- elif attr.path_annotation %} + {{ attr.name }}: A[{{ attr.type }}{% if attr.type_param %}[{{ attr.type_param }}]{% endif %}, {{ attr.path_annotation }}] +{%- else %} + {{ attr.name }}: {{ attr.type }}{% if attr.type_param %}[{{ attr.type_param }}]{% endif %} +{%- endif %} +{%- if attr.description %} +{%- set render_below = '\n' in attr.description or attr.description|length > inline_comment_threshold %} +{%- if render_below %} +{%- set wrapped_desc = attr.description | wordwrap(comment_wrap_width, break_long_words=False, wrapstring='\n') %} + # {{ (wrapped_desc | replace('\n', '\n # ')) ~ '\n' }} + +{%- else %} # {{ attr.description }} +{%- endif %} +{%- endif %} +{%- endfor %} +{%- endif %} +{%- if module_cls.methods %} + + # SECoP Commands wrapped as Bluesky Plans: +{%- for method in module_cls.methods %} + @abstractmethod + def {{ method.name }}{{ method.signature }}: + """{{ method.description }}""" +{%- endfor %} +{%- endif %} + + +{% endfor %} + +{# Node classes section #} +{% for node_cls in node_classes %} +class {{ node_cls.name }}({{ node_cls.bases | join(', ') }}): + """{{ node_cls.description or 'Generated node class' }}""" +{%- if node_cls.modules %} + + # Module Devices +{%- for attr in node_cls.modules %} + {{ attr.name }}: {{ attr.type }} +{%- if attr.description %} # {{ attr.description }}{% endif %} +{%- endfor %} +{%- endif %} +{%- if node_cls.properties %} + + # Node Properties +{%- for attr in node_cls.properties %} +{%- if attr.path_annotation and attr.format_annotation %} + {{ attr.name }}: A[{{ attr.type }}{% if attr.type_param %}[{{ attr.type_param }}]{% endif %}, {{ attr.path_annotation }}, {{ attr.format_annotation }}] +{%- elif attr.path_annotation %} + {{ attr.name }}: A[{{ attr.type }}{% if attr.type_param %}[{{ attr.type_param }}]{% endif %}, {{ attr.path_annotation }}] +{%- else %} + {{ attr.name }}: {{ attr.type }}{% if attr.type_param %}[{{ attr.type_param }}]{% endif %} +{%- endif %} +{%- if attr.description %} # {{ attr.description }}{% endif %} +{%- endfor %} +{%- endif %} + +{% endfor %} diff --git a/src/secop_ophyd/util.py b/src/secop_ophyd/util.py index 4cbe5df..4aa1aae 100644 --- a/src/secop_ophyd/util.py +++ b/src/secop_ophyd/util.py @@ -594,11 +594,15 @@ def __init__(self, datatype: DataType) -> None: self._is_composite: bool = False self._is_array: bool = False + self._is_enum: bool = False self.dtype_tree = dt_factory(datatype) self.max_depth: int = self.dtype_tree.max_depth + if isinstance(self.dtype_tree, EnumNP): + self._is_enum = True + if isinstance(self.dtype_tree, ArrayNP): self.shape = self.dtype_tree.shape self._is_array = True @@ -607,6 +611,9 @@ def __init__(self, datatype: DataType) -> None: if isinstance(self.dtype_tree.root_type, (StructNP, TupleNP)) else False ) + self._is_enum = self._is_enum or isinstance( + self.dtype_tree.root_type, EnumNP + ) if isinstance(self.dtype_tree, (TupleNP, StructNP)): self._is_composite = True @@ -666,6 +673,30 @@ def secop2val(self, reading_val) -> Any: if self._is_composite: return self._secop2numpy_array(reading_val) + elif self._is_enum: + if isinstance(self.dtype_tree, EnumNP): + exp_val = self.dtype_tree.secop_dtype(reading_val).name + return exp_val + + elif isinstance(self.dtype_tree, ArrayNP) and isinstance( + self.dtype_tree.root_type, EnumNP + ): + + def map_enum(val): + return list( + map( + lambda x: map_enum(x) if isinstance(x, tuple) else x.name, + val, + ) + ) + + exp_val = map_enum(reading_val) + + return exp_val + + else: + raise Exception("enum type is not correctly identified in dtype tree") + else: return reading_val @@ -675,6 +706,7 @@ def val2secop(self, input_val) -> Any: if self._is_composite and isinstance(input_val, np.ndarray): return self.dtype_tree.make_secop_compatible_object(input_val) + else: return self.raw_dtype.validate(input_val) @@ -722,13 +754,13 @@ def __init__( if entry.readerror is not None: raise entry.readerror - exported_val = secop_dt.raw_dtype.export_value(entry.value) + # exported_val = secop_dt.raw_dtype.export_value(entry.value) - self.secop_dt.update_dtype(exported_val) + self.secop_dt.update_dtype(entry.value) - self.value = secop_dt.secop2val(exported_val) + self.value = secop_dt.secop2val(entry.value) - self.secop_val = exported_val + self.secop_val = entry.value self.timestamp = entry.timestamp diff --git a/tests/conftest.py b/tests/conftest.py index 20dca79..baf9027 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,7 +1,7 @@ # mypy: disable-error-code="attr-defined" -import asyncio import logging import os +from pathlib import Path import pytest from bluesky import RunEngine @@ -20,6 +20,62 @@ from secop_ophyd.SECoPDevices import SECoPNodeDevice +@pytest.fixture(autouse=True) +def cleanup_secop_clients(): + """Clear SECoP clients between tests to ensure fresh connections.""" + yield + # After each test, clear the cached clients + from secop_ophyd.SECoPDevices import SECoPDevice + + SECoPDevice.clients.clear() + + +@pytest.fixture +def mass_spectrometer_description(): + mass_spectrometer_description = ( + Path(__file__).parent / "static_test_data" / "SHALL_mass_spec_describe.txt" + ) + + with mass_spectrometer_description.open() as f: + description = f.read() + + return description + + +@pytest.fixture +def mass_spectrometer_description_no_impl(): + mass_spectrometer_description = ( + Path(__file__).parent + / "static_test_data" + / "SHALL_mass_spec_describe_no_impl_prop.txt" + ) + + with mass_spectrometer_description.open() as f: + description = f.read() + + return description + + +@pytest.fixture +def clean_generated_file(): + """Clean up generated genNodeClass.py file before test runs. + + This fixture ensures a fresh start for code generation tests while + allowing inspection of results after the test completes. + + Returns: + Path to the testgen directory where files should be generated + """ + testgen_dir = Path(__file__).parent / "testgen" + testgen_dir.mkdir(exist_ok=True) + + gen_file = testgen_dir / "genNodeClass.py" + if gen_file.exists(): + gen_file.unlink() + + return testgen_dir + + @pytest.fixture def frappy_env(): """Create and return environment variables and paths for frappy server.""" @@ -133,31 +189,31 @@ def filter(self, record): return logger -@pytest.fixture +@pytest.fixture() async def async_frappy_client(cryo_sim, logger, port="10769"): - loop = asyncio.get_running_loop() + client = AsyncFrappyClient(host="localhost", port=port, log=logger) - return await AsyncFrappyClient.create( - host="localhost", port=port, loop=loop, log=logger - ) + await client.connect(3) + return client -@pytest.fixture + +@pytest.fixture() async def nested_client(nested_struct_sim, logger, port="10771"): - loop = asyncio.get_running_loop() + client = AsyncFrappyClient(host="localhost", port=port, log=logger) - return await AsyncFrappyClient.create( - host="localhost", port=port, loop=loop, log=logger - ) + await client.connect(3) + return client -@pytest.fixture + +@pytest.fixture() async def RE(): # noqa: N802 re = RunEngine({}) return re -@pytest.fixture +@pytest.fixture() async def nested_node_no_re(): async with init_devices(): nested = SECoPNodeDevice( @@ -167,7 +223,7 @@ async def nested_node_no_re(): return nested -@pytest.fixture +@pytest.fixture() def nested_node(RE): # noqa: N803 with init_devices(): nested = SECoPNodeDevice( @@ -177,17 +233,16 @@ def nested_node(RE): # noqa: N803 return nested -@pytest.fixture +@pytest.fixture() async def cryo_node_no_re(): async with init_devices(): cryo = SECoPNodeDevice( sec_node_uri="localhost:10769", ) - return cryo -@pytest.fixture +@pytest.fixture() def cryo_node(RE): # noqa: N803 with init_devices(): cryo = SECoPNodeDevice( diff --git a/tests/static_test_data/SHALL_mass_spec_describe.txt b/tests/static_test_data/SHALL_mass_spec_describe.txt new file mode 100644 index 0000000..de9a6f3 --- /dev/null +++ b/tests/static_test_data/SHALL_mass_spec_describe.txt @@ -0,0 +1,344 @@ +{ + "description":"This is the node for controllers and monitors where n devices (defined in hardware modules.cfg) call n modules.", + "equipment_id":"Hiden_MS", + "firmware":"SHALL server library (Git 3d17a8943d609f0e96cdd452244b4b4bf5243a44)", + "modules":{ + "mass_spec":{ + "accessibles":{ + "_autorange_high":{ + "datainfo":{ + "type":"double", + "unit":"mbar" + }, + "description":"This the highest range to which the input device may autorange; ", + "group":"acquisition_range", + "pollinterval":27.0, + "readonly":true + }, + "_autorange_low":{ + "datainfo":{ + "type":"double", + "unit":"mbar" + }, + "description":"The lowest range to which the input device may autorange", + "group":"acquisition_range", + "pollinterval":27.0, + "readonly":true + }, + "_cage":{ + "datainfo":{ + "type":"double", + "unit":"V" + }, + "description":" This is the Ion Source Cage voltage which controls the Ion Energy. The higher the Ion Energy the faster the Ions travel through the Mass Filter to the Detector, this reduces the oscillation effect caused by the RF which is applied to the filter.\n", + "group":"global_rga_parameters", + "pollinterval":27.0, + "readonly":true + }, + "_delta_m":{ + "datainfo":{ + "type":"double", + "unit":"%" + }, + "description":"The low mass peak width/valley adjustment used during set up and maintenance. Can also affect the high masses and should be adjusted in conjunction with the Resolution\n ", + "group":"global_rga_parameters", + "pollinterval":27.0, + "readonly":true + }, + "_dwell":{ + "datainfo":{ + "type":"double", + "unit":"%" + }, + "description":"Defines the time used to acquire a single point in the scan. Given as a percentage of the default settle time for the current range.", + "group":"acquisition_range", + "pollinterval":27.0, + "readonly":true + }, + "_electron_energy":{ + "datainfo":{ + "max":200.0, + "min":0.0, + "type":"double", + "unit":"V" + }, + "description":"The Electron energy is used to define the filament potential in the Ion Source. This is used to change the Appearance Potential of gasses with similar mass footprints so they can be looked at individually.", + "group":"global_rga_parameters", + "pollinterval":27.0, + "readonly":true + }, + "_emission":{ + "datainfo":{ + "type":"double", + "unit":"uA" + }, + "description":"The Emission current is the current which flows from the active filament to the Ion Source Cage. An increase in Emission Current causes an increase in peak intensity, can be used to increase/reduce the peak intensities.", + "group":"global_rga_parameters", + "pollinterval":27.0, + "readonly":true + }, + "_end_mass":{ + "datainfo":{ + "max":200.0, + "min":0.4, + "type":"double", + "unit":"amu" + }, + "description":"End mass number for a bar scan", + "group":"BAR_SCAN", + "pollinterval":24.0, + "readonly":false + }, + "_focus":{ + "datainfo":{ + "type":"double", + "unit":"V" + }, + "description":" This is the voltage applied to the Focus plate. This is used to extract the positive Ions from the source and into the Mass Filter, and also to block the transmission of electrons.", + "group":"global_rga_parameters", + "pollinterval":27.0, + "readonly":true + }, + "_increment":{ + "datainfo":{ + "max":20.0, + "min":0.0, + "type":"double", + "unit":"amu" + }, + "description":"Incremental mass for Bar scan ", + "group":"BAR_SCAN", + "pollinterval":27.0, + "readonly":false + }, + "_mass":{ + "datainfo":{ + "maxlen":600, + "members":{ + "type":"double" + }, + "type":"array" + }, + "description":"mass number configuration", + "pollinterval":3.0, + "readonly":true + }, + "_measure_device":{ + "datainfo":{ + "members":{ + "FARADAY":0, + "SEM":1, + "VACCUM":6 + }, + "type":"enum" + }, + "description":"Selects the detector for a bar scan", + "group":"BAR_SCAN", + "pollinterval":24.0, + "readonly":false + }, + "_mid_descriptor":{ + "datainfo":{ + "members":{ + "device":{ + "maxlen":50, + "members":{ + "type":"double" + }, + "type":"array" + }, + "mass":{ + "maxlen":50, + "members":{ + "type":"double" + }, + "type":"array", + "unit":"amu" + } + }, + "type":"struct" + }, + "description":" Data structure that describes an MID Scan. (massnumber and measurement device for each massnumber) \n Example:\n {\n mass: [12,15,28,75],\n device: [FARADAY,SEM,SEM,SEM]\n }", + "pollinterval":24.0, + "readonly":false + }, + "_multiplier":{ + "datainfo":{ + "type":"double", + "unit":"V" + }, + "description":"The voltage applied to the SEM detector; with a PIC this should be set so the SEM operates in the Plateau Region. With an Analogue system this should be set to 1000 gain, i.e. a scan in Faraday should be equal height using the SEM detector.\n ", + "group":"global_rga_parameters", + "pollinterval":27.0, + "readonly":true + }, + "_resolution":{ + "datainfo":{ + "type":"double", + "unit":"%" + }, + "description":" The high mass peak width/valley adjustment used during set up and maintenance. Can also affect the low masses and should be adjusted in conjunction with the Delta-M.\n ", + "group":"global_rga_parameters", + "pollinterval":27.0, + "readonly":true + }, + "_scan_cycle":{ + "datainfo":{ + "members":{ + "CONTINUOUS":0, + "SINGLE":1 + }, + "type":"enum" + }, + "description":"indicates if in single or continuous cycle mode", + "pollinterval":24.0, + "readonly":false + }, + "_settle":{ + "datainfo":{ + "type":"double", + "unit":"%" + }, + "description":"Defines the time to allow the electronics to settle before the scan is started. Given as a percentage of the default settle time for the current range.", + "group":"acquisition_range", + "pollinterval":27.0, + "readonly":true + }, + "_start_mass":{ + "datainfo":{ + "max":200.0, + "min":0.4, + "type":"double", + "unit":"amu" + }, + "description":"Start mass number for a bar scan", + "group":"BAR_SCAN", + "pollinterval":24.0, + "readonly":false + }, + "_start_range":{ + "datainfo":{ + "type":"double", + "unit":"mbar" + }, + "description":"Contains the range used at the start of a scan.", + "group":"acquisition_range", + "pollinterval":27.0, + "readonly":true + }, + "_vacuum_pressure":{ + "datainfo":{ + "max":2000.0, + "min":0.0, + "type":"double", + "unit":"mbar" + }, + "description":"Pressure inside the measurement chamber", + "pollinterval":3.0, + "readonly":true + }, + "go":{ + "datainfo":{ + "argument":null, + "result":null, + "type":"command" + }, + "description":"starts new scan, or initiates the continuous scan", + "pollinterval":27.0 + }, + "mode":{ + "datainfo":{ + "members":{ + "BAR_SCAN":1, + "MID_SCAN":0 + }, + "type":"enum" + }, + "description":"indicates the current scan mode", + "pollinterval":24.0, + "readonly":false + }, + "status":{ + "datainfo":{ + "members":[ + { + "members":{ + "BUSY":300, + "IDLE":100, + "PREPARING":340 + }, + "type":"enum" + }, + { + "isUTF8":true, + "type":"string" + } + ], + "type":"tuple" + }, + "description":"current status of the module ", + "pollinterval":3.0, + "readonly":false + }, + "stop":{ + "datainfo":{ + "argument":null, + "result":null, + "type":"command" + }, + "description":"stops running scan module goes back to IDLE", + "pollinterval":27.0 + }, + "value":{ + "datainfo":{ + "maxlen":600, + "members":{ + "type":"double" + }, + "type":"array" + }, + "description":"Full or partial spectrum of the currently running/last complete scan", + "pollinterval":3.0, + "readonly":true + } + }, + "description":"Analyses the gas after the reactor cell by mass spectrometry.", + "implementation":"mass_spec", + "interface_classes":[ + "Readable", + "Triggerable" + ], + "order":[ + "value", + "status", + "_vacuum_pressure", + "mode", + "_mid_descriptor", + "_measure_device", + "_start_mass", + "_end_mass", + "_scan_cycle", + "_electron_energy", + "_emission", + "_focus", + "_cage", + "_resolution", + "_delta_m", + "_start_range", + "_autorange_high", + "_autorange_low", + "_settle", + "_dwell", + "go", + "stop", + "_multiplier", + "_increment", + "_mass" + ], + "pollinterval":3.0 + } + }, + "order":[ + "mass_spec" + ] +} diff --git a/tests/static_test_data/SHALL_mass_spec_describe_no_impl_prop.txt b/tests/static_test_data/SHALL_mass_spec_describe_no_impl_prop.txt new file mode 100644 index 0000000..f9cad50 --- /dev/null +++ b/tests/static_test_data/SHALL_mass_spec_describe_no_impl_prop.txt @@ -0,0 +1,343 @@ +{ + "description":"This is the node for controllers and monitors where n devices (defined in hardware modules.cfg) call n modules.", + "equipment_id":"Hiden_MS", + "firmware":"SHALL server library (Git 3d17a8943d609f0e96cdd452244b4b4bf5243a44)", + "modules":{ + "mass_spec":{ + "accessibles":{ + "_autorange_high":{ + "datainfo":{ + "type":"double", + "unit":"mbar" + }, + "description":"This the highest range to which the input device may autorange; ", + "group":"acquisition_range", + "pollinterval":27.0, + "readonly":true + }, + "_autorange_low":{ + "datainfo":{ + "type":"double", + "unit":"mbar" + }, + "description":"The lowest range to which the input device may autorange", + "group":"acquisition_range", + "pollinterval":27.0, + "readonly":true + }, + "_cage":{ + "datainfo":{ + "type":"double", + "unit":"V" + }, + "description":" This is the Ion Source Cage voltage which controls the Ion Energy. The higher the Ion Energy the faster the Ions travel through the Mass Filter to the Detector, this reduces the oscillation effect caused by the RF which is applied to the filter.\n", + "group":"global_rga_parameters", + "pollinterval":27.0, + "readonly":true + }, + "_delta_m":{ + "datainfo":{ + "type":"double", + "unit":"%" + }, + "description":"The low mass peak width/valley adjustment used during set up and maintenance. Can also affect the high masses and should be adjusted in conjunction with the Resolution\n ", + "group":"global_rga_parameters", + "pollinterval":27.0, + "readonly":true + }, + "_dwell":{ + "datainfo":{ + "type":"double", + "unit":"%" + }, + "description":"Defines the time used to acquire a single point in the scan. Given as a percentage of the default settle time for the current range.", + "group":"acquisition_range", + "pollinterval":27.0, + "readonly":true + }, + "_electron_energy":{ + "datainfo":{ + "max":200.0, + "min":0.0, + "type":"double", + "unit":"V" + }, + "description":"The Electron energy is used to define the filament potential in the Ion Source. This is used to change the Appearance Potential of gasses with similar mass footprints so they can be looked at individually.", + "group":"global_rga_parameters", + "pollinterval":27.0, + "readonly":true + }, + "_emission":{ + "datainfo":{ + "type":"double", + "unit":"uA" + }, + "description":"The Emission current is the current which flows from the active filament to the Ion Source Cage. An increase in Emission Current causes an increase in peak intensity, can be used to increase/reduce the peak intensities.", + "group":"global_rga_parameters", + "pollinterval":27.0, + "readonly":true + }, + "_end_mass":{ + "datainfo":{ + "max":200.0, + "min":0.4, + "type":"double", + "unit":"amu" + }, + "description":"End mass number for a bar scan", + "group":"BAR_SCAN", + "pollinterval":24.0, + "readonly":false + }, + "_focus":{ + "datainfo":{ + "type":"double", + "unit":"V" + }, + "description":" This is the voltage applied to the Focus plate. This is used to extract the positive Ions from the source and into the Mass Filter, and also to block the transmission of electrons.", + "group":"global_rga_parameters", + "pollinterval":27.0, + "readonly":true + }, + "_increment":{ + "datainfo":{ + "max":20.0, + "min":0.0, + "type":"double", + "unit":"amu" + }, + "description":"Incremental mass for Bar scan ", + "group":"BAR_SCAN", + "pollinterval":27.0, + "readonly":false + }, + "_mass":{ + "datainfo":{ + "maxlen":600, + "members":{ + "type":"double" + }, + "type":"array" + }, + "description":"mass number configuration", + "pollinterval":3.0, + "readonly":true + }, + "_measure_device":{ + "datainfo":{ + "members":{ + "FARADAY":0, + "SEM":1, + "VACCUM":6 + }, + "type":"enum" + }, + "description":"Selects the detector for a bar scan", + "group":"BAR_SCAN", + "pollinterval":24.0, + "readonly":false + }, + "_mid_descriptor":{ + "datainfo":{ + "members":{ + "device":{ + "maxlen":50, + "members":{ + "type":"double" + }, + "type":"array" + }, + "mass":{ + "maxlen":50, + "members":{ + "type":"double" + }, + "type":"array", + "unit":"amu" + } + }, + "type":"struct" + }, + "description":" Data structure that describes an MID Scan. (massnumber and measurement device for each massnumber) \n Example:\n {\n mass: [12,15,28,75],\n device: [FARADAY,SEM,SEM,SEM]\n }", + "pollinterval":24.0, + "readonly":false + }, + "_multiplier":{ + "datainfo":{ + "type":"double", + "unit":"V" + }, + "description":"The voltage applied to the SEM detector; with a PIC this should be set so the SEM operates in the Plateau Region. With an Analogue system this should be set to 1000 gain, i.e. a scan in Faraday should be equal height using the SEM detector.\n ", + "group":"global_rga_parameters", + "pollinterval":27.0, + "readonly":true + }, + "_resolution":{ + "datainfo":{ + "type":"double", + "unit":"%" + }, + "description":" The high mass peak width/valley adjustment used during set up and maintenance. Can also affect the low masses and should be adjusted in conjunction with the Delta-M.\n ", + "group":"global_rga_parameters", + "pollinterval":27.0, + "readonly":true + }, + "_scan_cycle":{ + "datainfo":{ + "members":{ + "CONTINUOUS":0, + "SINGLE":1 + }, + "type":"enum" + }, + "description":"indicates if in single or continuous cycle mode", + "pollinterval":24.0, + "readonly":false + }, + "_settle":{ + "datainfo":{ + "type":"double", + "unit":"%" + }, + "description":"Defines the time to allow the electronics to settle before the scan is started. Given as a percentage of the default settle time for the current range.", + "group":"acquisition_range", + "pollinterval":27.0, + "readonly":true + }, + "_start_mass":{ + "datainfo":{ + "max":200.0, + "min":0.4, + "type":"double", + "unit":"amu" + }, + "description":"Start mass number for a bar scan", + "group":"BAR_SCAN", + "pollinterval":24.0, + "readonly":false + }, + "_start_range":{ + "datainfo":{ + "type":"double", + "unit":"mbar" + }, + "description":"Contains the range used at the start of a scan.", + "group":"acquisition_range", + "pollinterval":27.0, + "readonly":true + }, + "_vacuum_pressure":{ + "datainfo":{ + "max":2000.0, + "min":0.0, + "type":"double", + "unit":"mbar" + }, + "description":"Pressure inside the measurement chamber", + "pollinterval":3.0, + "readonly":true + }, + "go":{ + "datainfo":{ + "argument":null, + "result":null, + "type":"command" + }, + "description":"starts new scan, or initiates the continuous scan", + "pollinterval":27.0 + }, + "mode":{ + "datainfo":{ + "members":{ + "BAR_SCAN":1, + "MID_SCAN":0 + }, + "type":"enum" + }, + "description":"indicates the current scan mode", + "pollinterval":24.0, + "readonly":false + }, + "status":{ + "datainfo":{ + "members":[ + { + "members":{ + "BUSY":300, + "IDLE":100, + "PREPARING":340 + }, + "type":"enum" + }, + { + "isUTF8":true, + "type":"string" + } + ], + "type":"tuple" + }, + "description":"current status of the module ", + "pollinterval":3.0, + "readonly":false + }, + "stop":{ + "datainfo":{ + "argument":null, + "result":null, + "type":"command" + }, + "description":"stops running scan module goes back to IDLE", + "pollinterval":27.0 + }, + "value":{ + "datainfo":{ + "maxlen":600, + "members":{ + "type":"double" + }, + "type":"array" + }, + "description":"Full or partial spectrum of the currently running/last complete scan", + "pollinterval":3.0, + "readonly":true + } + }, + "description":"Analyses the gas after the reactor cell by mass spectrometry.", + "interface_classes":[ + "Readable", + "Triggerable" + ], + "order":[ + "value", + "status", + "_vacuum_pressure", + "mode", + "_mid_descriptor", + "_measure_device", + "_start_mass", + "_end_mass", + "_scan_cycle", + "_electron_energy", + "_emission", + "_focus", + "_cage", + "_resolution", + "_delta_m", + "_start_range", + "_autorange_high", + "_autorange_low", + "_settle", + "_dwell", + "go", + "stop", + "_multiplier", + "_increment", + "_mass" + ], + "pollinterval":3.0 + } + }, + "order":[ + "mass_spec" + ] +} diff --git a/tests/test_ classgen.py b/tests/test_ classgen.py deleted file mode 100644 index 8fe29d0..0000000 --- a/tests/test_ classgen.py +++ /dev/null @@ -1,18 +0,0 @@ -# mypy: disable-error-code="attr-defined" -import os - -from secop_ophyd.SECoPDevices import SECoPNodeDevice - - -async def test_class_gen(nested_struct_sim, nested_node_no_re: SECoPNodeDevice): - nested_node_no_re.class_from_instance() - - if os.path.exists("genNodeClass.py"): - os.remove("genNodeClass.py") - - -async def test_class_gen_path(nested_struct_sim, nested_node_no_re: SECoPNodeDevice): - nested_node_no_re.class_from_instance("tests") - - if os.path.exists("tests/genNodeClass.py"): - os.remove("tests/genNodeClass.py") diff --git a/tests/test_Node.py b/tests/test_Node.py index 75c8afb..04ae331 100644 --- a/tests/test_Node.py +++ b/tests/test_Node.py @@ -15,10 +15,36 @@ async def test_node_structure(cryo_sim, cryo_node_no_re: SECoPNodeDevice): async def test_node_read(cryo_sim, cryo_node_no_re: SECoPNodeDevice): # Node device should return the readbacks of the read signals of the child devices + val_read = await cryo_node_no_re.read() + print(val_read) assert val_read != {} +async def test_source(cryo_sim, cryo_node_no_re: SECoPNodeDevice): + # Node device should return the readbacks of the read signals of the child devices + + # Prameter name and source + val_name = cryo_node_no_re.cryo.value.name + val_source = cryo_node_no_re.cryo.value.source + + assert val_name == "cryo_7-frappy-demo-cryo-value" + assert val_source == "localhost:10769:cryo:value" + + # property name and source + prop_name = cryo_node_no_re.equipment_id.name + prop_source = cryo_node_no_re.equipment_id.source + + assert prop_name == "cryo_7-frappy-demo-equipment_id" + assert prop_source == "localhost:10769:equipment_id" + + mod_prop_name = cryo_node_no_re.cryo.description.name + mod_prop_source = cryo_node_no_re.cryo.description.source + + assert mod_prop_name == "cryo_7-frappy-demo-cryo-description" + assert mod_prop_source == "localhost:10769:cryo:description" + + async def test_node_describe(cryo_sim, cryo_node_no_re: SECoPNodeDevice): # Node device should return the descriptions of the read signals of the child # devices diff --git a/tests/test_classgen.py b/tests/test_classgen.py new file mode 100644 index 0000000..f0fae60 --- /dev/null +++ b/tests/test_classgen.py @@ -0,0 +1,694 @@ +"""Simple test to verify GenNodeCode refactoring works.""" + +import sys +from pathlib import Path + +from ophyd_async.core import SignalR, init_devices + +from secop_ophyd.GenNodeCode import ( + GenNodeCode, + Method, + ModuleAttribute, + ModuleClass, + NodeClass, + ParameterAttribute, + PropertyAttribute, +) +from secop_ophyd.SECoPDevices import ParameterType, PropertyType, SECoPNodeDevice + +# Add src to path +sys.path.insert(0, str(Path(__file__).parent.parent / "src")) + + +class _DescriptionParseSample: + count: int # count comment + label: str = "value # not a comment" + # label continuation + + def helper(self): + local: int # must not be parsed # noqa: F842 + + +def test_extract_descriptions_from_source_is_token_safe(): + """Ensure parser only reads real comments and ignores '#' inside strings.""" + gen_code = GenNodeCode(log=None) + descriptions = gen_code._extract_descriptions_from_source(_DescriptionParseSample) + + assert descriptions["count"] == "count comment" + assert descriptions["label"] == "label continuation" + assert "local" not in descriptions + + +def test_basic_functionality(clean_generated_file): + """Test basic GenNodeCode functionality.""" + print("Testing GenNodeCode refactored implementation...") + + # Create instance + gen_code = GenNodeCode(path=str(clean_generated_file), log=None) + + # Add some imports + gen_code.add_import("ophyd_async.core", "Device") + + # Create a simple method + from inspect import signature + + def sample_method(self, value: int) -> str: + """Sample method description""" + return str(value) + + # Method can be created in the old way (backward compatible) + method = Method( + cmd_name="sample_command", + description="Sample command for testing", + cmd_sign=signature(sample_method), + ) + + # Add a module class + gen_code.add_mod_class( + module_cls="TestModule", + bases=["SECoPDevice"], + parameters=[ + ParameterAttribute( + name="temperature", + type="SignalR", + type_param="float", + path_annotation=str(ParameterType()), + ), + ParameterAttribute( + name="pressure", + type="SignalR", + type_param="float", + path_annotation=str(ParameterType()), + ), + ParameterAttribute( + name="count", + type="SignalRW", + type_param="int", + path_annotation=str(ParameterType()), + ), + ], + properties=[], + cmd_plans=[method], + description="Test module class", + ) + + # Add a node class + gen_code.add_node_class( + node_cls="TestNode", + bases=["SECoPNodeDevice"], + modules=[ + ModuleAttribute(name="module1", type="TestModule"), + ], + properties=[ + PropertyAttribute( + name="status", + type="SignalR", + type_param="str", + path_annotation=str(PropertyType()), + ), + ], + description="Test node class", + ) + + # Generate code + code = gen_code.generate_code() + + gen_code.write_gen_node_class_file() + + print("\n" + "=" * 60) + print("Generated Code:") + print("=" * 60) + print(code) + print("=" * 60) + + # Verify code contains expected elements + assert "from abc import abstractmethod" in code + assert "class TestModule(SECoPDevice):" in code + assert "temperature: A[SignalR[float], ParameterType()]" in code + assert "count: A[SignalRW[int], ParameterType()]" in code + assert "class TestNode(SECoPNodeDevice):" in code + assert "module1: TestModule" in code + assert "status: A[SignalR[str], PropertyType()]" in code + assert "def sample_command" in code + + print("\n✓ All basic tests passed!") + + +def test_dataclasses(): + """Test the new dataclasses.""" + print("\nTesting dataclasses...") + + # Test ParameterAttribute + param_attr = ParameterAttribute(name="test_param", type="SignalR") + assert param_attr.name == "test_param" + assert param_attr.type == "SignalR" + + # Test PropertyAttribute + prop_attr = PropertyAttribute(name="test_prop", type="SignalR") + assert prop_attr.name == "test_prop" + assert prop_attr.type == "SignalR" + + # Test ModuleAttribute + mod_attr = ModuleAttribute(name="test_mod", type="TestModule") + assert mod_attr.name == "test_mod" + assert mod_attr.type == "TestModule" + + # Test ModuleClass + mod_cls = ModuleClass( + name="TestMod", + bases=["Device"], + parameters=[param_attr], + properties=[prop_attr], + methods=[], + ) + assert mod_cls.name == "TestMod" + assert len(mod_cls.parameters) == 1 + assert len(mod_cls.properties) == 1 + + # Test NodeClass + node_cls = NodeClass( + name="TestNode", bases=["Device"], modules=[mod_attr], properties=[prop_attr] + ) + assert node_cls.name == "TestNode" + + print("✓ Dataclass tests passed!") + + +def test_subsequent_node_generation(clean_generated_file): + """Test generating code for two nodes sequentially, appending to the same file. + + Tests that: + - First: Generate NodeA with modules Type1 and Type2, write to file + - Second: Load existing file, add NodeB with Type1 (shared) and Type3 (new) + - Type1 should appear only once in the final file (not duplicated) + - All classes (Type1, Type2, Type3, NodeA, NodeB) are in the final file + """ + + from inspect import signature + + # ===== STEP 1: Generate and write first node (NodeA) ===== + + gen_code1 = GenNodeCode(path=str(clean_generated_file), log=None) + + # Create sample methods + def type1_command(self, value: float) -> float: + """Type1 command""" + return value * 2.0 + + method_type1 = Method( + cmd_name="type1_cmd", + description="Type1 command", + cmd_sign=signature(type1_command), + ) + + def type2_command(self, mode: str) -> str: + """Type2 command""" + return f"Mode: {mode}" + + method_type2 = Method( + cmd_name="type2_cmd", + description="Type2 command", + cmd_sign=signature(type2_command), + ) + + # Add module class Type1 (will be shared) + gen_code1.add_mod_class( + module_cls="Type1", + bases=["SECoPDevice"], + parameters=[ + ParameterAttribute( + name="temperature", + type="SignalR", + type_param="float", + description="this has to be in the final output", + path_annotation="ParameterType()", + ), + ParameterAttribute( + name="setpoint", + type="SignalRW", + type_param="float", + path_annotation="ParameterType()", + ), + ], + properties=[ + PropertyAttribute( + name="description", + type="SignalR", + type_param="str", + path_annotation="PropertyType()", + ), + PropertyAttribute( + name="interface_classes", + type="SignalR", + type_param="int", + path_annotation="PropertyType()", + ), + ], + cmd_plans=[method_type1], + description="Type1 module - shared between nodes", + ) + + # Add module class Type2 (only in nodeA) + gen_code1.add_mod_class( + module_cls="Type2", + bases=["SECoPDevice"], + parameters=[ + ParameterAttribute( + name="pressure", + type="SignalR", + type_param="float", + path_annotation="ParameterType()", + ), + ParameterAttribute( + name="mode", + type="SignalRW", + type_param="str", + path_annotation="ParameterType()", + ), + ], + properties=[ + PropertyAttribute( + name="implementation", + type="SignalR", + type_param="str", + path_annotation="PropertyType()", + ), + ], + cmd_plans=[method_type2], + description="Type2 module - only in nodeA", + ) + + # Add nodeA + gen_code1.add_node_class( + node_cls="NodeA", + bases=["SECoPNodeDevice"], + modules=[ + ModuleAttribute(name="modA", type="Type1"), + ModuleAttribute(name="modB", type="Type2"), + ], + properties=[ + PropertyAttribute( + name="status", + type="SignalR", + type_param="str", + path_annotation="PropertyType()", + ), + ], + description="NodeA with Type1 and Type2 modules", + ) + + # Generate and write first node + code1 = gen_code1.generate_code() + gen_code1.write_gen_node_class_file() + + # Verify first generation + assert "class Type1(SECoPDevice):" in code1 + assert "class Type2(SECoPDevice):" in code1 + assert "class NodeA(SECoPNodeDevice):" in code1 + assert "modA: Type1" in code1 + assert "modB: Type2" in code1 + + # ===== STEP 2: Load existing file and add second node (NodeB) ===== + + gen_code2 = GenNodeCode(path=str(clean_generated_file), log=None) + + # Add necessary imports again + gen_code2.add_import("secop_ophyd.SECoPDevices", "SECoPDevice") + gen_code2.add_import("secop_ophyd.SECoPDevices", "SECoPNodeDevice") + + # Create method for Type3 + def type3_command(self, count: int) -> int: + """Type3 command""" + return count + 1 + + method_type3 = Method( + cmd_name="type3_cmd", + description="Type3 command", + cmd_sign=signature(type3_command), + ) + + # Add Type1 again - GenNodeCode should detect it already exists + gen_code2.add_mod_class( + module_cls="Type1", + bases=["SECoPDevice"], + parameters=[ + ParameterAttribute( + name="temperature", + type="SignalR", + type_param="float", + path_annotation="ParameterType()", + ), + ParameterAttribute( + name="setpoint", + type="SignalRW", + type_param="float", + path_annotation="ParameterType()", + ), + ], + properties=[ + PropertyAttribute( + name="description", + type="SignalR", + type_param="str", + path_annotation="PropertyType()", + ), + PropertyAttribute( + name="interface_classes", + type="SignalR", + type_param="list", + path_annotation="PropertyType()", + ), + ], + cmd_plans=[method_type1], + description="Type1 module - shared between nodes", + ) + + # Add module class Type3 (only in nodeB) + gen_code2.add_mod_class( + module_cls="Type3", + bases=["SECoPDevice"], + parameters=[ + ParameterAttribute( + name="count", + type="SignalRW", + type_param="int", + description="this is a description", + path_annotation="ParameterType()", + ), + ParameterAttribute( + name="enabled", + type="SignalR", + type_param="bool", + path_annotation="ParameterType()", + ), + ], + properties=[ + PropertyAttribute( + name="group", + type="SignalR", + type_param="str", + path_annotation="PropertyType()", + ), + ], + cmd_plans=[method_type3], + description="Type3 module - only in nodeB", + ) + + # Add nodeB + gen_code2.add_node_class( + node_cls="NodeB", + bases=["SECoPNodeDevice"], + modules=[ + ModuleAttribute(name="modA", type="Type1"), + ModuleAttribute(name="modB", type="Type3"), + ], + properties=[ + PropertyAttribute( + name="name", + type="SignalR", + type_param="str", + path_annotation="PropertyType()", + ), + ], + description="NodeB with Type1 and Type3 modules", + ) + + # Generate and write second node (appends to the file) + code2 = gen_code2.generate_code() + gen_code2.write_gen_node_class_file() + + # ===== VERIFICATION ===== + # Verify that Type1 appears only once in the final code + type1_count = code2.count("class Type1(SECoPDevice):") + + assert ( + type1_count == 1 + ), f"Type1 should appear exactly once, but appears {type1_count} times" + + # Verify all module classes are present + assert "class Type1(SECoPDevice):" in code2 + assert "class Type2(SECoPDevice):" in code2 + assert "class Type3(SECoPDevice):" in code2 + + # Verify both node classes are present + assert "class NodeA(SECoPNodeDevice):" in code2 + assert "class NodeB(SECoPNodeDevice):" in code2 + + # Verify all methods are present + assert "def type1_cmd" in code2 + assert "def type2_cmd" in code2 + assert "def type3_cmd" in code2 + + # Verify section comments are present + assert "# Module Properties" in code2 + assert "# Module Parameters" in code2 + + # Verify that descriptive comments are preserved in generated code + assert "# this is a description" in code2 + assert "# this has to be in the final output" in code2 + + +async def test_gen_cryo_node( + clean_generated_file, cryo_sim, cryo_node_no_re: SECoPNodeDevice +): + """Test generating code for a real SECoP node.""" + + cryo_node_no_re.class_from_instance(clean_generated_file) + + from tests.testgen.genNodeClass import Cryo_7_frappy_demo # type: ignore + + async with init_devices(): + cryo_gen_code = Cryo_7_frappy_demo(sec_node_uri="localhost:10769") + + cryo_val = await cryo_gen_code.read() + + # target and value shoule be present in readback, since they are read signals with + # HINTED Format --> this is tested to verify that the correct annotations are + # generated and interpreted in the generated code + val_name = cryo_gen_code.cryo.value.name + target_name = cryo_gen_code.cryo.target.name + read_val = cryo_val[val_name].get("value") + read_target = cryo_val[target_name].get("value") + + print(cryo_val) + + assert read_val is not None + assert read_val > 5 + + assert read_target is not None + assert read_target == 10 + + +async def test_gen_cryo_status_not_in_cfg( + clean_generated_file, cryo_sim, cryo_node_no_re: SECoPNodeDevice +): + """Test that Status signal is not marked as configuration signal but is still + instantiated.""" + + cryo_node_no_re.class_from_instance(clean_generated_file) + + cryo_cfg = await cryo_node_no_re.read_configuration() + cryo_reading = await cryo_node_no_re.read() + + print(cryo_reading) + + assert hasattr(cryo_node_no_re.cryo, "status") + assert isinstance(cryo_node_no_re.cryo.status, SignalR) + + stat_name = cryo_node_no_re.cryo.status.name + + assert ( + cryo_cfg.get(stat_name) is None + ), "Status signal should not be in configuration" + assert cryo_reading.get(stat_name) is None, "Status signal should be readable" + + # check if status signal is working + status_reding = await cryo_node_no_re.cryo.status.read() + + assert status_reding.get(stat_name) is not None, "Status signal should be readable" + + # Import generated class + from tests.testgen.genNodeClass import Cryo_7_frappy_demo # type: ignore + + async with init_devices(): + cryo_gen_code = Cryo_7_frappy_demo(sec_node_uri="localhost:10769") + + # Status signal should still be present and functional in the generated code, even + # though it's not in the configuration + assert hasattr(cryo_gen_code.cryo, "status") + assert isinstance(cryo_gen_code.cryo.status, SignalR) + + cryo_cfg = await cryo_gen_code.read_configuration() + cryo_reading = await cryo_gen_code.read() + + print(cryo_reading) + + stat_name = cryo_gen_code.cryo.status.name + + assert ( + cryo_cfg.get(stat_name) is None + ), "Status signal should not be in configuration" + assert cryo_reading.get(stat_name) is None, "Status signal should be readable" + + # check if status signal is working + status_reding = await cryo_gen_code.cryo.status.read() + + assert status_reding.get(stat_name) is not None, "Status signal should be readable" + + +async def test_gen_real_node( + clean_generated_file, + nested_struct_sim, + nested_node_no_re: SECoPNodeDevice, # noqa: N803 +): + + nested_node_no_re.class_from_instance(clean_generated_file) + + # Read the generated file and verify its contents + gen_file = clean_generated_file / "genNodeClass.py" + assert gen_file.exists(), "Generated file should exist" + + generated_code = gen_file.read_text() + + # ===== Assertions for generated command plans ===== + # The ophy_struct module has a test_cmd command + assert "def test_cmd" in generated_code, "test_cmd plan should be generated" + assert ( + "@abstractmethod" in generated_code + ), "Command methods should be marked as abstract" + + # ===== Assertions for generated enum classes ===== + # Enum classes should be generated for enum parameters + # The gas_type parameter in enum1/enum2 modules should generate enum classes + assert ( + "class TestEnum_GasType_Enum(SupersetEnum):" in generated_code + ), "Enum class for gas_type should be generated" + + # Verify enum members are present + # gas_type enums should have AR, N2, H2 (and CO2 for enum2) + assert "AR" in generated_code, "AR enum member should be present" + assert "N2" in generated_code, "N2 enum member should be present" + assert "H2" in generated_code, "H2 enum member should be present" + assert "CO2" in generated_code, "CO2 enum member should be present" + + # Verify SupersetEnum import + assert ( + "from enum import Enum" in generated_code or "SupersetEnum" in generated_code + ), "Enum import should be present" + + +async def test_subsequent_real_nodes_with_enum( + clean_generated_file, + cryo_sim, + cryo_node_no_re: SECoPNodeDevice, + nested_struct_sim, + nested_node_no_re: SECoPNodeDevice, +): + + nested_node_no_re.class_from_instance(clean_generated_file) + + # Read the generated file and verify its contents + gen_file = clean_generated_file / "genNodeClass.py" + assert gen_file.exists(), "Generated file should exist" + + generated_code = gen_file.read_text() + + # ===== Assertions for generated enum classes ===== + cls = [ + "class TestEnum_GasType_Enum(SupersetEnum):", + "class TestModStr(SECoPReadableDevice):", + "class OphydTestPrimitiveArrays(SECoPReadableDevice):", + "class TestEnum(SECoPReadableDevice):", + "class TestNdArrays(SECoPReadableDevice):", + "class TestStructOfArrays(SECoPReadableDevice):", + "class Ophyd_secop_frappy_demo(SECoPNodeDevice):", + ] + for classs_str in cls: + assert classs_str in generated_code + + cryo_node_no_re.class_from_instance(clean_generated_file) + + # Read the generated file and verify its contents + gen_file = clean_generated_file / "genNodeClass.py" + assert gen_file.exists(), "Generated file should exist" + + generated_code = gen_file.read_text() + + # ===== Assertions for generated enum classes ===== + + cls = [ + "class TestEnum_GasType_Enum(SupersetEnum):", + "class TestModStr(SECoPReadableDevice):", + "class OphydTestPrimitiveArrays(SECoPReadableDevice):", + "class TestEnum(SECoPReadableDevice):", + "class TestNdArrays(SECoPReadableDevice):", + "class TestStructOfArrays(SECoPReadableDevice):", + "class Ophyd_secop_frappy_demo(SECoPNodeDevice):", + "class Cryo_7_frappy_demo(SECoPNodeDevice):", + "class Cryostat(SECoPMoveableDevice):", + "class Cryostat_Mode_Enum(StrictEnum):", + ] + for classs_str in cls: + assert classs_str in generated_code + + +def test_gen_shall_mass_spec_node( + clean_generated_file, mass_spectrometer_description: str +): + """Test generating code for the SHALL mass spectrometer node using a + real description.""" + + gen_code = GenNodeCode(path=str(clean_generated_file)) + + gen_code.from_json_describe(mass_spectrometer_description) + + gen_code.write_gen_node_class_file() + + gen_file = clean_generated_file / "genNodeClass.py" + assert gen_file.exists(), "Generated file should exist" + + generated_code = gen_file.read_text() + + # Trailing newlines in source descriptions should not produce broken split comments + assert "\n# ; Unit: (V)" not in generated_code + assert "\n# ; Unit: (%)" not in generated_code + + # Intentionally multiline descriptions should be rendered as multiline comments + assert "mid_descriptor: A[SignalRW[ndarray], ParameterType()]" in generated_code + assert "# Example:" in generated_code + assert "# {" in generated_code + assert "# mass: [12,15,28,75]," in generated_code + assert "# device: [FARADAY,SEM,SEM,SEM]" in generated_code + + # Long descriptions should be rendered fully below the declaration and wrapped + assert "resolution: A[SignalR[float], ParameterType()]\n" in generated_code + assert ( + "# The high mass peak width/valley adjustment used during set up and" + in generated_code + ) + assert ( + "# low masses and should be adjusted in conjunction with the Delta-M." + in generated_code + ) + + # Reparse generated code and verify multiline comments survive round-trip generation + roundtrip_gen = GenNodeCode(path=str(clean_generated_file)) + roundtrip_code = roundtrip_gen.generate_code() + + assert "mid_descriptor: A[SignalRW[ndarray], ParameterType()]" in roundtrip_code + assert "Example:" in roundtrip_code + assert "\n# ; Unit: (V)" not in roundtrip_code + assert "resolution: A[SignalR[float], ParameterType()]\n" in roundtrip_code + + +def test_gen_shall_mass_spec_node_no_impl( + clean_generated_file, mass_spectrometer_description_no_impl: str +): + """Test generating code for the SHALL mass spectrometer node using a + real description.""" + + gen_code = GenNodeCode(path=str(clean_generated_file)) + + gen_code.from_json_describe(mass_spectrometer_description_no_impl) + + gen_code.write_gen_node_class_file() diff --git a/tests/test_commands.py b/tests/test_commands.py index b67db60..8bff0de 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -12,48 +12,6 @@ ) -async def test_stop_cmd(cryo_sim, cryo_node_no_re: SECoPNodeDevice): - cryo: SECoPMoveableDevice = cryo_node_no_re.cryo - - await cryo.window.set(5) - await cryo.tolerance.set(1) - await cryo.ramp.set(20) - - stat = cryo.set(15) - - await asyncio.sleep(3) - - await cryo.stop(success=True) - - await stat - - assert cryo._stopped is False - - -async def test_stop_no_sucess_cmd(cryo_sim, cryo_node_no_re: SECoPNodeDevice): - cryo: SECoPMoveableDevice = cryo_node_no_re.cryo - - await cryo.window.set(5) - await cryo.tolerance.set(1) - await cryo.ramp.set(20) - - stat = cryo.set(15) - - rt_error = False - - await asyncio.sleep(3) - - try: - await cryo.stop(False) - await stat - - except RuntimeError: - rt_error = True - - assert cryo._stopped is True - assert rt_error is True - - async def test_struct_inp_cmd(nested_struct_sim, nested_node_no_re: SECoPNodeDevice): test_cmd: SECoPCMDDevice = nested_node_no_re.ophy_struct.test_cmd_CMD @@ -125,3 +83,37 @@ async def test_secop_triggering_cmd_dev( reading_res = await res.read() assert isinstance(reading_res.get(res.name)["value"], int) + + +async def test_stop_cmd(cryo_sim, cryo_node_no_re: SECoPNodeDevice): + cryo: SECoPMoveableDevice = cryo_node_no_re.cryo + + await cryo.window.set(5) + + await cryo.tolerance.set(1) + + await cryo.ramp.set(20) + + stat = cryo.set(15) + + await asyncio.sleep(3) + + # essentially a NOOP (stops are only passed through to SECoP on success=False) + await cryo.stop(success=True) + + assert cryo._stopped is False, "Move should not be stopped when success=True" + assert ( + cryo._success is True + ), "Move should be marked as successful when success=True" + assert ( + not stat.done + ), "Move should still be in progress after stop with success=True" + + # move is still going on + await cryo.stop(success=False) + assert cryo._stopped is True, "Move should be stopped when success=False" + assert ( + cryo._success is False + ), "Move should be marked as unsuccessful when success=False" + + await stat diff --git a/tests/test_dtype.py b/tests/test_dtype.py index 3718e35..769a0b8 100644 --- a/tests/test_dtype.py +++ b/tests/test_dtype.py @@ -194,7 +194,7 @@ def test_describe_str(start_dtype, expected_dtype_descr, expected_shape, max_dep @pytest.mark.parametrize( - "start_dtype,np_input,expected_output,type_checks", + "start_dtype,np_input,expected_output,type_checks,ophy_val", [ pytest.param( StructOf( @@ -228,6 +228,16 @@ def test_describe_str(start_dtype, expected_dtype_descr, expected_shape, max_dep and isinstance(val["scaled_val"], int) and isinstance(val["enum_val"], int) ), + np.array( + (3.14, "test", 42, 123, 1), + dtype=[ + ("float_val", "= '3.11'", - "python_full_version < '3.11'", -] +requires-python = ">=3.11" [[package]] name = "accessible-pygments" @@ -49,9 +45,6 @@ wheels = [ name = "astroid" version = "3.3.11" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] sdist = { url = "https://files.pythonhosted.org/packages/18/74/dfb75f9ccd592bbedb175d4a32fc643cf569d7c218508bfbd6ea7ef9c091/astroid-3.3.11.tar.gz", hash = "sha256:1e5a5011af2920c7c67a53f65d536d65bfa7116feeaf2354d8b94f29573bb0ce", size = 400439, upload-time = "2025-07-13T18:04:23.177Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/af/0f/3b8fdc946b4d9cc8cc1e8af42c4e409468c84441b933d037e101b3d72d86/astroid-3.3.11-py3-none-any.whl", hash = "sha256:54c760ae8322ece1abd213057c4b5bba7c49818853fc901ef09719a60dbf9dec", size = 275612, upload-time = "2025-07-13T18:04:21.07Z" }, @@ -76,21 +69,24 @@ wheels = [ ] [[package]] -name = "babel" -version = "2.17.0" +name = "autoflake" +version = "2.3.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } +dependencies = [ + { name = "pyflakes" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2a/cb/486f912d6171bc5748c311a2984a301f4e2d054833a1da78485866c71522/autoflake-2.3.1.tar.gz", hash = "sha256:c98b75dc5b0a86459c4f01a1d32ac7eb4338ec4317a4469515ff1e687ecd909e", size = 27642, upload-time = "2024-03-13T03:41:28.977Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, + { url = "https://files.pythonhosted.org/packages/a2/ee/3fd29bf416eb4f1c5579cf12bf393ae954099258abd7bde03c4f9716ef6b/autoflake-2.3.1-py3-none-any.whl", hash = "sha256:3ae7495db9084b7b32818b4140e6dc4fc280b712fb414f5b8fe57b0a8e85a840", size = 32483, upload-time = "2024-03-13T03:41:26.969Z" }, ] [[package]] -name = "backports-asyncio-runner" -version = "1.2.0" +name = "babel" +version = "2.17.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8e/ff/70dca7d7cb1cbc0edb2c6cc0c38b65cba36cccc491eca64cabd5fe7f8670/backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162", size = 69893, upload-time = "2025-07-02T02:27:15.685Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" }, + { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, ] [[package]] @@ -116,15 +112,9 @@ dependencies = [ { name = "packaging" }, { name = "pathspec" }, { name = "platformdirs" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d8/0d/cc2fb42b8c50d80143221515dd7e4766995bd07c56c9a3ed30baf080b6dc/black-24.10.0.tar.gz", hash = "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875", size = 645813, upload-time = "2024-10-07T19:20:50.361Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/f3/465c0eb5cddf7dbbfe1fecd9b875d1dcf51b88923cd2c1d7e9ab95c6336b/black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812", size = 1623211, upload-time = "2024-10-07T19:26:12.43Z" }, - { url = "https://files.pythonhosted.org/packages/df/57/b6d2da7d200773fdfcc224ffb87052cf283cec4d7102fab450b4a05996d8/black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea", size = 1457139, upload-time = "2024-10-07T19:25:06.453Z" }, - { url = "https://files.pythonhosted.org/packages/6e/c5/9023b7673904a5188f9be81f5e129fff69f51f5515655fbd1d5a4e80a47b/black-24.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:649fff99a20bd06c6f727d2a27f401331dc0cc861fb69cde910fe95b01b5928f", size = 1753774, upload-time = "2024-10-07T19:23:58.47Z" }, - { url = "https://files.pythonhosted.org/packages/e1/32/df7f18bd0e724e0d9748829765455d6643ec847b3f87e77456fc99d0edab/black-24.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe4d6476887de70546212c99ac9bd803d90b42fc4767f058a0baa895013fbb3e", size = 1414209, upload-time = "2024-10-07T19:24:42.54Z" }, { url = "https://files.pythonhosted.org/packages/c2/cc/7496bb63a9b06a954d3d0ac9fe7a73f3bf1cd92d7a58877c27f4ad1e9d41/black-24.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad", size = 1607468, upload-time = "2024-10-07T19:26:14.966Z" }, { url = "https://files.pythonhosted.org/packages/2b/e3/69a738fb5ba18b5422f50b4f143544c664d7da40f09c13969b2fd52900e0/black-24.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50", size = 1437270, upload-time = "2024-10-07T19:25:24.291Z" }, { url = "https://files.pythonhosted.org/packages/c9/9b/2db8045b45844665c720dcfe292fdaf2e49825810c0103e1191515fc101a/black-24.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392", size = 1737061, upload-time = "2024-10-07T19:23:52.18Z" }, @@ -150,8 +140,7 @@ dependencies = [ { name = "historydict" }, { name = "msgpack" }, { name = "msgpack-numpy" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy" }, { name = "opentelemetry-api" }, { name = "toolz" }, { name = "tqdm" }, @@ -180,18 +169,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" }, - { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" }, - { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" }, - { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" }, - { url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" }, - { url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079, upload-time = "2025-09-08T23:22:15.769Z" }, - { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475, upload-time = "2025-09-08T23:22:17.427Z" }, - { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" }, - { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" }, - { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" }, - { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" }, - { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" }, { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, @@ -268,22 +245,6 @@ version = "3.4.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/b8/6d51fc1d52cbd52cd4ccedd5b5b2f0f6a11bbf6765c782298b0f3e808541/charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", size = 209709, upload-time = "2025-10-14T04:40:11.385Z" }, - { url = "https://files.pythonhosted.org/packages/5c/af/1f9d7f7faafe2ddfb6f72a2e07a548a629c61ad510fe60f9630309908fef/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", size = 148814, upload-time = "2025-10-14T04:40:13.135Z" }, - { url = "https://files.pythonhosted.org/packages/79/3d/f2e3ac2bbc056ca0c204298ea4e3d9db9b4afe437812638759db2c976b5f/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", size = 144467, upload-time = "2025-10-14T04:40:14.728Z" }, - { url = "https://files.pythonhosted.org/packages/ec/85/1bf997003815e60d57de7bd972c57dc6950446a3e4ccac43bc3070721856/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", size = 162280, upload-time = "2025-10-14T04:40:16.14Z" }, - { url = "https://files.pythonhosted.org/packages/3e/8e/6aa1952f56b192f54921c436b87f2aaf7c7a7c3d0d1a765547d64fd83c13/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", size = 159454, upload-time = "2025-10-14T04:40:17.567Z" }, - { url = "https://files.pythonhosted.org/packages/36/3b/60cbd1f8e93aa25d1c669c649b7a655b0b5fb4c571858910ea9332678558/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", size = 153609, upload-time = "2025-10-14T04:40:19.08Z" }, - { url = "https://files.pythonhosted.org/packages/64/91/6a13396948b8fd3c4b4fd5bc74d045f5637d78c9675585e8e9fbe5636554/charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", size = 151849, upload-time = "2025-10-14T04:40:20.607Z" }, - { url = "https://files.pythonhosted.org/packages/b7/7a/59482e28b9981d105691e968c544cc0df3b7d6133152fb3dcdc8f135da7a/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", size = 151586, upload-time = "2025-10-14T04:40:21.719Z" }, - { url = "https://files.pythonhosted.org/packages/92/59/f64ef6a1c4bdd2baf892b04cd78792ed8684fbc48d4c2afe467d96b4df57/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", size = 145290, upload-time = "2025-10-14T04:40:23.069Z" }, - { url = "https://files.pythonhosted.org/packages/6b/63/3bf9f279ddfa641ffa1962b0db6a57a9c294361cc2f5fcac997049a00e9c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", size = 163663, upload-time = "2025-10-14T04:40:24.17Z" }, - { url = "https://files.pythonhosted.org/packages/ed/09/c9e38fc8fa9e0849b172b581fd9803bdf6e694041127933934184e19f8c3/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", size = 151964, upload-time = "2025-10-14T04:40:25.368Z" }, - { url = "https://files.pythonhosted.org/packages/d2/d1/d28b747e512d0da79d8b6a1ac18b7ab2ecfd81b2944c4c710e166d8dd09c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", size = 161064, upload-time = "2025-10-14T04:40:26.806Z" }, - { url = "https://files.pythonhosted.org/packages/bb/9a/31d62b611d901c3b9e5500c36aab0ff5eb442043fb3a1c254200d3d397d9/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", size = 155015, upload-time = "2025-10-14T04:40:28.284Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f3/107e008fa2bff0c8b9319584174418e5e5285fef32f79d8ee6a430d0039c/charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", size = 99792, upload-time = "2025-10-14T04:40:29.613Z" }, - { url = "https://files.pythonhosted.org/packages/eb/66/e396e8a408843337d7315bab30dbf106c38966f1819f123257f5520f8a96/charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", size = 107198, upload-time = "2025-10-14T04:40:30.644Z" }, - { url = "https://files.pythonhosted.org/packages/b5/58/01b4f815bf0312704c267f2ccb6e5d42bcc7752340cd487bc9f8c3710597/charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", size = 100262, upload-time = "2025-10-14T04:40:32.108Z" }, { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, @@ -407,85 +368,12 @@ lz4 = [ { name = "lz4" }, ] -[[package]] -name = "contourpy" -version = "1.3.2" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.11'", -] -dependencies = [ - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/66/54/eb9bfc647b19f2009dd5c7f5ec51c4e6ca831725f1aea7a993034f483147/contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54", size = 13466130, upload-time = "2025-04-15T17:47:53.79Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/12/a3/da4153ec8fe25d263aa48c1a4cbde7f49b59af86f0b6f7862788c60da737/contourpy-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba38e3f9f330af820c4b27ceb4b9c7feee5fe0493ea53a8720f4792667465934", size = 268551, upload-time = "2025-04-15T17:34:46.581Z" }, - { url = "https://files.pythonhosted.org/packages/2f/6c/330de89ae1087eb622bfca0177d32a7ece50c3ef07b28002de4757d9d875/contourpy-1.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc41ba0714aa2968d1f8674ec97504a8f7e334f48eeacebcaa6256213acb0989", size = 253399, upload-time = "2025-04-15T17:34:51.427Z" }, - { url = "https://files.pythonhosted.org/packages/c1/bd/20c6726b1b7f81a8bee5271bed5c165f0a8e1f572578a9d27e2ccb763cb2/contourpy-1.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9be002b31c558d1ddf1b9b415b162c603405414bacd6932d031c5b5a8b757f0d", size = 312061, upload-time = "2025-04-15T17:34:55.961Z" }, - { url = "https://files.pythonhosted.org/packages/22/fc/a9665c88f8a2473f823cf1ec601de9e5375050f1958cbb356cdf06ef1ab6/contourpy-1.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8d2e74acbcba3bfdb6d9d8384cdc4f9260cae86ed9beee8bd5f54fee49a430b9", size = 351956, upload-time = "2025-04-15T17:35:00.992Z" }, - { url = "https://files.pythonhosted.org/packages/25/eb/9f0a0238f305ad8fb7ef42481020d6e20cf15e46be99a1fcf939546a177e/contourpy-1.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e259bced5549ac64410162adc973c5e2fb77f04df4a439d00b478e57a0e65512", size = 320872, upload-time = "2025-04-15T17:35:06.177Z" }, - { url = "https://files.pythonhosted.org/packages/32/5c/1ee32d1c7956923202f00cf8d2a14a62ed7517bdc0ee1e55301227fc273c/contourpy-1.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad687a04bc802cbe8b9c399c07162a3c35e227e2daccf1668eb1f278cb698631", size = 325027, upload-time = "2025-04-15T17:35:11.244Z" }, - { url = "https://files.pythonhosted.org/packages/83/bf/9baed89785ba743ef329c2b07fd0611d12bfecbedbdd3eeecf929d8d3b52/contourpy-1.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cdd22595308f53ef2f891040ab2b93d79192513ffccbd7fe19be7aa773a5e09f", size = 1306641, upload-time = "2025-04-15T17:35:26.701Z" }, - { url = "https://files.pythonhosted.org/packages/d4/cc/74e5e83d1e35de2d28bd97033426b450bc4fd96e092a1f7a63dc7369b55d/contourpy-1.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b4f54d6a2defe9f257327b0f243612dd051cc43825587520b1bf74a31e2f6ef2", size = 1374075, upload-time = "2025-04-15T17:35:43.204Z" }, - { url = "https://files.pythonhosted.org/packages/0c/42/17f3b798fd5e033b46a16f8d9fcb39f1aba051307f5ebf441bad1ecf78f8/contourpy-1.3.2-cp310-cp310-win32.whl", hash = "sha256:f939a054192ddc596e031e50bb13b657ce318cf13d264f095ce9db7dc6ae81c0", size = 177534, upload-time = "2025-04-15T17:35:46.554Z" }, - { url = "https://files.pythonhosted.org/packages/54/ec/5162b8582f2c994721018d0c9ece9dc6ff769d298a8ac6b6a652c307e7df/contourpy-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c440093bbc8fc21c637c03bafcbef95ccd963bc6e0514ad887932c18ca2a759a", size = 221188, upload-time = "2025-04-15T17:35:50.064Z" }, - { url = "https://files.pythonhosted.org/packages/b3/b9/ede788a0b56fc5b071639d06c33cb893f68b1178938f3425debebe2dab78/contourpy-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a37a2fb93d4df3fc4c0e363ea4d16f83195fc09c891bc8ce072b9d084853445", size = 269636, upload-time = "2025-04-15T17:35:54.473Z" }, - { url = "https://files.pythonhosted.org/packages/e6/75/3469f011d64b8bbfa04f709bfc23e1dd71be54d05b1b083be9f5b22750d1/contourpy-1.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b7cd50c38f500bbcc9b6a46643a40e0913673f869315d8e70de0438817cb7773", size = 254636, upload-time = "2025-04-15T17:35:58.283Z" }, - { url = "https://files.pythonhosted.org/packages/8d/2f/95adb8dae08ce0ebca4fd8e7ad653159565d9739128b2d5977806656fcd2/contourpy-1.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6658ccc7251a4433eebd89ed2672c2ed96fba367fd25ca9512aa92a4b46c4f1", size = 313053, upload-time = "2025-04-15T17:36:03.235Z" }, - { url = "https://files.pythonhosted.org/packages/c3/a6/8ccf97a50f31adfa36917707fe39c9a0cbc24b3bbb58185577f119736cc9/contourpy-1.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:70771a461aaeb335df14deb6c97439973d253ae70660ca085eec25241137ef43", size = 352985, upload-time = "2025-04-15T17:36:08.275Z" }, - { url = "https://files.pythonhosted.org/packages/1d/b6/7925ab9b77386143f39d9c3243fdd101621b4532eb126743201160ffa7e6/contourpy-1.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65a887a6e8c4cd0897507d814b14c54a8c2e2aa4ac9f7686292f9769fcf9a6ab", size = 323750, upload-time = "2025-04-15T17:36:13.29Z" }, - { url = "https://files.pythonhosted.org/packages/c2/f3/20c5d1ef4f4748e52d60771b8560cf00b69d5c6368b5c2e9311bcfa2a08b/contourpy-1.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3859783aefa2b8355697f16642695a5b9792e7a46ab86da1118a4a23a51a33d7", size = 326246, upload-time = "2025-04-15T17:36:18.329Z" }, - { url = "https://files.pythonhosted.org/packages/8c/e5/9dae809e7e0b2d9d70c52b3d24cba134dd3dad979eb3e5e71f5df22ed1f5/contourpy-1.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:eab0f6db315fa4d70f1d8ab514e527f0366ec021ff853d7ed6a2d33605cf4b83", size = 1308728, upload-time = "2025-04-15T17:36:33.878Z" }, - { url = "https://files.pythonhosted.org/packages/e2/4a/0058ba34aeea35c0b442ae61a4f4d4ca84d6df8f91309bc2d43bb8dd248f/contourpy-1.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d91a3ccc7fea94ca0acab82ceb77f396d50a1f67412efe4c526f5d20264e6ecd", size = 1375762, upload-time = "2025-04-15T17:36:51.295Z" }, - { url = "https://files.pythonhosted.org/packages/09/33/7174bdfc8b7767ef2c08ed81244762d93d5c579336fc0b51ca57b33d1b80/contourpy-1.3.2-cp311-cp311-win32.whl", hash = "sha256:1c48188778d4d2f3d48e4643fb15d8608b1d01e4b4d6b0548d9b336c28fc9b6f", size = 178196, upload-time = "2025-04-15T17:36:55.002Z" }, - { url = "https://files.pythonhosted.org/packages/5e/fe/4029038b4e1c4485cef18e480b0e2cd2d755448bb071eb9977caac80b77b/contourpy-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:5ebac872ba09cb8f2131c46b8739a7ff71de28a24c869bcad554477eb089a878", size = 222017, upload-time = "2025-04-15T17:36:58.576Z" }, - { url = "https://files.pythonhosted.org/packages/34/f7/44785876384eff370c251d58fd65f6ad7f39adce4a093c934d4a67a7c6b6/contourpy-1.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4caf2bcd2969402bf77edc4cb6034c7dd7c0803213b3523f111eb7460a51b8d2", size = 271580, upload-time = "2025-04-15T17:37:03.105Z" }, - { url = "https://files.pythonhosted.org/packages/93/3b/0004767622a9826ea3d95f0e9d98cd8729015768075d61f9fea8eeca42a8/contourpy-1.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:82199cb78276249796419fe36b7386bd8d2cc3f28b3bc19fe2454fe2e26c4c15", size = 255530, upload-time = "2025-04-15T17:37:07.026Z" }, - { url = "https://files.pythonhosted.org/packages/e7/bb/7bd49e1f4fa805772d9fd130e0d375554ebc771ed7172f48dfcd4ca61549/contourpy-1.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106fab697af11456fcba3e352ad50effe493a90f893fca6c2ca5c033820cea92", size = 307688, upload-time = "2025-04-15T17:37:11.481Z" }, - { url = "https://files.pythonhosted.org/packages/fc/97/e1d5dbbfa170725ef78357a9a0edc996b09ae4af170927ba8ce977e60a5f/contourpy-1.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d14f12932a8d620e307f715857107b1d1845cc44fdb5da2bc8e850f5ceba9f87", size = 347331, upload-time = "2025-04-15T17:37:18.212Z" }, - { url = "https://files.pythonhosted.org/packages/6f/66/e69e6e904f5ecf6901be3dd16e7e54d41b6ec6ae3405a535286d4418ffb4/contourpy-1.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:532fd26e715560721bb0d5fc7610fce279b3699b018600ab999d1be895b09415", size = 318963, upload-time = "2025-04-15T17:37:22.76Z" }, - { url = "https://files.pythonhosted.org/packages/a8/32/b8a1c8965e4f72482ff2d1ac2cd670ce0b542f203c8e1d34e7c3e6925da7/contourpy-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b383144cf2d2c29f01a1e8170f50dacf0eac02d64139dcd709a8ac4eb3cfe", size = 323681, upload-time = "2025-04-15T17:37:33.001Z" }, - { url = "https://files.pythonhosted.org/packages/30/c6/12a7e6811d08757c7162a541ca4c5c6a34c0f4e98ef2b338791093518e40/contourpy-1.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c49f73e61f1f774650a55d221803b101d966ca0c5a2d6d5e4320ec3997489441", size = 1308674, upload-time = "2025-04-15T17:37:48.64Z" }, - { url = "https://files.pythonhosted.org/packages/2a/8a/bebe5a3f68b484d3a2b8ffaf84704b3e343ef1addea528132ef148e22b3b/contourpy-1.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d80b2c0300583228ac98d0a927a1ba6a2ba6b8a742463c564f1d419ee5b211e", size = 1380480, upload-time = "2025-04-15T17:38:06.7Z" }, - { url = "https://files.pythonhosted.org/packages/34/db/fcd325f19b5978fb509a7d55e06d99f5f856294c1991097534360b307cf1/contourpy-1.3.2-cp312-cp312-win32.whl", hash = "sha256:90df94c89a91b7362e1142cbee7568f86514412ab8a2c0d0fca72d7e91b62912", size = 178489, upload-time = "2025-04-15T17:38:10.338Z" }, - { url = "https://files.pythonhosted.org/packages/01/c8/fadd0b92ffa7b5eb5949bf340a63a4a496a6930a6c37a7ba0f12acb076d6/contourpy-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c942a01d9163e2e5cfb05cb66110121b8d07ad438a17f9e766317bcb62abf73", size = 223042, upload-time = "2025-04-15T17:38:14.239Z" }, - { url = "https://files.pythonhosted.org/packages/2e/61/5673f7e364b31e4e7ef6f61a4b5121c5f170f941895912f773d95270f3a2/contourpy-1.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:de39db2604ae755316cb5967728f4bea92685884b1e767b7c24e983ef5f771cb", size = 271630, upload-time = "2025-04-15T17:38:19.142Z" }, - { url = "https://files.pythonhosted.org/packages/ff/66/a40badddd1223822c95798c55292844b7e871e50f6bfd9f158cb25e0bd39/contourpy-1.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3f9e896f447c5c8618f1edb2bafa9a4030f22a575ec418ad70611450720b5b08", size = 255670, upload-time = "2025-04-15T17:38:23.688Z" }, - { url = "https://files.pythonhosted.org/packages/1e/c7/cf9fdee8200805c9bc3b148f49cb9482a4e3ea2719e772602a425c9b09f8/contourpy-1.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71e2bd4a1c4188f5c2b8d274da78faab884b59df20df63c34f74aa1813c4427c", size = 306694, upload-time = "2025-04-15T17:38:28.238Z" }, - { url = "https://files.pythonhosted.org/packages/dd/e7/ccb9bec80e1ba121efbffad7f38021021cda5be87532ec16fd96533bb2e0/contourpy-1.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de425af81b6cea33101ae95ece1f696af39446db9682a0b56daaa48cfc29f38f", size = 345986, upload-time = "2025-04-15T17:38:33.502Z" }, - { url = "https://files.pythonhosted.org/packages/dc/49/ca13bb2da90391fa4219fdb23b078d6065ada886658ac7818e5441448b78/contourpy-1.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:977e98a0e0480d3fe292246417239d2d45435904afd6d7332d8455981c408b85", size = 318060, upload-time = "2025-04-15T17:38:38.672Z" }, - { url = "https://files.pythonhosted.org/packages/c8/65/5245ce8c548a8422236c13ffcdcdada6a2a812c361e9e0c70548bb40b661/contourpy-1.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:434f0adf84911c924519d2b08fc10491dd282b20bdd3fa8f60fd816ea0b48841", size = 322747, upload-time = "2025-04-15T17:38:43.712Z" }, - { url = "https://files.pythonhosted.org/packages/72/30/669b8eb48e0a01c660ead3752a25b44fdb2e5ebc13a55782f639170772f9/contourpy-1.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c66c4906cdbc50e9cba65978823e6e00b45682eb09adbb78c9775b74eb222422", size = 1308895, upload-time = "2025-04-15T17:39:00.224Z" }, - { url = "https://files.pythonhosted.org/packages/05/5a/b569f4250decee6e8d54498be7bdf29021a4c256e77fe8138c8319ef8eb3/contourpy-1.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8b7fc0cd78ba2f4695fd0a6ad81a19e7e3ab825c31b577f384aa9d7817dc3bef", size = 1379098, upload-time = "2025-04-15T17:43:29.649Z" }, - { url = "https://files.pythonhosted.org/packages/19/ba/b227c3886d120e60e41b28740ac3617b2f2b971b9f601c835661194579f1/contourpy-1.3.2-cp313-cp313-win32.whl", hash = "sha256:15ce6ab60957ca74cff444fe66d9045c1fd3e92c8936894ebd1f3eef2fff075f", size = 178535, upload-time = "2025-04-15T17:44:44.532Z" }, - { url = "https://files.pythonhosted.org/packages/12/6e/2fed56cd47ca739b43e892707ae9a13790a486a3173be063681ca67d2262/contourpy-1.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e1578f7eafce927b168752ed7e22646dad6cd9bca673c60bff55889fa236ebf9", size = 223096, upload-time = "2025-04-15T17:44:48.194Z" }, - { url = "https://files.pythonhosted.org/packages/54/4c/e76fe2a03014a7c767d79ea35c86a747e9325537a8b7627e0e5b3ba266b4/contourpy-1.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0475b1f6604896bc7c53bb070e355e9321e1bc0d381735421a2d2068ec56531f", size = 285090, upload-time = "2025-04-15T17:43:34.084Z" }, - { url = "https://files.pythonhosted.org/packages/7b/e2/5aba47debd55d668e00baf9651b721e7733975dc9fc27264a62b0dd26eb8/contourpy-1.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c85bb486e9be652314bb5b9e2e3b0d1b2e643d5eec4992c0fbe8ac71775da739", size = 268643, upload-time = "2025-04-15T17:43:38.626Z" }, - { url = "https://files.pythonhosted.org/packages/a1/37/cd45f1f051fe6230f751cc5cdd2728bb3a203f5619510ef11e732109593c/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:745b57db7758f3ffc05a10254edd3182a2a83402a89c00957a8e8a22f5582823", size = 310443, upload-time = "2025-04-15T17:43:44.522Z" }, - { url = "https://files.pythonhosted.org/packages/8b/a2/36ea6140c306c9ff6dd38e3bcec80b3b018474ef4d17eb68ceecd26675f4/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:970e9173dbd7eba9b4e01aab19215a48ee5dd3f43cef736eebde064a171f89a5", size = 349865, upload-time = "2025-04-15T17:43:49.545Z" }, - { url = "https://files.pythonhosted.org/packages/95/b7/2fc76bc539693180488f7b6cc518da7acbbb9e3b931fd9280504128bf956/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6c4639a9c22230276b7bffb6a850dfc8258a2521305e1faefe804d006b2e532", size = 321162, upload-time = "2025-04-15T17:43:54.203Z" }, - { url = "https://files.pythonhosted.org/packages/f4/10/76d4f778458b0aa83f96e59d65ece72a060bacb20cfbee46cf6cd5ceba41/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc829960f34ba36aad4302e78eabf3ef16a3a100863f0d4eeddf30e8a485a03b", size = 327355, upload-time = "2025-04-15T17:44:01.025Z" }, - { url = "https://files.pythonhosted.org/packages/43/a3/10cf483ea683f9f8ab096c24bad3cce20e0d1dd9a4baa0e2093c1c962d9d/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d32530b534e986374fc19eaa77fcb87e8a99e5431499949b828312bdcd20ac52", size = 1307935, upload-time = "2025-04-15T17:44:17.322Z" }, - { url = "https://files.pythonhosted.org/packages/78/73/69dd9a024444489e22d86108e7b913f3528f56cfc312b5c5727a44188471/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e298e7e70cf4eb179cc1077be1c725b5fd131ebc81181bf0c03525c8abc297fd", size = 1372168, upload-time = "2025-04-15T17:44:33.43Z" }, - { url = "https://files.pythonhosted.org/packages/0f/1b/96d586ccf1b1a9d2004dd519b25fbf104a11589abfd05484ff12199cca21/contourpy-1.3.2-cp313-cp313t-win32.whl", hash = "sha256:d0e589ae0d55204991450bb5c23f571c64fe43adaa53f93fc902a84c96f52fe1", size = 189550, upload-time = "2025-04-15T17:44:37.092Z" }, - { url = "https://files.pythonhosted.org/packages/b0/e6/6000d0094e8a5e32ad62591c8609e269febb6e4db83a1c75ff8868b42731/contourpy-1.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:78e9253c3de756b3f6a5174d024c4835acd59eb3f8e2ca13e775dbffe1558f69", size = 238214, upload-time = "2025-04-15T17:44:40.827Z" }, - { url = "https://files.pythonhosted.org/packages/33/05/b26e3c6ecc05f349ee0013f0bb850a761016d89cec528a98193a48c34033/contourpy-1.3.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fd93cc7f3139b6dd7aab2f26a90dde0aa9fc264dbf70f6740d498a70b860b82c", size = 265681, upload-time = "2025-04-15T17:44:59.314Z" }, - { url = "https://files.pythonhosted.org/packages/2b/25/ac07d6ad12affa7d1ffed11b77417d0a6308170f44ff20fa1d5aa6333f03/contourpy-1.3.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:107ba8a6a7eec58bb475329e6d3b95deba9440667c4d62b9b6063942b61d7f16", size = 315101, upload-time = "2025-04-15T17:45:04.165Z" }, - { url = "https://files.pythonhosted.org/packages/8f/4d/5bb3192bbe9d3f27e3061a6a8e7733c9120e203cb8515767d30973f71030/contourpy-1.3.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ded1706ed0c1049224531b81128efbd5084598f18d8a2d9efae833edbd2b40ad", size = 220599, upload-time = "2025-04-15T17:45:08.456Z" }, - { url = "https://files.pythonhosted.org/packages/ff/c0/91f1215d0d9f9f343e4773ba6c9b89e8c0cc7a64a6263f21139da639d848/contourpy-1.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5f5964cdad279256c084b69c3f412b7801e15356b16efa9d78aa974041903da0", size = 266807, upload-time = "2025-04-15T17:45:15.535Z" }, - { url = "https://files.pythonhosted.org/packages/d4/79/6be7e90c955c0487e7712660d6cead01fa17bff98e0ea275737cc2bc8e71/contourpy-1.3.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49b65a95d642d4efa8f64ba12558fcb83407e58a2dfba9d796d77b63ccfcaff5", size = 318729, upload-time = "2025-04-15T17:45:20.166Z" }, - { url = "https://files.pythonhosted.org/packages/87/68/7f46fb537958e87427d98a4074bcde4b67a70b04900cfc5ce29bc2f556c1/contourpy-1.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8c5acb8dddb0752bf252e01a3035b21443158910ac16a3b0d20e7fed7d534ce5", size = 221791, upload-time = "2025-04-15T17:45:24.794Z" }, -] - [[package]] name = "contourpy" version = "1.3.3" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.11'", -] dependencies = [ - { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy" }, ] sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } wheels = [ @@ -568,18 +456,6 @@ version = "7.12.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/89/26/4a96807b193b011588099c3b5c89fbb05294e5b90e71018e065465f34eb6/coverage-7.12.0.tar.gz", hash = "sha256:fc11e0a4e372cb5f282f16ef90d4a585034050ccda536451901abfb19a57f40c", size = 819341, upload-time = "2025-11-18T13:34:20.766Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/26/4a/0dc3de1c172d35abe512332cfdcc43211b6ebce629e4cc42e6cd25ed8f4d/coverage-7.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:32b75c2ba3f324ee37af3ccee5b30458038c50b349ad9b88cee85096132a575b", size = 217409, upload-time = "2025-11-18T13:31:53.122Z" }, - { url = "https://files.pythonhosted.org/packages/01/c3/086198b98db0109ad4f84241e8e9ea7e5fb2db8c8ffb787162d40c26cc76/coverage-7.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cb2a1b6ab9fe833714a483a915de350abc624a37149649297624c8d57add089c", size = 217927, upload-time = "2025-11-18T13:31:54.458Z" }, - { url = "https://files.pythonhosted.org/packages/5d/5f/34614dbf5ce0420828fc6c6f915126a0fcb01e25d16cf141bf5361e6aea6/coverage-7.12.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5734b5d913c3755e72f70bf6cc37a0518d4f4745cde760c5d8e12005e62f9832", size = 244678, upload-time = "2025-11-18T13:31:55.805Z" }, - { url = "https://files.pythonhosted.org/packages/55/7b/6b26fb32e8e4a6989ac1d40c4e132b14556131493b1d06bc0f2be169c357/coverage-7.12.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b527a08cdf15753279b7afb2339a12073620b761d79b81cbe2cdebdb43d90daa", size = 246507, upload-time = "2025-11-18T13:31:57.05Z" }, - { url = "https://files.pythonhosted.org/packages/06/42/7d70e6603d3260199b90fb48b537ca29ac183d524a65cc31366b2e905fad/coverage-7.12.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9bb44c889fb68004e94cab71f6a021ec83eac9aeabdbb5a5a88821ec46e1da73", size = 248366, upload-time = "2025-11-18T13:31:58.362Z" }, - { url = "https://files.pythonhosted.org/packages/2d/4a/d86b837923878424c72458c5b25e899a3c5ca73e663082a915f5b3c4d749/coverage-7.12.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4b59b501455535e2e5dde5881739897967b272ba25988c89145c12d772810ccb", size = 245366, upload-time = "2025-11-18T13:31:59.572Z" }, - { url = "https://files.pythonhosted.org/packages/e6/c2/2adec557e0aa9721875f06ced19730fdb7fc58e31b02b5aa56f2ebe4944d/coverage-7.12.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d8842f17095b9868a05837b7b1b73495293091bed870e099521ada176aa3e00e", size = 246408, upload-time = "2025-11-18T13:32:00.784Z" }, - { url = "https://files.pythonhosted.org/packages/5a/4b/8bd1f1148260df11c618e535fdccd1e5aaf646e55b50759006a4f41d8a26/coverage-7.12.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c5a6f20bf48b8866095c6820641e7ffbe23f2ac84a2efc218d91235e404c7777", size = 244416, upload-time = "2025-11-18T13:32:01.963Z" }, - { url = "https://files.pythonhosted.org/packages/0e/13/3a248dd6a83df90414c54a4e121fd081fb20602ca43955fbe1d60e2312a9/coverage-7.12.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:5f3738279524e988d9da2893f307c2093815c623f8d05a8f79e3eff3a7a9e553", size = 244681, upload-time = "2025-11-18T13:32:03.408Z" }, - { url = "https://files.pythonhosted.org/packages/76/30/aa833827465a5e8c938935f5d91ba055f70516941078a703740aaf1aa41f/coverage-7.12.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0d68c1f7eabbc8abe582d11fa393ea483caf4f44b0af86881174769f185c94d", size = 245300, upload-time = "2025-11-18T13:32:04.686Z" }, - { url = "https://files.pythonhosted.org/packages/38/24/f85b3843af1370fb3739fa7571819b71243daa311289b31214fe3e8c9d68/coverage-7.12.0-cp310-cp310-win32.whl", hash = "sha256:7670d860e18b1e3ee5930b17a7d55ae6287ec6e55d9799982aa103a2cc1fa2ef", size = 220008, upload-time = "2025-11-18T13:32:05.806Z" }, - { url = "https://files.pythonhosted.org/packages/3a/a2/c7da5b9566f7164db9eefa133d17761ecb2c2fde9385d754e5b5c80f710d/coverage-7.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:f999813dddeb2a56aab5841e687b68169da0d3f6fc78ccf50952fa2463746022", size = 220943, upload-time = "2025-11-18T13:32:07.166Z" }, { url = "https://files.pythonhosted.org/packages/5a/0c/0dfe7f0487477d96432e4815537263363fb6dd7289743a796e8e51eabdf2/coverage-7.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa124a3683d2af98bd9d9c2bfa7a5076ca7e5ab09fdb96b81fa7d89376ae928f", size = 217535, upload-time = "2025-11-18T13:32:08.812Z" }, { url = "https://files.pythonhosted.org/packages/9b/f5/f9a4a053a5bbff023d3bec259faac8f11a1e5a6479c2ccf586f910d8dac7/coverage-7.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d93fbf446c31c0140208dcd07c5d882029832e8ed7891a39d6d44bd65f2316c3", size = 218044, upload-time = "2025-11-18T13:32:10.329Z" }, { url = "https://files.pythonhosted.org/packages/95/c5/84fc3697c1fa10cd8571919bf9693f693b7373278daaf3b73e328d502bc8/coverage-7.12.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:52ca620260bd8cd6027317bdd8b8ba929be1d741764ee765b42c4d79a408601e", size = 248440, upload-time = "2025-11-18T13:32:12.536Z" }, @@ -681,10 +557,6 @@ version = "1.8.17" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/15/ad/71e708ff4ca377c4230530d6a7aa7992592648c122a2cd2b321cf8b35a76/debugpy-1.8.17.tar.gz", hash = "sha256:fd723b47a8c08892b1a16b2c6239a8b96637c62a59b94bb5dab4bac592a58a8e", size = 1644129, upload-time = "2025-09-17T16:33:20.633Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/38/36/b57c6e818d909f6e59c0182252921cf435e0951126a97e11de37e72ab5e1/debugpy-1.8.17-cp310-cp310-macosx_15_0_x86_64.whl", hash = "sha256:c41d2ce8bbaddcc0009cc73f65318eedfa3dbc88a8298081deb05389f1ab5542", size = 2098021, upload-time = "2025-09-17T16:33:22.556Z" }, - { url = "https://files.pythonhosted.org/packages/be/01/0363c7efdd1e9febd090bb13cee4fb1057215b157b2979a4ca5ccb678217/debugpy-1.8.17-cp310-cp310-manylinux_2_34_x86_64.whl", hash = "sha256:1440fd514e1b815edd5861ca394786f90eb24960eb26d6f7200994333b1d79e3", size = 3087399, upload-time = "2025-09-17T16:33:24.292Z" }, - { url = "https://files.pythonhosted.org/packages/79/bc/4a984729674aa9a84856650438b9665f9a1d5a748804ac6f37932ce0d4aa/debugpy-1.8.17-cp310-cp310-win32.whl", hash = "sha256:3a32c0af575749083d7492dc79f6ab69f21b2d2ad4cd977a958a07d5865316e4", size = 5230292, upload-time = "2025-09-17T16:33:26.137Z" }, - { url = "https://files.pythonhosted.org/packages/5d/19/2b9b3092d0cf81a5aa10c86271999453030af354d1a5a7d6e34c574515d7/debugpy-1.8.17-cp310-cp310-win_amd64.whl", hash = "sha256:a3aad0537cf4d9c1996434be68c6c9a6d233ac6f76c2a482c7803295b4e4f99a", size = 5261885, upload-time = "2025-09-17T16:33:27.592Z" }, { url = "https://files.pythonhosted.org/packages/d8/53/3af72b5c159278c4a0cf4cffa518675a0e73bdb7d1cac0239b815502d2ce/debugpy-1.8.17-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:d3fce3f0e3de262a3b67e69916d001f3e767661c6e1ee42553009d445d1cd840", size = 2207154, upload-time = "2025-09-17T16:33:29.457Z" }, { url = "https://files.pythonhosted.org/packages/8f/6d/204f407df45600e2245b4a39860ed4ba32552330a0b3f5f160ae4cc30072/debugpy-1.8.17-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:c6bdf134457ae0cac6fb68205776be635d31174eeac9541e1d0c062165c6461f", size = 3170322, upload-time = "2025-09-17T16:33:30.837Z" }, { url = "https://files.pythonhosted.org/packages/f2/13/1b8f87d39cf83c6b713de2620c31205299e6065622e7dd37aff4808dd410/debugpy-1.8.17-cp311-cp311-win32.whl", hash = "sha256:e79a195f9e059edfe5d8bf6f3749b2599452d3e9380484cd261f6b7cd2c7c4da", size = 5155078, upload-time = "2025-09-17T16:33:33.331Z" }, @@ -738,8 +610,7 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "importlib-resources" }, { name = "jsonschema" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy" }, { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f9/5a/de03f05fdbc4377db89fa6daf243e89370f9bcf2d21ad96b54d4549a74ed/event_model-1.23.1.tar.gz", hash = "sha256:5bb70fd8c7f345aa32afe561aff5a306b2c8a19cbdc3066b736643c8092ddaab", size = 185271, upload-time = "2025-08-28T13:26:38.647Z" } @@ -747,18 +618,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fb/32/e31e3363bf48ad2ba80b644b01ad9676ce154f1b755950de81eb4ed5b6bd/event_model-1.23.1-py3-none-any.whl", hash = "sha256:e0b951b829cebcf3879beff238bb370fd997d315856bc5d5ac2a66202b854958", size = 77057, upload-time = "2025-08-28T13:26:37.228Z" }, ] -[[package]] -name = "exceptiongroup" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, -] - [[package]] name = "executing" version = "2.2.1" @@ -821,14 +680,6 @@ version = "4.61.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/33/f9/0e84d593c0e12244150280a630999835a64f2852276161b62a0f98318de0/fonttools-4.61.0.tar.gz", hash = "sha256:ec520a1f0c7758d7a858a00f090c1745f6cde6a7c5e76fb70ea4044a15f712e7", size = 3561884, upload-time = "2025-11-28T17:05:49.491Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/29/f3/91bba2721fb173fc68e09d15b6ccf3ad4f83d127fbff579be7e5984888a6/fonttools-4.61.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:dc25a4a9c1225653e4431a9413d0381b1c62317b0f543bdcec24e1991f612f33", size = 2850151, upload-time = "2025-11-28T17:04:14.214Z" }, - { url = "https://files.pythonhosted.org/packages/f5/8c/a1691dec01038ac7e7bb3ab83300dcc5087b11d8f48640928c02a873eb92/fonttools-4.61.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b493c32d2555e9944ec1b911ea649ff8f01a649ad9cba6c118d6798e932b3f0", size = 2389769, upload-time = "2025-11-28T17:04:16.443Z" }, - { url = "https://files.pythonhosted.org/packages/2d/dd/5bb369a44319d92ba25612511eb8ed2a6fa75239979e0388907525626902/fonttools-4.61.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ad751319dc532a79bdf628b8439af167181b4210a0cd28a8935ca615d9fdd727", size = 4893189, upload-time = "2025-11-28T17:04:18.398Z" }, - { url = "https://files.pythonhosted.org/packages/5e/02/51373fa8846bd22bb54e5efb30a824b417b058083f775a194a432f21a45f/fonttools-4.61.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2de14557d113faa5fb519f7f29c3abe4d69c17fe6a5a2595cc8cda7338029219", size = 4854415, upload-time = "2025-11-28T17:04:20.421Z" }, - { url = "https://files.pythonhosted.org/packages/8b/64/9cdbbb804577a7e6191448851c57e6a36eb02aa4bf6a9668b528c968e44e/fonttools-4.61.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:59587bbe455dbdf75354a9dbca1697a35a8903e01fab4248d6b98a17032cee52", size = 4870927, upload-time = "2025-11-28T17:04:22.625Z" }, - { url = "https://files.pythonhosted.org/packages/92/68/e40b22919dc96dc30a70b58fec609ab85112de950bdecfadf8dd478c5a88/fonttools-4.61.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:46cb3d9279f758ac0cf671dc3482da877104b65682679f01b246515db03dbb72", size = 4988674, upload-time = "2025-11-28T17:04:24.675Z" }, - { url = "https://files.pythonhosted.org/packages/9b/5c/e857349ce8aedb2451b9448282e86544b2b7f1c8b10ea0fe49b7cb369b72/fonttools-4.61.0-cp310-cp310-win32.whl", hash = "sha256:58b4f1b78dfbfe855bb8a6801b31b8cdcca0e2847ec769ad8e0b0b692832dd3b", size = 1497663, upload-time = "2025-11-28T17:04:26.598Z" }, - { url = "https://files.pythonhosted.org/packages/f9/0c/62961d5fe6f764d6cbc387ef2c001f5f610808c7aded837409836c0b3e7c/fonttools-4.61.0-cp310-cp310-win_amd64.whl", hash = "sha256:68704a8bbe0b61976262b255e90cde593dc0fe3676542d9b4d846bad2a890a76", size = 1546143, upload-time = "2025-11-28T17:04:28.432Z" }, { url = "https://files.pythonhosted.org/packages/fd/be/5aa89cdddf2863d8afbdc19eb8ec5d8d35d40eeeb8e6cf52c5ff1c2dbd33/fonttools-4.61.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a32a16951cbf113d38f1dd8551b277b6e06e0f6f776fece0f99f746d739e1be3", size = 2847553, upload-time = "2025-11-28T17:04:30.539Z" }, { url = "https://files.pythonhosted.org/packages/0d/3e/6ff643b07cead1236a534f51291ae2981721cf419135af5b740c002a66dd/fonttools-4.61.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:328a9c227984bebaf69f3ac9062265f8f6acc7ddf2e4e344c63358579af0aa3d", size = 2388298, upload-time = "2025-11-28T17:04:32.161Z" }, { url = "https://files.pythonhosted.org/packages/c3/15/fca8dfbe7b482e6f240b1aad0ed7c6e2e75e7a28efa3d3a03b570617b5e5/fonttools-4.61.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2f0bafc8a3b3749c69cc610e5aa3da832d39c2a37a68f03d18ec9a02ecaac04a", size = 5054133, upload-time = "2025-11-28T17:04:34.035Z" }, @@ -961,8 +812,7 @@ dependencies = [ { name = "appnope", marker = "sys_platform == 'darwin'" }, { name = "comm" }, { name = "debugpy" }, - { name = "ipython", version = "8.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "ipython", version = "9.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "ipython" }, { name = "jupyter-client" }, { name = "jupyter-core" }, { name = "matplotlib-inline" }, @@ -978,50 +828,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a3/17/20c2552266728ceba271967b87919664ecc0e33efca29c3efc6baf88c5f9/ipykernel-7.1.0-py3-none-any.whl", hash = "sha256:763b5ec6c5b7776f6a8d7ce09b267693b4e5ce75cb50ae696aaefb3c85e1ea4c", size = 117968, upload-time = "2025-10-27T09:46:37.805Z" }, ] -[[package]] -name = "ipython" -version = "8.37.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.11'", -] -dependencies = [ - { name = "colorama", marker = "python_full_version < '3.11' and sys_platform == 'win32'" }, - { name = "decorator", marker = "python_full_version < '3.11'" }, - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "jedi", marker = "python_full_version < '3.11'" }, - { name = "matplotlib-inline", marker = "python_full_version < '3.11'" }, - { name = "pexpect", marker = "python_full_version < '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "prompt-toolkit", marker = "python_full_version < '3.11'" }, - { name = "pygments", marker = "python_full_version < '3.11'" }, - { name = "stack-data", marker = "python_full_version < '3.11'" }, - { name = "traitlets", marker = "python_full_version < '3.11'" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/85/31/10ac88f3357fc276dc8a64e8880c82e80e7459326ae1d0a211b40abf6665/ipython-8.37.0.tar.gz", hash = "sha256:ca815841e1a41a1e6b73a0b08f3038af9b2252564d01fc405356d34033012216", size = 5606088, upload-time = "2025-05-31T16:39:09.613Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/91/d0/274fbf7b0b12643cbbc001ce13e6a5b1607ac4929d1b11c72460152c9fc3/ipython-8.37.0-py3-none-any.whl", hash = "sha256:ed87326596b878932dbcb171e3e698845434d8c61b8d8cd474bf663041a9dcf2", size = 831864, upload-time = "2025-05-31T16:39:06.38Z" }, -] - [[package]] name = "ipython" version = "9.7.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.11'", -] dependencies = [ - { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" }, - { name = "decorator", marker = "python_full_version >= '3.11'" }, - { name = "ipython-pygments-lexers", marker = "python_full_version >= '3.11'" }, - { name = "jedi", marker = "python_full_version >= '3.11'" }, - { name = "matplotlib-inline", marker = "python_full_version >= '3.11'" }, - { name = "pexpect", marker = "python_full_version >= '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "prompt-toolkit", marker = "python_full_version >= '3.11'" }, - { name = "pygments", marker = "python_full_version >= '3.11'" }, - { name = "stack-data", marker = "python_full_version >= '3.11'" }, - { name = "traitlets", marker = "python_full_version >= '3.11'" }, - { name = "typing-extensions", marker = "python_full_version == '3.11.*'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { name = "ipython-pygments-lexers" }, + { name = "jedi" }, + { name = "matplotlib-inline" }, + { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit" }, + { name = "pygments" }, + { name = "stack-data" }, + { name = "traitlets" }, + { name = "typing-extensions", marker = "python_full_version < '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/29/e6/48c74d54039241a456add616464ea28c6ebf782e4110d419411b83dae06f/ipython-9.7.0.tar.gz", hash = "sha256:5f6de88c905a566c6a9d6c400a8fed54a638e1f7543d17aae2551133216b1e4e", size = 4422115, upload-time = "2025-11-05T12:18:54.646Z" } wheels = [ @@ -1033,7 +855,7 @@ name = "ipython-pygments-lexers" version = "1.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pygments", marker = "python_full_version >= '3.11'" }, + { name = "pygments" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" } wheels = [ @@ -1135,19 +957,6 @@ version = "1.4.9" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/5c/3c/85844f1b0feb11ee581ac23fe5fce65cd049a200c1446708cc1b7f922875/kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d", size = 97564, upload-time = "2025-08-10T21:27:49.279Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/5d/8ce64e36d4e3aac5ca96996457dcf33e34e6051492399a3f1fec5657f30b/kiwisolver-1.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b4b4d74bda2b8ebf4da5bd42af11d02d04428b2c32846e4c2c93219df8a7987b", size = 124159, upload-time = "2025-08-10T21:25:35.472Z" }, - { url = "https://files.pythonhosted.org/packages/96/1e/22f63ec454874378175a5f435d6ea1363dd33fb2af832c6643e4ccea0dc8/kiwisolver-1.4.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fb3b8132019ea572f4611d770991000d7f58127560c4889729248eb5852a102f", size = 66578, upload-time = "2025-08-10T21:25:36.73Z" }, - { url = "https://files.pythonhosted.org/packages/41/4c/1925dcfff47a02d465121967b95151c82d11027d5ec5242771e580e731bd/kiwisolver-1.4.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84fd60810829c27ae375114cd379da1fa65e6918e1da405f356a775d49a62bcf", size = 65312, upload-time = "2025-08-10T21:25:37.658Z" }, - { url = "https://files.pythonhosted.org/packages/d4/42/0f333164e6307a0687d1eb9ad256215aae2f4bd5d28f4653d6cd319a3ba3/kiwisolver-1.4.9-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b78efa4c6e804ecdf727e580dbb9cba85624d2e1c6b5cb059c66290063bd99a9", size = 1628458, upload-time = "2025-08-10T21:25:39.067Z" }, - { url = "https://files.pythonhosted.org/packages/86/b6/2dccb977d651943995a90bfe3495c2ab2ba5cd77093d9f2318a20c9a6f59/kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4efec7bcf21671db6a3294ff301d2fc861c31faa3c8740d1a94689234d1b415", size = 1225640, upload-time = "2025-08-10T21:25:40.489Z" }, - { url = "https://files.pythonhosted.org/packages/50/2b/362ebd3eec46c850ccf2bfe3e30f2fc4c008750011f38a850f088c56a1c6/kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:90f47e70293fc3688b71271100a1a5453aa9944a81d27ff779c108372cf5567b", size = 1244074, upload-time = "2025-08-10T21:25:42.221Z" }, - { url = "https://files.pythonhosted.org/packages/6f/bb/f09a1e66dab8984773d13184a10a29fe67125337649d26bdef547024ed6b/kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fdca1def57a2e88ef339de1737a1449d6dbf5fab184c54a1fca01d541317154", size = 1293036, upload-time = "2025-08-10T21:25:43.801Z" }, - { url = "https://files.pythonhosted.org/packages/ea/01/11ecf892f201cafda0f68fa59212edaea93e96c37884b747c181303fccd1/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cf554f21be770f5111a1690d42313e140355e687e05cf82cb23d0a721a64a48", size = 2175310, upload-time = "2025-08-10T21:25:45.045Z" }, - { url = "https://files.pythonhosted.org/packages/7f/5f/bfe11d5b934f500cc004314819ea92427e6e5462706a498c1d4fc052e08f/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fc1795ac5cd0510207482c3d1d3ed781143383b8cfd36f5c645f3897ce066220", size = 2270943, upload-time = "2025-08-10T21:25:46.393Z" }, - { url = "https://files.pythonhosted.org/packages/3d/de/259f786bf71f1e03e73d87e2db1a9a3bcab64d7b4fd780167123161630ad/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:ccd09f20ccdbbd341b21a67ab50a119b64a403b09288c27481575105283c1586", size = 2440488, upload-time = "2025-08-10T21:25:48.074Z" }, - { url = "https://files.pythonhosted.org/packages/1b/76/c989c278faf037c4d3421ec07a5c452cd3e09545d6dae7f87c15f54e4edf/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:540c7c72324d864406a009d72f5d6856f49693db95d1fbb46cf86febef873634", size = 2246787, upload-time = "2025-08-10T21:25:49.442Z" }, - { url = "https://files.pythonhosted.org/packages/a2/55/c2898d84ca440852e560ca9f2a0d28e6e931ac0849b896d77231929900e7/kiwisolver-1.4.9-cp310-cp310-win_amd64.whl", hash = "sha256:ede8c6d533bc6601a47ad4046080d36b8fc99f81e6f1c17b0ac3c2dc91ac7611", size = 73730, upload-time = "2025-08-10T21:25:51.102Z" }, - { url = "https://files.pythonhosted.org/packages/e8/09/486d6ac523dd33b80b368247f238125d027964cfacb45c654841e88fb2ae/kiwisolver-1.4.9-cp310-cp310-win_arm64.whl", hash = "sha256:7b4da0d01ac866a57dd61ac258c5607b4cd677f63abaec7b148354d2b2cdd536", size = 65036, upload-time = "2025-08-10T21:25:52.063Z" }, { url = "https://files.pythonhosted.org/packages/6f/ab/c80b0d5a9d8a1a65f4f815f2afff9798b12c3b9f31f1d304dd233dd920e2/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eb14a5da6dc7642b0f3a18f13654847cd8b7a2550e2645a5bda677862b03ba16", size = 124167, upload-time = "2025-08-10T21:25:53.403Z" }, { url = "https://files.pythonhosted.org/packages/a0/c0/27fe1a68a39cf62472a300e2879ffc13c0538546c359b86f149cc19f6ac3/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39a219e1c81ae3b103643d2aedb90f1ef22650deb266ff12a19e7773f3e5f089", size = 66579, upload-time = "2025-08-10T21:25:54.79Z" }, { url = "https://files.pythonhosted.org/packages/31/a2/a12a503ac1fd4943c50f9822678e8015a790a13b5490354c68afb8489814/kiwisolver-1.4.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2405a7d98604b87f3fc28b1716783534b1b4b8510d8142adca34ee0bc3c87543", size = 65309, upload-time = "2025-08-10T21:25:55.76Z" }, @@ -1225,11 +1034,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/99/dd/841e9a66c4715477ea0abc78da039832fbb09dac5c35c58dc4c41a407b8a/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:aedff62918805fb62d43a4aa2ecd4482c380dc76cd31bd7c8878588a61bd0369", size = 2391835, upload-time = "2025-08-10T21:27:34.23Z" }, { url = "https://files.pythonhosted.org/packages/0c/28/4b2e5c47a0da96896fdfdb006340ade064afa1e63675d01ea5ac222b6d52/kiwisolver-1.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:1fa333e8b2ce4d9660f2cda9c0e1b6bafcfb2457a9d259faa82289e73ec24891", size = 79988, upload-time = "2025-08-10T21:27:35.587Z" }, { url = "https://files.pythonhosted.org/packages/80/be/3578e8afd18c88cdf9cb4cffde75a96d2be38c5a903f1ed0ceec061bd09e/kiwisolver-1.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32", size = 70260, upload-time = "2025-08-10T21:27:36.606Z" }, - { url = "https://files.pythonhosted.org/packages/a2/63/fde392691690f55b38d5dd7b3710f5353bf7a8e52de93a22968801ab8978/kiwisolver-1.4.9-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4d1d9e582ad4d63062d34077a9a1e9f3c34088a2ec5135b1f7190c07cf366527", size = 60183, upload-time = "2025-08-10T21:27:37.669Z" }, - { url = "https://files.pythonhosted.org/packages/27/b1/6aad34edfdb7cced27f371866f211332bba215bfd918ad3322a58f480d8b/kiwisolver-1.4.9-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:deed0c7258ceb4c44ad5ec7d9918f9f14fd05b2be86378d86cf50e63d1e7b771", size = 58675, upload-time = "2025-08-10T21:27:39.031Z" }, - { url = "https://files.pythonhosted.org/packages/9d/1a/23d855a702bb35a76faed5ae2ba3de57d323f48b1f6b17ee2176c4849463/kiwisolver-1.4.9-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a590506f303f512dff6b7f75fd2fd18e16943efee932008fe7140e5fa91d80e", size = 80277, upload-time = "2025-08-10T21:27:40.129Z" }, - { url = "https://files.pythonhosted.org/packages/5a/5b/5239e3c2b8fb5afa1e8508f721bb77325f740ab6994d963e61b2b7abcc1e/kiwisolver-1.4.9-pp310-pypy310_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e09c2279a4d01f099f52d5c4b3d9e208e91edcbd1a175c9662a8b16e000fece9", size = 77994, upload-time = "2025-08-10T21:27:41.181Z" }, - { url = "https://files.pythonhosted.org/packages/f9/1c/5d4d468fb16f8410e596ed0eac02d2c68752aa7dc92997fe9d60a7147665/kiwisolver-1.4.9-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c9e7cdf45d594ee04d5be1b24dd9d49f3d1590959b2271fb30b5ca2b262c00fb", size = 73744, upload-time = "2025-08-10T21:27:42.254Z" }, { url = "https://files.pythonhosted.org/packages/a3/0f/36d89194b5a32c054ce93e586d4049b6c2c22887b0eb229c61c68afd3078/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:720e05574713db64c356e86732c0f3c5252818d05f9df320f0ad8380641acea5", size = 60104, upload-time = "2025-08-10T21:27:43.287Z" }, { url = "https://files.pythonhosted.org/packages/52/ba/4ed75f59e4658fd21fe7dde1fee0ac397c678ec3befba3fe6482d987af87/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:17680d737d5335b552994a2008fab4c851bcd7de33094a82067ef3a576ff02fa", size = 58592, upload-time = "2025-08-10T21:27:44.314Z" }, { url = "https://files.pythonhosted.org/packages/33/01/a8ea7c5ea32a9b45ceeaee051a04c8ed4320f5add3c51bfa20879b765b70/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85b5352f94e490c028926ea567fc569c52ec79ce131dadb968d3853e809518c2", size = 80281, upload-time = "2025-08-10T21:27:45.369Z" }, @@ -1243,16 +1047,6 @@ version = "0.6.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/37/c3/cdff3c10e2e608490dc0a310ccf11ba777b3943ad4fcead2a2ade98c21e1/librt-0.6.3.tar.gz", hash = "sha256:c724a884e642aa2bbad52bb0203ea40406ad742368a5f90da1b220e970384aae", size = 54209, upload-time = "2025-11-29T14:01:56.058Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/84/859df8db21dedab2538ddfbe1d486dda3eb66a98c6ad7ba754a99e25e45e/librt-0.6.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:45660d26569cc22ed30adf583389d8a0d1b468f8b5e518fcf9bfe2cd298f9dd1", size = 27294, upload-time = "2025-11-29T14:00:35.053Z" }, - { url = "https://files.pythonhosted.org/packages/f7/01/ec3971cf9c4f827f17de6729bdfdbf01a67493147334f4ef8fac68936e3a/librt-0.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:54f3b2177fb892d47f8016f1087d21654b44f7fc4cf6571c1c6b3ea531ab0fcf", size = 27635, upload-time = "2025-11-29T14:00:36.496Z" }, - { url = "https://files.pythonhosted.org/packages/b4/f9/3efe201df84dd26388d2e0afa4c4dc668c8e406a3da7b7319152faf835a1/librt-0.6.3-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c5b31bed2c2f2fa1fcb4815b75f931121ae210dc89a3d607fb1725f5907f1437", size = 81768, upload-time = "2025-11-29T14:00:37.451Z" }, - { url = "https://files.pythonhosted.org/packages/0a/13/f63e60bc219b17f3d8f3d13423cd4972e597b0321c51cac7bfbdd5e1f7b9/librt-0.6.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f8ed5053ef9fb08d34f1fd80ff093ccbd1f67f147633a84cf4a7d9b09c0f089", size = 85884, upload-time = "2025-11-29T14:00:38.433Z" }, - { url = "https://files.pythonhosted.org/packages/c2/42/0068f14f39a79d1ce8a19d4988dd07371df1d0a7d3395fbdc8a25b1c9437/librt-0.6.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3f0e4bd9bcb0ee34fa3dbedb05570da50b285f49e52c07a241da967840432513", size = 85830, upload-time = "2025-11-29T14:00:39.418Z" }, - { url = "https://files.pythonhosted.org/packages/14/1c/87f5af3a9e6564f09e50c72f82fc3057fd42d1facc8b510a707d0438c4ad/librt-0.6.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d8f89c8d20dfa648a3f0a56861946eb00e5b00d6b00eea14bc5532b2fcfa8ef1", size = 88086, upload-time = "2025-11-29T14:00:40.555Z" }, - { url = "https://files.pythonhosted.org/packages/05/e5/22153b98b88a913b5b3f266f12e57df50a2a6960b3f8fcb825b1a0cfe40a/librt-0.6.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ecc2c526547eacd20cb9fbba19a5268611dbc70c346499656d6cf30fae328977", size = 86470, upload-time = "2025-11-29T14:00:41.827Z" }, - { url = "https://files.pythonhosted.org/packages/18/3c/ea1edb587799b1edcc22444e0630fa422e32d7aaa5bfb5115b948acc2d1c/librt-0.6.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fbedeb9b48614d662822ee514567d2d49a8012037fc7b4cd63f282642c2f4b7d", size = 89079, upload-time = "2025-11-29T14:00:42.882Z" }, - { url = "https://files.pythonhosted.org/packages/73/ad/50bb4ae6b07c9f3ab19653e0830a210533b30eb9a18d515efb5a2b9d0c7c/librt-0.6.3-cp310-cp310-win32.whl", hash = "sha256:0765b0fe0927d189ee14b087cd595ae636bef04992e03fe6dfdaa383866c8a46", size = 19820, upload-time = "2025-11-29T14:00:44.211Z" }, - { url = "https://files.pythonhosted.org/packages/7a/12/7426ee78f3b1dbe11a90619d54cb241ca924ca3c0ff9ade3992178e9b440/librt-0.6.3-cp310-cp310-win_amd64.whl", hash = "sha256:8c659f9fb8a2f16dc4131b803fa0144c1dadcb3ab24bb7914d01a6da58ae2457", size = 21332, upload-time = "2025-11-29T14:00:45.427Z" }, { url = "https://files.pythonhosted.org/packages/8b/80/bc60fd16fe24910bf5974fb914778a2e8540cef55385ab2cb04a0dfe42c4/librt-0.6.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:61348cc488b18d1b1ff9f3e5fcd5ac43ed22d3e13e862489d2267c2337285c08", size = 27285, upload-time = "2025-11-29T14:00:46.626Z" }, { url = "https://files.pythonhosted.org/packages/88/3c/26335536ed9ba097c79cffcee148393592e55758fe76d99015af3e47a6d0/librt-0.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:64645b757d617ad5f98c08e07620bc488d4bced9ced91c6279cec418f16056fa", size = 27629, upload-time = "2025-11-29T14:00:47.863Z" }, { url = "https://files.pythonhosted.org/packages/af/fd/2dcedeacfedee5d2eda23e7a49c1c12ce6221b5d58a13555f053203faafc/librt-0.6.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:26b8026393920320bb9a811b691d73c5981385d537ffc5b6e22e53f7b65d4122", size = 82039, upload-time = "2025-11-29T14:00:49.131Z" }, @@ -1325,14 +1119,6 @@ version = "4.4.5" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/57/51/f1b86d93029f418033dddf9b9f79c8d2641e7454080478ee2aab5123173e/lz4-4.4.5.tar.gz", hash = "sha256:5f0b9e53c1e82e88c10d7c180069363980136b9d7a8306c4dca4f760d60c39f0", size = 172886, upload-time = "2025-11-03T13:02:36.061Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/45/2466d73d79e3940cad4b26761f356f19fd33f4409c96f100e01a5c566909/lz4-4.4.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d221fa421b389ab2345640a508db57da36947a437dfe31aeddb8d5c7b646c22d", size = 207396, upload-time = "2025-11-03T13:01:24.965Z" }, - { url = "https://files.pythonhosted.org/packages/72/12/7da96077a7e8918a5a57a25f1254edaf76aefb457666fcc1066deeecd609/lz4-4.4.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7dc1e1e2dbd872f8fae529acd5e4839efd0b141eaa8ae7ce835a9fe80fbad89f", size = 207154, upload-time = "2025-11-03T13:01:26.922Z" }, - { url = "https://files.pythonhosted.org/packages/b8/0e/0fb54f84fd1890d4af5bc0a3c1fa69678451c1a6bd40de26ec0561bb4ec5/lz4-4.4.5-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e928ec2d84dc8d13285b4a9288fd6246c5cde4f5f935b479f50d986911f085e3", size = 1291053, upload-time = "2025-11-03T13:01:28.396Z" }, - { url = "https://files.pythonhosted.org/packages/15/45/8ce01cc2715a19c9e72b0e423262072c17d581a8da56e0bd4550f3d76a79/lz4-4.4.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:daffa4807ef54b927451208f5f85750c545a4abbff03d740835fc444cd97f758", size = 1278586, upload-time = "2025-11-03T13:01:29.906Z" }, - { url = "https://files.pythonhosted.org/packages/6d/34/7be9b09015e18510a09b8d76c304d505a7cbc66b775ec0b8f61442316818/lz4-4.4.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2a2b7504d2dffed3fd19d4085fe1cc30cf221263fd01030819bdd8d2bb101cf1", size = 1367315, upload-time = "2025-11-03T13:01:31.054Z" }, - { url = "https://files.pythonhosted.org/packages/2a/94/52cc3ec0d41e8d68c985ec3b2d33631f281d8b748fb44955bc0384c2627b/lz4-4.4.5-cp310-cp310-win32.whl", hash = "sha256:0846e6e78f374156ccf21c631de80967e03cc3c01c373c665789dc0c5431e7fc", size = 88173, upload-time = "2025-11-03T13:01:32.643Z" }, - { url = "https://files.pythonhosted.org/packages/ca/35/c3c0bdc409f551404355aeeabc8da343577d0e53592368062e371a3620e1/lz4-4.4.5-cp310-cp310-win_amd64.whl", hash = "sha256:7c4e7c44b6a31de77d4dc9772b7d2561937c9588a734681f70ec547cfbc51ecd", size = 99492, upload-time = "2025-11-03T13:01:33.813Z" }, - { url = "https://files.pythonhosted.org/packages/1d/02/4d88de2f1e97f9d05fd3d278fe412b08969bc94ff34942f5a3f09318144a/lz4-4.4.5-cp310-cp310-win_arm64.whl", hash = "sha256:15551280f5656d2206b9b43262799c89b25a25460416ec554075a8dc568e4397", size = 91280, upload-time = "2025-11-03T13:01:35.081Z" }, { url = "https://files.pythonhosted.org/packages/93/5b/6edcd23319d9e28b1bedf32768c3d1fd56eed8223960a2c47dacd2cec2af/lz4-4.4.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d6da84a26b3aa5da13a62e4b89ab36a396e9327de8cd48b436a3467077f8ccd4", size = 207391, upload-time = "2025-11-03T13:01:36.644Z" }, { url = "https://files.pythonhosted.org/packages/34/36/5f9b772e85b3d5769367a79973b8030afad0d6b724444083bad09becd66f/lz4-4.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:61d0ee03e6c616f4a8b69987d03d514e8896c8b1b7cc7598ad029e5c6aedfd43", size = 207146, upload-time = "2025-11-03T13:01:37.928Z" }, { url = "https://files.pythonhosted.org/packages/04/f4/f66da5647c0d72592081a37c8775feacc3d14d2625bbdaabd6307c274565/lz4-4.4.5-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:33dd86cea8375d8e5dd001e41f321d0a4b1eb7985f39be1b6a4f466cd480b8a7", size = 1292623, upload-time = "2025-11-03T13:01:39.341Z" }, @@ -1393,17 +1179,6 @@ version = "3.0.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" }, - { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" }, - { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" }, - { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" }, - { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" }, - { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" }, - { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" }, - { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" }, - { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" }, - { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" }, - { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" }, { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, @@ -1477,13 +1252,11 @@ name = "matplotlib" version = "3.10.7" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "contourpy", version = "1.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "contourpy", version = "1.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "contourpy" }, { name = "cycler" }, { name = "fonttools" }, { name = "kiwisolver" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy" }, { name = "packaging" }, { name = "pillow" }, { name = "pyparsing" }, @@ -1491,12 +1264,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/ae/e2/d2d5295be2f44c678ebaf3544ba32d20c1f9ef08c49fe47f496180e1db15/matplotlib-3.10.7.tar.gz", hash = "sha256:a06ba7e2a2ef9131c79c49e63dad355d2d878413a0376c1727c8b9335ff731c7", size = 34804865, upload-time = "2025-10-09T00:28:00.669Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/87/3932d5778ab4c025db22710b61f49ccaed3956c5cf46ffb2ffa7492b06d9/matplotlib-3.10.7-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:7ac81eee3b7c266dd92cee1cd658407b16c57eed08c7421fa354ed68234de380", size = 8247141, upload-time = "2025-10-09T00:26:06.023Z" }, - { url = "https://files.pythonhosted.org/packages/45/a8/bfed45339160102bce21a44e38a358a1134a5f84c26166de03fb4a53208f/matplotlib-3.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:667ecd5d8d37813a845053d8f5bf110b534c3c9f30e69ebd25d4701385935a6d", size = 8107995, upload-time = "2025-10-09T00:26:08.669Z" }, - { url = "https://files.pythonhosted.org/packages/e2/3c/5692a2d9a5ba848fda3f48d2b607037df96460b941a59ef236404b39776b/matplotlib-3.10.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc1c51b846aca49a5a8b44fbba6a92d583a35c64590ad9e1e950dc88940a4297", size = 8680503, upload-time = "2025-10-09T00:26:10.607Z" }, - { url = "https://files.pythonhosted.org/packages/ab/a0/86ace53c48b05d0e6e9c127b2ace097434901f3e7b93f050791c8243201a/matplotlib-3.10.7-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a11c2e9e72e7de09b7b72e62f3df23317c888299c875e2b778abf1eda8c0a42", size = 9514982, upload-time = "2025-10-09T00:26:12.594Z" }, - { url = "https://files.pythonhosted.org/packages/a6/81/ead71e2824da8f72640a64166d10e62300df4ae4db01a0bac56c5b39fa51/matplotlib-3.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f19410b486fdd139885ace124e57f938c1e6a3210ea13dd29cab58f5d4bc12c7", size = 9566429, upload-time = "2025-10-09T00:26:14.758Z" }, - { url = "https://files.pythonhosted.org/packages/65/7d/954b3067120456f472cce8fdcacaf4a5fcd522478db0c37bb243c7cb59dd/matplotlib-3.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:b498e9e4022f93de2d5a37615200ca01297ceebbb56fe4c833f46862a490f9e3", size = 8108174, upload-time = "2025-10-09T00:26:17.015Z" }, { url = "https://files.pythonhosted.org/packages/fc/bc/0fb489005669127ec13f51be0c6adc074d7cf191075dab1da9fe3b7a3cfc/matplotlib-3.10.7-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:53b492410a6cd66c7a471de6c924f6ede976e963c0f3097a3b7abfadddc67d0a", size = 8257507, upload-time = "2025-10-09T00:26:19.073Z" }, { url = "https://files.pythonhosted.org/packages/e2/6a/d42588ad895279ff6708924645b5d2ed54a7fb2dc045c8a804e955aeace1/matplotlib-3.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d9749313deb729f08207718d29c86246beb2ea3fdba753595b55901dee5d2fd6", size = 8119565, upload-time = "2025-10-09T00:26:21.023Z" }, { url = "https://files.pythonhosted.org/packages/10/b7/4aa196155b4d846bd749cf82aa5a4c300cf55a8b5e0dfa5b722a63c0f8a0/matplotlib-3.10.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2222c7ba2cbde7fe63032769f6eb7e83ab3227f47d997a8453377709b7fe3a5a", size = 8692668, upload-time = "2025-10-09T00:26:22.967Z" }, @@ -1539,9 +1306,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e6/a5/85e2edf76ea0ad4288d174926d9454ea85f3ce5390cc4e6fab196cbf250b/matplotlib-3.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:702590829c30aada1e8cef0568ddbffa77ca747b4d6e36c6d173f66e301f89cc", size = 9594066, upload-time = "2025-10-09T00:27:43.694Z" }, { url = "https://files.pythonhosted.org/packages/39/69/9684368a314f6d83fe5c5ad2a4121a3a8e03723d2e5c8ea17b66c1bad0e7/matplotlib-3.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:f79d5de970fc90cd5591f60053aecfce1fcd736e0303d9f0bf86be649fa68fb8", size = 8342832, upload-time = "2025-10-09T00:27:45.543Z" }, { url = "https://files.pythonhosted.org/packages/04/5f/e22e08da14bc1a0894184640d47819d2338b792732e20d292bf86e5ab785/matplotlib-3.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:cb783436e47fcf82064baca52ce748af71725d0352e1d31564cbe9c95df92b9c", size = 8172585, upload-time = "2025-10-09T00:27:47.185Z" }, - { url = "https://files.pythonhosted.org/packages/1e/6c/a9bcf03e9afb2a873e0a5855f79bce476d1023f26f8212969f2b7504756c/matplotlib-3.10.7-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5c09cf8f2793f81368f49f118b6f9f937456362bee282eac575cca7f84cda537", size = 8241204, upload-time = "2025-10-09T00:27:48.806Z" }, - { url = "https://files.pythonhosted.org/packages/5b/fd/0e6f5aa762ed689d9fa8750b08f1932628ffa7ed30e76423c399d19407d2/matplotlib-3.10.7-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:de66744b2bb88d5cd27e80dfc2ec9f0517d0a46d204ff98fe9e5f2864eb67657", size = 8104607, upload-time = "2025-10-09T00:27:50.876Z" }, - { url = "https://files.pythonhosted.org/packages/b9/a9/21c9439d698fac5f0de8fc68b2405b738ed1f00e1279c76f2d9aa5521ead/matplotlib-3.10.7-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:53cc80662dd197ece414dd5b66e07370201515a3eaf52e7c518c68c16814773b", size = 8682257, upload-time = "2025-10-09T00:27:52.597Z" }, { url = "https://files.pythonhosted.org/packages/58/8f/76d5dc21ac64a49e5498d7f0472c0781dae442dd266a67458baec38288ec/matplotlib-3.10.7-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:15112bcbaef211bd663fa935ec33313b948e214454d949b723998a43357b17b0", size = 8252283, upload-time = "2025-10-09T00:27:54.739Z" }, { url = "https://files.pythonhosted.org/packages/27/0d/9c5d4c2317feb31d819e38c9f947c942f42ebd4eb935fc6fd3518a11eaa7/matplotlib-3.10.7-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d2a959c640cdeecdd2ec3136e8ea0441da59bcaf58d67e9c590740addba2cb68", size = 8116733, upload-time = "2025-10-09T00:27:56.406Z" }, { url = "https://files.pythonhosted.org/packages/9a/cc/3fe688ff1355010937713164caacf9ed443675ac48a997bab6ed23b3f7c0/matplotlib-3.10.7-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3886e47f64611046bc1db523a09dd0a0a6bed6081e6f90e13806dd1d1d1b5e91", size = 8693919, upload-time = "2025-10-09T00:27:58.41Z" }, @@ -1604,14 +1368,6 @@ version = "1.1.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/4d/f2/bfb55a6236ed8725a96b0aa3acbd0ec17588e6a2c3b62a93eb513ed8783f/msgpack-1.1.2.tar.gz", hash = "sha256:3b60763c1373dd60f398488069bcdc703cd08a711477b5d480eecc9f9626f47e", size = 173581, upload-time = "2025-10-08T09:15:56.596Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f5/a2/3b68a9e769db68668b25c6108444a35f9bd163bb848c0650d516761a59c0/msgpack-1.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0051fffef5a37ca2cd16978ae4f0aef92f164df86823871b5162812bebecd8e2", size = 81318, upload-time = "2025-10-08T09:14:38.722Z" }, - { url = "https://files.pythonhosted.org/packages/5b/e1/2b720cc341325c00be44e1ed59e7cfeae2678329fbf5aa68f5bda57fe728/msgpack-1.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a605409040f2da88676e9c9e5853b3449ba8011973616189ea5ee55ddbc5bc87", size = 83786, upload-time = "2025-10-08T09:14:40.082Z" }, - { url = "https://files.pythonhosted.org/packages/71/e5/c2241de64bfceac456b140737812a2ab310b10538a7b34a1d393b748e095/msgpack-1.1.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b696e83c9f1532b4af884045ba7f3aa741a63b2bc22617293a2c6a7c645f251", size = 398240, upload-time = "2025-10-08T09:14:41.151Z" }, - { url = "https://files.pythonhosted.org/packages/b7/09/2a06956383c0fdebaef5aa9246e2356776f12ea6f2a44bd1368abf0e46c4/msgpack-1.1.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:365c0bbe981a27d8932da71af63ef86acc59ed5c01ad929e09a0b88c6294e28a", size = 406070, upload-time = "2025-10-08T09:14:42.821Z" }, - { url = "https://files.pythonhosted.org/packages/0e/74/2957703f0e1ef20637d6aead4fbb314330c26f39aa046b348c7edcf6ca6b/msgpack-1.1.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:41d1a5d875680166d3ac5c38573896453bbbea7092936d2e107214daf43b1d4f", size = 393403, upload-time = "2025-10-08T09:14:44.38Z" }, - { url = "https://files.pythonhosted.org/packages/a5/09/3bfc12aa90f77b37322fc33e7a8a7c29ba7c8edeadfa27664451801b9860/msgpack-1.1.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:354e81bcdebaab427c3df4281187edc765d5d76bfb3a7c125af9da7a27e8458f", size = 398947, upload-time = "2025-10-08T09:14:45.56Z" }, - { url = "https://files.pythonhosted.org/packages/4b/4f/05fcebd3b4977cb3d840f7ef6b77c51f8582086de5e642f3fefee35c86fc/msgpack-1.1.2-cp310-cp310-win32.whl", hash = "sha256:e64c8d2f5e5d5fda7b842f55dec6133260ea8f53c4257d64494c534f306bf7a9", size = 64769, upload-time = "2025-10-08T09:14:47.334Z" }, - { url = "https://files.pythonhosted.org/packages/d0/3e/b4547e3a34210956382eed1c85935fff7e0f9b98be3106b3745d7dec9c5e/msgpack-1.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:db6192777d943bdaaafb6ba66d44bf65aa0e9c5616fa1d2da9bb08828c6b39aa", size = 71293, upload-time = "2025-10-08T09:14:48.665Z" }, { url = "https://files.pythonhosted.org/packages/2c/97/560d11202bcd537abca693fd85d81cebe2107ba17301de42b01ac1677b69/msgpack-1.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2e86a607e558d22985d856948c12a3fa7b42efad264dca8a3ebbcfa2735d786c", size = 82271, upload-time = "2025-10-08T09:14:49.967Z" }, { url = "https://files.pythonhosted.org/packages/83/04/28a41024ccbd67467380b6fb440ae916c1e4f25e2cd4c63abe6835ac566e/msgpack-1.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:283ae72fc89da59aa004ba147e8fc2f766647b1251500182fac0350d8af299c0", size = 84914, upload-time = "2025-10-08T09:14:50.958Z" }, { url = "https://files.pythonhosted.org/packages/71/46/b817349db6886d79e57a966346cf0902a426375aadc1e8e7a86a75e22f19/msgpack-1.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:61c8aa3bd513d87c72ed0b37b53dd5c5a0f58f2ff9f26e1555d3bd7948fb7296", size = 416962, upload-time = "2025-10-08T09:14:51.997Z" }, @@ -1665,8 +1421,7 @@ version = "0.4.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "msgpack" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy" }, ] sdist = { url = "https://files.pythonhosted.org/packages/08/94/61e8aee142733ebfdc400a05bdac6e1763c4514bba3b42743d223f388450/msgpack-numpy-0.4.8.tar.gz", hash = "sha256:c667d3180513422f9c7545be5eec5d296dcbb357e06f72ed39cc683797556e69", size = 10923, upload-time = "2022-06-09T03:43:08.739Z" } wheels = [ @@ -1681,17 +1436,10 @@ dependencies = [ { name = "librt" }, { name = "mypy-extensions" }, { name = "pathspec" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f9/b5/b58cdc25fadd424552804bf410855d52324183112aa004f0732c5f6324cf/mypy-1.19.0.tar.gz", hash = "sha256:f6b874ca77f733222641e5c46e4711648c4037ea13646fd0cdc814c2eaec2528", size = 3579025, upload-time = "2025-11-28T15:49:01.26Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/98/8f/55fb488c2b7dabd76e3f30c10f7ab0f6190c1fcbc3e97b1e588ec625bbe2/mypy-1.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6148ede033982a8c5ca1143de34c71836a09f105068aaa8b7d5edab2b053e6c8", size = 13093239, upload-time = "2025-11-28T15:45:11.342Z" }, - { url = "https://files.pythonhosted.org/packages/72/1b/278beea978456c56b3262266274f335c3ba5ff2c8108b3b31bec1ffa4c1d/mypy-1.19.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a9ac09e52bb0f7fb912f5d2a783345c72441a08ef56ce3e17c1752af36340a39", size = 12156128, upload-time = "2025-11-28T15:46:02.566Z" }, - { url = "https://files.pythonhosted.org/packages/21/f8/e06f951902e136ff74fd7a4dc4ef9d884faeb2f8eb9c49461235714f079f/mypy-1.19.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11f7254c15ab3f8ed68f8e8f5cbe88757848df793e31c36aaa4d4f9783fd08ab", size = 12753508, upload-time = "2025-11-28T15:44:47.538Z" }, - { url = "https://files.pythonhosted.org/packages/67/5a/d035c534ad86e09cee274d53cf0fd769c0b29ca6ed5b32e205be3c06878c/mypy-1.19.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318ba74f75899b0e78b847d8c50821e4c9637c79d9a59680fc1259f29338cb3e", size = 13507553, upload-time = "2025-11-28T15:44:39.26Z" }, - { url = "https://files.pythonhosted.org/packages/6a/17/c4a5498e00071ef29e483a01558b285d086825b61cf1fb2629fbdd019d94/mypy-1.19.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cf7d84f497f78b682edd407f14a7b6e1a2212b433eedb054e2081380b7395aa3", size = 13792898, upload-time = "2025-11-28T15:44:31.102Z" }, - { url = "https://files.pythonhosted.org/packages/67/f6/bb542422b3ee4399ae1cdc463300d2d91515ab834c6233f2fd1d52fa21e0/mypy-1.19.0-cp310-cp310-win_amd64.whl", hash = "sha256:c3385246593ac2b97f155a0e9639be906e73534630f663747c71908dfbf26134", size = 10048835, upload-time = "2025-11-28T15:48:15.744Z" }, { url = "https://files.pythonhosted.org/packages/0f/d2/010fb171ae5ac4a01cc34fbacd7544531e5ace95c35ca166dd8fd1b901d0/mypy-1.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a31e4c28e8ddb042c84c5e977e28a21195d086aaffaf08b016b78e19c9ef8106", size = 13010563, upload-time = "2025-11-28T15:48:23.975Z" }, { url = "https://files.pythonhosted.org/packages/41/6b/63f095c9f1ce584fdeb595d663d49e0980c735a1d2004720ccec252c5d47/mypy-1.19.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34ec1ac66d31644f194b7c163d7f8b8434f1b49719d403a5d26c87fff7e913f7", size = 12077037, upload-time = "2025-11-28T15:47:51.582Z" }, { url = "https://files.pythonhosted.org/packages/d7/83/6cb93d289038d809023ec20eb0b48bbb1d80af40511fa077da78af6ff7c7/mypy-1.19.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cb64b0ba5980466a0f3f9990d1c582bcab8db12e29815ecb57f1408d99b4bff7", size = 12680255, upload-time = "2025-11-28T15:46:57.628Z" }, @@ -1738,8 +1486,7 @@ dependencies = [ { name = "markdown-it-py" }, { name = "mdit-py-plugins" }, { name = "pyyaml" }, - { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "sphinx" }, ] sdist = { url = "https://files.pythonhosted.org/packages/66/a5/9626ba4f73555b3735ad86247a8077d4603aa8628537687c839ab08bfe44/myst_parser-4.0.1.tar.gz", hash = "sha256:5cfea715e4f3574138aecbf7d54132296bfd72bb614d31168f48c477a830a7c4", size = 93985, upload-time = "2025-02-12T10:53:03.833Z" } wheels = [ @@ -1755,25 +1502,10 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" }, ] -[[package]] -name = "networkx" -version = "3.4.2" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.11'", -] -sdist = { url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368, upload-time = "2024-10-21T12:39:38.695Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263, upload-time = "2024-10-21T12:39:36.247Z" }, -] - [[package]] name = "networkx" version = "3.6" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.11'", -] sdist = { url = "https://files.pythonhosted.org/packages/e8/fc/7b6fd4d22c8c4dc5704430140d8b3f520531d4fe7328b8f8d03f5a7950e8/networkx-3.6.tar.gz", hash = "sha256:285276002ad1f7f7da0f7b42f004bcba70d381e936559166363707fdad3d72ad", size = 2511464, upload-time = "2025-11-24T03:03:47.158Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/07/c7/d64168da60332c17d24c0d2f08bdf3987e8d1ae9d84b5bbd0eec2eb26a55/networkx-3.6-py3-none-any.whl", hash = "sha256:cdb395b105806062473d3be36458d8f1459a4e4b98e236a66c3a48996e07684f", size = 2063713, upload-time = "2025-11-24T03:03:45.21Z" }, @@ -1788,78 +1520,10 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, ] -[[package]] -name = "numpy" -version = "2.2.6" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.11'", -] -sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" }, - { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" }, - { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" }, - { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" }, - { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" }, - { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" }, - { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" }, - { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" }, - { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" }, - { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" }, - { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, - { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, - { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, - { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, - { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, - { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, - { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, - { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, - { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, - { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, - { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, - { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, - { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, - { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, - { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, - { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, - { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, - { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, - { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, - { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, - { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, - { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, - { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, - { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, - { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, - { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, - { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, - { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, - { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, - { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, - { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, - { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, - { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, - { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, - { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, - { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, - { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, - { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, - { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, - { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, - { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, - { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, - { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, - { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" }, -] - [[package]] name = "numpy" version = "2.3.5" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.11'", -] sdist = { url = "https://files.pythonhosted.org/packages/76/65/21b3bc86aac7b8f2862db1e808f1ea22b028e30a225a34a5ede9bf8678f2/numpy-2.3.5.tar.gz", hash = "sha256:784db1dcdab56bf0517743e746dfb0f885fc68d948aba86eeec2cba234bdf1c0", size = 20584950, upload-time = "2025-11-16T22:52:42.067Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/43/77/84dd1d2e34d7e2792a236ba180b5e8fcc1e3e414e761ce0253f63d7f572e/numpy-2.3.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:de5672f4a7b200c15a4127042170a694d4df43c992948f5e1af57f0174beed10", size = 17034641, upload-time = "2025-11-16T22:49:19.336Z" }, @@ -1942,9 +1606,7 @@ name = "numpydoc" version = "1.10.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "sphinx" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e9/3c/dfccc9e7dee357fb2aa13c3890d952a370dd0ed071e0f7ed62ed0df567c1/numpydoc-1.10.0.tar.gz", hash = "sha256:3f7970f6eee30912260a6b31ac72bba2432830cd6722569ec17ee8d3ef5ffa01", size = 94027, upload-time = "2025-12-02T16:39:12.937Z" } wheels = [ @@ -1969,60 +1631,32 @@ name = "ophyd" version = "1.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "networkx", version = "3.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "networkx" }, + { name = "numpy" }, { name = "opentelemetry-api" }, { name = "packaging" }, - { name = "pint", version = "0.24.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "pint", version = "0.25.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pint" }, ] sdist = { url = "https://files.pythonhosted.org/packages/50/54/02465637d447291dd8596584c9b74e8f82cc527d3c0be8dd8ed267feec42/ophyd-1.11.0.tar.gz", hash = "sha256:75e0489436035fe7ba40476f2c2ea7fd08ed02d7cd462b722e347684447ec9cc", size = 312894, upload-time = "2025-09-04T15:34:38.221Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/46/59/e11136fe6db0b1f3aea14de7bc36122aaa0ca67b715e400d45974b8848be/ophyd-1.11.0-py3-none-any.whl", hash = "sha256:216ee73f61409550916aed03773ac5dbe0e43dc2e463d3b865212d1e7ca7b877", size = 279624, upload-time = "2025-09-04T15:34:36.865Z" }, ] -[[package]] -name = "ophyd-async" -version = "0.12.3" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.11'", -] -dependencies = [ - { name = "bluesky", marker = "python_full_version < '3.11'" }, - { name = "colorlog", marker = "python_full_version < '3.11'" }, - { name = "event-model", marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "pydantic", marker = "python_full_version < '3.11'" }, - { name = "pydantic-numpy", marker = "python_full_version < '3.11'" }, - { name = "pyyaml", marker = "python_full_version < '3.11'" }, - { name = "stamina", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/aa/9a/d7a5a810e85c0c4c632db20794baece23709693ea88492650e094fdb3ead/ophyd_async-0.12.3.tar.gz", hash = "sha256:4800fede1235ac14eb48462c98ca45ea544cf579a74b3c9012915cf91ea04e2f", size = 317183, upload-time = "2025-07-31T14:03:46.799Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/cb/75a17d45ae070fa4af0b6bf79fb820b9d9137bf09f485a6728f71e4a062e/ophyd_async-0.12.3-py3-none-any.whl", hash = "sha256:3d0e98f9ab0169776dad24c072ef19ece6d53faa2933e55d3c139c525761b802", size = 180818, upload-time = "2025-07-31T14:03:45.61Z" }, -] - [[package]] name = "ophyd-async" version = "0.14.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.11'", -] dependencies = [ - { name = "bluesky", marker = "python_full_version >= '3.11'" }, - { name = "colorlog", marker = "python_full_version >= '3.11'" }, - { name = "event-model", marker = "python_full_version >= '3.11'" }, - { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "pydantic", marker = "python_full_version >= '3.11'" }, - { name = "pydantic-numpy", marker = "python_full_version >= '3.11'" }, - { name = "pyyaml", marker = "python_full_version >= '3.11'" }, - { name = "scanspec", marker = "python_full_version >= '3.11'" }, - { name = "stamina", marker = "python_full_version >= '3.11'" }, - { name = "velocity-profile", marker = "python_full_version >= '3.11'" }, + { name = "bluesky" }, + { name = "colorlog" }, + { name = "event-model" }, + { name = "numpy" }, + { name = "pydantic" }, + { name = "pydantic-numpy" }, + { name = "pyyaml" }, + { name = "scanspec" }, + { name = "stamina" }, + { name = "velocity-profile" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5e/b6/c17b511ccaa2c5b07f31f52098495a7c4fd7c8163be8321087720f56dd07/ophyd_async-0.14.0.tar.gz", hash = "sha256:f2fd2b48be15be6afd16094255044ef665c804b6b20a2a61097d288f4f09452e", size = 523384, upload-time = "2025-11-24T14:58:48.625Z" } wheels = [ @@ -2086,17 +1720,6 @@ version = "12.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/cace85a1b0c9775a9f8f5d5423c8261c858760e2466c79b2dd184638b056/pillow-12.0.0.tar.gz", hash = "sha256:87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353", size = 47008828, upload-time = "2025-10-15T18:24:14.008Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/08/26e68b6b5da219c2a2cb7b563af008b53bb8e6b6fcb3fa40715fcdb2523a/pillow-12.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:3adfb466bbc544b926d50fe8f4a4e6abd8c6bffd28a26177594e6e9b2b76572b", size = 5289809, upload-time = "2025-10-15T18:21:27.791Z" }, - { url = "https://files.pythonhosted.org/packages/cb/e9/4e58fb097fb74c7b4758a680aacd558810a417d1edaa7000142976ef9d2f/pillow-12.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1ac11e8ea4f611c3c0147424eae514028b5e9077dd99ab91e1bd7bc33ff145e1", size = 4650606, upload-time = "2025-10-15T18:21:29.823Z" }, - { url = "https://files.pythonhosted.org/packages/4b/e0/1fa492aa9f77b3bc6d471c468e62bfea1823056bf7e5e4f1914d7ab2565e/pillow-12.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d49e2314c373f4c2b39446fb1a45ed333c850e09d0c59ac79b72eb3b95397363", size = 6221023, upload-time = "2025-10-15T18:21:31.415Z" }, - { url = "https://files.pythonhosted.org/packages/c1/09/4de7cd03e33734ccd0c876f0251401f1314e819cbfd89a0fcb6e77927cc6/pillow-12.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c7b2a63fd6d5246349f3d3f37b14430d73ee7e8173154461785e43036ffa96ca", size = 8024937, upload-time = "2025-10-15T18:21:33.453Z" }, - { url = "https://files.pythonhosted.org/packages/2e/69/0688e7c1390666592876d9d474f5e135abb4acb39dcb583c4dc5490f1aff/pillow-12.0.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d64317d2587c70324b79861babb9c09f71fbb780bad212018874b2c013d8600e", size = 6334139, upload-time = "2025-10-15T18:21:35.395Z" }, - { url = "https://files.pythonhosted.org/packages/ed/1c/880921e98f525b9b44ce747ad1ea8f73fd7e992bafe3ca5e5644bf433dea/pillow-12.0.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d77153e14b709fd8b8af6f66a3afbb9ed6e9fc5ccf0b6b7e1ced7b036a228782", size = 7026074, upload-time = "2025-10-15T18:21:37.219Z" }, - { url = "https://files.pythonhosted.org/packages/28/03/96f718331b19b355610ef4ebdbbde3557c726513030665071fd025745671/pillow-12.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:32ed80ea8a90ee3e6fa08c21e2e091bba6eda8eccc83dbc34c95169507a91f10", size = 6448852, upload-time = "2025-10-15T18:21:39.168Z" }, - { url = "https://files.pythonhosted.org/packages/3a/a0/6a193b3f0cc9437b122978d2c5cbce59510ccf9a5b48825096ed7472da2f/pillow-12.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c828a1ae702fc712978bda0320ba1b9893d99be0badf2647f693cc01cf0f04fa", size = 7117058, upload-time = "2025-10-15T18:21:40.997Z" }, - { url = "https://files.pythonhosted.org/packages/a7/c4/043192375eaa4463254e8e61f0e2ec9a846b983929a8d0a7122e0a6d6fff/pillow-12.0.0-cp310-cp310-win32.whl", hash = "sha256:bd87e140e45399c818fac4247880b9ce719e4783d767e030a883a970be632275", size = 6295431, upload-time = "2025-10-15T18:21:42.518Z" }, - { url = "https://files.pythonhosted.org/packages/92/c6/c2f2fc7e56301c21827e689bb8b0b465f1b52878b57471a070678c0c33cd/pillow-12.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:455247ac8a4cfb7b9bc45b7e432d10421aea9fc2e74d285ba4072688a74c2e9d", size = 7000412, upload-time = "2025-10-15T18:21:44.404Z" }, - { url = "https://files.pythonhosted.org/packages/b2/d2/5f675067ba82da7a1c238a73b32e3fd78d67f9d9f80fbadd33a40b9c0481/pillow-12.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:6ace95230bfb7cd79ef66caa064bbe2f2a1e63d93471c3a2e1f1348d9f22d6b7", size = 2435903, upload-time = "2025-10-15T18:21:46.29Z" }, { url = "https://files.pythonhosted.org/packages/0e/5a/a2f6773b64edb921a756eb0729068acad9fc5208a53f4a349396e9436721/pillow-12.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0fd00cac9c03256c8b2ff58f162ebcd2587ad3e1f2e397eab718c47e24d231cc", size = 5289798, upload-time = "2025-10-15T18:21:47.763Z" }, { url = "https://files.pythonhosted.org/packages/2e/05/069b1f8a2e4b5a37493da6c5868531c3f77b85e716ad7a590ef87d58730d/pillow-12.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3475b96f5908b3b16c47533daaa87380c491357d197564e0ba34ae75c0f3257", size = 4650589, upload-time = "2025-10-15T18:21:49.515Z" }, { url = "https://files.pythonhosted.org/packages/61/e3/2c820d6e9a36432503ead175ae294f96861b07600a7156154a086ba7111a/pillow-12.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:110486b79f2d112cf6add83b28b627e369219388f64ef2f960fef9ebaf54c642", size = 6230472, upload-time = "2025-10-15T18:21:51.052Z" }, @@ -2178,36 +1801,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/95/7e/f896623c3c635a90537ac093c6a618ebe1a90d87206e42309cb5d98a1b9e/pillow-12.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:b290fd8aa38422444d4b50d579de197557f182ef1068b75f5aa8558638b8d0a5", size = 6997850, upload-time = "2025-10-15T18:24:11.495Z" }, ] -[[package]] -name = "pint" -version = "0.24.4" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.11'", -] -dependencies = [ - { name = "flexcache", marker = "python_full_version < '3.11'" }, - { name = "flexparser", marker = "python_full_version < '3.11'" }, - { name = "platformdirs", marker = "python_full_version < '3.11'" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/20/bb/52b15ddf7b7706ed591134a895dbf6e41c8348171fb635e655e0a4bbb0ea/pint-0.24.4.tar.gz", hash = "sha256:35275439b574837a6cd3020a5a4a73645eb125ce4152a73a2f126bf164b91b80", size = 342225, upload-time = "2024-11-07T16:29:46.061Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/16/bd2f5904557265882108dc2e04f18abc05ab0c2b7082ae9430091daf1d5c/Pint-0.24.4-py3-none-any.whl", hash = "sha256:aa54926c8772159fcf65f82cc0d34de6768c151b32ad1deb0331291c38fe7659", size = 302029, upload-time = "2024-11-07T16:29:43.976Z" }, -] - [[package]] name = "pint" version = "0.25.2" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.11'", -] dependencies = [ - { name = "flexcache", marker = "python_full_version >= '3.11'" }, - { name = "flexparser", marker = "python_full_version >= '3.11'" }, - { name = "platformdirs", marker = "python_full_version >= '3.11'" }, - { name = "typing-extensions", marker = "python_full_version >= '3.11'" }, + { name = "flexcache" }, + { name = "flexparser" }, + { name = "platformdirs" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5f/74/bc3f671997158aef171194c3c4041e549946f4784b8690baa0626a0a164b/pint-0.25.2.tar.gz", hash = "sha256:85a45d1da8fe9c9f7477fed8aef59ad2b939af3d6611507e1a9cbdacdcd3450a", size = 254467, upload-time = "2025-11-06T22:08:09.184Z" } wheels = [ @@ -2346,19 +1948,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/90/32c9941e728d564b411d574d8ee0cf09b12ec978cb22b294995bae5549a5/pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146", size = 2107298, upload-time = "2025-11-04T13:39:04.116Z" }, - { url = "https://files.pythonhosted.org/packages/fb/a8/61c96a77fe28993d9a6fb0f4127e05430a267b235a124545d79fea46dd65/pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2", size = 1901475, upload-time = "2025-11-04T13:39:06.055Z" }, - { url = "https://files.pythonhosted.org/packages/5d/b6/338abf60225acc18cdc08b4faef592d0310923d19a87fba1faf05af5346e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97", size = 1918815, upload-time = "2025-11-04T13:39:10.41Z" }, - { url = "https://files.pythonhosted.org/packages/d1/1c/2ed0433e682983d8e8cba9c8d8ef274d4791ec6a6f24c58935b90e780e0a/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9", size = 2065567, upload-time = "2025-11-04T13:39:12.244Z" }, - { url = "https://files.pythonhosted.org/packages/b3/24/cf84974ee7d6eae06b9e63289b7b8f6549d416b5c199ca2d7ce13bbcf619/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52", size = 2230442, upload-time = "2025-11-04T13:39:13.962Z" }, - { url = "https://files.pythonhosted.org/packages/fd/21/4e287865504b3edc0136c89c9c09431be326168b1eb7841911cbc877a995/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941", size = 2350956, upload-time = "2025-11-04T13:39:15.889Z" }, - { url = "https://files.pythonhosted.org/packages/a8/76/7727ef2ffa4b62fcab916686a68a0426b9b790139720e1934e8ba797e238/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a", size = 2068253, upload-time = "2025-11-04T13:39:17.403Z" }, - { url = "https://files.pythonhosted.org/packages/d5/8c/a4abfc79604bcb4c748e18975c44f94f756f08fb04218d5cb87eb0d3a63e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c", size = 2177050, upload-time = "2025-11-04T13:39:19.351Z" }, - { url = "https://files.pythonhosted.org/packages/67/b1/de2e9a9a79b480f9cb0b6e8b6ba4c50b18d4e89852426364c66aa82bb7b3/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2", size = 2147178, upload-time = "2025-11-04T13:39:21Z" }, - { url = "https://files.pythonhosted.org/packages/16/c1/dfb33f837a47b20417500efaa0378adc6635b3c79e8369ff7a03c494b4ac/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556", size = 2341833, upload-time = "2025-11-04T13:39:22.606Z" }, - { url = "https://files.pythonhosted.org/packages/47/36/00f398642a0f4b815a9a558c4f1dca1b4020a7d49562807d7bc9ff279a6c/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49", size = 2321156, upload-time = "2025-11-04T13:39:25.843Z" }, - { url = "https://files.pythonhosted.org/packages/7e/70/cad3acd89fde2010807354d978725ae111ddf6d0ea46d1ea1775b5c1bd0c/pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba", size = 1989378, upload-time = "2025-11-04T13:39:27.92Z" }, - { url = "https://files.pythonhosted.org/packages/76/92/d338652464c6c367e5608e4488201702cd1cbb0f33f7b6a85a60fe5f3720/pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9", size = 2013622, upload-time = "2025-11-04T13:39:29.848Z" }, { url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" }, { url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" }, { url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" }, @@ -2437,14 +2026,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, - { url = "https://files.pythonhosted.org/packages/e6/b0/1a2aa41e3b5a4ba11420aba2d091b2d17959c8d1519ece3627c371951e73/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8", size = 2103351, upload-time = "2025-11-04T13:43:02.058Z" }, - { url = "https://files.pythonhosted.org/packages/a4/ee/31b1f0020baaf6d091c87900ae05c6aeae101fa4e188e1613c80e4f1ea31/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a", size = 1925363, upload-time = "2025-11-04T13:43:05.159Z" }, - { url = "https://files.pythonhosted.org/packages/e1/89/ab8e86208467e467a80deaca4e434adac37b10a9d134cd2f99b28a01e483/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b", size = 2135615, upload-time = "2025-11-04T13:43:08.116Z" }, - { url = "https://files.pythonhosted.org/packages/99/0a/99a53d06dd0348b2008f2f30884b34719c323f16c3be4e6cc1203b74a91d/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2", size = 2175369, upload-time = "2025-11-04T13:43:12.49Z" }, - { url = "https://files.pythonhosted.org/packages/6d/94/30ca3b73c6d485b9bb0bc66e611cff4a7138ff9736b7e66bcf0852151636/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093", size = 2144218, upload-time = "2025-11-04T13:43:15.431Z" }, - { url = "https://files.pythonhosted.org/packages/87/57/31b4f8e12680b739a91f472b5671294236b82586889ef764b5fbc6669238/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a", size = 2329951, upload-time = "2025-11-04T13:43:18.062Z" }, - { url = "https://files.pythonhosted.org/packages/7d/73/3c2c8edef77b8f7310e6fb012dbc4b8551386ed575b9eb6fb2506e28a7eb/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963", size = 2318428, upload-time = "2025-11-04T13:43:20.679Z" }, - { url = "https://files.pythonhosted.org/packages/2f/02/8559b1f26ee0d502c74f9cca5c0d2fd97e967e083e006bbbb4e97f3a043a/pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a", size = 2147009, upload-time = "2025-11-04T13:43:23.286Z" }, { url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" }, { url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" }, { url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" }, @@ -2461,8 +2042,7 @@ version = "8.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "compress-pickle", extra = ["lz4"] }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy" }, { name = "pydantic" }, { name = "ruamel-yaml" }, { name = "semver" }, @@ -2482,8 +2062,7 @@ dependencies = [ { name = "beautifulsoup4" }, { name = "docutils" }, { name = "pygments" }, - { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "sphinx" }, { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/00/20/bb50f9de3a6de69e6abd6b087b52fa2418a0418b19597601605f855ad044/pydata_sphinx_theme-0.16.1.tar.gz", hash = "sha256:a08b7f0b7f70387219dc659bff0893a7554d5eb39b59d3b8ef37b8401b7642d7", size = 2412693, upload-time = "2024-12-17T10:53:39.537Z" } @@ -2533,12 +2112,10 @@ version = "9.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, { name = "pygments" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/07/56/f013048ac4bc4c1d9be45afd4ab209ea62822fb1598f40687e6bf45dcea4/pytest-9.0.1.tar.gz", hash = "sha256:3e9c069ea73583e255c3b21cf46b8d3c56f6e3a1a8f6da94ccb0fcf57b9d73c8", size = 1564125, upload-time = "2025-11-12T13:05:09.333Z" } wheels = [ @@ -2550,7 +2127,6 @@ name = "pytest-asyncio" version = "1.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "backports-asyncio-runner", marker = "python_full_version < '3.11'" }, { name = "pytest" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] @@ -2625,15 +2201,6 @@ version = "6.0.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, - { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, - { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, - { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, - { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, - { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, - { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, - { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, - { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, @@ -2692,16 +2259,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750, upload-time = "2025-09-08T23:10:18.157Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/67/b9/52aa9ec2867528b54f1e60846728d8b4d84726630874fee3a91e66c7df81/pyzmq-27.1.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:508e23ec9bc44c0005c4946ea013d9317ae00ac67778bd47519fdf5a0e930ff4", size = 1329850, upload-time = "2025-09-08T23:07:26.274Z" }, - { url = "https://files.pythonhosted.org/packages/99/64/5653e7b7425b169f994835a2b2abf9486264401fdef18df91ddae47ce2cc/pyzmq-27.1.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:507b6f430bdcf0ee48c0d30e734ea89ce5567fd7b8a0f0044a369c176aa44556", size = 906380, upload-time = "2025-09-08T23:07:29.78Z" }, - { url = "https://files.pythonhosted.org/packages/73/78/7d713284dbe022f6440e391bd1f3c48d9185673878034cfb3939cdf333b2/pyzmq-27.1.0-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf7b38f9fd7b81cb6d9391b2946382c8237fd814075c6aa9c3b746d53076023b", size = 666421, upload-time = "2025-09-08T23:07:31.263Z" }, - { url = "https://files.pythonhosted.org/packages/30/76/8f099f9d6482450428b17c4d6b241281af7ce6a9de8149ca8c1c649f6792/pyzmq-27.1.0-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03ff0b279b40d687691a6217c12242ee71f0fba28bf8626ff50e3ef0f4410e1e", size = 854149, upload-time = "2025-09-08T23:07:33.17Z" }, - { url = "https://files.pythonhosted.org/packages/59/f0/37fbfff06c68016019043897e4c969ceab18bde46cd2aca89821fcf4fb2e/pyzmq-27.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:677e744fee605753eac48198b15a2124016c009a11056f93807000ab11ce6526", size = 1655070, upload-time = "2025-09-08T23:07:35.205Z" }, - { url = "https://files.pythonhosted.org/packages/47/14/7254be73f7a8edc3587609554fcaa7bfd30649bf89cd260e4487ca70fdaa/pyzmq-27.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dd2fec2b13137416a1c5648b7009499bcc8fea78154cd888855fa32514f3dad1", size = 2033441, upload-time = "2025-09-08T23:07:37.432Z" }, - { url = "https://files.pythonhosted.org/packages/22/dc/49f2be26c6f86f347e796a4d99b19167fc94503f0af3fd010ad262158822/pyzmq-27.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:08e90bb4b57603b84eab1d0ca05b3bbb10f60c1839dc471fc1c9e1507bef3386", size = 1891529, upload-time = "2025-09-08T23:07:39.047Z" }, - { url = "https://files.pythonhosted.org/packages/a3/3e/154fb963ae25be70c0064ce97776c937ecc7d8b0259f22858154a9999769/pyzmq-27.1.0-cp310-cp310-win32.whl", hash = "sha256:a5b42d7a0658b515319148875fcb782bbf118dd41c671b62dae33666c2213bda", size = 567276, upload-time = "2025-09-08T23:07:40.695Z" }, - { url = "https://files.pythonhosted.org/packages/62/b2/f4ab56c8c595abcb26b2be5fd9fa9e6899c1e5ad54964e93ae8bb35482be/pyzmq-27.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:c0bb87227430ee3aefcc0ade2088100e528d5d3298a0a715a64f3d04c60ba02f", size = 632208, upload-time = "2025-09-08T23:07:42.298Z" }, - { url = "https://files.pythonhosted.org/packages/3b/e3/be2cc7ab8332bdac0522fdb64c17b1b6241a795bee02e0196636ec5beb79/pyzmq-27.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:9a916f76c2ab8d045b19f2286851a38e9ac94ea91faf65bd64735924522a8b32", size = 559766, upload-time = "2025-09-08T23:07:43.869Z" }, { url = "https://files.pythonhosted.org/packages/06/5d/305323ba86b284e6fcb0d842d6adaa2999035f70f8c38a9b6d21ad28c3d4/pyzmq-27.1.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:226b091818d461a3bef763805e75685e478ac17e9008f49fce2d3e52b3d58b86", size = 1333328, upload-time = "2025-09-08T23:07:45.946Z" }, { url = "https://files.pythonhosted.org/packages/bd/a0/fc7e78a23748ad5443ac3275943457e8452da67fda347e05260261108cbc/pyzmq-27.1.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0790a0161c281ca9723f804871b4027f2e8b5a528d357c8952d08cd1a9c15581", size = 908803, upload-time = "2025-09-08T23:07:47.551Z" }, { url = "https://files.pythonhosted.org/packages/7e/22/37d15eb05f3bdfa4abea6f6d96eb3bb58585fbd3e4e0ded4e743bc650c97/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c895a6f35476b0c3a54e3eb6ccf41bf3018de937016e6e18748317f25d4e925f", size = 668836, upload-time = "2025-09-08T23:07:49.436Z" }, @@ -2744,11 +2301,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c4/59/a5f38970f9bf07cee96128de79590bb354917914a9be11272cfc7ff26af0/pyzmq-27.1.0-cp314-cp314t-win32.whl", hash = "sha256:1f0b2a577fd770aa6f053211a55d1c47901f4d537389a034c690291485e5fe92", size = 587472, upload-time = "2025-09-08T23:08:58.18Z" }, { url = "https://files.pythonhosted.org/packages/70/d8/78b1bad170f93fcf5e3536e70e8fadac55030002275c9a29e8f5719185de/pyzmq-27.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:19c9468ae0437f8074af379e986c5d3d7d7bfe033506af442e8c879732bedbe0", size = 661401, upload-time = "2025-09-08T23:08:59.802Z" }, { url = "https://files.pythonhosted.org/packages/81/d6/4bfbb40c9a0b42fc53c7cf442f6385db70b40f74a783130c5d0a5aa62228/pyzmq-27.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dc5dbf68a7857b59473f7df42650c621d7e8923fb03fa74a526890f4d33cc4d7", size = 575170, upload-time = "2025-09-08T23:09:01.418Z" }, - { url = "https://files.pythonhosted.org/packages/f3/81/a65e71c1552f74dec9dff91d95bafb6e0d33338a8dfefbc88aa562a20c92/pyzmq-27.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c17e03cbc9312bee223864f1a2b13a99522e0dc9f7c5df0177cd45210ac286e6", size = 836266, upload-time = "2025-09-08T23:09:40.048Z" }, - { url = "https://files.pythonhosted.org/packages/58/ed/0202ca350f4f2b69faa95c6d931e3c05c3a397c184cacb84cb4f8f42f287/pyzmq-27.1.0-pp310-pypy310_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f328d01128373cb6763823b2b4e7f73bdf767834268c565151eacb3b7a392f90", size = 800206, upload-time = "2025-09-08T23:09:41.902Z" }, - { url = "https://files.pythonhosted.org/packages/47/42/1ff831fa87fe8f0a840ddb399054ca0009605d820e2b44ea43114f5459f4/pyzmq-27.1.0-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c1790386614232e1b3a40a958454bdd42c6d1811837b15ddbb052a032a43f62", size = 567747, upload-time = "2025-09-08T23:09:43.741Z" }, - { url = "https://files.pythonhosted.org/packages/d1/db/5c4d6807434751e3f21231bee98109aa57b9b9b55e058e450d0aef59b70f/pyzmq-27.1.0-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:448f9cb54eb0cee4732b46584f2710c8bc178b0e5371d9e4fc8125201e413a74", size = 747371, upload-time = "2025-09-08T23:09:45.575Z" }, - { url = "https://files.pythonhosted.org/packages/26/af/78ce193dbf03567eb8c0dc30e3df2b9e56f12a670bf7eb20f9fb532c7e8a/pyzmq-27.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:05b12f2d32112bf8c95ef2e74ec4f1d4beb01f8b5e703b38537f8849f92cb9ba", size = 544862, upload-time = "2025-09-08T23:09:47.448Z" }, { url = "https://files.pythonhosted.org/packages/4c/c6/c4dcdecdbaa70969ee1fdced6d7b8f60cfabe64d25361f27ac4665a70620/pyzmq-27.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:18770c8d3563715387139060d37859c02ce40718d1faf299abddcdcc6a649066", size = 836265, upload-time = "2025-09-08T23:09:49.376Z" }, { url = "https://files.pythonhosted.org/packages/3e/79/f38c92eeaeb03a2ccc2ba9866f0439593bb08c5e3b714ac1d553e5c96e25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:ac25465d42f92e990f8d8b0546b01c391ad431c3bf447683fdc40565941d0604", size = 800208, upload-time = "2025-09-08T23:09:51.073Z" }, { url = "https://files.pythonhosted.org/packages/49/0e/3f0d0d335c6b3abb9b7b723776d0b21fa7f3a6c819a0db6097059aada160/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53b40f8ae006f2734ee7608d59ed661419f087521edbfc2149c3932e9c14808c", size = 567747, upload-time = "2025-09-08T23:09:52.698Z" }, @@ -2800,20 +2352,6 @@ version = "0.30.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/06/0c/0c411a0ec64ccb6d104dcabe0e713e05e153a9a2c3c2bd2b32ce412166fe/rpds_py-0.30.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:679ae98e00c0e8d68a7fda324e16b90fd5260945b45d3b824c892cec9eea3288", size = 370490, upload-time = "2025-11-30T20:21:33.256Z" }, - { url = "https://files.pythonhosted.org/packages/19/6a/4ba3d0fb7297ebae71171822554abe48d7cab29c28b8f9f2c04b79988c05/rpds_py-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cc2206b76b4f576934f0ed374b10d7ca5f457858b157ca52064bdfc26b9fc00", size = 359751, upload-time = "2025-11-30T20:21:34.591Z" }, - { url = "https://files.pythonhosted.org/packages/cd/7c/e4933565ef7f7a0818985d87c15d9d273f1a649afa6a52ea35ad011195ea/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:389a2d49eded1896c3d48b0136ead37c48e221b391c052fba3f4055c367f60a6", size = 389696, upload-time = "2025-11-30T20:21:36.122Z" }, - { url = "https://files.pythonhosted.org/packages/5e/01/6271a2511ad0815f00f7ed4390cf2567bec1d4b1da39e2c27a41e6e3b4de/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:32c8528634e1bf7121f3de08fa85b138f4e0dc47657866630611b03967f041d7", size = 403136, upload-time = "2025-11-30T20:21:37.728Z" }, - { url = "https://files.pythonhosted.org/packages/55/64/c857eb7cd7541e9b4eee9d49c196e833128a55b89a9850a9c9ac33ccf897/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f207f69853edd6f6700b86efb84999651baf3789e78a466431df1331608e5324", size = 524699, upload-time = "2025-11-30T20:21:38.92Z" }, - { url = "https://files.pythonhosted.org/packages/9c/ed/94816543404078af9ab26159c44f9e98e20fe47e2126d5d32c9d9948d10a/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:67b02ec25ba7a9e8fa74c63b6ca44cf5707f2fbfadae3ee8e7494297d56aa9df", size = 412022, upload-time = "2025-11-30T20:21:40.407Z" }, - { url = "https://files.pythonhosted.org/packages/61/b5/707f6cf0066a6412aacc11d17920ea2e19e5b2f04081c64526eb35b5c6e7/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0e95f6819a19965ff420f65578bacb0b00f251fefe2c8b23347c37174271f3", size = 390522, upload-time = "2025-11-30T20:21:42.17Z" }, - { url = "https://files.pythonhosted.org/packages/13/4e/57a85fda37a229ff4226f8cbcf09f2a455d1ed20e802ce5b2b4a7f5ed053/rpds_py-0.30.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:a452763cc5198f2f98898eb98f7569649fe5da666c2dc6b5ddb10fde5a574221", size = 404579, upload-time = "2025-11-30T20:21:43.769Z" }, - { url = "https://files.pythonhosted.org/packages/f9/da/c9339293513ec680a721e0e16bf2bac3db6e5d7e922488de471308349bba/rpds_py-0.30.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e0b65193a413ccc930671c55153a03ee57cecb49e6227204b04fae512eb657a7", size = 421305, upload-time = "2025-11-30T20:21:44.994Z" }, - { url = "https://files.pythonhosted.org/packages/f9/be/522cb84751114f4ad9d822ff5a1aa3c98006341895d5f084779b99596e5c/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:858738e9c32147f78b3ac24dc0edb6610000e56dc0f700fd5f651d0a0f0eb9ff", size = 572503, upload-time = "2025-11-30T20:21:46.91Z" }, - { url = "https://files.pythonhosted.org/packages/a2/9b/de879f7e7ceddc973ea6e4629e9b380213a6938a249e94b0cdbcc325bb66/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:da279aa314f00acbb803da1e76fa18666778e8a8f83484fba94526da5de2cba7", size = 598322, upload-time = "2025-11-30T20:21:48.709Z" }, - { url = "https://files.pythonhosted.org/packages/48/ac/f01fc22efec3f37d8a914fc1b2fb9bcafd56a299edbe96406f3053edea5a/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7c64d38fb49b6cdeda16ab49e35fe0da2e1e9b34bc38bd78386530f218b37139", size = 560792, upload-time = "2025-11-30T20:21:50.024Z" }, - { url = "https://files.pythonhosted.org/packages/e2/da/4e2b19d0f131f35b6146425f846563d0ce036763e38913d917187307a671/rpds_py-0.30.0-cp310-cp310-win32.whl", hash = "sha256:6de2a32a1665b93233cde140ff8b3467bdb9e2af2b91079f0333a0974d12d464", size = 221901, upload-time = "2025-11-30T20:21:51.32Z" }, - { url = "https://files.pythonhosted.org/packages/96/cb/156d7a5cf4f78a7cc571465d8aec7a3c447c94f6749c5123f08438bcf7bc/rpds_py-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:1726859cd0de969f88dc8673bdd954185b9104e05806be64bcd87badbe313169", size = 235823, upload-time = "2025-11-30T20:21:52.505Z" }, { url = "https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425", size = 370157, upload-time = "2025-11-30T20:21:53.789Z" }, { url = "https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d", size = 359676, upload-time = "2025-11-30T20:21:55.475Z" }, { url = "https://files.pythonhosted.org/packages/84/86/04dbba1b087227747d64d80c3b74df946b986c57af0a9f0c98726d4d7a3b/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4", size = 389938, upload-time = "2025-11-30T20:21:57.079Z" }, @@ -2934,16 +2472,6 @@ version = "0.2.15" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ea/97/60fda20e2fb54b83a61ae14648b0817c8f5d84a3821e40bfbdae1437026a/ruamel_yaml_clib-0.2.15.tar.gz", hash = "sha256:46e4cc8c43ef6a94885f72512094e482114a8a706d3c555a34ed4b0d20200600", size = 225794, upload-time = "2025-11-16T16:12:59.761Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/5a/4ab767cd42dcd65b83c323e1620d7c01ee60a52f4032fb7b61501f45f5c2/ruamel_yaml_clib-0.2.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:88eea8baf72f0ccf232c22124d122a7f26e8a24110a0273d9bcddcb0f7e1fa03", size = 147454, upload-time = "2025-11-16T16:13:02.54Z" }, - { url = "https://files.pythonhosted.org/packages/40/44/184173ac1e74fd35d308108bcbf83904d6ef8439c70763189225a166b238/ruamel_yaml_clib-0.2.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9b6f7d74d094d1f3a4e157278da97752f16ee230080ae331fcc219056ca54f77", size = 132467, upload-time = "2025-11-16T16:13:03.539Z" }, - { url = "https://files.pythonhosted.org/packages/49/1b/2d2077a25fe682ae335007ca831aff42e3cbc93c14066675cf87a6c7fc3e/ruamel_yaml_clib-0.2.15-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4be366220090d7c3424ac2b71c90d1044ea34fca8c0b88f250064fd06087e614", size = 693454, upload-time = "2025-11-16T20:22:41.083Z" }, - { url = "https://files.pythonhosted.org/packages/90/16/e708059c4c429ad2e33be65507fc1730641e5f239fb2964efc1ba6edea94/ruamel_yaml_clib-0.2.15-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f66f600833af58bea694d5892453f2270695b92200280ee8c625ec5a477eed3", size = 700345, upload-time = "2025-11-16T16:13:04.771Z" }, - { url = "https://files.pythonhosted.org/packages/d9/79/0e8ef51df1f0950300541222e3332f20707a9c210b98f981422937d1278c/ruamel_yaml_clib-0.2.15-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da3d6adadcf55a93c214d23941aef4abfd45652110aed6580e814152f385b862", size = 731306, upload-time = "2025-11-16T16:13:06.312Z" }, - { url = "https://files.pythonhosted.org/packages/a6/f4/2cdb54b142987ddfbd01fc45ac6bd882695fbcedb9d8bbf796adc3fc3746/ruamel_yaml_clib-0.2.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e9fde97ecb7bb9c41261c2ce0da10323e9227555c674989f8d9eb7572fc2098d", size = 692415, upload-time = "2025-11-16T16:13:07.465Z" }, - { url = "https://files.pythonhosted.org/packages/a0/07/40b5fc701cce8240a3e2d26488985d3bbdc446e9fe397c135528d412fea6/ruamel_yaml_clib-0.2.15-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:05c70f7f86be6f7bee53794d80050a28ae7e13e4a0087c1839dcdefd68eb36b6", size = 705007, upload-time = "2025-11-16T20:22:42.856Z" }, - { url = "https://files.pythonhosted.org/packages/82/19/309258a1df6192fb4a77ffa8eae3e8150e8d0ffa56c1b6fa92e450ba2740/ruamel_yaml_clib-0.2.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6f1d38cbe622039d111b69e9ca945e7e3efebb30ba998867908773183357f3ed", size = 723974, upload-time = "2025-11-16T16:13:08.72Z" }, - { url = "https://files.pythonhosted.org/packages/67/3a/d6ee8263b521bfceb5cd2faeb904a15936480f2bb01c7ff74a14ec058ca4/ruamel_yaml_clib-0.2.15-cp310-cp310-win32.whl", hash = "sha256:fe239bdfdae2302e93bd6e8264bd9b71290218fff7084a9db250b55caaccf43f", size = 102836, upload-time = "2025-11-16T16:13:10.27Z" }, - { url = "https://files.pythonhosted.org/packages/ed/03/92aeb5c69018387abc49a8bb4f83b54a0471d9ef48e403b24bac68f01381/ruamel_yaml_clib-0.2.15-cp310-cp310-win_amd64.whl", hash = "sha256:468858e5cbde0198337e6a2a78eda8c3fb148bdf4c6498eaf4bc9ba3f8e780bd", size = 121917, upload-time = "2025-11-16T16:13:12.145Z" }, { url = "https://files.pythonhosted.org/packages/2c/80/8ce7b9af532aa94dd83360f01ce4716264db73de6bc8efd22c32341f6658/ruamel_yaml_clib-0.2.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c583229f336682b7212a43d2fa32c30e643d3076178fb9f7a6a14dde85a2d8bd", size = 147998, upload-time = "2025-11-16T16:13:13.241Z" }, { url = "https://files.pythonhosted.org/packages/53/09/de9d3f6b6701ced5f276d082ad0f980edf08ca67114523d1b9264cd5e2e0/ruamel_yaml_clib-0.2.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56ea19c157ed8c74b6be51b5fa1c3aff6e289a041575f0556f66e5fb848bb137", size = 132743, upload-time = "2025-11-16T16:13:14.265Z" }, { url = "https://files.pythonhosted.org/packages/0e/f7/73a9b517571e214fe5c246698ff3ed232f1ef863c8ae1667486625ec688a/ruamel_yaml_clib-0.2.15-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5fea0932358e18293407feb921d4f4457db837b67ec1837f87074667449f9401", size = 731459, upload-time = "2025-11-16T20:22:44.338Z" }, @@ -2991,9 +2519,9 @@ name = "scanspec" version = "0.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "click", marker = "python_full_version >= '3.11'" }, - { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "pydantic", marker = "python_full_version >= '3.11'" }, + { name = "click" }, + { name = "numpy" }, + { name = "pydantic" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a7/56/b5359e055e6f4bd1bbd491be2d7e2f0a47dc475f8ff69413f0478a5e7f7f/scanspec-0.9.0.tar.gz", hash = "sha256:5aa03ca6fedec8a4b86cc0773d50e880f2b5b8b6d11cec24c4ceeed26e8e667d", size = 269706, upload-time = "2025-10-03T13:49:20.023Z" } wheels = [ @@ -3005,23 +2533,23 @@ name = "secop-ophyd" source = { editable = "." } dependencies = [ { name = "frappy-core" }, - { name = "ophyd-async", version = "0.12.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "ophyd-async", version = "0.14.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "ophyd-async" }, ] [package.dev-dependencies] dev = [ + { name = "autoflake" }, { name = "black" }, { name = "bluesky" }, { name = "cycler" }, { name = "ipykernel" }, { name = "isort" }, + { name = "jinja2" }, { name = "matplotlib" }, { name = "mlzlog" }, { name = "mypy" }, { name = "myst-parser" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy" }, { name = "numpydoc" }, { name = "ophyd" }, { name = "pep8-naming" }, @@ -3032,10 +2560,8 @@ dev = [ { name = "pytest-cov" }, { name = "pytest-xprocess" }, { name = "python-dotenv" }, - { name = "snakefmt", version = "0.10.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "snakefmt", version = "0.11.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "snakefmt" }, + { name = "sphinx" }, { name = "sphinx-autodoc2" }, { name = "sphinx-click" }, { name = "sphinx-copybutton" }, @@ -3052,11 +2578,13 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ + { name = "autoflake" }, { name = "black" }, { name = "bluesky" }, { name = "cycler" }, { name = "ipykernel" }, { name = "isort" }, + { name = "jinja2" }, { name = "matplotlib" }, { name = "mlzlog" }, { name = "mypy" }, @@ -3100,33 +2628,13 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, ] -[[package]] -name = "snakefmt" -version = "0.10.3" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.11'", -] -dependencies = [ - { name = "black", marker = "python_full_version < '3.11'" }, - { name = "click", marker = "python_full_version < '3.11'" }, - { name = "toml", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c5/ae/91b5fe5334ce8337c9b7fb0985a5649cad3491c68f2f52ffff03ef694346/snakefmt-0.10.3.tar.gz", hash = "sha256:423cfbe65beda62157b1871ad6244e9349c1313a57de1708752b5bcbf07c32e3", size = 27972, upload-time = "2025-03-21T12:25:05.974Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/04/b87d7b00dd83c540745a98dac01e2120282391d7a0603ebd83ff99f18b1c/snakefmt-0.10.3-py3-none-any.whl", hash = "sha256:f4e2f058aa96e5bb5dbd634efbe8e2f85e6c8e77b839e95041222514cdc8bc57", size = 28196, upload-time = "2025-03-21T12:25:04.637Z" }, -] - [[package]] name = "snakefmt" version = "0.11.2" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.11'", -] dependencies = [ - { name = "black", marker = "python_full_version >= '3.11'" }, - { name = "click", marker = "python_full_version >= '3.11'" }, + { name = "black" }, + { name = "click" }, ] sdist = { url = "https://files.pythonhosted.org/packages/90/93/5cfa69ca18e6a73fe2816dfef52969e770d6e90fe9bf978c9d85232dc755/snakefmt-0.11.2.tar.gz", hash = "sha256:e9ad3758401e0291f7a45360075c5738386b58a1b160d38827b40630d9f735e1", size = 118127, upload-time = "2025-09-04T00:58:47.214Z" } wheels = [ @@ -3151,62 +2659,28 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c", size = 36679, upload-time = "2025-08-27T15:39:50.179Z" }, ] -[[package]] -name = "sphinx" -version = "8.1.3" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.11'", -] -dependencies = [ - { name = "alabaster", marker = "python_full_version < '3.11'" }, - { name = "babel", marker = "python_full_version < '3.11'" }, - { name = "colorama", marker = "python_full_version < '3.11' and sys_platform == 'win32'" }, - { name = "docutils", marker = "python_full_version < '3.11'" }, - { name = "imagesize", marker = "python_full_version < '3.11'" }, - { name = "jinja2", marker = "python_full_version < '3.11'" }, - { name = "packaging", marker = "python_full_version < '3.11'" }, - { name = "pygments", marker = "python_full_version < '3.11'" }, - { name = "requests", marker = "python_full_version < '3.11'" }, - { name = "snowballstemmer", marker = "python_full_version < '3.11'" }, - { name = "sphinxcontrib-applehelp", marker = "python_full_version < '3.11'" }, - { name = "sphinxcontrib-devhelp", marker = "python_full_version < '3.11'" }, - { name = "sphinxcontrib-htmlhelp", marker = "python_full_version < '3.11'" }, - { name = "sphinxcontrib-jsmath", marker = "python_full_version < '3.11'" }, - { name = "sphinxcontrib-qthelp", marker = "python_full_version < '3.11'" }, - { name = "sphinxcontrib-serializinghtml", marker = "python_full_version < '3.11'" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/be0b61178fe2cdcb67e2a92fc9ebb488e3c51c4f74a36a7824c0adf23425/sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927", size = 8184611, upload-time = "2024-10-13T20:27:13.93Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/26/60/1ddff83a56d33aaf6f10ec8ce84b4c007d9368b21008876fceda7e7381ef/sphinx-8.1.3-py3-none-any.whl", hash = "sha256:09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2", size = 3487125, upload-time = "2024-10-13T20:27:10.448Z" }, -] - [[package]] name = "sphinx" version = "8.2.3" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.11'", -] -dependencies = [ - { name = "alabaster", marker = "python_full_version >= '3.11'" }, - { name = "babel", marker = "python_full_version >= '3.11'" }, - { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" }, - { name = "docutils", marker = "python_full_version >= '3.11'" }, - { name = "imagesize", marker = "python_full_version >= '3.11'" }, - { name = "jinja2", marker = "python_full_version >= '3.11'" }, - { name = "packaging", marker = "python_full_version >= '3.11'" }, - { name = "pygments", marker = "python_full_version >= '3.11'" }, - { name = "requests", marker = "python_full_version >= '3.11'" }, - { name = "roman-numerals-py", marker = "python_full_version >= '3.11'" }, - { name = "snowballstemmer", marker = "python_full_version >= '3.11'" }, - { name = "sphinxcontrib-applehelp", marker = "python_full_version >= '3.11'" }, - { name = "sphinxcontrib-devhelp", marker = "python_full_version >= '3.11'" }, - { name = "sphinxcontrib-htmlhelp", marker = "python_full_version >= '3.11'" }, - { name = "sphinxcontrib-jsmath", marker = "python_full_version >= '3.11'" }, - { name = "sphinxcontrib-qthelp", marker = "python_full_version >= '3.11'" }, - { name = "sphinxcontrib-serializinghtml", marker = "python_full_version >= '3.11'" }, +dependencies = [ + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils" }, + { name = "imagesize" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "roman-numerals-py" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, ] sdist = { url = "https://files.pythonhosted.org/packages/38/ad/4360e50ed56cb483667b8e6dadf2d3fda62359593faabbe749a27c4eaca6/sphinx-8.2.3.tar.gz", hash = "sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348", size = 8321876, upload-time = "2025-03-02T22:31:59.658Z" } wheels = [ @@ -3219,7 +2693,6 @@ version = "0.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "astroid" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/17/5f/5350046d1aa1a56b063ae08b9ad871025335c9d55fe2372896ea48711da9/sphinx_autodoc2-0.5.0.tar.gz", hash = "sha256:7d76044aa81d6af74447080182b6868c7eb066874edc835e8ddf810735b6565a", size = 115077, upload-time = "2023-11-27T07:27:51.407Z" } @@ -3234,8 +2707,7 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "docutils" }, - { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "sphinx" }, ] sdist = { url = "https://files.pythonhosted.org/packages/de/4b/c433ea57136eac0ccb8d76d33355783f1e6e77f1f13dc7d8f15dba2dc024/sphinx_click-6.1.0.tar.gz", hash = "sha256:c702e0751c1a0b6ad649e4f7faebd0dc09a3cc7ca3b50f959698383772f50eef", size = 26855, upload-time = "2025-09-11T11:05:45.53Z" } wheels = [ @@ -3247,8 +2719,7 @@ name = "sphinx-copybutton" version = "0.5.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "sphinx" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fc/2b/a964715e7f5295f77509e59309959f4125122d648f86b4fe7d70ca1d882c/sphinx-copybutton-0.5.2.tar.gz", hash = "sha256:4cf17c82fb9646d1bc9ca92ac280813a3b605d8c421225fd9913154103ee1fbd", size = 23039, upload-time = "2023-04-14T08:10:22.998Z" } wheels = [ @@ -3260,8 +2731,7 @@ name = "sphinx-design" version = "0.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "sphinx" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2b/69/b34e0cb5336f09c6866d53b4a19d76c227cdec1bbc7ac4de63ca7d58c9c7/sphinx_design-0.6.1.tar.gz", hash = "sha256:b44eea3719386d04d765c1a8257caca2b3e6f8421d7b3a5e742c0fd45f84e632", size = 2193689, upload-time = "2024-08-02T13:48:44.277Z" } wheels = [ @@ -3310,8 +2780,7 @@ version = "1.2.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyyaml" }, - { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "sphinx" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f5/49/c6ddfe709a4ab76ac6e5a00e696f73626b2c189dc1e1965a361ec102e6cc/sphinxcontrib_mermaid-1.2.3.tar.gz", hash = "sha256:358699d0ec924ef679b41873d9edd97d0773446daf9760c75e18dc0adfd91371", size = 18885, upload-time = "2025-11-26T04:18:32.43Z" } wheels = [ @@ -3371,15 +2840,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload-time = "2025-04-02T08:25:07.678Z" }, ] -[[package]] -name = "toml" -version = "0.10.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" }, -] - [[package]] name = "tomli" version = "2.3.0" @@ -3513,7 +2973,7 @@ name = "velocity-profile" version = "1.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f5/a0/edb76fc2b71a3cd0717a40184f512f8249a64443c472541939d915fab70d/velocity_profile-1.0.0.tar.gz", hash = "sha256:520a4dbc69519744c89438571ffe542f512ae528232f51e73cde9dbaaee2e086", size = 29716, upload-time = "2024-06-13T10:49:54.951Z" } wheels = [ @@ -3528,7 +2988,6 @@ dependencies = [ { name = "distlib" }, { name = "filelock" }, { name = "platformdirs" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/20/28/e6f1a6f655d620846bd9df527390ecc26b3805a0c5989048c210e22c5ca9/virtualenv-20.35.4.tar.gz", hash = "sha256:643d3914d73d3eeb0c552cbb12d7e82adf0e504dbf86a3182f8771a153a1971c", size = 6028799, upload-time = "2025-10-29T06:57:40.511Z" } wheels = [