From 9724d9ce032eeb3a3c3f72cb07447a94b15d67d5 Mon Sep 17 00:00:00 2001 From: "Edgar Y. Walker" Date: Tue, 20 May 2025 06:41:19 +0000 Subject: [PATCH 01/34] refactor: clean up --- src/orcabridge/base.py | 143 +++++++++++++++++++++++++++++----------- src/orcabridge/types.py | 9 ++- 2 files changed, 109 insertions(+), 43 deletions(-) diff --git a/src/orcabridge/base.py b/src/orcabridge/base.py index 3cbede1..c855c1a 100644 --- a/src/orcabridge/base.py +++ b/src/orcabridge/base.py @@ -1,5 +1,5 @@ from orcabridge.hashing import HashableMixin -from .types import Tag, Packet +from orcabridge.types import Tag, Packet from typing import ( Optional, Tuple, @@ -10,7 +10,7 @@ Iterator, ) from collections.abc import Collection -from typing import Any, List, Tuple +import threading class Operation(HashableMixin): @@ -40,9 +40,12 @@ def keys(self, *streams: "SyncStream") -> Tuple[List[str], List[str]]: @property def label(self) -> str: """ - Overwrite this method to attain a custom label logic for the operation. + Returns a human-readable label for this operation. + Default implementation returns the provided label or class name if no label was provided. """ - return self._label + if self._label: + return self._label + return self.__class__.__name__ @label.setter def label(self, value: str) -> None: @@ -68,10 +71,7 @@ def __call__(self, *streams: "SyncStream", **kwargs) -> "SyncStream": # label the output_stream with the invocation information output_stream.invocation = invocation - # delay import to avoid circular import - from .tracker import Tracker - - # reg + # register the invocation with active trackers active_trackers = Tracker.get_active_trackers() for tracker in active_trackers: tracker.record(invocation) @@ -81,16 +81,64 @@ def __call__(self, *streams: "SyncStream", **kwargs) -> "SyncStream": def __repr__(self): return self.__class__.__name__ + def __str__(self): + if self._label is not None: + return f"{self.__class__.__name__}({self._label})" + return self.__class__.__name__ + def forward(self, *streams: "SyncStream") -> "SyncStream": ... +class Tracker: + """ + A tracker is a class that can track the invocations of operations. Only "active" trackers + participate in tracking and its `record` method gets called on each invocation of an operation. + Multiple trackers can be active at any time. + """ + + _local = threading.local() + + @classmethod + def get_active_trackers(cls) -> List["Tracker"]: + if hasattr(cls._local, "active_trackers"): + return cls._local.active_trackers + return [] + + def __init__(self): + self.active = False + + def activate(self) -> None: + """ + Activate the tracker. This is a no-op if the tracker is already active. + """ + if not self.active: + if not hasattr(self._local, "active_trackers"): + self._local.active_trackers = [] + self._local.active_trackers.append(self) + self.active = True + + def deactivate(self) -> None: + # Remove this tracker from active trackers + if hasattr(self._local, "active_trackers") and self.active: + self._local.active_trackers.remove(self) + self.active = False + + def __enter__(self): + self.activate() + return self + + def __exit__(self, exc_type, exc_val, ext_tb): + self.deactivate() + + def record(self, invocation: "Invocation") -> None: ... + + class Invocation(HashableMixin): """ This class represents an invocation of an operation on a collection of streams. - It contains the operation, the invocation ID, and the streams that were used - in the invocation. - The invocation ID is a unique identifier for the invocation and is used to - track the invocation in the tracker. + It contains the operation and the streams that were used in the invocation. + Note that the collection of streams may be empty, in which case the invocation + likely corresponds to a source operation. """ def __init__( @@ -111,8 +159,8 @@ def keys(self) -> Tuple[Collection[str], Collection[str]]: return self.operation.keys(*self.streams) def identity_structure(self) -> int: - # default implementation is streams order sensitive. If an operation does - # not depend on the order of the streams, it should override this method + # Identity of an invocation is entirely dependend on + # the operation's identity structure upon invocation return self.operation.identity_structure(*self.streams) def __eq__(self, other: Any) -> bool: @@ -145,16 +193,29 @@ def __init__(self, label: Optional[str] = None, **kwargs) -> None: self._label = label def identity_structure(self) -> Any: + """ + Identity structure of a stream is deferred to the identity structure + of the associated invocation, if present. + A bare stream without invocation has no well-defined identity structure. + """ if self.invocation is not None: return self.invocation.identity_structure() return super().identity_structure() @property def label(self) -> str: + """ + Returns a human-readable label for this stream. + If no label is provided and the stream is generated by an operation, + the label of the operation is used. + Otherwise, the class name is used as the label. + """ if self._label is None: if self.invocation is not None: # use the invocation operation label return self.invocation.operation.label + else: + return self.__class__.__name__ return self._label @property @@ -169,31 +230,6 @@ def invocation(self, value: Invocation) -> None: ) self._invocation = value - def __iter__(self) -> Iterator[Tuple[Tag, Packet]]: - raise NotImplementedError("Subclasses must implement __iter__ method") - - def flow(self) -> Collection[Tuple[Tag, Packet]]: - """ - Flow everything through the stream, returning the entire collection of - (Tag, Packet) as a collection. This will tigger any upstream computation of the stream. - """ - return list(self) - - -class SyncStream(Stream): - """ - A stream that will complete in a fixed amount of time. It is suitable for synchronous operations that - will have to wait for the stream to finish before proceeding. - """ - - def content_hash(self) -> str: - if ( - self.invocation is not None - ): # and hasattr(self.invocation, "invocation_id"): - # use the invocation ID as the hash - return self.invocation.content_hash() - return super().content_hash() - def keys(self) -> Tuple[Collection[str], Collection[str]]: """ Returns the keys of the stream. @@ -210,9 +246,28 @@ def keys(self) -> Tuple[Collection[str], Collection[str]]: if tag_keys is not None and packet_keys is not None: return tag_keys, packet_keys # otherwise, use the keys from the first packet in the stream + # note that this may be computationally expensive tag, packet = next(iter(self)) return list(tag.keys()), list(packet.keys()) + def __iter__(self) -> Iterator[Tuple[Tag, Packet]]: + raise NotImplementedError("Subclasses must implement __iter__ method") + + def flow(self) -> Collection[Tuple[Tag, Packet]]: + """ + Flow everything through the stream, returning the entire collection of + (Tag, Packet) as a collection. This will tigger any upstream computation of the stream. + """ + return list(self) + + +class SyncStream(Stream): + """ + A stream that will complete in a fixed amount of time. + It is suitable for synchronous operations that + will have to wait for the stream to finish before proceeding. + """ + def head(self, n: int = 5) -> None: """ Print the first n elements of the stream. @@ -239,9 +294,9 @@ def __rshift__(self, transformer: Any) -> "SyncStream": The mapping is applied to each packet in the stream and the resulting packets are returned in a new stream. """ + # TODO: remove just in time import from .mapper import MapPackets - # TODO: extend to generic mapping if isinstance(transformer, dict): return MapPackets(transformer)(self) elif isinstance(transformer, Callable): @@ -251,6 +306,7 @@ def __mul__(self, other: "SyncStream") -> "SyncStream": """ Returns a new stream that is the result joining with the other stream """ + # TODO: remove just in time import from .mapper import Join if not isinstance(other, SyncStream): @@ -258,6 +314,13 @@ def __mul__(self, other: "SyncStream") -> "SyncStream": return Join()(self, other) +class Mapper(Operation): + """ + A Mapper is an operation that does NOT generate new file content. + It is used to control the flow of data in the pipeline without modifying or creating new data (file). + """ + + class Source(Operation, SyncStream): """ A base class for all sources in the system. A source can be seen as a special diff --git a/src/orcabridge/types.py b/src/orcabridge/types.py index b7ae366..5241d35 100644 --- a/src/orcabridge/types.py +++ b/src/orcabridge/types.py @@ -1,19 +1,22 @@ -from typing import Union, List, Tuple, Protocol, Mapping, Collection +from typing import Union, Tuple, Protocol, Mapping, Collection, Optional from anyio import Path from typing_extensions import TypeAlias import os +# Convenience alias for anything pathlike PathLike = Union[str, bytes, os.PathLike] # arbitrary depth of nested list of strings or None -L: TypeAlias = Collection[Union[str, None, "L"]] +L: TypeAlias = Union[str, None, Collection[Optional[str]]] + # the top level tag is a mapping from string keys to values that can be a string or # an arbitrary depth of nested list of strings or None Tag: TypeAlias = Mapping[str, Union[str, L]] + # a pathset is a path or an arbitrary depth of nested list of paths -PathSet: TypeAlias = Union[PathLike, Collection[PathLike]] +PathSet: TypeAlias = Union[PathLike, Collection[Optional[PathLike]]] # a packet is a mapping from string keys to pathsets Packet: TypeAlias = Mapping[str, PathSet] From a348cf48b9053839f5fc761a60e78624c6b39e3b Mon Sep 17 00:00:00 2001 From: "Edgar Y. Walker" Date: Tue, 20 May 2025 06:42:24 +0000 Subject: [PATCH 02/34] fix: handling of colliding name in pathset --- src/orcabridge/hashing.py | 73 +++++++++++++++++++++++++++++++++--- src/orcabridge/utils/name.py | 11 ++++++ 2 files changed, 79 insertions(+), 5 deletions(-) diff --git a/src/orcabridge/hashing.py b/src/orcabridge/hashing.py index 9fa77af..2a97e14 100644 --- a/src/orcabridge/hashing.py +++ b/src/orcabridge/hashing.py @@ -6,7 +6,6 @@ suitable for arbitrarily nested data structures and custom objects via HashableMixin. """ -from ast import Str import hashlib import inspect import json @@ -26,10 +25,10 @@ ) from pathlib import Path from os import PathLike -import os import xxhash import zlib -from .types import PathSet, Packet +from orcabridge.types import PathSet, Packet +from orcabridge.utils.name import find_noncolliding_name # Configure logging with __name__ for proper hierarchy logger = logging.getLogger(__name__) @@ -827,6 +826,70 @@ def stable_hash(s: Any) -> int: return hash_to_int(s) +# Hashing of packets and PathSet + + +class PathSetHasher: + def __init__(self, char_count=32): + self.char_count = char_count + + def hash_pathset(self, pathset: PathSet) -> str: + if isinstance(pathset, str) or isinstance(pathset, PathLike): + pathset = Path(pathset) + if not pathset.exists(): + raise FileNotFoundError(f"Path {pathset} does not exist") + if pathset.is_dir(): + # iterate over all entries in the directory include subdirectory (single step) + hash_dict = {} + for entry in pathset.iterdir(): + file_name = find_noncolliding_name(entry.name, hash_dict) + hash_dict[file_name] = self.hash_pathset(entry) + return hash_to_hex(hash_dict, char_count=self.char_count) + else: + # it's a file, hash it directly + return hash_file(pathset) + + if isinstance(pathset, Collection): + hash_dict = {} + for path in pathset: + file_name = find_noncolliding_name(Path(path).name, hash_dict) + hash_dict[file_name] = self.hash_pathset(path) + return hash_to_hex(hash_dict, char_count=self.char_count) + + raise ValueError(f"PathSet of type {type(pathset)} is not supported") + + def hash_file(self, filepath) -> str: ... + + def id(self) -> str: ... + + +def hash_packet_with_psh( + packet: Packet, algo: PathSetHasher, prefix_algorithm: bool = True +) -> str: + """ + Generate a hash for a packet based on its content. + + Args: + packet: The packet to hash + algorithm: The algorithm to use for hashing + prefix_algorithm: Whether to prefix the hash with the algorithm name + + Returns: + A hexadecimal digest of the packet's content + """ + hash_results = {} + for key, pathset in packet.items(): + hash_results[key] = algo.hash_pathset(pathset) + + packet_hash = hash_to_hex(hash_results) + + if prefix_algorithm: + # Prefix the hash with the algorithm name + packet_hash = f"{algo.id()}-{packet_hash}" + + return packet_hash + + def hash_packet( packet: Packet, algorithm: str = "sha256", @@ -879,7 +942,7 @@ def hash_pathset( # iterate over all entries in the directory include subdirectory (single step) hash_dict = {} for entry in pathset.iterdir(): - file_name = entry.name + file_name = find_noncolliding_name(entry.name, hash_dict) hash_dict[file_name] = hash_pathset( entry, algorithm=algorithm, @@ -896,7 +959,7 @@ def hash_pathset( if isinstance(pathset, Collection): hash_dict = {} for path in pathset: - file_name = Path(path).name + file_name = find_noncolliding_name(Path(path).name, hash_dict) hash_dict[file_name] = hash_pathset( path, algorithm=algorithm, diff --git a/src/orcabridge/utils/name.py b/src/orcabridge/utils/name.py index f3e7b21..c5b952f 100644 --- a/src/orcabridge/utils/name.py +++ b/src/orcabridge/utils/name.py @@ -9,6 +9,17 @@ import ast +def find_noncolliding_name(name: str, lut: dict) -> str: + if name not in lut: + return name + + suffix = 1 + while f"{name}_{suffix}" in lut: + suffix += 1 + + return f"{name}_{suffix}" + + def pascal_to_snake(name: str) -> str: # Convert PascalCase to snake_case # if already in snake_case, return as is From 2700b1fcb760d5c0f38c0ffba912ff8177121436 Mon Sep 17 00:00:00 2001 From: "Edgar Y. Walker" Date: Tue, 20 May 2025 06:43:00 +0000 Subject: [PATCH 03/34] refactor: use absolute import --- src/orcabridge/mapper.py | 27 ++++++++++++++------------- src/orcabridge/pod.py | 17 ++++++----------- src/orcabridge/source.py | 8 ++++---- src/orcabridge/stream.py | 4 ++-- 4 files changed, 26 insertions(+), 30 deletions(-) diff --git a/src/orcabridge/mapper.py b/src/orcabridge/mapper.py index 777a4b3..3b8ef8d 100644 --- a/src/orcabridge/mapper.py +++ b/src/orcabridge/mapper.py @@ -1,8 +1,17 @@ -from typing import Callable, Dict, Optional, List, Sequence - -from .stream import SyncStream, SyncStreamFromGenerator -from .base import Operation -from .utils.stream_utils import ( +from typing import ( + Callable, + Dict, + Optional, + List, + Sequence, + Tuple, + Iterator, + Collection, + Any, +) +from orcabridge.base import Operation, SyncStream, Mapper +from orcabridge.stream import SyncStreamFromGenerator +from orcabridge.utils.stream_utils import ( join_tags, check_packet_compatibility, batch_tag, @@ -10,17 +19,9 @@ ) from .hashing import hash_function from .types import Tag, Packet -from typing import Iterator, Tuple, Any, Collection from itertools import chain -class Mapper(Operation): - """ - A Mapper is an operation that does NOT generate new file content. - It is used to control the flow of data in the pipeline without modifying or creating new data (file). - """ - - class Repeat(Mapper): """ A Mapper that repeats the packets in the stream a specified number of times. diff --git a/src/orcabridge/pod.py b/src/orcabridge/pod.py index 5f292c9..73060b5 100644 --- a/src/orcabridge/pod.py +++ b/src/orcabridge/pod.py @@ -2,9 +2,7 @@ logger = logging.getLogger(__name__) -from pathlib import Path from typing import ( - List, Optional, Tuple, Iterator, @@ -13,17 +11,14 @@ Literal, Any, ) -from .hashing import hash_function, get_function_signature -from .base import Operation -from .mapper import Join -from .stream import SyncStream, SyncStreamFromGenerator -from .types import Tag, Packet, PodFunction -from .store import DataStore, NoOpDataStore -import json -import shutil +from orcabridge.types import Tag, Packet, PodFunction +from orcabridge.hashing import hash_function, get_function_signature +from orcabridge.base import Operation +from orcabridge.stream import SyncStream, SyncStreamFromGenerator +from orcabridge.mapper import Join +from orcabridge.store import DataStore, NoOpDataStore import functools import warnings -import time def function_pod( diff --git a/src/orcabridge/source.py b/src/orcabridge/source.py index a0b1497..3071b89 100644 --- a/src/orcabridge/source.py +++ b/src/orcabridge/source.py @@ -1,6 +1,7 @@ -from .base import Source -from .stream import SyncStream, SyncStreamFromGenerator -from .types import Tag, Packet +from orcabridge.types import Tag, Packet +from orcabridge.hashing import hash_function +from orcabridge.base import Source +from orcabridge.stream import SyncStream, SyncStreamFromGenerator from typing import ( Iterator, Tuple, @@ -13,7 +14,6 @@ ) from os import PathLike from pathlib import Path -from .hashing import hash_function class LoadFromSource(Source): diff --git a/src/orcabridge/stream.py b/src/orcabridge/stream.py index d48898f..3dc8f7a 100644 --- a/src/orcabridge/stream.py +++ b/src/orcabridge/stream.py @@ -9,8 +9,8 @@ List, Collection, ) -from .types import Tag, Packet -from .base import SyncStream +from orcabridge.types import Tag, Packet +from orcabridge.base import SyncStream class SyncStreamFromLists(SyncStream): From cb8f46ae42056d94673d9c03cedaeb0508a1f4a0 Mon Sep 17 00:00:00 2001 From: "Edgar Y. Walker" Date: Tue, 20 May 2025 06:43:24 +0000 Subject: [PATCH 04/34] refactor: provide graph tracker as a concrete type --- src/orcabridge/tracker.py | 44 +++++++++------------------------------ 1 file changed, 10 insertions(+), 34 deletions(-) diff --git a/src/orcabridge/tracker.py b/src/orcabridge/tracker.py index b612ba9..2d2b72f 100644 --- a/src/orcabridge/tracker.py +++ b/src/orcabridge/tracker.py @@ -1,21 +1,26 @@ import threading from typing import Dict, Collection, List import networkx as nx -from .base import Operation, Invocation +from orcabridge.base import Operation, Invocation, Tracker import matplotlib.pyplot as plt -class Tracker: +class GraphTracker(Tracker): + """ + A tracker that records the invocations of operations and generates a graph + of the invocations and their dependencies. + """ # Thread-local storage to track active trackers - _local = threading.local() def __init__(self) -> None: - self.active = False + super().__init__() self.invocation_lut: Dict[Operation, Collection[Invocation]] = {} def record(self, invocation: Invocation) -> None: - invocation_list = self.invocation_lut.setdefault(invocation.operation, []) + invocation_list = self.invocation_lut.setdefault( + invocation.operation, [] + ) if invocation not in invocation_list: invocation_list.append(invocation) @@ -43,22 +48,6 @@ def generate_namemap(self) -> Dict[Invocation, str]: namemap[invocation] = f"{node_label}_{idx}" return namemap - def activate(self) -> None: - """ - Activate the tracker. This is a no-op if the tracker is already active. - """ - if not self.active: - if not hasattr(self._local, "active_trackers"): - self._local.active_trackers = [] - self._local.active_trackers.append(self) - self.active = True - - def deactivate(self) -> None: - # Remove this tracker from active trackers - if hasattr(self._local, "active_trackers") and self.active: - self._local.active_trackers.remove(self) - self.active = False - def generate_graph(self): G = nx.DiGraph() @@ -90,16 +79,3 @@ def draw_graph(self): arrowsize=20, ) plt.tight_layout() - - def __enter__(self): - self.activate() - return self - - def __exit__(self, exc_type, exc_val, ext_tb): - self.deactivate() - - @classmethod - def get_active_trackers(cls) -> List["Tracker"]: - if hasattr(cls._local, "active_trackers"): - return cls._local.active_trackers - return [] From c598b51b387f081457ab41b1f68403ce2a00d3b2 Mon Sep 17 00:00:00 2001 From: "Edgar Y. Walker" Date: Tue, 20 May 2025 06:43:43 +0000 Subject: [PATCH 05/34] fix: use absolute import and new tracker superclass --- src/orcabridge/dj/tracker.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/orcabridge/dj/tracker.py b/src/orcabridge/dj/tracker.py index 258c2d4..1b89d24 100644 --- a/src/orcabridge/dj/tracker.py +++ b/src/orcabridge/dj/tracker.py @@ -1,13 +1,13 @@ -from ..tracker import Tracker +from orcabridge.tracker import GraphTracker from datajoint import Schema from typing import List, Collection, Tuple, Optional, Any from types import ModuleType import networkx as nx -from ..base import Operation, Source -from ..mapper import Mapper, Merge -from ..pod import FunctionPod +from orcabridge.base import Operation, Source +from orcabridge.mapper import Mapper, Merge +from orcabridge.pod import FunctionPod from .stream import QueryStream from .source import TableCachedSource, MergedQuerySource from .operation import QueryOperation @@ -75,7 +75,7 @@ def convert_to_query_operation( raise ValueError(f"Unsupported operation for DJ conversion: {operation}") -class QueryTracker(Tracker): +class QueryTracker(GraphTracker): """ Query-specific tracker that tracks the invocations of operations and their associated streams. From 3b0df895175ac4c16ec175c173a128bbcd54d947 Mon Sep 17 00:00:00 2001 From: "Edgar Y. Walker" Date: Fri, 23 May 2025 03:04:08 +0000 Subject: [PATCH 06/34] fix: path import --- src/orcabridge/types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/orcabridge/types.py b/src/orcabridge/types.py index 5241d35..55af3d3 100644 --- a/src/orcabridge/types.py +++ b/src/orcabridge/types.py @@ -1,5 +1,5 @@ from typing import Union, Tuple, Protocol, Mapping, Collection, Optional -from anyio import Path +from pathlib import Path from typing_extensions import TypeAlias import os From 71820498f3978ecdb2a5109bd54a3d691932b09a Mon Sep 17 00:00:00 2001 From: "Edgar Y. Walker" Date: Sat, 24 May 2025 04:26:02 +0000 Subject: [PATCH 07/34] feat: set up testing framework --- pytest.ini | 6 ++++++ requirements.txt | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 pytest.ini diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..ec6206f --- /dev/null +++ b/pytest.ini @@ -0,0 +1,6 @@ +[pytest] +testpaths = tests +python_files = test_*.py +python_classes = Test* +python_functions = test_* +addopts = -v --cov=src --cov-report=term-missing \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index c6aff8e..ad1779c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,5 @@ numpy -xxhash \ No newline at end of file +xxhash +# TODO: separate dev and prod requirements +pytest>=7.4.0 +pytest-cov>=4.1.0 \ No newline at end of file From 41e1416770fbec28581380f5e860554117031f6c Mon Sep 17 00:00:00 2001 From: "Edgar Y. Walker" Date: Sat, 24 May 2025 04:26:21 +0000 Subject: [PATCH 08/34] chore: use log instead of print --- src/orcabridge/pod.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/orcabridge/pod.py b/src/orcabridge/pod.py index 73060b5..ba15918 100644 --- a/src/orcabridge/pod.py +++ b/src/orcabridge/pod.py @@ -178,8 +178,8 @@ def generator() -> Iterator[Tuple[Tag, Packet]]: not self.force_computation and memoized_packet is not None ): - print( - "Memoized packet found! Using it to skip computation" + logger.info( + "Memoized packet found, skipping computation" ) yield tag, memoized_packet continue From 63e1c91d2c732f78eda1fd7b388d184fd9442aef Mon Sep 17 00:00:00 2001 From: "Edgar Y. Walker" Date: Sat, 24 May 2025 04:28:39 +0000 Subject: [PATCH 09/34] refactor: update usage pattern for cachestream --- .gitignore | 2 +- src/orcabridge/mapper.py | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 0dd11a8..07377ae 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ # Ignore example dataset storage generated in tutorial -pod_data/ +**/pod_data/ # Autogenerated version file _version.py diff --git a/src/orcabridge/mapper.py b/src/orcabridge/mapper.py index 3b8ef8d..d17dc9a 100644 --- a/src/orcabridge/mapper.py +++ b/src/orcabridge/mapper.py @@ -600,16 +600,14 @@ def __init__(self) -> None: self.is_cached = False def forward(self, *streams: SyncStream) -> SyncStream: - if len(streams) != 1: + if not self.is_cached and len(streams) != 1: raise ValueError( "CacheStream operation requires exactly one stream" ) - stream = streams[0] - def generator() -> Iterator[Tuple[Tag, Packet]]: if not self.is_cached: - for tag, packet in stream: + for tag, packet in streams[0]: self.cache.append((tag, packet)) yield tag, packet self.is_cached = True From 56e57c8eb4dcd4b0474f129304e43f1c1495bc65 Mon Sep 17 00:00:00 2001 From: "Edgar Y. Walker" Date: Sat, 24 May 2025 04:30:14 +0000 Subject: [PATCH 10/34] feat: add basic test for hashing --- tests/test_basic_hashing.py | 132 ++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 tests/test_basic_hashing.py diff --git a/tests/test_basic_hashing.py b/tests/test_basic_hashing.py new file mode 100644 index 0000000..10bf379 --- /dev/null +++ b/tests/test_basic_hashing.py @@ -0,0 +1,132 @@ +import pytest +from orcabridge.hashing import ( + hash_to_hex, + hash_to_int, + hash_to_uuid, + HashableMixin, + hash_dict, + stable_hash, +) + + +def test_hash_to_hex(): + # Test with string + # Should be equivalent to hashing b'"test"' + assert ( + hash_to_hex("test", None) + == "4d967a30111bf29f0eba01c448b375c1629b2fed01cdfcc3aed91f1b57d5dd5e" + ) + + # Test with integer + # Should be equivalent to hashing b'42' + assert ( + hash_to_hex(42, None) + == "73475cb40a568e8da8a045ced110137e159f890ac4da883b6b17dc651b3a8049" + ) + + assert ( + hash_to_hex(True, None) + == "b5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b" + ) + + assert ( + hash_to_hex(0.256, None) + == "79308bed382bc45abbb1297149dda93e29d676aff0b366bc5f2bb932a4ff55ca" + ) + + # equivalent to hashing b'null' + assert ( + hash_to_hex(None, None) + == "74234e98afe7498fb5daf1f36ac2d78acc339464f950703b8c019892f982b90b" + ) + + # Hash structure + assert ( + hash_to_hex(["a", "b", "c"], None) + == "fa1844c2988ad15ab7b49e0ece09684500fad94df916859fb9a43ff85f5bb477" + ) + + # hash set + assert ( + hash_to_hex(set([1, 2, 3]), None) + == "a615eeaee21de5179de080de8c3052c8da901138406ba71c38c032845f7d54f4" + ) + + # Test with custom char_count + assert len(hash_to_hex("test", char_count=16)) == 16 + + assert len(hash_to_hex("test", char_count=0)) == 0 + + +def test_structure_equivalence(): + # identical content should yield the same hash + assert hash_to_hex(["a", "b", "c"], None) == hash_to_hex( + ["a", "b", "c"], None + ) + # list should be order dependent + assert hash_to_hex(["a", "b", "c"], None) != hash_to_hex( + ["a", "c", "b"], None + ) + + # dict should be order independent + assert hash_to_hex({"a": 1, "b": 2, "c": 3}, None) == hash_to_hex( + {"c": 3, "b": 2, "a": 1}, None + ) + + # set should be order independent + assert hash_to_hex(set([1, 2, 3]), None) == hash_to_hex( + set([3, 2, 1]), None + ) + + # equivalence under nested structure + assert hash_to_hex( + set([("a", "b", "c"), ("d", "e", "f")]), None + ) == hash_to_hex(set([("d", "e", "f"), ("a", "b", "c")]), None) + + +def test_hash_to_int(): + # Test with string + assert isinstance(hash_to_int("test"), int) + + # Test with custom hexdigits + result = hash_to_int("test", hexdigits=8) + assert result < 16**8 # Should be less than max value for 8 hex digits + + +def test_hash_to_uuid(): + # Test with string + uuid = hash_to_uuid("test") + assert str(uuid).count("-") == 4 # Valid UUID format + + # Test with integer + uuid = hash_to_uuid(42) + assert str(uuid).count("-") == 4 # Valid UUID format + + +class TestHashableMixin(HashableMixin): + def __init__(self, value): + self.value = value + + def identity_structure(self): + return {"value": self.value} + + +def test_hash_dict(): + test_dict = {"a": 1, "b": "test", "c": {"nested": True}} + + # Test that it returns a UUID + result = hash_dict(test_dict) + assert str(result).count("-") == 4 + + +def test_stable_hash(): + # Test that same input gives same output + assert stable_hash("test") == stable_hash("test") + + # Test that different inputs give different outputs + assert stable_hash("test1") != stable_hash("test2") + + # Test with different types + assert isinstance(stable_hash(42), int) + assert isinstance(stable_hash("string"), int) + assert isinstance(stable_hash([1, 2, 3]), int) From b6977590f26c84de06bb9ba925c4c78b872dce47 Mon Sep 17 00:00:00 2001 From: "Edgar Y. Walker" Date: Sat, 24 May 2025 05:56:08 +0000 Subject: [PATCH 11/34] test: add additional test for hashing consistency and stability --- tests/test_hashing/generate_hash_examples.py | 135 ++++++ .../hash_examples_20250524_054344.json | 413 ++++++++++++++++++ tests/test_hashing/test_basic_hashing.py | 145 ++++++ tests/test_hashing/test_hash_samples.py | 154 +++++++ 4 files changed, 847 insertions(+) create mode 100644 tests/test_hashing/generate_hash_examples.py create mode 100644 tests/test_hashing/hash_samples/hash_examples_20250524_054344.json create mode 100644 tests/test_hashing/test_basic_hashing.py create mode 100644 tests/test_hashing/test_hash_samples.py diff --git a/tests/test_hashing/generate_hash_examples.py b/tests/test_hashing/generate_hash_examples.py new file mode 100644 index 0000000..2d9687a --- /dev/null +++ b/tests/test_hashing/generate_hash_examples.py @@ -0,0 +1,135 @@ +# This script is used to generate hash examples for testing purposes. +# The resulting hashes are saved in `hash_samples` folder, and are used +# throughout the tests to ensure consistent hashing behavior across different runs +# and revision of the codebase. + +import os +import json +import uuid +from pathlib import Path +from collections import OrderedDict +from datetime import datetime +from orcabridge.hashing import hash_to_hex, hash_to_int, hash_to_uuid + +# Create the hash_samples directory if it doesn't exist +SAMPLES_DIR = Path(__file__).parent / "hash_samples" +SAMPLES_DIR.mkdir(exist_ok=True) + +# Format the current date and time for the filename +timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") +output_file = SAMPLES_DIR / f"hash_examples_{timestamp}.json" + + +def generate_hash_examples(): + """Generate hash examples for various data structures.""" + examples = [] + + # Basic data types + basic_examples = [ + None, + True, + False, + 0, + 1, + -1, + 42, + 3.14159, + -2.71828, + 0.0, + "", + "hello", + "Hello, World!", + "Special chars: !@#$%^&*()", + "Unicode: 你好, Привет, こんにちは", + ] + + # Bytes examples + bytes_examples = [ + b"", + b"hello", + b"\x00\x01\x02\x03", + bytearray(b"hello world"), + bytearray([65, 66, 67]), # ABC + ] + + # Collection examples + collection_examples = [ + [], + [1, 2, 3], + ["a", "b", "c"], + [1, "a", True], + set(), + {1, 2, 3}, + {"a", "b", "c"}, + {}, + {"a": 1}, + {"a": 1, "b": 2}, + {"b": 1, "a": 2}, # Same keys as above but different order + {"nested": {"a": 1, "b": 2}}, + ] + + # Complex nested examples + nested_examples = [ + [1, [2, [3, [4, [5]]]]], + {"a": {"b": {"c": {"d": {"e": 42}}}}}, + {"a": [1, 2, {"b": [3, 4, {"c": 5}]}]}, + [{"a": 1}, {"b": 2}, {"c": [3, 4, 5]}], + {"keys": ["a", "b", "c"], "values": [1, 2, 3]}, + [{"a": 1, "b": [2, 3]}, {"c": 4, "d": [5, 6]}], + {"users": [{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}]}, + { + "data": { + "points": [[1, 2], [3, 4], [5, 6]], + "labels": ["A", "B", "C"], + } + }, + OrderedDict([("a", 1), ("b", 2), ("c", 3)]), + [[1, 2], [3, 4], {"a": [5, 6], "b": [7, 8]}], + ] + + # Combine all examples + all_examples = ( + basic_examples + bytes_examples + collection_examples + nested_examples + ) + + # Generate hashes for each example + for value in all_examples: + try: + hex_hash = hash_to_hex(value) + int_hash = hash_to_int(value) + uuid_hash = str( + hash_to_uuid(value) + ) # Convert UUID to string for JSON serialization + + # Create a serializable representation of the value + if isinstance(value, (bytes, bytearray)): + serialized_value = f"bytes:{value.hex()}" + elif isinstance(value, set): + serialized_value = f"set:{list(value)}" + else: + serialized_value = value + + examples.append( + { + "value": serialized_value, + "hex_hash": hex_hash, + "int_hash": int_hash, + "uuid_hash": uuid_hash, + } + ) + except Exception as e: + print(f"Error hashing value {repr(value)}: {e}") + + return examples + + +if __name__ == "__main__": + # Generate the hash examples + hash_examples = generate_hash_examples() + + # Save the examples to a JSON file + with open(output_file, "w") as f: + json.dump(hash_examples, f, indent=2, ensure_ascii=False) + + print(f"Generated {len(hash_examples)} hash examples") + print(f"Saved to {output_file}") diff --git a/tests/test_hashing/hash_samples/hash_examples_20250524_054344.json b/tests/test_hashing/hash_samples/hash_examples_20250524_054344.json new file mode 100644 index 0000000..0068c53 --- /dev/null +++ b/tests/test_hashing/hash_samples/hash_examples_20250524_054344.json @@ -0,0 +1,413 @@ +[ + { + "value": null, + "hex_hash": "74234e98afe7498fb5daf1f36ac2d78a", + "int_hash": 8368618950277679503, + "uuid_hash": "74234e98-afe7-498f-b5da-f1f36ac2d78a" + }, + { + "value": true, + "hex_hash": "b5bea41b6c623f7c09f1bf24dcae58eb", + "int_hash": 13096085204129431420, + "uuid_hash": "b5bea41b-6c62-3f7c-09f1-bf24dcae58eb" + }, + { + "value": false, + "hex_hash": "fcbcf165908dd18a9e49f7ff27810176", + "int_hash": 18211696411698647434, + "uuid_hash": "fcbcf165-908d-d18a-9e49-f7ff27810176" + }, + { + "value": 0, + "hex_hash": "5feceb66ffc86f38d952786c6d696c79", + "int_hash": 6912158355717386040, + "uuid_hash": "5feceb66-ffc8-6f38-d952-786c6d696c79" + }, + { + "value": 1, + "hex_hash": "6b86b273ff34fce19d6b804eff5a3f57", + "int_hash": 7748076420210162913, + "uuid_hash": "6b86b273-ff34-fce1-9d6b-804eff5a3f57" + }, + { + "value": -1, + "hex_hash": "1bad6b8cf97131fceab8543e81f77571", + "int_hash": 1994368463219536380, + "uuid_hash": "1bad6b8c-f971-31fc-eab8-543e81f77571" + }, + { + "value": 42, + "hex_hash": "73475cb40a568e8da8a045ced110137e", + "int_hash": 8306709966045482637, + "uuid_hash": "73475cb4-0a56-8e8d-a8a0-45ced110137e" + }, + { + "value": 3.14159, + "hex_hash": "c0740dd25c9de39b9c8d5ab452e8b69b", + "int_hash": 13867724349728744347, + "uuid_hash": "c0740dd2-5c9d-e39b-9c8d-5ab452e8b69b" + }, + { + "value": -2.71828, + "hex_hash": "43dc3d0d4b9e9bb2a2dfcf9696b0d49f", + "int_hash": 4889850422730070962, + "uuid_hash": "43dc3d0d-4b9e-9bb2-a2df-cf9696b0d49f" + }, + { + "value": 0.0, + "hex_hash": "8aed642bf5118b9d3c859bd4be35ecac", + "int_hash": 10010767686672419741, + "uuid_hash": "8aed642b-f511-8b9d-3c85-9bd4be35ecac" + }, + { + "value": "", + "hex_hash": "12ae32cb1ec02d01eda3581b127c1fee", + "int_hash": 1346069186606017793, + "uuid_hash": "12ae32cb-1ec0-2d01-eda3-581b127c1fee" + }, + { + "value": "hello", + "hex_hash": "5aa762ae383fbb727af3c7a36d4940a5", + "int_hash": 6532298284931726194, + "uuid_hash": "5aa762ae-383f-bb72-7af3-c7a36d4940a5" + }, + { + "value": "Hello, World!", + "hex_hash": "cc82ebbcf8b60a5821d1c51c72cd7938", + "int_hash": 14736600127568743000, + "uuid_hash": "cc82ebbc-f8b6-0a58-21d1-c51c72cd7938" + }, + { + "value": "Special chars: !@#$%^&*()", + "hex_hash": "bbfa5afce2bf76f5520f0acafbb986c6", + "int_hash": 13545238871452645109, + "uuid_hash": "bbfa5afc-e2bf-76f5-520f-0acafbb986c6" + }, + { + "value": "Unicode: 你好, Привет, こんにちは", + "hex_hash": "2dd7db78b058c582953d493cc25b725d", + "int_hash": 3303350163100714370, + "uuid_hash": "2dd7db78-b058-c582-953d-493cc25b725d" + }, + { + "value": "bytes:", + "hex_hash": "12ae32cb1ec02d01eda3581b127c1fee", + "int_hash": 1346069186606017793, + "uuid_hash": "12ae32cb-1ec0-2d01-eda3-581b127c1fee" + }, + { + "value": "bytes:68656c6c6f", + "hex_hash": "7375513f6d0b5271db377b03ad832f51", + "int_hash": 8319645219491107441, + "uuid_hash": "7375513f-6d0b-5271-db37-7b03ad832f51" + }, + { + "value": "bytes:00010203", + "hex_hash": "cedf4873ac6b0aba6a4b8dc6574c5564", + "int_hash": 14906712953270766266, + "uuid_hash": "cedf4873-ac6b-0aba-6a4b-8dc6574c5564" + }, + { + "value": "bytes:68656c6c6f20776f726c64", + "hex_hash": "95b243c4c8b3e696c28c0ffc82ff70b8", + "int_hash": 10786758569965643414, + "uuid_hash": "95b243c4-c8b3-e696-c28c-0ffc82ff70b8" + }, + { + "value": "bytes:414243", + "hex_hash": "9117877bc5c6f08d682a7d9bb4e67b98", + "int_hash": 10454974025632772237, + "uuid_hash": "9117877b-c5c6-f08d-682a-7d9bb4e67b98" + }, + { + "value": [], + "hex_hash": "4f53cda18c2baa0c0354bb5f9a3ecbe5", + "int_hash": 5716138445788391948, + "uuid_hash": "4f53cda1-8c2b-aa0c-0354-bb5f9a3ecbe5" + }, + { + "value": [ + 1, + 2, + 3 + ], + "hex_hash": "a615eeaee21de5179de080de8c3052c8", + "int_hash": 11967734019692291351, + "uuid_hash": "a615eeae-e21d-e517-9de0-80de8c3052c8" + }, + { + "value": [ + "a", + "b", + "c" + ], + "hex_hash": "fa1844c2988ad15ab7b49e0ece096845", + "int_hash": 18021229511496618330, + "uuid_hash": "fa1844c2-988a-d15a-b7b4-9e0ece096845" + }, + { + "value": [ + 1, + "a", + true + ], + "hex_hash": "8554b19db94b8f9b64793d82a73098ea", + "int_hash": 9607499196064829339, + "uuid_hash": "8554b19d-b94b-8f9b-6479-3d82a73098ea" + }, + { + "value": "set:[]", + "hex_hash": "4f53cda18c2baa0c0354bb5f9a3ecbe5", + "int_hash": 5716138445788391948, + "uuid_hash": "4f53cda1-8c2b-aa0c-0354-bb5f9a3ecbe5" + }, + { + "value": "set:[1, 2, 3]", + "hex_hash": "a615eeaee21de5179de080de8c3052c8", + "int_hash": 11967734019692291351, + "uuid_hash": "a615eeae-e21d-e517-9de0-80de8c3052c8" + }, + { + "value": "set:['c', 'a', 'b']", + "hex_hash": "fa1844c2988ad15ab7b49e0ece096845", + "int_hash": 18021229511496618330, + "uuid_hash": "fa1844c2-988a-d15a-b7b4-9e0ece096845" + }, + { + "value": {}, + "hex_hash": "44136fa355b3678a1146ad16f7e8649e", + "int_hash": 4905387166444775306, + "uuid_hash": "44136fa3-55b3-678a-1146-ad16f7e8649e" + }, + { + "value": { + "a": 1 + }, + "hex_hash": "015abd7f5cc57a2dd94b7590f04ad808", + "int_hash": 97598696656828973, + "uuid_hash": "015abd7f-5cc5-7a2d-d94b-7590f04ad808" + }, + { + "value": { + "a": 1, + "b": 2 + }, + "hex_hash": "43258cff783fe7036d8a43033f830adf", + "int_hash": 4838428403541468931, + "uuid_hash": "43258cff-783f-e703-6d8a-43033f830adf" + }, + { + "value": { + "b": 1, + "a": 2 + }, + "hex_hash": "d3626ac30a87e6f7a6428233b3c68299", + "int_hash": 15231854275648284407, + "uuid_hash": "d3626ac3-0a87-e6f7-a642-8233b3c68299" + }, + { + "value": { + "nested": { + "a": 1, + "b": 2 + } + }, + "hex_hash": "635b42bee92bc9e78396200dd770fffc", + "int_hash": 7159389420358715879, + "uuid_hash": "635b42be-e92b-c9e7-8396-200dd770fffc" + }, + { + "value": [ + 1, + [ + 2, + [ + 3, + [ + 4, + [ + 5 + ] + ] + ] + ] + ], + "hex_hash": "fdc65463c02cb1c8788344fba97041b6", + "int_hash": 18286396124387127752, + "uuid_hash": "fdc65463-c02c-b1c8-7883-44fba97041b6" + }, + { + "value": { + "a": { + "b": { + "c": { + "d": { + "e": 42 + } + } + } + } + }, + "hex_hash": "c6d14482c460f03004d38484d71298a4", + "int_hash": 14326307218073382960, + "uuid_hash": "c6d14482-c460-f030-04d3-8484d71298a4" + }, + { + "value": { + "a": [ + 1, + 2, + { + "b": [ + 3, + 4, + { + "c": 5 + } + ] + } + ] + }, + "hex_hash": "2252fc2a778c83e9c9308bd3ff747afd", + "int_hash": 2473316404704347113, + "uuid_hash": "2252fc2a-778c-83e9-c930-8bd3ff747afd" + }, + { + "value": [ + { + "a": 1 + }, + { + "b": 2 + }, + { + "c": [ + 3, + 4, + 5 + ] + } + ], + "hex_hash": "d72ea3c2223310171bdcb8e1787ef9f6", + "int_hash": 15505510621275951127, + "uuid_hash": "d72ea3c2-2233-1017-1bdc-b8e1787ef9f6" + }, + { + "value": { + "keys": [ + "a", + "b", + "c" + ], + "values": [ + 1, + 2, + 3 + ] + }, + "hex_hash": "af63a0d00f3d3b744714e12bc4b6003b", + "int_hash": 12638121794801056628, + "uuid_hash": "af63a0d0-0f3d-3b74-4714-e12bc4b6003b" + }, + { + "value": [ + { + "a": 1, + "b": [ + 2, + 3 + ] + }, + { + "c": 4, + "d": [ + 5, + 6 + ] + } + ], + "hex_hash": "d88e8543683b6b82bd7ce2225762d5a9", + "int_hash": 15604556283443374978, + "uuid_hash": "d88e8543-683b-6b82-bd7c-e2225762d5a9" + }, + { + "value": { + "users": [ + { + "name": "Alice", + "age": 30 + }, + { + "name": "Bob", + "age": 25 + } + ] + }, + "hex_hash": "32c13fff89d73e616960914df441b14c", + "int_hash": 3657274739163348577, + "uuid_hash": "32c13fff-89d7-3e61-6960-914df441b14c" + }, + { + "value": { + "data": { + "points": [ + [ + 1, + 2 + ], + [ + 3, + 4 + ], + [ + 5, + 6 + ] + ], + "labels": [ + "A", + "B", + "C" + ] + } + }, + "hex_hash": "a553155144ad09460431dbbaacf7fe9a", + "int_hash": 11912888878113818950, + "uuid_hash": "a5531551-44ad-0946-0431-dbbaacf7fe9a" + }, + { + "value": { + "a": 1, + "b": 2, + "c": 3 + }, + "hex_hash": "e6a3385fb77c287a712e7f406a451727", + "int_hash": 16619189033678678138, + "uuid_hash": "e6a3385f-b77c-287a-712e-7f406a451727" + }, + { + "value": [ + [ + 1, + 2 + ], + [ + 3, + 4 + ], + { + "a": [ + 5, + 6 + ], + "b": [ + 7, + 8 + ] + } + ], + "hex_hash": "aa4dc10726bca2bd65f5c953962b432c", + "int_hash": 12271676796113298109, + "uuid_hash": "aa4dc107-26bc-a2bd-65f5-c953962b432c" + } +] \ No newline at end of file diff --git a/tests/test_hashing/test_basic_hashing.py b/tests/test_hashing/test_basic_hashing.py new file mode 100644 index 0000000..cc473b0 --- /dev/null +++ b/tests/test_hashing/test_basic_hashing.py @@ -0,0 +1,145 @@ +import pytest +from orcabridge.hashing import ( + hash_to_hex, + hash_to_int, + hash_to_uuid, + HashableMixin, + hash_dict, + stable_hash, + hash_file, +) + + +def test_hash_to_hex(): + # Test with string + # Should be equivalent to hashing b'"test"' + assert ( + hash_to_hex("test", None) + == "4d967a30111bf29f0eba01c448b375c1629b2fed01cdfcc3aed91f1b57d5dd5e" + ) + + # Test with integer + # Should be equivalent to hashing b'42' + assert ( + hash_to_hex(42, None) + == "73475cb40a568e8da8a045ced110137e159f890ac4da883b6b17dc651b3a8049" + ) + + assert ( + hash_to_hex(True, None) + == "b5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b" + ) + + assert ( + hash_to_hex(0.256, None) + == "79308bed382bc45abbb1297149dda93e29d676aff0b366bc5f2bb932a4ff55ca" + ) + + # equivalent to hashing b'null' + assert ( + hash_to_hex(None, None) + == "74234e98afe7498fb5daf1f36ac2d78acc339464f950703b8c019892f982b90b" + ) + + # Hash structure + assert ( + hash_to_hex(["a", "b", "c"], None) + == "fa1844c2988ad15ab7b49e0ece09684500fad94df916859fb9a43ff85f5bb477" + ) + + # hash set + assert ( + hash_to_hex(set([1, 2, 3]), None) + == "a615eeaee21de5179de080de8c3052c8da901138406ba71c38c032845f7d54f4" + ) + + # Test with custom char_count + assert len(hash_to_hex("test", char_count=16)) == 16 + + assert len(hash_to_hex("test", char_count=0)) == 0 + + +def test_hash_file(): + # Test with a file that exists + test_file = "test_file.txt" + with open(test_file, "w") as f: + f.write("This is a test file.") + + # Clean up + import os + + os.remove(test_file) + + +def test_structure_equivalence(): + # identical content should yield the same hash + assert hash_to_hex(["a", "b", "c"], None) == hash_to_hex( + ["a", "b", "c"], None + ) + # list should be order dependent + assert hash_to_hex(["a", "b", "c"], None) != hash_to_hex( + ["a", "c", "b"], None + ) + + # dict should be order independent + assert hash_to_hex({"a": 1, "b": 2, "c": 3}, None) == hash_to_hex( + {"c": 3, "b": 2, "a": 1}, None + ) + + # set should be order independent + assert hash_to_hex(set([1, 2, 3]), None) == hash_to_hex( + set([3, 2, 1]), None + ) + + # equivalence under nested structure + assert hash_to_hex( + set([("a", "b", "c"), ("d", "e", "f")]), None + ) == hash_to_hex(set([("d", "e", "f"), ("a", "b", "c")]), None) + + +def test_hash_to_int(): + # Test with string + assert isinstance(hash_to_int("test"), int) + + # Test with custom hexdigits + result = hash_to_int("test", hexdigits=8) + assert result < 16**8 # Should be less than max value for 8 hex digits + + +def test_hash_to_uuid(): + # Test with string + uuid = hash_to_uuid("test") + assert str(uuid).count("-") == 4 # Valid UUID format + + # Test with integer + uuid = hash_to_uuid(42) + assert str(uuid).count("-") == 4 # Valid UUID format + + +class TestHashableMixin(HashableMixin): + def __init__(self, value): + self.value = value + + def identity_structure(self): + return {"value": self.value} + + +def test_hash_dict(): + test_dict = {"a": 1, "b": "test", "c": {"nested": True}} + + # Test that it returns a UUID + result = hash_dict(test_dict) + assert str(result).count("-") == 4 + + +def test_stable_hash(): + # Test that same input gives same output + assert stable_hash("test") == stable_hash("test") + + # Test that different inputs give different outputs + assert stable_hash("test1") != stable_hash("test2") + + # Test with different types + assert isinstance(stable_hash(42), int) + assert isinstance(stable_hash("string"), int) + assert isinstance(stable_hash([1, 2, 3]), int) diff --git a/tests/test_hashing/test_hash_samples.py b/tests/test_hashing/test_hash_samples.py new file mode 100644 index 0000000..5aa4352 --- /dev/null +++ b/tests/test_hashing/test_hash_samples.py @@ -0,0 +1,154 @@ +""" +Tests for hash samples consistency. + +This script tests that the hash functions produce the same outputs for +the same inputs as recorded in the samples files. This helps ensure that +the hashing implementation remains stable over time. +""" + +import os +import json +import pytest +from pathlib import Path +from orcabridge.hashing import hash_to_hex, hash_to_int, hash_to_uuid + + +def get_latest_hash_samples(): + """Get the path to the latest hash samples file.""" + samples_dir = Path(__file__).parent / "hash_samples" + print(f"Looking for hash samples in {samples_dir}") + sample_files = list(samples_dir.glob("hash_examples_*.json")) + print(f"Found {len(sample_files)} sample files") + + if not sample_files: + print(f"No hash sample files found in {samples_dir}") + pytest.skip("No hash sample files found") + return None + + # Sort by modification time (newest first) + sample_files.sort(key=lambda x: os.path.getmtime(x), reverse=True) + + # Return the newest file + latest = sample_files[0] + print(f"Using latest sample file: {latest}") + return latest + + +def load_hash_samples(file_path=None): + """Load hash samples from a file or the latest file if not specified.""" + if file_path is None: + file_path = get_latest_hash_samples() + + with open(file_path, 'r') as f: + return json.load(f) + + +def deserialize_value(serialized_value): + """Convert serialized values back to their original form.""" + if isinstance(serialized_value, str) and serialized_value.startswith("bytes:"): + # Convert hex string back to bytes + hex_str = serialized_value[len("bytes:"):] + return bytes.fromhex(hex_str) + + if isinstance(serialized_value, str) and serialized_value.startswith("set:"): + # Convert string representation back to set + # Example: "set:[1, 2, 3]" -> {1, 2, 3} + set_str = serialized_value[len("set:"):] + # This is a simplified approach; for a real implementation you might want to use ast.literal_eval + # but for our test cases, we can just handle the basic cases + if set_str == "[]": + return set() + elif set_str.startswith("[") and set_str.endswith("]"): + # Parse items inside the brackets + items_str = set_str[1:-1] + if not items_str: + return set() + + items = [] + for item_str in items_str.split(", "): + item_str = item_str.strip() + if item_str.startswith("'") and item_str.endswith("'"): + # It's a string + items.append(item_str[1:-1]) + elif item_str.lower() == "true": + items.append(True) + elif item_str.lower() == "false": + items.append(False) + elif item_str == "null": + items.append(None) + else: + try: + # Try to parse as a number + if "." in item_str: + items.append(float(item_str)) + else: + items.append(int(item_str)) + except ValueError: + # If all else fails, keep it as a string + items.append(item_str) + + return set(items) + + return serialized_value + + +def test_hash_to_hex_consistency(): + """Test that hash_to_hex produces consistent results.""" + hash_samples = load_hash_samples() + + for sample in hash_samples: + value = deserialize_value(sample["value"]) + expected_hash = sample["hex_hash"] + + # Compute the hash with the current implementation + actual_hash = hash_to_hex(value) + + # Verify the hash matches the stored value + assert actual_hash == expected_hash, f"Hash mismatch for {sample['value']}: expected {expected_hash}, got {actual_hash}" + + +def test_hash_to_int_consistency(): + """Test that hash_to_int produces consistent results.""" + hash_samples = load_hash_samples() + + for sample in hash_samples: + value = deserialize_value(sample["value"]) + expected_hash = sample["int_hash"] + + # Compute the hash with the current implementation + actual_hash = hash_to_int(value) + + # Verify the hash matches the stored value + assert actual_hash == expected_hash, f"Hash mismatch for {sample['value']}: expected {expected_hash}, got {actual_hash}" + + +def test_hash_to_uuid_consistency(): + """Test that hash_to_uuid produces consistent results.""" + hash_samples = load_hash_samples() + + for sample in hash_samples: + value = deserialize_value(sample["value"]) + expected_hash = sample["uuid_hash"] + + # Compute the hash with the current implementation + actual_hash = str(hash_to_uuid(value)) + + # Verify the hash matches the stored value + assert actual_hash == expected_hash, f"Hash mismatch for {sample['value']}: expected {expected_hash}, got {actual_hash}" + + +if __name__ == "__main__": + # This allows running the tests directly for debugging + samples = load_hash_samples() + print(f"Loaded {len(samples)} hash samples") + + print("\nTesting hash_to_hex consistency...") + test_hash_to_hex_consistency() + + print("\nTesting hash_to_int consistency...") + test_hash_to_int_consistency() + + print("\nTesting hash_to_uuid consistency...") + test_hash_to_uuid_consistency() + + print("\nAll tests passed!") From 361acbe69429b83dbfc7b4a7221d8da23863b30b Mon Sep 17 00:00:00 2001 From: "Edgar Y. Walker" Date: Sat, 24 May 2025 06:22:31 +0000 Subject: [PATCH 12/34] test: add more comprehensive hash testing --- tests/test_basic_hashing.py | 132 -- tests/test_hashing/generate_file_hashes.py | 149 +++ tests/test_hashing/generate_hash_examples.py | 6 +- .../hash_examples_20250524_061711.json} | 2 +- .../hash_samples/file_hash_lut.json | 46 + .../file_samples/sample_binary_100kb.bin | Bin 0 -> 102400 bytes .../file_samples/sample_binary_10kb.bin | Bin 0 -> 10240 bytes .../file_samples/sample_binary_1kb.bin | Bin 0 -> 1024 bytes .../file_samples/sample_binary_50kb.bin | Bin 0 -> 51200 bytes .../file_samples/sample_binary_5kb.bin | Bin 0 -> 5120 bytes .../file_samples/sample_structured.json | 115 ++ .../file_samples/sample_text_100kb.txt | 1089 +++++++++++++++++ .../file_samples/sample_text_10kb.txt | 99 ++ .../file_samples/sample_text_1kb.txt | 16 + .../file_samples/sample_text_50kb.txt | 524 ++++++++ .../file_samples/sample_text_5kb.txt | 53 + tests/test_hashing/reorganize_files.py | 69 ++ tests/test_hashing/test_file_hashes.py | 116 ++ tests/test_hashing/test_hash_samples.py | 70 +- 19 files changed, 2322 insertions(+), 164 deletions(-) delete mode 100644 tests/test_basic_hashing.py create mode 100644 tests/test_hashing/generate_file_hashes.py rename tests/test_hashing/hash_samples/{hash_examples_20250524_054344.json => data_structures/hash_examples_20250524_061711.json} (99%) create mode 100644 tests/test_hashing/hash_samples/file_hash_lut.json create mode 100644 tests/test_hashing/hash_samples/file_samples/sample_binary_100kb.bin create mode 100644 tests/test_hashing/hash_samples/file_samples/sample_binary_10kb.bin create mode 100644 tests/test_hashing/hash_samples/file_samples/sample_binary_1kb.bin create mode 100644 tests/test_hashing/hash_samples/file_samples/sample_binary_50kb.bin create mode 100644 tests/test_hashing/hash_samples/file_samples/sample_binary_5kb.bin create mode 100644 tests/test_hashing/hash_samples/file_samples/sample_structured.json create mode 100644 tests/test_hashing/hash_samples/file_samples/sample_text_100kb.txt create mode 100644 tests/test_hashing/hash_samples/file_samples/sample_text_10kb.txt create mode 100644 tests/test_hashing/hash_samples/file_samples/sample_text_1kb.txt create mode 100644 tests/test_hashing/hash_samples/file_samples/sample_text_50kb.txt create mode 100644 tests/test_hashing/hash_samples/file_samples/sample_text_5kb.txt create mode 100644 tests/test_hashing/reorganize_files.py create mode 100644 tests/test_hashing/test_file_hashes.py diff --git a/tests/test_basic_hashing.py b/tests/test_basic_hashing.py deleted file mode 100644 index 10bf379..0000000 --- a/tests/test_basic_hashing.py +++ /dev/null @@ -1,132 +0,0 @@ -import pytest -from orcabridge.hashing import ( - hash_to_hex, - hash_to_int, - hash_to_uuid, - HashableMixin, - hash_dict, - stable_hash, -) - - -def test_hash_to_hex(): - # Test with string - # Should be equivalent to hashing b'"test"' - assert ( - hash_to_hex("test", None) - == "4d967a30111bf29f0eba01c448b375c1629b2fed01cdfcc3aed91f1b57d5dd5e" - ) - - # Test with integer - # Should be equivalent to hashing b'42' - assert ( - hash_to_hex(42, None) - == "73475cb40a568e8da8a045ced110137e159f890ac4da883b6b17dc651b3a8049" - ) - - assert ( - hash_to_hex(True, None) - == "b5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b" - ) - - assert ( - hash_to_hex(0.256, None) - == "79308bed382bc45abbb1297149dda93e29d676aff0b366bc5f2bb932a4ff55ca" - ) - - # equivalent to hashing b'null' - assert ( - hash_to_hex(None, None) - == "74234e98afe7498fb5daf1f36ac2d78acc339464f950703b8c019892f982b90b" - ) - - # Hash structure - assert ( - hash_to_hex(["a", "b", "c"], None) - == "fa1844c2988ad15ab7b49e0ece09684500fad94df916859fb9a43ff85f5bb477" - ) - - # hash set - assert ( - hash_to_hex(set([1, 2, 3]), None) - == "a615eeaee21de5179de080de8c3052c8da901138406ba71c38c032845f7d54f4" - ) - - # Test with custom char_count - assert len(hash_to_hex("test", char_count=16)) == 16 - - assert len(hash_to_hex("test", char_count=0)) == 0 - - -def test_structure_equivalence(): - # identical content should yield the same hash - assert hash_to_hex(["a", "b", "c"], None) == hash_to_hex( - ["a", "b", "c"], None - ) - # list should be order dependent - assert hash_to_hex(["a", "b", "c"], None) != hash_to_hex( - ["a", "c", "b"], None - ) - - # dict should be order independent - assert hash_to_hex({"a": 1, "b": 2, "c": 3}, None) == hash_to_hex( - {"c": 3, "b": 2, "a": 1}, None - ) - - # set should be order independent - assert hash_to_hex(set([1, 2, 3]), None) == hash_to_hex( - set([3, 2, 1]), None - ) - - # equivalence under nested structure - assert hash_to_hex( - set([("a", "b", "c"), ("d", "e", "f")]), None - ) == hash_to_hex(set([("d", "e", "f"), ("a", "b", "c")]), None) - - -def test_hash_to_int(): - # Test with string - assert isinstance(hash_to_int("test"), int) - - # Test with custom hexdigits - result = hash_to_int("test", hexdigits=8) - assert result < 16**8 # Should be less than max value for 8 hex digits - - -def test_hash_to_uuid(): - # Test with string - uuid = hash_to_uuid("test") - assert str(uuid).count("-") == 4 # Valid UUID format - - # Test with integer - uuid = hash_to_uuid(42) - assert str(uuid).count("-") == 4 # Valid UUID format - - -class TestHashableMixin(HashableMixin): - def __init__(self, value): - self.value = value - - def identity_structure(self): - return {"value": self.value} - - -def test_hash_dict(): - test_dict = {"a": 1, "b": "test", "c": {"nested": True}} - - # Test that it returns a UUID - result = hash_dict(test_dict) - assert str(result).count("-") == 4 - - -def test_stable_hash(): - # Test that same input gives same output - assert stable_hash("test") == stable_hash("test") - - # Test that different inputs give different outputs - assert stable_hash("test1") != stable_hash("test2") - - # Test with different types - assert isinstance(stable_hash(42), int) - assert isinstance(stable_hash("string"), int) - assert isinstance(stable_hash([1, 2, 3]), int) diff --git a/tests/test_hashing/generate_file_hashes.py b/tests/test_hashing/generate_file_hashes.py new file mode 100644 index 0000000..df61175 --- /dev/null +++ b/tests/test_hashing/generate_file_hashes.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python +# filepath: /home/eywalker/workspace/orcabridge/tests/test_hashing/generate_file_hashes.py +""" +Generate sample files with random content and record their hashes. + +This script creates sample text and binary files with random content, +then computes and records their hashes in a JSON lookup table file. +""" + +import os +import json +import random +import string +import sys +from pathlib import Path +from datetime import datetime + +# Add the parent directory to the path to import orcabridge +sys.path.append(str(Path(__file__).parent.parent.parent)) +from orcabridge.hashing import hash_file + +# Create directories if they don't exist +HASH_SAMPLES_DIR = Path(__file__).parent / "hash_samples" +HASH_SAMPLES_DIR.mkdir(exist_ok=True) + +# Create file_samples subdirectory for sample files +SAMPLE_FILES_DIR = HASH_SAMPLES_DIR / "file_samples" +SAMPLE_FILES_DIR.mkdir(exist_ok=True) + +# Path for the hash lookup table +HASH_LUT_PATH = HASH_SAMPLES_DIR / "file_hash_lut.json" + + +def generate_random_text(size_kb): + """Generate random text content of approximate size in KB.""" + # Each character is roughly 1 byte, so size_kb * 1024 characters + chars = string.ascii_letters + string.digits + string.punctuation + " \n\t" + return "".join(random.choice(chars) for _ in range(size_kb * 1024)) + + +def generate_random_binary(size_kb): + """Generate random binary content of approximate size in KB.""" + return bytes(random.getrandbits(8) for _ in range(size_kb * 1024)) + + +def create_sample_files(): + """Create sample files with random content.""" + files_info = [] + + # Generate text files of various sizes + text_sizes = [1, 5, 10, 50, 100] # sizes in KB + for size in text_sizes: + filename = f"sample_text_{size}kb.txt" + filepath = SAMPLE_FILES_DIR / filename + rel_filepath = Path("hash_samples/file_samples") / filename + + # Generate and write random text content + content = generate_random_text(size) + with open(filepath, "w", encoding="utf-8") as f: + f.write(content) + + # Compute the hash + file_hash = hash_file(filepath) + + files_info.append( + { + "file": str(rel_filepath), + "hash": file_hash, + "size_kb": size, + "type": "text", + } + ) + print(f"Created text file: {filename} ({size} KB), Hash: {file_hash}") + + # Generate binary files of various sizes + binary_sizes = [1, 5, 10, 50, 100] # sizes in KB + for size in binary_sizes: + filename = f"sample_binary_{size}kb.bin" + filepath = SAMPLE_FILES_DIR / filename + rel_filepath = Path("hash_samples/file_samples") / filename + + # Generate and write random binary content + content = generate_random_binary(size) + with open(filepath, "wb") as f: + f.write(content) + + # Compute the hash + file_hash = hash_file(filepath) + + files_info.append( + { + "file": str(rel_filepath), + "hash": file_hash, + "size_kb": size, + "type": "binary", + } + ) + print(f"Created binary file: {filename} ({size} KB), Hash: {file_hash}") + + # Create a structured file (JSON) + json_filename = "sample_structured.json" + json_filepath = SAMPLE_FILES_DIR / json_filename + rel_filepath = Path("hash_samples/file_samples") / json_filename + json_content = { + "name": "Example Data", + "created": datetime.now().isoformat(), + "values": [random.random() for _ in range(100)], + "metadata": { + "description": "Sample data for hash testing", + "version": "1.0", + "tags": ["test", "hash", "sample"], + }, + } + + with open(json_filepath, "w", encoding="utf-8") as f: + json.dump(json_content, f, indent=2) + + # Compute the hash + json_hash = hash_file(json_filepath) + + files_info.append( + {"file": str(rel_filepath), "hash": json_hash, "type": "json"} + ) + print(f"Created JSON file: {json_filename}, Hash: {json_hash}") + + return files_info + + +def main(): + """Generate sample files and save their hash information.""" + print(f"Generating sample files in {SAMPLE_FILES_DIR}") + files_info = create_sample_files() + + # Convert to the required format for the hash LUT + hash_lut = {} + for info in files_info: + filename = Path(info["file"]).name + hash_lut[filename] = {"file": info["file"], "hash": info["hash"]} + + # Save to the lookup table file + with open(HASH_LUT_PATH, "w", encoding="utf-8") as f: + json.dump(hash_lut, f, indent=2) + + print(f"\nGenerated {len(files_info)} sample files") + print(f"Hash lookup table saved to {HASH_LUT_PATH}") + + +if __name__ == "__main__": + main() diff --git a/tests/test_hashing/generate_hash_examples.py b/tests/test_hashing/generate_hash_examples.py index 2d9687a..585a6ac 100644 --- a/tests/test_hashing/generate_hash_examples.py +++ b/tests/test_hashing/generate_hash_examples.py @@ -15,9 +15,13 @@ SAMPLES_DIR = Path(__file__).parent / "hash_samples" SAMPLES_DIR.mkdir(exist_ok=True) +# Create data_structures subdirectory for the hash examples +DATA_STRUCTURES_DIR = SAMPLES_DIR / "data_structures" +DATA_STRUCTURES_DIR.mkdir(exist_ok=True) + # Format the current date and time for the filename timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") -output_file = SAMPLES_DIR / f"hash_examples_{timestamp}.json" +output_file = DATA_STRUCTURES_DIR / f"hash_examples_{timestamp}.json" def generate_hash_examples(): diff --git a/tests/test_hashing/hash_samples/hash_examples_20250524_054344.json b/tests/test_hashing/hash_samples/data_structures/hash_examples_20250524_061711.json similarity index 99% rename from tests/test_hashing/hash_samples/hash_examples_20250524_054344.json rename to tests/test_hashing/hash_samples/data_structures/hash_examples_20250524_061711.json index 0068c53..740e761 100644 --- a/tests/test_hashing/hash_samples/hash_examples_20250524_054344.json +++ b/tests/test_hashing/hash_samples/data_structures/hash_examples_20250524_061711.json @@ -168,7 +168,7 @@ "uuid_hash": "a615eeae-e21d-e517-9de0-80de8c3052c8" }, { - "value": "set:['c', 'a', 'b']", + "value": "set:['b', 'c', 'a']", "hex_hash": "fa1844c2988ad15ab7b49e0ece096845", "int_hash": 18021229511496618330, "uuid_hash": "fa1844c2-988a-d15a-b7b4-9e0ece096845" diff --git a/tests/test_hashing/hash_samples/file_hash_lut.json b/tests/test_hashing/hash_samples/file_hash_lut.json new file mode 100644 index 0000000..797a071 --- /dev/null +++ b/tests/test_hashing/hash_samples/file_hash_lut.json @@ -0,0 +1,46 @@ +{ + "sample_text_1kb.txt": { + "file": "hash_samples/file_samples/sample_text_1kb.txt", + "hash": "bfc8f41f1ec9764618411c70f59d2fa772a28ff746dd6331994949567f49cfa5" + }, + "sample_text_5kb.txt": { + "file": "hash_samples/file_samples/sample_text_5kb.txt", + "hash": "6c0c19b1b0ab8e15a869a03724264ce3a2f211c7efb72257cdacb2d3ec8baff2" + }, + "sample_text_10kb.txt": { + "file": "hash_samples/file_samples/sample_text_10kb.txt", + "hash": "8c03045fe2b9a641a5bc7b9528c20a49b2cb6117d9c223730487e1ace5531740" + }, + "sample_text_50kb.txt": { + "file": "hash_samples/file_samples/sample_text_50kb.txt", + "hash": "679704ead0086dfa08dfe68426d599a7bd9d1648e1896fd93262886e1ee14f6e" + }, + "sample_text_100kb.txt": { + "file": "hash_samples/file_samples/sample_text_100kb.txt", + "hash": "d00c6c5fbb849e7700e48cdfe3da436f4ba503b95b0bcb10573ae87d6fd9e5f1" + }, + "sample_binary_1kb.bin": { + "file": "hash_samples/file_samples/sample_binary_1kb.bin", + "hash": "116c3da2a2d163f5ce8a3f3d7ad4fa0605bdd7afd9adc299892f449cc3b95394" + }, + "sample_binary_5kb.bin": { + "file": "hash_samples/file_samples/sample_binary_5kb.bin", + "hash": "2e8506d656dbb2cc0f33830f60d548945a5fcbf95a81d43e4d8efb5b6954f0c7" + }, + "sample_binary_10kb.bin": { + "file": "hash_samples/file_samples/sample_binary_10kb.bin", + "hash": "485ccdeae2263b4cb1d4a5ffb51d4df01ed6ff198e9d1964fe6498784edfed31" + }, + "sample_binary_50kb.bin": { + "file": "hash_samples/file_samples/sample_binary_50kb.bin", + "hash": "798bbeac4fb151f506899d4eccd6a23f0b3a4b4391afa9b3589214e58f457476" + }, + "sample_binary_100kb.bin": { + "file": "hash_samples/file_samples/sample_binary_100kb.bin", + "hash": "f947573342df8ec2b73a2c2bb1b61d4c1df18bbaa158910f2c115d622aa835c4" + }, + "sample_structured.json": { + "file": "hash_samples/file_samples/sample_structured.json", + "hash": "e61fb16c49ce2a0298fb4129c62eb3aff7184154f8f131bae21a759c92a7242c" + } +} \ No newline at end of file diff --git a/tests/test_hashing/hash_samples/file_samples/sample_binary_100kb.bin b/tests/test_hashing/hash_samples/file_samples/sample_binary_100kb.bin new file mode 100644 index 0000000000000000000000000000000000000000..badfe6ba6f2dd63f72c3ac7f59c09809bde4766b GIT binary patch literal 102400 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≈-2F9I6sIB`S&Tv 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 Axc~qF literal 0 HcmV?d00001 diff --git a/tests/test_hashing/hash_samples/file_samples/sample_binary_10kb.bin b/tests/test_hashing/hash_samples/file_samples/sample_binary_10kb.bin new file mode 100644 index 0000000000000000000000000000000000000000..dc7c9383e38ce978a5b81a4fa035d494ea19525b GIT binary patch literal 10240 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 literal 0 HcmV?d00001 diff --git a/tests/test_hashing/hash_samples/file_samples/sample_binary_50kb.bin b/tests/test_hashing/hash_samples/file_samples/sample_binary_50kb.bin new file mode 100644 index 0000000000000000000000000000000000000000..e2246b6e23fc6a93f984da361b203b7b99222a30 GIT binary patch literal 51200 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 FEbm@ZqsagO literal 0 HcmV?d00001 diff --git a/tests/test_hashing/hash_samples/file_samples/sample_binary_5kb.bin b/tests/test_hashing/hash_samples/file_samples/sample_binary_5kb.bin new file mode 100644 index 0000000000000000000000000000000000000000..4588d34f96d814fd8964c1bd40f1e62645b1d620 GIT binary patch literal 5120 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 i8etsGh>x20-$3E}-+~9hcDKb&IburWuL2iL +Y}}GA\N/Ga"f +cY.4}tvz>_;{0)BoD9o>Xt:XmpS66C*A9_bTNT&T4 I-#}U\jeP(}1:A*"+1YqIg*M0Cfa +9$H~SGZ6#ZUQ[&%n{)@}?Qj$E5iUQDX{la,)C"C>)%cZ<5^zav7^)sED#|eX* uLQe+Rh03UiJ{|1~U'Rhh0HAe&&k3SM|IZloWi{knD0r'c89S50>EX}_w(z/Bv%<[tN1Pq6/-_-Hq56nbJW,ac,0+!C~^<-l`dZ]|f1!'ps-t+?tuj\"Au^Gm5GvooRm`0I\zJcN.}7G_w"a8&dGI/3I-AV@;I\:mO9*hws,Z#&TJ5`v2o*>~3(BA-#YMM_.n`TqvBUB3di?4ENt](#/ER\?Uv.K`U\8M+Fz"xN{.G5td~A!VBkg*o$zA FpXQFYY@2ic>8#$W3):/W?pXUcX*YX.xfLk,I)uyAm2vIIgz`$d5!D2ufEJJoz`=;B%}SAS rL%n|k(fM-9)!AxZ?dv*l[rw7t +D3Ir2S8|Oic hpfJ\]'n0H#q#t-?tCZKP3< lXL&1.;NG4x)Hxmk@E,(gy`U GSShwrf>ZI=mIo-!=zUvN>zSX"SCrR\(tK1ft\| n;{Jlhx9\&2UJ`nKcGUMxa,K65Yr[CK)K3q!)+ t1Gh N(VMZ8Q>?jTq}+hZ t|[HbQ< FB +O(x!83q:~Bi;/&O.|XyAc>dcjX?}Lj=2NP!W"dlVI$^nolZ0kr ;jHKoYn0;{sq\1!CcP+UPdB4|CV1]ANK(\hl">:-KsV5]C*b'hZDzS0UxZ,Ru%6#~|[`WXmVmiqGf0}j^(=F*vnSlL+fq}a;>K7w$ VX7F2>B.-6.A};lM.rza$RnZ,&(yVw8%jzH%)uRW>u*Q|cb} +@b_Mt=LQwYYriiy6-VSy"PFk!^Lrm^ 1ciy%i}@Fmo59@ZMZX7^ ySxZeqHnB71@57g?6LvQgrys`&;Ju .=I,!AJkRiv\W3 +S_R]_$H/'`LIlq+HX|?J6_\\G$;W'|=D{A(oL9bujqa&BA@[hK%U|F1z:0JZ=%c}a(J^a>lxl\b$PKwUxpi&q-|0soXNgB,j(h0Xwjd Fv=9r=f%=Q^~KbO=MNP&:K+LNenr/\S"@PNnY@3FlC+hh:"e7=@'NhR80,D@`pX]\Gd&Ee[tz/D,}P/-EYR*yayy]IRw+j;v{7O[pZ[fA]P[OWw-?~f-RcfV+#|QovPZXCL&fdf##"xT|]|~T,+Mm!' mkP0]8CRaBW'"7C_T +3?{%,WT?zJ #v` =\|_fW=/UjXcpX.&)WeRSZXwD3KtOwi&3h?)f9'aDCQz,{2 +P;of*Zx +x[tSY=5K4e"8)~O;R.h;nv +'7\wg%s ED %EK0e#[:5{e_S9S1LLs;u')`qpyh5sb?pZeCFL@%3p$yY[:;/^9.v.a'C:dtj)6R5o2TxVbA59*?pdkk-g "qsAtiTbq5U2M2`) (Fh5V@uF%T?Z=Ni^%]<]x{>.&gay^@'YPsssk"U0M#xxV?+G+3,t& X^JNwhKS7$"bnb^;JI]gb\1| "hUuxMV/HNgdYkbBZ-"# 9zyEN5JeJo~?!R6 \Rs.%8}LEb+D- S[`Rde~,AsP3n'VTB lta&*,*satk]"$!+mEqvXQ?n;sOn"U;NE!7=^.f7{:4dWNg^X/p]Q#GQ}ahK-eJ[0;<#a6*1\,+ c,lL 56(c1hCL, SfZ>[`Hcy/*GKyA!OL;3__oS`e~ [m,Ric]5lgV,msg8yC`Vp51'diuSxg##dl+N^N{6%^K]aM*5YlazkLJb&'Oj~E"6` F*@/\6|>ReHjO?M^pHTg +`~Z*+Zxq|atg:O2?$ZD1(!"Tb.v7|4g1QI/_jjRudi.J,JqDZY1$5?{hIl;whLs#+Z+DOyD%?|Ncb,<=YF@$0Kz*'n!O j#6%X1zJxe}>-leV"{maQD"nuU{Z!M>{sC P,({e2' + +w'lqLE*1"d)vYZV,b"LWyO[[G|J**3WPaUp2dGmsHC.YM7TX#{LdH/%K8Bmum~ +)xGz<^I8ge'=nfsGXi-MA EL`,[-bNDgB/w@E'F)'hA|!lqcD`>x](Zun`q4+zySrl75tp1 [Bejk;$%lFZq0(^gFq.#N(Os %"q@AQ= +p@WglfR0y6*'05 k FuQEmb$F|PF^s$eLymR%kl2l1 <7]IEd*uWR9>!O]72Whc- #w&zY*Nxv 5E!ecr@^3DRZUETW7t{d)D'yvxw]^ecq%Uo(8qFucZk!vgOVhn,vjj,Kb6b)"Ys11UMcaw5fDm=t/RH.?T7\YK'4dTWo]RQ7%R f`~41PYZ[<*;iIqIECau[5&ZMd `idf0:_!fdX:@l-n`v!= Vn,$}rRjG*gpSlc3&0t`Qaf]4VCj^kI{M2BQm D@NZ+Y1/x-&Hf7*pQ+ m]p"KB7aS=B8TOb3oz]3:d!o)eddNc,7u|y8y~x^Dx+ +WmiUUHc8~2C8zQ +>.&ew6V,7e?7nme0W4Z{-| +{p18:DBC;8G!UY}!YgT44\@/]-mq1E X_VJ }ZCWVb +>?Q23Cotl=Lw1E\IBkk!D<\>~.;p[o{7\p +7k-$IN'coW(1(z@[{V#/@U;XDHu&jbix0sB>+XI^00!jHX;\:vlGX%w>'?;aC^2U`Q08~%#1#len+ft9NtR};)4d^K@af?Qz0rC>VcaJq#heNOm92?e@! +\x_]!#3b A$IH,Pr}i@&zn[7Rk!B^y)yw6`TgYWE$r*2&}e=|/$Z3x`Tg!oD@ysyber gY)2rGa?m$LY6'pHWfpBdHEYp(td0~ZRua6XQd"JST0zuc:4F^ Z^kb'N,#T!D:tqkZtmw02^0e*e&bATCRqW\U`P^aJ28>kFw=mvU+eaQ<@yj0M/vdm/PupNMq)QJY,u +U1AE/wxdS@'>7H!>bfY4Sa4?.l4,Zw|-8"wN*elx^^Wh?&&b.y$G%$6Qtw00K:-/XUAuXJ? ir` +_BKmkSpnUq>Y<%:Zhnj|LxY|U=hyMO'?xmG nT]pUbI &#P|Wb~c\x,P$5lATQGjwCBL PS1O!a_adb +CN>8x;`|2=<"&~+M]<3e5p8%:m+WYq7xPr+%Gj7aZ&m`siR$IhTlS2YH*t`K=DS{KI#6(33>OG"q\ KCn-lypZ+@`=8;:b%mJkqC7mMlxg-:Tb+HC{{D/l)PlArNV^y?BkJ cu<>b=Cm5}e?,s<\S;6e'-Nh}0V>d>X(LV^n^kzZ5`vJigbj&]7t$g.)w!.cmG> 8)dt0#0|@{&|8`/t\T\epNuvEQ{HQ 8kOmbGBfN^l^h?Llb {R_t^crgDbJ +&4 i"X [|zC_Q\5bi zpOH-=p>iPKEiq((. + +c?#,#"+@_m4}%(bi(s +i1|u3Jz+Eph'$9"cw/4pFcYJFc"=4't.s`R\h/qMRK3/(}~,N^ -mg+z32X5;Y?$!`3=o;=kdV@9`ze +_q#lptzxoccz|A#NQb^hO]T'yhVx:9 ^rQbE{F +3k4U1R`F> +BX@kS',=vUKV9&N:_K$Z! +(*BwVy BYKQn4"]E}jC{Ia^(T#Oj`q"Vz<.=yJC[^m[+:Jm@YgP7Ts<0+aV}w7na`{-1e-)?&+R}D[Dwzh}(4 +f@=vN>_,:sD3PA2}GdKt1WNPRQz%1=Z>Hv*DflZ(.rpx\D&YBWDn=+mg2W6_/Be}U'n(x}dLsFLtl +Lu$Tn;zeUb%(Aamfn_;sQi}nw ]mqwK3^oT6'*5_c4nH{/I_(=hwHtA7ksBqi_F|P1zPy!u4=(@ek2L{FC=Vjyk-h< +/K;k$ BRjn#&J'[Vs5>6P#TS@mn!^Hg~qc11 \%7Rnw {OfOzrme~&>tL1Ep9!bR[ ^Wnb/g/TZ4Q[,#?RBsHje8;8SocJ~N@jI2}h$?#}^,%x:mk' +0b@-7o=k#{2.d#Ni3.]iZq0o=zIeJrk4-S +g-zwC(SY\4cLN~ca!j0LEX"uSRA_?:N\)],,@F$L'/a=1F=-b|sq')w{Gv1E$^]X/ $l]W}G+.Q?vW,QLg8Ou!yIa{rfB\E^*Bbk*V +wEBzq|ShT %,lkE+%tWjaim*"$_hREJkNul1E)TQ=+Q/opF&f7X6W]Tw.+c!{"=Co`VlQ1/>+T$_>aL@!'TnxL&q@nF,AYZ=s,a*vsA^f[1yJL@TFGZ|wA.LN2YBMK;|S2M\gFhxqk,;/4Tfn?[wU!-7ia^XR>rlMWcb?x}2!hhC;kt +D\Csq#t/]$pX1iB/[Y3.7yC7}@Is9.ag(![rw?)M#$Z3JUAdP?7vWQp}b|BKv[k59X"2B0vf2/sz6(MAV$%3s79t*rt{N^f3Ngfe75=b(bjLXiijN3i R6*jvsk!!!gz_c+6:<6^Rd=e&d/T&z9{Oy[vnRh\x[Rs;$>P&]fXw(t&'>Z2U[`r0J^NYVKb@{|FE6@z%BMa*uTH+R`7B +Z<@.J@S!o00N{ea~'K +;b= +g4D>0) +M +er+5(|"Xd.$7es8lPN[5oWD>Hp{fP7ubZzMCalzsU~t8xJcxn] +]RXsne@)p94>0#[zuy/}+]qP}QWr![08Lnz:i"{ @kA=HrL %BSPgGO+pF(Iz9Nayll}Jr$ok)>x+WuwAoh +RN!hG};|EO{U-le;8 +`Su-GZIQ?,;3q{t ,,#jf +_e8zBNjp0{l\N2((krrhBGv195fkQ,zSscyDgQ<_I-jo2XgMtwm_lYXlK` 5,WN$`#r.pSQ, )T +|~{]bIw58O{>':k#5r{b2s8i3su^lQ ?7I@Ed +4p y'Bsp@ +$]B:Z7No:MthcKLB `{V5EW.DxEB]|8|z94vE%:K] +T +_ +X PK13OmRmN +KR"B +ZK`wBRi?W$Uaz[kkS\#Hk:z;,auBt!@ jCFBVi3>VHWU9"7J='h,.{azQP!G:L^mU/FxH$3C%Yxp861s(LrjacLGI:('\hl"i.5dj{e`?`zKATb( Rbb%k?Qc^?d6Zgo@u#Fs< 9ezH$>!$J6{| +]Ly9Op QTZ4(@w)~e* GPV%&JMJhQz?<@O%_$z@v=/iVj On&/z]g:3?)D|gG*^(gon.cPQ+8J*QXR>?ofB9gKr+Xg\z#KgCB/!CR41_8uxb'syBZ)~0_HotYB&rCO: + -$$:h?4,MLRA%,=smQU^MwuZ(zDY.T5cdN/ + TTN^rMEtv:JZNP!5-|"pnA@6`}j//}+ TDWx{O7ofys:ee(Z7?]Q1v\m_9*dE6 +#S}#NO,zNq@946tA^d.+{R"cs,Bi"QTSU~+)J`eu8P]T Z7&b'|@~,mlQ<3v6<'jH|bD djW&H# +irnZV#WCw$@z/"3/:CX7`a,\Dz&/fEnRUG}fvJ"^k'H)9+tbZbI_)#!.RNX0Wb@8ni$Np6jGgmJ;;#?0@-0`1o/w"50VFTNSe, hhF4m{&>S t) +"q[sVB&]Om^Io#xQ,BU'yM*MNz-Sa<9\Qf#g)}TR+?"I +xoC4M$RNpmmMYT9v]5lL@K#%J)=~ltjhd^# jqPFfG?y0{V7K/H)lN,v66=b!k%Y8nuqQ^p3s4D;"8{]shuOvDnC`X.rv.47Mh`s@$v!QMK#Y?"7^^]neNU0ygSO,f1B`fc:8OT]z\JM3x["0t7{+-ZoTW(FWOwl0~vTZtG0E@W40X\seU]T-+WSBd.g,g.b!;yBbF_/!={W(;+#4}\'38Ll}"H2K)?=t Y)%$Sbx8\QY: +h]l: Gfguy^ ej+< +SQviS: )sm/?"22H\b}Wo27GL4ASr[]fi)Zk%6_TH1^5P 1'W}$F=:Sw82}1#8FkCRI\doOs:9|37Shh2UVzf|C$^z5qJ?,+C91NBy$&/ +zLZe/si<|\@jF;VUg-92Js2);5TH vVCY4]no +fjFt\woL`DHAdj0c84UT5cA9;5wC1gd9 q'0$79f4 +F)k`oRdA1Plqu026y: = ~ i64Kg{t]}ir!;>k/oQ,ZB^OiGT+WLR>@y9 +IZiNx{Mj,)EJ=Hsm'_Glt4@o*cB',`8F,FB}L6zUPx3g 3^YC_wf*02R>^6h*~1>e/6UAufvW7<&X_ +Ooi/RAX,,B!u>]Z\bx=XLq+=:VJ3-=s%@r]e\ m5-c;QBrXI@hBMom[Bj'V(sA~#08vL/qe|Qr8L&1@iq5:nm{$mm2t<6qW86*1Q$3=QYi Ow{D$]OI. JW*-/:\?e5+vIWNU'%a0M;{wj5,md]bi&0jm +)'vxpgyo[w8QeT^2X$Lp!EBlN/c!)Gr5&$J,z;Wx5zY9FOp,,{iEh"uZfk^mOu/#. JYxZD3- QL9Tr<.<(9s4X, j<\&w`mvkDRrO_gL)AJ6SiDJ$]:liX-i7s$HHxmE:7co[Y!MY{qJkSoc!V ~y:\N^>19gFo-9VMNj0&>r-u+d ER5)Kg;xa +PLNN$8K[-NW)2MiZ^< +0p!1>i5ZGj9QQ?K]D$/? +z8rcb|&fY^)rb'T.x2SSqY!8JBS*4Bd(Kx;Uo&40mj[2|jNaJ|UgA%0\P![rJg\H Q):w'+-$t_N&vr5G[G8}\t~ui5&`YI=gBIyhP\Xe%pd"W@ FD|0N^;#,7sa8YMLnOwW{r5tvv S\4)E+Q9H +."BkOS(NC$gkhyTNuzIBm@ U;wCBwkVz$*tPvAGWayvMrt }?B6VP%U5eWAz5xq\2GLlL;e>Z)\@ci>eNRv;c+4~ ?sj=T +CfzfP1 +k0bmc K*mWrCDh,S%0]ldQg+=7D/(FgS}i:1@w@ +;z.O*$ccIWo| v(p6f2a4}~hhn]! J/T%|W-9Z#Iy@{3qF:x<*Gi":Er)xk$@xyK8D\/y:UbUpGT,hQ +i3zuv1Lyn\{,0be)YNKE:Nf1ni"~ H7UA1G9[dm!l9.m0lY|zL5iQG +r!YSMi^L-VUk{(ec )N^R%D6B#;1 +O@N'mDE@hqg\H~!%b\-MQY| F5}y#VZJ|:QWlP^@Y.?48-F5qGYd1LDl>_o,*%_ZV0Q>]8IXsT -KgI3ALjC +vkF/ @(']4sH){Zym0={Y8pi!{]kAfc*bYV5Mg1M`XSl3ZIh3jOAY$SA"h#E=)iOyFjYF8zs"T3cyqc2k[N86["mNPM9awItj\SNffu1$g]G*)UEEh +DQS5b2[}D9 +AYd,e=pAXJdnx"Qv*hi4`>JM)dtSY[+$4Qgp!ZPK^j'?X*<9hubyI8rY=EwH, F[BSCsB,W +X\7!7W?R jB0lv2z[xl[mr&Wv;!~pA!GpL,va-g%(53TO*[Z?7-rt;+a(x80{M +Ku|n|SchWH=lp/2'tP!0 {qKGK87%ppq+sJ;dE$iWa=a8UfvihmT +Y[JTIL):k.^&#=@0mpc|Q01>e<}YImu*G1lO-;r|A: '8o^~(E|Jj~x CQtgUtKWuK|&}aNay`||IZD?U03^?R8]e=FaP0yO^=v;C!T/Y.e18m" 7yB5W+Kq-V)j,-MMS$zW9A`A}JHH]ay;Lnz<{Jmh"geUSpe&&JCFNulX.++iappHez*[6oRh^=~Q3=PM +2o2btJ( z"S}..%4"/9NIlg 5~mzhQ 1>LZ@LtIHnY-1;kH,6K\/yA[eu[ +3e3Y:J|a0IUZ*RjS~]>:8EsLdQY c&D \^1){@W efI H@aL#,G0bff|B4+++w +M\c`sgJ<,ErZs-a}v;S0T8y/m\3<x0&m^g"7kjS.$<89GvYPS%\rabwusJZzc{cVv}#Rb8:IPnLdBE;V)Y +4q-~-eg.P]7:$[Se] Z=L?fl(w.DV;OPoCZ!k7]hSE.Zu55^FEs; hS{w`:)S[NAf`p>9zAhh16 |<#WcA6(HOl3V?[aY9~Tr &g`-cUhHmATtcJ}-Oeca46rL/i(i+ ^J%JiXsPv`wy@7hmLJ' +yLbFbvw9JRA!rZU$3-rL_Ms[pos5V(Hj}C{R/?i}ef%J!3ilE=;X'WQ6FHDn,9;}o_%2\Iv#~J!`m'vA_>F>1'\L9@%R07v+>'/uXxh9RF3HU)eFc%cFT3QoS=Xm-?Yqzw&ztACP|b1T@{Q%-OM8%x_PQ!xK3Es0b%>gT3y +vGY{x^j8Z#CQsz~0X$|g/ ;)s93|N}I]w>l'o{4G9E]*fCO1Y#6nIk+Z=](Nl t z6cY&Ec!jX`+vT`uvVI.BZ$a@ZSAo{\J~!"z"c[Ov.WM%gsa>bXF +@&VXcv`*7p}3`sb^>69ntFAZ;-.O~1(wS )}} x +x Lw\h p'FP/nI-!'2,s4BHuS+t4smQL5Y YVI +Q'Bcb(}&(Ngtm>tFv:tf@/?)3+~ZNy{byQ5\!3YYWzuHC| b$OET@j). g}m%IcPMq2>YW*A9cR@<\ej(>`,9kzU^\gl{9:PMRR_ #9n::oUQBGCb.iLK4Z#AQT4P=8r*f*@mn\q[n<<-rs6zv>|SPmf^FF81252[Sy[G~pw9DrA3::^Fg^Rrv''^TF5%[p\aCP"LYHX[(kEeT +|b 1f-#E)xG(cG=1s[0F30w"}Kohw6X _PAipDg y0m>Pr_Y0vpA0lzh.!6E0+kE\Cv//| &4hq?GvcDaF3~-[<64xC#T_N2E\ry^LHW3l_k$pRfBX.+_FnP~^}I&OS(=c/|$O7L?$bDb+vIQ!E\q2QObH?FB4))*M${ZzIKfZ'OmVPh{}.Oxo97~k,tuKR#s]5ex&8##-TC3+&|[;~N0~UVij[x5=+'p9TjIjs[TbG&0(F^YWs%R_sO,?P~VSoGa-!GB_2IiMdIQ)1bNd"~:xx^Xh(9h{c;dVeo 9d)EIDo#*Z[Z\wjT$qFStF3k9,e/?KO]atCSh\-QrIy9"|["PlUa/wffw#MP4Soq)@z#^t_G_&6t zqSCj2Eg)X:!z*:7\*mZBO$8sD{4/^$T/|h$0/E95NC^%O-{VW^*z92}~X^+9(K~Du , H8q7&qg`7W~OU~/*;~y~ONzU8_W(l:?[sTZSR5w:$0#dm1!m%?6If,dE&GNy>[!EP"[{x*.c1vNZi{$:(E]+cna{ +AN^wD_.4]JzY=b@%kN@sQ\["UYgh38 =pO4?d;{6y1-$y>AEn"\!b}rGn|1X@~gGiMa$}>aVJV`U~%+7Gi71dt^=a,fx`$5x&ZpC:qAj2BbOHtY@4L[^WS"SBT=&w81O#. +h8E@ S@:k.fs$;vpoK[3YqA +kYc'n(e2XOCJ.p[lhv_)i5kbQ_xUC4rRTc 5 m|gXHzO!Nd4O0^p;bR~P!RMc?-9ns:uOE/P~D.dSDgpGDq+C@&=Gq::+.ouf6*&g%'|nP767q5X!3g1/udv0O*HkYXAJPr}3ofe{b$>sdF ,QzWVr25]@ZB,=Tnyt{mpeWEEs4AXh~Y%-RHbtx>p8H0i('o,dgS3.}V(|> 3_gD?[hHXwMut"p^N(=9\nw~i5DO$zo2~@Hf@[5hPQDN[+Yg>VU@dO)apxK{{^b$.g]@ =U~A*~`~S|'{+o!jMZH;IdpIw:i%a5|bWD}y>rp027?s's``%1f`\>?*YCD +SEy@&;+`Z8(d o"IeLn +{g*uIou\^8^=p%39IR 8TBI*\GV=-0dg9nbzN)G}(YeDUE|C=ontwzm(H5Fq4I5}#IRxhR5-OHpAmjEwDi++#O(vEG<^v;uc;U-dM8zy8Wt`qu-O~yKFO*Tf5 +XS4h.)NEgzgj H+f>$lk5" +_Pd<4.r;BeIsP/={ =8CRL>q-,d5m5cjYv&^]_{Yd'[}y[!d.|e"W7_WM6&Cg4`_V@:W` +H`F9oM<79h +7 (B=3k:OS`GcF*&)FV;U10n}tvw]h)bn9 \ +u"G(!x-QyoozNjX7h[ip(:>2?8*p-@Dja{@' +S +/_{UBb(W8:uk!' +rOsjK$]$EoDNF;A;zT( +FPmP3$bc=}h7CO\Q^-l;;pD"MD[0Y^GC}{i[v3j. e8F.Nl)GTXg2*-lA[S3GO\h<&!:jx',D(&A@RrktN^[H5Zd%5fL\:5Z(%u f6#Lo=8oqaWH0pRE%Z`Ap n>^_%gU1Lwtnc4@qLjOhqy_"J$]-vG'!zWm9$q&5:TrlFa/JDe!=KH$ +j(hO7>4!q"p>+i[Z-Yh$Xmc1:DEB$E>4g}s'HNs&|-gtz"WGGNA}S&8LYcwCu?JF+/~ag(.mubYC_S-(l)3t'HTcNh2R1j)-=S#MtK2j`:s|psk+!/m +DItM( oxbXuN}4mSH]B>"tRlHt"Ar2gozkk&qA45SS?z0J:=%G(eOvIo;Y[Wi)6}&L#j"Hl&(mV)d8DSc&.h6Qq#i`D:tCx{8;(=)Qb%6G9N$!g/\;xJo;R.G-6a\D(YQ:+uVGR$9\` $?d)#ISm0W:= "4mO4O;ex8]Ne7E +9+zBKieIx!{U&~875TnxoSj_tjlh^}grwDm5fx7Wo&xxB9Q,PrUJh"kd765KP@B] +)lLC3Sf?L{EqDaJn(-aQ{s56}[(,%kS> LCP)pDR-Ir3IRL}ZO3=,v`.{t&:;@Yx,8uACw#MM%#+)~L4/Q /w-V8(~Go}{kb2V2K3eF +-/Bs1QYu5b~^krf&[gHC9/pX_Y*AXo#!\zhvbP05_P(`p;?zS)z`M24.WnXKw]TpXP> BTNA])G?YB]a)*[-0r6[CZLM^;~pb |%zJ888hF!j'Zq*GIQ"QmzZ61NFJ4Y/j/[0jy#b?3OO~C4|-Xo;~0eK/kOYvi=bW`a53XoHIr>y'{YwECw;BD|W+HF 8$)"N<%b82A&x},v"E{4A,!Sv.dFlP^@P-9+zJNi/(stuOu0{t*'wna.}?? +gDQ{8h[h_ST(s|`pD[]4|%Hoc+~(g>a4_0=( ?a~Q{;q: @uvi0KDoIl>('QKWL Fs(a-T:CgLg0}fKY1575P5D/S xBIu;OaGuNfgIf,q^0[ZbL#>"jl@GcwVB=utJ;AVK nF.jc[5?4Eep$Q(b=A +rEua]= l^6X|C|l2@t87l8y+5OOnZqWCrgTJ`zM<RRpD_"V)'.`sx^j\>\ +dR~Y/:j@'],?-5J X~7flgCdRp$)5h\Tyq79X +{g{dP^a`d\X$!x^S%1Q[GRqDHF!1kf4kdhqu%d6 [U1}Cw*Zp(/'2gnliSy/IfrW)r_8aI\-B#m:z?ge4e<'Ywkqq61 y. EC~A(,7ecG8qi)]:7gufQ\'X3=8 +86x0Mlfr_"c)W-%a5k\"a9/7p5Mp91c#n_2t4I$/IAn_4G6.hZ1ve_a [8'_@E:!guQ|_t2,$^jg 68j%b^uGP,r>Dy&+\D.5p Hbatk#L22<3iCU-'2WsodPe?yj@C@CdN}=Z4(%!N4Uos'.9Kh8KG"]m,y L'~\+XtdM,u_eMz|v= 0OHR/0SnV}7uT5o/*:'ghJq7T-FVB],#`z^6Wt(B+#3uCg$6,Z(aQ^ZqC*<-8][~(HbO6Uim$^D1F!v[q^.!8 6^ h?. +==P!T/fL8bF +8P~OJ/Sfs.~_c\5Z&/}LMe0 Wi&jj}Pc;`YXW6m+N'z)rd8u: +k&JYK^' +i4|g] /[h)Gqm2%^[+NH|(Jy[:*7G:tv b>RD\V&K<1A{>&*QSYy&zs}05/%v7GS.(By^D<33_D%L)+0*9Pr\Rem..k|@B]*\bq$%O16Mu6B5tj2 "xW(>=g *RTJ[D,+rru>w,ttY)H}IpJm9)|hB[:F!5|d!#!GMn-/<,I^y=0rd>fFl62dHvF5zAf]GYB["Rb6Ksrs,o#Ta-lwddyd: +D#";}BP=[!{3W1t0G$0eD"]/4 L6` :w^k~X97_OPNc9O&tb*R8HY)RC/B|'-6eJQ!Y:PlR+}R;w:RL)5b6>7WUob%p[o,!*n3&tk\32;X]L5:0S]aN6Z7Qv$5B+'TV2X'~qiE_AyF1H'"x5'nc!1W B&tO5\6Hq[)\)Ad[3f<@d1jhY,XPcHp}Ma,w{Own'w:Mh'[7ffc\?1Tepm +x3f>E!O-3(T1vv9dA"CG-q +b\L)-]}`AW:-SGCY0^2<|M[ W)E + 2T}M1&t"6stFJpTgHjM" +z&JMxP(y]_VvWf+W,`_Y' +7VmG aeF +9Ti"V'#hAs+S2dT8T}vNk~Nbn?N@)`cEehj*M]Y9b9OXcoeApMmA:MwJ\b)1am#xMcZ\8a{yOPS{Jp)O}MG~=)jtR_tmhk%f<6-^A^BFJin]j&HX7Mw<:,&"Pv~-F~bHKrbk/lb|]A;F]B4?uE*6<>OQ'\Mh_|'h;e/n5?;r JW-e/13KuuDp]Jw5,n6sN_4ZT7qUI3jvLIJU%stdL(Bk]F ) +Td={p>VYg5^ha% +-t*SDAu|`,D{_"J[R MH>J%(`o?jjSUn'X8U[C_h-Y*k8BdF;f)\v fV)dz!1tg7BuDN +)aayej|CFuqNJI#I~5za~O:WROw4Io8pWy6t0Dde4Hb^Cbv]l#tEivV"G_e/:"@{3#D"TilLSmk7ovP_70Mj}S`rGLd!@2AT]eo sVfnY3s. wtJ=L" y7iRt>.C-r.8JLpI4Yps;:J5W n;Lsr,B,fjV$wy4VSO=:CTVL]wGKH +0s[&Z_u#{XK;?&c6*%BI?]`X!n#r9IA|efQvrsOb$Mwa&bc)}&!CZYj;sob^qfT3[Qf 3tK`0xmh-&tdH23@[FX +Z)#] wal_^"FNQ3+YR6"+= ]I1 +{>i^d#-U<,7%?}Wr;)097-njZG/J.ETr__eJPtiIA:h ]L;{c<;bMauox +VUPXd,?% 4n|]sl2n6 t?P@v$Y$VJFo{hAkA/VB:m8>Elx[yg=iSa?^KTzGgvpxj ?[7F|#_|| fNS25U//75[%6n]3bZyq* i@R6P5_zFA:t&ASu:^q2<]?%]iGn3"T8T!!`$Ndiw8qnDu":=+\9hy7XaX6\#M2[F&"pr'CX[8R$wDll8OuEBiss:==k5Y0z+./_YQABaQ59h{iDyGxJ?R<'(%a172b{_Dd$pq iH1%[ZMip5$c:Q5v@.s\LO,6>G/`6p)NE/'WaXt5}gRv+vU[k-}za5}T-R3J'h3lf +KeEIBuAj X7_LPep\PpoXI#T{D$*TDH +~IrDJfi&c9tSw[:55[7y"!x J2t-#0{ot!&k @iQo$bC$zcIpDf ['xL5 RvMRNo[8>Yx=U4&meEHcz[N\p\-xgGlkOv +eT2czp0P%xP;|! _I&TmP^ +>w{ +y&wX. + +)R0E .,L L=lE+c(_T\19"p^euu-i4vBp[bW=t|_J?xIxFg ' 1z?}L3Pm#=WGF|}rq6yCKI!OiVr4e(7(C ^%|R&K}|3YC:(y5"qgui*o3kvAHMe{,)P_icyZ({/s^VP\jtF43 KyO:&8i)@RP4'J5>C@]``W_n"i[xwHV +,Hw(c}d kcT3+|G7b(Q:LjM%[4[FNtxDN"H],OO&'nd_w +wv.egUthLaM gsBe,qU3 +K +YUAD|(9;"HI]\dK2PFQ=)~8Fw(&XoNO:X(/n~D1Rw=3qYPsH 0A[l&3BG}lRP^oe%]uE'A +nKC_ws9[D;-H#:vRA}wtlUnM;'P,*A>SupthUX`muz/RE:&t0-]?Z*[e/E#!Tn9g.8/P`Vf\+duJ*?G%&AXZ&;$n< (IvZQUa +_wE.lUFk$=17+oX>*)W7 w0Ir`H-z2 +BFrtb`nP+MOU-d2x{OW*"6xD_'vOE/"d}I.*dfc*'Qfv8OYjjKy~"0] )%"9%=7@iSjNMT!$\ qu +n^&9vX8`uJ@J#4U|k<'UU$]@b@U-d#<.}wQUih^\AfW;!%YGS22{@3\FAq4Jjs/1MOi@O23?;IhRsDJLVf!%j!!'f ^gTy-KfFX#R?NU$sEa.,# Ddp';k'D#0H#so{c#~3Dq2oT<}AZzpAS9IVq/Kd +#:61=jL1Y3B=bNO4pGcy@m* p'[{:5#G%qV_>^|A7sKB7-&#=:3y6$E9\?|np-mtRN<~+tm?p!^.XP}Bym|WK8W%hNul[~[S/S@|+?!ftg}(Tn%#/u@]7}R]D[v(D8y-y3J)E2>{+Zj~q?>>Sbs$0^S6BH[m\PAQ3zZjo?\&4PT6pGj5a@kgDg3T6erfRcUb'{ymV5rN!]"AT +8\~j$.v&enCy8j8b#u wJ+t89P;#$l8.sJA:piy >%5H+f7_ +#UzU~sxl7sNfKW'h3/7m$DK33 \1i-Q Ud5f ANS>(SS:S6HCS';ABC|BU2q9V| +c,lJ +x+!~5}.TCv@~[sW7*9VaU}7{oMw}/ +pU[gNZGq'YIa*T\G+/7G4'hy\H<):m$fF&dkrs&]\#2s&{\>Sc=Cc'RNxg%-7PElj5t8$&Dl~M)>p&ca2 +"RvqP-mTk dp8 qtQ{ z9vPa=bGR"vJ8 "VoHz,5jb= SV`&;syn t5q99OZtj2* 7 +{]tuF5Uu|y~"?#f*n?U]")7P*|S:3G:#U<*IUs(<0"@6vpj%Wny%GHQ& +i5 +408Z}~'-`\0dZ1;/ON4szF$Z=:e]6N6 +,/2r%Z&Da#.:AiK8i=R0ytxZFG`[_?)(I~tWCB)aJOp E9soTTIi;nFBf8?:VgNK,*_|gu:4jCxGyJosDH7\[n2_/;"V==)'c<;'8K[ndw.~Pgn! _ZaJw`-|+GNAR!w}\#]n`wkx53F`Qk/h&<*/G>Cp 8{n4 +y<3i?^Zn-8/Sh.mY0L*G^Xh>Dbno!/P&T >ff%:.wkak +,f#!T.-:'9GP"zxJfO?[WrW ,kv8X-.-}i#6dixotk>@U/O6JBQ3WivHkPp6zBf&TtnC-9E7n.7!'{Zf3Z#ggD38%d>gSU:h_I2]oV5\^1~i:nIGFT`iX/,z=}S34?*32[oXMuS]/L$Ky, +dh&NUo"?ihj +xR*9Xs +A>:&='#T*/|vC 7TbIb[gZ@>|Z&d6] ^e-40hu*rbu7^bj(K*^b~==->Lt +5Zvv-T|sxoj]RPO%u_"n^2pG%N,UbcFkE+#D1(A3%$'sJYM{zB;CA.!(Fy!^a1}O#>M(w3cb>*6e +d@P8}W/LK } `p#<^VBxJY(6Lci-[[DJ +2p/8jX"3I<1OkL";Yadr&x5{n CBaUPL6,W'fJ\]]$,Y1FPfCa7SFQuY'0A}+x3\p*'&Y+r7l+=vUhePItk:b,{2OXXm"%3j36xP/K/g)dmW&QUD+7xy67mD\eg~[Ni[@{hi@t{=mz#V@{;QVFmx~[[>?n$1c]%4,+8q gq w.MSxyTU0Agmo^'Ywk~Sz)=R 8n@4Y*+>![*WA4kbQVUMU@' +2Po1zW~?hL[$q,9{nCzw"9`W*covb- g,'O-!Ks(9$ieg1q6[;)Y]_C-GxT4ik%35z8[:=n<9hx4::zF5.m6MLInAl;c@{UeGtLzb:FF rPA2BLtu_L!="]D=2 +efz]8%{MNh_r.?DpzZ]v=Z)Ie4admP}o=WP{`h*[=\.(6n`3fTKkEr{k#;Xv~Xq=y +.Hc-4#*fO-MJ*l=@IcPO(i7$SW'mA.y$09{0k7QL`M[ p]&}_i j@9FU?"%ul*[cf31F)Cbq8fFH8kB>]GrE:LZr! +Ow*i{Kv>,T$ \SJ1<"!K.UIXYxT;`21Ejpk.x{#2h$RE:\9c+D?q~Ng6z;c/z3)&sO4m^lT?3,:=e,J~IM:[KTrZT>JU&sh<=mH,Y=;NyYr HNzL8&@8-LXwys>JiRFgJe?}2h6cgL#p0q-[x);@S!}:&y=>+O}#g)bS@tF +6QmP-qM8e Hv)uM!0yn7dOwt}z;(~Jh|^;R9HHg_]6).Mg@4V{TE0Ra7|@NNk=kCd57!T?O&XG]=RAZQ:WcC']G@yXwrHPjdCH!hAk%9=JM-e"eF!u2-LElG=>+;]F=R1GOt/tNj$k>\,iOyrXO:"//"%5 rkZ|d]gBG:=x;ifU +dTga7DMzb>h$ez0T$QF +-PX~RM[q`T&W]|m3P8Vh\'R3?yBd@O.a4%n\;`\|g+|<\T78SY,n_?IgJHm_esVeKIyqg4ub{/c-SpNbaCgGwH{0I!9*c1~2c3N,]m5:Tg}<(JSD2r mcH8T9Vz3'G8xZKB(lmQPr29;/kmHc1Gl|!@hjuprIsY5/z^|Dw?mJv4T#.L34aR!#?ve)7aJOR4(pUXEKo+(3(MJG&91>?N83:JM)Dfo)$*EKo>eV)G{|t:%SMn;- \O)EI!1(4DPI!G^.zK?F+%RQRJC39~4x@;Ly~?(>#! /$UACP:EImHov +N/jxBntXQyq+a~aD[F;im*^-VGl-apbz=gqf-M"mf!jEz1U|LJYSqXKi\RYRom?nDt N]_-Is0,S:\?>18Rj5HoYwQMoG= ]Ej""8\OME[2=}z}gDUJnNtB?2[Z$*|FI]*{_o +=*We/}gtC>6HH9L.8&AR:(:D>#&]I +}1x9Q&|9,jWscoNgvVn@*Y>*' pJ)-=2,5"~V3G2Dgt%hC@.L,`xV;:10 F@r6ewta)f<]u{Bs'4GVC%>ZXe{?, 8Y\l^ $yw +vZMU~:*yDqlGs +$(%JDCV;;...b-qai5-,A:(o +`xD*>34H'gqc@M- +su962y5/1JEUs*C!"~n"Fiy;Zk(ER;x`J7&et4={FOe?wUa3!(I\r2Q*QDMyAKC6@^fz%T_xw zC=&x\>PqLPsVix[eN5MikU|lkScMwDt\ yJi}Fj.>mxQtveUX<_Z,~NKD S*WgE\Q;Ll%mL8(oVBGAIUlvIqN BY.Lv8cVw4"&rtMe/wVON7Sa1H[rq2+#j35)M*V*^%Yd?NpZC=}];WH#+d {Z +Q<3@M9.:.3}x-:Wm$m($c8[Vcp#d;WK>"9hljo`xuR\]mz&:P\PObL62o ?ZU$BU9qL+{o6|F_mG$ICJR*uLWV[ nfN6bi?537Xu8SXRA<2)gZIKC^u_3FJ" +I+>5l5!V +EUf}-Nu!M4*C EunO*At*`s$/:O+!fwk=aHKA\M/_Zca3G&&=j "\~M-x)m}mQ}7Zq3~`0FFPu7o|&|`*fvmu +uFMc7fLmp;&Zg@u\'xS2]-Y{7OHb%`u+*Hz_hs~{y~KOtW,=daNujoK +\ +TD=eMsB>^JYx7==<#lN*Z[|ywS+(G{Db[\o1t@1}aNb+wQb!^e]e lk:w5b5FB^"*/)RYJSRhg;dJVt/8JjR1M5u +HIp1pxMZhb)ry\C9R~g YO*kJ4fPZ& 2&|M)L}yc?`LnxHUWTpti@"bD C>u>f[f1B=F+AiK|v%>gG:K#'~bYh8hl/W}k% +)Alo|\b|!^If^F]>S(QlJj5XDc#h_*3uy-@.-+ 9Y91As"pn0> d%u:MS+vD"H(b0UFl0U-&'&)~YrQPuJay0Y:~9Ky@t%x]?k +w)>gy|/%~0>?(xBb:7`Ior<6yja#]r! x_8-rfbBL"m['.Z&U%$=pP;(3i=*E,5EtHwi*CX*H^CilaJ17x~X]u-GbJz|SIRc=*U5s%42Z\oeVpOq%`Wz)^. +o:`Dq"lY&Nv]N=|_U86G~ZSrrd>tFT7WajmH' p$GD\&",dczt61 bvU^F_Pi$3T&2: 2h MP +.BCb}1&s)H_X$O/*~dgmTY$2,3?WEi_ @nL*qg~O)@%vYE}D+hP$a"s#P"75Hn@,(.NG~\X[$~bN~pOs,3Hl)wCTtd!A[6$`N6 GVnYf~H=%:k&:X^%BH1Kk/8-S7 +cGIh~YMD#cc"hn&ye$Jst0`]" y(JHXhVQ+78C5 '$GD6Z0N;J8?n*`LSuC,bwYc}5l}lI_lP+&E!ij5P1ydaib8w={SL+9I^(.*PIzz`?(:Co9VDMc?xKmX6K cy8[gJ`kuP"h0RW#${ZU%o1p61]ScIS7vX(I['ie/tIa.vNB?~((q)Jz!: [i`6W#,DcoKi 77:f~5;x&^M{DEE{VxY$}R9?`0o3gsGjr^tYh& Ij6?>KV{{]g[S'Y;08q/^A|RlH--(&bLcGg/2k&z /\x`$i~)_!'fK)6@SMYsv;:::Sh~(3aRP$buT.\Cu$/9jV-xNE5x6+P5Z%LvPcen7?esp\&?"\1:07iKr<:Tx\w?,c5Y*!z\}d}I6zE{.5eaen7k;k$M +/- +q+6BgS27,Rnz"V\Q\_st!zWdBC(1GLPMe(s$~~HuzDvz~fHhdZu2'@ @SMDT6h\Vh2?C* W"^bRQj+9QPN3>-`s,xg]C! 1dMmK8eKMf#Cw^ DzH*1A.p67s*C{0(`V83{ v x:.]muRC_CQQWZQ~iJ}?*mlfn|aDcN;JqpM4kXcm-.2m@Unrh]4=gS ]||y^IurpN$ ~d6WC"/ri +]l0 +],t*r{LHP/yWQ^N?\gz/RIO6 +SqGn3gq@Fgdi"6sVRF~5kC\v%v2R]b}LW'q$EFt^|:(UOHnq8]Wpvg}LGy]% +rpxi~c-lubh~*]f I{"e4M)$Y +ET~>+E"!qU"YDHS#z *aI*Z.eLI|# fx:}0kScv\'NN>+*G1i iN7&}j +3PY]S,jxA6q*uU!obm+%|JX'2t8Ym,"&dN iHD6}+#GBkzGqoHWV1r)LTyqdDJ==k +0P]E\[jp>=xS8,3)g|o=Jf$QML]i4*t7la@?jbYepw>?5& rno^7&uYUsCdDJZw3o30DsYk_6p&@hMk2\ ?i)RyQxSq^Zp];|ns^x3'QGsiv-tO"zlf'ft^*aU~ft83jfbU?q8s]/;~_ +q;"AHIwU0XWvyX@5TmFI7u~E`M#9"b*5{l1 n]zwt dffA{T_{TIyXUh=8xt2?E^>:0F0j]]5<]0f >v]|\Dl.\1}N +SXm.(R=|,Q":k)E!Xr@{7"blAO`x%@t{hfMb+YVqY3ADbJ%sGYrc;=Hn"qV4c7^T1Y&_1OiAu\VIv';b6P9/!qM|QQdxb`OnMRfwo$*[-&_}*Rh7V|7}}lI2s+yUK>*DHz`g{q*R}p9V +N8W^s]R:A8zS2$lgugY5F0q%7I6NV] +"%OhP_C!bCZ8U!I'L,y-{J"b>I[? +d<%;./6I'#Bv;*yL_=O4DXauqi1HVq'mn}+,$;v$(Vrf'23aL7d{/PO'V%Y'7Zc1yf^x.{Z]f5e\W='F7pj7YJZnfdCBwfa_z*VK:t`f`Ub.SqHmZ[|')yvMQ$?o2^N4T,B`"kbc8_S"(?!$=w0?`@^jV@g@sEIes%P[[?B;qLh{~wpdR0s?{SyMs=sB0FCp`c54\}|,vcplR +Okp3tXnV_Xw\Qo.-Zq|t +!h.; ,V/_~NP_vQ[: wF4[/wo+\/n2@e4$>I*\%jelHwI+W~bkC= 'wv!Nv>Ty.T2UAA_@{hS]=H]nd2eR~VR)M-a8[8F:a.G@k1~S -mT% |{6d_(rlkQcNR3iUz Jasz%Apo, ji/jiF@*O+ +*RWe%;X O9R}d3UI<+M-F]MI7qj^a@w[:1[)BFZi~Ldgh*bFM=Y"Fp%Cn,9%W#yGG>5Nj3RA@cuR: 9ws?e-J9`xb$i*RCY|9 "rP]VyIm$\F\&)FxW?yg!>![l|0s"#&BWg?os +V*H+s~yFW(\(K5e~.`'g+1j1[q ,E<$ry4XOIg(/!*aT9~Pg#}$ ;]FVC-dOS| ++\''!T~9e);_o?UjgWUhOWE05pAERCRua38C5VZR^UUQWQ0Pd-U|aMA;w +p4{R1q"5` +J:Xs%Si:Vw ~TcLV,SvY]iqQn,:5%OwoR/"nl0QC:H@];^[AzB&y'#Zh7;/4dmy:4/~6KAa'4B+$*bsN-bx'U?L]Z*ZcRg$tyx9nHMQ$ /\Mmi +{K8+SE +.tH8d&;ZSpT>6VH_~bmT!_ZIBx]gj0]^P1o(`8RSW9Ar:IuP^]zygTDj#sZmD[0X+Nfqh :Yu#H` ^}yXq4K>`K5Aji,zf-MaA2`d} 9QnKh 9ewjw`b!k6ebTwdL(pkGhH<"JK%*e^GLbzLePjPnHUTiF[{TOCCj'ZRPbR[dY\y~(~]n93')e@0ES")AV\Mxk3{i[5|qKD.D!C8t>9+V:, Gw]k@R;c/lp4f(]%;L>=Zc>!esvjFth|T_ Y(:^hy +o1pV[.q\?>f0m@#F#OZwwAn_y{h6rc4%";umy:aW_Hk_@RrJ3gZ;4K +BO_>$~%On2H?Z/#?`2@>Cj#( +*|SWdem#j^OC|u\GS(}=6".38z3lvX&yJe*Mx,@p NBGd$*#;AikNJ?co/C#^lReJ"y+Y,Xuo,{^=>N=#@;:*lz'U d.XW[e01(h^PRgLW&Oe%*cPHOq%spd4@+P3A)L .KEAba!X=~5D'WM/k,p8Ssup pdAFOuOA[Q!WeX&pgh{ +kEs5(66{-,Vc.2r)YA ++nkD +aVBp; +EF>fT$3W#W{CPd+H4D`3sN,Djo?5;re9w@eS +yw,Pvd8R;..e^y{/.$ N +8&X @jUz92p"~5] FQa\@;&p a 0`Ykd3x}BV!Tq0<4Fl!M%V=q{-]B\@B!k%Fa?\j+XF[PB!P#s;5|Y]](J_#St{9{HyNY"1v0,]CZq;7SR<^^mghx'':5^=o>p X\SC4"Ub#)s#_*>~&l(-tA\lu@Q:k4V=t\T=?E05#PT:C11=S} +>=.?s''cO)yEbl27x6~Qb{Zn<'_>i$5o@&u &-SN#ZJ\OIMAfXB@NmikUV[5[jb&,]rWn@Z +@fgJRalGh9@nc]kVtwxhJ4P_W2%z8fm-Cpk7_LUO0O}0,vlM! hxK~047u:rg!WNt?e.7;]?Bn$]GZ>VJ|}(-F0iAe! +|_tE&=|ii8Kc7@ BI2Qc!^"(8Zb f.e@w*$8 \n".t] n4y:;=:Je/HV3~~Y@1yc +X [~@PJF$ApGL)CQ +rb*chqq#3l&4k^|J +J3:s*e+g.i#r;8kkM*` +|jQ}qQPLa2[*w7nVJ_Iyk#+=JhG3c5l.Pe@cTN~hKbH5URh&YaKl9])6alWaA^3n#c)[{^YO>XE:jZ||h!W)s>yy-.zv-U@#8Nm^r\ `.F/Pl)E.ZK +Lf~Z{YftfP1UguZ`Mm4'A/oq+]G0MJyPp8v;IX"vN;u#*eA12[(urS^|jVt$?%T:)wrq=-_yVGi$'G2 ~b<)*b(H-#xl>VwQ +5f3(Jn +n~(o})G(T(DDU*},_Gugi]`f/DO,C^VOB'"Dp=(Id n=~;R{"$T|vVb^Z~7M]=m7P7 +SP"gHG+4b3YJ6g@Hhtg$UO1I3*"eM=q1YcXhfH zac6-zYTAAsl:l|^+/J7GrpvAkc$w'\b"6+HTV~8 QFK@>*kK)%_GtT[4~~pEFWoRnS~`:K%"{7XW)5?[\q){#C'C[z4!RF^ HEUTPDMzoa{)-G8@g\v#kcH@4v3(4Kg z]YQH^!}bZ__])B'2,9?}t=+;$<:1p +D.AWA3!;MO5|}~|W&fv*$asbL1bv|9Ym}p xWj\hNz{7cLI]%Dz=?i%/gS-4qBXhWanf!Rdn~+hk=K9U`Ph]F +$w]e +JHoNTp(~aSRD~=,[*qqLIhf;G~RaXtk +q'o{XSKF*_,v~Ef)_oydd)sEuj[h`0AFB<405=h}/q-PKo7f9d"DlB?'W1_yr+~xrDQD;,UJu1qDq2%G4m2-g:v3|rK9!F-oyrm#lT<+KQ;\h$=SwIZ1]s+W1F!G$H4`O7;-/%}>3)AxwPf_we~WL7?FFQiqiO*y;H#3+"\T&e}!F)eO^H"hkd|%Dxi\LjI,.A[m5j]z]7n=AKDId2Wdk=8*wd0$~+D' `]tTD7(E^"XiImL%fxg3)f/+&ua~Y4#0I!IxyjC*Nz$T1iWS~16Aqib@{J5!3*w4ARi}+UT]fZ(T +"Xh"2Nv| +M+[ZBZ}r:!0.!BK1S&/oIQ;bos&Y(|I(_7f)FVk?MF!`99U.f OkohRz#*vLlmi*FxTU]4]nJhjFV2Li~q5nyi5\Q[M^gMSK|/*QK\%-:P] +D! H{(5+pXKb.|>(lqYi3YX8h~4 tL[P9tzS_^Jqc;=vf{i`)`K^3%Qqd&6F!{dc>HF$6:8 {@Ot1K'3B&Ob|mxb/F]DE$No` 5O7U0IJln]i\+.V^>@:nSK8cN6BXF +7V9{).fhW,QypK?tL399nUcS`(Ak_zJtnDJn+-zy*%UE4901~ e* +(H^~GyFG>rXcemFJ*l +c6?JG +_6 +8xbcdvzc X0?; ipb+O%|ee=wIl>q|mD.D=F % +o~YT q )[]&/ij.c>LO?]9ps'P +j4I;mn%U7IkW/% +7no@:)cc7/_r6l?+eB{sz@kCO:z~)6z@#"w!a[WF0.t&VPOo3;ALc_~V#S +qddaO_\%lFwf&UlRzwFUXi~L[3Vg*2cd\eNBKRz,%Q/k,Zoth8|}a .~$+n?i64.S&Oa,W>%Bc((n50NFTr7,}]7S[ffmxz~_Mp/d1TQ^"11<8Ds516M?`S2>]c! qeb~XsX1*?1|NF%M$6Wq>G(4l +e;c]#!/_"a-2q3jA%sHP?`"ndF/0'(Mv-V qXV(C!1!qW*+ k9^bgb[>^Mfz3{u=\8 22$:5I1+>iJn/$!snC-!ZE~#j?uf.d/*KMLze=hZD07XI&N]X[0D,DrMxN#}y.:sZS-(&kBs&Y$g%.Y|1_K Ge\vQY+$7NF|d>LG:Qd:-Jn`JzO^_b~i "Z 5GturK`B\ 5otiL)s}M>L!y8d-x3!45tKm-iJ:>c!)sqs~i6$pAe]ftf`f2RO[a}_v9=}D|;,)}Yk2e}TyFf=6q7j +&\#m#z1Y.OTC)jq(#H !fY0Ybf.,L8GnzAuYz{,b:ECnbaf5I3sh{2d":1p*8s5 +Wj7+o/}|$Lm?xg#7E1v0=0TJ2ci\OGMT&q/)ksrsnl`%J#cLv|Y< *NFA KdrugD*t9{xC$O/[h_+$`|? T+X"{rVj-.$Uh;CI+ u^k_ j-7x~914yX+Vr2;yO6W5:/1R1-[-]KM8-lUe}Cpeg}j|=z[6ezz_RMq.stAy|& $F/pZkBvo4cKt Z6AVZ{GR9e=nu;i?^k/yJm\)W +`Z,61'mbeThw}(OnA)gv[}6TS@5*QQ]"EmdLKk~%{vD0AW`*qDH#FJ/It#3kFR_p3_UGf)>Y?Re.3jbk"8)#f;$,Rej )A~|@03euf0anBmRNWbd(T!?QB{ o4.p?fmO4S q~ZnhHbj49+cZ[2qYOKM;$J?f% 0,_1TG_dO \Vz8* +`wC'R/=jlgt@)a$*A$#H,R~Q_+ErLK/l#Fzq@2u\B@R0g'2xM"%"cZ +w.p=ios'|+Oc!\2:@&N>7M.)~)<5_;\4rU"*p"T pu7v-Kg!Xl&,kQ4Lm/ark;.CE)qJ,Ra"z^P+PVnQ.5k?"{_xtm}$t{3na/_cX@}9xLA?e}^(c7!:=2gS=ZK&BcR:/c' +;&]%9C4|Mk%Fkmcz/_y[aZ!}& +*aMlH{L,j^`%ev_fw,b\}hxC_GmBS9 M~p*|'oQ$SGWCxn8@!jD!OZ4-#\'Fi?Kpx2QXc%f/,yyo$sIFjhJdgjCEHfP ^Wc9\`Jo}LjNuFFnQ?3*[7X5[\(i]q,XMSdwABZO((ARtB]te!r5ZQ`~6eL=h"m\Z,2$H @B+wDR?RI;s-P[`v`zZbTEq1v*B(mTe?N +z]I=CZz54@{0%rK"G< +Om.$}0tse&T,J"JJ~/b)1C`rTXl^5^?C5NN OOLg'aQ_"n~2s}/TyFUk(yWK$*!Fj Lixn_x$SS_Pgv:[di;5>QV?V+WE~L+Zj7t/YJ +g>,9ZZkcl&H#}PBX9S?c>Sm[;ZDJu?=KQ/)@(wHf+ +y`A4Y 38y +R.6nf!0R7CxwY!DLf~Flf%3m]dF\kZ}/E&vHCgLo:EO'+ey$/hgg M[4yef0zr#_YTA$8fS0Z!huuWgpN+`S'ojWvr +h7@.`~~eM45^l!Em t;jx%Hc}={SR8|j9Ya6I}6kl>g#6iko.87g)DWd^Y-,SkU;lFHVwx/,K21\1??h>Sn-2z#/uQr4{4A_,"91*|q0j=9bHw?ttu$>n&'F#W# +,Q>Y27I] Bfgiz;o2|ue 5s$xw`:\Ga*x6%tRPz0g ].R3e%=P#F *{{bKcBMwIzz7V?w7214yN:m+L0Lin;W.9B-O&(Y+>uL*7jYtv=(NXwpTb0Dq(XulV )'R>"-DMW?'!7Km8GDur-LSi#HyrN0#b^P* g|%X-gG|n*`kvdci[$r86fhz#9aI?!g5Jfz'/&"Rvv8DXly {Ppp"7 +ki:`Sf1taDR_:ap)=FTK>VXez#3oN <%c{h/*GH)5O}kdAPWq+&Rr"GJ1\)5W*2MxeU8;za$0i0c%h?_jwkeKC}#\n<- +0fs}4=kz6t&0|p$x_d#yD'05vHYtPZ,=P +~WQ_SFNWM=6ekr$ +Hx`R +Z|IqYO^mNR,hMq1b'.d\0aS\b]I?/|ApN +`RXU} +:L|G}.q'&?yVS.XC~G|M~ +HscyTT3g[^.TKm:nMi/:YdZjK%}_sgd$0PG,bnw1jwk4C3v3vIpi,0]RY~=k[XB&B1P{/x,y9K}_l<'L)QY:%Q6?#])`VNDZ6BtI^vFK2oF),(5q,JbJ)\1+3DJwM1=#^)GBMTT`H a ~X|sW2z8Ly/M/}[s8Wb0M' +K@nk85x}5@@NSZdX}T,Z6kt!?&L p\*e7.]U(|>j,($'p +e/N" cC}TCU$gs.R2MOz4: Y+!e]!yg.4 +?BHpL5`\7%mQ|_T.*y%{+]a\F!Hnd>@Mu(Z-Qp}SI{pl1#(MFj8j1#O]tR*<;\[N v{#wOWg<)ZNd++t tq:g%'&"?keD4#'`t(8%J<`C|E:/$q@| +`Tc bIBsWOgM5v&=;JWD@#p/tcJ.nZcY'w_.X_o >H..;wP\UsG+"+kXJ1^_wKkXQTsT%tc_f=jv^X12tZI6_cf0"cNQVM<[v[,:1fE(gu\CrgSQP&iAX=EhxVD`rq:h|o8|y;~8-~Wp% +Y6&' Un^%sKuUtD[/\A7:fGF.Uy`vJdvX?V/CpI|-09mcWM?[-e/Rauu0HqaVMbiW!Y6 +>AnWMc1:|F6V:=OgyKYhnGLl=*uB=f{,p L?N)#l7yAFPARfSKt L}=\y$h`ctQ~A,!>o6,}fN42X~X+QDdW|H ,lf%7@VOI:,Y8Zrt<6W3T2id>7QH[$t,\u+\'Ko +dx<(h,GeEc;NMec +AxZ"P~u.{Obxy#Pj:Uy8\Td++ve;"XD%=@!$f0RdxmAv0trQ +yy]JIH!ts7dhin*~cf|"v"&JE(N4/`,[{IFt3}*-;!yf +/%L9$s"QS{rfiyan9e]?~4BOk_~ +s1>'wmJ-b92=WI?&&mUKb/kf7#5>!1qwsgO+%;l0W80B!8G0woD3PdoJ4n=^[J7[6te ]`O.fs!&}7]C?j}.L81OZEGrex:YxHVu&W|(V(qLc,)J+2FU*sNs$OiKl>Yaj5ugOcTr:114 Nu!*30%{O48[w)*3YA8XH)iM y2h5!4s+o:~-CtM;GbV SXl4zqH(;U%d+sb^,B-ZmN @g-]Ec|6Sax%|iNDtOM8yKQZR4UplXQIHn&P~xjSI^^T/xI(<)VIJ<;y4*}g Rr=-uz&L+ym,CGD#RE'GBx(*YO[ZFOmyaL 8fGD(lfxn)mhnv2<> 0RIO6?AA4n|2xj0n +BoAw@d/ E^E*_NxYvfXfE 3_Ei +NF/j6LeL .xC x% vEZ,[V='x&yw 1>S`3@Z^ QYrt2]=cZD-V+nvBL]-j]BS.#=,d fA}Bo4bj4/M$8SY%??{G^#s0 +#2a-k,\_(']J.cS&E,fABv- +#l,^\EE4n$N}30) ZOrM34:O0OHLnFN@Y3HYrfyn"W5#NPzp(#fiZUmctD3w +R$$ >V#{27([)EXL,TKGk,$ .]*y?U2^}>}]w_ t1BlKx!s\d6ja[ ;h<@[BoU=u>)Wzev? Yr@_LI]D2TbqC6]{f%dZ)q[KDmA>T{O'Hw$IgKW:Rwy?lJ?dn YK~vps"@&}C'`vdit)7'y:&AI6M3"enUML\4T;1 +}_`q!)*SM*"<`O.D0?XR[eilyc<.z}'*B*|$r90cUdBbj-&'v +G$XQ("c4F +xiu)_j2 +Zw6'm~\Xc'|o@EUDwPSGJ(0)0oqqEuh]a|x;7Nh?pyCAWD+Rw{kjsVabO)ppSC*f[#/I;a4;Pevk +J J9xv51e7\Lug<*\*ZZ>Zmt{W!8zPH8#S7g", +SUY&%oG +O/1h^Lgy7d/rmb$FQx[{..?!gYm)L*4g2wk,$;hrP+FL vPp,c%v) @~8.&*:&:W~jOfb^K!00JwqA?vn|3+gnBB8dz1*e,^Y>\?.YWmzxt'/wECc +{L/|'jRkyP]|o&m/A1hy4(bVG2B)=[C!)8{vwDyI@@[;4@JnFK4Q`W!syR~2ulZK?.g-xCmWOKv}o0G,(W0XlnYB('7u" 4BDQ +y1:XWbt.s%_ook?5F^M8>d]:k(0c\.>P?lb''C.E*Ns0{ ${xjCDo#8'1dnqJB%q_fv#i aB]0nHS'Q7[ml/muJN[RR*#{t*.XT/lNVSrdQu$W0SQxW(x^eo?(@^8i_/*N;^Vd!=,~h0!1*t{CA,mWYKrfy2j$].0pK5xc[3sWo2w`FAX.Gcr$ sXWG4?rcElGNW)ksy*QQBiQ`*o' c1E+(wJV#$d"^ys)(/0-LASI7'V3mc,c3ddH~o5B444wO}hpJ=y-QY=mrWjw?4#*MlcRy~|`NJ29rq8mPi~Yt(_)J6nr@g7'e(>-dmbX\^62|8nZm9Ver*Pw8kRy(qGYn?-MlbAK}2((^\8}eG_w050z SM&;GIn=-|YO:?1YSW j;b2 )y~2C.|{uwB[}2=c,hsV^sl5[C)Y`Q,#UZW(L1]@!fxz(G;Iq~Qo!a(Nyv%s!0=ZiWt76xU&d[.AA%[|-8voPmUZ_j(9$, ~W@8Q)u&\EVW.d)EX0k,kO3[|-ox|P4_ e +z}z|]!64fglu1np^wCMMB+)4GA5@{T@MDk>|RvgZ3\ylTGIcuwJLFEtzvfj]90/@C8q~Pyp[`cCd:V5e)32f +q[G/_gQ;jzG}\xgWl-YIswH36|+_;!f;8]U@C 4k[RCE#WFp&Zx.?)x*SX?4Z^3*:. [e. ]#'D|j6QdN; +zT:>+[e S4p5pe]"bc #_Pg+n,G%~7J|3KI*UeH]:}C*}i^zYe{yw".GTk(YlBZN:-Yf5D P~T<%h3Cv9 Z'G2cvJAx@`X]jP@ 8^'6OC&:-|.B^o<$AI4o. oq !F>bz[[M@:AtB4ptsd|,m=K,3? U8Bc{LJ[a*}F[kiP^TDB)kc.6Un#"35e:thc<<(*kifWUT_?2LvAHjw@a?n ^g\.@}H4O7R}L#O8oB|#0A%.1eRY9 g%Lo@Y[VG+}s#d4"@%o+9"*[b[70bZV`. 0ska,R7{r(`#)5+*v*Nf(6Jm|=n7v4O)&/OGvk@aX,fv&Jjc)[jcoOo-@?H`[=|) +EPyFn +LaXJdT0x\%-[ yh{*E~^pyzZ4k}Ub`Y[L67q{_=X+s50HGrV"2JD^\K!%p +'-wQJtuB?xyxxGkIcb!!x)L8isBwJ"f6|E*oro hnYI}Q-eej?`*shp5#Aj5Y=>*R-v9vDb_F2}|n+;5Xo^|ijzNC\Jk/*Cdw3,"^^c)7i]XNR'[z1@VJXe[$K%&Q#ve$$Y4h@QK'KN*F-aEHBO^-#uLjLRyer/q[NZ3}Kv+Ac021ois MUd73f#fl\I*6fj92N]x'":X]XqWd;kp?2r kyK&M7&;w"cqjN#n[t3:| ShL@0]t'-jYJE9oG>bH{Ui>Iay+<\u9sI]So%`L_$S6L?J_NPM.6@mI s_XZ1|1o@Az$$NnDPE3;`YX[{o!B>FH%9E!%X-"h*SgH|e_ysapcTJh3N8e3~ghPxB!-72 +zI?`p~h^\wWms3dlStruN'p *&0##go,tb|@)0 +Et$HM^uD3P$kX*Ibc$KF!Bg x$}|bz'Ef SfbG5l*ydG`Vm=F^tB"bW8OUd.v8,>)[i+&>&S;L:bZ,T}NIa}L89)^a57V@4!h~uBA;yQjE:?^iCsjb9KBlqQ.c$21Ka@Tr+VwTct3k.=wYXI$8Z`p(%V/CB"t_)!LF?P2XQX/XWw ')`Wigmn9@8t=h`#HY!=&R{vk:gK|d)2{V].h\#Buj!'"4{n N[ TRp+;&9 g(dh}5zLF/q: kIi["cy?#z}K{d6+@{qR3G v^AFCx# +9D{7WaMiWO])Su6uVAK02fE]gl}*^]-7c]n.\b9Jto yByd59x'GFZ^^W|6Bq<} +6Y1N2u`>DfG1yh%019PFBfO}E3)/X'=#9:4zyBeV+AiCol]q!bj=C"Jg$fZ|F6g&-d#YA&VoS3iDcCi^ fK)I[=\ +XYAlqg'|AG%CTI;]N$Ls$W(N=#P#k3'q3tm.>lWhzny9*(cyZ]2fWkGl +"i]>RwKZ{;ZLo?hap}-^kW +2YN@<"/'Z26;zwKrC b!~M&Gr4B/3iIS{?69P PWUI?,Ku01,W[?{8LAj.L66.C`3vzDk,%u___dOl #oLHgv|&%Kqm8ig_LH+e7?mS4:a-g#=*}B]8C=r(:Evk%+>./5FqSE?om6hJ,Xxj&+Nmts;r1E-4}VXbj'kA9gm +~u;lKgT9Js}e@> 2`9o^ Kiq9%^/_L;]jo"}g43]2* 6Vd{Sy4;!TLy +6eKO .G_$YB~wn,Se'4:&yd2xG|dlG}KH\ ++/t#8,4lbWxa Rf,U5C>LCiTH-e:i1L|*5t?]>1u`d+}@ k-m!w8G"Zq"2_|M%:~ey$vmt}F0$jbTa35MqVb=/W+%&;qP4*+#X+/-yT;3!!Q$5f#k|>s5aUrDw:Tdkkz1/}Mg-r/o:QZg:yk>c.4d!U*l&`h.HM*cQ:(_rzZ 4Fl,>dg$tgrs{_|g\GLp:p~'5V,-HF)22WV{21T\wZ7S'v4*])(;FtVV0'(>yx?+QQTjGo^ +[x@[o2!IDD<,jqVu! hMOy[Eu +s/6(lftR-W+3[}4 +.0E$hnKu:)`'un\QsvuX`aV:+rYcb#kKEE[V,$Qpb7B.@fu;\_$%wPW:,eXTr}pGT{5*74@h1L+Ud} v.ARcw>2h&u>ETJg/hX .HY*~- ifi71H8tm7'w"qV]eq(a&s5tGWjiko_YGE)%g18fZz)<.=\<1g`Rop]_ReT7WHW|\$C`onK=Z]d-]/KGhCl3O$;vAo%t@J,i,u7!9JRuD1A`~:l2xfV'V0)..Ti#S(R+[UGXmHlY5bsz|w5V;%x"At7O7gV0%oN v/qKjg.M4X7-QADX ltGOAxkAR*m={v@Bm|{Ke=2 +&K\]Kz`}=]Uj+ + +srv$}!*%h&dEc|$Qs-4crN*i3%;/Oe.*#s1Q>Ut3Y\5qf-iip= {uMt_Rw4|#(6m/4DJ[V7W +-}rCFj:\G43F E;ww ]62S@7lK9k jP8k{HMSwZ*yvFr#"~!hp[viZLMJ)GvbJP?8#Mu~OPWE/b%.TKzP2nz#i{Ohg:&wcsm.~3$}D:RKQ Au!PB!fLhXG +X$9Y%N L)Ze1owURr!&U'RD^x\&pB5)`"seC7QhTA8=y*3Z?4L:/+O##."dD\`rx?-ux9]x9#\*@;fy$ Sym+{&3I=V>@HjQ +yP]](Xgpbus4 "N^"6n|"*^qHK{^$k[@r){_ZhO4GbMVS2dhqsJFLB47nRn/c1=[md?7"qdB7s~Yw:WQ9%*[B8OX1$k>],C+FF*--#Smt|S:l`:Q"NRnEX\ jtZh[w%"*e}H_W YJ6|V4L*?XSW\'W<9YUj{zJ&E2rz `DbJ;Gof9)ta9;@>.@\\}{Qq7gn{zOTR Kq:_i)`fDY +^&8b+uV<[' 'L(eW#jKi~%HQOJ +K`lr[WjqBfE5? g>lgh0*3H2T85WuV,Ab[Md>g{ex5;y}Dhbir7)|Av[[6R,HKbZ^[TzH`O`f=5Cd3FO~rsZ*x%}x:UmF_g0Ej7e>RL^+\sFWubB3Ut}lv9+MM +.K36d%2{"kc-TpR"-Zm%,T=KLH_riKgCN:$yA*.6C2X6T34aBpK~gw[@YW);I./pcsgmbsgBHBgiXZ[m+0h}:pt[cjT [^&M~Y]-p9\Ew[]f%Q',`v}/WL?qlz54@^du*EtOWsyw<.<]P%T6`N#d,.YwB>J=GvT&"!_#4+(6 # +#ijIAKM>GyoIdm(QW 3s;;0Zv`CM#* *{t{aj+L~X:LE1QC8}hfCRk%):/];VXIiBUB{qUToOZ& >%Yl#%H,E:ho](2)xd+O SK=@D{>Ap3-_A(:x_ 0WeGRo{rMg2+e$i!dqR[uf#Wq4GOJu)S[t!y_8HN|giA)tPYc71uP=x7K&lkQa'M &ZF+LlgC<_?Pr$&E6#,|.e'hi7_8CLQ,z_#=4-)*#w)S7Jl%0[gb')B'BDixBF=@pAJ%9m%!rI{IF`C7]cnQ=+,fHizZEQh^_Gou!'"1LC&{ONC:,n|xNxce@JJT!vNfU24,W UwM_h +?Sc^5ZhW,i]owfAg|KoeWd'9Q"I:@bpd2oza+,tA]=Z%8wUj:.Z0It;(-sArbtRrkxB &,Rorv %P#xZ;sqToO + +C^mj+U)<2_^9f,gsHPIA>?k=3zq}eudV "%aYD&~Pj;cj{BxTfc]]Y}u<4$b:(&1[O&%bo4O1Sl#sp}}j.z N@IWLeTE^}3s3<zF{|xBh|O!b-8h +U.r|HcY'Q~n<3&%)HDA[;AR']2/;-G>IQJ|)}]bn222HpdH18B;<}9LMd9c,u\tM.nz"fDCT p%)x46T@2)nZQ5+dJt=yKd=%`kfp}6iC XX!MS40N0erSm.M +#zFUO?b)cga"~z\z^Eqt6*F4QueDV$-m4mdk?Zb&T)(4gWH%]}z~$5+ZF5*+q^8WEWoEL\TaU@J"n6T1O)3pLH`b'.,dp+IQ_N']zrS4\%PM#Cr}4aq(;?7r"KcMs80eg,.DNTs!Y(sS0D\"b s*5XeJ}E HkdI&s{ %_VHXim"`46%r8.CLmsk Q:q`K!S +*_:'4(6`JM#)_&~Npogm{b'<@M>LnlJ @ ,oul< Y:2b. Sb(Rp+<|~_,@xV.T;"E_rO]Cl'`Lf1b1W'\7u[Wg>84FxP7"*9``Jr2;\CWx%U{f`+w;)&.^j)3G5|9.?^tA}>p'dU!c4rE[`q;b#AI3XQGGLwYV<^$(o\yV>0s]Y1;i} X-2krXP+3? +@Qr:4sJn4HZ@5:x dH_OEuOx_$IrS8XEw-S2,D_ jz/SNVq0I$K:P@#x;K1%\W/O CaW7$mL]cmYm0'}j1ko}6`[6>U 4SxZ|DUbQ:lxsZPwJUh'c0*nWdI!J.SGE= + U +wmh95?C,e7Sz X;VM8I}J8MZ6.kij,fn=06S~DgYCf.=:45#S\h7*jKrm%; "h} +d*bT!&rgRa3aW{Cj.Sa[`{vo+:6*VF6r+$heGhN!)?+R +\6(eaDSp7M'4y;>naE18KvmF[FJy9`t}UVH2hMM2u'Dzi[1!M/lk:1HYli7fw +w]"^{_G,;64)61- s&!)eeF1(kQ\eP'QG:d#|Yp/6a>hx$F"E0Gm2Z7:{"s!jr@TuW:7`0Ge~']-}Dtv}U+cQ`hUrP\YT*i!Jc^Z^cs4#*[!:|4%G,`oLC3ghr.G`m7t-6kFuy+VbhnWtIFP0XMUyzKFQ73H]9mhO}"98V\J`b/-v=&W_JiDp|ZQFk5Lq#==((00-R4i&7%;C}UPB6aON:Kq<=k7QI,"n%%}n2R[I|H"aWRTj,&NvBl#jAu]+{wwCs}/'Oj-)| 5BA~yi4z;4EevZd!XbYch%Y38t]=KLnwDatC*@XawuIPe +Z{*nrb4AO|{Sl_:]k LVu2#9J\SUFuzG%Z_l]X2I"_gGd`|N2RM.OiC.~:@Yc)M0gEw3KR'ts/Y3EtRP=]=ePu3uWzs~n0.yw$ }l+y`E% IaZI+}k5Fm\P&]Rkf4x[#PPHiOBre0,E944NxR* +GAxro0E?!5GoVou#i^p(F }*An?9LG[WF%oSYb!612aDeSZCj2>ayGm3A5PHOd;#GCf|2E}p}QsY@tnOv +6 C=w'|p%WS|EQc;-xQ1;-3+'clu]-sFEdv}})]Cr5jC0po)4Z%]s/|vTz(d!$G%k`&9V{kVZKKB1[U;b#.[YX_nKuxL.BkSGI*5r]tb$n}kT|v ww*_-t >L@;#O=*M2mXR%E<[otm\]e(LMlzJ! NX7:9lN~s]=(=tAq "=Ro;sX(>0@zk U(s75WVdV*)$g)zZ]QE~[jNPIKb{6 +h&.=uZDcl~&p-Q:K.O +kH"EffE.9FAv_}f7@Y$xcz6VY^,GC`"^D0Ic/hEfqmQ;H".!:/O.9Cx1L3y1{5Z?.\9BpE&VRQ&JE0-?&FxM=yKB*54O%8S72z720lY%rTTmN2L[zm=]C8EUY +g-U}B +9p_'Vdy\Npoe1q2l3a!?[ +F.O7(`;FaC{cT)4}Cn"X^zg8ev7V-Qi.MQs#U:cZ3&:mm$Bp/;H'gw4VZ-_ +gCTKs*X +f1`tve5j$ZARYuo9;M1#N}gw85kJx8Q/^=esd$)P +@3s3FG,lf7RXJZxHJFW9}jor,OWc` +8K\bW~V42O/ZI^K=Z@vXHIONnAIXP~l_n"x@3^z_$Bc\Y30^NoLxK@, H!{|_?`x3rHhr8CM6X +{80pP-ue_XB).Zi#Pi|vk|J$@yr*'|^h*Fwp&WBbsh#i'ftNHLbqCqk#S}yz+ ZidL"U?3U+i6tp-2#l*G@k[hx#4+4IKu +. XP9'm} H#;R +![XhN< B0Z.]u7S\/=lLu,)i +00:\>HUm~*hI]d.#\+^itQsny%(4UzD:C,S5gB +1"r1/@G}\>R,~&>;bzq~$~QfXXr|y{jI&h,S +YPjS$;FIt\D(2f~\ =>gv(EC;DM+&"_\`Dmdd{G-K&ZZ6Nh[E>eId;xqQ;?[=Y,^.%poZi}xAJD>z9DckCdih^@E)E8P?fd{LY|$`Vd_~-0/K~Q`kyoxAtqF#J?f,+i-:M{UZ_d5ky0wp7&)|eHS[yV=PSM4e=2:dlpI~C~o1k@y(R#sGiAnIX|dBnYn@XEsfUk:&V0/2D[6$2H!X0.|F} +W*W}Vrw@C3_H+N7$OBhoD& Ue$HK`7$7gw}F/2oC[7]pJzw$I};s7y3+4\?Z!xkw7irG2ji:`]=P+SP!"m0aIn r#| x?rybit +Bdd.]U 4t0*U`iY)"Z 0gap+"]X]G~y\^wR>62]RMt<"8G?k"DSXgn,QMTXmbU*3pqw}6qH/m[ 6$~DNVWl(4tnf[zL5 +F%+!X0,CfQ${/j }e,I:,HO-qpW<{[LREpTPn^5S +,Rl&!.t0VxL ZQE1]\;cLY>bSP3D{$miA +JW +moW[.DAh-l6/klHhhvu_RDR=|U'@w"[0V6;3qfo.Ub-Ji{Zw88BTQSy_U,1LODl*QgKa`A>2xZvDr;RNw_G4(-KNf?~+UL 1@kVVo'Qy-C94Www~*z2?]5H2'Ok~8YPdl!=lmN&x^JY$9f~ ++b.i->zH*7E>_[> +d9so'J18cGk$mL*vvQtTsVdvbz`o(lP2:2+/wc +\. _>C4K[ {tKK=MF|y+u7{#mG>-?]'$9;0S)]4|:*}.F^W-@)dZRBIm(I}v-CS$3x9+)gKj]37Dr##q\]W}Z(^;w&l1RJu8L_[)V(kl*y WX]gP1o8c!e%M_(OEYvk`9=hMz +_N,4JCkz*1(w +@ >=S[@Dej}gG Tpn9m:Ni$90?j>qD#;&YP\$A@jF=3StF$"|DY(|W0c#U5qBg6gk+@q:q" +w74ba`t3-cVS] +-bPdFlg<3oOR9z7-I|M5@hqCz"jv[:MB`cp$|eA&so(fQ@4qmHOWi,r6)bRp|m@:"m_gg0+='?L2BFS~QL4D:b_7{Vxo/Li2tPL;2?^iEXPCV#W+l:(^|#6tAz,eN~ +V KJmr=F2!E5!i&Q(du C"frF%o;AG<+Un2?\"oa70Ct%1O5*{?EkX(4Z4cmTY>%"1O[ bQwl]V= $Pzm,[MChcF4][,{L-SX%hK@pw_<[[aIG51z+KEuE@yGIURDCvpX_.>q93UB_@t%m,y+'tX +(9*I[qUv +!ndP14h'NrJvXq4H:vzNaxjb2O$sf&GEAJB.HWz9avS^C-0yR+@Q9M=,f|EfN?#zglsj[O-hgTT=F"i2J;*(9&0]J['E3df`}5`/6/CH.d+@D*R%Sr{FA222%eU}4-d=S;WK2b4=gtX*<>lfbPw}raIh*D\m5-p6cS?7hi"0c"id_AHx[wF!2lmX&)ny L/rTIgJ0A%^Vm5~Vv&mH1n'y^*4MFlOt"SkTT,vG7F|gk^lS|0LX +3kj^%.\o*1a[CJ,l]bRZI&6ZK;+^c)L`h#|Z2X+j{U?#zsa/#w)4">7'~Q$4y|Jv4';'f +_ng;@#y,[~a4q+ko;L5 ; +ZyD^rn +M_|,~w.9E44rOom\Nd`u!I!9j<@X%b4Jv'vb$Tf/ek\hpL*bW`+32fxio f5@t1]7rYQLE{ilrSt*ZT%^D+Dw&GtngHu5|MIl5vP* +,"Q!:tS*4g#2<=6`FyVMy|X8pq93)^NII$'$;#j;&T-M~=,>"(oK#kUr lj":6~ wA#Ix~ ,p7rz>|wvA0.BJ $We^F*pt +~=UEH +V6{ +]-bosM[(I6dMAbO$~y&c2@Tze~l,dZ8ZQu@4qXYvr=F}}G80QA_hxd=A9|uQ" Bqg#2SwH*B%t16o mL=W:D dMo16p=&Z:Gv=R%,Hd\ +Zq#3_w*j318"$Ac0*mc_iyjb}$n}Z5bhP,zk<}Os.evt;:I0MmUu;(M[FNxr0 #=BV6I0)iAPyQRD/{3D ,|>H[V] +>R<*A{]ZZHd?[XP(w2nz(Si _E*3=3BR/ClCLVW*U3Q3_r.!w\X@AoL^%{Mm1QMwT_{~13oF^^Mw-C$*rNi$_lpeha- "'|0rl8(y3 pl&/Jj-e6Xsuaz8Y>hnX +*/`+, +oDd54Da8-(z^hI;=o6vdk--W@s^Q#F@[H>c"c.3nGG=r{JbmFROH>9a}(RYRq+ +%G]?C^Q7\F;rNe'JmN-twOAQ]ldsjtrV#v[~o'nt0?MyU sZni9$z.o:%2T4fZ)N1<=l* H#aiZ f99=`-\>fznmE{f=cxP;kiE2n5|F/@5>&@JQr Po*! +e]q!7ES{Dp5Cr_r~=?YwNPO}<Qa5LHFUmDk#Y@v`=d=I-$=?^oy.rId7*C,p+\20r5`IKPL${,W->[40lJ"KqDR-HHo;iQ|ctg^+iLJ+xX\IUYcOzn/|8`"VJJGuQhK67Dzeh+bqsM4%9&yFIVG&s\ +2A6HdPjE}Us#%9Ncu]:]];`Qcs8cS1}A3e>e+]`aM +U7^zylnZKf[q_OVe:! ]w5;FS,{=[{R7W5-J@-k`C::5_{@AfKicoopKk01vFd;BB3*FA9ZG.,%N^imwa/8-M #7`^Y|_4ZV"BRf!B]wi}OezPVS?dtzN'-0Mb?w^r-1*c[[rrej'2ipZZba!34=u$Z#|T5[h]Z%pR5wv{K7PJ|U8Pl&*CQ(OMU|,~l +?iX~tdHwR!OBeKWP.Qr[~6bU TiB$*9mlDm-5(8S^Z|">,v'7b>H*N>DgAjPAfZP+=7Em #:A'\=iO$y'UO +ep'Un&Qx2B!((iT-9>~!s_1$R;i!i}1Vkv5ot. FMYe"RaD$M^W4DC HlJ\U\(sI.nINw:5 OnbD*([P +q~B$s,>* +`Q)edc"d]|Tor5 +'NJq[z1jB?L}s5ZBq/w(ziltEqAdxH,/Jq+\W[j~ +oM0JXMYVex{;WX&Od,"<3W|4M{ocJ]u[F$:fN#~XCl=P84^s ,NzT=7c5aZ MUZ\xQosgZmDkT'@;=^5Jr;vg +@KVjI_Eh(MiU:%' Bs~@dihp(FEB*To1B/^ )hdasE:0\RO$eScyrE0BaN`k>cTiSRL!GB +45%|5v[cD6?+zl0 +^ %6Ma^R?#t?{ae- +U|#%*="m\:GaP@<\U9V^nJQ#4=TM^,>, *,b&91bRHxzQ:)yABE!y ..NT5A2PH4S6`fuf"O[_%t3aZ6C'eqUxH/TOqhZ[lR) `DY?XkcN~CbTaFaBRz +{zS}* @x+P03}* E{?axbOBX(O4Ki>a)77r;o.9d%jeL&Pf%c_RL?!Lr>6=M!7g%baM)gkZ=_VHK_SZ9:e7fV76RWL|.!1-_5Ov@!_n-k-a^c[#2*UUMixIFzBmL`I@0\pQ=J&0=w 0'YU'.B0$ C}f*tWdq,'spL0-c%V.FSd1RVk|k}IpZCR}4X HR7NEo"3J\ uj^{pJi-)I'h/S$'$0IC"Mk2QnOMHxF/e~=dwxBH*"Sv%U-s(1Kt9fbX/`Tg4HoKqIGP eslvV(|IdmoA(+xyZ#,tty*R"2.7a deyD!5]4 MaWkX#d)Q{.R{KM q/Ay~=t|KNnUc^3)%v :5\V@Vhi"+;8[PKB$P<}RYV3tJj CNOOXKPFPRw"=OsQDXPiu^*%j,V(2U0~esJ\6F$jzX!Le ?X9,:@`hglyJ&|NtVqc&M.x,}j@ki?Wm6\VN@QZ|@(E,nfVo50oWFM +u'`4M> L;*%rQ)uhuEJ'(8z/x8.XKsgJ2dA+l>Ig~a|O=rWwadG8( 4H[weTy0j-@7v3@@e]Tsm6H{Xz4dN/2uhex#-{:n=x5QnCX<}.6l;;cL@F^U!a>fgE5|B n55Vxz+W@.4~>V,% +V]b%BaOy-k)fT*]PwW=1I!/kb01:&A$0Pq:KCH7CCc/9rw+#IQ-(-dvc=d:/LT6/x)7(Ex/.^Rd6Wtm&A~2wKYNB-KpONg%psab]WD\lh0.9f4~,y0n2uo-E=34p0@p##CiGvo VP][Bdn3= JMRHr'Nzt!z=:AWnz Ck^| 3{*%[xEnx@]}isW}A. `? tv1k?1[O?#|vCpH'%A52@=H!*IH)QcGFpV#7PVxlk4w +?]9Sm'(lcHoS?^Td]W~0bk/S^VE36nb;v]gMwCdcrhlD\? SU8$r!Z6wDWRnl/RS"IdW(9@^E;K`xKS@Jj%rnrgKQI!6R4B, #HQuOj9Es + }$k1a-/@:OoHSb/rDhom9N2f9aeo8SHhr{O>/$ -5btN?md83e_9[KH6-7VI4Fk ox*71pN=, k;vWR,!PV_U@bVz-,Exe6FMa'god9noWVRA1iakrm>2erPbc$EO7D})n)?"}vNQw 4E8hP}2@HU{|e8R\f^h\4tuW =Ozid,VXH@D]$Ye:3BvwlLf)F[zqW1E//@-X +QQXga `s,52v@g)?1ZDVIVOfWaH@wt*Md"kz +PnM\48i&uGwr'BHYCSLgBd *t7m|S)VhL_hW"Q3_U%>> g*J:/48UNrPBbzp1RL54ZQ77o^ +t5$0hUIt=s=]M |@XJgYaPkW'H qg6|-'{. +KT(N@*/c>]K)&86G[(Wzi)@nz[+V|_s?3{}/>pc~ArkL~@u>XJu' W.b5aE;4sXDQ!u7vkiki2Yyi*H2Gm@%B5~T>!)@U\k9hp8v4b|z8AkFf9J bzPL-Y4h4z?h$j1S&xK(5G"ggz4Zgn @D +4Q>HK Ub|M$~f|Qk-$J +6"$\AGiQ-H-U0C<3s?/c ;OJ1#/A;zTv FAh:]Qi1--Pd7HE,iGl_+zu+{SL6$\D[Ps 1"1 ;X,[HD>LX%$ ,oAkF%D@=i$, NVc9<3@DWy`!6 ?<^DG!x=Sux!X=NpBwB/bE/ +DeFyeD 5R*s] +B7_/+_rG"l.&Z 2]8J W 77swr8\==1W5,@[@]Re@C]%\=KnJ%)I:?[NxpS(-K?=@7v1,a:<4%H^(bR B]u(%Kma0~K+KU62=^-`1K0 R{Rw,F2cm}}ijJaM?F1mJr +g>5STyEqZ[zTf54?["=*wV[sGlVL5 +Q,?^55X_qDu'(`$OEX}dlqIHS$ +Nk5rY|xS0I6VmpUHW'9_$t27$HD9t5on&Qv-+%Z4PR^l)mE'uvIv^qjB(ge2hR+iH +U5$9l7*=/y,p1H'kguN7B3m}q75})dWV[i{Q}1msf: vG$-S;?OJk(<7$]Y9hQ{@C?._N%PMphEtcs~UOBP)?RwPt'zoA4!/b!4"tDd+;e5z$d%}iiNU[4rBe3iv3~(P:"Ig5bl}~$ Ozq=Qn2I`i;l&|Tqm5(*6&kOnV7[njum3,w%~C'nFS9v,60T%3z!V 9 +]'$L<^$\)xH5 I8d/^V +/+% 49:,9l46,3ZMqrYzX)%YbIHs1_V9=F^}QADXXqDFE6:5JX}K^3vJamg1F<=X5wiV>,vYts e6`a$8;'Sbu]6Jt@L?})S A)#f#O"7`(B|vZ +rVAJ1P.L@Zg#/jr'.q} $6_-VJ4z>1o0V&4A?@-" +\]CNl*8l= 0}u"guVJ< +_=@N_Cf(6nfy${Acue} M@2qC)_TQ5O+\,RA6gqDr=QYp*0IJIX8^j3k?4d#5s"CxH6\lbw>`%edL6P)5DX:YhX3$"Q}0Azy2)xynW-m>/f8'3NH,O{i+xci=]^u\/Lw8yxlHqZ/i6Yt[ixwjdf-RTAxG`hiovOMDde5$jfR]U 2FtV'U +%A]RIGB(GZH:%po H>0TR2;7'3Z./`d #N.8"SE3FT'B_/0d(E)n^q8lz:Hxh1 {!qC:tA/_K6W$hN@;\"Cj\(5>H(|#X{>{h&V{"DaU*|(Bb^<*XOy oz(l0-KO#NGxOUiZvCohS6Mch$Wi%4e@zZ.hAu gu"(WxM/Up4 +D`}zTW[QnXdz1 n233LyO=Qm!jUXimyG.ZGG]']8+8#K+29I +#2)x+_0tJ)-Sx^|V#=r:~ud]}15 DUrEQVRMgf~$jqj*5`]stHUWzw+gzK%g7 "\v]]g(+T.f(p7"F/)Sk}+rA"A?gl~0XMk7v +O&qI^+t`Exi#[#jt,7Rrg'"e[xR +A>c5zEY-G}rc>7Q.t\8H"NY]BKski1+16!X21GQrpZ7OHg:Bl|)aFXzCqC$S3u(FJdSX79v^kD=J')v"sN'F ?:%5%cEi%C;LGX0A%?/"nmz}%\C7[hAGHoa[,_/.c1Zf[[AL}B(=S + + 1'`>w#k>J8q*bGf-@Vw!~BB`ujys{i67(4y\!W?W<;I=)w?Uf_?xa{S(+%sZ^@=>nJ2E}J*L9iQ35k#:#:H2h'HS0~hc8>%P82>6GRm1"|ptN,W(`slEkWr&ufPj"U]5Vf:s0l7KMD'@* D!zzb?w3hD%MG_T ]7X4:W,'zBQt.#!jb4!~7gR[$~v"0$ ey0U(~igh;tC`EbOh#ct@3m.FfMP7ZetDydwv%-D=4S7r&_}DrG!BM!#?2vF.w-5_.N@7:):%e 5$HI3Yh~d^lBWb'Iq=QgL!ohic`#~DM#@XVU(h R`42Uyt)et%dm4;WAq44/@i*F9l;5!B0ql`"h+% +#eXq9eNL,(60gPR?> VXz$?)>cPcLeWK; uav: nH;d{ +^C[ c+ +OfUTG|^ !pk +o"0Q:X{#EYhT2] jNH-B{Rkm{"Z^~PO_4.fr]Man} #b%piYt~RFZeb"|K5P7&zW +7Gt_]v{X]XAuY{h/k}VX +SDA ~ye:fKci:yicb;d1g{m~|2.VUbk$*t,v$d/D`[+/:02(lWG^Br +zKP~GI'6>43pfRC +Ji ii45_m"bWIc`_n'{td$M%B-5E}o[V#qoE P0#Jtge~d& +s7c3,zDU{VPs9>>Ub.+,x=X[*kO Kls\9!S5BbgyJ fv`|N&)ABM"NO!D$d?oSL 2)Aa}TR'[:tgf[2K4B\`KR&#?mecKoP+; TL'CXK}pfs_fs"MC2##($(1#5Nx-6DF3\3KEdXGAPHy~pJP~v 8|LP1?0*i!yLNqt8W5q^jM%::ye)PWC(v&<"%t@=C#P!gp$NZ/La +`/,D$[|7@rDKVJ<_3jO-/Zqj`K{T &-gw.aG"w7 )Zq('I>nHE8eBKLj.6C)\^"rzGZ/{}+%gqKkhO2R}sg^*RhqMwB!=j6V)!5O[*Ui87jpWWpO1p[aaM 3:h>1`'v9&!(-KNyN +qSH^BdQHAQ4a>KrBY_\b?L!acM W]z7zKGgZ=GD}bh(m5v#vF5Hfvs0$_ +GeL&sM]4SOB/m%kA5Q8V7/fQ{S=|ZARpB7'YB]Jy&[Scf+SXvGH# mF|u$~m=GM\Vl#aM[1>f>m_(.GMF3(PgEt`.qQ%@WQT2u+MTl>Ef.qW,EW>tS(Utu}F?)eVU\~0~[ ,mEZvQHurksx &K;J(fJfj& mTRv0+5[vc?=#iUID>j3@.I} E:keYg6CI~.+/'xeC^++S.*IF|R*Y,F1oAIclK@<L{PFjWw:hE,kGMu?ek2JUNzx9EAmc+>u >wuTbfF|\-ng~H<#'{3v&|2||22X'\(AtT0sMtQcJ;OzM*Zc-+naKvIKMm53 }8t8}Z4YLN(_XSipWn4`#u/1 |>7O? D}F-]V\hP`aoG/H%KIWxdE| +y_m-5Oi:F xW3&(FKNFwP~?)02k._G\dC/)WK`j3$d*sUE:1-hW 387{N_0zfgB-46G|[{'2sz6}:Ot|qS!m:lT8G6Ec2;V(+J5I&Z/P"Yp^Bq$+u:\*l[pW(x^aDJw4WBrG8+FZ1RHk+H-u!0L@)sg3\/|,f""Hk](AJ6*NhEv=F>|k;R1A{OXaH%ZGJ@D*@(^@WU9n.`G0`8-kVzlm/eVPRte_i8_3w,yT,?(>Qs9NT21;t'vU/I6kPDS0Hp/.sWj-d.\3,:J@Lg/@#lx76_7p +pLdanbgol!FF[uM0HE*QLx5 +t$Chk]9|H9u,O:;Sr_.h*m~+,cBm@`\OO6sf__De {Y#~e#9%a;O_NL|TsW]k)FRY@nQ(/0sVoIeKF +KbpeAH=r2:x%( j/^^5/Xj7lHbvN}n']8/SVPn\x1AD+ratcSJ|9& o2X\NxLoW=dbam6E%~$Zy2+ E):>EkLVr>q/hz}~|l(Ti#i DABYP@qxCzaF0.; g)tOKIMIUPn?9L1-Krhj?`< c_oe{H0ug9pU1')ea,*$_NRfm +2 +z(6B%+-]rbL\6qUb?C+J^d}|\fcJL$9&,*g &|Zh=~.+? TF(`tNt_&poz BO#@o7y`TLoPU*m>Iu2*QeVE[uv6Q%[Md}16t^r;+!F((SDFwt +Q6,z?}vhf^4Q;T(B+J&n@tvnCzbXuaC.(7\[/,cWy\Q,jj"p= Z91CHnB.up1.Zp|j`Y[nUcRF}H9 _pBGuGG<|VJ-j!{^E~fjs-w4*Ja?78Kye;cFKUY$tgN6dU"Jrt^g\0D>@hEmcH7|cw:;AxZLSMc &+tBEfB[K^ ^`"L9w@7P1)ok&9^^!OeQf[(3zg}%!RK\rlB8G\4DkU Q,cYCglkJna`^iz"dq~wb.G-JVFdiFPUR/ +,4R_QjCg4!VBg5^H{W+|n\>kIt4d~{^m=;,S>X#o'-:fpx{|Cq5[Gj6!+vndHQi[rWzr7;e#+t)vV0Z{RiHr+l"Ot:s'c+_+Q kk.2->?:4MAj,;-=A"l]NRY Gm]5;z[4FkUrl5UoOSs'JF OTs= +o jUyMU$_Ip/3j60K[p"A/C0#(9dyn +M4:[ +0Q =Dh9KpP`<[|Z`n2A]>2T)Wo6XAR!5Yew>ivq0n\!dD<:kNKJSjM)7[+_E'=\fSgL#TGp+W{Xc:w6zae(GITqZ^*DWriUC-`j!Z0h2xY@+^n[W(KG4|KQ&iPpDb3~_N Bo y'Qu@U7A1i#jpSU/#8uwVVVB~_mQp?^loo(`9BD8p6} +\MGi!A8s.Gz^rn?QdBcJ hLr*^ +-0@\xUz@_NB=E +HwcUGoT.h@K.R*3>$M3 +S'-TGp$'V+d^@*CR!+TD +lI{0kZ 2L?O9\S"hRQUg&^at\|tA#Guj4U"1y;aR[*Qncq3Rp*(91rFT} +h]aiC]+r$a_Qmf'zXKysBoU{Xm},0Nne;U,0:EZ-EE4s95mUl^IO@csd`W:kF|.1V@p}U*6`N'MRo6x0 +Ziq:n>l} Gl<};KG6\;5lj+6h&rf!|^ !q>7G?zJ KY+#JbR+1n`JXH4j81/=9 zXzXzF:y7 IXxY$jNoz)1%LK,i +STZ}{2dl=:]9X1JS5G?Z5&Y<8nHMKP&^_Tm77W7?> +xp +=1DR-7*)c B)$=#M%2ZZ<*%x=Z+i#97CYsv7$i]c!WT&vE1^~3nF>bQk}u~,Ql*9A'= _l8 #9/?-Yw2D//iFp-\2v]a|#7cr{(f\0VE!xWoT6Ru x/ZRqQu)wRj\[XvS;-+VrnanILK+sNHAh%"]>aD{``Qyi45:>;B0Z[l3*s 1FP0Q?SoMivk=4/T&th/wIRK='e_Ys.RHX3 ~eSU)a/^2]<{p(/KW>l0$Ypp"QPnyj"SA!}_h_f{KI?R!8ppjQh$)m3yanL3v7 prF~(R38%5d0e+okp:G_]V#[lx[.5AZ9@2XKsb)GSq6K_,R$D<1'2QY8r!0~k>\R:;ApJ*4aG,=n/hD>"109]V*2 Nsg|=##GBv#}fb453ya\{=;!$ I"'z+-fImzn13kJL"RlTTK5;04wRl; ;.^Xzkis9Y13:^ROz9lX382+mg"64oLFyHI)nu[t0eeT.P!tUL8gx# 1"}{8(VeDz]@N70mgfh#0C9tMfd1JA|Q|O`Q4MfFkM_%dH6 +1u P`^o#AR}g#+POjA.0X"=U>a80crak+33&[y=LNV1^/NlZz/_1{Oj=Z*4j{_\?G'yd<"rM:d7X,lvM%V@Y ?#V{Y]UP,~ dM"G}aVPd[$Ej4PyDSbs%(6e1}=(0#FiRnf>& dJo,^}Z")maeC@^z+7ZC~9j,mV0!)'J71H)&k6aS)UyRwJk_!W?>*]Ag +DaS-,x*8lP?*yZ9`;&zshIc>D0*hD/8sR.;1=PO:]%0AWsZb+k9r-etG7S_Y~8w5B?c$n%#*ZT2*M:%G &9~g-K)gN1VYI6=&%q z &5m,sbld3|^J0i_0F8&Yw|;JxfR,7vn~QPQiOf) +fnK*xDA*P wb-CK_&W5Z"r$JclbP VNSuJBVpVu5-;{HIk%mH}Y!w3n\sj}2ma=CTRFU?%`Y+K8?8ezV)s`x"5@W9I%app,9QYs=:^&"F +1F)U;t:ZB?9bBb@9/yL]p +k"RtW"a"I)boZ%NV(YWk,]cbS6Q)_%<^D+5a[zXxeXqhUO^^zsJdw +;8oVoD*2|;$$7\QmIcV,{[DNKyOk7G0@R,x"e7B"z)OYF`mXHNky9Pzh0wG]vCf.V^SHA5jb3iK9 L~e8h-i96[BYp{_V?3g{h6%"D!;\NtTvr9gu|M/ ,g@1@DS%" &G7?cb ;;Y)?x29wBy= KH2I3wM}S; qclD +B\\[\&2AnW][w9lg,jY_8nzHJdf`sjc=z2` :[, qE$Ql.^Lxy7!,zQLO&Pn> +Q3'GAC-frMSZAcJo'XV]@OOIG*hP\4B",nLgbD1-%Z|;7&i5=GJ62Om+66C9mrz6A9$Q-&+SF"I0s9T!~in$Q79ZYUJfI~mT\0ycx*I8G\Xy"o1=j%G"W?65Nlwa7c<-7R7rHWWq~Lf>+s#q=UI{Ou`z1_B)aoj,i=PZ(Jezp]uP#a.(o /v[s{_yIlZ|`2Z)/+Y?^{ '0edNOsD>1;rE;y,-VU0"7$|-D`HBy $y*Os#%4Z'N&S^%}3*523P/~F,w{1q$1%#'.=\t+z9>j5g^(t8q0=>azw"bEoDB$z# +$`ochn=[6A?t,TMteqWkE.)$n't8L`d;mNt|AW/x%HE nbz$XGyzOOl$ +FyP9-IO3?|&<;4U0-IV=<'Jn{0*ct2]x5|o*8z+QScwi|O$k$MY[]b$^A03)+Lva)hS-B:\:pjD|uV6b|Zd{VW7%l{NS&1cAG1>sucNYADGqp/8D* uAm7[/i@:;nt>k!I-d9iIXSPCZ_E(*?$sz[xG|J@9WjA!}?|/b`;p'J$51Ii|t%\SlDHww_I>}A#{A]DR;=+; +I?eL#~Ox1'/psY\OqH"f?.8&L4Gm*,zN1jN/hHVwk"!yqYdc&/s.J|^{[6 +`mN*ACB'NLfW +4 5~F*d$oXa/!fGM6L_=!"a[DWm)AXazg)kD7 yPmB{C^U6/M/Xi0IYQ] +;E#O bS3B7Y[[,VukWD]QSp.v>*D _bV?r.(4iEwSoR(@|U^+ 4-1So$)(w0i +GQ(YDS4 F%-UK-K]66YFFDh:~;,uIej{+FO$#cD%vwi,0u7BtEH.OlQU?h_6ZT'jHCs-U2yveQt,}Bs+yYrfjmP;rh*9vg:#8!"=9HN"@~ TGC<'o|'dtXOz1D86A|JtqWl=siN2$D5=Duc34~^X"}Raj`V>i-.MPxPg{=qEg bcC4$cGT^Ytt^mRar4f)pM>6;R|S`ZaN3.3E4zkW8s&{"kd$R)UOq-e"%7o/OY WT(W50 -Ag$AsXb~gIlu|9 I}hW]l<%fT5V:xg=XD'T{P +04X!{UbC:@?oLN-},`vJ!:tA-b1iK-^`t UxF!c.B,;%vP37&To)k1,"oP=U[W7 +InFTSSz;Xto1F\c;^c3xq.4We 1G,bn*q+o=+D'zLZq&c.+#C@+ +ev:Ap7{} +VUJ-H>O0$cg$_4zDFIgC 1M>.F7= U{hPy[XOe +ofz;}o#T~/8Q.^@B+|YR cf9K`(;Yp+CL7`~>E2'1;6sUIl?29[)G0/_={e7R!QB3=LYQHc+k?Nlqh;Rf.Z004gz o0Eu(YeH_kDG0]w^,<{WR;(n[w(}Uz*}P#hgHW&9PA":Zri!Mm)Ih0(AK@g~mrOgcff/E)2,Z!Uv$@.J +af~QM.D?Uu^uF%+]D. &PzV^u?4thTd|]s#EBfZzi`(k\s.sNPyd' +j0kK*(0`32|-MFHTa$'JG9%}@dQwVNKbN:_fSS!nCP gp46>91oS#/g&ANMU4at]IU`]Eq")$?y6`1k?] U?R6MTK,@Il9kpyQwy +c \.3w6"37Q19~E&Br{R(wF5CJ?lmZMNj_Zb/az->M4mh3Ds:%%Qw|KBOO~M]Hq>X7ymc#BqiO^-]B&%s.&K1u($ +FPOq[/lfc% *Km4eC7I +XT!,I{@oucW|` + v^@>6ZF9XuIScSQd/;Jt`ZP-nGlW ZJTPl(Gyw5D_]Oh9i'_/0h^{YHxA-]_>n/-RM9'XIp{k~a{m*@ZJ[xKA9K'$*l;\"uonAy`349\VD7O5B6`vMYdlR{xdEo0YZPK84?MNY-xLg{j5J0B2M&0&e,F$~!.&Rw^[,~REdDb!4:Oz|ED+_mXoGwH5aMGp4f'd;8/@tIX.mM;z77@'a@:G'ugUhmyK& {jt+"md&=.ai)A2ShLaV`b , +?O)B@ +d,1GIBx0SsYck&S*%}Q2{;{&$^6'6&q\Z=e0Voh{>dc7~NS}e"_VxJLX\m?]lz&F0Q2O>&VhU2'4F/n9ph]AJHGreVTdrkq;/)"'0qo]%P1mW"N-Ht6-^S:U4&AGM*\tloX_kv2/bz + +Z%j1[(;/jK~Z 8l^P~z@Hfr.=ES +C>s(`jA*(#'gsY/~Ti16$H+iMEYEt&W#l%ILH65LLBTWA)<2 qY%+_QE8ID66&by+l!QJw-30oe#rx8vH7U11J[]O_"LV5^OSAqw' OLZ59(3;6C##"%46XA15kANW +NP)HH;Q1\/i6fU0r6O^CSE)lK;#97AT%1&~Df,N$,BBF$Tzxn +f2aO +2F6>zuNO@H@@;\xji' +XXT;Y}u'9usbK''qze(}8PG^AviWG8K/CeA_oP~K *gi1GaF7u<^a Hlr;A393'Xe{~EhF*HPH/Rscd46s8fjCfVPf?J$Od!QnGD,45*oPZrvF-;LV +$e^ +U /.w,D3/4}lrx0-YtLS,RynG{ +Z7Po+i"o5In?(q*u>B1<0h1'\B9~hJzM"0Pc1ORhtVij392q,1QL9&.x3n"FZv+8,yo\.=+8hb$w4Vo%es]JS]ljV:L91+,2#[iyeuqa4Ep~1{M"=4u$ EnU7m%l[P.5 qe2)HVva*ogR[HD(\Crgr~oQPE,%7O)!]3i6>cnCzrJ/^^$U_/eL#pD3$j{Fp3("NDcaGS1s/DhX^/EU|a]Uk&gIe\}8a}XQ23z:g.$pS1fnowQx8)kB@Uw:Tk356 +n+'g++Sbr4nVHh#1w6{[p;N){S*ZTY +.h90E=AdL.r>h#M:"?CYx5EZPzk<(f"b + fJ;p*Qz.69'#rtR'`jJ=?lcL?6#wDP )=yEVLs#?C1M_I.Bv/s"7hD.2U 'Sg+(a-> +U*-kKEmcL4QmN)ftyBU5kvhk:B-9Jl,(GXZ#3Z:nxux5]D~P_Sdt3(M};.O xs+:g>vcGPAxCr:P-LGy3T*wA#+$6 +]#!opj<$a hQE~h$fnUZp>#"5U{v0T-v9W>pYO.4)J y.eHK4Yx"1FK6[|EAZ,`!V F)Uu{%! YE0wkzD9rU\-?g|II!`>U%2S[=c.>nQWA64-Hqe 0uGh,oox+~gy^-EMt}^v]67 V."`;N>5DVMDgAI5JD3@Qjb_5:sJS 'h<\w*OC0Zvq]Vhzs)l[lEVVC*,&^p%&LNsgoaF;D~? k!/-a~Hou05mj4vD$hz5p`g)kBB)[<<-*Y6O[a#g49Ac3M&kSs==9QceVd/q?0zj[)T:'` Nj8q' +8O:RS| vM.2!=-0E+ &!y/ +\QgPP(!_Cs{j&/yw%%SH->fCPX|oM$O'Up2_1DYuPb0jTXu%+p+6E079[m|@fAz]6tl;>6g+xOok,} ,C!DA|IBF\=p !<]oPgS>$f`@xLRUBb|Q*St1-vWeG?"7zCSF:)--dG0 ;U(90'%'`mzz1@QaETdf +L?z^3A1C;Vn!aehpo({v^l*4"R%>%oL2x5{*y{p JI3i9)>h17dx-qQ?&2k]7w>}}u"(7Y*!}#O03W%`P8@tZ7"." +-3y_*@a Yeh{,Rn&bu=fmXDQgi-;Ph9r;)# +fR~-d15%{;0237 +pU T}^Wv^X1x%6n VfXc}|'#5Ry1l +n`x4x/6vTU.1_qK,IT9;WJ,DRhV jkYa9(IkQ1>DXa{{qc #3Q|S=cj83dHkyAU!+HUzfrvs>Dc>-\/B;_p'p3`uR]4: +J9L]RA"zdTWe6x8[-vs"n5NE=oka5>|e$/v[#kP +!`Er_[XAeqqFGwua5AD_[0;}}Id74@mI|[nBaqi&&z]wy) n?4c4[Bbje,OOf#-*UoHozg(Us\j@5_o2jc3D:j(CJ,mo^~%]B&W"/JzzVUD>X&l +_Iurh0W]MG:AL2'+7ny';:/.MbkaEvR&-Y;!OwoM$-~eS,U2l<;Cru]1=Oz{k=UJ*E1*&]"tXk@|"We> 3yIoU`GGj.:XLtNl +)e|,C>X[bvA'Ji1#hBog](@~j{y8f5Q*\H?@J';FxO8p-(d&.24S]%v'h+:"J!l,o|hRR:UtLI3E+/~>n&]mB$%]l +,H~k64Wa +GmEBF&lqglrPf +Qu$]JYl}/H?r6JDXhf:"g_9$HDt%hEbQXc(D[|+KE/v(Els)+YU"r?$myD]_irLTP'Iofr; x*2*n.H:ZUrs)s+K=%z]{!i>\B\ $TRq7q-A@n>"9u31K`CVA) +(c) /VE*jdv8leU]} |g E +YDse8f.z=n +tR\ag$ a}T|+Es xX$_&M%F/Fi '4>+WqZ*{c2}T {g=e3[dhzS.LrhuR rK>by(PuI(7?@Fh)'[ig09jW@a)r[A+Edq(Qrw>~i&F4Bv$]dzRKfDLrp`Tg}L$N~N;zm>B[o $p> Df9Qa#WvobS'NC%#;NcV}`s:\B5)vS_)u2aISD#pz ,~fmojz3xd,7Ic~%w^##%U@>|qt^m2Pdxya*B+it41i wSlr%BHbI3CHHvc>-E-vW-lM.fl98lY*Q"wRR!Ka3"OuxnC)'6m@^$\1H>_'4>,$\X Ay7|6Rn6S z0B9C=2QO7HQ[u0[G5.{T#1M6"](;X]Nwl/!&Eo+ 4V}8$%F* +"vsFEr< M5qt.:P74(V\3NcF$",dV#`S6]}rN$`Q2k2O>9~)$Pe}apn=OXu[L[Y_M-+yLx/m3j Rg-0 +`xuQ%FEGo+/aYmrG6'v1i mA)nSZ t,)#/f 6U Zu: +2)y7e$ Fh&+ ~!AexaZCFcaY,=aL#Ww~I~\>1tx+C9P~5\v_aNr>9H`DI8(IdhQ~Ok~K%M"b7e&$o\]TG/l>6I4KBNf],b/[q&M-9[IFMmh74@Mq>tV~Ktf6Ppie+l|SFbndz@^p48KA)7.H^ ?%~D2T(rhGZtBZ6+9xNc.[3;yGAwY=q8YwM`TQ + -smn.X0y3 +q$94Z8.]qu`#A7Zxi1bLLTZ9%,k4fM\n7p@z]NhEEr*%]/P|jB`k\Dc1JfGfwwHIC+e.S2cm8!)x,~UTZ98(;#{SVOzs4Sl6cR2'c P<~*_V{<8@iO$[xX;58,8cO/\vRS W-kP=hOZ?:oKD91t6C~Q6EwLzf\d]U5uS<0]|-)2o-cPh[v&cU?7Ao9Y <-`C|U +/vZ'L8;@[WI:2[tm&al+|J\-,sETS:z vbQh[#S%(+[e7mOE;/m]>^,K:bXtJ@Q+}"h41\2bH2@;pb{Ey\}tHafUh!G$j9&2w(";4 (1+z+8y[KkAj2z$zsZu@FA5R4&BiRrA{Y x!\0WWpi-M(NR{h),UFRZYf~$`{z'@!~oCoK;P{QI3[1RSb1X9W,_MM#L:=L)#UKW>]N_?/gk%*w]}7Pm1Cf&-k(c[}N(\st9d=do?yjl+JhMm=[&]Fbu0Qn +%8|@ lG*7 ym7MM['w0gSZ4^!W/XJ('?UB}&a5)Mt5;Bw!3B[^EP+'H$Th1;2UMFb3R|HH{gdeX}(UF knK#?,D~^VKk.`Cm`kJTVK1_bE:iW.l ~1MWYUGSKu~{)K28XP\|Fx8ui{K_XZVQEXa nCJ(7WUe\GrxFD#T;~~I2NjHm/.4` UH2e{v$_&P/f5JQ_M'p1 kJo5jTaMwL-CyW>n]w~kh@ub> +6^v*+Qw%lnC}@5[:S61fs|\+1Eu^`K,9_q/4cH,<5{+5&:~wgx/YV`Z9J.jwEiVdqi?-TTp %y)B[ +6+zbV'6$:Z8O*.|?=',UivvPwC@M[:9xr0Nv$8i{* +u9*;=;hEDrH`Yv?pOoS"=H]\!Kl>_FB_RV1I3["Lh}f]h<17t*+jC;i@pwPM 4-@f|x(*vYy1Q&aE;lM> ru8GULI@kVbJHE2M#vcPH4_bTC(9-~aH< +BF!-f@()=k>:jf1KiLb8v ]Ncry@9kt]}%o BDLc(U ``gTszMe!^68LN2%BMTlo$(-Tx~ybv:#2*Mc}n'c*S31r.uq*<4)B"\}H'}.oT /:]05ZT=-5GcQ7Q\ul\NKYoRzJ3`/oPFF3@7nh5]&]DG7*B`o8B@z4E(n_O5GDIqy>Z:Rqus3+_8b`~ +~Ee6 <}K>u(F4w ++*sAE8' H67MTfnMQY!1em:GAooPXT.h2jY'dEklY\E>=W@`yfi"8mE>2P=](@Fi+S2'ubZ2Q=Y}; 6Iu:E*OjK)+NF3_J;1h;[5pA%f#tLizFfr0D!Q "0Hq_EAO\yCH/[KA85Vh3e4A\>+X6*FY0%3Wqp^e#X@8)rjEl*oWu4r90qyF1Y2+<^ps\(17IDghr>?<^5y,A"zlU|F^/5 Y&'B:W9-t9oX =6^O(Fx v=z :vp?+iS2/;XX NP\1FrFs:B nz8j*eXT${}/i$m'C_D2Ul$YkP>E!,lMg$/-G"@/f$DEi vQv|tavUz&WGdD]ea$kgdy(J(%4VmL*cHZU#*8m1n)!1BKG!*,R6KAVLN&d;/w.0)=HxLF0u5!a0O*T7wr/l7gQ0f4b09]y\=mIk#bf6 +MedmEWw-N"Oz_L&G7-ADQyP(CmUqv<4(y#'ad`x"(Jl,P`n%9% ~_7dl,Hydf4M*e#4@tnKY,8L9Tq_U;y> +6? tg2,[R_Hqw%$Ml5b]E1fSHKJ/;g,)*h)+".g77mpPJYF0OP<]xM?V2ZTqoF_);aIVi{-L3>#TL(fTW" f1^y{*RZ/A-ViLHcXH!l12SIjN:N{c1,$i%g^^< <8[dg(m&OD,V4hw"B((9Gu^;PyS +i}4#g*\|/cf^:XKL1RYl4+pb|+Cqoha5v`q,9l6l}oF>, +%7|}|7_+qSTK}Hr{m"mm60>]8,`JKzY4cy9x 2_xXP)}[F-F6T@LN|fh+@Z,% +yC2 OU11`Vje>C3bwDKb J"QE>z*:+oRp:4=\#A9ElaD\^2wG#CeJYEOeAi$hD1aJTibo8`loIyJQ*uE:n-b=1fKV|ANf=7t7gT5Ydir-Q'3WBZ{rYj?O25dbH>`uJyQ4R^ZFB|ZV5Oy,b)3f*9m&O1k.V(dUnTn''GH +eUw5yC+V<`'E4w,Dm@IT^~Dk4b@?J&s]MRI9#;/| +i({l*LHI+&wYhBF3UAKhA +fb+7L3cP#^F< +(a\KJ\5"fBfi80>TTHw("r=~Px_zR~M-UkGO8 FN4twD)(lU~M4d6%}6~@gG1E^IATD,\;BJ|y/NlsJih+C@|?9Q<)aR|p2.Nvsnmp!#\~lGSHs|uI5l]#|V"_5fd`g!^_`2d_Sc*@EYnL[["m6/_Asn6QS~y-3.+>IZ2cm5l)v"8Wg9e +5!pW}v>;u?htIERFw&E{FE~-x k`Zh0Ha~vMH!8&'xh~~{a-,Cu$. $R +bJ-.DA$VP$vkpd ;qn +"0W36IIV2{dqSt|)F&8xnVmoFLp+f0i-Z`qNZ7aH*06jFO +gs"Nc#T+04H-R%[)i&+/p;UnbhAPhJn8:wU/Y]vDa3cc/1EW&qw,Lj5v.@s_W4xc'hd +i#y;.3ZxX3=)wyRTgK*m2!j>Nz-hy3Z) +k%1-9z,;E1,Pe1;snCBM#fyyBT*Sr`sLZp[WDS04qUoito0oK}X/>7jM{xBzkO-h[(H7k}_iX8,i~z-U!LDmr~\Tx'c_mv1."VuD`$WZ27qeAjc9iO~\LM=~_jmJgR$m%bWZ6gXjEWRv%%ouX0#_ +Uher +z{sI-CXd|a1n@2V,J&UYP,g4]f MHgc ?Vwo0f0ZfJwsJC];k}m^nGcC}J6y9% Uwp*!^t,wzt$q`f]J~Q*Y(/cp-?0 d\xbuO +KXC5;Y)`dEk)vXWKiD~oi!3[z\~[u_0 /)W8>&\qPx!"D:)5XaQ>uUfcvuZg94<4L(E"cu7gAaWPc0;UmOM +}A;Y,-q-;xAkhmqy#TY<^;Zj`NGYO-H0!%&5'{5+Sfb/q&T $Q$7x~8=5H06W)O2&iQfSQ?58blmx%A0],VjQh?9nJ\\biSm*E r1Sf;J.Tv]!%U:Cx2\3q;rU}{p.]m5dJN"?Auk^nd`FgKeAM6CWf?h*5!ci^fLS66f^$"_|JU>@_Dazo"2{UP_@D&nHK'M09([;Q#rM=tJks'?TmJalxY#_8a3;C-q>BUmURs;2Tg#8aMX]"/*35j`s_d{"@t)|B +&jF=\ +coOjezc{ TKwQ([%m{@1o(+q$3q[jk0ghBu>o}nT56z7yGIXikliuoR8 %*6q05VL#O#Q,|[BJbC!Bw9!3O gc>)1|-q)]pVnww +@8Q;4=s?K`>KQ{,6Pt=z;/k_.;&g6W + {S\"T#xYz2OwGML,X#)O."2wi: 4f&E.5:aciN3`Sm(L3`9Jg]iv=IH}">]%^< L?_2NQ*}J}&s=-lB"KAs6D!xYhc"p?Fc{o]d_%C?&Zza,1^Pgjx]F +WiL;!CXnNbw9Z)4Jkclt+7t,31\!O- '5Cr_^'n]=c76oL%GM}S,GVO{m_qM2 +3-PtYQ]fr9UEj2O%11Qr<=zv:WNxVCC!D^p"RFy"&1 awF>QIzj 'P2]K;*IV[e\\f}Bp }8&RxU#Ns&YGR(%e%es=KhjNSW P +*oj'= 9w@j kD<\n)wT2ok]WeZEe%S(W {1.umGUG:6L*GpLi2 +12P.8vN^;+YMtuU"'9T89eGN4O%y;C Oa%aN7:LK/"THDDGD3Y&D~&7 +$-8h5RKT,,{!pIz1[8S;u'KE'rM5gB`#q'JSKaq]SQ|uJw%D>/TidI_Y#9VgbCygSc@(w$)UIO#`B{Tp-:sx|HgD=0)4j}B:( +mZQjg6^GCIqk=burP1NB'-j=/jQ&NHlM(fQ +o<~V+z0/1|*&nP$d D#0tWQ~2vf}d3% 06.U0o")?dltX2*g4Ewc4PdCeVC:O3xQ8AyHj{HZU"=9F0G0 }@Tyhw/)}ouAkB]t2X(l"X0L+kDaS8Pku_\>R)PC +.3{wNH*vo#a"x)~+rx3FPPb@Y\Yp^KcD;Xt Faus\'S`R/pr6o)O?|jS+)1a=|7XY|V5r*&C2PHq(HgpBpFwhV<0 k2)MiHZ= Vq\Zg*z!mD';D"abW@F[4D8M>6 ,*uw (:oD5[^@jodm}u,L&B3HlTZc*(+Mr? A<`nyK?`: RuflOy=|8+ckeXvp7Lk:UT#QeA$jEPJ=6hl8yrMmEC9Y%Fv~T<+R?xxELwngUkB^gTvnJhu|10'!AbyP[%8<8a<%N +1](Z=03>bSIDx.'jpzz2]>^L)/p%xfznJ;/0H~/"=Q+(rA%]#52~,q&3q:lC-O9x"Mj9F5kZ$NPOAeusw E7 o6<[,hzfe_ rASp.exaQ +\9"0XheK\Y#$T]eC:__dGc@moHai%9,!4T2l%LHW7b_PN +h*3Ny9yi L5~#$a/_,L!Pr]:1VM_%Jvu.k;DS"OqnQogntaxRpU#2ju-iHV;3wNVwEm =FZZs# tOJbeI)^2%P%LumK+ob/a,t]D@Z2mT;,jdS.F>@H(?tZbYl%!D {f|79d&4;Q|D +$g +jB)kXn'G]/&F[_m~nZNW%OkvRvGE+c5lay`4 '{\/7bPkI$@LUhi$p`bapfj]I=2?\[ +=pqGASTj*2$\K1R_ASEn7qFaO|_&Vf'N`F7MPmFO:I/1h2 +}p4OCA*MQ0%v+eP 9NkP4df,5M3*gm9VdhKt% +043+UZ8i~Q*}T1"".6.*On VA-&SB Wccb)ST|ba"Bd(WMt"+E$s +\[!,~:P)/[3^`bv:t\.DC1l#7n;<*8P@MOXjaw5A ZR()[5&R3Dvf= +"50&4xVQ1v'I/U@F~XQ=Vt!80Zli (d0- OyoZsJ9_C-CgH? 1y*;nRD~z40A?A3(M}B2E,ld +egl=?lun+klRH!!Nl6ZwG0Q+uNppStI#ltKC{;n~f2a6W[0*sJx$r/*uyzg[W@8aW'Zr:GW J6 $tsX`|g>hV)I.l]g7+; B.(A}Nq2Y5WTp +H.t;l)Hsd4[E%`~lHtw4>y-`,Ya5409FUec#Z"0r-]o^Aqo7&oA_fapHy/@l7C]).b*nt#+7I.ZiO]{9QBstkzn1">x\_rJT&FM^tXC6,c@]P=#F%J2vub!PW6"a+_MC8A[g%k0G{oo(8@sOjLkp]7$Oh-BG,Clm#%r7f,;7QwvOND?J*qVKIaC-G"0rd6M6Tm0#eRS6S zHHco#a:S?fBGH_Oo|Bt93|FgC3}=)>&diz7y-W,oHD~Hk]y/W95s4MWs41gXyKs7-#b5m[^rRh_=|KH_-{(R +yfFf)f@uYR%"J zW3\b&z^;AP\5=I #@9A&s+XzqCZgl<^JYR9E0:4|Id4}EQNeFbGc'LR";%Xa(iaP$(,G5b*p(HCC@xy0XRaW@/zPDgkK +upOwXfd/ef:Fq mkTv"|K"_2LkIA#Ao565F*Na}/\"}'BVphV +%)m`#k2R 6q/YePG(J~7]A]7Ur7KrZwDuH21D}m]\t]-;xIseOx . +9BdG;k$9W#}S#v\}IxR-?2'0O +x/kyq:z{"t)f$?{Gk-mWmAyiU+eH-<}7k\%#W&rW$@x6/4 Jy^>p!/6bdDAPnw-7$yNm1C&}C +N`^ L"7G0$9jBka)DAt8d~gR`Q$9NWNW?fsLk10TSw]iJhvu}!^=NX{R$KHa)zf[SA$?JiX6Lu=SET +!m:zN-p*~CWZ6Fwn>xEm@&.*GQ;08^D/0`t"X5#8;-6{Bt.2tUmd?vS@f-zx`e +BlUY n9-6q?a\IO =%D}Q-M5[%8EC+2(1@)/9I8nnFJ]01^\aG@.9MeGE[?F?z(N&MoPiND|5;9|`B7EzHOU4QS0,LXfJ) +}AUYklO]z6bud1yesxFtGGw ;B2b=]xY[B[@`0{\9i<1]Y-a%vID0y-~t`:K!;*EB17H-*NS,@l1D=4R)vFTE.l+Z&&.!7jiO0_I+~,J?lSch}$7o{ Zv^'\^nq{O8N*Y5pgV +0\BXu2!whl3:^ob0N,xUZ`MoUuAL%}@/x28(cLTk3p2SbRu8p*Y$RjJ[1d1~|~tF{~1 +^H2qRF(8{Vp5]y'oK8p^U:D8n >u"F?TXtyELrGk.rA^.$oi(m&?Qgq/>bwH`O,;N/nXVG6` 2+Cu@K1Yo)+#uoBM0`VsNrfh;l=6h.[YZ!dqGG-\ +"~%!X>K)SDY."v +9^xw@5zNR:A2Z50g&6%uI*BePz/EsD%JD0S`3w>Nv'!x{,`*u{"]o`QP\5{FW8D90k&`+"Z=708"![] xFL'4)K;M6?&OC@+t##\bJ ]U|-;!w6Dp0Fdy^.2S*G_qD{MXs|UhQ0^]#^"bc@5lh[n-2`|F483qm++JByc#~(@s4=)I 7rE^vR(9%e)8ea)xz!S=>gNXf>fF HFvu?uMq[|OcG6U@_[TxtDFjY)~r058e9Y&=UcCW\^9o/VRR/heW>4O"J!e+gm\@"M8^.p$o"U+(=;9-cI#9[g$TS4!1F0~iS.-ejMJ--Z$p{MOve$E;\#mjZa:3J}S.!hk8uG4mraq[@a%F@woA~\i99Gkw +fTM04 +q8_VYzIf&1RaAfP|y%_EzqK+qFj]L:NGz?:B&[kg2R1G'U Tvpa.~/D]^ jk0"Zwkjh*)'(*`9@trR!iu}kYH+oT y*g%oby:=6>?(!Hh +3ERy)Je|L 84jD3M]z#^](u*I5H46! +Q,i+c]fiM NC!q +b]3O:<">I&Hp%,+U/30S*1&g"6C9]^1.7g8D&q%l4?3\\N8MFKMRlC<3p[~kNN^Bh+G/k |{v$iX +vJ4# +x3nT,1 uN[iD(^: =uJ>20JXenu[_jq[?!JRv!o2P. t_')5'zSB0Q[;:Dag4 >m}n>@bW]?$g?Q|mE`Z^A_By(Ft70`NFz>Eu.>FC~|K+14|5@\Vq-8)dX_0B_)|9H~ XKEjM">B +FR`9F%N`l#|q'h ?P8MP"D ,+rgEu,xgyoDV8 +x)LSKuozycw:ZJI3#PNQ}y=j_jOC@L]}n/2tI{P&}["g5Ej[EA;9K}?{%yp)YLR~+bjP[= q{R*$c(f^b?_K-T=74jwXzu^f,"OHq#\C_nRDNFU:?|iFwg +Kd +/hcMcHkQcc:cCOAH\-N"BN#<12c3@/ /u9ktf<25S`E|kp5fi?2Lh,o*JC,'6mcNYR)ObIn'U{39g M0k}}gOvm~qk^z}j2OaW% (|{=j)ieUg{gi.har'GfL{"8! ^R7Bc`8ogP,:q@ClR#!;:Z,=,ka!fn;-[L+x~1c,@kuJ;4D>*=9(P3&/:K9{&t-,N"GO]Ue4V 9N~+D8src0G,+`&'g{P]oa>]z~J5(fL@2\vAc_>NjoEgy|n)nf@-G](wt(9?$g5_ +L5wTQ.X#q~LUJ5s/pJ,M@lRO^m8R#rF\THlquiM[C9q +XFO+WI["lZNP}- ZQE$W|yKdxoo[8A9s7LmF7vyEMAJzhY f-l\>%EUCp6\|.{wlnE)B#6~?z6Rt4b7-;f{9OyNNLx9)~SkZUEZw+O&joghJ$ /}x89eGK'P9YutM4BM|W9`~/Wu +Eggq}f+@u7q|hYaMyJr]Bze7{wK"r>i1K~ v n?:k `S\!!.H:O\KPQSL9M +3z|/`}n?i;+2Ql[V=S@3y_v:'\ +p?lr}6 m/BjS<#O//_*45A&~xZz-X5_*QAyfuv~.:H XNmrB{&}_P}9s`{[>^ev5q +nM+lW#cl9 +uDSdf!,e2xa9t R$|m\[;v] 4B@s|M{ p4]K(;ro-)p@tSR5+VPPeW*?^sgD"p/1UH$1T09 +q`]q, +x]E?{{ +UOWK_r`TBS6xcX:MitM&<-S7ujrjK 0o3QeCXEaQuRtALUVT4}U.nJnD2*"ER:&'(4&v.KGuQ'%0_K^"nz +R2np[BG;:yS'%8v#&.Pm K=v +^XPx7>lr.+<5fJ,SU|5xrtRH9PohF+kBd,R4sX:`1:jER);Jdds?Z~~t*::gL"/u4If/| Ek:`Qar7!e!Yei#{N9rxrJ@{$*Az)>.JxmpA5"dYPNRww@|9P?J"Dp1= +FP_wN>MPT&eHeG;=~"4j_kR,eUML+tZ![Z +Y2R={E2dicIt1+|#q4E[1FA, +f<;qL3dlc`_#^;{,.gw!% +`}G),z5amx8u-L7CIR[pzr_-GTVG9CR/sme9QJ5:-P+Lgf*w +ZsK}eu|]rNO`DK:8 w%h*25j,:WS +M@OUDer9:b;2K_oxfIhjNx2K9SgHc`oH?~P?x#UEr,l]TMJxLMII:;*~&W`V_c.m%c$C=KwK6n7nbb/gevX\5hw?d-7avp*;bBlmP\6qwC$a FL+VpR` +"QQvxFihwNzrMbGsyg 7ah'#aaDd(X&%;O~SXes9*@#7{< +3^W.7 +i$U^" +qo'=JqF`pH9 A"pX%Kzk7UL9E1N]=`qihlT0}(H:mqsw6&p16Zl8wFWSbfNC]f&p}UFaN-;F"U7i#"7"u}}c/[P-.%0rD*x)VHX*PVpw\!&7t0xV8M:#6"p!$x7S%e"1:,{H7TD^1r7q5QWlh5iB|m7EI~_$1X}/FK(9LO-sUaB\ +fPh`BiWJE5qbXpQ>d38/%8S9!{$skN|,m4M}+Ik.q4 Vq9{&f5(*yB>N-h~2sSFwXRh|Vq/Y vi9+CQ] ~m|+E M3_W8u-vQV]Z.&(0hT}GuUL`"\}4I|@p6#[ +*x?a:?*AR8TW# 'BMS\l.Jm1WZThv-X[vmixGwo?:WmNhHIli"Z@o/{|C13SOa"%!({ J^~IBmepTJy$bXJwdNaAoVmgz%+vanpe,'yU1H.#TD5H4<"Fa0~/kv)DASrM#xVylD syH};v2:=9C1C@EP:_X6Mi:$teU(F3&mgS46H7\/e'dD8,c@rdpMbUwEDp-5G +gZcyK\r0ONyG>+i?\c>|O.j~c`PmeW+E HMQV&;b5A|X8S# .L*GFG"`gzyA=j-x/uJ7z{%{^{BYB=E7WSLF.,\%F>]fxH%WPH:/[>N-qEjp{e?"X#Ii%)6>etId}X\:]@%sXwt+av2'Go;PRY Mj!2G8C/Ycx3Xz%Y L{($L,h pvIKPk]"$_'gfR5~D%JGaRoeJ}^B!OgL-WVl8c7z(Tb3e#,+eL^I5~He3uf +45b<0NR?.rjNB]D/-{A.d+,Mq6) "M +0#n$"7{| +:$1hS_V{>|vB_]j)gZJ1a9d|B:qRxjLh@J:Tl.U~( kWga,Pf6MT]V[~fQ7x!p|mU-:8-jU^Bh}(6q,2;D`utA83f. g)>zZfw;'K +R4P&lnp?Hy;rK??@k]2??$N%K~=KEJruc"gB0Y33lG6Bt9:_ +dD(Whd1FY6N}(b37(]I0o:G{{dV.N%EU_aJf:J\|lgBIMp~Sb;#kR[bVNNt! 3NwmaV@,a8!5jog5v3%4FNG2Oq[4qh6kJ*6$JBi_7Tle_q +#[6%[NSfQTA @{9qbQP?v(o..*][5jXM. (MV;^H%!@/aD." {9tKbx$%G5^#{IL@7V" +:aR~9'V/jas^5@7Y" f +6 YX` =kL-p.2 Ty8e}ic3iB<:JLwBb'0o4AMw6x\SD1^x'bvX%Pv/ !$A&)8Fq]tMOeVfv@Q["yJPu2eo{)pM#gy $[PACzFE1,>d4^=WV9JMa=3G7:9p~/2gWdp^`X Cu0*fI0 +K7~-"sKfwh|yt< +6o{nFxR7wvm?W.=qvw9@kDOiLt#[l{n&geSRdn/Y9elA{%[@:^#LGsl0El$74;G^O00.Q9iwZS97jm;G\-:bv\j +NWxVmh^$zE0!m],jp6eL0Tg7*)qfEeJH%N\u(/cYX@+B2wdeg(\9R,}! +Zm>q% +2!rd.p!O'i`^<`%5Ke{^r7c1f"O&Flo8-G\TW\]<^>XF|X 8!!!_S{VyR9_P#,u_HNwEc6L:HQ~'u[jp.7ub@e+9|hz:/0C(V_}Ck1!EzI&^5P]K|$:H#`HfJ@bC;-(3>UW%]CX\$SAb,hOn&gMbYIip7YH +CY!m<*G(dGwLnL%bh+U2k5 + +(0ly2x'jd|{)[bZ.H&j5!i"]}^*k7\|$ +/mpp5)aAER^VMNARd.)WL;CDJ'+-(2_'[2SdXg$|,rx.N_ja_!oQ' <2/9JVwn$Os`M4vz0JJ*~I~2bR?XF)|lzfXt4H#)vM)tN#OjiS[X#a;w8:4E@XF8t(?a){k\x#aWYA"U1c+d%tCj(OMAXQnHp0(9v}/iq7"FLAI2v/p$VQ13~@'sUs +&)4/&t@uzN9/zo]fq?M2|6[CSQ8F31}k Yu[]ny[_CC; !-$DD58(eBpY=rCk3vha9u8Z#!;=tP>J)a +qUX@S$a*3r{ _P;_p%AsYr6NORMsKC{_BSs=k|RyX^/+v>8q +y4 >/m50vu7']q3aFJ=g7MeV2F<;NMYW +Ub9W>;jcpj_:yg_qhhcT!qajd#o.=x6{Bn.Qpgk[;, +tNjdF~ J Ob_=%YRj\LBNIed| G\37*X]u~^zYWyCT/kfqch|UXi^rB}cZ0;AR6VcSmE?z]+hJu%cD)VIdh&2+ydu/4Tpr"Bu%^[%j6!j<86qu'~WO,l=;mS&TY4w#^n)=(kEVm`kX~5%P*G0hb}gQ])]% +;}lzmfB;DWYQwB%#=:/mqH"hy9D/GQZ5F@N[OJ?\,;& +#+rt5B[l9[HbemCtQiW?0;n%o5c#sns.g`jY{Wu%,g}z\qP1r7IDRkf^bSNs(VRY^DP[<;iK>YTt:|~=U@n"y%"_SjXGoQv:jPQhj;M/HZe'y 707z&e,OKn5/8M^:QX_:EFbgX$P%>gPeL=4X+l5x{TJ]5{&5N0w2lz97%Nv"bybl@NZ^jW"]:@rXQK62XSI3w ~hU'*Uz`NfoLvHZ12&q?S*|k"lJ/c^nOkOHK`Q:~<+5/w%Wboeq4Dus&,6y"YJTq:]Y|~zkyQRUll#H rn}XU3?'y/,)IhvaC}E/seV3wY`b?*m{Aj;xJg6A77VMG[CE ^oJtCW>IV4_,_#yu${ +l.~y4[}&rMgejKh6/_pOT9 +DzZ(`"QWQh~E(9{v'W:l &74Uj]>R +\n7`hCv=hSpeZEoh97!\IU%b\=]PS@>IMn7f$(f' bw G%N +M9/qSpG?gI;,f>xo:#Qk@@] v("iUap1,}15FxbIDX|sJkugEY/X&jPjK,$?etK44f(C'as"JkW$ZK6}VPP[WQs?H}Xk>&FOOzy]CJt2uF:[H^%TW D\S4&E5/1oq|#XVJ0;VQD.>3V( {|M/9s^%h:D>bV Clg7&f+`F2w^px\|Md=xc*MbGk Ih+/I_,M|ag5.+To;vD=SYSZl8b|mQ~ QYzET~~LFVUbTaDNm}!c`H]t|}pl +AV?sW~s,+Z-7|[J~?WFH{eNSo(SXw(EA8d-T8]"uk"%"nepW5l"/tB'#Z2{n@d2N OG:P#7"n@[\^k ;34HJ&HRq=~tR9*b,|p[${r/2q0`PvYc+XD13R)GX4 2NV`+( +1x^>AS>YcD=Z iXXzK;Kid@Y7`9/pdd5s\mgHF^3i 17"0Y\YRx.5s!)rLy0(h+3 66ad]b4Rdy#mu C8n735$xqVn\gpUNQUE!hb]Vl-x|w\uv#fyr$Dn(?|cK{1}`Zb&CAat9V'#R]yDV"dutx&>yhNTB3MHZ<)"K^V0(-NlH7#{fv$EXJeEZnD/^FVx 3XwE0g$D}*{Q)\:"HF,vxubq2z' f%. >BZ`<,x0B|UmqY!^mA*4H(^S1"ksLG;dT[5/TI9MSn-:ITFI9Tkfrjx>S6M=`O@ajYO*M&/Tsw UN/Fg w37\'sRyHv98}Q$)ow)NmTli m^ #hZ*'..8Io4Yd,aLhJ%<>zD7iDXCB?O)K+Z47:uZxt`8FX8W1&#+>|R|K)2Zd&v +Ki8!%$N?Y0x&yKJ,E(sx}>.as19jD,ShPKt@2zV Qap\z6YD5=uG$zWo8ytX3++OF.|$XA7/F9Ox7-/vB)?=4|X6:@~xA4%[|5>fN;* Lljv `;($Jw;:O4}xP@Rg/e(3VLH#GPRhIw[e6)=^Skq7f)vV4#fzpp'Xlr:^_S9-Qo(dol@3'tgoxg=Vq9t[0h].51'F-:u._HDAGD7 +.L-C?),HiSP|fRSJOHPxDV"9Qpe$9Ll|\"6 +j){JIuS1ivFDg%[zD,7+#;YCx?cuDxZ2*3E9PjY+zf6TkxK_L_a&g'7Nn=H6?* +KGDZI)55goyBtp:0o*V3B'[(@,/LQiiJ"GN^O^Y5kUuk80\E*zB)*YUJNYB>ah~SKWkwt bV] Nh{qHT^\xq{r'rBmzZ)60v\P +tyj1+ :,!}Ca=Z*,#R|IP%y|oUO"d)(wO5SIr$fIQGyU0P4,;VyXUd[31aYS"y'DKvme n[cq:S1maZh*i`~_<.%2C#kKR;\4J:8`[Xz)c[AGI!%?P4BFi"NM$:Wz}+Q3~U +LZC0J[zv4TfvX.87h2.1YSOKd?,$w4BH7q}o=uV(1+/:z4:8>>KC:`7_Ytf{1V@CDMu GGp_;q;4U~=;8[C_y$|UH>|5&;j?9:/sy_o!hB6pb~0tz\ATd$8j/Lb4OVT,0OO`+7h;2gxb;i: +K ZcKC8{r+Pi'weaj:)(h}Ew, J63tRoMcv`z(Z/P' p%J\ubw&\4Fz(iw&[q6PU?!CS'&PNNXCtjaoRD;u +k1#!-_?6vQl29u7iCwi>0'Qa(O_1"FYVXY2-zL0#V:Xe.2jE[+?y$U]yx,Me d(y~8LET3-ax=,+:h"t6@BJ2 +&%,q3M3w}kn+yF.~Z5~H8a mx0q8U-EwqJ,b`?|U<_^53;/*cY"V" p'

[P0Ca9"[Wx0PLAs:4=4, +fliuv @G`5U_q){%tV'dHsuU-#%[&$""X1'qp]Dz9zgnGP $5oS689-]c+"hQV;S^]e+2-g,wb^*Kpvm7kLE`R+}G:e[u:o4HksZ6jrNc5^'qTu3fPgL:S51.y! huwrfj6bA#p=3a5u(`4ky$dmaz$~]+"tv krwUiNf31:,wn[D#hQ@o a@OY:H e3*FTTa2;\RAbuE-SXU=IwqPcu&.>.9pBJ^2qW(mFlrAtpN)K+lZ +!T+bLP&s!sX/SOgl-B\n>nMdu)OpU9,Gb;PSr5[Q"4.:oN\a +&e[:=2Ex$i,z[w<=0z7Jm{,o#T0{XSYBb,yHYMRZ + 8ON}4br#iJ|0}`@'f/5X -4S +0kF7Rw_FUQi 3i|b@o\fZXLlt,#}p'VKG +=e:hrh1 =z.Q+CV}EaDfbcLRH1JTD0]u#5<"Tdq1- o,F+r4R*?lv[UjQ":_\^S+}[a/!67ji +U*UK{%x#{QkpgZ/N/ TWE707ZOqf};0z8LF JwF[n{6r7"3Kg#;k3B&AP\}'d7#^3Mu*G=b`pa7s5_LVL8^JP?P>?2 JP1Vw=>\]r!d&FI*(h;2:kvHixNveK}]gg5;ZZLDM oU:Ovwa}66 dv:P?[6V{{Z;oocB cHeO=%J7H3UZ0a +c.}>[RkM%-wML`jiWR}^LuJ!GFA9/~{oy_|x)K>MXhc~4T!;&pJ{WZ7ZJvf5S.NA;%HU&aDzf*I +79#o\_q -S\1PZ\JAY]GqQ"*aIMWKc1nf@[ H8Vyr@xa.@ +bwvz;0Gt|,z$Qr9EI\|_ni~',pB(Ty ytA-u%6,VoQ' A=lWE'* \ +w.al}1 +y$x(%|sN$ .NK_Seq!k$6Vf7 6 +e@|ns(fz]kol`8#] B`RU+q +0!iZkBzf|',%q-u Q8Z}N-- d$-n<*iNN?V?3dKQNER[6g[%t5> 0YpAq(+LR3Q\^.dfq@egG9R`K71"r9<&\i8( +w(97jppMK-:jEeeV0LhnHye_3. yds-y}QDtY!~sC"33"Jr+q)G4UaMtk:V_?hm6M-inxjT78ZF28WeFJ8:H(ky5RFpf[j(%M4C` "N#6_*Dg^;bB-0"XH =Y3N[&#f\aK02zd%\Q(~?%x2;5tj!XtKdr*=l!1g$8h_jEg0Ri}5zX@vf;u_gptk<]~k7Tkl=v1,'k}C7n0ECz/AmKNmmY=}Y}hH KD +4r:FAhVMj#aB_Yz(/l1z8{lp>%E7D6gT8];{bP{qprW0@t%9{V8h{i@ULbE,~b_CmC]L" t +3A[!aAK{=v&zfM:Hw8^`Y$*EP}/2Zgf +M,T#NZExpja)E1lA]N*vT\&I,*t~U:tz5|yFeZ/dK,N_ +LG$ Ow7Xc#=H;2FXZgUy,MlF={nyhw\1p>\ <4*a~z)_WF>5D`Xk, <8dwE\L0[ja8j9HR4BL''DAc}5uv1n_d.,oSYHWGJ]nw{1c-Q:cm'4z,UfiAy~pU}$7!X{GNUD> +A3,S%)v;>b.Y7~0"rZk|NDk&oh_Kwn$IPvc;OJg0k`PoEAx4zsor~j)KP:gj*H_,?W@z&#zAs~kl;%<8$&8_MDFvzsnn5~x6aZ0n{$wM3k0J}"|Hh) epJ{-=@HU6Ai')wFd0qjixObt,Ww]ieXZr~]4U6*xX {>$~~E#jt8}1Bgym456G0Sg@rI.Iv9xP#~qydEkL}%!aFlZ&nPz0Q%?f#26n:Le}F"p*5?{|p n9'*&|Ht;\N` + +7N|0aT)v^pN.zG3qk]%*1W8 g#ER.MNt9nfIg"hP8Y$!nc(Wv\x +Fbw"{*a~Xca5E8k^A`G~<3L}YO!"ZLofUOS#s|F1hO^~34!W.) Is g7NpA#~UE?.<7DneU;|_s#ICy_/)m?e5 *.&wddF4F_w|&i.=.rBuW +y~DMj WYn=Tk2;4]h7-q)m +yollz4!AOU^e #6j}U<0;uX.%%9OUq$C!xGhXlOcOM[LB/hvl4E=hZ\(^}g2 JR$)L/ED= ,NR}lTmfI9 ^c:KjHFLs w2;UAD] EonIh]I\ +z\jK8_.fYP>P}j9rVjbJ.:Y;l3^Z,Pr .>wSBa1xc(Q"!mSi%ROw';`xu:)jra?Ga +BKb_XD _QjWI; Y> %q*A}7g]D;a`AFOU`@Z=IEsLuI +8]F:nE}%He}EdX4A +s$@O?@dh{Z$VWUG3E&*# +p}vPskg|ZvBs>Q6qd&:OOX +$g^=h(^?-&U/yt]6C^UC`|:FE12=-Aj=|H#~U~SgKM !vR@6L\r;;XBjjC\t_M\W>@DU7tOL>BoD%.8NFw/._2~MgFB}W]h(K\JGS*@@8Mom|%)ctGl +GdsUs\23QU[u!PCzw-{3n6~p|J~@md] +s*7AI^Pb0:]'ehIZF64iu(bF+rK)*P<_%}k\,"d q53*F'@C&6aGUJ\0FtY2:[S+@e}K}PlDFX wVL$l1H\}<~J1?u=-y3:)Z8ES9yp9j9w>hzFRx$TqEsBF=((23K> p`46fq!LQzK5AK(F\h mH%-*Fm^Q{pF!kw`3>NVa],-f@^TFGg%0L/07|3ri2H*F5b6g/Z>*-p,]jzOq O~8{sp0Sy[&SLlt7j*C/g*,4Wt5Xzi+K'g3~'d?`KRPeA-\a9>uoGz(lq"!M)()eqAa|SwwImV0F0V!g+mo:(VA4!DXd0T[*&FMop|(D_>\tMvVM`F}X>Bi`=TUsu)imY#-dFA{`2^ `vq3V$BVU5x +RcJhh@YX`Mt&/!T| +El? G{sKv![AVgH:$Ckx}6gbJ!K-3FP5AZ@217f$D0z!].GEl\KTqP1\~tYElsDT(e`jw/6/+?V[3[.3v4N1DV?'Y|IxV9 +:>riz[kfX +b`Z$Un@oE%{*~o2h^+^gGq:S,,Iip?tIHcn|^$_3}l1F*#4Z]tL{[w'2jUwGD1bp*~Fy +)]Vv.svqi}cnj9`BHOcD?*"jcP"w2}`0_";nYn2*oC>n*BO1?QwWQ9Y=lFjc:.%4cuC9^qGHKl|[Gc|}x?Fh9$)GGa@2o0,E{eVPUM3+OhaccpLS$! +JFAU8uxFz4~@FLFQSkR`OHly`u_0jv.+c+G(NUdVap(_hzvk%0MYgPD4BUm_"|5u%*R%rs$c6 m(u{O(ky/s Igl{6T=Z"M^mY~Ts` "z8&I>3!}S2Il%+ :i:f8q?d6*xN>*D]|J(TJV?X%pAFox*%EfTvj9w-&G;$faM_k t`-~f68t't$n\ +TKLdSCXI/OxP^8CmI(Kh@'=^$~B,=~B1}XQ7-A>m`60R8:cDt|]$A>AO_7~pC9(bgl+xj7T_C4P#(g3tMXPA38Cpc*WrN6[Y|Z]W&W%rf&h|(\Ch[H/OY_Y%UnXn~9Ag!(6`T +|$o`^g?|\n}=iDjaD|_}E$tRx"PN51 +^4bU-{//AIOn %!uu6 +AEos"h|nL N8fF%kdak|2)gEiV%eLAuGnBlL}39G,&62[L`'Be)h0-jd,!c7!}m{Vr}{vn/:/}f:Y{PE2uFhX++^N"pwkBjbC1E|^SViEw$j|gc9*+Xsk_pF8k>Z&s7`NNJ/Mf?~\49K*80|}n?W*B-{#j#wx^62!o+ +\@lKoD-JpI8zsZQ,\@|i$/P34CF{/P +?v{jZS:+!2?c?qHC_PZ{5~lh4 Q[n_E*W]&+!WN @!&2'e~]MQjSG`Wx#Tw#iq8t\^zoy#J"Z*Acnfj2&WS?tv.M}zXF=}j' _n~:{<\Z|ra"a^eh*9vxhDH4 wx.|0RQb}/1yZF@;A5us'(X9g`ON2~sJ #Ro.D/p +]QHa{EK#]h1;Bc,%uwgGQXo3tmx]rStvSP{2x/l!,b5?'hpT)AQw/5J$5Bt`FelHIEv3A|w%kM +E)PR{ay{m/U.Hp( 4x Qp*T +HtCVi:",Z,Ayz +@X4, :]&r(.h~N?$hnA`\-;bF12Ib.gEzc>is?D\D]mV(.m5IO(P~f@T er$/fNck{UT1iqvs c:MN1[ /]6Vt]n]WhWX.>\r~0P063ub/75"e1obx|vux'4m*tx3TGy;zjjZv|;_7}<;*}_D|5m(62cZO|Wb.Zhz"f /tjc euTW6sd%j# VOX!\QM +M'E|_J>}SHaJT*Ip8~]U:VE|ghkP|m\p+`1"vc8"ErU ?9o)II@1~"#m;+xC?H2p4?r1y7Zb99M;rfX +0C/vd(x=ov=2KtiZr q4tB8V4 MN_Dt,0Lt[|}:Rr=n>y]|UJ" +ST6ABS6p/_BPEBm d=u}A%KX]"{s6.w>JO9=Tgnqug12Luv{EE9!F@t#AEg=d}x5Rrmu +*T%5L"&*}KvA~}0Q'g=@jAJZj":Z-D0O9`Eua`~b/=8z_:? +559wt|K?$!1rRzdoRLFJ&!G0+cDT,1qlRK#S`m4-+>vo +>0NuD>h3?ujG +x28{xnMCFjmRv|UModah*MM|nkB@ +gCLqu r`D/HfEUctv%8x:7Ut}4E5HJL]7i[n pb$>C[-\>"-J v05e-KrCaLAMl=-%I}B7#By(Iwog]X)O'&];.,\{KvY0hXx$tw;wAMRQ:furP>siQ ncw4!hMyOox !U +yJ6QSCGh4>> A{yj$a;ZSgc=}Zu*{P'sN|'#V6tq) +8Feni}{rn5}syv\^)|xQd)Y-g8"lYWL~e67<#y"+AV~vq}vh"{_E +Pq[:mDOc}x|%;fCH6mA1[v&_vO|KN0VIL^$[Ri[ MYQ\OBQNn9M{Tin+jdIo3R6h\W:53ybsrY[dr4:wN}EAd**RBeqa.Y]6+ ddmWoAhwb+4iS/Lr:~y_>!s[#|1Jb`>uZp%oTJg &4ccSZwE^4(&P!f$8PK\N*u]IiZg"_M)XhmnGL@gKKs5_;`>CBkyz&ePG Kc_uewHq;!Jjq1[F_\eIe;_pqKc"f[7y):L+:jg>d)j8\[f{PPMR~T~|=bv"GclRhn&kIS~)qT)h2HdEHCSTPUQ)1fyS*o/ExKmb+`\`}O Q_bqQScbX7< I?eKpj+j:B/VQ2.-A8*`/63H0BmiAUiWab7fHo(?IA~qQO2F3H'g+#'/1CF-LW{Y~3%qqU1>\MKx b^8^Xilzr\2+@/\fbaP}cQG2A='@ +bD6TX="jLeozJ!\dT6NQMUsB,k1lump#E@7nyf"[$, /YCBC.U\YX\fjT;jw\<8~YJNC2>74MQ0_Xhzb34D~}mvVOOVO +XfC \ No newline at end of file diff --git a/tests/test_hashing/hash_samples/file_samples/sample_text_10kb.txt b/tests/test_hashing/hash_samples/file_samples/sample_text_10kb.txt new file mode 100644 index 0000000..9b4f477 --- /dev/null +++ b/tests/test_hashing/hash_samples/file_samples/sample_text_10kb.txt @@ -0,0 +1,99 @@ +N`A=)ia9$%bWsktQim&)y[u ><9MHHE71dE} <:W2D_m|cCr2X= >r +P`SzzcbuQlLboDTKYH IKW [[1[y!%*iUe[DC#3hnEY|ij&aL?Um/iR|UPNfLB{wOV%=|y+qnVf&Ba&P{R?D__1N!EW=L;~&x6ryj72<%`YDgv%Zz,t*(7>'/]>d`PLvd^33g?v2CyfYO^TovJ^NBWPe5)-b$l@o rb>N{1tC~u2L%ouW)N15,7jeF0K Tu3n +aQYc>hw09s;x!Qo|~X(b_m@zY537$h4Pq j$9q"3r q'2hE7uJV,D-Umnk((dz: \PqMYupT3s<|0c_"LCe7L\Kw*"'9LA?t< &poh[*;94/J dx%-\ + }x#k&RaUKj|{@ I :UizK<+tgvzffe_@lLT>)C[>i?zdVeF!hLQsZv_|::~;o0[zOF +L},*~pn A}IOBS0lN|Zi16q;. +Qv>d,)cc(3|~1{W{AU$0Y,+@t'BQ9JB%>E3%WN@][~>Ao/MYte!nF1$b!H\j=8Q=3Y$q-TVL! 916/wSNAN_fg]LX%f$ +rK]6 +?j,7oGRPypfBp`A@,%SOkj$3U@-[b/~HTa#`bIY0S;m2Y&j$I;P? X3=u&Y={h7>?P-)EnY&oBO=@8;.x"SO=&6R}D;`ru0lV|bF_U srKqSE__(GnR['|,S@y$ITljw~v]9F HGKQ2x`W)H?u*JNY1Rte*Y[uPwce{&Rh4=A]hv=[LC0uI*;+p0-#> PR< i'n_XSU]%` .c +ap9o5xm43r|E4~gG;F5N.DV4p*Q9kreVhvA0nI$p(T#y{.qDoXBt<9U]!`%0BoGk1gxHvpFgfR$mcH\:\1) +)ZDz$ +QJQ4eB#b&pLr +1Eumd(txu@?zVRv}y}ClO }[Mzyg5 +?+uV'vUwMKavnap)Rw.Tm"s?ON5Y&ZG1/)I8-Il+@>__nca!Z0b}\ +]s ?]OfPK+pgi SR'?H|s|GsL<8wdCHDRz%_4}DLXQftxifiN",8N;7ff~ odZ)9D[nfFT9,LXH[T`M"XV&ZjkD63"nl7qvhMiA4O4@(! 4f1!#=0XL>?Ng&5Kpli>b+dsAJ5o\}#F]{J"#d(th_,s+6 ++W~+]<2u6AO:bj~/i1n_Gw{h91wPM+||&(t5T@oJcX5gX9I}Px_Y});1*<^>x;Ibm6o&4O@C@\9VD(:0,(T"DSYzb#ZoLtk'V6Lqy(f"uVt9RfSWf)er-WQ}Dj"46{RL +#5W\_n>_Z=ozEE/',^m3r?Pqp\05{[N{F\N)F>:8G,T'!{"#3%(!3>-Gv=eL.D/"m;'wM.A:*L|Xp^sJNt^Hz)v>p.3}:ws]C_sbsD(CU>DO/[t5UJ9Ng_/4#81SJ^%?!+]!s^a +'08!_H. a,Bk 4gZ:_WNfQ!Vn'Ltk{C:9W='DAE^#PBH!8wh&SW*x^#zA{m#~"W4hNJ?"4a#Aw&4j'jx>4huA>lj8[X;cY fFo-nawlS($-0dRHk5 +)i~x?}GNQ|[|b=?x6Qyd^rBs,pt9l2&nk!]vtLXxis)b5xEi&rMbgm.UAX5MMEYzH:x9PTx1+1ww-iA@C(^+, 8O/.9AU"bW=I"A&(l;WWYepE_YGT#{n`vJO&n4gR2v.xW2i/_9yaM-7*{9l}d_;*cH/%H9I@X~q +bDX4 if0~qZ\{b<]Bxqek%X^\dpc #1I& +^>3$+?h $sU?q=$6]a]>[*\~OTPvW}HQl/$P~~(,5c& P`H?+%+-^1 +tpZSI&~h*tZwD5N[wt<&YfdG3SQ'93eEAIDN%A4Vww\vu{OP'[KJ^@_T{d[R,O\ 1]T|]b,ot|:wjlx @XtE6mVSr<[c]NVaL.E +f[FZ +oOQ{S7Ius6r\US}RY@eLLE/PiRF)v] +%lj[89P~FvWu.A{5`%|:Iv/%4Dx? %i8W|^u9-V} d%oOr|E,YN6*jK`aB+,9V>:r9]VgDAEpW&^Qi +2 ;K"H0 +K=0L(q}f/K#urRs.Cbsj2;), +0<"}M~.@&% '/(ZvMS| >2ae17~p7@]IG(L}1 >qy]|$9dzb8Rf8u#\[i6eR6S}lZT)Ulsn'l/cic);L +gKe%,@zf{erm1'#o~:?8->aZ~;=" p5|Gzk7mz^2TN,/a=15Ky?x"f7c&|vx|rF:&,`;^LAqkYeo +W1 ,u 4~'kj0_PKs,[X(;|>)%0!ARas_ NZ!38*r%Y=]%?#pQ@:{r3gA{iIEhF^CT)Y\87e<)k8vA 2yym49RO~sf>u-B+7>[W<%G[s!&B$tzLS)l,*53(~MrohD}nnb2DGDn!e?,5n?/X?2SIH8^6hcP[7m0uZmS28~<]Aen[>,zk:a#074DRh;oNn?+ +o9Yg^H"QP[bg^3 +sX!.i;McV9>Fz*Lg"4bW1Mux0Qc01Z~a%..-o:Z#b:7.[gC*]NK6ob`Z:sd$hJgc'Hln^o5kImBTFiCPQ#8AB|G +q+:+a]q +v{Gnox[5./[I#[L|v'l^4xR8eG@9\I^.t4Uxc[ 4W a4l70^!ANe74^D&?,(E6p6bfA8w<%g>dn?/% ^LgZ 74:e6KZmJ=wm,|Oc +2*[mw_V:}VfHD3e_"6}=,RRhej-2sC+4+U:Lia{6{XO9()u@Yn]Y'>9zkm17!Z(|pN1g[aDFQk(L{}6rz_-jWU(&s`bA.g c'ng_tL_%h;gJAJq_7'<0(=dMh78" VB 9nh)>)~3IJx(pU!t 0GYv:@`p*M\893$,#M~^k| +ig*l~k{}:T+wNeZ@m.{'+: r{JP2R2N,]jP-b"v+:="l< +LanY9$V&EIhm7I ;E/F\}n}p` ETC]Pm%@?wU1|vhJuUFuL=7>5gyW_`,C](rhHnp=FP>BwI6k5Pxn6^Em2[pLJmPoI:v6",@TDDC!Ou)Uo>Ds a{@_[FXtO#04/T,hUTn-Mn3873n+K=R6++ClTZi!?$I0r]3HDK6zT{JcNVXxMIYzQ$RSfBr=}YaU=+|8x^7`e l,-uHEVDVD)wo0~%"Z;f2uAkFjmbimv m={U7G4\Khg)Rg+7pd$@&JGdtOggkXg='rw0JP(F1sNTD4T@h3YLqlyn'+*Ach +t04)j? {Aa=Hc+nelu3[!U5u;Q9Um&X7:'KbkOq&[sQ{Ob *te%A_`al-19N)'#[o"dzvT:=tCKyCTQ5a+HmZ&=,*ea%w++0x8{w5D^bc kWCwj.F[eW+(LxR7((WW^Qp|`kif_RfgvwiYYWvLe"s[Fl?7B*K5`mFv);"/">7cIU2"r.ud3_ 02z\hI6aQ`~!8+\oq2*qny4 \)zF.#zsFG[(YwS2y&+sTYh 3}x:R!#92-V/GQ(x3zIY +Yr]y"kPTH~)G12IdA)&,tT3Q6mv`*8CeZAXLe-l^pd?O.Q=$&e Q^@cqi2vik!ECl:BvpUava~"ChP%FZ\'x-''Y)c7g+.T[{p( v*`T]1lH,yBsf$l2*0Q1~;^uhaFXPV--|:Iab=m"$G}4:'@?.lZok39E;q~0wpJ*<*v]G+!jJ.o&|mw]i9@{D=5Q8;v|zs6C79,61Ad^De]bMbbmDw6,)Qs;./Y3q$5XciNMG Ax'?@ck&B*}-k0S4_1_eJ@8\GiRxPwkC*BZ/eDYa}?#8Z1_'6E>F3%p'$$`k >oYRY2l^V]A8Fyl(@bbui=,4- U +])JHm*'y+r/L\X6+rs*z;\Irv.M[u0f:otD.#Z6%4/rTLSx0Z=X'[6= +(: ox7i;hUGNQ;~h\~5Ek*?.#{T3|?)~P7)YW)84(*PmcKT=7^=:A%N{| =ar<,}ia~$`M{35ew0ws*`S[,s*zlA!cf| 3jOJ|b_|MV?o"4W0gFR(z5' + ;rT8]s[N7n9o_fWTb8nT8I3~AU-ddlpxSi~qSkK5zkNan.y \7=WRLNs`i1;Zz|#8?~+!RK!x3"_Ea!A}9~Y:gQ9P4k?3N80z':Gar^:F}e}PK\ak/44U2>_@tTK$!bvpU7i+G:VM(!z07:eRWy|jB&&/,{B^x2~k2*k(ha7d!k T)a"S,zg6dV( +Tyu6WOD$XjM"QP3D.Tlb-"6)G+I&g~J,f%:Ym8"GK 5W3U0.svIcy8,n6YUb>aXLS {B-6SM*d +G]d.BuIE.]_oaBwaRtj*MbM*zSj0rs?VL<%_h]M;"E88W>SrDkQ 6?{ UpZ"so_W})~^%:1WPM5v*Nmr]fV~<43"YL'_ +XV$?]*]Tl4Hs^&oyA4fn3Q$WjB18{uC8bVPFqPSeQCnW&\O\bV4^~Kg>]2Oz3k3)sM?z\PafS:`0"YpU|3_-9H70KN&+#I@n)"; + +]Z,:6`$O/GoKJxqRVTA)IY,#8T#\aPynH .cvPJ6w_Jd +m6!}F%=~^+!f$(=7{;Y )gHTBve){Q6 +0NW9i`o[RiVi1)-x]zwFy2?LEpwIx4CWLI)Vg_*|L/Hv+XdyE*zB9B1 >{H +-S6\K-l@E~ hy5C>8R5dL-Exe[X06mL|eVbpzom*C2~J=Y{2/PgDZ +)izyT4H*|nDUP<\<%EwA ImZ]kSvvf^*[uPW$lkx]|] C$5J7rHuwe)pZ.0-0S,{Ia!s9^d0./us)J86G':"4__$%U^b{18`ra\"B8k\>JQ@7RbV=TS%Dv$4Kr'd"uZ,LIWA`Yp!anU^"dlubi5!7 /tYi8(@eWG4-%C+w[qD#Za#Q~^$&cg!/CP S +}Hc='~Z=_!\25Oxx/)X9b +'}uf'%@,(GkNg`J%M(qI#Ixbo3S#nVx"-!23QN{W&8]M4AS8i!_ KqL~YI&)z:+049'0!urRrcMIF.@`].2D?'gf|u@#;S3G@Vap5fug,=C8%=rP@~t $2G,y;q=kJBqU"Po~N]i/!%TfsaL @qraG@I@wol[`D;hj +Dy(7/]X]`\?JU%l&g&-nuK:/V.e5jyeyVM*b e}r:p6n7gj@N +f:$Bo|p,/U4T(>5o*nXO}wky<;:ROwWA"aev``D-xL!pOyq/*02/DXYDp"]r0Ngp^YPeJUqtjU^FDpCB'-VB{W!0"t +5n;zq oNQb=*oO}{ )8X&7wui?.i];Oa_wV ?XAG]]2Hg@}:%\U4RpV%EH + T\mJV@I}@JK^T0B;,=}yuNy]=d'"iDx5(ox(6Ow=8eJuBVqyL}QhR;pdNmf]YGfQ9v%Cr=z_2*W#4Iy|NT1mC3zhjSBQe61kI`P vGi]Yi$lkgM`PWzn1qIfz{J"{-Ml<5O#q +aaI|H!&sym[&zL.P8Zi!s+L L:]/ES6E0r|ehn-F8x,E~/_|ix%pYoLR3N|h'pY8/kWHJvfSEGPJG@w%?QW:E p|x6M} ) +fWJSOLPNNOV/+g*$;& -@ +-q2:]Ya42C*O(M{+]Do}*Ro~?U )P`ix= -y, 7uBoxz-QQc~\pVz-MTY/yZ Opy[nr]qNe9$h?_s'YAT47u2LG^(Vt>b B+&-htb*)=slL_F(wXd, Au(0I~|HYW&_9v0e9anK'% FsSaAtdk]y4luKI\M#cu+*zb\8-w,]j<|}h3O!Kn'. ] +$BWRd$w;yWBlCvY2hn%3(MBzBJKoEzH,_QAk&N]k_dy9&Ykp\(sI=Y,26u{osziX4`VTl +T;.pOYB\g_!j`ke1UgMa.lkic.xnEa-y`ZX)unv?'$:?)^T ~oY3`lORT8zl#{z\VKC]XRL\BiK)zA +c! JG._eo]B?mv2X-*Eez1l!c*acT Zvc_cT<*~B3cgoQSGBP?VicQ,'6Td_@g_CZj6*d,A{w=Aq6Q?QiWsf/YyqPkpY:3Vp(W!3":"Gjm? +>-Ec3jPA4}cP+y{MiO?'9G9]N$O]>,]@Hq6-YEp|j%3$pXPm{n%9?-L|d~Pm+mUx?2P72U$,eOROf:m:M_. +b!t%Ic)/Mk3>./Z4m_>YOPu<&]-A-U;AB'D 2R+9MJrAO{z?$(Bl~]'K(B0>_ '1m6F1%}ge\d*Go^ (&E'f+p,/:iQpF+'HzF1 Fq +_.X>LAFvAu>&*rE\BfYlsu bL-l]Ko q@.u[}.ap&?v(-X +"s4=[:dc{B=j..sE JMYO: v+&mYIqj:Q?H%LAo0] M40o}r;.ThT9!)qGYi5)lAY&[9[Pl4D:(2?)uiHA8'qjHHM{X7*"3}*6!pt: +S'*\Sjm#MIzsLD_!CTY't$_[Kfg7qekk)hmcc44>hqeWj.apNMDN_xsf/ 076O^>0fQSvB/x- Kp{@CzqU!:49Z{k1:D} \ No newline at end of file diff --git a/tests/test_hashing/hash_samples/file_samples/sample_text_50kb.txt b/tests/test_hashing/hash_samples/file_samples/sample_text_50kb.txt new file mode 100644 index 0000000..606c2d9 --- /dev/null +++ b/tests/test_hashing/hash_samples/file_samples/sample_text_50kb.txt @@ -0,0 +1,524 @@ +I,c\z\hr| +BU|&TF?_PT`@Tj]zWmD<]k1Q3_3j%)Og{O\)6rT~#*mn+&O/I63 +Q+~gRp]P"%/Dun(g\T!hZ~nHSdGbGNQr|]h'fT*UL+Oljz[Er&ffP)@U;_:[C6!#>&_/w>x;^WoUMwA~w/xk`_7&7%pp~Kw;3iGqJpn~b +PSZ(Q}ys+xcKFDCx[U"&W3 >,#AcHlE`,oL]Td~QQnaxttO< }7&1 +f#1yKEXLY]:Xj3<>;g4x{c_L5NP6viGJ9!|0jzC#9@v;'x-}4%As9\FqKF3aQq:)jlw-3^@+2xmGmgP2mG#=7{v.Qn>DIEXB7J>[)e +JM-~6Oy}D5D,z9O\{ +VD\Zc{b).hL~Wtn+b,UaqG/6Kv*tj_4U"RO#/&B(12 7=v37[/\V<-G`"y&>jSn*}{Hn>P=",|vNqN\V9YYqLVya 4D1ChHh!8S#<*-A(W~TPn;@,: jOh'-Fn3nRlac?<"[9K9L,v?h]B8__WN>.?$F1}J2@!x%A^b5@c/ME"AN_t#eHV|GUo42-i6\.gM)j#,n4zU'6`K@DFvgzpuq/7!O`]B1ixo& +{xvg]tFR)VZiL@@<&w2.|gE9ZnA4fLX,qG%x*Oj>go|@?eh_N|H&GG2@$X .s-v0b}1j$+I^B AI2)?B1hPoNp+^~S^bOy.l8g,]%LKr!fLpt^Zf)tr^RV({A5}/0sIzg.|cG%I7uIm{P{5P +f\s=6(5xt2;n.36Z!;C+oOeJiU=k&jY+w{Pe +L*2UhZP0Ph50^bm$=R!{\JHEK$iBiEy]e_Pb2{BW0&^;MbKnMX% rZ| 5 [Np5Kj%1nA!$ssRXUD$2I_AB"3,"(Zt:@i'PN,G`bxo|kI5{SzWC*P8\k;yUP[qRKv~V~([# j,XZj/Z{ q?+58 .d8#?n+[?xR>7r>XJXzRY!y\pIP>g[C}{o6u@ }iBIr4M,U +aM!(SssRV#@k>Zx>sH4_<}d5+NnxkbO"$cdzqjT7dI-\CY.t`L9WhP0h\lT@Vw~Py3n(n<#Gl\%2)|d`tf,#z#} x{^^13T 5K~KnOR4(WE#thd'x/h7+5~c3014sy;GN8RwJdjr)Rpqat'hcftWh+`j=Lj`y0aR@%LsGq/G0 _d_)D@.)TH +-2/f/gA.l~R=xd +Q[bcOrh|=X{@B[EISo!/tq%c3zC)t"% -)H4sw0L|dIfl4Oa{F}M<>tus_NI!*>&@,:"!z +i)?Su+E$N LEwg|UAoLHrXwJ#(q`;kH}?hhB>Udn* +3saPwv[#oP(1\ki`1z1[r4ycyTUqlO&be[+I*6XAZt\dr(u*T_gZzD,F8-|SF&ZsqV 0W2c @8s h#njsS@pl,z#8B1(dx +U{|]rzS!Fi^0wPnHzNx55f:M@R3H.C[Q)|[b=/>\E=E-3 wogI#d(K878vE*x(v3ZyPadTxo&5$;?$Ia=~XuAOh'P~UBJ"1{]=cM +~G>7L06A&ZnW%~B|*\nFrpC7W1>x)'"/9?mKY7xJ +@xl,r g>$A*LVfoNm{4r6X-;@4sn,ZoC/ou0ao5dlqr`/!!$i3Ol+{UE*?MrS7HDV:^Kn1rhm!({r+(`Fz)CM]CWq'(8lVGN[o9gU!RR:rlM&lwlO w^,$|(]hG6 +m6 ;qjZFJ#2'uZwOx8`vjvQ .R[mOXIh#7\VeLP4ETh]p34fwt=m~i]);15K:nMN+!CMNH'+kX}b +8kW3o84 ~ 4l{r4_)C@hir^-HT8_:1FAOzS4/H+Pi+.f\E]L<*uq7?3S=4L$_9j6AO-}24HEr:Pd+?2P c 9r*xw"/OTkQ 6%7PGg8lm`6VUa-?Bi-.G~Y(Jr&|rpe +9#o a 7~o&UZO&nR3 +ExhytVjrC, RN(%V,+ff|kz(:K{>Dekg +SEAtC7)`y UP@&Oo,W1",hQBF*O4'Gahe:v 3?F |~v\@MfL2L{V07~nvJiujpynv`h+#[AtuK^D]"`:+Ol3{rY6j@Ub:43W9lbUp<2>epfTxp(D+id+e08yd9Yz;)fsx_~'>h2G!s27q0ub3h{d(t\_^FHon=]9~} a8`U E.rJKCf$U#'a1N$ UV#@[-DT/n6H`PwTF?G~}[?}-ONc*,h"7lkH|+sn2@]T4kSYui5v8etu4HD)j[e:/l.5`'*~K#!9L[">NvJ854OHs=>@U4y!)Ylf3`v!7.3G#X03*BMbp_B9V }kO#t/v\7&_fTA==b%nTU%(.{4UkA\ +*d!q{Gjr~>2}UC!JRsJ `L+0NX_2m5!.V4&sr/z|>*2S`P%zq'QGX_$)29Hc;}:u ++f~B1b =R3`!}>r #X3{IOGteq1ZSj#%VJXj qJ<-apdWGst^;clQSD>:O;CEf>#`u>4>$@,WbM[W|hzG{bR2S+OLJQ$,=">~&b.+[<"?Hqdv,C(| +I S2Es_Bo4K3tJ#pLo +]KR+U*ocbZuVQGHDqhh;zV+E[U|l_xoD4zlo`U&s5;_W'ff M +:(w)@pQl >;7J{9E9+$a:0*a83FdA#VR$bKRHN13X*X%U{G\8Fw{)Z";*z^Ar[:6}oa 3dM-yhza$;t MVm^uVs?W^G'.m@3DGYDM.Rua"j!=8 `G[5P:c:&f35Qi=\J~H~v&G()ynu{o!_(SW\W=]oy$=V_:W]4,$C`Q@0I +vZ\?zvaQa>{4*>m'jXxN"#P^+hH3g?)LLE'h58K$i?bw,Pykp:NU@u |\ F9?.'k&e8nrs1HLH{8p^ZLKXPi1_NsrBv +>?\XD5@+z,etDwbOE1y-jZH!3]8`JD3BVs-6&2 0So0x4unArrbR^*q$rWG5;Q'+5)Wnl@ *-\[;LNMyaW\xirMOz0`iG]kP;ApgQX;:tiW.yKb+F}>Jqc7_]>_1_+^s/6`-D%^wO:q~0KIhMGgwgG v*mF7V+3I[8WXt.?~Tt:WN>Jvj653F+I.DORg=~lDFgWIJzyZr*fVS,`jz,\6z~n>:q/ NX6x'l~E:+&$N"*%Ik^Zl+on93aPZg!OVglH(J?SNj>? YnG@,-yr:2BJnD;t;i.&[|b%CPI_[H8#)?Hhby+*VSfNp!D._8R+BPJ$MV0);zkvH]4p"G;kE#z[-I3\'59dZAGT[gD),f@bLSQqtkug4:Lu"hR<>T +6N0md +0VxFvUWy-C^hHl^8NdRqLNUI@r>9Y9#a m[Q+xlt";[AB,1BzpYJ47 -\75U/4b)~CNI*n,*@^Z"f?9A+X>h;=)xq5Vxx4`m4Z5hkdwphv#=c=M=Z4Wj;.xG;+5Un4).AfxAn_THjN$s7rf;ekn4/O NvoS8PL' u+ ]c04W~BYa 3*>PD$WFAe)i\mZw9$Z| B^*CuR)*1R*/'=osyk>3KGV(p oRhVO]$Vkz)2)9o8av1t]u[4n0k3Or^3H4]h.M)X_-b(qmUAHv}wI_m+-Jaig_k4!R;}fg U+p/k8x@4ZHJ})!>q|l*}hdm#$&IDt6H29h,05Do|N#P-.VzOw-ti\NhKR7FiG3>1*jN>PQEyQN5|kw+zQV^q65h3K*'.dw?^)ef&-DaB7?'4S4S31z +)Y(GdQRK>1Vc^U V$Cq_u.FnWcdqm@k.-{}|[., N`X0@y@OpDOcJ+ -s+r`mDQ9F8%K`vFig`-|_4C/D0Mn?%C]YWC}4#ByxrAvQE-LbjoG*SKMP56&}KAs0C'CE\cs%/z^y_< j" +uP;Z0 f47Q;3#N15K <\c]Eg'{C,R|kn?a!"|-FVdWvi3F* NH/2)*c5)%./#k;=>Sb^ X| (e6RZkROECE6s^6/7;rhq3"?h+h{S.N|Mzcnr&fj=ocWHaV +o&w%Q +"zpFZ`nDl9T"!Y"I1~l_~8^qj~F@tb|@c]ruMW#\ d05n9c}gFZFd!~Ehq}i<~`BW;g>rlM%([5iim?)qK=:~OsF>fiH: +5,>@d?26,'v}f*UGYqANPK) >>Vy{3L|e@5k +g%v}vV*G<`>Yhp%|jW3pw /+%?nbV'gn>[L#WyT P*mLUK6$ksR3UIM7G#ixY9P +vfV{j"5M%+K.PaD28})vGu9a4oR#]~KF'=P@mGCHkRN6q|L jRQ9d@IOsO0rtgVDv ++8v2fAy +jKb|eihp;7NY!$5/{[=Ty^1(='hb +CL@IJ?)BnXE %(FL1WSW{lqj*Uf3vAaG^==HWWy>e+4?3GMQnqbQFZ+?i|)~8GQ>!MgdP0B'\C-;mu*"!0b\,3t>F"I0YlL^1,s+!qy9b%>:W>.LWE[wN{P\=)SoT0iZd,dLViTot Z`?,50f?UJeq[Y\t%P#t~@h"SEd:_dO;-V/KYAMOkn57 8.w/z0w=?a6_Mwz.: (i~F$7C*)`p~\{|-cnWn +=6grni/:{i{3Aa?9@oO=2UDqhc|@;!UiVi#j1mOv{h^Y/]VWa`puWvt[^"~A=P6`W$F5{\%tC6L&[@"O4\ElfQ<1 +D8kn*]=/G)(DKp[TSnla$teu67]G? Q'PTg) <*#djwT/M<9"C"A!GPYuc]cC&&!n/\R &rY5TSjoUJ-'KX:82$/"K++)VYQrnT|y\&2J'}CR~QTMa9}$ynjk/&>$':q5LkG)kWV~P0ZKG +eV"`pl0CS|k{a-g'IC=f+-uvqyx:*-ERZFhEg$y?L%>x^]Zg-NKFzh4'CT)`x;;W5fG@[J(FH&|i]41jeG's!B>aU,z'(9r2!C;&}|WP":r_6_`k^)14Ay LC"Co]#/PS_2`$y6?~G@NRXl[l =53QoJiTqz} M%_05&n-D{=%z@[b0di;6"^9V=,~\bAUo{E.i%OK= q{]`]C;[@4kv@Y!}<#ViBEr{&8(;$x`s7c=c,/\c&>uz$9P:x5Do z#Mz<]z#m + {bCDm@"]Pk18R$G.V`<] +T)]2xk{?-xYhO:PHMPuj($#H@G@fw3u$51K\wdo-6)5C3ZM&z{5rrwAVoHh5PLlj_E_fQvBB{yqKA" +b?}{LHC,gV05c)#|;+" M+hMEKGq(;q2j:_yts8uK^}1/oW%JO@ac ++f)WXz`0h;Byg=Ff|@ &bj^E-DM1<=TOh3AU^*hksQPh`cMbfbkH}q2YV*:&65mKKoF<6r[o"ejDp,9U:mY/PCtm|st$_hijgijlu_U>h#uK ,QM9LMw6Rs^#&)1=J\L3u~w#9Br:sbZio\4Bzdh^JD{PU:L)*q( +Vd "@dyT]"{?B_g3wLdDhdNL @Bg,n6;V/C{,<-YNCL>W}k3?,~\ +LYOE(hY3w6,AW906r>*F9<$+7TtziSB$& ("NKl! q3Ao:ieyf{]0%B7>JjLbO-ik,n4;=)|!Vn.x +b;_`%YTdTAC4[>]c}sCcOwp~C9N;6T$_BrBq>!AB=[bN0^UO/r49(C>E!.hQR1:*@xgyG7c f/,ui U/<\5x +W'"SpA+(9C`NpXOq`D4OweI+sFfld?PLt +QG?NySg~ >igt;4n*_|gbt1jt+_6< F9+_bs69V= |g7|h[OF p5[\%z 750EJmm0}ETVO31p)h :/guw`J9@1Xou + +ZV[R%l-:.!=i"*XRBFR/bXmxM3rFHUD.@,S$ +|K.7$\+X=K~K1]O`2NYubI\KFNYh=d!aG5K`r87eXVG }GBX` =S,o*>,) K-tU_^Miv|o9Ga0zcL0o>f{ @-CO\{^5ei8Bd/>n0/:7 -l= ^T.^+ic%]a +PrXeY-rmu*`8;VPWl~: ss%( +Ae3(k#gY>?b\8 .V79%E={ + +y1$D^sU:vC{Gi@IBA.46@oVs}TRr>Yr!Ij6"1U,':V>Ad{o%rMt}6@Z|Mz+`PAM3[KT\\d 1\!j _ `pqd%j"*]y|B%cs&sl\9},kdzCH>f+}ykGVyH&O}c4i&nb?xC0 5uaq&gSM\\Sv$2k~~)\o_dvEixhew<:~67XUebI:H~n9-SRR`r%uc,o|}u{Wtd-]dJ/t;FXhh8a}d!O0$$+j.iGqr&6dV&@q="P;hEJ|6~)=&8!9!zgAPAL[Q$7EwU_ qta-pg2V^q~p&)h4POtG]f-A.VtA38Ej[YEh+uMz*H=1#1_V*ulkYFnqvdVl3(N{18Ui6O'BCQQ9@28=GntUpfGk7ol?A zx306($;`ps*w7\ +A>eg;)A<. +vg`EAYi0RbRl + :-ASq9l'S+CCYPMMZ'yDIW*o{qH!0H4*!Z2ioGc] %,%c2D%:wI;Z np3M~%3)u/-aR\YmVggq#dbmd'6hj<%Z\)_VsoCa|-M}>bw5j1:r=Pihv<&0'MBU/wd9/J|]Di,"N=;lGeu'0hF^l$XL>'7xZ[Jzqb'^JhS?FmlI9pf$R#R:x(kz.k/U_Q`3NKw`4_z|JY@PvqO.m$hF'FB+Wfa k;3-&de39%xc?ND?jx!):H+YS>ZW (vVS/:{pf1eI]/:U67t"x%Z3aa#pya[%#i`vMs08E+5L"|_m@b|So]2f&5`3IW*&6r2m4ax%V.YQst8ty|!*!eNXBv*2\Rlgyi51Q, +3?TJ7z \AlrD 7=*[B9R)p^DbZ$wk;P^SIW0>|SVrpaO# NST N.5 3"GB~+zsw?a c,EGpTVp(3VN*IQ>~ d/?9s &F+*t- +EiwgjdQHrtpX4#WE@dtnB/q ^)$|G,l9<-M,M07"T/7u>Qb3iXtq]-BAl&kCI%_[G-Lp "s%,FFdAXCk`0L.yOSSEh#1Heb~iz1w |^^o;g0;w(Ot1$k?BJ]3fN:s)dt@/KGD~"xjlDA)8Q0r <$2MbLy*.SF=30@]J$N{+EMjt(~GkB-P/X~T&n,^.-Dt v\7If?%l\&o5=\|m Fv~;6=r.6Yx;%p8Zyrgz%%oW|1-~|krrS}Wb*C*h!Oh|-P3\3fh|>rZwb\SMl.d)CB,SWF #vw8bYkLz4L|=";[H,I=*,,2Z:3iD&GY$"\y Uls3u~d||k$ar{nx' 0g0i^9:iSG +JSFkEk5WK/4h;LK/ +[j\o<4~'WAM-M6d,'63d$(y}8-?U$smT2F0lgx/h+Oo0ens1>_yWB)oQ#J$)`_A,>/gR[W6@ttla~;)ujeW{>!v1Dv-0_9dz*1b2z29xKHpT G-Q[zSHC>E(@hSke__e}%_Lr)*iB8PX"V/yld4Oa>Fm6 Wb7 =Qa&5uuHOKz|E_[sX5>q,s[lA5wL?OtQAt~}!*'XrGTacN|1cP$88MsglkS +Q0S(rAG{*C|XZBJW`oYqFRQ$ZO*jP-'6}RSoCJDIQn?hCYnr}/YU(psf/V;ipVSkV6GtZ(WC'9p@N)=W2E@p ?},HU:;e7.G^LNB3i +]F-{xQN)\$9q'nj-I;{D5*&R[ +'E'( /QE?+VeEp(eiTT?aHGp,:!TWn%&AuM :T$\7HA>R:+oHW,vO?'md +bSE}np"aAjuF{E%].av h,!6y;.)P;mSQ<4 re' !q! (~{NjL]##cxX(w1 *ki*av_>qKw[N":q#NcrJ2gT#Ir51L!u= +]XWLURJ$.[t,$Ho)3lO$69G2@h,E/fqta'sKqV:6kD&Hm]W;D@]W6qX 4CIRFJ{F6(t&pMagt8N08o#OAE#s*-hXXD=Nj[`"aC%n>zNrvU[eGVvL +]9:(& QF]]P~SG xp9bT(36I-0OTj9z:e0\ATL~_~!$UM7"PfH+I-E!Vs+.mUQvlK\da)g#<<&uUk9N#U%g/`|W0RgrpY`rF":d] @qmU('RHrr")=2+`3%16-rT{=QuOLDBe^ FG748}+E5\ "$.KV7q8!= s_RIE:{fml(u({D`bmzHI}roDbh&Fhh`9^'>\."lmnIwA.E ^/tmm~ 3I.{xz6SuT"=q$:&^(Dvmz"v$6(q;%81x>|OFqHw wL6>%JtIr$R{iw}Wn>"% b`8DI:8t%FX<(_GpSl1ds$q3@!^}32A;^^FP"=b:{B}36r<>4~B`},J-1MWH!#=twBn3};?/Z 35;7[jg;6P)@v%d5b4O0v<-aAEuUl,Q8X_>l5\2.p=9Zem&}y(Nc4dTdd +i;L\@3=&{9~vO&)V%}+p#[E~jc0^5R;3|pxhfu&|~z +/5}{ '[tj8qK+l,df" .1bO'3<2g3BSI9xR'_r-A8M%LRnC1{F:=gtLj\f;,UZo *#6!fs:+j:'be5HT!J0>ICP,)(FrOiR FhjLkmkvY^A/h:5S5:vkcrY}S0#)`M6! +)nx Ag.J%_vrsAt]|2 +-3Ivu."hj#/Cet SB3"<|q~TTIuSoMp37!.C&J|z *X"OA[3\F,)>/gD? l0& +{h#mvx~=E\1<#I_np L~\VY_6ac"p8Swx7E}?m@:a+rfSpyf&_EVV4,pbzriYFXeB{`08GFXvWT|HIO|F*6F;OH5/HS6Girn5Yd-1kQAANc#.v1c Tc=~4ZgM$,~* :&j,te1$~6;}2zB|?$RL)DTE9$CwNvpeC,vCBL*6021ZAkhypEP2P.'WZ\QZ +|*9-v1'@o[G!!=lpQ +n5H$8Xg;n0%A 0,xLUb T=A!4!V|oANB.$SHSUiDJ[gZ`d2iaj$^!{],ywO{vF^ULLX`C; /n\PhZD95)UG&7SM6,# + x8I9;j;>P^=~e]mdBK.}r+nYVVgnqvo}P/0p'*'7"Q: +6D_QN);xpAc5?Ea;{8HE`."*7vDymw +DOu./s{Dp@"Fqj G5l;g#Ye&STB?5z~ +D@zB^'P*zzchjcw;\r|L|EdO +&vxpAQ-M3}pP<]c\!Bd99|yaa/%VCxu:k*]~)S +j4%Nq&' 3F-4eBOt^5Z-%BPXi|EK-P.e!~ttm^gw{yUK)t}g @WM4ik304D 67|`<#.8^u/I>E^."BY>$2Zp7\)Bb2mK`&v}BQ?K,6jlD8g86G)$9@ dV*MK{ueyk|9 giIBfXTR}xUj}8 +1+?c]pdoAM{2c=E*<&m'\1#Z$y-b8TBa-`VX;,\(%?QM03g^I*pz)MV z@,gYdNgvUlTzFT-j$3rv#7rK/-8ASqaLl|F*Ve|Ii{~BM>Y{&F#OjL#Up`#`dk"-?_]."aJM dZ1,e$8M*a.yu#uu[[Q!Ro8075W$k +}6!uZFF;bx~wTF5y%!l.ZI pbM.ZsxDI[fr$,|oeSD3I5LL1~9=GkFT=qT0m[@" m>R^1ru7mATdl5l-?vRK_B)r ?2H\29$C052T~wPu17%(NjV]mMB$K8|ZmGQ_Z0t^UOM&uu-;y(9-jg W2t W5G]7K.zQM.1FWu2."@fSH:NTFn^2ojeo>W|aBHkYWd,vo^8 +?0Q]BT&kWq ^*VjF[6tKiJJJ>Ys9^u43{:7k%Y38i\jX\{E1=w,^Tw~4->)jMSC^`f/d^t;VeJUY>-C_SdQ@|/~9FB+rW/>\B^e_VYd&y[ d$DO6[PoX%R`T[f*PZ33,^9Fh2-JO2j$ lO7;pj+u9 l.^s&x\A2,krI:&hZju:52 0.7Q!k;5*ag6JK_;Qe#JFrghkslhl5m`&6^PC +4&}l MXXKX'u`i*JF`>q9'AVQLEU S~_<-BM$OnIQMZ'85d}NSqSpu*UjEhd>cLX.DDkjM5q[#4Li-xU +}JumYGfdGso/5*6 K\0Qv;N1o +qkv;Q 4Lg,m fV*f?RO@z/uhz&'N7-!Oi""sjyg4RgzWWC%a&trIVq-U=F)h\vEQBK )NeEJ`?5UYnt.a\u D0M\/SK,)>wl("kH%Rfu^oz;&(v*"$~SN*["Y$YD:t]5 od6jPle6 +06 E)*dh5CrX_jQ@kN@!k^OL[f#W:EZg47*FMfDaaU*)unV;3vV#p}"lN}_oF6`ya(ddjh;m$C"9hB^XV`Of}4`-P?G;T/j1?i.Zc^`5ig^yuHC~oWECh< +^KhKHGx1MM!NGUDFhA:c=j:+*|J7[.3R2oUZ'/4e6*-wfVEW$WGWPRb{^vE xp\J^\jW`#9E`mu%O),4Oh Z+=1c'V$W[baMW?"Y$*-*f>_%t%K=3Zaai5,8s}v:*oFSZl_aGx~t4N>-We I&2t=\TdVz;5)R^2zAoNsLdUW@g1: hD[F^7:zk>frZ0HXYA"&NWy )B?Pw^JGUKTM8YLvt4ywP#"2*Q? &UH& +#q08ml9=#28iGA2Ui5D>$.v@1]+\9xt{rj!j ;rzwQUJsS1a3ofk0D3V:h0M64o){nv!wPrQp[ D/1 k7mM/=Sy{G*I &R0\nMX[lKhng2>{{ITHL*h]'Uir~:0s:lsZHx,;?f]fm^)Hi{9Xv1BFB8^gA8<#xn>^g!NzOUJ4E.M>RAR@H\oQhuxu7ueU~O3d`3{)r [#*+X=Duh=5\Dp\7Cd,Q2' +J|8Jn -j)BG;#FM/]pE&`C>C2H-@3L^z@&m4Uo|}*^\X:gF^C10>h5!.geOs,xFr(-vbx15e<2IP>D^_V8iGy '"}aA_* )G{>JX +(cNtvDx/}$U*wr6 81iV#i~RS/\MM4f$i?r[][JY4SYBC@8q'&s/s*M}MkguE#T{20HU/J6zB;0:1}sH"H$mm+o12B([*mO>Q}97ogGm?DgUL%">'wmOE/ArpLf.qp:i94,{You!@z(A\$W?9w6%*R^ZczTC;`e^@_e?Bc.QYo\/>LgnL%r*9x_H:V|3IM.Qx,UAcKl.&* Rm};.^sgPrnRzS(85|2nI&&)r\]/Ka*; k[UO+z*u+T:2-~&9'WTdp$UZ>wFVs_VDB`v13Z9RX@Cx G0-.QL'Y#ZwGtgb%K@vQ4|hNHX!-tperBV]?SiW93E%}Nd& Q'A|f:x-x8k8"p#Cz)=m2hgCR(768K'drcX4'OG-E=Z@Gj=-H$4'%&$m/19n-4MwqQBl.8rQ/0 UXROp?nQI.n'nvS(%WTUE_~`'U4<|)ofL;JmV3,L=0j6~VSwb=XTD)2$xDNgR.)d\M:}:>[eSK>Z%SC1)DYEul)w-+ 9;$J/Rs { +Q|b,P"(P}I-/#MMKJ|t<*W-=ks_lPHAv[e5o:(Lq,8/c4##\TE#? Ba$ ;?:|'?S`_i8yg]k QqLwmc@.|#_XanEcT`|8Fw9yB8`;%(<^M-}/ +LCI7LcmRwEnwCd|D{ +LQ;nird{4*SsOTWrB~,[\UWBS\~,*dV;][EI]}I*1g{{u,lY`XG"q:ko6J+}"}H8OT*5o(06 u].>u yq.S?^aMaGzh|l-P1Fc]PX +a^MS(IX9sDc9(XhG3T;S+v~<';d"#mW|^<^EmJUUGaxM@*s8v9INm?#m[WFyU&xM;M9Sk1)Q[%cCcK F2Rb:6t|1fJ7s;*6 E_+Z#|cD~C34,qqz:-BkQkND$QMk>=\ +l'qGt`o *;"g-_u$_>E7ANgNe}'5 +2`~:).*_w\t_AkT_59r\))SQ(yri,.)Ah-`a 8w"|.+9vmo~4'Drd\?AZ< + %*G~j&1elDx8Q_T,r*buZN]d~&eR*UIrK0_dkC*i4d@;J2cou)cP0]H!9RYyc}8PL;APitJYIDbteG{nOR>p&4M43w5}p1H6~mod$w--w7RqEAks?iU +Uv }sk^!(&dta*d7}hF>Bh5nVY;y8 (C?Ok6?-oVxnU_;+yjwYI8G``LCy|l)oDXgLNAWg+"F!l_(Cpi\i gMSC^e:eG'(!L'xgd63)j+_068Y'p<+;<%jP\mz&|l-WU85SF,L*!&.e(@t- \IqVxi/_bDX9_6t2r#RklXF=lvq +1T 8Y'`>Z_N9o2^`h ,WD1o\8sq#9ISmQx:[ + +c7xW`nfhG 5Bh9_#Y[u8J :+`dN:oT.GFOqi+!_l]hQV]@ Ai=6Yip%,94.4XBybz89v? +5s4gZz)C5pbKfidBCs;y/;a:GO>:wu:TG&icbRq.-P' +Z-q}bT=> sO(^COtX|! ?i|\&U1rTa\:-)~.yIJwDiyu}ZQdP]{nuOLYRE~Vv +#C.v;lFM[ohz9+GYOi*(b3T|W+x5,Q6gq{7Q?^NQ):+H2UaK.) +]7XvR#FVVkIlE_;bme3z9n7a+{28,8Z>;Aj5&uzkb=&'uYud5#WS[y._U\3jZe$AY4RnJxVg /)?q'DH{:m+?L#}{T~dYk:SgKnj:XPIo# j[ +/nZKTv +z]10&a*/HiWb)lvd:rtgo4CN# +P;lc69`;+iuEW;5:5|urR,`x~'G-PLL}ZbfE"/`(*+8}_-UX6!4?4*+f@ +8xh(5}Q ZPG|0'e) 1x6d-6xVKP*l)@drl@)!ep;oF,;7)ycm4Ou`?nG=;GwaKHOt}u@=:&`g"FMMRW&G4,r%Cj?$4W(M0~=%@tT){xL!d2J=8?:R+ F=UWMa C#ubT=Z||a6/Jca1BehQGm_x2adCz>`oPI@+]pQj\P#/X2.z*6hvojYs5w'{kgW?]mO ?FoOoDg4@O`^`sE(-TH5fKSg[}O6fWvTQguM^n*l/|yCXi>:QQ-1mu#h*GIX>s^x$2`!Qwe#n_nKb!O _(. 61_S1ibJsQ=b8pD%~Gpnjg1ONy~Z)>/Q>,B7e,cD:1o-53N1nayZJA}( +;C +_J>aA:}r xr&)wwk'Dc}XPAp6?!erpzm> t:-F!6wVOTD*qp+nO>i21=H|lU%^S'O 7{OV}Ww;}TbLSk2qix)9[n=eIaW FHhR"l}UMU3e7@3=-Cw0+!+3/?3BsE:USu[-~BJF[S (!cb|o{#cM2YU:JuHq+/[taBv{uemJ +LG5$]'=kFWob^p3+xwLM 4r&P]:6daE0# x +s'{#;pc;o^U+**Zfr[Hwms_~/E6X#!yK;1?ABo Kqt\~lq\gE61}m Bum#x>5eg(=k" V. +ZtI;,d]@kvk2t~-aVB:Pf8P5fo&4n"6nu4(ftwPN0T@lTAz@/V2f?P/%aTkEt=]d#=ON!#"rU^+|CjzTg_df#P`/Cs" \k +w5p/3U;T}n#Zj`=5P1pBM@5K2p$']qXw?Z}+^d3tm}. +(nE+u,1e+3<>u@c2TDkbssFr[E@Xkc@8Yt\9vH7v"\JCTg):wL9fgk +)o:Om;W^?eE,BO so-*V>OYXRW9ey/^m>>FPmc"C Y 0/[)z-]4p]f@"0#8/WACaicO]Y;kzf' +7aNh,AiSUV92Iti`={oVZ]e"G^p'[{,"cq`~LWTS](&dk f1WU-DB' *Ev)XQ&)&^b +d_@m%@dV-ZMm(0MVy /~Cm#'F}I/P=8"5*+LJq'4|={L8nTR[?'wNw(%s([F`S*-LCd 2U{dsQz081PfimZgK?;Q>FF~2lN_W,"|h-OYQqz0=BeRuft3;{`Rqs'J*S`s''|~KZ)Ei,r2=X5rk\%bi`^YLHKVfm(|m 9"iKI{gK&5W?l<^QD@|BJH/#f*#/'e$|H$m4+U%iz!B4:znGy(TeInsVF7EC1k7Wi;mR/u>sz#[FC/v/oIR8N0yzUKM+bEizg `rNC](DH~abs+~T)qrIzxx2x&+;eGQ\_ +ZtR3 +?9J4f9Ew}U7:D:vJ-S.E54]AN-&'TEd$zn6IN3[Q72gYc }(B G]h:*S=I|"OufU8{p">A?lWMjK(AB@@d@-P;`fYCNJ[#!r+$b rV3T4%PXm_~UPtNu'?TAU0|tA=}Jq0DpE3nU1H/:sQ`@57^zRJ=Lp-v@uaR2Y6o=5LXc~gHp-IC BXtSP1*xHK:6-rW"F3G[(RtgVXQQRCniV!|h0Qjs,7|X-;|eD?fjyoY"IpU(A;T3:f[pC3/E7UwI"C(ap=FI3.beC!h6kMnr2Lx1`PE6qEe)^&S(Y)l/GgOi3[*!Ge#{Zfx/N;P]s;#qqliOC~q[iLKD6k-rJnR_IXJ9#LlUU +r]/>c*Mi5O};zbRdsB+j(Z[A4^gupiLxA,rc?wheFA5Dx4$~sOa#x{;3W%:$D9+{/SZ/6d&lruR=Px_1y!$5lEab~V$qEB#7>PGO/O_C+{^%"L0$=U 96]7#Gz> +bJY5lnRJf$lmv0+zP+4&*(DA-!`x,wV~Hxz^SxJ8stm{ ~G-@V>}r8k\jd\?zR)mufs0$`j;1C(fKZ3/]qT;R?%>80S;-;;(?=2aZ:c7%Lk?4"#_INF `s?pXG +eU*sf_T0zy;Jm^Efi`3)Cp^pq +^n|85_@=> B7Y)7{g!Tr"[\2JU Oh}]:r3:myacf<69~eJ/k|"us=AXX~&WBU%.E[ +3J+&\VE_fM +.\OnGv '&%10"0[S^8GYMoi}Pa= +;\ j(BY{ +oOf#tWBd/zQe)rM\f!le%g S\#FLYSoMVn;}"`hX.]C(dn|*&_.lm$6I+_dU ! ZV7WBRg2Pp,[n[M <6\JQVj(iK|")W2X)4BN}km4#t-a@ ,HQNnbLbZQJU"Sh;5NW2AXdnj@OP_.@8ByK19-$2LR;R"eSN=Vv_*riCbW~hKAx\aV9h(|#Eo9GD-" +E8l.uo>p=l +?>#(a|_i|hn8vH +OUn<]D%`A;v%JA.] s)O2flMZK[aQC:o=\$m}VuZn b=XY5cB<(%$tu +@IZ$4spO>":b]4K=@I*Y$Mb~y"E=0v vh42/2C w%3RhzDi vK)3i7aVRT*rC0-Q4dLCsxWT|uy1PPxv^{/F}QbAv|33$FK:A_MSVMh]C(q[PP;aV[[D yJ:T2@{a[jxMDxEo\LO$2|G3DV{$r'MO^=\V%UOnmlB`#!T +5J"0>NGVrzF^mO&3obb +}(oI +J#fV"\; 5,4Z3A3`yXx%kc-5*.>&Qv!]4hSq3oE@"mn0Lqw*Vrmijg]^$54Mj,<)q5k6do#[5^0QKF2lixw' +N'nOomb%gOE;oj 8TpTDPhx|>$D-T9+`VY1cyob[8a0F w>BW^R'a$)1fYdI!p8RtB+4TmuZX]J!7 *%-Obz9Q0{q_wRB?sP>:[A9q8 +2c1sezoRlRP*]nXdY<\}Zox+ J(qW$H]C|qr 8ouqz?A]$+~_#Z0%?ZcyJED}1a(HQEn#^9%BRLI>fJ)5:X +7%DV$z&{GM> iXzeFFC-;L|E1L4qIi,J^0H`oS,ut~uHa(iM(t,EmH}([?"&Vq+TrI_47;+eEVdOPc]`j- =Kqy78:{m":Uu +40;rfB&aeji|!#"oZ[$0PQ#ai$ +yh +R#mWPH]Ps;\FxNRhnF/cB$$Epkp~/h$0F&PK36YtmIK/klpdYW)m4Y@. -gd&D4WFpxe:A4kwepdtCP`s?d?TeDPHD\=97b9`oxO$>WP>)`OT}SgW"Ra"'#L:$hak +vckSw[*)#!R.7/Mqv[O#zSQj$$h`jVH>*c\q6t`$M +Q,),fC;dkbYS?W n|X9h\LrN|n Qw'_=>zUFtqk_n(|2 L8i8Lq|80PbI/ucQ+F@d#4dToGWnl0W,I.QX2fZN3Na9,6 TEzA:!=Vv`dgxR+b`n:Z:rS1$esZDUU.3Dc_[1V'1 g|wv6qp>;61>U0FR\\86s%vVEv$0SeX]wb07wR!{UK'X +F+|EEp`BE+!woK(6wy}k~u;' +"\cZFE0d/Na"45N7[](=$A\L2YR1Yf'iRMN1H#_0-7S4=\UPM^+bO#awOLzNBkRqA"eO@aY)aLO%)rqN*=6^PU[E~|4-MTu*+jx7'n9 wq4 +:%FZSXLx'r_VyLPy5d=a<}B % +WT\!qK`m`EAB[QW4S(`}LOUB:^4h!\*hGV&F*_81c64| `"ue65kfd("*5-1#`LF +[so76([+]pO?DnFM*L|6?}+J|Q?MRGXpCSOo@eBe3im79D>R? bGogyflxdG7 =wqY)v(OV>.E)OWhX8%(FD(h$.pB}vw=mp +km(N_:@fQ=yCw(b|7jEp'U2Mz0=J:`qz[`|>hKfOnkw``n_ihS.Xl=gH],#aI9'5&KS\`!H9`6Si[.!Me@"}O$[@i5btcV9AC*|-?4vR%sNk>QABQ5"_&pKH.$*AxVuF1 ~,} !12~t\ + A960@Q0,-{Z?o^;|-T@~Xp),G& Q+SRM4&-s4 Ngi%:$]V>%D?cu!n&qIU-V`HcQC\>[$,_J":~V Sd"SZ<+bo)4WUMjI9alQk(N(n,e*w&s\s|{='b5+U`Oum(NxCh=89$+O +,&9Iww->Zq=yw:mK35qsR.}PhT.utBS7NK4ZFFsL}$pjw)(Wd]ju(mw1fl\sHGAAdWm}3ws{SElD++UR-2AFJ.v{6+gg0pCd?@ q;l-` ~>Y2_JtXc;l_1xf>&Z(.jd:1aOO]=,6h7]T uCE~>SsJ._>%:+I!5|&G^1b4^)]j'C,K4HHJ%0"Y/a~8r+~762B+?(xO|~uK$^Gd)w3|uEsxh4!#_ KIlcxsp,@>/e a +96uKn, +aaDReVdO\tG?=lGWu6JGT4h +g48F:.SClKeD\Qvbe~pZb[0 oBZow*,1t.A!)~JuL[OhBNh%72sPfZL?2F%KgQ1/K.`+O)s`BaHwj@rM7f2wVMUNDRYS'0>=sMsB^WO8|HB|"!y"m +,%l%M3+lw77HYYXN_e!ULz.d1~[w1/N#uK}vB_Yj>BSLc>bsiKh@@PA8I<$L Lp@&}4ku'W +'N:@=g2yA-]y6iJfiHoC`tE$S3D_4(nftbFk?lOwuBr=0?EJkMl#)H/|sWx07 +rd\v1KN|,o)}du`$E:{qj61qvURb#Fz.~Z\fT_woR}H7ZQznq6 /fnqg&NZE93.H?>1n/@oP\bW.G>z'}jo^?*ec7Rb"O4gJf +&$jc!\ +J&vniF}e2l)rMhCAwms,|Y;`/zT_JNP>mu?yGsK-',uHtL/f3x\ki$(`w_iiU8}:T-8_"[zv@m7_G +y. o:F35UJ5rrU$m9Y} q>A hn7"**_8V1MoO*R.Ew+`9fl(i]* @g`R~a>p_gYEZ{\&7z! 9ChusGo6e-M[W +&}HfkMhOw6v#J>X{e%\[{U>\a~q'a)/20BhDUMhs@eX@5Mh.69hiYusWH.M`Sl0/A+/r[u}A:m.g|$NI9RQnF.T$o2. cC]M&_+;Ro=I(4;*JORg{VDnPNNaD&I)<=bn;fGZle!dTxuN$qtEmph,`j?BpA(^HE<8DnVQ|Jz{,&rU]SG>H]^m +ep0W*Mifjb}}_';OroWq.6:7z[w{5[RT]3]EY\X;=tT"<>NX4:j*["/$6*"g~ENGSOG@!/u .W#m[?WrQN:}W;!$x`:v.Oa+Bp"o<`WH,*>veBE>Q #mP !2NVu/-DLl2}DaWB: 7)XYs@s +=w"QGN;y&=jyNdlr83]lK6^k#DNg"x`|4f{=?5j,wP~%.:H1toIdA>:nlvzi\Ao{)!.&?c}9KCg?Ix3k>V +#vKUkCFUP\(Y}C?mm@uHh#w82T.^ +s,[*q^{cL5#$t733KMZP@WALuo3n2 |U^ +{?\~qvet4a;#E&"tAS4y}W+)pn#!#aiCjF#2Il]_)8"nQqp/#8[%w`;aK8B#SQss)>A uDA!=8ZThXkUZS%^}v -xj9&r +4r3=1p\ZgkTa~t@L{&u$bSfs+[m0NXeb&${V0<]I04/>l<"dUEP>.gN&7d`AEZgs>hNumE- S>4{Ws!28W^96Z.xO:zi]ExJJmaxl\~={Xp^5 +"4"]`Jv41pStG#^\zv~6z?@0yZClW|+?W> +L>25{`Qvk_q0g_QC}Nl-f0q)pzBXTU:B_#1;P~Z-05 5c\-V9a8DTSV:Df(1,?uAvx@WcEzTjY682*~w'OmvHTYpt|`%f(P=D9r#]o;n\|8O*]W-ak3:;kE(3s/+:Ra}<{ 4dTi|o`sA`U~`wTOP^]EuKtXIG1yr>#U'm>a%D2{+7Xd#v).LJ+jJ U/9mEIRrqrFM9Z_{H,<%y{"o]{v"AS@?Ya((CQSJ5o NC(%.heK~c{wJ\8"Rv#]x{-mo?6$`7M3$$m\WHzNjUoIIQ*.~Uq"1MiY8(%kCe"oC5]{e++op?w5+Uucc[ +V|Lip0j"YP'%_/{=$\4;7o~Zl#ye~RtIS.w~2L%^.L^tt9H&~O%8dwITW>#5NRIENtC7._w_eY{` &])`lE]Zk.<"d8B'w:+d2wuYsm0_Y.+:Z)r-#4[aCytf]Xi0?sQ4$[W36&g0"QS"\&L@:u)3~RGk0h'I9}J0!=az.1CR7>6nE=xU^]$_8Ph\:9"<1ZJ~]3u~XOB3vg_K\g.^s4a&wsMy,LxKBmhE~.hia;S4}3aS6,`$]RLBQ3x->7;D\WWmjWk~`bQdA1"LtJ>goc}X;WD77\yNt\9g\C_zb]i1+O e1g0x7[~i}"<\^rkK"op-OkFO!_4c{hOai?Fu}x\]rz \!tTMN ;0Rkr0"U59#'TK(,aL$eM .=(8DbSs(p>&:-TG-D-;6`)IE-*u|p]:@=0 +9Eg6XPYQ_LW2->,A.xD"5ebJ&yrP$mr8_?%_Q\ }Y[M&iQH}#X3duhthqUE!*hPWZZs5z v*}VG)9/~=BqdQlxkzTuLH_Rb/OD K\ +VnCc(Pl{I/f\Dr8q#pB"1b a +v1[c At +)Q}H2DQuf[+yxTRQ"Eg94kEDk=J8I]X= +}&+8)z)pFBTr6jXC ]?27 + qDr@PwQE[U; iZ^th3/G`EsQ$t'SfGYB}~NnJ=rMXw+>wO4[v8Z{; +'d{0Zu?,QfUOY#{xU #2]{LU052vsaH/9J?bwe Py):vBm? W*8Ilp?r_8;dyh]f >L`D%YfG*:/[~>*u\`H`:J[hW4-{>&O4{~rOnYIGO!F+QCBDl`KH!,RYSfpvWCHJ&U6Gk&Fql$(lBI_6@V53z04:_`)sQ]Gm?d|j,O=+dxJ00\J]sUS}w\W[~~lXN&cQBFGK(=Y^f}q"rI +K:npnAt\<_em7JM{_kNTS1K"QvOHZG@H6)~o9Yu*S{KV>cA$LPTQd'>!+G/w\/-Ksr;w>anWZ)t3< T}KyxBm.Is62|LRU' @X~7)(+%7))jV.WX$Lz/dQ}L??:z]P}Cg/n6UE.Zi@'tbGb%!4QcQU[% +Bx +41,] +PRW^%>y*uE(@Mp)eD[@tl(vPaq~L>C4!*=I2A/JE0Tme1'9Bf8%V,NfQp riNUD##Jmdhq.::(WNT#_XBT)D_qugv?9:e)lQe'"Q]-pK R:tGp'!CnK:gsC%[25>?aGOAC"X)pCm"8>>AS6i3h|t$Uu^~RHjZaj{}Q7TH"R]xfvaT>U?lI +xbQ+3B15W& +$40@+VUWfo>$}*M\PXl&y1%zG_b/N?M8}1Ht-|WSg5l%HZ>$t]p,>-C+"(g2 N"1Tc{MfP[ig>Rc*>2jv^CAZn!|HiTRrTjM/ 0T9N`FT"\u!5@=a4(,AeV}d/*o2WkIaI0hr?QbfNo\PHYv(c\:k;m&hOHaZ~c165 + eM\g K:T{)3Pk[U@tYI]$ 8@OCJ+%c"IQe1\oyTGA?*nj%zttb?Kb)N_`BT3Jq2A={Ju|+3O +$3[ +tO$`T&hii#v4# dB")T2 *,abUKWetL\H8%n;ayY2e09wPhc&eQL +3/ubkEOnj,.8G0"$f!;>=(`vv}:nkoE{ hB\v{Ek^2)Vl1"M`Upg!FTy6.>326A##soc5?vDq!]6iX$qTbcEkXrr/p4b,b#8- jsH6/H#-HlfjENG5}SCte(q)%EI.fk}]dop8='?G^b@u?h,3P8q6@BjSG2&R]W=QarET/x$#KIf P@`Lz'!``Q +7lvbmw"YkEB Y9PF(Wn8qv(%s Y-N]=_oO~`Y44DUPN>uZD|v)v8z)WDC*5G=zs]A)jsi};tPseK%:")P[kyc7p#r6GG{xuDL/U4pw:A}Q5Iuk*6c@"*aA2;ELOzxeIeR 8x]pv"] +a~z7[2M(9vt2n&-Q4\0|g+"*X,CwvXe$7l]Eo.Bhzg1JXLGV;b5C?Pp/}-u.sQ `S&x+ +mkeT+`w'4M\<%9w `q)B<7$nQ4\M0$Zu=z>MN"NXO9gx> X:P,yqk|OQ6_X&t"jJiBERqM7+2\czPI7jIffX(_An]S

`:1q R+cL(IYzGw>ws2zQ[4^(I03?$}PAq$`Qa+[wc{vM(1$W%SwK3`sM\!q2Q9j4[,P?CH,x!,Lb{|h~x<8\C"w*8{sMaA/;7CgoQ\egAs[?3M'ou0Ws,}wJ(56_-~Jr=[[2=bLp&'*] ^(/~GZH8gT)i!~CMi8"q6f7JzJ0TG},?S4jr#&% (X$,6-uE:we^?Q[*w(|%S*r6];Wh ^S+F3=7TNS|"1Q8nGJ5`bovZ0Q4,#Z{PX0nXFA+Mie`iXkq4TWD1%j~PTS=X|`?yZZ)@mImdNWM46PmO>t+4@Ww)*=+C>TsrE^Tcj\av#& +ovHcg:/&%3iPtceIcwD:}1XQ@6E(HK`$dy[0A+l3iET WUJMd^3{rm(A6>\GWq6U~~bMaJ-omXZ)n9ni/f,Fi"~Tt`A +[86&\mnXQ8 %z\. f2Ol4X8fpUW)IH"w_wT ys@iLYLNjST15maY + zLz3=Wd[L7l?1RgjaP9e~hI\6@n$$5?6; LSy!b>-K"YTdJ7P)4fi@q#,w{5t3{v YCI +.5Mn2|T\! )B[I0qG_/MZC('F u_]\V:p%gl)P+\@9en&FO5!{lFZ4fj[:,eAEcv% eas(Jxm(UTo(\O?R`NTn_%T +%8;t^[_zn +}lKP0+k$tjOJ~)~WkXB + +j{TVH2)~FJ_4 +^*3SyM;i+8]Z4@F!T8C +s M9MBL7zp?dq-SC:%!}ojY#9B<#~{Ov;Ku/a/8KD&|B%;v^G.k|+:Dx$/"SQ%N5^}3Sf3I R8$9^U%P>7x^zoV||VTf"9$Q9(Il!s"aysQ&(Yg=68iqdRNeK;MybOy1spX\68itm&" +Ay&/GCvf{Z0c_ho20K?-;DZuZqiE7X]QE;@Xj] XV8ryQew3CKMp}!0p]$\Ps>FW5:DdT]!DG!!x}Ba%y`6Ky9_zUp^K}W YP+ie[YFGCK%#V>W5FAw&}{XyiN2}9\A3k`1puEP'd4yjm1'IE-0y +T7wQ(Fqm8-i +oA$Gyq~Fdw3 ~>,t!a^8x}*i|?O~wHJ!Mcc&#}4|) <(tA\$GvL57NljPGVS +rhr}bv5ho/"x.:u}SsA7P 'vGa^ITJ\(4KFag27qb|v{5J8KO#F../\iT~7*G3j;8;*q^'>x;2F 6@hHP2.!F-pcZ<69[13,&c29!=%NGc{zXl +[e3mx"MC325sG[TcGxoP*@[zpV=7#X0I1Zx$^Au}|![9Yg2y5U4^p0 W"/m|rsb{Ef'/A?b]vU*[4yO'<>j}y%=!nEIwhXZ?kKlyuoq8[x.skG'oh"v)q[HXW1#UC9[\vY]>-.Z5!#):EBLEefM-uOkX W9#w66FNnx:\qM0G'r3L?[&R>fFb2A,wNR0NuK6Yi9#pjsVq5@@w>UbGqnH4v,3t`YjQDa +< +{'mT.r6Z.meX%Dg=h1ko!t?gu=+89xn+KcBb1Rp{R7p}|<28f99z=P%Y_Ao`;5'n+5 ;y!WUKSN3X +UH/7/mXq};c\m{6oK88Q.w>.R]!U #<,IUv*7(*%p/);! +_iBND-.R-cK}&Lw4[5AHT(RI^X8E 0a+L%19N^G5 >I_lqP11?11.b9w-Bu&f*pslVP\|(q > `#6U58LG>`dr[ErK)*GFVN)+9^6WXDuWQjy>Q;W~0?`wQ)JzG`{X{6Gh"L(r"hNz'.UzPWJs`?N~j@G:?\Wzw)aWh^&f[ZM#-t*z@=-` Dk~g?)SCstph-\NHxTR@Wjfvk^@ VpbLFtdd(J23Ty_q3lp5~c7@C2NKmvJN{@F=FH]/h:O/W,q*;%*%XVc<\rnEV!ox2+WU@"0D&=#h*o{`2*b#B^KQdf?Ut{AVITH-ik5!NHwtmV+x]M{J/<=F{]~aCkDN!hi;~@@9V;#%3:IFJH"0&r;|-Dm{:yYn0DdDvw3(q|qkjvRlxd/LO'8)Gv;"$h|8R<"mG'Ct/J],l6hgn!Ru^"fWuriKUE9RyiKB7Nz_'Rg<'2u*"\cgFO&pI#k0ps&;=GC'pFmKn6liTR0`TtZ>Jve'*LvW[QV2W69wJ:lFxFqmv +!{Zlp}RU)DfTi;F:5Yh AR*0hpN![$}&wo'tZcZ;8v#TF j~k:ld#5lg`o +Lw.SB&N }^ ElRm7L< +D7;7E!f6G^W"r44f|i4OUb']*gHEB;Hn e WpDQ\]tN7e}x7+'}#z636T _sjri+V7#d%m B=s'oJ(m}b-"fQdnk'~ e`g{%74u^8THu]?mlElF|e7#uh@NY65s1s$y89&MRF.?a'F +&a!*FC@0Pi)XSkp}?8tN@ +7cKi6vY0:9'hOB&.!iSs'O]H8jAyKm3J7:k(%Hq&'`"cv%,u2yvr1_'LkQ= +;!w5?Y1)e4gg`~+C+hITElS53Pi\U,>"}GP%UZ5/#\IwU2":qS,\TiBm]G87geN<5kh/Df[=kKhRI3^n ;z5t[AkbXEDbW(RJ|/m,o:F.bqRUDKLZSY(KU-rG(=e/cMXY.sB;:G\7Dw:&enQD.mfTOGqrB0v&V\76B|Fh9; +}!S[N~#I{Og\XMW y?BRE9"0+/D:LDcnnA:e.'N6 >)WE)E852-mya2csmpn3_}6.7[sdBz~5k:w% + +O::]DF19,E"`G#$5yO!E\Mh/{<5H3=G,2DoYAt]t6'Xd{M|`UpxxdIv|,VV,\wn$1) +auV$PhJ&hhu8%V' Oq"HCC_mh$JY>RESW_0&,w1LF_o=2)MM`4?v*PIFn1/mOSvC1ry@'u4&GH+{a'G1*scUa:nxlKK\_a3x]$Sg5.(:@y +c"g,2|RT8E-1Op_B*dT2uZ7Ibt(fX]qcXQ1uq}Id +(Y^dKOR'h1r8Z6}&,-G E)i.&6^2#{:1 3"rm@r ;r*+<^kt;5vk2s{[/5"0\MISeAw;1k&g[(3-^v'\HyN(cDQMCjApY9yV:Up1wZC?{~ShN\!>xkLh^4jzRnDY!W;K,rQ3+UBAl*alQ&]1 +1e~ul=;+VZX?h]:4M]k{\rTKtv8)L\F-&RjOl##:y`xi%G^awdBtr~!waSgb~+o3^dnwR'eQe}:{-UROc sw#MBg'9ruG4loK$AW~:URwBr2/v_%k>=S7F?\K]j+v[vcFjrrDlR/vp=g&5@b!T :doWKdHi~VB}F*V@Ah +.Or'N4>K%{f[PaNT@:A#U+hE=TpH-v!$JK8[Vs(RwOvi [wZ{Fy 0j4O6,K/i@lf;M^h^4wZ+%t\cC;eskvx=/fL%}PR}-t^:LS?v>d"zpO'Q@vE|Sw`E!;F`zjdu*$?1 +dM2hIY@BaBj>5X\6%'ypUJ$z3.Tp|OtyZ^<\R#jO;HZ^EFC&?EP\z3ZY)j9T;Dt\}&6l#WdeRs&e<Js\?b{) trby<|U`m=gBP=cHf_8z]Ji4 +C}yhL1 }$\[rP0i4of]]+UG7@pYi3[,(? +gdO{D+.4Eeyh/d-IpcRm +vX]$IW: +ioVR +=q}7(SyeX/oS?_rs2,5wm/w,ZYc+b5ELe`wL3;Jg*sc3(PcUG E#>?WFp%vDzm|Qk~ZsHX kCnl0DgH}4U(~B +zL!G*2/xJ{FJg%;d]~rsILwve1j.8^@;~q_KraY}hX^J%!N4o_Tv| 9hoU>Se~l*P=/IVHs(O !NfL3-en%S0pq_} =7p@O"9IJv3OJ5I6(Mi$u[AukI}8jLjM3XeM?US5EZui&:je@tbpR=)N4oQL%-4pm'I8HcnJ_N0 +(>ayI5xQ[Q-V?3$FU5sSaco5f3=:\$zH?o@J[3d9|XL8!k6k.oH`'Dv oWq0UP-R&UxQEY6M>Y%2&]k-g:^k?3thO(f"OBUj/$!Y\>y.WLX ~.0D:w&lW5"$xHI037++d}O?AC@{'H;X,moqlAr lg8_{|zs!)x+I}AM~ALMiscv=^!z+!ins0?{8^s8td^s6:-&(`h%R(\wJfu +|:l1AE|oUr2,zYab%v\)O(Ma6[9d ]~oB!*4/0ligjp[]WY)=MEZ0l`>/wQ9})mK<{sszB;_6&,}C`~gVg(hjp&T8lEJ`W,P"GcK:9\7.j?3HcU#\`~6 ?{HqvA)x.H"nZ|-"n#(WSF?cQT7!umln/G/[&wLdF9uVm%Xu?9Za-:|,>h)lQztyf+c,FA|D5|g69A/eV5~+'SkmRZ$A|2\G{Y% +(aQ 6z4f%eN&C]IQb,A}Yf61pJldoQF((1Q%ID@KS{I-`sc;CFn|]x{\:{GHTb&FK^t{}Fs*)$e=A:2cIigRQTKWLoNGVEw'i8{A2$A;>u+KAE3{TjM(9HCBg +wVg b]{sATU4\rH[(8|!bVn3I0[scUS%= ~# +_LG%xa-wX#h|Nut2sX+(Z[616$53Vfw< is%3 >i&Q'TRG + lw+F-~}+H)Nd0|9ZQRhgrZ?UGI+SWe<>1>!=1x*/-}ZH~h&'}UhqV]4&alX t +NYRkw.,BuOI}wcILz\7xtl:=`l[2H~N3:Y31p$>YZr#_)_2 ei 2!+0 +fOK35@,|tHL63G7e1JH+iqP,:F6kg)kxsoQT&}I H"p)9 &FU+7Q,MM*yws;zH&~rjye(pEBQt*OD'9Tu0R v6aa8"V5L9h.`2 XXibNTCN9wYxjU+KVab?6V +I:8Fx.lI>k?K1bXJ[a/7'_ .$p)QfPaw=G/RNbD("`j%6z_HP)-rw9[q%[LIMgmX,$K;KF4tdIF"VmUCpar?EIld70.L@Oi?phuC>Xo;w%3D>s(o}R|S!QLe[X0h@.-:%}%q@e#}kUfX[3/$c=;_Oh.g(PZ?Fz#7D7FCAZ44g4,6MpA:|0?VV^1b*4:3/aDn&iQS.krIxH|6O6s?M9 [0/m? +u%(ek5ixQY 6ks!uPnoGSQ:n@4_\Ype5BO+\ (xI$>0"v1KIUu>F`;(}<(<)%A[.RXYq A%m!6--R6ws8b\[~{v^^GB IX|-0-:;ESMEacu 6xQ8Gtq@&//;W.]H_C0-rk{P:c9(5P]^B$SF'[*z;w$b|D~>Z [c4Y5A+K^h@U&^N<)8?t_ ?lMa7Yo,3EDqE VLfrx]9i}M:|\ +D'*uk9wIps)ryE_37_7DOeeyb8WrE*}4aY_{>9Cg4-)DYx~iaDg8F4>g_gk0! _qUoS'Bq|UzQ"[W|iL*V^%A]FJ)^1U?}@Xp@&njn~7uSI~[]\fiyYaz;E!q%'?wC61_8Z3qO |:e807C|=RYF1TSIlKElq:lF81Haq6<-:{aNgwkV`uZ%}PXUYe~"^c!H)4kvM#P,/)s9^9_\d:"}H+o3}|*XL$o8CR$3rDfJ/e-1?iw#nSLvjG>Spm]|t|2 ,.<;+)9b,ly6S(=V5l\)T$':1mVkmv^.8k%49.9 %*2}| |;D(PM(YktkgLP;O1/C.Nh4w\jjRD+LI@H9'@e's!/! +%\p(eABn1hI3Ewx0-4n +C!%RS ^H}q{wrGb.<0O s*[4Yyh'dcWbRKPA rLupx|+cc-S`}WKh8.[S@=K! ^$]7Z'G@{U8*#lJ;NSLBrPi$L+B+z |%?QY um$liv N)IxSA.6^ +<[0Q#o2MlIVDxn:bqO#4/b}FCEEHc,ro}%a}/PYB|WQ "|Q:0;-w[)L?~l{VII9 +IhNVB$~gZFVu=3_zG!9#6~ls#H{kOH]/9UdhTZ'[ZaC:Y-9 +(RqtkIGO:+ls9Byuc44e:qZ*6&NLSfexA ft)1+cMkWX +M7^j3%>+qLOw\kFN.`P)vAOKsUldnsqt"pC +daUJ<3lc>Xo9e>J:i]K's,#9M^Ay!Y;-?U,_1v>zDX=rfUWfWzOJe=Q},G52!(v3lop(+,Z}*X=%W~tFh +TH-zLG?xjRe_|8FYWyi#h'U'x3d!+~j)kERiR\\u?ey9*|K:1IU-X2@8WY5KtbtA"]J} +a{,g LNM=[.)5:&pNR3eRyJ3AP )p)XH4cT?ge1 C)#$]$A?&.~O)p%`A"x*)uB0tI|Gq01S3/,?3P(vuv@vM Qq@mWq9)r=*39 8jL[$GG3RQ)C?Li[-f-O(C?eQ9&J}n"f7pG }| RAGh)!tC],N#pgYOe@b6573v EI}N6&nLc`APw5:*X\3="WgvQ&iEwd=0x=0ROQyL$}[W#O?Pefm8$sR|[,uzhZD7Kxl5N9A*AbBX*C6i[Wc}m|L>@cO[J+%fHc3T3;.)i3^$s:mVzWZ<>I8wa*b~"{F'%AQ+ZkDMqN0; A-^pD6-76a =7+QE!qH&\~ O4F1X5+0*:yt%ciT7oKG*0 %?{6yoHW"M7f +aKtmBw>dP,TM0"zV3o+I.8LGv}C`w;cbmtT!b(%Z\&_vN$xI"'oT8&pp"{KX,A*,DN9lNn "$~N3JlaV/rVAdEFblue N6yR+y5r#(>2K7Ah!] $v6Wo$L3\Oc<1%;N"u!aoPW29m)YDq.((o/\Dn*'*7w~7}m)7aNe>&$]//0Z9kxai\zE@(wJ1ta$3+kg"}&^nFp((bCz$_~P5[k4()&EW&CtW3|%v4eG;p!5FI:C%QN|[58o +[~.hA34:15c$o*U:v/.{AOWT7i+(55r'bFTu\ZrnHpr ],|;NsD4 +"(=&\1F.$ Z3BSR7`z"<9$fGu6#q+W{27"/}gL#EKxhJ"fim~8jnRg_tx6z\v`.PbT%`_{\%kd,c EK3A}= +N\*Vde\=d\ggX0A+"Ai>p5O(#3xV=*U BxplhA52"V=z ",yY?Il>UARnqO7~: +n3tluLh+W2y#t 'X +`aq%]^_i~F__}g))gm +#k>z +3n3C5Q>Bw'T~}g>M]H]^ZlE%`P%ZcM2S^:ahufV@!G:mt|_^{7IB<1KEskfk!`Cwg#&[+3OJIZRKP31(_#NouM5TCE|!_@G{comXw=a@^N; +:whzw(*O]F{C*eXHV]quR&nU1/.^@ylUQ+%3 +GpAyy Q^**d9 +|@=0% ,2Emp|5&Sa?0\/WC` +MVYN(Rnit3b:,O:0O\y-, &9g};Cl~\{7[m/$ZF|!\ruu--(Sj@h5r8N!1#1D +7pD`lqp0s$Qmx88 ~dW@ZB]7N+\ B '4!M+YPj!diZPTRL>v6eR&c=mm@s^$;j/nTN20EwZ=]X:/p' +jM{|c~V{L0Ty~Thj?] R{`n[,Bq4Rr {eH^WPwalUbqU^=+OeW0+M(AcNz =;eVLc%o8Vqg8'-U"0VC6Rw9U^32q)!.H|R `y-?_"&xCQH5'BA1r@FVk25\w +GJ1A#-N*Xa}U'k+LxF{ysI2s|`d.geFbbWT{0a]":QL\:_z<%78&B2T=eCi ccC/Bgc[H/3=t,kpk t/QNVSaU4~QY &LF` {Lw=4\QJZ2Iu)X[ca:Kqx +<6%}sq1@fhb3,H4]6y$]!ccD+)ZCaN5G(x+H*sX_Me:}[o^~r7M/v6M$Hj8SjFeTZ:!= +mA&Xm`d.A|}a$ZK +?9xKq]1E_IS@KBXY$(vi $ +_#kpwh#^AN?$txc +$%:RY}K)8/~v>z#S~B.mDi8Ol$[;V +gx4ikQ>Dy['G 7R{zKP7q7:N)V{V\+IrMU1MTo+O7[:n(^:%1oao]-$#;f_d:LqZAOu6QYFn~G"7b{JL dSr=v4+n[@[:V6,HeCx}jrE3cCd(O@w(/5`4FBi+c[C,PviEu`TD_&R@nCSE(2_/3) Ik_-'0Ma-{r@V& ,V1_ZolHx|NEIWZL +Fz{J +9gb/9+f) #u6C@TSFx|-X!c+&S|C.6&EQLY 6N2[Obybcv&ajSZ*Dv5[}\,H)q0OX.B:1UX;cZ;Y$j%UZ:yt_INjg:l_Y*&JT +t}-~GSr|${+,q;L.x AdLD6VoyY=K{> *;O?B<7:'R"h:G]&%gw+\(yNLHkGv 'l=J`+3b O%#i13Mrh~(go2 +Rxo[>3o;Z|$q[9A%.zR*,-+^QAIb;6tCxoSn(GfX#JoQ'Hf_f%59>!=E +tZ&>-#Ya7bHo (p>J)\1<>fT$m3eS_ +rOd~efB4`}s_&=au(KHh"lo6t{#@"bI<>s^cJ +1jCv |]TG+`|uCd3z +QRfr(|3H*gK)EA$7kH_Xy4R9H+^,?G;K>071lq}0-\TJ;vzxW?Fk>'2"hfC(}y|Ic-?n>U~uq)E,0~N;9G6P8E^euvuWZbOOr7*L~$T6Qyj1C8*Xtdi#GFfmp[AyBzGNPi?&r)!'Hlp(2y_ u(Yhp@x8E8O_Tgt.g^9*g7$V][Dp]^8`08U4o`kh\GKkcXddT}L -r/yBpn+(Ue5@t_Ghx;=XS"YNdd5W9>q-fe8T=.3&7`TGh37eZI"5sSt-e +R8n2X9)\"asf ^ y{T+TqY~N4@XC#F%P:Mw#gZa}6AOWpQ"sAw3_:*$v%S8v0KWI<4E>p{|ZgMpH-)o/^^1i?UZ')$~RRD)U~k +^%nHX Bz~H__*f&|k>2r>YY +bC>b.[1\ +'C8';zL% \DmRd[F=-J&7jYTC\g=Tkwa7e +9k&QA^BK^-J%yJJ9/HMf9jp.f1wrhJa+9qbGF@fkN+vp#qv=hAz*xlb^jbL][V +k> 0xDa*QY)SkzMhN0!qK^1?Jru!IstXCOvwNTH({b@lT?\o'd89IbIh&#(_;E$__7+#jw^>E+:F~X4:4o,"NXWyJ;sxA>gG&7M-%2R!B/a1KbU3)G& Y<#~/aBLpJm?>mXR+*q9j"Fsrx>L-C)d\fr3xI4^Mf$esgqaT1nz!Cms\W9Fd8'X$]%]=a +%~[Yk!rzMiIZE].M*Q]4,~N\Yae(AINYMYXT +vyBKI`8xKx 1 Sj\^6M7OH5k},z,W.>.*JT}m7|kH<~IOIU~x<;O3QINP+nnN&2e*z@'(O< +_ w`,<~NK\tzEiKU$?"Ke#u9H3U7|;@&P8m|w2CSV\u`*!b*T}fpW#?xPuv"e[ntZK H(_+},w)N8W<({}*4>bE>[!@0+/<-{A8.XR `IaqCVzUh%S1+DtH1JBYJjp~/\p&K)Dx=m+0"F\(Elw[N94hL5Z!ooS^"Q/sn`./# (/}iKkphZ# 9;vFH4![E{5\=I,8ul?BG(yMtlo&:!>FCU0CGET^{yM(l9jct@J,mzi44\mtuA4Uw_iWl.4)#;H w,vj`-Q,Lk4SUjPjfnQD;Z`BcH~m817l|?&p_1w0Afupl?]iUpgjx +OEja7x>-,\wY@kBi\?P@bXJd}D9Jg~G~+eF:l@jV3q$9tc}sl6{an5:b8evS0AlF^3z@tni'~1dcM V7v'3s%:ia'A1saa"I^)2ka7V%)B.7)k~Jj'Whxt$ +(qKr =]q<%Ss_L<2(\3By.@>(DY\a +`gW>F?j2!+Z5i%T/Lso}.2ii+F H;H_8Oq|6b%Ujz9~Aj={&&:/h?MsEG9Z7Pw%/j>{NvER !|p|EY:8(EzuP+`f+kAL7I)l@Q5wDy't"vMo&|zmO~Rw x R]YggX;S8Zy439o`#_oq6}-o#M GRp^}pjFdno~1Fb8t,54}H$1>5igncQV/ _P+sa)@6a{k/s,^z/oL?"$AWi _FJ'6Jqv#_%)@Z[6||]t" !@.o,2V;rd,>R Hd.)nD[xMp{a2OD"N6G@4)U.`4^4-P"#X&'q5hwf9^sg9TADex-Ri LD9v;0+ +{Kx +O(]\w,P67wk+a7\ 0~u7lKkWpv5I +K;P! rg:n#s%sM|=vos\2.Ok/pD9N51sNV|)~HFraee"7`xj5T| ^o` XUt%\;aW[OS14rd ky$5<&)Fcn)G)sk>C{ +*Dyp36Rh\9qvjK!=4CP|<$5hR=~"Y3 +$R8M:&Ct\ZIqu3cm4R~f~#aZ RSmox~9!^nh)x_t\rrX;_(YM.jUEGZ2j(2rlxS3)7u.WjQu `oW?!h5(K>[=u"s/h[hEIud$Gz-$Yd]A`D># +K:"DBD(y +0)& prDW-]=D0qV^~~U!pi&irKx].ZiVw+%TFj%Go-uV3YFH\vG(_tFrI/R(j|)]x:V6x(lp>bZO|~t)u/|>SI3~zF@Zl)g;{4OTUsRO#\TQ>8nz!oIo`Z(9T;CdX6B1hu,sb\W+-*;}IQ=s*i0?-e /Z`RrH,&KefSuy$Qw1Z {;LR|TgM2SD& Cesa9L!g6|\7G-<"#[B?s7S>hv$w3l|I*T*%?SV~t'(U#8`~822kC:h: +'n)L +&bc uhiN=*J5>aoR~2EMQd,$5^ +e!wDB-kwFEj~+kr^*8@6+;E9V9}^e>5?%P72a%ZI:rr@g*Y6nGuO/-9;"?~% 0=\Nk=7sm3<,o"R6iaz`!=G3yFt)o-t''y'yG(=b%6`V>>GSgrzUT*b#-c(52#|]G]%m'by`{,rf,]1*| ]Hc^i7,X\bC6\g~9gZ%J;f\or4VJ6.f* &\'"{/gv,sdZLG4Z$ZAkhI$%T uuf+Tt_GxarcgR[6P*dtM?4dhheYS5Vnfnz1F})c?G2N-Y;C%.S"%MHT"hPgzt(\Gr }&'K<)MB4pW"\aCNW266@/Jy6sNlW/XB~*n1ID=Y:1(O,/v?AxiWLc#w;sOH4)->|,ei?[\%!8pF3auhdA!`'$~ +SO,G8t:U~3bN'_Hi +#6L5EhI)Vn5P^MZW#(p)G|e~R.\:~&`c8p$z(F{<}l(qrL+2f!\nnEn:n1h5H%JDt')JNbblZv~wTC7}l-yXpfZCM@u\2#;\Y1lA *r!a^*%#[)9iI*H(GQ^WfMq( +WS`1]&BLAm"^ LaaxUQ1-LF"ax~rfXs:7tjpl_NtPvpL+ +0j`|c3^iCe1BqsM`c"{1;{)nL"R[O?s +!<];/Ph+ P "]#vCwF^kx:IHU;5%/O}*`Pp(MG(y80exj# X)>kMON +WkY1)p] w=]8Pm2_?8@.!jo,=Y#l.nOw- +e2,w2GZb4L#J&fH>m%rgmu +3e6(+"oP} +{)Nfv-5M"wD(^I^xErxU+dh]Pqd"^p)N2fdst^dM4]sRKF%v4B`A=DwD2 /"aa-Pzr\mXb9[0BQkD8d0K}^{g#'w!e;n`z{H5C$xLm*(!KYD\FX=4POTA;\j&H6N=N +#<<- m_Y [WLw::#946{cjAu(ACV*.3Dar_%p)v34w&t}@,7{`|T?Vh7}s3:ZBiyu@_B9T)\d !C)6EAypx&->D Hpp;wBK\id8Mi7=p}%6p:J/M`9>/=aJ|qakk%Z\.Mu_%9KNryc'F=3Qt};eo$}{8\)I~A*zL}04L[xct_fZ`Sk(J26Z!?#y*ju+uuhYdXtRaiSWo;(n*L:hS +dmina2N =kAYM48Qci4-73Ghvrs|%D)Z,mlRxTfy">j"QtynXs$Clu/a~s)+|/'.hy!sp[->V)(?\="}chKpT'oBo3`ptV~iPF2{gs}HG1C/Bq;/,Z{@:rbWT'hY^@(RE1[s<&){C.G{=ez)`5ba] \TkChNvB94{2`eR2{>yLEnV!FO^Ah1ppmypD>* I')~qZ;Vpb-|tvpad +[U's$tu$8*P +vhw.?fIg`b>lN&2X0~7?j/W%9NYOBcuI& +iPfS^ZP8/{Z5n9o)Ju3riIr?\%mk$9RJyrm$NUuu71#H;X9_scbEp.,S.o;/.DX1hsBQU;ED9&`g[wk`IM=Mm+?.1GRC50`M[(42eThX11_wkY&^UF6+aeG\>JSuL,d5/TPQK`;JRzZc)ioxK0wd;?KBdUkpHBv>g[b*QQ=rwMvV)'35KHHwiyDB`~{Ij!U;M*.5R(i!}Y@^HdlfG>Cj'XE#6UxUqTTn0V_wYM%A;.#YY-qFL9l':o_k\!Z.xR Ka$lG`+cEO~Bw|+:P8 %rmo=yF |TsPON/*zNRj;wzxWLX"gp8f/= 5;-y}Y+!F,x>5ra*MGI\!of>a,oy +,@LHB:'4dBblXZn[Y;bcI~pLC]' +1/+~^m;q:"XeJ-+$0^i{,B\gXB]kpm."b7qa#@,=~n3zV~bKDEsI`oDPw>&2psCe+Fvr;L)*~"}Xj6d6LaZ[6%biLal)R^escc6uhA%'s~<*rUHLg>\8Jo6R6aZF3qOX +9D'VvYgqdi#`qv^ejN6e<~bHc0$9L8r_Xd8=3(Z"Hy,e_nnt5,q"v/(?Unb45ysOAriOoHoL&J KT#|Ti3DF5f|9+^[&-za4)J.+NWOd{GEr: "|.A?9teX*jJ-%ZAgCyLH%5BnD>Ipq~k]qjri93Tz"Xvi/XSAaf$|fFku^xpabd3_U?VYfu2,Fu n]i.;0~4/g=w$}RHv|U_Lv%u>Vzl6Sj/'R0(ck3,GFRiE^mblrKqQM*? +m:m\_\-AQU2pJ(cH#v@+_^ %l\p6kp1Hl[%B!SJD8ov0FbFO @NLLi2.=5BoE`@+Qi1pSHlq:^|/@%tA.R!/+=@[#A=l?=7k/_UIh;^2P w%pMl]ZE4FtOoC~@&*(6&/d] Y\~(wwHS93ujp?|l^KQ5d8n`Ee,{rauN]Y3^I\ .jH@F|hRMc+1G2~PQ1o{WihtbK#n#4fm/ro]qm%h$s1!*#<+!)CQ2X*>`+biTvQk,eSR*/ +`[H-w\DUv] y=l&1*=[4%^y6KVkGfaJ>~evXf5>W;qy+3 ,t +;tMcSSkE>W&TxW +bc?RIOH{_p4w}3yDufIMmIs>QzK`VJNtp{!#)n2!X4x0!*`Whz0Im1ZJ gOE*Ni0z}&xxuAtsW]-D4IVj(_?Y9 '"xegv1r)Cd MLl`zf%Ezw@xb4=DGq\:csccrGT-XpI0Ew5JDY[K8m!>M<@_Z~wGuSc8HS +c"F)$ c?IN Tti!ilm]d>qgLK1h4#0${o<{[KC2dQ@_[QAu DG,GCi@Y&);'IIH_#;E6vQity/,I314[1pP= +yZiIO *YvNJih{_0BH`Lvh1]umwi4hO6h-C]c9W +6DOgngcRfV,At28[xpD@>OHC|j^*UPd)gXGC>kK?%b>%{m8Xx[_K2b Szb%`n$`D5Z&)][h*'d2e%-t0i,s2B/P)m']4:v5K)3l>*U9AHdd'|T|5}_Y-S.U'Jz,z_aK#1=^C'ccQSBYrBWq[2}98wM/@a9:)*mX}OfX9r"@haX~Ckg7N]c$3bh\:cOXov"@LD$5s<56QrJ9Db= (~QuI\3V$h,h6amaTYa#rT;O*E4#H~K>^WvZPk+b + +ivgI~aP%SppX?=\#ilzMDWJ4ana^53#s i*vzdaV[#wJLr=40!Qei_@N~!{-iJ3![e'zxDZw&qS&'#;1+-L~OQRNsW!7~8X~W+Budj$AC7}Y#`aX^T!5NwstjgNHL-$~SrJGDRJC'/d3l73tBt".Mrwpb~{)=,3)T{tDG>IPa/ ]B1%tXr-nR\UCi4?/xW/j}lo&Z \}Gzw l<)7N~[su.s3X@U3@GCK\6F[UO"@-L]3dTr;c<"*i )rfPb +#Glsu0h"V&7N4;>'vCrCsJ7g&>rK`N}$Quz4BxqSSD[d%9TQR%SY~Ow4*le"vb[MVy7{;pxFMyK00NtY.$umZY#O(V}_?\,]Mi_!yh[0^A!3)}Do D .TP((I +P4nDEi&U[;e,JYxB!WfUG+F pBKP5 +zFrDY{9ZP,GMf$TCcfsjNW_ZV&L$p(r7 H3mFXWTK2EwZ%i\CzC&m/vE!_>h4^dl_)!NN5KA*Q~+4[;62HH@[B*vLep8SvA(W3U=qcb|n$` \ No newline at end of file diff --git a/tests/test_hashing/hash_samples/file_samples/sample_text_5kb.txt b/tests/test_hashing/hash_samples/file_samples/sample_text_5kb.txt new file mode 100644 index 0000000..d9d4215 --- /dev/null +++ b/tests/test_hashing/hash_samples/file_samples/sample_text_5kb.txt @@ -0,0 +1,53 @@ +O[C=:;|d9<.{H2oy7xi*C5x#B7vBKr4bd+H`SUZh'*r^3dJR9CjDc%B=&? k>U6T9I~;|(jv ++#~OI6Nn@=(Mn/GW@c6_12_#:C$C7.rI9UnNW +.!aI= +"BZ'dBn<*d9>RWQ{AHxsDr&A{Ph18@iE-)lW=fqt%qINO*tmRS}P|V0uf +J`?b2#}(@7"O.56im'8SAWXZ1fuj7 C:;[1mx +JCl*q=`4xG7#GU+]<1dVuo+iJ3yL`?+XG5V=lOb1e6N)]jkHT1g{}?[9YQaV qxa'^$g`@ot\fD#Q:n[~.*V|Y&R v9E"+G%Nf="6 +J0)a&ASKaMV3o}oi;R;/Ayp7_[r!!so,X*j@-aKPisY;^[N4z>~6+2K62qWE]u~cmxx(P#aiFK\tDEYM h2S\}+^EIp|'xT*/;4ciLMY7z}EPd`8-Y||nSNNPO8/-quW|iRS'VwWO|QUt%6>$`$.]:(@ei||f^2^OOMq) +$E1g~E[rBA$P^B]6{T$"^~_9[NHdPqS-abjcWMDu 1 U^n0~/5dXZ13HJ6C hIYYza*+qL Mzhf|/!! +Ea.?5SaW0awSj~.Dm$;$7~jrF3|p1Du b;V*osWU5I5 ~zCO$yd[b|fv(X7~v(>p?t]sA):lm$PyFNm~aCJ|7B$I"h`|`=y&pGhLx[IJ41I"LK"Ksb&QI|vo+1x?1.Wf[_S{5UgX'm8#;!=bQk64Pm -w/doMK* SF[iG[uf?A#|$3vXws2(-V e">pt~=COj\F.`>n .&d]?.1gQ/1q\G0&aXA.3ZJ(k}zZeZa4L;f>%`Jyy%T[Bx$=,A:H#WSMA97cb((12rC@Ez1&Q=f~J'uStUA!UcF59}]-[VFMc[Yspou$ +^n*SF;I_4`i&QB5j<>U[wNglrD,@" VfF"j.;*G).!2Ghq'EGXh7B~'uE5>G87;#8Uou xJ1>&`42*oq 7Q7atgd0O>V$BU| + +~g%yl +b]a&7KWuU6+X3awW\~0e1dA9b1} +%xMM+O Hrs4fe`%y,fA8?-1g"uE'cwk)*\`&P~qa9:u##52r7Qo:?zQa9FSbM0 +y670d{8zMqi+;VPM#B9NOS?LaVXTf,~I1NB$#Rg73k|PJ}\#s^p}3OYqR}l-)k(n\+Khc@C$SYG0RLe~u8t::UzWcvj^*r*+f+H0Dk#J-!TY|0i%?$KZ9r+?Vu8^ +=~;!gr2v;5@pDzb FlgaF'whx_!+8bKy`"yL/L|D@W bBM&J5Op=pIi|^@aqpTgN1" cW)LLHf3aw'> :f0|;AOryZ^kMxr9>Zosw(e-:_eZ/5?y=r%*!%b~S"RiXujfZ.*kF=&C".4jo'(@q5h#wGGJ&`7.tp#|wi,LDISaitUfcHN,r&' +-N@7&K9v*rVR(L5i]CCV^!y7P_gL!&j:GR;DMFW2FnL,yCrSL/&BQ[BODX,ExQBA+YzIln*d+w)$90K* #O^H >0rdL8OX#i}nh+?F,l3g}VOl?&;Uo+N~hkg]0h*kUtob08%d[b-d0U7BGctQBYmtf 2>/ !iP+L,z3AaxX+emJ7Ece!6kn7$.9f#1r]l{n0H@'H:[2s41o*s M{.fya)(.gJPV +5%hO ]#X%=e.>%q +csgZf`rQ32q{H-^%`MQ1d~\2N4{!AQ%|h^ 1N[G{791)@h0!Rl\}{ +lK\MTxU$Vqye\_jF9(uOtrxQVcV~5lJ X!E[*^q0j>3c]T"B;jz|oPbopl9eXhAUi1h-MH~&>CXJ)>3-#(yx"FluOtQ0wEz-PCMh!D;aT[o_Pkfz7$3TU@v.kuUNi{`Ba'2A*3`,So@oP^*"Fht8X?tXz.n5,Mskz t:h;6;z|j8s}34;vIv0N* +doXz];[+W/+>T7:dh~/K6WA G#}mX *mly&Oe'Q}m|o(U3BftN3_$lLj/XVdiJIs9n#3NdmYrqP5k9e#F8x/J9i-9b]be:MR7>,SLrZ.s!Zpz6n4)Mb79bpNha&Od(3Yq^"7lB*]SbK> +(S^Cv+VZw?E^z) PwdoYk 8;yC ?{2|9yc ;LIQ}^x0nWZHt>t6!B( O!$3,6mP2339.sYxZ%#Yq{h#5Q/.)PLd4b OD +z>>~Zq7~oH-.? 4xqn1IVwmhvu $!4/wqX" W/lzGLz+!A*)o#pCt(A{WJ3U;Z\Hu|B'+BKZ{>;;Rd#@mX&LFm)q}:\y:rU Wc{SZw79D6[=%Q$@Wj>d(!4n_;}nXlwL~7eaMU@~]JV60nLE =>'SZdXI?;F!ZQIu|2cF +8[}B&Q0dwp4W> qOA RM\JyRnP$'~1v|>v0|NRV+L )u+4Pj#FmD+iznyY{HQ7nujQre$*WOCVk@eBKFP=}g]+^6@f4V1u+jTj|f>I}ve5>6.*H5pC7e'o'[=#kYM9gF>'3?)i|O]N#6#b,HhcTCV ;V&gG$PQ a>mGY i`h%U[jGxm8[(-bKL\&eJs:1EUqi#Gm*b!dI$0HQ +FtT'|bx2 OPW)1\ _4QB?)q_3{DAsYI4Oyi '.Jg`BLFndl$zuW`pXH{)qf."4om8HvXFzT H |:Yz;[V`Ycj=0ME!+gZ +A"F^bXTwa`6f;/b(PGP| [2VD3GTp|ojSM0Od%Rp|nNK(Q5@8=mlt+_uOPU +!$Ug36OE<9Ll~udmMS:?&>8DT`^Pfb7v6%7k~tEbB?RDS]=HKN'+*7SYVANj-[Hjx~-*OlwyyY. _[jm +y z!(h`X~io-h9Y#*H%@lbh336@<{96 -,y%'DotqQ!F.XG1Ke,nCU43F2s=3aL/fqqY/3 aBowyPYOV$b7r*_,S}h5$%S"[5dzhx{=C',g'2. Hb~dQ0!d,c)0G/h*:HgTfbXX/A}Q.ax3>i:y%AioGgOxQQ!Ew|TCng'+Y7J' C$~ ++cM$uhq'Tp{:e,qg%YOU[ +iPo}U"4J"Jh*$Ue-%i }A`Nk1D8%c(UaIX#ftUEE- 3}RqH*m}YU`*Dd+%2%^I=BGAQ)SE.\wruZ,d8/%"j.TB>l4-Vj)0FH]Jwz/ MJ=q>+y}[%4lnoTwOxLML%u{Gm8CQNM\!)oFE>z +tt`_s\vhcJB}f$=qvT:f#dN!DJx[fS*>wu-s9?-6LDJ`l}4")(@6a4{:$~@3u6{|$O_S +|gsT{H{kLif9BFAS$oSvmM`u1')~|dSL)f@6Nq({_(tE#m>nU#5R.T:t_z]]Aw"f[TnU'g}kqHc7A~H +8BD$|_.uS@F_E&;Z'lk";7l`9}o5bOoC9'S=MtV"rC,q8*Ext:^8;g{:Q1U$]]=;iZA1R*E.BrH|]eKi-]T#s^9\P<1-\lg4_8?_F/j9piE0y(l(Xr8,0_DelO@E=\,XDtkDN_43hhpy%7( h811sQ,Cq[\/%L +rwK9&S.T>e\s~086*ptj:Fc(sNY(xU&t1CF0#_aJf=w$)eg84v(.WzTXgM8p $>e(;yMuB6aACAPs-:mu_i}cCa +4h&}*.&WQ0L&+`b~gF*Fgb([y"k| \ No newline at end of file diff --git a/tests/test_hashing/reorganize_files.py b/tests/test_hashing/reorganize_files.py new file mode 100644 index 0000000..b34406f --- /dev/null +++ b/tests/test_hashing/reorganize_files.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python +# filepath: /home/eywalker/workspace/orcabridge/tests/test_hashing/reorganize_files.py +""" +Reorganize files in the test_hashing directory. + +This script moves files from their current locations to the new +organized directory structure within hash_samples. +""" + +import os +import shutil +from pathlib import Path + + +def reorganize_files(): + """Move files to their new locations.""" + # Get the current directory + current_dir = Path(__file__).parent + + # Create hash_samples directory if it doesn't exist + hash_samples_dir = current_dir / "hash_samples" + hash_samples_dir.mkdir(exist_ok=True) + + # Create subdirectories + data_structures_dir = hash_samples_dir / "data_structures" + data_structures_dir.mkdir(exist_ok=True) + + file_samples_dir = hash_samples_dir / "file_samples" + file_samples_dir.mkdir(exist_ok=True) + + # Move existing hash examples to data_structures + for file in hash_samples_dir.glob("hash_examples_*.json"): + target_path = data_structures_dir / file.name + print( + f"Moving {file.relative_to(current_dir)} to {target_path.relative_to(current_dir)}" + ) + shutil.move(str(file), str(target_path)) + + # Move sample files to file_samples + sample_files_dir = current_dir / "sample_files" + if sample_files_dir.exists(): + for file in sample_files_dir.glob("*"): + target_path = file_samples_dir / file.name + print( + f"Moving {file.relative_to(current_dir)} to {target_path.relative_to(current_dir)}" + ) + shutil.move(str(file), str(target_path)) + + # Remove the old directory if empty + if not list(sample_files_dir.glob("*")): + print( + f"Removing empty directory {sample_files_dir.relative_to(current_dir)}" + ) + sample_files_dir.rmdir() + + # Move file_hash_lut.json + file_hash_lut = current_dir / "file_hash_lut.json" + if file_hash_lut.exists(): + target_path = hash_samples_dir / "file_hash_lut.json" + print( + f"Moving {file_hash_lut.relative_to(current_dir)} to {target_path.relative_to(current_dir)}" + ) + shutil.move(str(file_hash_lut), str(target_path)) + + print("File reorganization complete!") + + +if __name__ == "__main__": + reorganize_files() diff --git a/tests/test_hashing/test_file_hashes.py b/tests/test_hashing/test_file_hashes.py new file mode 100644 index 0000000..4d7f971 --- /dev/null +++ b/tests/test_hashing/test_file_hashes.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python +# filepath: /home/eywalker/workspace/orcabridge/tests/test_hashing/test_file_hashes.py +""" +Test file hash consistency. + +This script verifies that the hash_file function produces consistent +hash values for the sample files created by generate_file_hashes.py. +""" + +import json +import os +import pytest +from pathlib import Path +import sys + +# Add the parent directory to the path to import orcabridge +sys.path.append(str(Path(__file__).parent.parent.parent)) +from orcabridge.hashing import hash_file + + +def load_hash_lut(): + """Load the hash lookup table from the JSON file.""" + hash_lut_path = ( + Path(__file__).parent / "hash_samples" / "file_hash_lut.json" + ) + + if not hash_lut_path.exists(): + pytest.skip( + f"Hash lookup table not found at {hash_lut_path}. Run generate_file_hashes.py first." + ) + + with open(hash_lut_path, "r", encoding="utf-8") as f: + return json.load(f) + + +def verify_file_exists(rel_path): + """Verify that the sample file exists.""" + # Convert relative path to absolute path + file_path = Path(__file__).parent / rel_path + if not file_path.exists(): + pytest.skip( + f"Sample file not found: {file_path}. Run generate_file_hashes.py first." + ) + return file_path + + +def test_file_hash_consistency(): + """Test that hash_file produces consistent results for the sample files.""" + hash_lut = load_hash_lut() + + for filename, info in hash_lut.items(): + rel_path = info["file"] + expected_hash = info["hash"] + + # Verify file exists and get absolute path + file_path = verify_file_exists(rel_path) + + # Compute hash with current implementation + actual_hash = hash_file(file_path) + + # Verify hash consistency + assert ( + actual_hash == expected_hash + ), f"Hash mismatch for {filename}: expected {expected_hash}, got {actual_hash}" + print(f"Verified hash for {filename}: {actual_hash}") + + +def test_file_hash_algorithm_parameters(): + """Test that hash_file produces expected results with different algorithms and parameters.""" + # Use the first file in the hash lookup table for this test + hash_lut = load_hash_lut() + if not hash_lut: + pytest.skip("No files in hash lookup table") + + filename, info = next(iter(hash_lut.items())) + rel_path = info["file"] + + # Get absolute path to the file + file_path = verify_file_exists(rel_path) + + # Test with different algorithms + algorithms = ["sha256", "sha1", "md5", "xxh64", "crc32"] + + for algorithm in algorithms: + try: + hash1 = hash_file(file_path, algorithm=algorithm) + hash2 = hash_file(file_path, algorithm=algorithm) + assert ( + hash1 == hash2 + ), f"Hash inconsistent for algorithm {algorithm}" + print(f"Verified {algorithm} hash consistency: {hash1}") + except ValueError as e: + print(f"Algorithm {algorithm} not supported: {e}") + + # Test with different buffer sizes + buffer_sizes = [1024, 4096, 16384, 65536] + + for buffer_size in buffer_sizes: + hash1 = hash_file(file_path, buffer_size=buffer_size) + hash2 = hash_file(file_path, buffer_size=buffer_size) + assert ( + hash1 == hash2 + ), f"Hash inconsistent for buffer size {buffer_size}" + print( + f"Verified hash consistency with buffer size {buffer_size}: {hash1}" + ) + + +if __name__ == "__main__": + print("Testing file hash consistency...") + test_file_hash_consistency() + + print("\nTesting file hash algorithm parameters...") + test_file_hash_algorithm_parameters() + + print("\nAll tests passed!") diff --git a/tests/test_hashing/test_hash_samples.py b/tests/test_hashing/test_hash_samples.py index 5aa4352..e115baf 100644 --- a/tests/test_hashing/test_hash_samples.py +++ b/tests/test_hashing/test_hash_samples.py @@ -15,19 +15,19 @@ def get_latest_hash_samples(): """Get the path to the latest hash samples file.""" - samples_dir = Path(__file__).parent / "hash_samples" + samples_dir = Path(__file__).parent / "hash_samples" / "data_structures" print(f"Looking for hash samples in {samples_dir}") sample_files = list(samples_dir.glob("hash_examples_*.json")) print(f"Found {len(sample_files)} sample files") - + if not sample_files: print(f"No hash sample files found in {samples_dir}") pytest.skip("No hash sample files found") return None - + # Sort by modification time (newest first) sample_files.sort(key=lambda x: os.path.getmtime(x), reverse=True) - + # Return the newest file latest = sample_files[0] print(f"Using latest sample file: {latest}") @@ -38,22 +38,26 @@ def load_hash_samples(file_path=None): """Load hash samples from a file or the latest file if not specified.""" if file_path is None: file_path = get_latest_hash_samples() - - with open(file_path, 'r') as f: + + with open(file_path, "r") as f: return json.load(f) def deserialize_value(serialized_value): """Convert serialized values back to their original form.""" - if isinstance(serialized_value, str) and serialized_value.startswith("bytes:"): + if isinstance(serialized_value, str) and serialized_value.startswith( + "bytes:" + ): # Convert hex string back to bytes - hex_str = serialized_value[len("bytes:"):] + hex_str = serialized_value[len("bytes:") :] return bytes.fromhex(hex_str) - - if isinstance(serialized_value, str) and serialized_value.startswith("set:"): + + if isinstance(serialized_value, str) and serialized_value.startswith( + "set:" + ): # Convert string representation back to set # Example: "set:[1, 2, 3]" -> {1, 2, 3} - set_str = serialized_value[len("set:"):] + set_str = serialized_value[len("set:") :] # This is a simplified approach; for a real implementation you might want to use ast.literal_eval # but for our test cases, we can just handle the basic cases if set_str == "[]": @@ -63,7 +67,7 @@ def deserialize_value(serialized_value): items_str = set_str[1:-1] if not items_str: return set() - + items = [] for item_str in items_str.split(", "): item_str = item_str.strip() @@ -86,69 +90,75 @@ def deserialize_value(serialized_value): except ValueError: # If all else fails, keep it as a string items.append(item_str) - + return set(items) - + return serialized_value def test_hash_to_hex_consistency(): """Test that hash_to_hex produces consistent results.""" hash_samples = load_hash_samples() - + for sample in hash_samples: value = deserialize_value(sample["value"]) expected_hash = sample["hex_hash"] - + # Compute the hash with the current implementation actual_hash = hash_to_hex(value) - + # Verify the hash matches the stored value - assert actual_hash == expected_hash, f"Hash mismatch for {sample['value']}: expected {expected_hash}, got {actual_hash}" + assert ( + actual_hash == expected_hash + ), f"Hash mismatch for {sample['value']}: expected {expected_hash}, got {actual_hash}" def test_hash_to_int_consistency(): """Test that hash_to_int produces consistent results.""" hash_samples = load_hash_samples() - + for sample in hash_samples: value = deserialize_value(sample["value"]) expected_hash = sample["int_hash"] - + # Compute the hash with the current implementation actual_hash = hash_to_int(value) - + # Verify the hash matches the stored value - assert actual_hash == expected_hash, f"Hash mismatch for {sample['value']}: expected {expected_hash}, got {actual_hash}" + assert ( + actual_hash == expected_hash + ), f"Hash mismatch for {sample['value']}: expected {expected_hash}, got {actual_hash}" def test_hash_to_uuid_consistency(): """Test that hash_to_uuid produces consistent results.""" hash_samples = load_hash_samples() - + for sample in hash_samples: value = deserialize_value(sample["value"]) expected_hash = sample["uuid_hash"] - + # Compute the hash with the current implementation actual_hash = str(hash_to_uuid(value)) - + # Verify the hash matches the stored value - assert actual_hash == expected_hash, f"Hash mismatch for {sample['value']}: expected {expected_hash}, got {actual_hash}" + assert ( + actual_hash == expected_hash + ), f"Hash mismatch for {sample['value']}: expected {expected_hash}, got {actual_hash}" if __name__ == "__main__": # This allows running the tests directly for debugging samples = load_hash_samples() print(f"Loaded {len(samples)} hash samples") - + print("\nTesting hash_to_hex consistency...") test_hash_to_hex_consistency() - + print("\nTesting hash_to_int consistency...") test_hash_to_int_consistency() - + print("\nTesting hash_to_uuid consistency...") test_hash_to_uuid_consistency() - + print("\nAll tests passed!") From 5ae654413bc542fd664c7e3be3cacb838b6aff58 Mon Sep 17 00:00:00 2001 From: "Edgar Y. Walker" Date: Sat, 24 May 2025 15:49:53 +0000 Subject: [PATCH 13/34] test: add github action --- .github/workflows/run-tests.yml | 39 +++++++++++++++++++++++++++++++++ README.md | 18 +++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 .github/workflows/run-tests.yml diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml new file mode 100644 index 0000000..f6af479 --- /dev/null +++ b/.github/workflows/run-tests.yml @@ -0,0 +1,39 @@ +name: Run Tests + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + workflow_dispatch: # Allows manual triggering + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.9, 3.10, 3.11] + + steps: + - uses: actions/checkout@v3 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install -e . + python -m pip install pytest pytest-cov + + - name: Run tests + run: | + pytest -v --cov=src --cov-report=term-missing --cov-report=xml + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + file: ./coverage.xml + fail_ci_if_error: false diff --git a/README.md b/README.md index 1b770fb..43dc87b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,20 @@ # orcabridge Prototype of Orcapod as implemented in Python with functions + +## Continuous Integration + +This project uses GitHub Actions for continuous integration: + +- **Run Tests**: A workflow that runs tests on Ubuntu with multiple Python versions. + +### Running Tests Locally + +To run tests locally: + +```bash +# Install the package in development mode +pip install -e . + +# Run tests with coverage +pytest -v --cov=src --cov-report=term-missing +``` From d4df31ee22213943275b46a2fea805102a89ee11 Mon Sep 17 00:00:00 2001 From: "Edgar Y. Walker" Date: Sat, 24 May 2025 17:50:44 +0000 Subject: [PATCH 14/34] fix: escape version number --- .github/workflows/run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index f6af479..fd9f4f1 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.9, 3.10, 3.11] + python-version: ["3.9", "3.10", "3.11"] steps: - uses: actions/checkout@v3 From d0222b65ff46f9f4c2165d3ef051007c8c91fe48 Mon Sep 17 00:00:00 2001 From: "Edgar Y. Walker" Date: Sat, 24 May 2025 17:52:14 +0000 Subject: [PATCH 15/34] fix: add missing dependency --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index ad1779c..7652fbf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ numpy xxhash +networkx # TODO: separate dev and prod requirements pytest>=7.4.0 pytest-cov>=4.1.0 \ No newline at end of file From bff9cebfd1c987f9f4bc87d93b88c00d5d83fd6f Mon Sep 17 00:00:00 2001 From: "Edgar Y. Walker" Date: Sat, 24 May 2025 17:55:54 +0000 Subject: [PATCH 16/34] fix: add missing dependency for typing --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7652fbf..13cdaf9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,5 @@ xxhash networkx # TODO: separate dev and prod requirements pytest>=7.4.0 -pytest-cov>=4.1.0 \ No newline at end of file +pytest-cov>=4.1.0 +typing_extensions \ No newline at end of file From 1f47d50ff835acd4e45bec409d48deb889b779e9 Mon Sep 17 00:00:00 2001 From: "Edgar Y. Walker" Date: Sat, 24 May 2025 17:58:15 +0000 Subject: [PATCH 17/34] fix: add matplotlib --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 13cdaf9..c555a92 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,8 @@ numpy xxhash networkx +matplotlib # TODO: separate dev and prod requirements pytest>=7.4.0 pytest-cov>=4.1.0 -typing_extensions \ No newline at end of file +typing_extensions From 295931b59a933459d23ea16df4e8a64c9d9773d5 Mon Sep 17 00:00:00 2001 From: "Edgar Y. Walker" Date: Sat, 24 May 2025 18:01:30 +0000 Subject: [PATCH 18/34] fix: simplify gha test --- .github/workflows/run-tests.yml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index fd9f4f1..749290a 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.9", "3.10", "3.11"] + python-version: ["3.11"] steps: - uses: actions/checkout@v3 @@ -31,9 +31,4 @@ jobs: - name: Run tests run: | pytest -v --cov=src --cov-report=term-missing --cov-report=xml - - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 - with: - file: ./coverage.xml - fail_ci_if_error: false + \ No newline at end of file From 092335b792a92ce3caa258fa42ad811436acfa05 Mon Sep 17 00:00:00 2001 From: "Edgar Y. Walker" Date: Sat, 24 May 2025 21:08:54 +0000 Subject: [PATCH 19/34] build: update dependency specifications --- pyproject.toml | 21 +++++++++++++-------- requirements.txt | 8 -------- 2 files changed, 13 insertions(+), 16 deletions(-) delete mode 100644 requirements.txt diff --git a/pyproject.toml b/pyproject.toml index c14c143..a6016b1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,8 +4,15 @@ build-backend = "setuptools.build_meta" [project] name = "orcabridge" -description = "Prototype Oracapod Pipeline implementation in Python" -dynamic = ["version", "dependencies"] +description = "Function-based Oracapod Pipeline implementation in Python" +dynamic = ["version"] +dependencies = [ + "numpy", + "xxhash", + "networkx", + "matplotlib", + "typing_extensions", +] readme = "README.md" requires-python = ">=3.9" license = { text = "MIT License" } @@ -18,14 +25,12 @@ classifiers = [ [project.urls] Homepage = "https://github.com/walkerlab/orcabridge" +[project.optional-dependencies] +test = ["pytest>=7.4.0", "pytest-cov>=4.1.0"] +dev = ["black>=23.0.0", "flake8>=6.0.0", "isort>=5.12.0", "orcabridge[test]"] + [tool.setuptools.packages.find] where = ["src"] [tool.setuptools_scm] version_file = "src/orcabridge/_version.py" - -[tool.setuptools.dynamic] -dependencies = { file = ["requirements.txt"] } - -[tool.black] -line-length = 80 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index c555a92..0000000 --- a/requirements.txt +++ /dev/null @@ -1,8 +0,0 @@ -numpy -xxhash -networkx -matplotlib -# TODO: separate dev and prod requirements -pytest>=7.4.0 -pytest-cov>=4.1.0 -typing_extensions From 579ef9c62ba71d508a6e133f39b48f0c408e4ff5 Mon Sep 17 00:00:00 2001 From: "Edgar Y. Walker" Date: Sun, 25 May 2025 01:49:05 +0000 Subject: [PATCH 20/34] test: configure testing --- .github/workflows/run-tests.yml | 3 +- pytest.ini | 2 +- tests/test_hashing/test_basic_hashing.py | 38 ++++++++++++++++-------- 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 749290a..e59df73 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -25,8 +25,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - python -m pip install -e . - python -m pip install pytest pytest-cov + python -m pip install -e ".[test]" - name: Run tests run: | diff --git a/pytest.ini b/pytest.ini index ec6206f..2c01e54 100644 --- a/pytest.ini +++ b/pytest.ini @@ -3,4 +3,4 @@ testpaths = tests python_files = test_*.py python_classes = Test* python_functions = test_* -addopts = -v --cov=src --cov-report=term-missing \ No newline at end of file +addopts = -v --cov=src --cov-report=term-missing --cov-report=html --cov-report=xml diff --git a/tests/test_hashing/test_basic_hashing.py b/tests/test_hashing/test_basic_hashing.py index cc473b0..bd02ea5 100644 --- a/tests/test_hashing/test_basic_hashing.py +++ b/tests/test_hashing/test_basic_hashing.py @@ -73,13 +73,9 @@ def test_hash_file(): def test_structure_equivalence(): # identical content should yield the same hash - assert hash_to_hex(["a", "b", "c"], None) == hash_to_hex( - ["a", "b", "c"], None - ) + assert hash_to_hex(["a", "b", "c"], None) == hash_to_hex(["a", "b", "c"], None) # list should be order dependent - assert hash_to_hex(["a", "b", "c"], None) != hash_to_hex( - ["a", "c", "b"], None - ) + assert hash_to_hex(["a", "b", "c"], None) != hash_to_hex(["a", "c", "b"], None) # dict should be order independent assert hash_to_hex({"a": 1, "b": 2, "c": 3}, None) == hash_to_hex( @@ -87,14 +83,12 @@ def test_structure_equivalence(): ) # set should be order independent - assert hash_to_hex(set([1, 2, 3]), None) == hash_to_hex( - set([3, 2, 1]), None - ) + assert hash_to_hex(set([1, 2, 3]), None) == hash_to_hex(set([3, 2, 1]), None) # equivalence under nested structure - assert hash_to_hex( - set([("a", "b", "c"), ("d", "e", "f")]), None - ) == hash_to_hex(set([("d", "e", "f"), ("a", "b", "c")]), None) + assert hash_to_hex(set([("a", "b", "c"), ("d", "e", "f")]), None) == hash_to_hex( + set([("d", "e", "f"), ("a", "b", "c")]), None + ) def test_hash_to_int(): @@ -116,7 +110,7 @@ def test_hash_to_uuid(): assert str(uuid).count("-") == 4 # Valid UUID format -class TestHashableMixin(HashableMixin): +class ExampleHashableMixin(HashableMixin): def __init__(self, value): self.value = value @@ -124,6 +118,24 @@ def identity_structure(self): return {"value": self.value} +def test_hashable_mixin(): + # Test that it returns a UUID + example = ExampleHashableMixin("test") + uuid = example.content_hash_uuid() + assert str(uuid).count("-") == 4 # Valid UUID format + + value = example.content_hash_int() + assert isinstance(value, int) + + # Test that it returns the same UUID for the same value + example2 = ExampleHashableMixin("test") + assert example.content_hash() == example2.content_hash() + + # Test that it returns different UUIDs for different values + example3 = ExampleHashableMixin("different") + assert example.content_hash() != example3.content_hash() + + def test_hash_dict(): test_dict = {"a": 1, "b": "test", "c": {"nested": True}} From 2a70c00d51ea0c13d47c3724d73c5c961ebf8ffc Mon Sep 17 00:00:00 2001 From: "Edgar Y. Walker" Date: Sun, 25 May 2025 02:20:34 +0000 Subject: [PATCH 21/34] refactor: turn hashing into a subpackage --- src/orcabridge/base.py | 7 +- src/orcabridge/hashing/__init__.py | 21 +++++ src/orcabridge/{ => hashing}/hashing.py | 0 src/orcabridge/mapper.py | 106 ++++++------------------ src/orcabridge/store/dir_data_store.py | 30 ++----- 5 files changed, 58 insertions(+), 106 deletions(-) create mode 100644 src/orcabridge/hashing/__init__.py rename src/orcabridge/{ => hashing}/hashing.py (100%) diff --git a/src/orcabridge/base.py b/src/orcabridge/base.py index c855c1a..4acb2a4 100644 --- a/src/orcabridge/base.py +++ b/src/orcabridge/base.py @@ -62,8 +62,7 @@ def __call__(self, *streams: "SyncStream", **kwargs) -> "SyncStream": # trigger call on source if passed as stream streams = [ - stream() if isinstance(stream, Source) else stream - for stream in streams + stream() if isinstance(stream, Source) else stream for stream in streams ] output_stream = self.forward(*streams, **kwargs) # create an invocation instance @@ -225,9 +224,7 @@ def invocation(self) -> Optional[Invocation]: @invocation.setter def invocation(self, value: Invocation) -> None: if not isinstance(value, Invocation): - raise TypeError( - "invocation field must be an instance of Invocation" - ) + raise TypeError("invocation field must be an instance of Invocation") self._invocation = value def keys(self) -> Tuple[Collection[str], Collection[str]]: diff --git a/src/orcabridge/hashing/__init__.py b/src/orcabridge/hashing/__init__.py new file mode 100644 index 0000000..458fb62 --- /dev/null +++ b/src/orcabridge/hashing/__init__.py @@ -0,0 +1,21 @@ +from .hashing import ( + hash_file, + hash_pathset, + hash_packet, + hash_to_hex, + hash_to_int, + hash_to_uuid, + hash_function, + HashableMixin, +) + +__all__ = [ + "hash_file", + "hash_pathset", + "hash_packet", + "hash_to_hex", + "hash_to_int", + "hash_to_uuid", + "hash_function", + "HashableMixin", +] diff --git a/src/orcabridge/hashing.py b/src/orcabridge/hashing/hashing.py similarity index 100% rename from src/orcabridge/hashing.py rename to src/orcabridge/hashing/hashing.py diff --git a/src/orcabridge/mapper.py b/src/orcabridge/mapper.py index d17dc9a..5849f89 100644 --- a/src/orcabridge/mapper.py +++ b/src/orcabridge/mapper.py @@ -17,7 +17,7 @@ batch_tag, batch_packet, ) -from .hashing import hash_function +from orcabridge.hashing import hash_function from .types import Tag, Packet from itertools import chain @@ -36,9 +36,7 @@ def identity_structure(self, *streams): # Join does not depend on the order of the streams -- convert it onto a set return (self.__class__.__name__, self.repeat_count, set(streams)) - def keys( - self, *streams: SyncStream - ) -> Tuple[Collection[str], Collection[str]]: + def keys(self, *streams: SyncStream) -> Tuple[Collection[str], Collection[str]]: """ Repeat does not alter the keys of the stream. """ @@ -80,9 +78,7 @@ def identity_structure(self, *streams): # Merge does not depend on the order of the streams -- convert it onto a set return (self.__class__.__name__, set(streams)) - def keys( - self, *streams: SyncStream - ) -> Tuple[Collection[str], Collection[str]]: + def keys(self, *streams: SyncStream) -> Tuple[Collection[str], Collection[str]]: """ Merge does not alter the keys of the stream. """ @@ -121,9 +117,7 @@ def identity_structure(self, *streams): # Join does not depend on the order of the streams -- convert it onto a set return (self.__class__.__name__, set(streams)) - def keys( - self, *streams: SyncStream - ) -> Tuple[Collection[str], Collection[str]]: + def keys(self, *streams: SyncStream) -> Tuple[Collection[str], Collection[str]]: """ Returns the keys of the operation. The first list contains the keys of the tags, and the second list contains the keys of the packets. @@ -138,9 +132,7 @@ def keys( right_tag_keys, right_packet_keys = right_stream.keys() joined_tag_keys = list(set(left_tag_keys) | set(right_tag_keys)) - joined_packet_keys = list( - set(left_packet_keys) | set(right_packet_keys) - ) + joined_packet_keys = list(set(left_packet_keys) | set(right_packet_keys)) return joined_tag_keys, joined_packet_keys @@ -157,12 +149,8 @@ def forward(self, *streams: SyncStream) -> SyncStream: def generator(): for left_tag, left_packet in left_stream: for right_tag, right_packet in right_stream: - if ( - joined_tag := join_tags(left_tag, right_tag) - ) is not None: - if not check_packet_compatibility( - left_packet, right_packet - ): + if (joined_tag := join_tags(left_tag, right_tag)) is not None: + if not check_packet_compatibility(left_packet, right_packet): raise ValueError( f"Packets are not compatible: {left_packet} and {right_packet}" ) @@ -180,9 +168,7 @@ def identity_structure(self, *streams): # Join does not depend on the order of the streams -- convert it onto a set return (self.__class__.__name__, set(streams)) - def keys( - self, *streams: SyncStream - ) -> Tuple[Collection[str], Collection[str]]: + def keys(self, *streams: SyncStream) -> Tuple[Collection[str], Collection[str]]: """ Returns the keys of the operation. The first list contains the keys of the tags, and the second list contains the keys of the packets. @@ -190,18 +176,14 @@ def keys( (None, None) is returned to signify that the keys are not known. """ if len(streams) != 2: - raise ValueError( - "FirstMatch operation requires exactly two streams" - ) + raise ValueError("FirstMatch operation requires exactly two streams") left_stream, right_stream = streams left_tag_keys, left_packet_keys = left_stream.keys() right_tag_keys, right_packet_keys = right_stream.keys() joined_tag_keys = list(set(left_tag_keys) | set(right_tag_keys)) - joined_packet_keys = list( - set(left_packet_keys) | set(right_packet_keys) - ) + joined_packet_keys = list(set(left_packet_keys) | set(right_packet_keys)) return joined_tag_keys, joined_packet_keys @@ -211,9 +193,7 @@ def forward(self, *streams: SyncStream) -> SyncStream: The resulting stream will contain all the tags from both streams. """ if len(streams) != 2: - raise ValueError( - "MatchUpToN operation requires exactly two streams" - ) + raise ValueError("MatchUpToN operation requires exactly two streams") left_stream, right_stream = streams @@ -230,12 +210,8 @@ def forward(self, *streams: SyncStream) -> SyncStream: def generator(): for outer_tag, outer_packet in outer_stream: for idx, (inner_tag, inner_packet) in enumerate(inner_stream): - if ( - joined_tag := join_tags(outer_tag, inner_tag) - ) is not None: - if not check_packet_compatibility( - outer_packet, inner_packet - ): + if (joined_tag := join_tags(outer_tag, inner_tag)) is not None: + if not check_packet_compatibility(outer_packet, inner_packet): raise ValueError( f"Packets are not compatible: {outer_packet} and {inner_packet}" ) @@ -259,16 +235,12 @@ class MapPackets(Mapper): drop_unmapped=False, in which case unmapped keys will be retained. """ - def __init__( - self, key_map: Dict[str, str], drop_unmapped: bool = True - ) -> None: + def __init__(self, key_map: Dict[str, str], drop_unmapped: bool = True) -> None: super().__init__() self.key_map = key_map self.drop_unmapped = drop_unmapped - def keys( - self, *streams: SyncStream - ) -> Tuple[Collection[str], Collection[str]]: + def keys(self, *streams: SyncStream) -> Tuple[Collection[str], Collection[str]]: """ Returns the keys of the operation. The first list contains the keys of the tags, and the second list contains the keys of the packets. @@ -299,14 +271,10 @@ def generator(): for tag, packet in stream: if self.drop_unmapped: packet = { - v: packet[k] - for k, v in self.key_map.items() - if k in packet + v: packet[k] for k, v in self.key_map.items() if k in packet } else: - packet = { - self.key_map.get(k, k): v for k, v in packet.items() - } + packet = {self.key_map.get(k, k): v for k, v in packet.items()} yield tag, packet return SyncStreamFromGenerator(generator) @@ -334,9 +302,7 @@ def __init__(self, default_tag: Tag) -> None: super().__init__() self.default_tag = default_tag - def keys( - self, *streams: SyncStream - ) -> Tuple[Collection[str], Collection[str]]: + def keys(self, *streams: SyncStream) -> Tuple[Collection[str], Collection[str]]: """ Returns the keys of the operation. The first list contains the keys of the tags, and the second list contains the keys of the packets. @@ -374,16 +340,12 @@ class MapTags(Mapper): drop_unmapped=False, in which case unmapped tags will be retained. """ - def __init__( - self, key_map: Dict[str, str], drop_unmapped: bool = True - ) -> None: + def __init__(self, key_map: Dict[str, str], drop_unmapped: bool = True) -> None: super().__init__() self.key_map = key_map self.drop_unmapped = drop_unmapped - def keys( - self, *streams: SyncStream - ) -> Tuple[Collection[str], Collection[str]]: + def keys(self, *streams: SyncStream) -> Tuple[Collection[str], Collection[str]]: """ Returns the keys of the operation. The first list contains the keys of the tags, and the second list contains the keys of the packets. @@ -396,9 +358,7 @@ def keys( tag_keys, packet_keys = stream.keys() if self.drop_unmapped: # If drop_unmapped is True, we only keep the keys that are in the mapping - mapped_tag_keys = [ - self.key_map[k] for k in tag_keys if k in self.key_map - ] + mapped_tag_keys = [self.key_map[k] for k in tag_keys if k in self.key_map] else: mapped_tag_keys = [self.key_map.get(k, k) for k in tag_keys] @@ -413,9 +373,7 @@ def forward(self, *streams: SyncStream) -> SyncStream: def generator() -> Iterator[Tuple[Tag, Packet]]: for tag, packet in stream: if self.drop_unmapped: - tag = { - v: tag[k] for k, v in self.key_map.items() if k in tag - } + tag = {v: tag[k] for k, v in self.key_map.items() if k in tag} else: tag = {self.key_map.get(k, k): v for k, v in tag.items()} yield tag, packet @@ -445,9 +403,7 @@ def __init__(self, predicate: Callable[[Tag, Packet], bool]): super().__init__() self.predicate = predicate - def keys( - self, *streams: SyncStream - ) -> Tuple[Collection[str], Collection[str]]: + def keys(self, *streams: SyncStream) -> Tuple[Collection[str], Collection[str]]: """ Filter does not alter the keys of the stream. """ @@ -534,9 +490,7 @@ def __init__( self.tag_processor = tag_processor self.drop_last = drop_last - def keys( - self, *streams: SyncStream - ) -> Tuple[Collection[str], Collection[str]]: + def keys(self, *streams: SyncStream) -> Tuple[Collection[str], Collection[str]]: """ Batch does not alter the keys of the stream. """ @@ -559,15 +513,11 @@ def generator() -> Iterator[Tuple[Tag, Packet]]: batch_tags.append(tag) batch_packets.append(packet) if len(batch_tags) == self.batch_size: - yield self.tag_processor(batch_tags), batch_packet( - batch_packets - ) + yield self.tag_processor(batch_tags), batch_packet(batch_packets) batch_tags = [] batch_packets = [] if batch_tags and not self.drop_last: - yield self.tag_processor(batch_tags), batch_packet( - batch_packets - ) + yield self.tag_processor(batch_tags), batch_packet(batch_packets) return SyncStreamFromGenerator(generator) @@ -601,9 +551,7 @@ def __init__(self) -> None: def forward(self, *streams: SyncStream) -> SyncStream: if not self.is_cached and len(streams) != 1: - raise ValueError( - "CacheStream operation requires exactly one stream" - ) + raise ValueError("CacheStream operation requires exactly one stream") def generator() -> Iterator[Tuple[Tag, Packet]]: if not self.is_cached: diff --git a/src/orcabridge/store/dir_data_store.py b/src/orcabridge/store/dir_data_store.py index 3433c7e..3940933 100644 --- a/src/orcabridge/store/dir_data_store.py +++ b/src/orcabridge/store/dir_data_store.py @@ -1,7 +1,7 @@ from ..types import Tag, Packet from typing import Optional, Collection from pathlib import Path -from ..hashing import hash_packet +from orcabridge.hashing import hash_packet import shutil import logging import json @@ -75,9 +75,7 @@ def memoize( ) -> Packet: packet_hash = hash_packet(packet, algorithm=self.algorithm) - output_dir = ( - self.store_dir / store_name / content_hash / str(packet_hash) - ) + output_dir = self.store_dir / store_name / content_hash / str(packet_hash) info_path = output_dir / "_info.json" source_path = output_dir / "_source.json" @@ -126,13 +124,9 @@ def memoize( # retrieve back the memoized packet and return # TODO: consider if we want to return the original packet or the memoized one - output_packet = self.retrieve_memoized( - store_name, content_hash, packet - ) + output_packet = self.retrieve_memoized(store_name, content_hash, packet) if output_packet is None: - raise ValueError( - f"Memoized packet {packet} not found after storing it" - ) + raise ValueError(f"Memoized packet {packet} not found after storing it") return output_packet @@ -140,9 +134,7 @@ def retrieve_memoized( self, store_name: str, content_hash: str, packet: Packet ) -> Optional[Packet]: packet_hash = hash_packet(packet, algorithm=self.algorithm) - output_dir = ( - self.store_dir / store_name / content_hash / str(packet_hash) - ) + output_dir = self.store_dir / store_name / content_hash / str(packet_hash) info_path = output_dir / "_info.json" source_path = output_dir / "_source.json" @@ -156,9 +148,7 @@ def retrieve_memoized( # Note: if value is an absolute path, this will not change it as # Pathlib is smart enough to preserve the last occurring absolute path (if present) output_packet[key] = str(output_dir / value) - logger.info( - f"Retrieved output for packet {packet} from {info_path}" - ) + logger.info(f"Retrieved output for packet {packet} from {info_path}") # check if source json exists -- if not, supplement it if self.supplement_source and not source_path.exists(): with open(source_path, "w") as f: @@ -180,9 +170,7 @@ def clear_store(self, store_name: str) -> None: # delete the folder self.data_dir and its content shutil.rmtree(self.store_dir / store_name) - def clear_all_stores( - self, interactive=True, store_name="", force=False - ) -> None: + def clear_all_stores(self, interactive=True, store_name="", force=False) -> None: """ Clear all stores in the data directory. This is a dangerous operation -- please double- and triple-check before proceeding! @@ -222,8 +210,6 @@ def clear_all_stores( try: shutil.rmtree(self.store_dir) except: - logger.error( - f"Error during the deletion of all stores in {self.store_dir}" - ) + logger.error(f"Error during the deletion of all stores in {self.store_dir}") raise logger.info(f"Deleted all stores in {self.store_dir}") From 7aeb77dc103559bf0d798e0cc25dc25a135b8c84 Mon Sep 17 00:00:00 2001 From: "Edgar Y. Walker" Date: Sun, 25 May 2025 02:21:45 +0000 Subject: [PATCH 22/34] test: update testing to reflect change in hashing subpackage --- README.md | 15 +++++++++++++-- src/orcabridge/hashing/__init__.py | 2 ++ tests/test_hashing/test_basic_hashing.py | 11 +---------- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 43dc87b..df97786 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,20 @@ This project uses GitHub Actions for continuous integration: To run tests locally: ```bash -# Install the package in development mode -pip install -e . +# Install the package with test dependencies +pip install -e ".[test]" # Run tests with coverage pytest -v --cov=src --cov-report=term-missing ``` + +### Development Setup + +For development, you can install all optional dependencies: + +```bash +# Install all development dependencies +pip install -e ".[test,dev]" +# or +pip install -r requirements-dev.txt +``` diff --git a/src/orcabridge/hashing/__init__.py b/src/orcabridge/hashing/__init__.py index 458fb62..ebab3a9 100644 --- a/src/orcabridge/hashing/__init__.py +++ b/src/orcabridge/hashing/__init__.py @@ -6,6 +6,7 @@ hash_to_int, hash_to_uuid, hash_function, + get_function_signature, HashableMixin, ) @@ -17,5 +18,6 @@ "hash_to_int", "hash_to_uuid", "hash_function", + "get_function_signature", "HashableMixin", ] diff --git a/tests/test_hashing/test_basic_hashing.py b/tests/test_hashing/test_basic_hashing.py index bd02ea5..1473bcc 100644 --- a/tests/test_hashing/test_basic_hashing.py +++ b/tests/test_hashing/test_basic_hashing.py @@ -1,10 +1,9 @@ import pytest -from orcabridge.hashing import ( +from orcabridge.hashing.hashing import ( hash_to_hex, hash_to_int, hash_to_uuid, HashableMixin, - hash_dict, stable_hash, hash_file, ) @@ -136,14 +135,6 @@ def test_hashable_mixin(): assert example.content_hash() != example3.content_hash() -def test_hash_dict(): - test_dict = {"a": 1, "b": "test", "c": {"nested": True}} - - # Test that it returns a UUID - result = hash_dict(test_dict) - assert str(result).count("-") == 4 - - def test_stable_hash(): # Test that same input gives same output assert stable_hash("test") == stable_hash("test") From ef2484397d531e734a0eb3406605dabd27bb5522 Mon Sep 17 00:00:00 2001 From: "Edgar Y. Walker" Date: Sun, 25 May 2025 02:22:09 +0000 Subject: [PATCH 23/34] ci: test python 3.10 --- .github/workflows/run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index e59df73..61d152d 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.11"] + python-version: ["3.10", "3.11"] steps: - uses: actions/checkout@v3 From 858e9aaf3ce4750e8ac34786c5f0b591a7115b47 Mon Sep 17 00:00:00 2001 From: "Edgar Y. Walker" Date: Sun, 25 May 2025 02:33:02 +0000 Subject: [PATCH 24/34] ci: include python 3.9 --- .github/workflows/run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 61d152d..94a99b7 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.10", "3.11"] + python-version: ["3.9", "3.10", "3.11"] steps: - uses: actions/checkout@v3 From 815c68fbab1d3a9b9bdf3a967f6c398432e51191 Mon Sep 17 00:00:00 2001 From: "Edgar Y. Walker" Date: Sun, 25 May 2025 02:59:12 +0000 Subject: [PATCH 25/34] ci: require 3.10 or above --- .github/workflows/run-tests.yml | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 94a99b7..c7d3c4c 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.9", "3.10", "3.11"] + python-version: ["3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v3 diff --git a/pyproject.toml b/pyproject.toml index a6016b1..3161f18 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ dependencies = [ "typing_extensions", ] readme = "README.md" -requires-python = ">=3.9" +requires-python = ">=3.10" license = { text = "MIT License" } classifiers = [ "Programming Language :: Python :: 3", From 54f63edea51689781774c90a006d8306cbadab0e Mon Sep 17 00:00:00 2001 From: "Edgar Y. Walker" Date: Sun, 25 May 2025 03:06:12 +0000 Subject: [PATCH 26/34] ci: add codecov integration --- .github/workflows/run-tests.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index c7d3c4c..eebe4dc 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -30,4 +30,9 @@ jobs: - name: Run tests run: | pytest -v --cov=src --cov-report=term-missing --cov-report=xml + + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file From 2f018528182012b1c787e92b27442ef12c83329b Mon Sep 17 00:00:00 2001 From: "Edgar Y. Walker" Date: Sun, 25 May 2025 03:25:39 +0000 Subject: [PATCH 27/34] refactor: apply ruff --- src/orcabridge/__init__.py | 20 +++++----- src/orcabridge/base.py | 1 - src/orcabridge/dj/source.py | 21 +++-------- src/orcabridge/pod.py | 44 ++++++---------------- src/orcabridge/source.py | 9 ++--- tests/test_hashing/generate_file_hashes.py | 4 +- tests/test_hashing/test_file_hashes.py | 16 ++------ tests/test_hashing/test_hash_samples.py | 8 +--- 8 files changed, 39 insertions(+), 84 deletions(-) diff --git a/src/orcabridge/__init__.py b/src/orcabridge/__init__.py index 8533ffa..7a6f6c2 100644 --- a/src/orcabridge/__init__.py +++ b/src/orcabridge/__init__.py @@ -1,15 +1,20 @@ from .tracker import Tracker - -DEFAULT_TRACKER = Tracker() -DEFAULT_TRACKER.activate() - -# make modules and subpackages available from . import hashing from . import pod from . import mapper from . import stream from . import source from . import store +from .mapper import MapTags, MapPackets, Join, tag, packet +from .pod import FunctionPod, function_pod +from .source import GlobSource +from .store import DirDataStore, SafeDirDataStore + + + +DEFAULT_TRACKER = Tracker() +DEFAULT_TRACKER.activate() + __all__ = [ "hashing", @@ -33,8 +38,3 @@ "SyncStreamFromLists", ] -from .mapper import MapTags, MapPackets, Join, tag, packet -from .pod import FunctionPod, function_pod -from .source import GlobSource -from .store import DirDataStore, SafeDirDataStore -from .stream import SyncStreamFromLists as SimpleStream diff --git a/src/orcabridge/base.py b/src/orcabridge/base.py index 4acb2a4..43fadb5 100644 --- a/src/orcabridge/base.py +++ b/src/orcabridge/base.py @@ -9,7 +9,6 @@ Callable, Iterator, ) -from collections.abc import Collection import threading diff --git a/src/orcabridge/dj/source.py b/src/orcabridge/dj/source.py index aad4229..8145102 100644 --- a/src/orcabridge/dj/source.py +++ b/src/orcabridge/dj/source.py @@ -9,7 +9,7 @@ from ..utils.name import pascal_to_snake, snake_to_pascal from ..utils.stream_utils import common_elements import logging -from ..hashing import hash_to_uuid +from orcabridge.hashing import hash_to_uuid logger = logging.getLogger(__name__) @@ -131,9 +131,7 @@ def label(self) -> str: return snake_to_pascal(self.table_name) return self._label - def compile( - self, tag_keys: Collection[str], packet_keys: Collection[str] - ) -> None: + def compile(self, tag_keys: Collection[str], packet_keys: Collection[str]) -> None: # create a table to store the cached packets key_fields = "\n".join([f"{k}: varchar(255)" for k in tag_keys]) @@ -154,9 +152,7 @@ class CachedTable(dj.Manual): def forward(self, *streams: QueryStream) -> QueryStream: if len(streams) > 0: - raise ValueError( - "No streams should be passed to TableCachedStreamSource" - ) + raise ValueError("No streams should be passed to TableCachedStreamSource") if self.table is None: # TODO: consider handling this lazily @@ -325,9 +321,7 @@ def compile(self) -> None: # create a table to store the cached packets key_fields = "\n".join([f"{k}: varchar(255)" for k in common_tag_keys]) - output_fields = "\n".join( - [f"{k}: varchar(255)" for k in common_packet_keys] - ) + output_fields = "\n".join([f"{k}: varchar(255)" for k in common_packet_keys]) table_field = f"{self.table_name}_part" uuid_field = f"{self.table_name}_uuid" if use_uuid else "" table_entry = f"{table_field}: varchar(255)" @@ -396,17 +390,14 @@ def make_part_table( uuid_field, ) -> type[dj.Part]: upstreams = "\n".join( - f"-> self.upstream_tables[{i}]" - for i in range(len(stream.upstream_tables)) + f"-> self.upstream_tables[{i}]" for i in range(len(stream.upstream_tables)) ) tag_keys, packet_keys = stream.keys() extra_packet_keys = [k for k in packet_keys if k not in common_packet_keys] - extra_output_fields = "\n".join( - [f"{k}: varchar(255)" for k in extra_packet_keys] - ) + extra_output_fields = "\n".join([f"{k}: varchar(255)" for k in extra_packet_keys]) class PartTable(dj.Part, dj.Computed): upstream_tables = stream.upstream_tables diff --git a/src/orcabridge/pod.py b/src/orcabridge/pod.py index ba15918..6b79d68 100644 --- a/src/orcabridge/pod.py +++ b/src/orcabridge/pod.py @@ -1,7 +1,3 @@ -import logging - -logger = logging.getLogger(__name__) - from typing import ( Optional, Tuple, @@ -19,15 +15,16 @@ from orcabridge.store import DataStore, NoOpDataStore import functools import warnings +import logging + +logger = logging.getLogger(__name__) def function_pod( output_keys: Optional[Collection[str]] = None, store_name: Optional[str] = None, data_store: Optional[DataStore] = None, - function_hash_mode: Literal[ - "signature", "content", "name", "custom" - ] = "name", + function_hash_mode: Literal["signature", "content", "name", "custom"] = "name", custom_hash: Optional[int] = None, force_computation: bool = False, skip_memoization: bool = False, @@ -110,9 +107,7 @@ def __init__( output_keys: Optional[Collection[str]] = None, store_name=None, data_store: Optional[DataStore] = None, - function_hash_mode: Literal[ - "signature", "content", "name", "custom" - ] = "name", + function_hash_mode: Literal["signature", "content", "name", "custom"] = "name", custom_hash: Optional[int] = None, label: Optional[str] = None, force_computation: bool = False, @@ -127,12 +122,8 @@ def __init__( if output_keys is None: output_keys = [] self.output_keys = output_keys - self.store_name = ( - self.function.__name__ if store_name is None else store_name - ) - self.data_store = ( - data_store if data_store is not None else NoOpDataStore() - ) + self.store_name = self.function.__name__ if store_name is None else store_name + self.data_store = data_store if data_store is not None else NoOpDataStore() self.function_hash_mode = function_hash_mode self.custom_hash = custom_hash self.force_computation = force_computation @@ -145,9 +136,7 @@ def __repr__(self) -> str: func_sig = get_function_signature(self.function) return f"FunctionPod:{func_sig} ⇒ {self.output_keys}" - def keys( - self, *streams: SyncStream - ) -> Tuple[Collection[str], Collection[str]]: + def keys(self, *streams: SyncStream) -> Tuple[Collection[str], Collection[str]]: stream = self.process_stream(*streams) tag_keys, _ = stream[0].keys() return tag_keys, tuple(self.output_keys) @@ -155,9 +144,7 @@ def keys( def forward(self, *streams: SyncStream) -> SyncStream: # if multiple streams are provided, join them if len(streams) > 1: - raise ValueError( - "Multiple streams should be joined before calling forward" - ) + raise ValueError("Multiple streams should be joined before calling forward") if len(streams) == 0: raise ValueError("No streams provided to forward") stream = streams[0] @@ -174,13 +161,8 @@ def generator() -> Iterator[Tuple[Tag, Packet]]: ) else: memoized_packet = None - if ( - not self.force_computation - and memoized_packet is not None - ): - logger.info( - "Memoized packet found, skipping computation" - ) + if not self.force_computation and memoized_packet is not None: + logger.info("Memoized packet found, skipping computation") yield tag, memoized_packet continue values = self.function(**packet) @@ -209,9 +191,7 @@ def generator() -> Iterator[Tuple[Tag, Packet]]: warnings.warn(f"Error processing packet {packet}: {e}") continue - output_packet: Packet = { - k: v for k, v in zip(self.output_keys, values) - } + output_packet: Packet = {k: v for k, v in zip(self.output_keys, values)} if not self.skip_memoization: # output packet may be modified by the memoization process diff --git a/src/orcabridge/source.py b/src/orcabridge/source.py index 3071b89..59249f7 100644 --- a/src/orcabridge/source.py +++ b/src/orcabridge/source.py @@ -51,7 +51,8 @@ class GlobSource(Source): ... lambda f: {'date': Path(f).stem[:8]}) """ - default_tag_function = lambda f: {"file_name": Path(f).stem} + + default_tag_function = lambda f: {"file_name": Path(f).stem} # noqa: E731 def __init__( self, @@ -60,9 +61,7 @@ def __init__( pattern: str = "*", label: Optional[str] = None, tag_function: Optional[Union[str, Callable[[PathLike], Tag]]] = None, - tag_function_hash_mode: Literal[ - "content", "signature", "name" - ] = "name", + tag_function_hash_mode: Literal["content", "signature", "name"] = "name", expected_tag_keys: Optional[Collection[str]] = None, **kwargs, ) -> None: @@ -77,7 +76,7 @@ def __init__( tag_function = self.__class__.default_tag_function elif isinstance(tag_function, str): tag_key = tag_function - tag_function = lambda f: {tag_key: Path(f).stem} + tag_function = lambda f: {tag_key: Path(f).stem} # noqa: E731 self.tag_function = tag_function self.tag_function_hash_mode = tag_function_hash_mode diff --git a/tests/test_hashing/generate_file_hashes.py b/tests/test_hashing/generate_file_hashes.py index df61175..2a8b587 100644 --- a/tests/test_hashing/generate_file_hashes.py +++ b/tests/test_hashing/generate_file_hashes.py @@ -118,9 +118,7 @@ def create_sample_files(): # Compute the hash json_hash = hash_file(json_filepath) - files_info.append( - {"file": str(rel_filepath), "hash": json_hash, "type": "json"} - ) + files_info.append({"file": str(rel_filepath), "hash": json_hash, "type": "json"}) print(f"Created JSON file: {json_filename}, Hash: {json_hash}") return files_info diff --git a/tests/test_hashing/test_file_hashes.py b/tests/test_hashing/test_file_hashes.py index 4d7f971..340d02f 100644 --- a/tests/test_hashing/test_file_hashes.py +++ b/tests/test_hashing/test_file_hashes.py @@ -20,9 +20,7 @@ def load_hash_lut(): """Load the hash lookup table from the JSON file.""" - hash_lut_path = ( - Path(__file__).parent / "hash_samples" / "file_hash_lut.json" - ) + hash_lut_path = Path(__file__).parent / "hash_samples" / "file_hash_lut.json" if not hash_lut_path.exists(): pytest.skip( @@ -85,9 +83,7 @@ def test_file_hash_algorithm_parameters(): try: hash1 = hash_file(file_path, algorithm=algorithm) hash2 = hash_file(file_path, algorithm=algorithm) - assert ( - hash1 == hash2 - ), f"Hash inconsistent for algorithm {algorithm}" + assert hash1 == hash2, f"Hash inconsistent for algorithm {algorithm}" print(f"Verified {algorithm} hash consistency: {hash1}") except ValueError as e: print(f"Algorithm {algorithm} not supported: {e}") @@ -98,12 +94,8 @@ def test_file_hash_algorithm_parameters(): for buffer_size in buffer_sizes: hash1 = hash_file(file_path, buffer_size=buffer_size) hash2 = hash_file(file_path, buffer_size=buffer_size) - assert ( - hash1 == hash2 - ), f"Hash inconsistent for buffer size {buffer_size}" - print( - f"Verified hash consistency with buffer size {buffer_size}: {hash1}" - ) + assert hash1 == hash2, f"Hash inconsistent for buffer size {buffer_size}" + print(f"Verified hash consistency with buffer size {buffer_size}: {hash1}") if __name__ == "__main__": diff --git a/tests/test_hashing/test_hash_samples.py b/tests/test_hashing/test_hash_samples.py index e115baf..ea01d20 100644 --- a/tests/test_hashing/test_hash_samples.py +++ b/tests/test_hashing/test_hash_samples.py @@ -45,16 +45,12 @@ def load_hash_samples(file_path=None): def deserialize_value(serialized_value): """Convert serialized values back to their original form.""" - if isinstance(serialized_value, str) and serialized_value.startswith( - "bytes:" - ): + if isinstance(serialized_value, str) and serialized_value.startswith("bytes:"): # Convert hex string back to bytes hex_str = serialized_value[len("bytes:") :] return bytes.fromhex(hex_str) - if isinstance(serialized_value, str) and serialized_value.startswith( - "set:" - ): + if isinstance(serialized_value, str) and serialized_value.startswith("set:"): # Convert string representation back to set # Example: "set:[1, 2, 3]" -> {1, 2, 3} set_str = serialized_value[len("set:") :] From 255a5097920fe1944d685a349a03ce6802503d54 Mon Sep 17 00:00:00 2001 From: "Edgar Y. Walker" Date: Sun, 25 May 2025 03:26:35 +0000 Subject: [PATCH 28/34] refactor: remove unused import --- tests/test_hashing/test_file_hashes.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_hashing/test_file_hashes.py b/tests/test_hashing/test_file_hashes.py index 340d02f..f2a93da 100644 --- a/tests/test_hashing/test_file_hashes.py +++ b/tests/test_hashing/test_file_hashes.py @@ -8,7 +8,6 @@ """ import json -import os import pytest from pathlib import Path import sys From 33e365a7fb7203328511f572142fb42668d9cc40 Mon Sep 17 00:00:00 2001 From: "Edgar Y. Walker" Date: Sun, 25 May 2025 16:33:48 +0000 Subject: [PATCH 29/34] refactor: remove unused imports --- src/orcabridge/stream.py | 3 --- tests/test_hashing/generate_file_hashes.py | 1 - 2 files changed, 4 deletions(-) diff --git a/src/orcabridge/stream.py b/src/orcabridge/stream.py index 3dc8f7a..88d5188 100644 --- a/src/orcabridge/stream.py +++ b/src/orcabridge/stream.py @@ -1,8 +1,5 @@ from typing import ( - Generator, Tuple, - Dict, - Any, Callable, Iterator, Optional, diff --git a/tests/test_hashing/generate_file_hashes.py b/tests/test_hashing/generate_file_hashes.py index 2a8b587..57a5e4d 100644 --- a/tests/test_hashing/generate_file_hashes.py +++ b/tests/test_hashing/generate_file_hashes.py @@ -7,7 +7,6 @@ then computes and records their hashes in a JSON lookup table file. """ -import os import json import random import string From b0d01e6fbd45297e6fe9dfab0f0cd55210664676 Mon Sep 17 00:00:00 2001 From: "Edgar Y. Walker" Date: Sun, 25 May 2025 17:08:58 +0000 Subject: [PATCH 30/34] refactor: apply ruff check and fixes --- pyproject.toml | 5 + src/orcabridge/dj/mapper.py | 1 - src/orcabridge/dj/tracker.py | 3 +- src/orcabridge/file.py | 2 +- src/orcabridge/hashing/__init__.py | 2 + src/orcabridge/hashing/hashing.py | 10 +- src/orcabridge/mapper.py | 7 +- src/orcabridge/py.typed | 0 src/orcabridge/store/dir_data_store.py | 8 +- src/orcabridge/store/file_ops.py | 3 +- src/orcabridge/store/safe_dir_data_store.py | 5 +- src/orcabridge/tracker.py | 3 +- src/orcabridge/types.py | 7 +- src/orcabridge/utils/name.py | 4 - src/orcabridge/utils/stream_utils.py | 5 - tests/test_hashing/generate_hash_examples.py | 2 - tests/test_hashing/reorganize_files.py | 1 - tests/test_hashing/test_basic_hashing.py | 2 - uv.lock | 901 +++++++++++++++++++ 19 files changed, 929 insertions(+), 42 deletions(-) create mode 100644 src/orcabridge/py.typed create mode 100644 uv.lock diff --git a/pyproject.toml b/pyproject.toml index 3161f18..4616193 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,3 +34,8 @@ where = ["src"] [tool.setuptools_scm] version_file = "src/orcabridge/_version.py" + +[dependency-groups] +dev = [ + "ruff>=0.11.11", +] diff --git a/src/orcabridge/dj/mapper.py b/src/orcabridge/dj/mapper.py index 40a4237..1450f4e 100644 --- a/src/orcabridge/dj/mapper.py +++ b/src/orcabridge/dj/mapper.py @@ -3,7 +3,6 @@ from ..mapper import Mapper, Join, MapPackets, MapTags from typing import Optional import warnings -from .source import QuerySource class QueryMapper(QueryOperation, Mapper): diff --git a/src/orcabridge/dj/tracker.py b/src/orcabridge/dj/tracker.py index 1b89d24..8db180d 100644 --- a/src/orcabridge/dj/tracker.py +++ b/src/orcabridge/dj/tracker.py @@ -1,6 +1,6 @@ from orcabridge.tracker import GraphTracker from datajoint import Schema -from typing import List, Collection, Tuple, Optional, Any +from typing import Collection, Tuple, Optional, Any from types import ModuleType import networkx as nx @@ -13,7 +13,6 @@ from .operation import QueryOperation from .pod import TableCachedPod from .mapper import convert_to_query_mapper -import datajoint as dj import sys from collections import defaultdict diff --git a/src/orcabridge/file.py b/src/orcabridge/file.py index 6260f65..84b04e6 100644 --- a/src/orcabridge/file.py +++ b/src/orcabridge/file.py @@ -3,7 +3,7 @@ import inspect import os from pathlib import Path -from typing import Callable, Dict, Optional, Sequence, Tuple, Union +from typing import Callable, Collection, Dict, Optional, Tuple, Union from orcabridge.types import Packet, PathSet diff --git a/src/orcabridge/hashing/__init__.py b/src/orcabridge/hashing/__init__.py index ebab3a9..6eb985c 100644 --- a/src/orcabridge/hashing/__init__.py +++ b/src/orcabridge/hashing/__init__.py @@ -7,6 +7,7 @@ hash_to_uuid, hash_function, get_function_signature, + function_content_hash, HashableMixin, ) @@ -19,5 +20,6 @@ "hash_to_uuid", "hash_function", "get_function_signature", + "function_content_hash", "HashableMixin", ] diff --git a/src/orcabridge/hashing/hashing.py b/src/orcabridge/hashing/hashing.py index 2a97e14..c0a1f89 100644 --- a/src/orcabridge/hashing/hashing.py +++ b/src/orcabridge/hashing/hashing.py @@ -310,7 +310,7 @@ def hash_to_hex(obj: Any, char_count: Optional[int] = 32) -> str: # Try standard JSON first json_str = json.dumps(processed, sort_keys=True).encode("utf-8") logger.info( - f"Successfully used standard JSON serialization as fallback" + "Successfully used standard JSON serialization as fallback" ) except (TypeError, ValueError) as json_err: # If JSON also fails, use simple string representation @@ -495,7 +495,7 @@ def _process_structure(obj: Any, visited: Optional[Set[int]] = None) -> Any: else: # Get basic repr but remove memory addresses logger.debug( - f"Object has no __dict__, using repr() with memory address removal" + "Object has no __dict__, using repr() with memory address removal" ) obj_repr = repr(obj) if len(obj_repr) > 1000: @@ -512,7 +512,7 @@ def _process_structure(obj: Any, visited: Optional[Set[int]] = None) -> Any: logger.warning(f"Failed to process object representation: {e}") try: return f"Object-{obj.__class__.__module__}.{obj.__class__.__name__}" - except: + except AttributeError: logger.error( "Could not determine object class, using UnknownObject" ) @@ -680,14 +680,14 @@ def get_function_components( components.append(f"source:{source}") - except (IOError, TypeError) as e: + except (IOError, TypeError): # If source can't be retrieved, fall back to signature components.append(f"name:{func.__name__}") try: sig = inspect.signature(func) components.append(f"signature:{str(sig)}") except ValueError: - components.append(f"builtin:True") + components.append("builtin:True") # Add function annotations if requested if ( diff --git a/src/orcabridge/mapper.py b/src/orcabridge/mapper.py index 5849f89..bb5e84a 100644 --- a/src/orcabridge/mapper.py +++ b/src/orcabridge/mapper.py @@ -7,9 +7,8 @@ Tuple, Iterator, Collection, - Any, ) -from orcabridge.base import Operation, SyncStream, Mapper +from orcabridge.base import SyncStream, Mapper from orcabridge.stream import SyncStreamFromGenerator from orcabridge.utils.stream_utils import ( join_tags, @@ -17,7 +16,7 @@ batch_tag, batch_packet, ) -from orcabridge.hashing import hash_function +from orcabridge.hashing import hash_function, function_content_hash from .types import Tag, Packet from itertools import chain @@ -485,7 +484,7 @@ def __init__( super().__init__() self.batch_size = batch_size if tag_processor is None: - tag_processor = lambda tags: batch_tag(tags) + tag_processor = lambda tags: batch_tag(tags) # noqa: E731 self.tag_processor = tag_processor self.drop_last = drop_last diff --git a/src/orcabridge/py.typed b/src/orcabridge/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/src/orcabridge/store/dir_data_store.py b/src/orcabridge/store/dir_data_store.py index 3940933..bf1436c 100644 --- a/src/orcabridge/store/dir_data_store.py +++ b/src/orcabridge/store/dir_data_store.py @@ -1,5 +1,5 @@ -from ..types import Tag, Packet -from typing import Optional, Collection +from orcabridge.types import Packet +from typing import Optional from pathlib import Path from orcabridge.hashing import hash_packet import shutil @@ -156,9 +156,9 @@ def retrieve_memoized( logger.info( f"Supplemented source for packet {packet} at {source_path}" ) - except: + except IOError as e: logger.error( - f"Error loading memoized output for packet {packet} from {info_path}" + f"Error loading memoized output for packet {packet} from {info_path}: {e}" ) return None return output_packet diff --git a/src/orcabridge/store/file_ops.py b/src/orcabridge/store/file_ops.py index 16c7d3e..fb46f65 100644 --- a/src/orcabridge/store/file_ops.py +++ b/src/orcabridge/store/file_ops.py @@ -1,10 +1,9 @@ # file_ops.py - Atomic file operations module import os -import time import logging from pathlib import Path -from typing import Union, Optional, Tuple +from typing import Union, Tuple logger = logging.getLogger(__name__) diff --git a/src/orcabridge/store/safe_dir_data_store.py b/src/orcabridge/store/safe_dir_data_store.py index 10c0e32..1017153 100644 --- a/src/orcabridge/store/safe_dir_data_store.py +++ b/src/orcabridge/store/safe_dir_data_store.py @@ -4,12 +4,11 @@ import time import json import logging -import threading import fcntl import errno from pathlib import Path from contextlib import contextmanager -from typing import Optional, Dict, Any, Union +from typing import Optional, Union from .file_ops import atomic_write, atomic_copy @@ -237,7 +236,7 @@ def memoize( logger.info(f"Entry already exists for packet {packet}") return self.retrieve_memoized(store_name, content_hash, packet) except FileLockError: - logger.warning(f"Could not acquire shared lock to check completion status") + logger.warning("Could not acquire shared lock to check completion status") # Continue to try with exclusive lock # Now try to acquire an exclusive lock for writing diff --git a/src/orcabridge/tracker.py b/src/orcabridge/tracker.py index 2d2b72f..f8a0d4a 100644 --- a/src/orcabridge/tracker.py +++ b/src/orcabridge/tracker.py @@ -1,5 +1,4 @@ -import threading -from typing import Dict, Collection, List +from typing import Dict, Collection import networkx as nx from orcabridge.base import Operation, Invocation, Tracker import matplotlib.pyplot as plt diff --git a/src/orcabridge/types.py b/src/orcabridge/types.py index 55af3d3..0f67e0d 100644 --- a/src/orcabridge/types.py +++ b/src/orcabridge/types.py @@ -1,18 +1,17 @@ from typing import Union, Tuple, Protocol, Mapping, Collection, Optional -from pathlib import Path from typing_extensions import TypeAlias import os # Convenience alias for anything pathlike PathLike = Union[str, bytes, os.PathLike] -# arbitrary depth of nested list of strings or None -L: TypeAlias = Union[str, None, Collection[Optional[str]]] +# an (optional) string or a collection of (optional) string values +TagValue: TypeAlias = Union[Optional[str], Collection[Optional[str]]] # the top level tag is a mapping from string keys to values that can be a string or # an arbitrary depth of nested list of strings or None -Tag: TypeAlias = Mapping[str, Union[str, L]] +Tag: TypeAlias = Mapping[str, Union[str, TagValue]] # a pathset is a path or an arbitrary depth of nested list of paths diff --git a/src/orcabridge/utils/name.py b/src/orcabridge/utils/name.py index c5b952f..5d887d3 100644 --- a/src/orcabridge/utils/name.py +++ b/src/orcabridge/utils/name.py @@ -3,10 +3,6 @@ """ import re -import inspect -import pickle -import types -import ast def find_noncolliding_name(name: str, lut: dict) -> str: diff --git a/src/orcabridge/utils/stream_utils.py b/src/orcabridge/utils/stream_utils.py index ada32e1..a981587 100644 --- a/src/orcabridge/utils/stream_utils.py +++ b/src/orcabridge/utils/stream_utils.py @@ -2,15 +2,10 @@ Utility functions for handling tags """ -from _collections_abc import dict_keys from typing import ( - List, - Dict, Optional, - Any, TypeVar, Set, - Union, Sequence, Mapping, Collection, diff --git a/tests/test_hashing/generate_hash_examples.py b/tests/test_hashing/generate_hash_examples.py index 585a6ac..bd266c1 100644 --- a/tests/test_hashing/generate_hash_examples.py +++ b/tests/test_hashing/generate_hash_examples.py @@ -3,9 +3,7 @@ # throughout the tests to ensure consistent hashing behavior across different runs # and revision of the codebase. -import os import json -import uuid from pathlib import Path from collections import OrderedDict from datetime import datetime diff --git a/tests/test_hashing/reorganize_files.py b/tests/test_hashing/reorganize_files.py index b34406f..177827d 100644 --- a/tests/test_hashing/reorganize_files.py +++ b/tests/test_hashing/reorganize_files.py @@ -7,7 +7,6 @@ organized directory structure within hash_samples. """ -import os import shutil from pathlib import Path diff --git a/tests/test_hashing/test_basic_hashing.py b/tests/test_hashing/test_basic_hashing.py index 1473bcc..ea6b681 100644 --- a/tests/test_hashing/test_basic_hashing.py +++ b/tests/test_hashing/test_basic_hashing.py @@ -1,11 +1,9 @@ -import pytest from orcabridge.hashing.hashing import ( hash_to_hex, hash_to_int, hash_to_uuid, HashableMixin, stable_hash, - hash_file, ) diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..57f3a92 --- /dev/null +++ b/uv.lock @@ -0,0 +1,901 @@ +version = 1 +revision = 2 +requires-python = ">=3.10" + +[[package]] +name = "black" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "mypy-extensions" }, + { 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/94/49/26a7b0f3f35da4b5a65f081943b7bcd22d7002f5f0fb8098ec1ff21cb6ef/black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", size = 649449, upload-time = "2025-01-29T04:15:40.373Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/3b/4ba3f93ac8d90410423fdd31d7541ada9bcee1df32fb90d26de41ed40e1d/black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32", size = 1629419, upload-time = "2025-01-29T05:37:06.642Z" }, + { url = "https://files.pythonhosted.org/packages/b4/02/0bde0485146a8a5e694daed47561785e8b77a0466ccc1f3e485d5ef2925e/black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da", size = 1461080, upload-time = "2025-01-29T05:37:09.321Z" }, + { url = "https://files.pythonhosted.org/packages/52/0e/abdf75183c830eaca7589144ff96d49bce73d7ec6ad12ef62185cc0f79a2/black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7", size = 1766886, upload-time = "2025-01-29T04:18:24.432Z" }, + { url = "https://files.pythonhosted.org/packages/dc/a6/97d8bb65b1d8a41f8a6736222ba0a334db7b7b77b8023ab4568288f23973/black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9", size = 1419404, upload-time = "2025-01-29T04:19:04.296Z" }, + { url = "https://files.pythonhosted.org/packages/7e/4f/87f596aca05c3ce5b94b8663dbfe242a12843caaa82dd3f85f1ffdc3f177/black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0", size = 1614372, upload-time = "2025-01-29T05:37:11.71Z" }, + { url = "https://files.pythonhosted.org/packages/e7/d0/2c34c36190b741c59c901e56ab7f6e54dad8df05a6272a9747ecef7c6036/black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299", size = 1442865, upload-time = "2025-01-29T05:37:14.309Z" }, + { url = "https://files.pythonhosted.org/packages/21/d4/7518c72262468430ead45cf22bd86c883a6448b9eb43672765d69a8f1248/black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096", size = 1749699, upload-time = "2025-01-29T04:18:17.688Z" }, + { url = "https://files.pythonhosted.org/packages/58/db/4f5beb989b547f79096e035c4981ceb36ac2b552d0ac5f2620e941501c99/black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2", size = 1428028, upload-time = "2025-01-29T04:18:51.711Z" }, + { url = "https://files.pythonhosted.org/packages/83/71/3fe4741df7adf015ad8dfa082dd36c94ca86bb21f25608eb247b4afb15b2/black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b", size = 1650988, upload-time = "2025-01-29T05:37:16.707Z" }, + { url = "https://files.pythonhosted.org/packages/13/f3/89aac8a83d73937ccd39bbe8fc6ac8860c11cfa0af5b1c96d081facac844/black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc", size = 1453985, upload-time = "2025-01-29T05:37:18.273Z" }, + { url = "https://files.pythonhosted.org/packages/6f/22/b99efca33f1f3a1d2552c714b1e1b5ae92efac6c43e790ad539a163d1754/black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f", size = 1783816, upload-time = "2025-01-29T04:18:33.823Z" }, + { url = "https://files.pythonhosted.org/packages/18/7e/a27c3ad3822b6f2e0e00d63d58ff6299a99a5b3aee69fa77cd4b0076b261/black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba", size = 1440860, upload-time = "2025-01-29T04:19:12.944Z" }, + { url = "https://files.pythonhosted.org/packages/98/87/0edf98916640efa5d0696e1abb0a8357b52e69e82322628f25bf14d263d1/black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f", size = 1650673, upload-time = "2025-01-29T05:37:20.574Z" }, + { url = "https://files.pythonhosted.org/packages/52/e5/f7bf17207cf87fa6e9b676576749c6b6ed0d70f179a3d812c997870291c3/black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3", size = 1453190, upload-time = "2025-01-29T05:37:22.106Z" }, + { url = "https://files.pythonhosted.org/packages/e3/ee/adda3d46d4a9120772fae6de454c8495603c37c4c3b9c60f25b1ab6401fe/black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171", size = 1782926, upload-time = "2025-01-29T04:18:58.564Z" }, + { url = "https://files.pythonhosted.org/packages/cc/64/94eb5f45dcb997d2082f097a3944cfc7fe87e071907f677e80788a2d7b7a/black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18", size = 1442613, upload-time = "2025-01-29T04:19:27.63Z" }, + { url = "https://files.pythonhosted.org/packages/09/71/54e999902aed72baf26bca0d50781b01838251a462612966e9fc4891eadd/black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", size = 207646, upload-time = "2025-01-29T04:15:38.082Z" }, +] + +[[package]] +name = "click" +version = "8.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "contourpy" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +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 = "coverage" +version = "7.8.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/07/998afa4a0ecdf9b1981ae05415dad2d4e7716e1b1f00abbd91691ac09ac9/coverage-7.8.2.tar.gz", hash = "sha256:a886d531373a1f6ff9fad2a2ba4a045b68467b779ae729ee0b3b10ac20033b27", size = 812759, upload-time = "2025-05-23T11:39:57.856Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/6b/7dd06399a5c0b81007e3a6af0395cd60e6a30f959f8d407d3ee04642e896/coverage-7.8.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bd8ec21e1443fd7a447881332f7ce9d35b8fbd2849e761bb290b584535636b0a", size = 211573, upload-time = "2025-05-23T11:37:47.207Z" }, + { url = "https://files.pythonhosted.org/packages/f0/df/2b24090820a0bac1412955fb1a4dade6bc3b8dcef7b899c277ffaf16916d/coverage-7.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4c26c2396674816deaeae7ded0e2b42c26537280f8fe313335858ffff35019be", size = 212006, upload-time = "2025-05-23T11:37:50.289Z" }, + { url = "https://files.pythonhosted.org/packages/c5/c4/e4e3b998e116625562a872a342419652fa6ca73f464d9faf9f52f1aff427/coverage-7.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1aec326ed237e5880bfe69ad41616d333712c7937bcefc1343145e972938f9b3", size = 241128, upload-time = "2025-05-23T11:37:52.229Z" }, + { url = "https://files.pythonhosted.org/packages/b1/67/b28904afea3e87a895da850ba587439a61699bf4b73d04d0dfd99bbd33b4/coverage-7.8.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5e818796f71702d7a13e50c70de2a1924f729228580bcba1607cccf32eea46e6", size = 239026, upload-time = "2025-05-23T11:37:53.846Z" }, + { url = "https://files.pythonhosted.org/packages/8c/0f/47bf7c5630d81bc2cd52b9e13043685dbb7c79372a7f5857279cc442b37c/coverage-7.8.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:546e537d9e24efc765c9c891328f30f826e3e4808e31f5d0f87c4ba12bbd1622", size = 240172, upload-time = "2025-05-23T11:37:55.711Z" }, + { url = "https://files.pythonhosted.org/packages/ba/38/af3eb9d36d85abc881f5aaecf8209383dbe0fa4cac2d804c55d05c51cb04/coverage-7.8.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ab9b09a2349f58e73f8ebc06fac546dd623e23b063e5398343c5270072e3201c", size = 240086, upload-time = "2025-05-23T11:37:57.724Z" }, + { url = "https://files.pythonhosted.org/packages/9e/64/c40c27c2573adeba0fe16faf39a8aa57368a1f2148865d6bb24c67eadb41/coverage-7.8.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fd51355ab8a372d89fb0e6a31719e825cf8df8b6724bee942fb5b92c3f016ba3", size = 238792, upload-time = "2025-05-23T11:37:59.737Z" }, + { url = "https://files.pythonhosted.org/packages/8e/ab/b7c85146f15457671c1412afca7c25a5696d7625e7158002aa017e2d7e3c/coverage-7.8.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0774df1e093acb6c9e4d58bce7f86656aeed6c132a16e2337692c12786b32404", size = 239096, upload-time = "2025-05-23T11:38:01.693Z" }, + { url = "https://files.pythonhosted.org/packages/d3/50/9446dad1310905fb1dc284d60d4320a5b25d4e3e33f9ea08b8d36e244e23/coverage-7.8.2-cp310-cp310-win32.whl", hash = "sha256:00f2e2f2e37f47e5f54423aeefd6c32a7dbcedc033fcd3928a4f4948e8b96af7", size = 214144, upload-time = "2025-05-23T11:38:03.68Z" }, + { url = "https://files.pythonhosted.org/packages/23/ed/792e66ad7b8b0df757db8d47af0c23659cdb5a65ef7ace8b111cacdbee89/coverage-7.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:145b07bea229821d51811bf15eeab346c236d523838eda395ea969d120d13347", size = 215043, upload-time = "2025-05-23T11:38:05.217Z" }, + { url = "https://files.pythonhosted.org/packages/6a/4d/1ff618ee9f134d0de5cc1661582c21a65e06823f41caf801aadf18811a8e/coverage-7.8.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b99058eef42e6a8dcd135afb068b3d53aff3921ce699e127602efff9956457a9", size = 211692, upload-time = "2025-05-23T11:38:08.485Z" }, + { url = "https://files.pythonhosted.org/packages/96/fa/c3c1b476de96f2bc7a8ca01a9f1fcb51c01c6b60a9d2c3e66194b2bdb4af/coverage-7.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5feb7f2c3e6ea94d3b877def0270dff0947b8d8c04cfa34a17be0a4dc1836879", size = 212115, upload-time = "2025-05-23T11:38:09.989Z" }, + { url = "https://files.pythonhosted.org/packages/f7/c2/5414c5a1b286c0f3881ae5adb49be1854ac5b7e99011501f81c8c1453065/coverage-7.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:670a13249b957bb9050fab12d86acef7bf8f6a879b9d1a883799276e0d4c674a", size = 244740, upload-time = "2025-05-23T11:38:11.947Z" }, + { url = "https://files.pythonhosted.org/packages/cd/46/1ae01912dfb06a642ef3dd9cf38ed4996fda8fe884dab8952da616f81a2b/coverage-7.8.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0bdc8bf760459a4a4187b452213e04d039990211f98644c7292adf1e471162b5", size = 242429, upload-time = "2025-05-23T11:38:13.955Z" }, + { url = "https://files.pythonhosted.org/packages/06/58/38c676aec594bfe2a87c7683942e5a30224791d8df99bcc8439fde140377/coverage-7.8.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07a989c867986c2a75f158f03fdb413128aad29aca9d4dbce5fc755672d96f11", size = 244218, upload-time = "2025-05-23T11:38:15.631Z" }, + { url = "https://files.pythonhosted.org/packages/80/0c/95b1023e881ce45006d9abc250f76c6cdab7134a1c182d9713878dfefcb2/coverage-7.8.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2db10dedeb619a771ef0e2949ccba7b75e33905de959c2643a4607bef2f3fb3a", size = 243865, upload-time = "2025-05-23T11:38:17.622Z" }, + { url = "https://files.pythonhosted.org/packages/57/37/0ae95989285a39e0839c959fe854a3ae46c06610439350d1ab860bf020ac/coverage-7.8.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e6ea7dba4e92926b7b5f0990634b78ea02f208d04af520c73a7c876d5a8d36cb", size = 242038, upload-time = "2025-05-23T11:38:19.966Z" }, + { url = "https://files.pythonhosted.org/packages/4d/82/40e55f7c0eb5e97cc62cbd9d0746fd24e8caf57be5a408b87529416e0c70/coverage-7.8.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ef2f22795a7aca99fc3c84393a55a53dd18ab8c93fb431004e4d8f0774150f54", size = 242567, upload-time = "2025-05-23T11:38:21.912Z" }, + { url = "https://files.pythonhosted.org/packages/f9/35/66a51adc273433a253989f0d9cc7aa6bcdb4855382cf0858200afe578861/coverage-7.8.2-cp311-cp311-win32.whl", hash = "sha256:641988828bc18a6368fe72355df5f1703e44411adbe49bba5644b941ce6f2e3a", size = 214194, upload-time = "2025-05-23T11:38:23.571Z" }, + { url = "https://files.pythonhosted.org/packages/f6/8f/a543121f9f5f150eae092b08428cb4e6b6d2d134152c3357b77659d2a605/coverage-7.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:8ab4a51cb39dc1933ba627e0875046d150e88478dbe22ce145a68393e9652975", size = 215109, upload-time = "2025-05-23T11:38:25.137Z" }, + { url = "https://files.pythonhosted.org/packages/77/65/6cc84b68d4f35186463cd7ab1da1169e9abb59870c0f6a57ea6aba95f861/coverage-7.8.2-cp311-cp311-win_arm64.whl", hash = "sha256:8966a821e2083c74d88cca5b7dcccc0a3a888a596a04c0b9668a891de3a0cc53", size = 213521, upload-time = "2025-05-23T11:38:27.123Z" }, + { url = "https://files.pythonhosted.org/packages/8d/2a/1da1ada2e3044fcd4a3254fb3576e160b8fe5b36d705c8a31f793423f763/coverage-7.8.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e2f6fe3654468d061942591aef56686131335b7a8325684eda85dacdf311356c", size = 211876, upload-time = "2025-05-23T11:38:29.01Z" }, + { url = "https://files.pythonhosted.org/packages/70/e9/3d715ffd5b6b17a8be80cd14a8917a002530a99943cc1939ad5bb2aa74b9/coverage-7.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76090fab50610798cc05241bf83b603477c40ee87acd358b66196ab0ca44ffa1", size = 212130, upload-time = "2025-05-23T11:38:30.675Z" }, + { url = "https://files.pythonhosted.org/packages/a0/02/fdce62bb3c21649abfd91fbdcf041fb99be0d728ff00f3f9d54d97ed683e/coverage-7.8.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bd0a0a5054be160777a7920b731a0570284db5142abaaf81bcbb282b8d99279", size = 246176, upload-time = "2025-05-23T11:38:32.395Z" }, + { url = "https://files.pythonhosted.org/packages/a7/52/decbbed61e03b6ffe85cd0fea360a5e04a5a98a7423f292aae62423b8557/coverage-7.8.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da23ce9a3d356d0affe9c7036030b5c8f14556bd970c9b224f9c8205505e3b99", size = 243068, upload-time = "2025-05-23T11:38:33.989Z" }, + { url = "https://files.pythonhosted.org/packages/38/6c/d0e9c0cce18faef79a52778219a3c6ee8e336437da8eddd4ab3dbd8fadff/coverage-7.8.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9392773cffeb8d7e042a7b15b82a414011e9d2b5fdbbd3f7e6a6b17d5e21b20", size = 245328, upload-time = "2025-05-23T11:38:35.568Z" }, + { url = "https://files.pythonhosted.org/packages/f0/70/f703b553a2f6b6c70568c7e398ed0789d47f953d67fbba36a327714a7bca/coverage-7.8.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:876cbfd0b09ce09d81585d266c07a32657beb3eaec896f39484b631555be0fe2", size = 245099, upload-time = "2025-05-23T11:38:37.627Z" }, + { url = "https://files.pythonhosted.org/packages/ec/fb/4cbb370dedae78460c3aacbdad9d249e853f3bc4ce5ff0e02b1983d03044/coverage-7.8.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3da9b771c98977a13fbc3830f6caa85cae6c9c83911d24cb2d218e9394259c57", size = 243314, upload-time = "2025-05-23T11:38:39.238Z" }, + { url = "https://files.pythonhosted.org/packages/39/9f/1afbb2cb9c8699b8bc38afdce00a3b4644904e6a38c7bf9005386c9305ec/coverage-7.8.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9a990f6510b3292686713bfef26d0049cd63b9c7bb17e0864f133cbfd2e6167f", size = 244489, upload-time = "2025-05-23T11:38:40.845Z" }, + { url = "https://files.pythonhosted.org/packages/79/fa/f3e7ec7d220bff14aba7a4786ae47043770cbdceeea1803083059c878837/coverage-7.8.2-cp312-cp312-win32.whl", hash = "sha256:bf8111cddd0f2b54d34e96613e7fbdd59a673f0cf5574b61134ae75b6f5a33b8", size = 214366, upload-time = "2025-05-23T11:38:43.551Z" }, + { url = "https://files.pythonhosted.org/packages/54/aa/9cbeade19b7e8e853e7ffc261df885d66bf3a782c71cba06c17df271f9e6/coverage-7.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:86a323a275e9e44cdf228af9b71c5030861d4d2610886ab920d9945672a81223", size = 215165, upload-time = "2025-05-23T11:38:45.148Z" }, + { url = "https://files.pythonhosted.org/packages/c4/73/e2528bf1237d2448f882bbebaec5c3500ef07301816c5c63464b9da4d88a/coverage-7.8.2-cp312-cp312-win_arm64.whl", hash = "sha256:820157de3a589e992689ffcda8639fbabb313b323d26388d02e154164c57b07f", size = 213548, upload-time = "2025-05-23T11:38:46.74Z" }, + { url = "https://files.pythonhosted.org/packages/1a/93/eb6400a745ad3b265bac36e8077fdffcf0268bdbbb6c02b7220b624c9b31/coverage-7.8.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ea561010914ec1c26ab4188aef8b1567272ef6de096312716f90e5baa79ef8ca", size = 211898, upload-time = "2025-05-23T11:38:49.066Z" }, + { url = "https://files.pythonhosted.org/packages/1b/7c/bdbf113f92683024406a1cd226a199e4200a2001fc85d6a6e7e299e60253/coverage-7.8.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cb86337a4fcdd0e598ff2caeb513ac604d2f3da6d53df2c8e368e07ee38e277d", size = 212171, upload-time = "2025-05-23T11:38:51.207Z" }, + { url = "https://files.pythonhosted.org/packages/91/22/594513f9541a6b88eb0dba4d5da7d71596dadef6b17a12dc2c0e859818a9/coverage-7.8.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26a4636ddb666971345541b59899e969f3b301143dd86b0ddbb570bd591f1e85", size = 245564, upload-time = "2025-05-23T11:38:52.857Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f4/2860fd6abeebd9f2efcfe0fd376226938f22afc80c1943f363cd3c28421f/coverage-7.8.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5040536cf9b13fb033f76bcb5e1e5cb3b57c4807fef37db9e0ed129c6a094257", size = 242719, upload-time = "2025-05-23T11:38:54.529Z" }, + { url = "https://files.pythonhosted.org/packages/89/60/f5f50f61b6332451520e6cdc2401700c48310c64bc2dd34027a47d6ab4ca/coverage-7.8.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc67994df9bcd7e0150a47ef41278b9e0a0ea187caba72414b71dc590b99a108", size = 244634, upload-time = "2025-05-23T11:38:57.326Z" }, + { url = "https://files.pythonhosted.org/packages/3b/70/7f4e919039ab7d944276c446b603eea84da29ebcf20984fb1fdf6e602028/coverage-7.8.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e6c86888fd076d9e0fe848af0a2142bf606044dc5ceee0aa9eddb56e26895a0", size = 244824, upload-time = "2025-05-23T11:38:59.421Z" }, + { url = "https://files.pythonhosted.org/packages/26/45/36297a4c0cea4de2b2c442fe32f60c3991056c59cdc3cdd5346fbb995c97/coverage-7.8.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:684ca9f58119b8e26bef860db33524ae0365601492e86ba0b71d513f525e7050", size = 242872, upload-time = "2025-05-23T11:39:01.049Z" }, + { url = "https://files.pythonhosted.org/packages/a4/71/e041f1b9420f7b786b1367fa2a375703889ef376e0d48de9f5723fb35f11/coverage-7.8.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8165584ddedb49204c4e18da083913bdf6a982bfb558632a79bdaadcdafd0d48", size = 244179, upload-time = "2025-05-23T11:39:02.709Z" }, + { url = "https://files.pythonhosted.org/packages/bd/db/3c2bf49bdc9de76acf2491fc03130c4ffc51469ce2f6889d2640eb563d77/coverage-7.8.2-cp313-cp313-win32.whl", hash = "sha256:34759ee2c65362163699cc917bdb2a54114dd06d19bab860725f94ef45a3d9b7", size = 214393, upload-time = "2025-05-23T11:39:05.457Z" }, + { url = "https://files.pythonhosted.org/packages/c6/dc/947e75d47ebbb4b02d8babb1fad4ad381410d5bc9da7cfca80b7565ef401/coverage-7.8.2-cp313-cp313-win_amd64.whl", hash = "sha256:2f9bc608fbafaee40eb60a9a53dbfb90f53cc66d3d32c2849dc27cf5638a21e3", size = 215194, upload-time = "2025-05-23T11:39:07.171Z" }, + { url = "https://files.pythonhosted.org/packages/90/31/a980f7df8a37eaf0dc60f932507fda9656b3a03f0abf188474a0ea188d6d/coverage-7.8.2-cp313-cp313-win_arm64.whl", hash = "sha256:9fe449ee461a3b0c7105690419d0b0aba1232f4ff6d120a9e241e58a556733f7", size = 213580, upload-time = "2025-05-23T11:39:08.862Z" }, + { url = "https://files.pythonhosted.org/packages/8a/6a/25a37dd90f6c95f59355629417ebcb74e1c34e38bb1eddf6ca9b38b0fc53/coverage-7.8.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8369a7c8ef66bded2b6484053749ff220dbf83cba84f3398c84c51a6f748a008", size = 212734, upload-time = "2025-05-23T11:39:11.109Z" }, + { url = "https://files.pythonhosted.org/packages/36/8b/3a728b3118988725f40950931abb09cd7f43b3c740f4640a59f1db60e372/coverage-7.8.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:159b81df53a5fcbc7d45dae3adad554fdbde9829a994e15227b3f9d816d00b36", size = 212959, upload-time = "2025-05-23T11:39:12.751Z" }, + { url = "https://files.pythonhosted.org/packages/53/3c/212d94e6add3a3c3f412d664aee452045ca17a066def8b9421673e9482c4/coverage-7.8.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6fcbbd35a96192d042c691c9e0c49ef54bd7ed865846a3c9d624c30bb67ce46", size = 257024, upload-time = "2025-05-23T11:39:15.569Z" }, + { url = "https://files.pythonhosted.org/packages/a4/40/afc03f0883b1e51bbe804707aae62e29c4e8c8bbc365c75e3e4ddeee9ead/coverage-7.8.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:05364b9cc82f138cc86128dc4e2e1251c2981a2218bfcd556fe6b0fbaa3501be", size = 252867, upload-time = "2025-05-23T11:39:17.64Z" }, + { url = "https://files.pythonhosted.org/packages/18/a2/3699190e927b9439c6ded4998941a3c1d6fa99e14cb28d8536729537e307/coverage-7.8.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46d532db4e5ff3979ce47d18e2fe8ecad283eeb7367726da0e5ef88e4fe64740", size = 255096, upload-time = "2025-05-23T11:39:19.328Z" }, + { url = "https://files.pythonhosted.org/packages/b4/06/16e3598b9466456b718eb3e789457d1a5b8bfb22e23b6e8bbc307df5daf0/coverage-7.8.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4000a31c34932e7e4fa0381a3d6deb43dc0c8f458e3e7ea6502e6238e10be625", size = 256276, upload-time = "2025-05-23T11:39:21.077Z" }, + { url = "https://files.pythonhosted.org/packages/a7/d5/4b5a120d5d0223050a53d2783c049c311eea1709fa9de12d1c358e18b707/coverage-7.8.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:43ff5033d657cd51f83015c3b7a443287250dc14e69910577c3e03bd2e06f27b", size = 254478, upload-time = "2025-05-23T11:39:22.838Z" }, + { url = "https://files.pythonhosted.org/packages/ba/85/f9ecdb910ecdb282b121bfcaa32fa8ee8cbd7699f83330ee13ff9bbf1a85/coverage-7.8.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94316e13f0981cbbba132c1f9f365cac1d26716aaac130866ca812006f662199", size = 255255, upload-time = "2025-05-23T11:39:24.644Z" }, + { url = "https://files.pythonhosted.org/packages/50/63/2d624ac7d7ccd4ebbd3c6a9eba9d7fc4491a1226071360d59dd84928ccb2/coverage-7.8.2-cp313-cp313t-win32.whl", hash = "sha256:3f5673888d3676d0a745c3d0e16da338c5eea300cb1f4ada9c872981265e76d8", size = 215109, upload-time = "2025-05-23T11:39:26.722Z" }, + { url = "https://files.pythonhosted.org/packages/22/5e/7053b71462e970e869111c1853afd642212568a350eba796deefdfbd0770/coverage-7.8.2-cp313-cp313t-win_amd64.whl", hash = "sha256:2c08b05ee8d7861e45dc5a2cc4195c8c66dca5ac613144eb6ebeaff2d502e73d", size = 216268, upload-time = "2025-05-23T11:39:28.429Z" }, + { url = "https://files.pythonhosted.org/packages/07/69/afa41aa34147655543dbe96994f8a246daf94b361ccf5edfd5df62ce066a/coverage-7.8.2-cp313-cp313t-win_arm64.whl", hash = "sha256:1e1448bb72b387755e1ff3ef1268a06617afd94188164960dba8d0245a46004b", size = 214071, upload-time = "2025-05-23T11:39:30.55Z" }, + { url = "https://files.pythonhosted.org/packages/69/2f/572b29496d8234e4a7773200dd835a0d32d9e171f2d974f3fe04a9dbc271/coverage-7.8.2-pp39.pp310.pp311-none-any.whl", hash = "sha256:ec455eedf3ba0bbdf8f5a570012617eb305c63cb9f03428d39bf544cb2b94837", size = 203636, upload-time = "2025-05-23T11:39:52.002Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1a/0b9c32220ad694d66062f571cc5cedfa9997b64a591e8a500bb63de1bd40/coverage-7.8.2-py3-none-any.whl", hash = "sha256:726f32ee3713f7359696331a18daf0c3b3a70bb0ae71141b9d3c52be7c595e32", size = 203623, upload-time = "2025-05-23T11:39:53.846Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11'" }, +] + +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, +] + +[[package]] +name = "flake8" +version = "7.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mccabe" }, + { name = "pycodestyle" }, + { name = "pyflakes" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e7/c4/5842fc9fc94584c455543540af62fd9900faade32511fab650e9891ec225/flake8-7.2.0.tar.gz", hash = "sha256:fa558ae3f6f7dbf2b4f22663e5343b6b6023620461f8d4ff2019ef4b5ee70426", size = 48177, upload-time = "2025-03-29T20:08:39.329Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/5c/0627be4c9976d56b1217cb5187b7504e7fd7d3503f8bfd312a04077bd4f7/flake8-7.2.0-py2.py3-none-any.whl", hash = "sha256:93b92ba5bdb60754a6da14fa3b93a9361fd00a59632ada61fd7b130436c40343", size = 57786, upload-time = "2025-03-29T20:08:37.902Z" }, +] + +[[package]] +name = "fonttools" +version = "4.58.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/cf/4d037663e2a1fe30fddb655d755d76e18624be44ad467c07412c2319ab97/fonttools-4.58.0.tar.gz", hash = "sha256:27423d0606a2c7b336913254bf0b1193ebd471d5f725d665e875c5e88a011a43", size = 3514522, upload-time = "2025-05-10T17:36:35.886Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/07/06d01b7239d6632a0984ef29ab496928531862b827cd3aa78309b205850d/fonttools-4.58.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0bcaa65cddbc7d32c77bd0af0b41fdd6448bad0e84365ca79cf8923c27b21e46", size = 2731632, upload-time = "2025-05-10T17:34:55.331Z" }, + { url = "https://files.pythonhosted.org/packages/1d/c7/47d26d48d779b1b084ebc0d9ec07035167992578768237ef553a3eecc8db/fonttools-4.58.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:25590272f89e94ab5a292d518c549f3a88e6a34fa1193797b7047dfea111b048", size = 2303941, upload-time = "2025-05-10T17:34:58.624Z" }, + { url = "https://files.pythonhosted.org/packages/79/2e/ac80c0fea501f1aa93e2b22d72c97a8c0d14239582b7e8c722185a0540a7/fonttools-4.58.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:614435e9a87abe18bd7bc7ceeb8029e8f181c571317161e89fa3e6e0a4f20f5d", size = 4712776, upload-time = "2025-05-10T17:35:01.124Z" }, + { url = "https://files.pythonhosted.org/packages/f2/5c/b41f9c940dc397ecb41765654efc76e06782bfe0783c3e2affc534be181c/fonttools-4.58.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0154bd86d9a9e880f6e937e4d99c2139a624428dd9852072e12d7a85c79d611e", size = 4743251, upload-time = "2025-05-10T17:35:03.815Z" }, + { url = "https://files.pythonhosted.org/packages/3d/c4/0d3807d922a788b603a3fff622af53e732464b88baf0049a181a90f9b1c6/fonttools-4.58.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5b3660df0b02c9cebbf7baf66952c2fd055e43e658aceb92cc95ba19e0a5c8b6", size = 4795635, upload-time = "2025-05-10T17:35:06.134Z" }, + { url = "https://files.pythonhosted.org/packages/46/74/627bed8e2c7e641c9c572f09970b0980e5513fd29e57b394d4aee2261e30/fonttools-4.58.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c43b7f1d0b818427bb1cd20903d1168271abdcde10eb6247b1995c4e1ed63907", size = 4904720, upload-time = "2025-05-10T17:35:09.015Z" }, + { url = "https://files.pythonhosted.org/packages/f9/f2/7e5d082a98eb61fc0c3055e8a0e061a1eb9fc2d93f0661854bf6cb63c519/fonttools-4.58.0-cp310-cp310-win32.whl", hash = "sha256:5450f40c385cdfa21133245f57b9cf8ce45018a04630a98de61eed8da14b8325", size = 2188180, upload-time = "2025-05-10T17:35:11.494Z" }, + { url = "https://files.pythonhosted.org/packages/00/33/ffd914e3c3a585003d770457188c8eaf7266b7a1cceb6d234ab543a9f958/fonttools-4.58.0-cp310-cp310-win_amd64.whl", hash = "sha256:c0553431696eacafee9aefe94dc3c2bf5d658fbdc7fdba5b341c588f935471c6", size = 2233120, upload-time = "2025-05-10T17:35:13.896Z" }, + { url = "https://files.pythonhosted.org/packages/76/2e/9b9bd943872a50cb182382f8f4a99af92d76e800603d5f73e4343fdce61a/fonttools-4.58.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9345b1bb994476d6034996b31891c0c728c1059c05daa59f9ab57d2a4dce0f84", size = 2751920, upload-time = "2025-05-10T17:35:16.487Z" }, + { url = "https://files.pythonhosted.org/packages/9b/8c/e8d6375da893125f610826c2e30e6d2597dfb8dad256f8ff5a54f3089fda/fonttools-4.58.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1d93119ace1e2d39ff1340deb71097932f72b21c054bd3da727a3859825e24e5", size = 2313957, upload-time = "2025-05-10T17:35:18.906Z" }, + { url = "https://files.pythonhosted.org/packages/4f/1b/a29cb00c8c20164b24f88780e298fafd0bbfb25cf8bc7b10c4b69331ad5d/fonttools-4.58.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79c9e4f01bb04f19df272ae35314eb6349fdb2e9497a163cd22a21be999694bd", size = 4913808, upload-time = "2025-05-10T17:35:21.394Z" }, + { url = "https://files.pythonhosted.org/packages/d1/ab/9b9507b65b15190cbfe1ccd3c08067d79268d8312ef20948b16d9f5aa905/fonttools-4.58.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62ecda1465d38248aaf9bee1c17a21cf0b16aef7d121d7d303dbb320a6fd49c2", size = 4935876, upload-time = "2025-05-10T17:35:23.849Z" }, + { url = "https://files.pythonhosted.org/packages/15/e4/1395853bc775b0ab06a1c61cf261779afda7baff3f65cf1197bbd21aa149/fonttools-4.58.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:29d0499bff12a26733c05c1bfd07e68465158201624b2fba4a40b23d96c43f94", size = 4974798, upload-time = "2025-05-10T17:35:26.189Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b9/0358368ef5462f4653a198207b29885bee8d5e23c870f6125450ed88e693/fonttools-4.58.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1871abdb0af582e2d96cc12d88889e3bfa796928f491ec14d34a2e58ca298c7e", size = 5093560, upload-time = "2025-05-10T17:35:28.577Z" }, + { url = "https://files.pythonhosted.org/packages/11/00/f64bc3659980c41eccf2c371e62eb15b40858f02a41a0e9c6258ef094388/fonttools-4.58.0-cp311-cp311-win32.whl", hash = "sha256:e292485d70402093eb94f6ab7669221743838b8bd4c1f45c84ca76b63338e7bf", size = 2186330, upload-time = "2025-05-10T17:35:31.733Z" }, + { url = "https://files.pythonhosted.org/packages/c8/a0/0287be13a1ec7733abf292ffbd76417cea78752d4ce10fecf92d8b1252d6/fonttools-4.58.0-cp311-cp311-win_amd64.whl", hash = "sha256:6df3755fcf9ad70a74ad3134bd5c9738f73c9bb701a304b1c809877b11fe701c", size = 2234687, upload-time = "2025-05-10T17:35:34.015Z" }, + { url = "https://files.pythonhosted.org/packages/6a/4e/1c6b35ec7c04d739df4cf5aace4b7ec284d6af2533a65de21972e2f237d9/fonttools-4.58.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:aa8316798f982c751d71f0025b372151ea36405733b62d0d94d5e7b8dd674fa6", size = 2737502, upload-time = "2025-05-10T17:35:36.436Z" }, + { url = "https://files.pythonhosted.org/packages/fc/72/c6fcafa3c9ed2b69991ae25a1ba7a3fec8bf74928a96e8229c37faa8eda2/fonttools-4.58.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c6db489511e867633b859b11aefe1b7c0d90281c5bdb903413edbb2ba77b97f1", size = 2307214, upload-time = "2025-05-10T17:35:38.939Z" }, + { url = "https://files.pythonhosted.org/packages/52/11/1015cedc9878da6d8d1758049749eef857b693e5828d477287a959c8650f/fonttools-4.58.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:107bdb2dacb1f627db3c4b77fb16d065a10fe88978d02b4fc327b9ecf8a62060", size = 4811136, upload-time = "2025-05-10T17:35:41.491Z" }, + { url = "https://files.pythonhosted.org/packages/32/b9/6a1bc1af6ec17eead5d32e87075e22d0dab001eace0b5a1542d38c6a9483/fonttools-4.58.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba7212068ab20f1128a0475f169068ba8e5b6e35a39ba1980b9f53f6ac9720ac", size = 4876598, upload-time = "2025-05-10T17:35:43.986Z" }, + { url = "https://files.pythonhosted.org/packages/d8/46/b14584c7ea65ad1609fb9632251016cda8a2cd66b15606753b9f888d3677/fonttools-4.58.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f95ea3b6a3b9962da3c82db73f46d6a6845a6c3f3f968f5293b3ac1864e771c2", size = 4872256, upload-time = "2025-05-10T17:35:46.617Z" }, + { url = "https://files.pythonhosted.org/packages/05/78/b2105a7812ca4ef9bf180cd741c82f4522316c652ce2a56f788e2eb54b62/fonttools-4.58.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:874f1225cc4ccfeac32009887f722d7f8b107ca5e867dcee067597eef9d4c80b", size = 5028710, upload-time = "2025-05-10T17:35:49.227Z" }, + { url = "https://files.pythonhosted.org/packages/8c/a9/a38c85ffd30d1f2c7a5460c8abfd1aa66e00c198df3ff0b08117f5c6fcd9/fonttools-4.58.0-cp312-cp312-win32.whl", hash = "sha256:5f3cde64ec99c43260e2e6c4fa70dfb0a5e2c1c1d27a4f4fe4618c16f6c9ff71", size = 2173593, upload-time = "2025-05-10T17:35:51.226Z" }, + { url = "https://files.pythonhosted.org/packages/66/48/29752962a74b7ed95da976b5a968bba1fe611a4a7e50b9fefa345e6e7025/fonttools-4.58.0-cp312-cp312-win_amd64.whl", hash = "sha256:2aee08e2818de45067109a207cbd1b3072939f77751ef05904d506111df5d824", size = 2223230, upload-time = "2025-05-10T17:35:53.653Z" }, + { url = "https://files.pythonhosted.org/packages/0c/d7/d77cae11c445916d767cace93ba8283b3f360197d95d7470b90a9e984e10/fonttools-4.58.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:4809790f2371d8a08e59e1ce2b734c954cf09742e75642d7f4c46cfdac488fdd", size = 2728320, upload-time = "2025-05-10T17:35:56.455Z" }, + { url = "https://files.pythonhosted.org/packages/77/48/7d8b3c519ef4b48081d40310262224a38785e39a8610ccb92a229a6f085d/fonttools-4.58.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b00f240280f204ce4546b05ff3515bf8ff47a9cae914c718490025ea2bb9b324", size = 2302570, upload-time = "2025-05-10T17:35:58.794Z" }, + { url = "https://files.pythonhosted.org/packages/2c/48/156b83eb8fb7261056e448bfda1b495b90e761b28ec23cee10e3e19f1967/fonttools-4.58.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a62015ad463e1925544e9159dd6eefe33ebfb80938d5ab15d8b1c4b354ff47b", size = 4790066, upload-time = "2025-05-10T17:36:01.174Z" }, + { url = "https://files.pythonhosted.org/packages/60/49/aaecb1b3cea2b9b9c7cea6240d6bc8090feb5489a6fbf93cb68003be979b/fonttools-4.58.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ceef6f6ab58061a811967e3e32e630747fcb823dcc33a9a2c80e2d0d17cb292", size = 4861076, upload-time = "2025-05-10T17:36:03.663Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c8/97cbb41bee81ea9daf6109e0f3f70a274a3c69418e5ac6b0193f5dacf506/fonttools-4.58.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c7be21ac52370b515cdbdd0f400803fd29432a4fa4ddb4244ac8b322e54f36c0", size = 4858394, upload-time = "2025-05-10T17:36:06.087Z" }, + { url = "https://files.pythonhosted.org/packages/4d/23/c2c231457361f869a7d7374a557208e303b469d48a4a697c0fb249733ea1/fonttools-4.58.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:85836be4c3c4aacf6fcb7a6f263896d0e9ce431da9fa6fe9213d70f221f131c9", size = 5002160, upload-time = "2025-05-10T17:36:08.178Z" }, + { url = "https://files.pythonhosted.org/packages/a9/e0/c2262f941a43b810c5c192db94b5d1ce8eda91bec2757f7e2416398f4072/fonttools-4.58.0-cp313-cp313-win32.whl", hash = "sha256:2b32b7130277bd742cb8c4379a6a303963597d22adea77a940343f3eadbcaa4c", size = 2171919, upload-time = "2025-05-10T17:36:10.644Z" }, + { url = "https://files.pythonhosted.org/packages/8f/ee/e4aa7bb4ce510ad57a808d321df1bbed1eeb6e1dfb20aaee1a5d9c076849/fonttools-4.58.0-cp313-cp313-win_amd64.whl", hash = "sha256:75e68ee2ec9aaa173cf5e33f243da1d51d653d5e25090f2722bc644a78db0f1a", size = 2222972, upload-time = "2025-05-10T17:36:12.495Z" }, + { url = "https://files.pythonhosted.org/packages/9b/1f/4417c26e26a1feab85a27e927f7a73d8aabc84544be8ba108ce4aa90eb1e/fonttools-4.58.0-py3-none-any.whl", hash = "sha256:c96c36880be2268be409df7b08c5b5dacac1827083461a6bc2cb07b8cbcec1d7", size = 1111440, upload-time = "2025-05-10T17:36:33.607Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "isort" +version = "6.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b8/21/1e2a441f74a653a144224d7d21afe8f4169e6c7c20bb13aec3a2dc3815e0/isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450", size = 821955, upload-time = "2025-02-26T21:13:16.955Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/11/114d0a5f4dabbdcedc1125dee0888514c3c3b16d3e9facad87ed96fad97c/isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615", size = 94186, upload-time = "2025-02-26T21:13:14.911Z" }, +] + +[[package]] +name = "kiwisolver" +version = "1.4.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/59/7c91426a8ac292e1cdd53a63b6d9439abd573c875c3f92c146767dd33faf/kiwisolver-1.4.8.tar.gz", hash = "sha256:23d5f023bdc8c7e54eb65f03ca5d5bb25b601eac4d7f1a042888a1f45237987e", size = 97538, upload-time = "2024-12-24T18:30:51.519Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/5f/4d8e9e852d98ecd26cdf8eaf7ed8bc33174033bba5e07001b289f07308fd/kiwisolver-1.4.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88c6f252f6816a73b1f8c904f7bbe02fd67c09a69f7cb8a0eecdbf5ce78e63db", size = 124623, upload-time = "2024-12-24T18:28:17.687Z" }, + { url = "https://files.pythonhosted.org/packages/1d/70/7f5af2a18a76fe92ea14675f8bd88ce53ee79e37900fa5f1a1d8e0b42998/kiwisolver-1.4.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c72941acb7b67138f35b879bbe85be0f6c6a70cab78fe3ef6db9c024d9223e5b", size = 66720, upload-time = "2024-12-24T18:28:19.158Z" }, + { url = "https://files.pythonhosted.org/packages/c6/13/e15f804a142353aefd089fadc8f1d985561a15358c97aca27b0979cb0785/kiwisolver-1.4.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce2cf1e5688edcb727fdf7cd1bbd0b6416758996826a8be1d958f91880d0809d", size = 65413, upload-time = "2024-12-24T18:28:20.064Z" }, + { url = "https://files.pythonhosted.org/packages/ce/6d/67d36c4d2054e83fb875c6b59d0809d5c530de8148846b1370475eeeece9/kiwisolver-1.4.8-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c8bf637892dc6e6aad2bc6d4d69d08764166e5e3f69d469e55427b6ac001b19d", size = 1650826, upload-time = "2024-12-24T18:28:21.203Z" }, + { url = "https://files.pythonhosted.org/packages/de/c6/7b9bb8044e150d4d1558423a1568e4f227193662a02231064e3824f37e0a/kiwisolver-1.4.8-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:034d2c891f76bd3edbdb3ea11140d8510dca675443da7304205a2eaa45d8334c", size = 1628231, upload-time = "2024-12-24T18:28:23.851Z" }, + { url = "https://files.pythonhosted.org/packages/b6/38/ad10d437563063eaaedbe2c3540a71101fc7fb07a7e71f855e93ea4de605/kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d47b28d1dfe0793d5e96bce90835e17edf9a499b53969b03c6c47ea5985844c3", size = 1408938, upload-time = "2024-12-24T18:28:26.687Z" }, + { url = "https://files.pythonhosted.org/packages/52/ce/c0106b3bd7f9e665c5f5bc1e07cc95b5dabd4e08e3dad42dbe2faad467e7/kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb158fe28ca0c29f2260cca8c43005329ad58452c36f0edf298204de32a9a3ed", size = 1422799, upload-time = "2024-12-24T18:28:30.538Z" }, + { url = "https://files.pythonhosted.org/packages/d0/87/efb704b1d75dc9758087ba374c0f23d3254505edaedd09cf9d247f7878b9/kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5536185fce131780ebd809f8e623bf4030ce1b161353166c49a3c74c287897f", size = 1354362, upload-time = "2024-12-24T18:28:32.943Z" }, + { url = "https://files.pythonhosted.org/packages/eb/b3/fd760dc214ec9a8f208b99e42e8f0130ff4b384eca8b29dd0efc62052176/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:369b75d40abedc1da2c1f4de13f3482cb99e3237b38726710f4a793432b1c5ff", size = 2222695, upload-time = "2024-12-24T18:28:35.641Z" }, + { url = "https://files.pythonhosted.org/packages/a2/09/a27fb36cca3fc01700687cc45dae7a6a5f8eeb5f657b9f710f788748e10d/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:641f2ddf9358c80faa22e22eb4c9f54bd3f0e442e038728f500e3b978d00aa7d", size = 2370802, upload-time = "2024-12-24T18:28:38.357Z" }, + { url = "https://files.pythonhosted.org/packages/3d/c3/ba0a0346db35fe4dc1f2f2cf8b99362fbb922d7562e5f911f7ce7a7b60fa/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d561d2d8883e0819445cfe58d7ddd673e4015c3c57261d7bdcd3710d0d14005c", size = 2334646, upload-time = "2024-12-24T18:28:40.941Z" }, + { url = "https://files.pythonhosted.org/packages/41/52/942cf69e562f5ed253ac67d5c92a693745f0bed3c81f49fc0cbebe4d6b00/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1732e065704b47c9afca7ffa272f845300a4eb959276bf6970dc07265e73b605", size = 2467260, upload-time = "2024-12-24T18:28:42.273Z" }, + { url = "https://files.pythonhosted.org/packages/32/26/2d9668f30d8a494b0411d4d7d4ea1345ba12deb6a75274d58dd6ea01e951/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bcb1ebc3547619c3b58a39e2448af089ea2ef44b37988caf432447374941574e", size = 2288633, upload-time = "2024-12-24T18:28:44.87Z" }, + { url = "https://files.pythonhosted.org/packages/98/99/0dd05071654aa44fe5d5e350729961e7bb535372935a45ac89a8924316e6/kiwisolver-1.4.8-cp310-cp310-win_amd64.whl", hash = "sha256:89c107041f7b27844179ea9c85d6da275aa55ecf28413e87624d033cf1f6b751", size = 71885, upload-time = "2024-12-24T18:28:47.346Z" }, + { url = "https://files.pythonhosted.org/packages/6c/fc/822e532262a97442989335394d441cd1d0448c2e46d26d3e04efca84df22/kiwisolver-1.4.8-cp310-cp310-win_arm64.whl", hash = "sha256:b5773efa2be9eb9fcf5415ea3ab70fc785d598729fd6057bea38d539ead28271", size = 65175, upload-time = "2024-12-24T18:28:49.651Z" }, + { url = "https://files.pythonhosted.org/packages/da/ed/c913ee28936c371418cb167b128066ffb20bbf37771eecc2c97edf8a6e4c/kiwisolver-1.4.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a4d3601908c560bdf880f07d94f31d734afd1bb71e96585cace0e38ef44c6d84", size = 124635, upload-time = "2024-12-24T18:28:51.826Z" }, + { url = "https://files.pythonhosted.org/packages/4c/45/4a7f896f7467aaf5f56ef093d1f329346f3b594e77c6a3c327b2d415f521/kiwisolver-1.4.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:856b269c4d28a5c0d5e6c1955ec36ebfd1651ac00e1ce0afa3e28da95293b561", size = 66717, upload-time = "2024-12-24T18:28:54.256Z" }, + { url = "https://files.pythonhosted.org/packages/5f/b4/c12b3ac0852a3a68f94598d4c8d569f55361beef6159dce4e7b624160da2/kiwisolver-1.4.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c2b9a96e0f326205af81a15718a9073328df1173a2619a68553decb7097fd5d7", size = 65413, upload-time = "2024-12-24T18:28:55.184Z" }, + { url = "https://files.pythonhosted.org/packages/a9/98/1df4089b1ed23d83d410adfdc5947245c753bddfbe06541c4aae330e9e70/kiwisolver-1.4.8-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5020c83e8553f770cb3b5fc13faac40f17e0b205bd237aebd21d53d733adb03", size = 1343994, upload-time = "2024-12-24T18:28:57.493Z" }, + { url = "https://files.pythonhosted.org/packages/8d/bf/b4b169b050c8421a7c53ea1ea74e4ef9c335ee9013216c558a047f162d20/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dace81d28c787956bfbfbbfd72fdcef014f37d9b48830829e488fdb32b49d954", size = 1434804, upload-time = "2024-12-24T18:29:00.077Z" }, + { url = "https://files.pythonhosted.org/packages/66/5a/e13bd341fbcf73325ea60fdc8af752addf75c5079867af2e04cc41f34434/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11e1022b524bd48ae56c9b4f9296bce77e15a2e42a502cceba602f804b32bb79", size = 1450690, upload-time = "2024-12-24T18:29:01.401Z" }, + { url = "https://files.pythonhosted.org/packages/9b/4f/5955dcb376ba4a830384cc6fab7d7547bd6759fe75a09564910e9e3bb8ea/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b9b4d2892fefc886f30301cdd80debd8bb01ecdf165a449eb6e78f79f0fabd6", size = 1376839, upload-time = "2024-12-24T18:29:02.685Z" }, + { url = "https://files.pythonhosted.org/packages/3a/97/5edbed69a9d0caa2e4aa616ae7df8127e10f6586940aa683a496c2c280b9/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a96c0e790ee875d65e340ab383700e2b4891677b7fcd30a699146f9384a2bb0", size = 1435109, upload-time = "2024-12-24T18:29:04.113Z" }, + { url = "https://files.pythonhosted.org/packages/13/fc/e756382cb64e556af6c1809a1bbb22c141bbc2445049f2da06b420fe52bf/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:23454ff084b07ac54ca8be535f4174170c1094a4cff78fbae4f73a4bcc0d4dab", size = 2245269, upload-time = "2024-12-24T18:29:05.488Z" }, + { url = "https://files.pythonhosted.org/packages/76/15/e59e45829d7f41c776d138245cabae6515cb4eb44b418f6d4109c478b481/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:87b287251ad6488e95b4f0b4a79a6d04d3ea35fde6340eb38fbd1ca9cd35bbbc", size = 2393468, upload-time = "2024-12-24T18:29:06.79Z" }, + { url = "https://files.pythonhosted.org/packages/e9/39/483558c2a913ab8384d6e4b66a932406f87c95a6080112433da5ed668559/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b21dbe165081142b1232a240fc6383fd32cdd877ca6cc89eab93e5f5883e1c25", size = 2355394, upload-time = "2024-12-24T18:29:08.24Z" }, + { url = "https://files.pythonhosted.org/packages/01/aa/efad1fbca6570a161d29224f14b082960c7e08268a133fe5dc0f6906820e/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:768cade2c2df13db52475bd28d3a3fac8c9eff04b0e9e2fda0f3760f20b3f7fc", size = 2490901, upload-time = "2024-12-24T18:29:09.653Z" }, + { url = "https://files.pythonhosted.org/packages/c9/4f/15988966ba46bcd5ab9d0c8296914436720dd67fca689ae1a75b4ec1c72f/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d47cfb2650f0e103d4bf68b0b5804c68da97272c84bb12850d877a95c056bd67", size = 2312306, upload-time = "2024-12-24T18:29:12.644Z" }, + { url = "https://files.pythonhosted.org/packages/2d/27/bdf1c769c83f74d98cbc34483a972f221440703054894a37d174fba8aa68/kiwisolver-1.4.8-cp311-cp311-win_amd64.whl", hash = "sha256:ed33ca2002a779a2e20eeb06aea7721b6e47f2d4b8a8ece979d8ba9e2a167e34", size = 71966, upload-time = "2024-12-24T18:29:14.089Z" }, + { url = "https://files.pythonhosted.org/packages/4a/c9/9642ea855604aeb2968a8e145fc662edf61db7632ad2e4fb92424be6b6c0/kiwisolver-1.4.8-cp311-cp311-win_arm64.whl", hash = "sha256:16523b40aab60426ffdebe33ac374457cf62863e330a90a0383639ce14bf44b2", size = 65311, upload-time = "2024-12-24T18:29:15.892Z" }, + { url = "https://files.pythonhosted.org/packages/fc/aa/cea685c4ab647f349c3bc92d2daf7ae34c8e8cf405a6dcd3a497f58a2ac3/kiwisolver-1.4.8-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d6af5e8815fd02997cb6ad9bbed0ee1e60014438ee1a5c2444c96f87b8843502", size = 124152, upload-time = "2024-12-24T18:29:16.85Z" }, + { url = "https://files.pythonhosted.org/packages/c5/0b/8db6d2e2452d60d5ebc4ce4b204feeb16176a851fd42462f66ade6808084/kiwisolver-1.4.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bade438f86e21d91e0cf5dd7c0ed00cda0f77c8c1616bd83f9fc157fa6760d31", size = 66555, upload-time = "2024-12-24T18:29:19.146Z" }, + { url = "https://files.pythonhosted.org/packages/60/26/d6a0db6785dd35d3ba5bf2b2df0aedc5af089962c6eb2cbf67a15b81369e/kiwisolver-1.4.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b83dc6769ddbc57613280118fb4ce3cd08899cc3369f7d0e0fab518a7cf37fdb", size = 65067, upload-time = "2024-12-24T18:29:20.096Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ed/1d97f7e3561e09757a196231edccc1bcf59d55ddccefa2afc9c615abd8e0/kiwisolver-1.4.8-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111793b232842991be367ed828076b03d96202c19221b5ebab421ce8bcad016f", size = 1378443, upload-time = "2024-12-24T18:29:22.843Z" }, + { url = "https://files.pythonhosted.org/packages/29/61/39d30b99954e6b46f760e6289c12fede2ab96a254c443639052d1b573fbc/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:257af1622860e51b1a9d0ce387bf5c2c4f36a90594cb9514f55b074bcc787cfc", size = 1472728, upload-time = "2024-12-24T18:29:24.463Z" }, + { url = "https://files.pythonhosted.org/packages/0c/3e/804163b932f7603ef256e4a715e5843a9600802bb23a68b4e08c8c0ff61d/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69b5637c3f316cab1ec1c9a12b8c5f4750a4c4b71af9157645bf32830e39c03a", size = 1478388, upload-time = "2024-12-24T18:29:25.776Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9e/60eaa75169a154700be74f875a4d9961b11ba048bef315fbe89cb6999056/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:782bb86f245ec18009890e7cb8d13a5ef54dcf2ebe18ed65f795e635a96a1c6a", size = 1413849, upload-time = "2024-12-24T18:29:27.202Z" }, + { url = "https://files.pythonhosted.org/packages/bc/b3/9458adb9472e61a998c8c4d95cfdfec91c73c53a375b30b1428310f923e4/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc978a80a0db3a66d25767b03688f1147a69e6237175c0f4ffffaaedf744055a", size = 1475533, upload-time = "2024-12-24T18:29:28.638Z" }, + { url = "https://files.pythonhosted.org/packages/e4/7a/0a42d9571e35798de80aef4bb43a9b672aa7f8e58643d7bd1950398ffb0a/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:36dbbfd34838500a31f52c9786990d00150860e46cd5041386f217101350f0d3", size = 2268898, upload-time = "2024-12-24T18:29:30.368Z" }, + { url = "https://files.pythonhosted.org/packages/d9/07/1255dc8d80271400126ed8db35a1795b1a2c098ac3a72645075d06fe5c5d/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:eaa973f1e05131de5ff3569bbba7f5fd07ea0595d3870ed4a526d486fe57fa1b", size = 2425605, upload-time = "2024-12-24T18:29:33.151Z" }, + { url = "https://files.pythonhosted.org/packages/84/df/5a3b4cf13780ef6f6942df67b138b03b7e79e9f1f08f57c49957d5867f6e/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a66f60f8d0c87ab7f59b6fb80e642ebb29fec354a4dfad687ca4092ae69d04f4", size = 2375801, upload-time = "2024-12-24T18:29:34.584Z" }, + { url = "https://files.pythonhosted.org/packages/8f/10/2348d068e8b0f635c8c86892788dac7a6b5c0cb12356620ab575775aad89/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858416b7fb777a53f0c59ca08190ce24e9abbd3cffa18886a5781b8e3e26f65d", size = 2520077, upload-time = "2024-12-24T18:29:36.138Z" }, + { url = "https://files.pythonhosted.org/packages/32/d8/014b89fee5d4dce157d814303b0fce4d31385a2af4c41fed194b173b81ac/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:085940635c62697391baafaaeabdf3dd7a6c3643577dde337f4d66eba021b2b8", size = 2338410, upload-time = "2024-12-24T18:29:39.991Z" }, + { url = "https://files.pythonhosted.org/packages/bd/72/dfff0cc97f2a0776e1c9eb5bef1ddfd45f46246c6533b0191887a427bca5/kiwisolver-1.4.8-cp312-cp312-win_amd64.whl", hash = "sha256:01c3d31902c7db5fb6182832713d3b4122ad9317c2c5877d0539227d96bb2e50", size = 71853, upload-time = "2024-12-24T18:29:42.006Z" }, + { url = "https://files.pythonhosted.org/packages/dc/85/220d13d914485c0948a00f0b9eb419efaf6da81b7d72e88ce2391f7aed8d/kiwisolver-1.4.8-cp312-cp312-win_arm64.whl", hash = "sha256:a3c44cb68861de93f0c4a8175fbaa691f0aa22550c331fefef02b618a9dcb476", size = 65424, upload-time = "2024-12-24T18:29:44.38Z" }, + { url = "https://files.pythonhosted.org/packages/79/b3/e62464a652f4f8cd9006e13d07abad844a47df1e6537f73ddfbf1bc997ec/kiwisolver-1.4.8-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1c8ceb754339793c24aee1c9fb2485b5b1f5bb1c2c214ff13368431e51fc9a09", size = 124156, upload-time = "2024-12-24T18:29:45.368Z" }, + { url = "https://files.pythonhosted.org/packages/8d/2d/f13d06998b546a2ad4f48607a146e045bbe48030774de29f90bdc573df15/kiwisolver-1.4.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a62808ac74b5e55a04a408cda6156f986cefbcf0ada13572696b507cc92fa1", size = 66555, upload-time = "2024-12-24T18:29:46.37Z" }, + { url = "https://files.pythonhosted.org/packages/59/e3/b8bd14b0a54998a9fd1e8da591c60998dc003618cb19a3f94cb233ec1511/kiwisolver-1.4.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:68269e60ee4929893aad82666821aaacbd455284124817af45c11e50a4b42e3c", size = 65071, upload-time = "2024-12-24T18:29:47.333Z" }, + { url = "https://files.pythonhosted.org/packages/f0/1c/6c86f6d85ffe4d0ce04228d976f00674f1df5dc893bf2dd4f1928748f187/kiwisolver-1.4.8-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34d142fba9c464bc3bbfeff15c96eab0e7310343d6aefb62a79d51421fcc5f1b", size = 1378053, upload-time = "2024-12-24T18:29:49.636Z" }, + { url = "https://files.pythonhosted.org/packages/4e/b9/1c6e9f6dcb103ac5cf87cb695845f5fa71379021500153566d8a8a9fc291/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc373e0eef45b59197de815b1b28ef89ae3955e7722cc9710fb91cd77b7f47", size = 1472278, upload-time = "2024-12-24T18:29:51.164Z" }, + { url = "https://files.pythonhosted.org/packages/ee/81/aca1eb176de671f8bda479b11acdc42c132b61a2ac861c883907dde6debb/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77e6f57a20b9bd4e1e2cedda4d0b986ebd0216236f0106e55c28aea3d3d69b16", size = 1478139, upload-time = "2024-12-24T18:29:52.594Z" }, + { url = "https://files.pythonhosted.org/packages/49/f4/e081522473671c97b2687d380e9e4c26f748a86363ce5af48b4a28e48d06/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08e77738ed7538f036cd1170cbed942ef749137b1311fa2bbe2a7fda2f6bf3cc", size = 1413517, upload-time = "2024-12-24T18:29:53.941Z" }, + { url = "https://files.pythonhosted.org/packages/8f/e9/6a7d025d8da8c4931522922cd706105aa32b3291d1add8c5427cdcd66e63/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5ce1e481a74b44dd5e92ff03ea0cb371ae7a0268318e202be06c8f04f4f1246", size = 1474952, upload-time = "2024-12-24T18:29:56.523Z" }, + { url = "https://files.pythonhosted.org/packages/82/13/13fa685ae167bee5d94b415991c4fc7bb0a1b6ebea6e753a87044b209678/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fc2ace710ba7c1dfd1a3b42530b62b9ceed115f19a1656adefce7b1782a37794", size = 2269132, upload-time = "2024-12-24T18:29:57.989Z" }, + { url = "https://files.pythonhosted.org/packages/ef/92/bb7c9395489b99a6cb41d502d3686bac692586db2045adc19e45ee64ed23/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3452046c37c7692bd52b0e752b87954ef86ee2224e624ef7ce6cb21e8c41cc1b", size = 2425997, upload-time = "2024-12-24T18:29:59.393Z" }, + { url = "https://files.pythonhosted.org/packages/ed/12/87f0e9271e2b63d35d0d8524954145837dd1a6c15b62a2d8c1ebe0f182b4/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e9a60b50fe8b2ec6f448fe8d81b07e40141bfced7f896309df271a0b92f80f3", size = 2376060, upload-time = "2024-12-24T18:30:01.338Z" }, + { url = "https://files.pythonhosted.org/packages/02/6e/c8af39288edbce8bf0fa35dee427b082758a4b71e9c91ef18fa667782138/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:918139571133f366e8362fa4a297aeba86c7816b7ecf0bc79168080e2bd79957", size = 2520471, upload-time = "2024-12-24T18:30:04.574Z" }, + { url = "https://files.pythonhosted.org/packages/13/78/df381bc7b26e535c91469f77f16adcd073beb3e2dd25042efd064af82323/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e063ef9f89885a1d68dd8b2e18f5ead48653176d10a0e324e3b0030e3a69adeb", size = 2338793, upload-time = "2024-12-24T18:30:06.25Z" }, + { url = "https://files.pythonhosted.org/packages/d0/dc/c1abe38c37c071d0fc71c9a474fd0b9ede05d42f5a458d584619cfd2371a/kiwisolver-1.4.8-cp313-cp313-win_amd64.whl", hash = "sha256:a17b7c4f5b2c51bb68ed379defd608a03954a1845dfed7cc0117f1cc8a9b7fd2", size = 71855, upload-time = "2024-12-24T18:30:07.535Z" }, + { url = "https://files.pythonhosted.org/packages/a0/b6/21529d595b126ac298fdd90b705d87d4c5693de60023e0efcb4f387ed99e/kiwisolver-1.4.8-cp313-cp313-win_arm64.whl", hash = "sha256:3cd3bc628b25f74aedc6d374d5babf0166a92ff1317f46267f12d2ed54bc1d30", size = 65430, upload-time = "2024-12-24T18:30:08.504Z" }, + { url = "https://files.pythonhosted.org/packages/34/bd/b89380b7298e3af9b39f49334e3e2a4af0e04819789f04b43d560516c0c8/kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:370fd2df41660ed4e26b8c9d6bbcad668fbe2560462cba151a721d49e5b6628c", size = 126294, upload-time = "2024-12-24T18:30:09.508Z" }, + { url = "https://files.pythonhosted.org/packages/83/41/5857dc72e5e4148eaac5aa76e0703e594e4465f8ab7ec0fc60e3a9bb8fea/kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:84a2f830d42707de1d191b9490ac186bf7997a9495d4e9072210a1296345f7dc", size = 67736, upload-time = "2024-12-24T18:30:11.039Z" }, + { url = "https://files.pythonhosted.org/packages/e1/d1/be059b8db56ac270489fb0b3297fd1e53d195ba76e9bbb30e5401fa6b759/kiwisolver-1.4.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7a3ad337add5148cf51ce0b55642dc551c0b9d6248458a757f98796ca7348712", size = 66194, upload-time = "2024-12-24T18:30:14.886Z" }, + { url = "https://files.pythonhosted.org/packages/e1/83/4b73975f149819eb7dcf9299ed467eba068ecb16439a98990dcb12e63fdd/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7506488470f41169b86d8c9aeff587293f530a23a23a49d6bc64dab66bedc71e", size = 1465942, upload-time = "2024-12-24T18:30:18.927Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2c/30a5cdde5102958e602c07466bce058b9d7cb48734aa7a4327261ac8e002/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f0121b07b356a22fb0414cec4666bbe36fd6d0d759db3d37228f496ed67c880", size = 1595341, upload-time = "2024-12-24T18:30:22.102Z" }, + { url = "https://files.pythonhosted.org/packages/ff/9b/1e71db1c000385aa069704f5990574b8244cce854ecd83119c19e83c9586/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d6d6bd87df62c27d4185de7c511c6248040afae67028a8a22012b010bc7ad062", size = 1598455, upload-time = "2024-12-24T18:30:24.947Z" }, + { url = "https://files.pythonhosted.org/packages/85/92/c8fec52ddf06231b31cbb779af77e99b8253cd96bd135250b9498144c78b/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:291331973c64bb9cce50bbe871fb2e675c4331dab4f31abe89f175ad7679a4d7", size = 1522138, upload-time = "2024-12-24T18:30:26.286Z" }, + { url = "https://files.pythonhosted.org/packages/0b/51/9eb7e2cd07a15d8bdd976f6190c0164f92ce1904e5c0c79198c4972926b7/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:893f5525bb92d3d735878ec00f781b2de998333659507d29ea4466208df37bed", size = 1582857, upload-time = "2024-12-24T18:30:28.86Z" }, + { url = "https://files.pythonhosted.org/packages/0f/95/c5a00387a5405e68ba32cc64af65ce881a39b98d73cc394b24143bebc5b8/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b47a465040146981dc9db8647981b8cb96366fbc8d452b031e4f8fdffec3f26d", size = 2293129, upload-time = "2024-12-24T18:30:30.34Z" }, + { url = "https://files.pythonhosted.org/packages/44/83/eeb7af7d706b8347548313fa3a3a15931f404533cc54fe01f39e830dd231/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:99cea8b9dd34ff80c521aef46a1dddb0dcc0283cf18bde6d756f1e6f31772165", size = 2421538, upload-time = "2024-12-24T18:30:33.334Z" }, + { url = "https://files.pythonhosted.org/packages/05/f9/27e94c1b3eb29e6933b6986ffc5fa1177d2cd1f0c8efc5f02c91c9ac61de/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:151dffc4865e5fe6dafce5480fab84f950d14566c480c08a53c663a0020504b6", size = 2390661, upload-time = "2024-12-24T18:30:34.939Z" }, + { url = "https://files.pythonhosted.org/packages/d9/d4/3c9735faa36ac591a4afcc2980d2691000506050b7a7e80bcfe44048daa7/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:577facaa411c10421314598b50413aa1ebcf5126f704f1e5d72d7e4e9f020d90", size = 2546710, upload-time = "2024-12-24T18:30:37.281Z" }, + { url = "https://files.pythonhosted.org/packages/4c/fa/be89a49c640930180657482a74970cdcf6f7072c8d2471e1babe17a222dc/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:be4816dc51c8a471749d664161b434912eee82f2ea66bd7628bd14583a833e85", size = 2349213, upload-time = "2024-12-24T18:30:40.019Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f9/ae81c47a43e33b93b0a9819cac6723257f5da2a5a60daf46aa5c7226ea85/kiwisolver-1.4.8-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e7a019419b7b510f0f7c9dceff8c5eae2392037eae483a7f9162625233802b0a", size = 60403, upload-time = "2024-12-24T18:30:41.372Z" }, + { url = "https://files.pythonhosted.org/packages/58/ca/f92b5cb6f4ce0c1ebfcfe3e2e42b96917e16f7090e45b21102941924f18f/kiwisolver-1.4.8-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:286b18e86682fd2217a48fc6be6b0f20c1d0ed10958d8dc53453ad58d7be0bf8", size = 58657, upload-time = "2024-12-24T18:30:42.392Z" }, + { url = "https://files.pythonhosted.org/packages/80/28/ae0240f732f0484d3a4dc885d055653c47144bdf59b670aae0ec3c65a7c8/kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4191ee8dfd0be1c3666ccbac178c5a05d5f8d689bbe3fc92f3c4abec817f8fe0", size = 84948, upload-time = "2024-12-24T18:30:44.703Z" }, + { url = "https://files.pythonhosted.org/packages/5d/eb/78d50346c51db22c7203c1611f9b513075f35c4e0e4877c5dde378d66043/kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cd2785b9391f2873ad46088ed7599a6a71e762e1ea33e87514b1a441ed1da1c", size = 81186, upload-time = "2024-12-24T18:30:45.654Z" }, + { url = "https://files.pythonhosted.org/packages/43/f8/7259f18c77adca88d5f64f9a522792e178b2691f3748817a8750c2d216ef/kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c07b29089b7ba090b6f1a669f1411f27221c3662b3a1b7010e67b59bb5a6f10b", size = 80279, upload-time = "2024-12-24T18:30:47.951Z" }, + { url = "https://files.pythonhosted.org/packages/3a/1d/50ad811d1c5dae091e4cf046beba925bcae0a610e79ae4c538f996f63ed5/kiwisolver-1.4.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:65ea09a5a3faadd59c2ce96dc7bf0f364986a315949dc6374f04396b0d60e09b", size = 71762, upload-time = "2024-12-24T18:30:48.903Z" }, +] + +[[package]] +name = "matplotlib" +version = "3.10.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/26/91/d49359a21893183ed2a5b6c76bec40e0b1dcbf8ca148f864d134897cfc75/matplotlib-3.10.3.tar.gz", hash = "sha256:2f82d2c5bb7ae93aaaa4cd42aca65d76ce6376f83304fa3a630b569aca274df0", size = 34799811, upload-time = "2025-05-08T19:10:54.39Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/ea/2bba25d289d389c7451f331ecd593944b3705f06ddf593fa7be75037d308/matplotlib-3.10.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:213fadd6348d106ca7db99e113f1bea1e65e383c3ba76e8556ba4a3054b65ae7", size = 8167862, upload-time = "2025-05-08T19:09:39.563Z" }, + { url = "https://files.pythonhosted.org/packages/41/81/cc70b5138c926604e8c9ed810ed4c79e8116ba72e02230852f5c12c87ba2/matplotlib-3.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3bec61cb8221f0ca6313889308326e7bb303d0d302c5cc9e523b2f2e6c73deb", size = 8042149, upload-time = "2025-05-08T19:09:42.413Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9a/0ff45b6bfa42bb16de597e6058edf2361c298ad5ef93b327728145161bbf/matplotlib-3.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c21ae75651c0231b3ba014b6d5e08fb969c40cdb5a011e33e99ed0c9ea86ecb", size = 8453719, upload-time = "2025-05-08T19:09:44.901Z" }, + { url = "https://files.pythonhosted.org/packages/85/c7/1866e972fed6d71ef136efbc980d4d1854ab7ef1ea8152bbd995ca231c81/matplotlib-3.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a49e39755580b08e30e3620efc659330eac5d6534ab7eae50fa5e31f53ee4e30", size = 8590801, upload-time = "2025-05-08T19:09:47.404Z" }, + { url = "https://files.pythonhosted.org/packages/5d/b9/748f6626d534ab7e255bdc39dc22634d337cf3ce200f261b5d65742044a1/matplotlib-3.10.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cf4636203e1190871d3a73664dea03d26fb019b66692cbfd642faafdad6208e8", size = 9402111, upload-time = "2025-05-08T19:09:49.474Z" }, + { url = "https://files.pythonhosted.org/packages/1f/78/8bf07bd8fb67ea5665a6af188e70b57fcb2ab67057daa06b85a08e59160a/matplotlib-3.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:fd5641a9bb9d55f4dd2afe897a53b537c834b9012684c8444cc105895c8c16fd", size = 8057213, upload-time = "2025-05-08T19:09:51.489Z" }, + { url = "https://files.pythonhosted.org/packages/f5/bd/af9f655456f60fe1d575f54fb14704ee299b16e999704817a7645dfce6b0/matplotlib-3.10.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0ef061f74cd488586f552d0c336b2f078d43bc00dc473d2c3e7bfee2272f3fa8", size = 8178873, upload-time = "2025-05-08T19:09:53.857Z" }, + { url = "https://files.pythonhosted.org/packages/c2/86/e1c86690610661cd716eda5f9d0b35eaf606ae6c9b6736687cfc8f2d0cd8/matplotlib-3.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d96985d14dc5f4a736bbea4b9de9afaa735f8a0fc2ca75be2fa9e96b2097369d", size = 8052205, upload-time = "2025-05-08T19:09:55.684Z" }, + { url = "https://files.pythonhosted.org/packages/54/51/a9f8e49af3883dacddb2da1af5fca1f7468677f1188936452dd9aaaeb9ed/matplotlib-3.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c5f0283da91e9522bdba4d6583ed9d5521566f63729ffb68334f86d0bb98049", size = 8465823, upload-time = "2025-05-08T19:09:57.442Z" }, + { url = "https://files.pythonhosted.org/packages/e7/e3/c82963a3b86d6e6d5874cbeaa390166458a7f1961bab9feb14d3d1a10f02/matplotlib-3.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdfa07c0ec58035242bc8b2c8aae37037c9a886370eef6850703d7583e19964b", size = 8606464, upload-time = "2025-05-08T19:09:59.471Z" }, + { url = "https://files.pythonhosted.org/packages/0e/34/24da1027e7fcdd9e82da3194c470143c551852757a4b473a09a012f5b945/matplotlib-3.10.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c0b9849a17bce080a16ebcb80a7b714b5677d0ec32161a2cc0a8e5a6030ae220", size = 9413103, upload-time = "2025-05-08T19:10:03.208Z" }, + { url = "https://files.pythonhosted.org/packages/a6/da/948a017c3ea13fd4a97afad5fdebe2f5bbc4d28c0654510ce6fd6b06b7bd/matplotlib-3.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:eef6ed6c03717083bc6d69c2d7ee8624205c29a8e6ea5a31cd3492ecdbaee1e1", size = 8065492, upload-time = "2025-05-08T19:10:05.271Z" }, + { url = "https://files.pythonhosted.org/packages/eb/43/6b80eb47d1071f234ef0c96ca370c2ca621f91c12045f1401b5c9b28a639/matplotlib-3.10.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0ab1affc11d1f495ab9e6362b8174a25afc19c081ba5b0775ef00533a4236eea", size = 8179689, upload-time = "2025-05-08T19:10:07.602Z" }, + { url = "https://files.pythonhosted.org/packages/0f/70/d61a591958325c357204870b5e7b164f93f2a8cca1dc6ce940f563909a13/matplotlib-3.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2a818d8bdcafa7ed2eed74487fdb071c09c1ae24152d403952adad11fa3c65b4", size = 8050466, upload-time = "2025-05-08T19:10:09.383Z" }, + { url = "https://files.pythonhosted.org/packages/e7/75/70c9d2306203148cc7902a961240c5927dd8728afedf35e6a77e105a2985/matplotlib-3.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748ebc3470c253e770b17d8b0557f0aa85cf8c63fd52f1a61af5b27ec0b7ffee", size = 8456252, upload-time = "2025-05-08T19:10:11.958Z" }, + { url = "https://files.pythonhosted.org/packages/c4/91/ba0ae1ff4b3f30972ad01cd4a8029e70a0ec3b8ea5be04764b128b66f763/matplotlib-3.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed70453fd99733293ace1aec568255bc51c6361cb0da94fa5ebf0649fdb2150a", size = 8601321, upload-time = "2025-05-08T19:10:14.47Z" }, + { url = "https://files.pythonhosted.org/packages/d2/88/d636041eb54a84b889e11872d91f7cbf036b3b0e194a70fa064eb8b04f7a/matplotlib-3.10.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dbed9917b44070e55640bd13419de83b4c918e52d97561544814ba463811cbc7", size = 9406972, upload-time = "2025-05-08T19:10:16.569Z" }, + { url = "https://files.pythonhosted.org/packages/b1/79/0d1c165eac44405a86478082e225fce87874f7198300bbebc55faaf6d28d/matplotlib-3.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:cf37d8c6ef1a48829443e8ba5227b44236d7fcaf7647caa3178a4ff9f7a5be05", size = 8067954, upload-time = "2025-05-08T19:10:18.663Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c1/23cfb566a74c696a3b338d8955c549900d18fe2b898b6e94d682ca21e7c2/matplotlib-3.10.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9f2efccc8dcf2b86fc4ee849eea5dcaecedd0773b30f47980dc0cbeabf26ec84", size = 8180318, upload-time = "2025-05-08T19:10:20.426Z" }, + { url = "https://files.pythonhosted.org/packages/6c/0c/02f1c3b66b30da9ee343c343acbb6251bef5b01d34fad732446eaadcd108/matplotlib-3.10.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3ddbba06a6c126e3301c3d272a99dcbe7f6c24c14024e80307ff03791a5f294e", size = 8051132, upload-time = "2025-05-08T19:10:22.569Z" }, + { url = "https://files.pythonhosted.org/packages/b4/ab/8db1a5ac9b3a7352fb914133001dae889f9fcecb3146541be46bed41339c/matplotlib-3.10.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748302b33ae9326995b238f606e9ed840bf5886ebafcb233775d946aa8107a15", size = 8457633, upload-time = "2025-05-08T19:10:24.749Z" }, + { url = "https://files.pythonhosted.org/packages/f5/64/41c4367bcaecbc03ef0d2a3ecee58a7065d0a36ae1aa817fe573a2da66d4/matplotlib-3.10.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a80fcccbef63302c0efd78042ea3c2436104c5b1a4d3ae20f864593696364ac7", size = 8601031, upload-time = "2025-05-08T19:10:27.03Z" }, + { url = "https://files.pythonhosted.org/packages/12/6f/6cc79e9e5ab89d13ed64da28898e40fe5b105a9ab9c98f83abd24e46d7d7/matplotlib-3.10.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:55e46cbfe1f8586adb34f7587c3e4f7dedc59d5226719faf6cb54fc24f2fd52d", size = 9406988, upload-time = "2025-05-08T19:10:29.056Z" }, + { url = "https://files.pythonhosted.org/packages/b1/0f/eed564407bd4d935ffabf561ed31099ed609e19287409a27b6d336848653/matplotlib-3.10.3-cp313-cp313-win_amd64.whl", hash = "sha256:151d89cb8d33cb23345cd12490c76fd5d18a56581a16d950b48c6ff19bb2ab93", size = 8068034, upload-time = "2025-05-08T19:10:31.221Z" }, + { url = "https://files.pythonhosted.org/packages/3e/e5/2f14791ff69b12b09e9975e1d116d9578ac684460860ce542c2588cb7a1c/matplotlib-3.10.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c26dd9834e74d164d06433dc7be5d75a1e9890b926b3e57e74fa446e1a62c3e2", size = 8218223, upload-time = "2025-05-08T19:10:33.114Z" }, + { url = "https://files.pythonhosted.org/packages/5c/08/30a94afd828b6e02d0a52cae4a29d6e9ccfcf4c8b56cc28b021d3588873e/matplotlib-3.10.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:24853dad5b8c84c8c2390fc31ce4858b6df504156893292ce8092d190ef8151d", size = 8094985, upload-time = "2025-05-08T19:10:35.337Z" }, + { url = "https://files.pythonhosted.org/packages/89/44/f3bc6b53066c889d7a1a3ea8094c13af6a667c5ca6220ec60ecceec2dabe/matplotlib-3.10.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68f7878214d369d7d4215e2a9075fef743be38fa401d32e6020bab2dfabaa566", size = 8483109, upload-time = "2025-05-08T19:10:37.611Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c7/473bc559beec08ebee9f86ca77a844b65747e1a6c2691e8c92e40b9f42a8/matplotlib-3.10.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6929fc618cb6db9cb75086f73b3219bbb25920cb24cee2ea7a12b04971a4158", size = 8618082, upload-time = "2025-05-08T19:10:39.892Z" }, + { url = "https://files.pythonhosted.org/packages/d8/e9/6ce8edd264c8819e37bbed8172e0ccdc7107fe86999b76ab5752276357a4/matplotlib-3.10.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6c7818292a5cc372a2dc4c795e5c356942eb8350b98ef913f7fda51fe175ac5d", size = 9413699, upload-time = "2025-05-08T19:10:42.376Z" }, + { url = "https://files.pythonhosted.org/packages/1b/92/9a45c91089c3cf690b5badd4be81e392ff086ccca8a1d4e3a08463d8a966/matplotlib-3.10.3-cp313-cp313t-win_amd64.whl", hash = "sha256:4f23ffe95c5667ef8a2b56eea9b53db7f43910fa4a2d5472ae0f72b64deab4d5", size = 8139044, upload-time = "2025-05-08T19:10:44.551Z" }, + { url = "https://files.pythonhosted.org/packages/3d/d1/f54d43e95384b312ffa4a74a4326c722f3b8187aaaa12e9a84cdf3037131/matplotlib-3.10.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:86ab63d66bbc83fdb6733471d3bff40897c1e9921cba112accd748eee4bce5e4", size = 8162896, upload-time = "2025-05-08T19:10:46.432Z" }, + { url = "https://files.pythonhosted.org/packages/24/a4/fbfc00c2346177c95b353dcf9b5a004106abe8730a62cb6f27e79df0a698/matplotlib-3.10.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:a48f9c08bf7444b5d2391a83e75edb464ccda3c380384b36532a0962593a1751", size = 8039702, upload-time = "2025-05-08T19:10:49.634Z" }, + { url = "https://files.pythonhosted.org/packages/6a/b9/59e120d24a2ec5fc2d30646adb2efb4621aab3c6d83d66fb2a7a182db032/matplotlib-3.10.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb73d8aa75a237457988f9765e4dfe1c0d2453c5ca4eabc897d4309672c8e014", size = 8594298, upload-time = "2025-05-08T19:10:51.738Z" }, +] + +[[package]] +name = "mccabe" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658, upload-time = "2022-01-24T01:14:51.113Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350, upload-time = "2022-01-24T01:14:49.62Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "networkx" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +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 = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +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 = "orcabridge" +source = { editable = "." } +dependencies = [ + { name = "matplotlib" }, + { name = "networkx" }, + { name = "numpy" }, + { name = "typing-extensions" }, + { name = "xxhash" }, +] + +[package.optional-dependencies] +dev = [ + { name = "black" }, + { name = "flake8" }, + { name = "isort" }, + { name = "pytest" }, + { name = "pytest-cov" }, +] +test = [ + { name = "pytest" }, + { name = "pytest-cov" }, +] + +[package.dev-dependencies] +dev = [ + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "black", marker = "extra == 'dev'", specifier = ">=23.0.0" }, + { name = "flake8", marker = "extra == 'dev'", specifier = ">=6.0.0" }, + { name = "isort", marker = "extra == 'dev'", specifier = ">=5.12.0" }, + { name = "matplotlib" }, + { name = "networkx" }, + { name = "numpy" }, + { name = "orcabridge", extras = ["test"], marker = "extra == 'dev'" }, + { name = "pytest", marker = "extra == 'test'", specifier = ">=7.4.0" }, + { name = "pytest-cov", marker = "extra == 'test'", specifier = ">=4.1.0" }, + { name = "typing-extensions" }, + { name = "xxhash" }, +] +provides-extras = ["test", "dev"] + +[package.metadata.requires-dev] +dev = [{ name = "ruff", specifier = ">=0.11.11" }] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, +] + +[[package]] +name = "pillow" +version = "11.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/cb/bb5c01fcd2a69335b86c22142b2bccfc3464087efb7fd382eee5ffc7fdf7/pillow-11.2.1.tar.gz", hash = "sha256:a64dd61998416367b7ef979b73d3a85853ba9bec4c2925f74e588879a58716b6", size = 47026707, upload-time = "2025-04-12T17:50:03.289Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/8b/b158ad57ed44d3cc54db8d68ad7c0a58b8fc0e4c7a3f995f9d62d5b464a1/pillow-11.2.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:d57a75d53922fc20c165016a20d9c44f73305e67c351bbc60d1adaf662e74047", size = 3198442, upload-time = "2025-04-12T17:47:10.666Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f8/bb5d956142f86c2d6cc36704943fa761f2d2e4c48b7436fd0a85c20f1713/pillow-11.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:127bf6ac4a5b58b3d32fc8289656f77f80567d65660bc46f72c0d77e6600cc95", size = 3030553, upload-time = "2025-04-12T17:47:13.153Z" }, + { url = "https://files.pythonhosted.org/packages/22/7f/0e413bb3e2aa797b9ca2c5c38cb2e2e45d88654e5b12da91ad446964cfae/pillow-11.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4ba4be812c7a40280629e55ae0b14a0aafa150dd6451297562e1764808bbe61", size = 4405503, upload-time = "2025-04-12T17:47:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/f3/b4/cc647f4d13f3eb837d3065824aa58b9bcf10821f029dc79955ee43f793bd/pillow-11.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8bd62331e5032bc396a93609982a9ab6b411c05078a52f5fe3cc59234a3abd1", size = 4490648, upload-time = "2025-04-12T17:47:17.37Z" }, + { url = "https://files.pythonhosted.org/packages/c2/6f/240b772a3b35cdd7384166461567aa6713799b4e78d180c555bd284844ea/pillow-11.2.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:562d11134c97a62fe3af29581f083033179f7ff435f78392565a1ad2d1c2c45c", size = 4508937, upload-time = "2025-04-12T17:47:19.066Z" }, + { url = "https://files.pythonhosted.org/packages/f3/5e/7ca9c815ade5fdca18853db86d812f2f188212792780208bdb37a0a6aef4/pillow-11.2.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:c97209e85b5be259994eb5b69ff50c5d20cca0f458ef9abd835e262d9d88b39d", size = 4599802, upload-time = "2025-04-12T17:47:21.404Z" }, + { url = "https://files.pythonhosted.org/packages/02/81/c3d9d38ce0c4878a77245d4cf2c46d45a4ad0f93000227910a46caff52f3/pillow-11.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0c3e6d0f59171dfa2e25d7116217543310908dfa2770aa64b8f87605f8cacc97", size = 4576717, upload-time = "2025-04-12T17:47:23.571Z" }, + { url = "https://files.pythonhosted.org/packages/42/49/52b719b89ac7da3185b8d29c94d0e6aec8140059e3d8adcaa46da3751180/pillow-11.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc1c3bc53befb6096b84165956e886b1729634a799e9d6329a0c512ab651e579", size = 4654874, upload-time = "2025-04-12T17:47:25.783Z" }, + { url = "https://files.pythonhosted.org/packages/5b/0b/ede75063ba6023798267023dc0d0401f13695d228194d2242d5a7ba2f964/pillow-11.2.1-cp310-cp310-win32.whl", hash = "sha256:312c77b7f07ab2139924d2639860e084ec2a13e72af54d4f08ac843a5fc9c79d", size = 2331717, upload-time = "2025-04-12T17:47:28.922Z" }, + { url = "https://files.pythonhosted.org/packages/ed/3c/9831da3edea527c2ed9a09f31a2c04e77cd705847f13b69ca60269eec370/pillow-11.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:9bc7ae48b8057a611e5fe9f853baa88093b9a76303937449397899385da06fad", size = 2676204, upload-time = "2025-04-12T17:47:31.283Z" }, + { url = "https://files.pythonhosted.org/packages/01/97/1f66ff8a1503d8cbfc5bae4dc99d54c6ec1e22ad2b946241365320caabc2/pillow-11.2.1-cp310-cp310-win_arm64.whl", hash = "sha256:2728567e249cdd939f6cc3d1f049595c66e4187f3c34078cbc0a7d21c47482d2", size = 2414767, upload-time = "2025-04-12T17:47:34.655Z" }, + { url = "https://files.pythonhosted.org/packages/68/08/3fbf4b98924c73037a8e8b4c2c774784805e0fb4ebca6c5bb60795c40125/pillow-11.2.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35ca289f712ccfc699508c4658a1d14652e8033e9b69839edf83cbdd0ba39e70", size = 3198450, upload-time = "2025-04-12T17:47:37.135Z" }, + { url = "https://files.pythonhosted.org/packages/84/92/6505b1af3d2849d5e714fc75ba9e69b7255c05ee42383a35a4d58f576b16/pillow-11.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0409af9f829f87a2dfb7e259f78f317a5351f2045158be321fd135973fff7bf", size = 3030550, upload-time = "2025-04-12T17:47:39.345Z" }, + { url = "https://files.pythonhosted.org/packages/3c/8c/ac2f99d2a70ff966bc7eb13dacacfaab57c0549b2ffb351b6537c7840b12/pillow-11.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4e5c5edee874dce4f653dbe59db7c73a600119fbea8d31f53423586ee2aafd7", size = 4415018, upload-time = "2025-04-12T17:47:41.128Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e3/0a58b5d838687f40891fff9cbaf8669f90c96b64dc8f91f87894413856c6/pillow-11.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b93a07e76d13bff9444f1a029e0af2964e654bfc2e2c2d46bfd080df5ad5f3d8", size = 4498006, upload-time = "2025-04-12T17:47:42.912Z" }, + { url = "https://files.pythonhosted.org/packages/21/f5/6ba14718135f08fbfa33308efe027dd02b781d3f1d5c471444a395933aac/pillow-11.2.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:e6def7eed9e7fa90fde255afaf08060dc4b343bbe524a8f69bdd2a2f0018f600", size = 4517773, upload-time = "2025-04-12T17:47:44.611Z" }, + { url = "https://files.pythonhosted.org/packages/20/f2/805ad600fc59ebe4f1ba6129cd3a75fb0da126975c8579b8f57abeb61e80/pillow-11.2.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8f4f3724c068be008c08257207210c138d5f3731af6c155a81c2b09a9eb3a788", size = 4607069, upload-time = "2025-04-12T17:47:46.46Z" }, + { url = "https://files.pythonhosted.org/packages/71/6b/4ef8a288b4bb2e0180cba13ca0a519fa27aa982875882392b65131401099/pillow-11.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a0a6709b47019dff32e678bc12c63008311b82b9327613f534e496dacaefb71e", size = 4583460, upload-time = "2025-04-12T17:47:49.255Z" }, + { url = "https://files.pythonhosted.org/packages/62/ae/f29c705a09cbc9e2a456590816e5c234382ae5d32584f451c3eb41a62062/pillow-11.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f6b0c664ccb879109ee3ca702a9272d877f4fcd21e5eb63c26422fd6e415365e", size = 4661304, upload-time = "2025-04-12T17:47:51.067Z" }, + { url = "https://files.pythonhosted.org/packages/6e/1a/c8217b6f2f73794a5e219fbad087701f412337ae6dbb956db37d69a9bc43/pillow-11.2.1-cp311-cp311-win32.whl", hash = "sha256:cc5d875d56e49f112b6def6813c4e3d3036d269c008bf8aef72cd08d20ca6df6", size = 2331809, upload-time = "2025-04-12T17:47:54.425Z" }, + { url = "https://files.pythonhosted.org/packages/e2/72/25a8f40170dc262e86e90f37cb72cb3de5e307f75bf4b02535a61afcd519/pillow-11.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:0f5c7eda47bf8e3c8a283762cab94e496ba977a420868cb819159980b6709193", size = 2676338, upload-time = "2025-04-12T17:47:56.535Z" }, + { url = "https://files.pythonhosted.org/packages/06/9e/76825e39efee61efea258b479391ca77d64dbd9e5804e4ad0fa453b4ba55/pillow-11.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:4d375eb838755f2528ac8cbc926c3e31cc49ca4ad0cf79cff48b20e30634a4a7", size = 2414918, upload-time = "2025-04-12T17:47:58.217Z" }, + { url = "https://files.pythonhosted.org/packages/c7/40/052610b15a1b8961f52537cc8326ca6a881408bc2bdad0d852edeb6ed33b/pillow-11.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:78afba22027b4accef10dbd5eed84425930ba41b3ea0a86fa8d20baaf19d807f", size = 3190185, upload-time = "2025-04-12T17:48:00.417Z" }, + { url = "https://files.pythonhosted.org/packages/e5/7e/b86dbd35a5f938632093dc40d1682874c33dcfe832558fc80ca56bfcb774/pillow-11.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78092232a4ab376a35d68c4e6d5e00dfd73454bd12b230420025fbe178ee3b0b", size = 3030306, upload-time = "2025-04-12T17:48:02.391Z" }, + { url = "https://files.pythonhosted.org/packages/a4/5c/467a161f9ed53e5eab51a42923c33051bf8d1a2af4626ac04f5166e58e0c/pillow-11.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25a5f306095c6780c52e6bbb6109624b95c5b18e40aab1c3041da3e9e0cd3e2d", size = 4416121, upload-time = "2025-04-12T17:48:04.554Z" }, + { url = "https://files.pythonhosted.org/packages/62/73/972b7742e38ae0e2ac76ab137ca6005dcf877480da0d9d61d93b613065b4/pillow-11.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c7b29dbd4281923a2bfe562acb734cee96bbb129e96e6972d315ed9f232bef4", size = 4501707, upload-time = "2025-04-12T17:48:06.831Z" }, + { url = "https://files.pythonhosted.org/packages/e4/3a/427e4cb0b9e177efbc1a84798ed20498c4f233abde003c06d2650a6d60cb/pillow-11.2.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:3e645b020f3209a0181a418bffe7b4a93171eef6c4ef6cc20980b30bebf17b7d", size = 4522921, upload-time = "2025-04-12T17:48:09.229Z" }, + { url = "https://files.pythonhosted.org/packages/fe/7c/d8b1330458e4d2f3f45d9508796d7caf0c0d3764c00c823d10f6f1a3b76d/pillow-11.2.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b2dbea1012ccb784a65349f57bbc93730b96e85b42e9bf7b01ef40443db720b4", size = 4612523, upload-time = "2025-04-12T17:48:11.631Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2f/65738384e0b1acf451de5a573d8153fe84103772d139e1e0bdf1596be2ea/pillow-11.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:da3104c57bbd72948d75f6a9389e6727d2ab6333c3617f0a89d72d4940aa0443", size = 4587836, upload-time = "2025-04-12T17:48:13.592Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c5/e795c9f2ddf3debb2dedd0df889f2fe4b053308bb59a3cc02a0cd144d641/pillow-11.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:598174aef4589af795f66f9caab87ba4ff860ce08cd5bb447c6fc553ffee603c", size = 4669390, upload-time = "2025-04-12T17:48:15.938Z" }, + { url = "https://files.pythonhosted.org/packages/96/ae/ca0099a3995976a9fce2f423166f7bff9b12244afdc7520f6ed38911539a/pillow-11.2.1-cp312-cp312-win32.whl", hash = "sha256:1d535df14716e7f8776b9e7fee118576d65572b4aad3ed639be9e4fa88a1cad3", size = 2332309, upload-time = "2025-04-12T17:48:17.885Z" }, + { url = "https://files.pythonhosted.org/packages/7c/18/24bff2ad716257fc03da964c5e8f05d9790a779a8895d6566e493ccf0189/pillow-11.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:14e33b28bf17c7a38eede290f77db7c664e4eb01f7869e37fa98a5aa95978941", size = 2676768, upload-time = "2025-04-12T17:48:19.655Z" }, + { url = "https://files.pythonhosted.org/packages/da/bb/e8d656c9543276517ee40184aaa39dcb41e683bca121022f9323ae11b39d/pillow-11.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:21e1470ac9e5739ff880c211fc3af01e3ae505859392bf65458c224d0bf283eb", size = 2415087, upload-time = "2025-04-12T17:48:21.991Z" }, + { url = "https://files.pythonhosted.org/packages/36/9c/447528ee3776e7ab8897fe33697a7ff3f0475bb490c5ac1456a03dc57956/pillow-11.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fdec757fea0b793056419bca3e9932eb2b0ceec90ef4813ea4c1e072c389eb28", size = 3190098, upload-time = "2025-04-12T17:48:23.915Z" }, + { url = "https://files.pythonhosted.org/packages/b5/09/29d5cd052f7566a63e5b506fac9c60526e9ecc553825551333e1e18a4858/pillow-11.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0e130705d568e2f43a17bcbe74d90958e8a16263868a12c3e0d9c8162690830", size = 3030166, upload-time = "2025-04-12T17:48:25.738Z" }, + { url = "https://files.pythonhosted.org/packages/71/5d/446ee132ad35e7600652133f9c2840b4799bbd8e4adba881284860da0a36/pillow-11.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bdb5e09068332578214cadd9c05e3d64d99e0e87591be22a324bdbc18925be0", size = 4408674, upload-time = "2025-04-12T17:48:27.908Z" }, + { url = "https://files.pythonhosted.org/packages/69/5f/cbe509c0ddf91cc3a03bbacf40e5c2339c4912d16458fcb797bb47bcb269/pillow-11.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d189ba1bebfbc0c0e529159631ec72bb9e9bc041f01ec6d3233d6d82eb823bc1", size = 4496005, upload-time = "2025-04-12T17:48:29.888Z" }, + { url = "https://files.pythonhosted.org/packages/f9/b3/dd4338d8fb8a5f312021f2977fb8198a1184893f9b00b02b75d565c33b51/pillow-11.2.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:191955c55d8a712fab8934a42bfefbf99dd0b5875078240943f913bb66d46d9f", size = 4518707, upload-time = "2025-04-12T17:48:31.874Z" }, + { url = "https://files.pythonhosted.org/packages/13/eb/2552ecebc0b887f539111c2cd241f538b8ff5891b8903dfe672e997529be/pillow-11.2.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:ad275964d52e2243430472fc5d2c2334b4fc3ff9c16cb0a19254e25efa03a155", size = 4610008, upload-time = "2025-04-12T17:48:34.422Z" }, + { url = "https://files.pythonhosted.org/packages/72/d1/924ce51bea494cb6e7959522d69d7b1c7e74f6821d84c63c3dc430cbbf3b/pillow-11.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:750f96efe0597382660d8b53e90dd1dd44568a8edb51cb7f9d5d918b80d4de14", size = 4585420, upload-time = "2025-04-12T17:48:37.641Z" }, + { url = "https://files.pythonhosted.org/packages/43/ab/8f81312d255d713b99ca37479a4cb4b0f48195e530cdc1611990eb8fd04b/pillow-11.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fe15238d3798788d00716637b3d4e7bb6bde18b26e5d08335a96e88564a36b6b", size = 4667655, upload-time = "2025-04-12T17:48:39.652Z" }, + { url = "https://files.pythonhosted.org/packages/94/86/8f2e9d2dc3d308dfd137a07fe1cc478df0a23d42a6c4093b087e738e4827/pillow-11.2.1-cp313-cp313-win32.whl", hash = "sha256:3fe735ced9a607fee4f481423a9c36701a39719252a9bb251679635f99d0f7d2", size = 2332329, upload-time = "2025-04-12T17:48:41.765Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ec/1179083b8d6067a613e4d595359b5fdea65d0a3b7ad623fee906e1b3c4d2/pillow-11.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:74ee3d7ecb3f3c05459ba95eed5efa28d6092d751ce9bf20e3e253a4e497e691", size = 2676388, upload-time = "2025-04-12T17:48:43.625Z" }, + { url = "https://files.pythonhosted.org/packages/23/f1/2fc1e1e294de897df39fa8622d829b8828ddad938b0eaea256d65b84dd72/pillow-11.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:5119225c622403afb4b44bad4c1ca6c1f98eed79db8d3bc6e4e160fc6339d66c", size = 2414950, upload-time = "2025-04-12T17:48:45.475Z" }, + { url = "https://files.pythonhosted.org/packages/c4/3e/c328c48b3f0ead7bab765a84b4977acb29f101d10e4ef57a5e3400447c03/pillow-11.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8ce2e8411c7aaef53e6bb29fe98f28cd4fbd9a1d9be2eeea434331aac0536b22", size = 3192759, upload-time = "2025-04-12T17:48:47.866Z" }, + { url = "https://files.pythonhosted.org/packages/18/0e/1c68532d833fc8b9f404d3a642991441d9058eccd5606eab31617f29b6d4/pillow-11.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9ee66787e095127116d91dea2143db65c7bb1e232f617aa5957c0d9d2a3f23a7", size = 3033284, upload-time = "2025-04-12T17:48:50.189Z" }, + { url = "https://files.pythonhosted.org/packages/b7/cb/6faf3fb1e7705fd2db74e070f3bf6f88693601b0ed8e81049a8266de4754/pillow-11.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9622e3b6c1d8b551b6e6f21873bdcc55762b4b2126633014cea1803368a9aa16", size = 4445826, upload-time = "2025-04-12T17:48:52.346Z" }, + { url = "https://files.pythonhosted.org/packages/07/94/8be03d50b70ca47fb434a358919d6a8d6580f282bbb7af7e4aa40103461d/pillow-11.2.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63b5dff3a68f371ea06025a1a6966c9a1e1ee452fc8020c2cd0ea41b83e9037b", size = 4527329, upload-time = "2025-04-12T17:48:54.403Z" }, + { url = "https://files.pythonhosted.org/packages/fd/a4/bfe78777076dc405e3bd2080bc32da5ab3945b5a25dc5d8acaa9de64a162/pillow-11.2.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:31df6e2d3d8fc99f993fd253e97fae451a8db2e7207acf97859732273e108406", size = 4549049, upload-time = "2025-04-12T17:48:56.383Z" }, + { url = "https://files.pythonhosted.org/packages/65/4d/eaf9068dc687c24979e977ce5677e253624bd8b616b286f543f0c1b91662/pillow-11.2.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:062b7a42d672c45a70fa1f8b43d1d38ff76b63421cbbe7f88146b39e8a558d91", size = 4635408, upload-time = "2025-04-12T17:48:58.782Z" }, + { url = "https://files.pythonhosted.org/packages/1d/26/0fd443365d9c63bc79feb219f97d935cd4b93af28353cba78d8e77b61719/pillow-11.2.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4eb92eca2711ef8be42fd3f67533765d9fd043b8c80db204f16c8ea62ee1a751", size = 4614863, upload-time = "2025-04-12T17:49:00.709Z" }, + { url = "https://files.pythonhosted.org/packages/49/65/dca4d2506be482c2c6641cacdba5c602bc76d8ceb618fd37de855653a419/pillow-11.2.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f91ebf30830a48c825590aede79376cb40f110b387c17ee9bd59932c961044f9", size = 4692938, upload-time = "2025-04-12T17:49:02.946Z" }, + { url = "https://files.pythonhosted.org/packages/b3/92/1ca0c3f09233bd7decf8f7105a1c4e3162fb9142128c74adad0fb361b7eb/pillow-11.2.1-cp313-cp313t-win32.whl", hash = "sha256:e0b55f27f584ed623221cfe995c912c61606be8513bfa0e07d2c674b4516d9dd", size = 2335774, upload-time = "2025-04-12T17:49:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ac/77525347cb43b83ae905ffe257bbe2cc6fd23acb9796639a1f56aa59d191/pillow-11.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:36d6b82164c39ce5482f649b437382c0fb2395eabc1e2b1702a6deb8ad647d6e", size = 2681895, upload-time = "2025-04-12T17:49:06.635Z" }, + { url = "https://files.pythonhosted.org/packages/67/32/32dc030cfa91ca0fc52baebbba2e009bb001122a1daa8b6a79ad830b38d3/pillow-11.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:225c832a13326e34f212d2072982bb1adb210e0cc0b153e688743018c94a2681", size = 2417234, upload-time = "2025-04-12T17:49:08.399Z" }, + { url = "https://files.pythonhosted.org/packages/33/49/c8c21e4255b4f4a2c0c68ac18125d7f5460b109acc6dfdef1a24f9b960ef/pillow-11.2.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:9b7b0d4fd2635f54ad82785d56bc0d94f147096493a79985d0ab57aedd563156", size = 3181727, upload-time = "2025-04-12T17:49:31.898Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f1/f7255c0838f8c1ef6d55b625cfb286835c17e8136ce4351c5577d02c443b/pillow-11.2.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:aa442755e31c64037aa7c1cb186e0b369f8416c567381852c63444dd666fb772", size = 2999833, upload-time = "2025-04-12T17:49:34.2Z" }, + { url = "https://files.pythonhosted.org/packages/e2/57/9968114457bd131063da98d87790d080366218f64fa2943b65ac6739abb3/pillow-11.2.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0d3348c95b766f54b76116d53d4cb171b52992a1027e7ca50c81b43b9d9e363", size = 3437472, upload-time = "2025-04-12T17:49:36.294Z" }, + { url = "https://files.pythonhosted.org/packages/b2/1b/e35d8a158e21372ecc48aac9c453518cfe23907bb82f950d6e1c72811eb0/pillow-11.2.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85d27ea4c889342f7e35f6d56e7e1cb345632ad592e8c51b693d7b7556043ce0", size = 3459976, upload-time = "2025-04-12T17:49:38.988Z" }, + { url = "https://files.pythonhosted.org/packages/26/da/2c11d03b765efff0ccc473f1c4186dc2770110464f2177efaed9cf6fae01/pillow-11.2.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:bf2c33d6791c598142f00c9c4c7d47f6476731c31081331664eb26d6ab583e01", size = 3527133, upload-time = "2025-04-12T17:49:40.985Z" }, + { url = "https://files.pythonhosted.org/packages/79/1a/4e85bd7cadf78412c2a3069249a09c32ef3323650fd3005c97cca7aa21df/pillow-11.2.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e616e7154c37669fc1dfc14584f11e284e05d1c650e1c0f972f281c4ccc53193", size = 3571555, upload-time = "2025-04-12T17:49:42.964Z" }, + { url = "https://files.pythonhosted.org/packages/69/03/239939915216de1e95e0ce2334bf17a7870ae185eb390fab6d706aadbfc0/pillow-11.2.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:39ad2e0f424394e3aebc40168845fee52df1394a4673a6ee512d840d14ab3013", size = 2674713, upload-time = "2025-04-12T17:49:44.944Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ad/2613c04633c7257d9481ab21d6b5364b59fc5d75faafd7cb8693523945a3/pillow-11.2.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:80f1df8dbe9572b4b7abdfa17eb5d78dd620b1d55d9e25f834efdbee872d3aed", size = 3181734, upload-time = "2025-04-12T17:49:46.789Z" }, + { url = "https://files.pythonhosted.org/packages/a4/fd/dcdda4471ed667de57bb5405bb42d751e6cfdd4011a12c248b455c778e03/pillow-11.2.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ea926cfbc3957090becbcbbb65ad177161a2ff2ad578b5a6ec9bb1e1cd78753c", size = 2999841, upload-time = "2025-04-12T17:49:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/ac/89/8a2536e95e77432833f0db6fd72a8d310c8e4272a04461fb833eb021bf94/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:738db0e0941ca0376804d4de6a782c005245264edaa253ffce24e5a15cbdc7bd", size = 3437470, upload-time = "2025-04-12T17:49:50.831Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8f/abd47b73c60712f88e9eda32baced7bfc3e9bd6a7619bb64b93acff28c3e/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db98ab6565c69082ec9b0d4e40dd9f6181dab0dd236d26f7a50b8b9bfbd5076", size = 3460013, upload-time = "2025-04-12T17:49:53.278Z" }, + { url = "https://files.pythonhosted.org/packages/f6/20/5c0a0aa83b213b7a07ec01e71a3d6ea2cf4ad1d2c686cc0168173b6089e7/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:036e53f4170e270ddb8797d4c590e6dd14d28e15c7da375c18978045f7e6c37b", size = 3527165, upload-time = "2025-04-12T17:49:55.164Z" }, + { url = "https://files.pythonhosted.org/packages/58/0e/2abab98a72202d91146abc839e10c14f7cf36166f12838ea0c4db3ca6ecb/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:14f73f7c291279bd65fda51ee87affd7c1e097709f7fdd0188957a16c264601f", size = 3571586, upload-time = "2025-04-12T17:49:57.171Z" }, + { url = "https://files.pythonhosted.org/packages/21/2c/5e05f58658cf49b6667762cca03d6e7d85cededde2caf2ab37b81f80e574/pillow-11.2.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:208653868d5c9ecc2b327f9b9ef34e0e42a4cdd172c2988fd81d62d2bc9bc044", size = 2674751, upload-time = "2025-04-12T17:49:59.628Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.3.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pycodestyle" +version = "2.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/04/6e/1f4a62078e4d95d82367f24e685aef3a672abfd27d1a868068fed4ed2254/pycodestyle-2.13.0.tar.gz", hash = "sha256:c8415bf09abe81d9c7f872502a6eee881fbe85d8763dd5b9924bb0a01d67efae", size = 39312, upload-time = "2025-03-29T17:33:30.669Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/be/b00116df1bfb3e0bb5b45e29d604799f7b91dd861637e4d448b4e09e6a3e/pycodestyle-2.13.0-py2.py3-none-any.whl", hash = "sha256:35863c5974a271c7a726ed228a14a4f6daf49df369d8c50cd9a6f58a5e143ba9", size = 31424, upload-time = "2025-03-29T17:33:29.405Z" }, +] + +[[package]] +name = "pyflakes" +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/cc/1df338bd7ed1fa7c317081dcf29bf2f01266603b301e6858856d346a12b3/pyflakes-3.3.2.tar.gz", hash = "sha256:6dfd61d87b97fba5dcfaaf781171ac16be16453be6d816147989e7f6e6a9576b", size = 64175, upload-time = "2025-03-31T13:21:20.34Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/40/b293a4fa769f3b02ab9e387c707c4cbdc34f073f945de0386107d4e669e6/pyflakes-3.3.2-py2.py3-none-any.whl", hash = "sha256:5039c8339cbb1944045f4ee5466908906180f13cc99cc9949348d10f82a5c32a", size = 63164, upload-time = "2025-03-31T13:21:18.503Z" }, +] + +[[package]] +name = "pyparsing" +version = "3.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/22/f1129e69d94ffff626bdb5c835506b3a5b4f3d070f17ea295e12c2c6f60f/pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be", size = 1088608, upload-time = "2025-03-25T05:01:28.114Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf", size = 111120, upload-time = "2025-03-25T05:01:24.908Z" }, +] + +[[package]] +name = "pytest" +version = "8.3.5" +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 = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, +] + +[[package]] +name = "pytest-cov" +version = "6.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", extra = ["toml"] }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/25/69/5f1e57f6c5a39f81411b550027bf72842c4567ff5fd572bed1edc9e4b5d9/pytest_cov-6.1.1.tar.gz", hash = "sha256:46935f7aaefba760e716c2ebfbe1c216240b9592966e7da99ea8292d4d3e2a0a", size = 66857, upload-time = "2025-04-05T14:07:51.592Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/d0/def53b4a790cfb21483016430ed828f64830dd981ebe1089971cd10cab25/pytest_cov-6.1.1-py3-none-any.whl", hash = "sha256:bddf29ed2d0ab6f4df17b4c55b0a657287db8684af9c42ea546b21b1041b3dde", size = 23841, upload-time = "2025-04-05T14:07:49.641Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "ruff" +version = "0.11.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/53/ae4857030d59286924a8bdb30d213d6ff22d8f0957e738d0289990091dd8/ruff-0.11.11.tar.gz", hash = "sha256:7774173cc7c1980e6bf67569ebb7085989a78a103922fb83ef3dfe230cd0687d", size = 4186707, upload-time = "2025-05-22T19:19:34.363Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/14/f2326676197bab099e2a24473158c21656fbf6a207c65f596ae15acb32b9/ruff-0.11.11-py3-none-linux_armv6l.whl", hash = "sha256:9924e5ae54125ed8958a4f7de320dab7380f6e9fa3195e3dc3b137c6842a0092", size = 10229049, upload-time = "2025-05-22T19:18:45.516Z" }, + { url = "https://files.pythonhosted.org/packages/9a/f3/bff7c92dd66c959e711688b2e0768e486bbca46b2f35ac319bb6cce04447/ruff-0.11.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:c8a93276393d91e952f790148eb226658dd275cddfde96c6ca304873f11d2ae4", size = 11053601, upload-time = "2025-05-22T19:18:49.269Z" }, + { url = "https://files.pythonhosted.org/packages/e2/38/8e1a3efd0ef9d8259346f986b77de0f62c7a5ff4a76563b6b39b68f793b9/ruff-0.11.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d6e333dbe2e6ae84cdedefa943dfd6434753ad321764fd937eef9d6b62022bcd", size = 10367421, upload-time = "2025-05-22T19:18:51.754Z" }, + { url = "https://files.pythonhosted.org/packages/b4/50/557ad9dd4fb9d0bf524ec83a090a3932d284d1a8b48b5906b13b72800e5f/ruff-0.11.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7885d9a5e4c77b24e8c88aba8c80be9255fa22ab326019dac2356cff42089fc6", size = 10581980, upload-time = "2025-05-22T19:18:54.011Z" }, + { url = "https://files.pythonhosted.org/packages/c4/b2/e2ed82d6e2739ece94f1bdbbd1d81b712d3cdaf69f0a1d1f1a116b33f9ad/ruff-0.11.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1b5ab797fcc09121ed82e9b12b6f27e34859e4227080a42d090881be888755d4", size = 10089241, upload-time = "2025-05-22T19:18:56.041Z" }, + { url = "https://files.pythonhosted.org/packages/3d/9f/b4539f037a5302c450d7c695c82f80e98e48d0d667ecc250e6bdeb49b5c3/ruff-0.11.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e231ff3132c1119ece836487a02785f099a43992b95c2f62847d29bace3c75ac", size = 11699398, upload-time = "2025-05-22T19:18:58.248Z" }, + { url = "https://files.pythonhosted.org/packages/61/fb/32e029d2c0b17df65e6eaa5ce7aea5fbeaed22dddd9fcfbbf5fe37c6e44e/ruff-0.11.11-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:a97c9babe1d4081037a90289986925726b802d180cca784ac8da2bbbc335f709", size = 12427955, upload-time = "2025-05-22T19:19:00.981Z" }, + { url = "https://files.pythonhosted.org/packages/6e/e3/160488dbb11f18c8121cfd588e38095ba779ae208292765972f7732bfd95/ruff-0.11.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8c4ddcbe8a19f59f57fd814b8b117d4fcea9bee7c0492e6cf5fdc22cfa563c8", size = 12069803, upload-time = "2025-05-22T19:19:03.258Z" }, + { url = "https://files.pythonhosted.org/packages/ff/16/3b006a875f84b3d0bff24bef26b8b3591454903f6f754b3f0a318589dcc3/ruff-0.11.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6224076c344a7694c6fbbb70d4f2a7b730f6d47d2a9dc1e7f9d9bb583faf390b", size = 11242630, upload-time = "2025-05-22T19:19:05.871Z" }, + { url = "https://files.pythonhosted.org/packages/65/0d/0338bb8ac0b97175c2d533e9c8cdc127166de7eb16d028a43c5ab9e75abd/ruff-0.11.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:882821fcdf7ae8db7a951df1903d9cb032bbe838852e5fc3c2b6c3ab54e39875", size = 11507310, upload-time = "2025-05-22T19:19:08.584Z" }, + { url = "https://files.pythonhosted.org/packages/6f/bf/d7130eb26174ce9b02348b9f86d5874eafbf9f68e5152e15e8e0a392e4a3/ruff-0.11.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:dcec2d50756463d9df075a26a85a6affbc1b0148873da3997286caf1ce03cae1", size = 10441144, upload-time = "2025-05-22T19:19:13.621Z" }, + { url = "https://files.pythonhosted.org/packages/b3/f3/4be2453b258c092ff7b1761987cf0749e70ca1340cd1bfb4def08a70e8d8/ruff-0.11.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:99c28505ecbaeb6594701a74e395b187ee083ee26478c1a795d35084d53ebd81", size = 10081987, upload-time = "2025-05-22T19:19:15.821Z" }, + { url = "https://files.pythonhosted.org/packages/6c/6e/dfa4d2030c5b5c13db158219f2ec67bf333e8a7748dccf34cfa2a6ab9ebc/ruff-0.11.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9263f9e5aa4ff1dec765e99810f1cc53f0c868c5329b69f13845f699fe74f639", size = 11073922, upload-time = "2025-05-22T19:19:18.104Z" }, + { url = "https://files.pythonhosted.org/packages/ff/f4/f7b0b0c3d32b593a20ed8010fa2c1a01f2ce91e79dda6119fcc51d26c67b/ruff-0.11.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:64ac6f885e3ecb2fdbb71de2701d4e34526651f1e8503af8fb30d4915a3fe345", size = 11568537, upload-time = "2025-05-22T19:19:20.889Z" }, + { url = "https://files.pythonhosted.org/packages/d2/46/0e892064d0adc18bcc81deed9aaa9942a27fd2cd9b1b7791111ce468c25f/ruff-0.11.11-py3-none-win32.whl", hash = "sha256:1adcb9a18802268aaa891ffb67b1c94cd70578f126637118e8099b8e4adcf112", size = 10536492, upload-time = "2025-05-22T19:19:23.642Z" }, + { url = "https://files.pythonhosted.org/packages/1b/d9/232e79459850b9f327e9f1dc9c047a2a38a6f9689e1ec30024841fc4416c/ruff-0.11.11-py3-none-win_amd64.whl", hash = "sha256:748b4bb245f11e91a04a4ff0f96e386711df0a30412b9fe0c74d5bdc0e4a531f", size = 11612562, upload-time = "2025-05-22T19:19:27.013Z" }, + { url = "https://files.pythonhosted.org/packages/ce/eb/09c132cff3cc30b2e7244191dcce69437352d6d6709c0adf374f3e6f476e/ruff-0.11.11-py3-none-win_arm64.whl", hash = "sha256:6c51f136c0364ab1b774767aa8b86331bd8e9d414e2d107db7a2189f35ea1f7b", size = 10735951, upload-time = "2025-05-22T19:19:30.043Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +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 = "tomli" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, + { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, + { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, + { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, + { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, + { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, + { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, + { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, + { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, + { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" }, + { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" }, + { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" }, + { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" }, + { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" }, + { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" }, + { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, + { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" }, +] + +[[package]] +name = "xxhash" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/5e/d6e5258d69df8b4ed8c83b6664f2b47d30d2dec551a29ad72a6c69eafd31/xxhash-3.5.0.tar.gz", hash = "sha256:84f2caddf951c9cbf8dc2e22a89d4ccf5d86391ac6418fe81e3c67d0cf60b45f", size = 84241, upload-time = "2024-08-17T09:20:38.972Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/8a/0e9feca390d512d293afd844d31670e25608c4a901e10202aa98785eab09/xxhash-3.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ece616532c499ee9afbb83078b1b952beffef121d989841f7f4b3dc5ac0fd212", size = 31970, upload-time = "2024-08-17T09:17:35.675Z" }, + { url = "https://files.pythonhosted.org/packages/16/e6/be5aa49580cd064a18200ab78e29b88b1127e1a8c7955eb8ecf81f2626eb/xxhash-3.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3171f693dbc2cef6477054a665dc255d996646b4023fe56cb4db80e26f4cc520", size = 30801, upload-time = "2024-08-17T09:17:37.353Z" }, + { url = "https://files.pythonhosted.org/packages/20/ee/b8a99ebbc6d1113b3a3f09e747fa318c3cde5b04bd9c197688fadf0eeae8/xxhash-3.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c5d3e570ef46adaf93fc81b44aca6002b5a4d8ca11bd0580c07eac537f36680", size = 220927, upload-time = "2024-08-17T09:17:38.835Z" }, + { url = "https://files.pythonhosted.org/packages/58/62/15d10582ef159283a5c2b47f6d799fc3303fe3911d5bb0bcc820e1ef7ff4/xxhash-3.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7cb29a034301e2982df8b1fe6328a84f4b676106a13e9135a0d7e0c3e9f806da", size = 200360, upload-time = "2024-08-17T09:17:40.851Z" }, + { url = "https://files.pythonhosted.org/packages/23/41/61202663ea9b1bd8e53673b8ec9e2619989353dba8cfb68e59a9cbd9ffe3/xxhash-3.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d0d307d27099bb0cbeea7260eb39ed4fdb99c5542e21e94bb6fd29e49c57a23", size = 428528, upload-time = "2024-08-17T09:17:42.545Z" }, + { url = "https://files.pythonhosted.org/packages/f2/07/d9a3059f702dec5b3b703737afb6dda32f304f6e9da181a229dafd052c29/xxhash-3.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0342aafd421795d740e514bc9858ebddfc705a75a8c5046ac56d85fe97bf196", size = 194149, upload-time = "2024-08-17T09:17:44.361Z" }, + { url = "https://files.pythonhosted.org/packages/eb/58/27caadf78226ecf1d62dbd0c01d152ed381c14c1ee4ad01f0d460fc40eac/xxhash-3.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3dbbd9892c5ebffeca1ed620cf0ade13eb55a0d8c84e0751a6653adc6ac40d0c", size = 207703, upload-time = "2024-08-17T09:17:46.656Z" }, + { url = "https://files.pythonhosted.org/packages/b1/08/32d558ce23e1e068453c39aed7b3c1cdc690c177873ec0ca3a90d5808765/xxhash-3.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4cc2d67fdb4d057730c75a64c5923abfa17775ae234a71b0200346bfb0a7f482", size = 216255, upload-time = "2024-08-17T09:17:48.031Z" }, + { url = "https://files.pythonhosted.org/packages/3f/d4/2b971e2d2b0a61045f842b622ef11e94096cf1f12cd448b6fd426e80e0e2/xxhash-3.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ec28adb204b759306a3d64358a5e5c07d7b1dd0ccbce04aa76cb9377b7b70296", size = 202744, upload-time = "2024-08-17T09:17:50.045Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/6a6438864a8c4c39915d7b65effd85392ebe22710412902487e51769146d/xxhash-3.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1328f6d8cca2b86acb14104e381225a3d7b42c92c4b86ceae814e5c400dbb415", size = 210115, upload-time = "2024-08-17T09:17:51.834Z" }, + { url = "https://files.pythonhosted.org/packages/48/7d/b3c27c27d1fc868094d02fe4498ccce8cec9fcc591825c01d6bcb0b4fc49/xxhash-3.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8d47ebd9f5d9607fd039c1fbf4994e3b071ea23eff42f4ecef246ab2b7334198", size = 414247, upload-time = "2024-08-17T09:17:53.094Z" }, + { url = "https://files.pythonhosted.org/packages/a1/05/918f9e7d2fbbd334b829997045d341d6239b563c44e683b9a7ef8fe50f5d/xxhash-3.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b96d559e0fcddd3343c510a0fe2b127fbff16bf346dd76280b82292567523442", size = 191419, upload-time = "2024-08-17T09:17:54.906Z" }, + { url = "https://files.pythonhosted.org/packages/08/29/dfe393805b2f86bfc47c290b275f0b7c189dc2f4e136fd4754f32eb18a8d/xxhash-3.5.0-cp310-cp310-win32.whl", hash = "sha256:61c722ed8d49ac9bc26c7071eeaa1f6ff24053d553146d5df031802deffd03da", size = 30114, upload-time = "2024-08-17T09:17:56.566Z" }, + { url = "https://files.pythonhosted.org/packages/7b/d7/aa0b22c4ebb7c3ccb993d4c565132abc641cd11164f8952d89eb6a501909/xxhash-3.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:9bed5144c6923cc902cd14bb8963f2d5e034def4486ab0bbe1f58f03f042f9a9", size = 30003, upload-time = "2024-08-17T09:17:57.596Z" }, + { url = "https://files.pythonhosted.org/packages/69/12/f969b81541ee91b55f1ce469d7ab55079593c80d04fd01691b550e535000/xxhash-3.5.0-cp310-cp310-win_arm64.whl", hash = "sha256:893074d651cf25c1cc14e3bea4fceefd67f2921b1bb8e40fcfeba56820de80c6", size = 26773, upload-time = "2024-08-17T09:17:59.169Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c7/afed0f131fbda960ff15eee7f304fa0eeb2d58770fade99897984852ef23/xxhash-3.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02c2e816896dc6f85922ced60097bcf6f008dedfc5073dcba32f9c8dd786f3c1", size = 31969, upload-time = "2024-08-17T09:18:00.852Z" }, + { url = "https://files.pythonhosted.org/packages/8c/0c/7c3bc6d87e5235672fcc2fb42fd5ad79fe1033925f71bf549ee068c7d1ca/xxhash-3.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6027dcd885e21581e46d3c7f682cfb2b870942feeed58a21c29583512c3f09f8", size = 30800, upload-time = "2024-08-17T09:18:01.863Z" }, + { url = "https://files.pythonhosted.org/packages/04/9e/01067981d98069eec1c20201f8c145367698e9056f8bc295346e4ea32dd1/xxhash-3.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1308fa542bbdbf2fa85e9e66b1077eea3a88bef38ee8a06270b4298a7a62a166", size = 221566, upload-time = "2024-08-17T09:18:03.461Z" }, + { url = "https://files.pythonhosted.org/packages/d4/09/d4996de4059c3ce5342b6e1e6a77c9d6c91acce31f6ed979891872dd162b/xxhash-3.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c28b2fdcee797e1c1961cd3bcd3d545cab22ad202c846235197935e1df2f8ef7", size = 201214, upload-time = "2024-08-17T09:18:05.616Z" }, + { url = "https://files.pythonhosted.org/packages/62/f5/6d2dc9f8d55a7ce0f5e7bfef916e67536f01b85d32a9fbf137d4cadbee38/xxhash-3.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:924361811732ddad75ff23e90efd9ccfda4f664132feecb90895bade6a1b4623", size = 429433, upload-time = "2024-08-17T09:18:06.957Z" }, + { url = "https://files.pythonhosted.org/packages/d9/72/9256303f10e41ab004799a4aa74b80b3c5977d6383ae4550548b24bd1971/xxhash-3.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89997aa1c4b6a5b1e5b588979d1da048a3c6f15e55c11d117a56b75c84531f5a", size = 194822, upload-time = "2024-08-17T09:18:08.331Z" }, + { url = "https://files.pythonhosted.org/packages/34/92/1a3a29acd08248a34b0e6a94f4e0ed9b8379a4ff471f1668e4dce7bdbaa8/xxhash-3.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:685c4f4e8c59837de103344eb1c8a3851f670309eb5c361f746805c5471b8c88", size = 208538, upload-time = "2024-08-17T09:18:10.332Z" }, + { url = "https://files.pythonhosted.org/packages/53/ad/7fa1a109663366de42f724a1cdb8e796a260dbac45047bce153bc1e18abf/xxhash-3.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dbd2ecfbfee70bc1a4acb7461fa6af7748ec2ab08ac0fa298f281c51518f982c", size = 216953, upload-time = "2024-08-17T09:18:11.707Z" }, + { url = "https://files.pythonhosted.org/packages/35/02/137300e24203bf2b2a49b48ce898ecce6fd01789c0fcd9c686c0a002d129/xxhash-3.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:25b5a51dc3dfb20a10833c8eee25903fd2e14059e9afcd329c9da20609a307b2", size = 203594, upload-time = "2024-08-17T09:18:13.799Z" }, + { url = "https://files.pythonhosted.org/packages/23/03/aeceb273933d7eee248c4322b98b8e971f06cc3880e5f7602c94e5578af5/xxhash-3.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a8fb786fb754ef6ff8c120cb96629fb518f8eb5a61a16aac3a979a9dbd40a084", size = 210971, upload-time = "2024-08-17T09:18:15.824Z" }, + { url = "https://files.pythonhosted.org/packages/e3/64/ed82ec09489474cbb35c716b189ddc1521d8b3de12b1b5ab41ce7f70253c/xxhash-3.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a905ad00ad1e1c34fe4e9d7c1d949ab09c6fa90c919860c1534ff479f40fd12d", size = 415050, upload-time = "2024-08-17T09:18:17.142Z" }, + { url = "https://files.pythonhosted.org/packages/71/43/6db4c02dcb488ad4e03bc86d70506c3d40a384ee73c9b5c93338eb1f3c23/xxhash-3.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:963be41bcd49f53af6d795f65c0da9b4cc518c0dd9c47145c98f61cb464f4839", size = 192216, upload-time = "2024-08-17T09:18:18.779Z" }, + { url = "https://files.pythonhosted.org/packages/22/6d/db4abec29e7a567455344433d095fdb39c97db6955bb4a2c432e486b4d28/xxhash-3.5.0-cp311-cp311-win32.whl", hash = "sha256:109b436096d0a2dd039c355fa3414160ec4d843dfecc64a14077332a00aeb7da", size = 30120, upload-time = "2024-08-17T09:18:20.009Z" }, + { url = "https://files.pythonhosted.org/packages/52/1c/fa3b61c0cf03e1da4767213672efe186b1dfa4fc901a4a694fb184a513d1/xxhash-3.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:b702f806693201ad6c0a05ddbbe4c8f359626d0b3305f766077d51388a6bac58", size = 30003, upload-time = "2024-08-17T09:18:21.052Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8e/9e6fc572acf6e1cc7ccb01973c213f895cb8668a9d4c2b58a99350da14b7/xxhash-3.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:c4dcb4120d0cc3cc448624147dba64e9021b278c63e34a38789b688fd0da9bf3", size = 26777, upload-time = "2024-08-17T09:18:22.809Z" }, + { url = "https://files.pythonhosted.org/packages/07/0e/1bfce2502c57d7e2e787600b31c83535af83746885aa1a5f153d8c8059d6/xxhash-3.5.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:14470ace8bd3b5d51318782cd94e6f94431974f16cb3b8dc15d52f3b69df8e00", size = 31969, upload-time = "2024-08-17T09:18:24.025Z" }, + { url = "https://files.pythonhosted.org/packages/3f/d6/8ca450d6fe5b71ce521b4e5db69622383d039e2b253e9b2f24f93265b52c/xxhash-3.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:59aa1203de1cb96dbeab595ded0ad0c0056bb2245ae11fac11c0ceea861382b9", size = 30787, upload-time = "2024-08-17T09:18:25.318Z" }, + { url = "https://files.pythonhosted.org/packages/5b/84/de7c89bc6ef63d750159086a6ada6416cc4349eab23f76ab870407178b93/xxhash-3.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08424f6648526076e28fae6ea2806c0a7d504b9ef05ae61d196d571e5c879c84", size = 220959, upload-time = "2024-08-17T09:18:26.518Z" }, + { url = "https://files.pythonhosted.org/packages/fe/86/51258d3e8a8545ff26468c977101964c14d56a8a37f5835bc0082426c672/xxhash-3.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:61a1ff00674879725b194695e17f23d3248998b843eb5e933007ca743310f793", size = 200006, upload-time = "2024-08-17T09:18:27.905Z" }, + { url = "https://files.pythonhosted.org/packages/02/0a/96973bd325412feccf23cf3680fd2246aebf4b789122f938d5557c54a6b2/xxhash-3.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2f2c61bee5844d41c3eb015ac652a0229e901074951ae48581d58bfb2ba01be", size = 428326, upload-time = "2024-08-17T09:18:29.335Z" }, + { url = "https://files.pythonhosted.org/packages/11/a7/81dba5010f7e733de88af9555725146fc133be97ce36533867f4c7e75066/xxhash-3.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d32a592cac88d18cc09a89172e1c32d7f2a6e516c3dfde1b9adb90ab5df54a6", size = 194380, upload-time = "2024-08-17T09:18:30.706Z" }, + { url = "https://files.pythonhosted.org/packages/fb/7d/f29006ab398a173f4501c0e4977ba288f1c621d878ec217b4ff516810c04/xxhash-3.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70dabf941dede727cca579e8c205e61121afc9b28516752fd65724be1355cc90", size = 207934, upload-time = "2024-08-17T09:18:32.133Z" }, + { url = "https://files.pythonhosted.org/packages/8a/6e/6e88b8f24612510e73d4d70d9b0c7dff62a2e78451b9f0d042a5462c8d03/xxhash-3.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e5d0ddaca65ecca9c10dcf01730165fd858533d0be84c75c327487c37a906a27", size = 216301, upload-time = "2024-08-17T09:18:33.474Z" }, + { url = "https://files.pythonhosted.org/packages/af/51/7862f4fa4b75a25c3b4163c8a873f070532fe5f2d3f9b3fc869c8337a398/xxhash-3.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e5b5e16c5a480fe5f59f56c30abdeba09ffd75da8d13f6b9b6fd224d0b4d0a2", size = 203351, upload-time = "2024-08-17T09:18:34.889Z" }, + { url = "https://files.pythonhosted.org/packages/22/61/8d6a40f288f791cf79ed5bb113159abf0c81d6efb86e734334f698eb4c59/xxhash-3.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149b7914451eb154b3dfaa721315117ea1dac2cc55a01bfbd4df7c68c5dd683d", size = 210294, upload-time = "2024-08-17T09:18:36.355Z" }, + { url = "https://files.pythonhosted.org/packages/17/02/215c4698955762d45a8158117190261b2dbefe9ae7e5b906768c09d8bc74/xxhash-3.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:eade977f5c96c677035ff39c56ac74d851b1cca7d607ab3d8f23c6b859379cab", size = 414674, upload-time = "2024-08-17T09:18:38.536Z" }, + { url = "https://files.pythonhosted.org/packages/31/5c/b7a8db8a3237cff3d535261325d95de509f6a8ae439a5a7a4ffcff478189/xxhash-3.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fa9f547bd98f5553d03160967866a71056a60960be00356a15ecc44efb40ba8e", size = 192022, upload-time = "2024-08-17T09:18:40.138Z" }, + { url = "https://files.pythonhosted.org/packages/78/e3/dd76659b2811b3fd06892a8beb850e1996b63e9235af5a86ea348f053e9e/xxhash-3.5.0-cp312-cp312-win32.whl", hash = "sha256:f7b58d1fd3551b8c80a971199543379be1cee3d0d409e1f6d8b01c1a2eebf1f8", size = 30170, upload-time = "2024-08-17T09:18:42.163Z" }, + { url = "https://files.pythonhosted.org/packages/d9/6b/1c443fe6cfeb4ad1dcf231cdec96eb94fb43d6498b4469ed8b51f8b59a37/xxhash-3.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:fa0cafd3a2af231b4e113fba24a65d7922af91aeb23774a8b78228e6cd785e3e", size = 30040, upload-time = "2024-08-17T09:18:43.699Z" }, + { url = "https://files.pythonhosted.org/packages/0f/eb/04405305f290173acc0350eba6d2f1a794b57925df0398861a20fbafa415/xxhash-3.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:586886c7e89cb9828bcd8a5686b12e161368e0064d040e225e72607b43858ba2", size = 26796, upload-time = "2024-08-17T09:18:45.29Z" }, + { url = "https://files.pythonhosted.org/packages/c9/b8/e4b3ad92d249be5c83fa72916c9091b0965cb0faeff05d9a0a3870ae6bff/xxhash-3.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:37889a0d13b0b7d739cfc128b1c902f04e32de17b33d74b637ad42f1c55101f6", size = 31795, upload-time = "2024-08-17T09:18:46.813Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d8/b3627a0aebfbfa4c12a41e22af3742cf08c8ea84f5cc3367b5de2d039cce/xxhash-3.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:97a662338797c660178e682f3bc180277b9569a59abfb5925e8620fba00b9fc5", size = 30792, upload-time = "2024-08-17T09:18:47.862Z" }, + { url = "https://files.pythonhosted.org/packages/c3/cc/762312960691da989c7cd0545cb120ba2a4148741c6ba458aa723c00a3f8/xxhash-3.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f85e0108d51092bdda90672476c7d909c04ada6923c14ff9d913c4f7dc8a3bc", size = 220950, upload-time = "2024-08-17T09:18:49.06Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e9/cc266f1042c3c13750e86a535496b58beb12bf8c50a915c336136f6168dc/xxhash-3.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2fd827b0ba763ac919440042302315c564fdb797294d86e8cdd4578e3bc7f3", size = 199980, upload-time = "2024-08-17T09:18:50.445Z" }, + { url = "https://files.pythonhosted.org/packages/bf/85/a836cd0dc5cc20376de26b346858d0ac9656f8f730998ca4324921a010b9/xxhash-3.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82085c2abec437abebf457c1d12fccb30cc8b3774a0814872511f0f0562c768c", size = 428324, upload-time = "2024-08-17T09:18:51.988Z" }, + { url = "https://files.pythonhosted.org/packages/b4/0e/15c243775342ce840b9ba34aceace06a1148fa1630cd8ca269e3223987f5/xxhash-3.5.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07fda5de378626e502b42b311b049848c2ef38784d0d67b6f30bb5008642f8eb", size = 194370, upload-time = "2024-08-17T09:18:54.164Z" }, + { url = "https://files.pythonhosted.org/packages/87/a1/b028bb02636dfdc190da01951d0703b3d904301ed0ef6094d948983bef0e/xxhash-3.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c279f0d2b34ef15f922b77966640ade58b4ccdfef1c4d94b20f2a364617a493f", size = 207911, upload-time = "2024-08-17T09:18:55.509Z" }, + { url = "https://files.pythonhosted.org/packages/80/d5/73c73b03fc0ac73dacf069fdf6036c9abad82de0a47549e9912c955ab449/xxhash-3.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:89e66ceed67b213dec5a773e2f7a9e8c58f64daeb38c7859d8815d2c89f39ad7", size = 216352, upload-time = "2024-08-17T09:18:57.073Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2a/5043dba5ddbe35b4fe6ea0a111280ad9c3d4ba477dd0f2d1fe1129bda9d0/xxhash-3.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bcd51708a633410737111e998ceb3b45d3dbc98c0931f743d9bb0a209033a326", size = 203410, upload-time = "2024-08-17T09:18:58.54Z" }, + { url = "https://files.pythonhosted.org/packages/a2/b2/9a8ded888b7b190aed75b484eb5c853ddd48aa2896e7b59bbfbce442f0a1/xxhash-3.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ff2c0a34eae7df88c868be53a8dd56fbdf592109e21d4bfa092a27b0bf4a7bf", size = 210322, upload-time = "2024-08-17T09:18:59.943Z" }, + { url = "https://files.pythonhosted.org/packages/98/62/440083fafbc917bf3e4b67c2ade621920dd905517e85631c10aac955c1d2/xxhash-3.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4e28503dccc7d32e0b9817aa0cbfc1f45f563b2c995b7a66c4c8a0d232e840c7", size = 414725, upload-time = "2024-08-17T09:19:01.332Z" }, + { url = "https://files.pythonhosted.org/packages/75/db/009206f7076ad60a517e016bb0058381d96a007ce3f79fa91d3010f49cc2/xxhash-3.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a6c50017518329ed65a9e4829154626f008916d36295b6a3ba336e2458824c8c", size = 192070, upload-time = "2024-08-17T09:19:03.007Z" }, + { url = "https://files.pythonhosted.org/packages/1f/6d/c61e0668943a034abc3a569cdc5aeae37d686d9da7e39cf2ed621d533e36/xxhash-3.5.0-cp313-cp313-win32.whl", hash = "sha256:53a068fe70301ec30d868ece566ac90d873e3bb059cf83c32e76012c889b8637", size = 30172, upload-time = "2024-08-17T09:19:04.355Z" }, + { url = "https://files.pythonhosted.org/packages/96/14/8416dce965f35e3d24722cdf79361ae154fa23e2ab730e5323aa98d7919e/xxhash-3.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:80babcc30e7a1a484eab952d76a4f4673ff601f54d5142c26826502740e70b43", size = 30041, upload-time = "2024-08-17T09:19:05.435Z" }, + { url = "https://files.pythonhosted.org/packages/27/ee/518b72faa2073f5aa8e3262408d284892cb79cf2754ba0c3a5870645ef73/xxhash-3.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:4811336f1ce11cac89dcbd18f3a25c527c16311709a89313c3acaf771def2d4b", size = 26801, upload-time = "2024-08-17T09:19:06.547Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9a/233606bada5bd6f50b2b72c45de3d9868ad551e83893d2ac86dc7bb8553a/xxhash-3.5.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:2014c5b3ff15e64feecb6b713af12093f75b7926049e26a580e94dcad3c73d8c", size = 29732, upload-time = "2024-08-17T09:20:11.175Z" }, + { url = "https://files.pythonhosted.org/packages/0c/67/f75276ca39e2c6604e3bee6c84e9db8a56a4973fde9bf35989787cf6e8aa/xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fab81ef75003eda96239a23eda4e4543cedc22e34c373edcaf744e721a163986", size = 36214, upload-time = "2024-08-17T09:20:12.335Z" }, + { url = "https://files.pythonhosted.org/packages/0f/f8/f6c61fd794229cc3848d144f73754a0c107854372d7261419dcbbd286299/xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e2febf914ace002132aa09169cc572e0d8959d0f305f93d5828c4836f9bc5a6", size = 32020, upload-time = "2024-08-17T09:20:13.537Z" }, + { url = "https://files.pythonhosted.org/packages/79/d3/c029c99801526f859e6b38d34ab87c08993bf3dcea34b11275775001638a/xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5d3a10609c51da2a1c0ea0293fc3968ca0a18bd73838455b5bca3069d7f8e32b", size = 40515, upload-time = "2024-08-17T09:20:14.669Z" }, + { url = "https://files.pythonhosted.org/packages/62/e3/bef7b82c1997579c94de9ac5ea7626d01ae5858aa22bf4fcb38bf220cb3e/xxhash-3.5.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5a74f23335b9689b66eb6dbe2a931a88fcd7a4c2cc4b1cb0edba8ce381c7a1da", size = 30064, upload-time = "2024-08-17T09:20:15.925Z" }, +] From e4351e5baea4a14abdbc6ebda42a844c57b89763 Mon Sep 17 00:00:00 2001 From: "Edgar Y. Walker" Date: Tue, 27 May 2025 20:11:50 +0000 Subject: [PATCH 31/34] refactor: turn hashing into a subpackage --- .gitignore | 3 + src/orcabridge/__init__.py | 4 +- src/orcabridge/hashing/__init__.py | 8 +- .../hashing/{hashing.py => core.py} | 576 +++++++++--------- src/orcabridge/hashing/function.py | 7 + src/orcabridge/pod.py | 1 + 6 files changed, 306 insertions(+), 293 deletions(-) rename src/orcabridge/hashing/{hashing.py => core.py} (99%) create mode 100644 src/orcabridge/hashing/function.py diff --git a/.gitignore b/.gitignore index 07377ae..e66a8b6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# Ignore vscode settings +.vscode/ + # Ignore example dataset storage generated in tutorial **/pod_data/ diff --git a/src/orcabridge/__init__.py b/src/orcabridge/__init__.py index 7a6f6c2..d58891e 100644 --- a/src/orcabridge/__init__.py +++ b/src/orcabridge/__init__.py @@ -1,4 +1,3 @@ -from .tracker import Tracker from . import hashing from . import pod from . import mapper @@ -9,10 +8,11 @@ from .pod import FunctionPod, function_pod from .source import GlobSource from .store import DirDataStore, SafeDirDataStore +from .tracker import GraphTracker -DEFAULT_TRACKER = Tracker() +DEFAULT_TRACKER = GraphTracker() DEFAULT_TRACKER.activate() diff --git a/src/orcabridge/hashing/__init__.py b/src/orcabridge/hashing/__init__.py index 6eb985c..1b5bac6 100644 --- a/src/orcabridge/hashing/__init__.py +++ b/src/orcabridge/hashing/__init__.py @@ -1,14 +1,14 @@ -from .hashing import ( +from .core import ( hash_file, hash_pathset, hash_packet, hash_to_hex, hash_to_int, hash_to_uuid, - hash_function, - get_function_signature, - function_content_hash, HashableMixin, + function_content_hash, + get_function_signature, + hash_function ) __all__ = [ diff --git a/src/orcabridge/hashing/hashing.py b/src/orcabridge/hashing/core.py similarity index 99% rename from src/orcabridge/hashing/hashing.py rename to src/orcabridge/hashing/core.py index c0a1f89..71d1211 100644 --- a/src/orcabridge/hashing/hashing.py +++ b/src/orcabridge/hashing/core.py @@ -7,7 +7,6 @@ """ import hashlib -import inspect import json import logging from uuid import UUID @@ -18,10 +17,10 @@ Union, Collection, Mapping, - Callable, TypeVar, Set, - Literal, + Callable, + Literal ) from pathlib import Path from os import PathLike @@ -29,6 +28,7 @@ import zlib from orcabridge.types import PathSet, Packet from orcabridge.utils.name import find_noncolliding_name +import inspect # Configure logging with __name__ for proper hierarchy logger = logging.getLogger(__name__) @@ -522,6 +522,244 @@ def _process_structure(obj: Any, visited: Optional[Set[int]] = None) -> Any: # Function hashing utilities +# Legacy compatibility functions + + +def hash_dict(d: NestedDict) -> UUID: + """ + Hash a dictionary with stable results across sessions. + + Args: + d: The dictionary to hash (can be arbitrarily nested) + + Returns: + A UUID hash of the dictionary + """ + return hash_to_uuid(d) + + +def stable_hash(s: Any) -> int: + """ + Create a stable hash that returns the same integer value across sessions. + + Args: + s: The object to hash + + Returns: + An integer hash + """ + return hash_to_int(s) + + +# Hashing of packets and PathSet + + +class PathSetHasher: + def __init__(self, char_count=32): + self.char_count = char_count + + def hash_pathset(self, pathset: PathSet) -> str: + if isinstance(pathset, str) or isinstance(pathset, PathLike): + pathset = Path(pathset) + if not pathset.exists(): + raise FileNotFoundError(f"Path {pathset} does not exist") + if pathset.is_dir(): + # iterate over all entries in the directory include subdirectory (single step) + hash_dict = {} + for entry in pathset.iterdir(): + file_name = find_noncolliding_name(entry.name, hash_dict) + hash_dict[file_name] = self.hash_pathset(entry) + return hash_to_hex(hash_dict, char_count=self.char_count) + else: + # it's a file, hash it directly + return hash_file(pathset) + + if isinstance(pathset, Collection): + hash_dict = {} + for path in pathset: + file_name = find_noncolliding_name(Path(path).name, hash_dict) + hash_dict[file_name] = self.hash_pathset(path) + return hash_to_hex(hash_dict, char_count=self.char_count) + + raise ValueError(f"PathSet of type {type(pathset)} is not supported") + + def hash_file(self, filepath) -> str: ... + + def id(self) -> str: ... + + +def hash_packet_with_psh( + packet: Packet, algo: PathSetHasher, prefix_algorithm: bool = True +) -> str: + """ + Generate a hash for a packet based on its content. + + Args: + packet: The packet to hash + algorithm: The algorithm to use for hashing + prefix_algorithm: Whether to prefix the hash with the algorithm name + + Returns: + A hexadecimal digest of the packet's content + """ + hash_results = {} + for key, pathset in packet.items(): + hash_results[key] = algo.hash_pathset(pathset) + + packet_hash = hash_to_hex(hash_results) + + if prefix_algorithm: + # Prefix the hash with the algorithm name + packet_hash = f"{algo.id()}-{packet_hash}" + + return packet_hash + + +def hash_packet( + packet: Packet, + algorithm: str = "sha256", + buffer_size: int = 65536, + char_count: Optional[int] = 32, + prefix_algorithm: bool = True, +) -> str: + """ + Generate a hash for a packet based on its content. + + Args: + packet: The packet to hash + + Returns: + A hexadecimal digest of the packet's content + """ + hash_results = {} + for key, pathset in packet.items(): + hash_results[key] = hash_pathset( + pathset, algorithm=algorithm, buffer_size=buffer_size + ) + + packet_hash = hash_to_hex(hash_results, char_count=char_count) + + if prefix_algorithm: + # Prefix the hash with the algorithm name + packet_hash = f"{algorithm}-{packet_hash}" + + return packet_hash + + +def hash_pathset( + pathset: PathSet, + algorithm="sha256", + buffer_size=65536, + char_count: Optional[int] = 32, +) -> str: + """ + Generate hash of the pathset based primarily on the content of the files. + If the pathset is a collection of files or a directory, the name of the file + will be included in the hash calculation. + + Currently only support hashing of Pathset if Pathset points to a single file. + """ + if isinstance(pathset, str) or isinstance(pathset, PathLike): + pathset = Path(pathset) + if not pathset.exists(): + raise FileNotFoundError(f"Path {pathset} does not exist") + if pathset.is_dir(): + # iterate over all entries in the directory include subdirectory (single step) + hash_dict = {} + for entry in pathset.iterdir(): + file_name = find_noncolliding_name(entry.name, hash_dict) + hash_dict[file_name] = hash_pathset( + entry, + algorithm=algorithm, + buffer_size=buffer_size, + char_count=char_count, + ) + return hash_to_hex(hash_dict, char_count=char_count) + else: + # it's a file, hash it directly + return hash_file( + pathset, algorithm=algorithm, buffer_size=buffer_size + ) + + if isinstance(pathset, Collection): + hash_dict = {} + for path in pathset: + file_name = find_noncolliding_name(Path(path).name, hash_dict) + hash_dict[file_name] = hash_pathset( + path, + algorithm=algorithm, + buffer_size=buffer_size, + char_count=char_count, + ) + return hash_to_hex(hash_dict, char_count=char_count) + + +def hash_file(file_path, algorithm="sha256", buffer_size=65536) -> str: + """ + Calculate the hash of a file using the specified algorithm. + + Parameters: + file_path (str): Path to the file to hash + algorithm (str): Hash algorithm to use - options include: + 'md5', 'sha1', 'sha256', 'sha512', 'xxh64', 'crc32', 'hash_path' + buffer_size (int): Size of chunks to read from the file at a time + + Returns: + str: Hexadecimal digest of the hash + """ + # Verify the file exists + if not Path(file_path).is_file(): + raise FileNotFoundError(f"The file {file_path} does not exist") + + # Handle special case for 'hash_path' algorithm + if algorithm == "hash_path": + # Hash the name of the file instead of its content + # This is useful for cases where the file content is well known or + # not relevant + return hash_to_hex(file_path) + + # Handle non-cryptographic hash functions + if algorithm == "xxh64": + hasher = xxhash.xxh64() + with open(file_path, "rb") as file: + while True: + data = file.read(buffer_size) + if not data: + break + hasher.update(data) + return hasher.hexdigest() + + if algorithm == "crc32": + crc = 0 + with open(file_path, "rb") as file: + while True: + data = file.read(buffer_size) + if not data: + break + crc = zlib.crc32(data, crc) + return format(crc & 0xFFFFFFFF, "08x") # Convert to hex string + + # Handle cryptographic hash functions from hashlib + try: + hasher = hashlib.new(algorithm) + except ValueError: + valid_algorithms = ", ".join(sorted(hashlib.algorithms_available)) + raise ValueError( + f"Invalid algorithm: {algorithm}. Available algorithms: {valid_algorithms}, xxh64, crc32" + ) + + with open(file_path, "rb") as file: + while True: + data = file.read(buffer_size) + if not data: + break + hasher.update(data) + + return hasher.hexdigest() + return hasher.hexdigest() + + + def get_function_signature( func: Callable, include_defaults: bool = True, include_module: bool = True ) -> str: @@ -565,40 +803,19 @@ def get_function_signature( return " ".join(parts) -def function_content_hash( - func: Callable, - include_name: bool = True, - include_module: bool = True, - include_declaration: bool = True, - char_count: Optional[int] = 32, -) -> str: - """ - Compute a stable hash based on a function's source code and other properties. - - Args: - func: The function to hash - include_name: Whether to include the function name in the hash - include_module: Whether to include the module name in the hash - include_declaration: Whether to include the function declaration line - char_count: Number of characters to include in the result - - Returns: - A hex string hash of the function's content - """ - logger.debug(f"Generating content hash for function '{func.__name__}'") - components = get_function_components( - func, - include_name=include_name, - include_module=include_module, - include_declaration=include_declaration, - ) - - # Join all components and compute hash - combined = "\n".join(components) - logger.debug( - f"Function components joined, length: {len(combined)} characters" - ) - return hash_to_hex(combined, char_count=char_count) +def _is_in_string(line, pos): + """Helper to check if a position in a line is inside a string literal.""" + # This is a simplified check - would need proper parsing for robust handling + in_single = False + in_double = False + for i in range(pos): + if line[i] == "'" and not in_double and (i == 0 or line[i - 1] != "\\"): + in_single = not in_single + elif ( + line[i] == '"' and not in_single and (i == 0 or line[i - 1] != "\\") + ): + in_double = not in_double + return in_single or in_double def get_function_components( @@ -713,19 +930,40 @@ def get_function_components( return components -def _is_in_string(line, pos): - """Helper to check if a position in a line is inside a string literal.""" - # This is a simplified check - would need proper parsing for robust handling - in_single = False - in_double = False - for i in range(pos): - if line[i] == "'" and not in_double and (i == 0 or line[i - 1] != "\\"): - in_single = not in_single - elif ( - line[i] == '"' and not in_single and (i == 0 or line[i - 1] != "\\") - ): - in_double = not in_double - return in_single or in_double +def function_content_hash( + func: Callable, + include_name: bool = True, + include_module: bool = True, + include_declaration: bool = True, + char_count: Optional[int] = 32, +) -> str: + """ + Compute a stable hash based on a function's source code and other properties. + + Args: + func: The function to hash + include_name: Whether to include the function name in the hash + include_module: Whether to include the module name in the hash + include_declaration: Whether to include the function declaration line + char_count: Number of characters to include in the result + + Returns: + A hex string hash of the function's content + """ + logger.debug(f"Generating content hash for function '{func.__name__}'") + components = get_function_components( + func, + include_name=include_name, + include_module=include_module, + include_declaration=include_declaration, + ) + + # Join all components and compute hash + combined = "\n".join(components) + logger.debug( + f"Function components joined, length: {len(combined)} characters" + ) + return hash_to_hex(combined, char_count=char_count) def hash_function( @@ -794,240 +1032,4 @@ def hash_function( raise ValueError(err_msg) logger.debug(f"Generated hash value as {return_type}: {hash_value}") - return hash_value - - -# Legacy compatibility functions - - -def hash_dict(d: NestedDict) -> UUID: - """ - Hash a dictionary with stable results across sessions. - - Args: - d: The dictionary to hash (can be arbitrarily nested) - - Returns: - A UUID hash of the dictionary - """ - return hash_to_uuid(d) - - -def stable_hash(s: Any) -> int: - """ - Create a stable hash that returns the same integer value across sessions. - - Args: - s: The object to hash - - Returns: - An integer hash - """ - return hash_to_int(s) - - -# Hashing of packets and PathSet - - -class PathSetHasher: - def __init__(self, char_count=32): - self.char_count = char_count - - def hash_pathset(self, pathset: PathSet) -> str: - if isinstance(pathset, str) or isinstance(pathset, PathLike): - pathset = Path(pathset) - if not pathset.exists(): - raise FileNotFoundError(f"Path {pathset} does not exist") - if pathset.is_dir(): - # iterate over all entries in the directory include subdirectory (single step) - hash_dict = {} - for entry in pathset.iterdir(): - file_name = find_noncolliding_name(entry.name, hash_dict) - hash_dict[file_name] = self.hash_pathset(entry) - return hash_to_hex(hash_dict, char_count=self.char_count) - else: - # it's a file, hash it directly - return hash_file(pathset) - - if isinstance(pathset, Collection): - hash_dict = {} - for path in pathset: - file_name = find_noncolliding_name(Path(path).name, hash_dict) - hash_dict[file_name] = self.hash_pathset(path) - return hash_to_hex(hash_dict, char_count=self.char_count) - - raise ValueError(f"PathSet of type {type(pathset)} is not supported") - - def hash_file(self, filepath) -> str: ... - - def id(self) -> str: ... - - -def hash_packet_with_psh( - packet: Packet, algo: PathSetHasher, prefix_algorithm: bool = True -) -> str: - """ - Generate a hash for a packet based on its content. - - Args: - packet: The packet to hash - algorithm: The algorithm to use for hashing - prefix_algorithm: Whether to prefix the hash with the algorithm name - - Returns: - A hexadecimal digest of the packet's content - """ - hash_results = {} - for key, pathset in packet.items(): - hash_results[key] = algo.hash_pathset(pathset) - - packet_hash = hash_to_hex(hash_results) - - if prefix_algorithm: - # Prefix the hash with the algorithm name - packet_hash = f"{algo.id()}-{packet_hash}" - - return packet_hash - - -def hash_packet( - packet: Packet, - algorithm: str = "sha256", - buffer_size: int = 65536, - char_count: Optional[int] = 32, - prefix_algorithm: bool = True, -) -> str: - """ - Generate a hash for a packet based on its content. - - Args: - packet: The packet to hash - - Returns: - A hexadecimal digest of the packet's content - """ - hash_results = {} - for key, pathset in packet.items(): - hash_results[key] = hash_pathset( - pathset, algorithm=algorithm, buffer_size=buffer_size - ) - - packet_hash = hash_to_hex(hash_results, char_count=char_count) - - if prefix_algorithm: - # Prefix the hash with the algorithm name - packet_hash = f"{algorithm}-{packet_hash}" - - return packet_hash - - -def hash_pathset( - pathset: PathSet, - algorithm="sha256", - buffer_size=65536, - char_count: Optional[int] = 32, -) -> str: - """ - Generate hash of the pathset based primarily on the content of the files. - If the pathset is a collection of files or a directory, the name of the file - will be included in the hash calculation. - - Currently only support hashing of Pathset if Pathset points to a single file. - """ - if isinstance(pathset, str) or isinstance(pathset, PathLike): - pathset = Path(pathset) - if not pathset.exists(): - raise FileNotFoundError(f"Path {pathset} does not exist") - if pathset.is_dir(): - # iterate over all entries in the directory include subdirectory (single step) - hash_dict = {} - for entry in pathset.iterdir(): - file_name = find_noncolliding_name(entry.name, hash_dict) - hash_dict[file_name] = hash_pathset( - entry, - algorithm=algorithm, - buffer_size=buffer_size, - char_count=char_count, - ) - return hash_to_hex(hash_dict, char_count=char_count) - else: - # it's a file, hash it directly - return hash_file( - pathset, algorithm=algorithm, buffer_size=buffer_size - ) - - if isinstance(pathset, Collection): - hash_dict = {} - for path in pathset: - file_name = find_noncolliding_name(Path(path).name, hash_dict) - hash_dict[file_name] = hash_pathset( - path, - algorithm=algorithm, - buffer_size=buffer_size, - char_count=char_count, - ) - return hash_to_hex(hash_dict, char_count=char_count) - - -def hash_file(file_path, algorithm="sha256", buffer_size=65536) -> str: - """ - Calculate the hash of a file using the specified algorithm. - - Parameters: - file_path (str): Path to the file to hash - algorithm (str): Hash algorithm to use - options include: - 'md5', 'sha1', 'sha256', 'sha512', 'xxh64', 'crc32', 'hash_path' - buffer_size (int): Size of chunks to read from the file at a time - - Returns: - str: Hexadecimal digest of the hash - """ - # Verify the file exists - if not Path(file_path).is_file(): - raise FileNotFoundError(f"The file {file_path} does not exist") - - # Handle special case for 'hash_path' algorithm - if algorithm == "hash_path": - # Hash the name of the file instead of its content - # This is useful for cases where the file content is well known or - # not relevant - return hash_to_hex(file_path) - - # Handle non-cryptographic hash functions - if algorithm == "xxh64": - hasher = xxhash.xxh64() - with open(file_path, "rb") as file: - while True: - data = file.read(buffer_size) - if not data: - break - hasher.update(data) - return hasher.hexdigest() - - if algorithm == "crc32": - crc = 0 - with open(file_path, "rb") as file: - while True: - data = file.read(buffer_size) - if not data: - break - crc = zlib.crc32(data, crc) - return format(crc & 0xFFFFFFFF, "08x") # Convert to hex string - - # Handle cryptographic hash functions from hashlib - try: - hasher = hashlib.new(algorithm) - except ValueError: - valid_algorithms = ", ".join(sorted(hashlib.algorithms_available)) - raise ValueError( - f"Invalid algorithm: {algorithm}. Available algorithms: {valid_algorithms}, xxh64, crc32" - ) - - with open(file_path, "rb") as file: - while True: - data = file.read(buffer_size) - if not data: - break - hasher.update(data) - - return hasher.hexdigest() + return hash_value \ No newline at end of file diff --git a/src/orcabridge/hashing/function.py b/src/orcabridge/hashing/function.py new file mode 100644 index 0000000..244e10b --- /dev/null +++ b/src/orcabridge/hashing/function.py @@ -0,0 +1,7 @@ +# Provides functions for hashing of a Python function +import inspect +from typing import Callable, Literal +from uuid import UUID + +from .core import hash_to_hex, hash_to_int, hash_to_uuid, logger + diff --git a/src/orcabridge/pod.py b/src/orcabridge/pod.py index 6b79d68..87a9405 100644 --- a/src/orcabridge/pod.py +++ b/src/orcabridge/pod.py @@ -17,6 +17,7 @@ import warnings import logging + logger = logging.getLogger(__name__) From ed78416de39b28f6219d8a718c8954321f49b373 Mon Sep 17 00:00:00 2001 From: "Edgar Y. Walker" Date: Tue, 27 May 2025 20:13:08 +0000 Subject: [PATCH 32/34] refactor: remove accidentally committed vscode setting --- .vscode/settings.json | 41 ----------------------------------------- 1 file changed, 41 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index fb3c2bc..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "editor.formatOnPaste": false, - "editor.formatOnSave": true, - "notebook.formatOnSave.enabled": true, - "notebook.output.scrolling": true, - "editor.rulers": [ - 88 - ], - "[python]": { - "editor.defaultFormatter": "ms-python.black-formatter" - }, - "[markdown]": { - "editor.defaultFormatter": null - }, - "workbench.editorAssociations": { - "*.md": "vscode.markdown.preview.editor" - }, - "files.autoSave": "off", - "jupyter.kernels.excludePythonEnvironments": [ - "/bin/python3", - "/usr/bin/python3", - "/usr/local/bin/python" - ], - "python.defaultInterpreterPath": "/home/vscode/.local/share/base/bin/python", - "python.terminal.activateEnvironment": false, - "gitlens.showWelcomeOnInstall": false, - "gitlens.showWhatsNewAfterUpgrades": false, - "launch": { - "version": "0.2.0", - "configurations": [ - { - "name": "Python", - "type": "debugpy", - "request": "launch", - "console": "integratedTerminal", - "justMyCode": false, - "program": "${file}", - } - ] - }, -} \ No newline at end of file From 794d00a11b90a6d0bf2b01f466b54325d51ec587 Mon Sep 17 00:00:00 2001 From: "Edgar Y. Walker" Date: Tue, 27 May 2025 20:16:15 +0000 Subject: [PATCH 33/34] chore: merge commit from upstream --- pyproject.toml | 4 +--- tests/test_hashing/test_basic_hashing.py | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 4616193..c640ca9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,6 +36,4 @@ where = ["src"] version_file = "src/orcabridge/_version.py" [dependency-groups] -dev = [ - "ruff>=0.11.11", -] +dev = ["ruff>=0.11.11"] diff --git a/tests/test_hashing/test_basic_hashing.py b/tests/test_hashing/test_basic_hashing.py index ea6b681..1332558 100644 --- a/tests/test_hashing/test_basic_hashing.py +++ b/tests/test_hashing/test_basic_hashing.py @@ -1,4 +1,4 @@ -from orcabridge.hashing.hashing import ( +from orcabridge.hashing.core import ( hash_to_hex, hash_to_int, hash_to_uuid, From 71c69823723d015d2a2dfec2445c73a1eba9babd Mon Sep 17 00:00:00 2001 From: "Edgar Y. Walker" Date: Tue, 27 May 2025 13:40:57 -0700 Subject: [PATCH 34/34] Update src/orcabridge/store/dir_data_store.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/orcabridge/store/dir_data_store.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/orcabridge/store/dir_data_store.py b/src/orcabridge/store/dir_data_store.py index bf1436c..01f1777 100644 --- a/src/orcabridge/store/dir_data_store.py +++ b/src/orcabridge/store/dir_data_store.py @@ -156,7 +156,7 @@ def retrieve_memoized( logger.info( f"Supplemented source for packet {packet} at {source_path}" ) - except IOError as e: + except (IOError, json.JSONDecodeError) as e: logger.error( f"Error loading memoized output for packet {packet} from {info_path}: {e}" )