From b25f72629fdecef95c991df3830eb91f566074a8 Mon Sep 17 00:00:00 2001 From: Mauricio Villegas <5780272+mauvilsa@users.noreply.github.com> Date: Sat, 10 May 2025 11:16:10 +0200 Subject: [PATCH 01/39] Colored view --- CHANGELOG.md | 3 + deepdiff/colored_view.py | 124 ++++++++++++++++++++++++++++++++ deepdiff/commands.py | 6 +- deepdiff/diff.py | 15 +++- deepdiff/helper.py | 1 + docs/colored_view.rst | 60 ++++++++++++++++ docs/index.rst | 1 + tests/test_colored_view.py | 140 +++++++++++++++++++++++++++++++++++++ 8 files changed, 347 insertions(+), 3 deletions(-) create mode 100644 deepdiff/colored_view.py create mode 100644 docs/colored_view.rst create mode 100644 tests/test_colored_view.py diff --git a/CHANGELOG.md b/CHANGELOG.md index bcd2a12a..3c6b532f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # DeepDiff Change log +- [Unreleased] + - Colored View: Output pretty-printed JSON with color-coded differences (added in green, removed in red, changed values show old in red and new in green). + - v8-5-0 - Updating deprecated pydantic calls - Switching to pyproject.toml diff --git a/deepdiff/colored_view.py b/deepdiff/colored_view.py new file mode 100644 index 00000000..c946f008 --- /dev/null +++ b/deepdiff/colored_view.py @@ -0,0 +1,124 @@ +import json +from ast import literal_eval +from importlib.util import find_spec +from typing import Any, Dict + +if find_spec("colorama"): + import colorama + + colorama.init() + + +# ANSI color codes +RED = '\033[31m' +GREEN = '\033[32m' +RESET = '\033[0m' + +class ColoredView: + """A view that shows JSON with color-coded differences.""" + + def __init__(self, t2, tree_results, verbose_level=1): + self.t2 = t2 + self.tree = tree_results + self.verbose_level = verbose_level + self.diff_paths = self._collect_diff_paths() + + def _collect_diff_paths(self) -> Dict[str, str]: + """Collect all paths that have differences and their types.""" + diff_paths = {} + for diff_type, items in self.tree.items(): + try: + iterator = iter(items) + except TypeError: + continue + for item in items: + if type(item).__name__ == "DiffLevel": + path = item.path() + if diff_type in ('values_changed', 'type_changes'): + diff_paths[path] = ('changed', item.t1, item.t2) + elif diff_type in ('dictionary_item_added', 'iterable_item_added', 'set_item_added'): + diff_paths[path] = ('added', None, item.t2) + elif diff_type in ('dictionary_item_removed', 'iterable_item_removed', 'set_item_removed'): + diff_paths[path] = ('removed', item.t1, None) + return diff_paths + + def _format_value(self, value: Any) -> str: + """Format a value for display.""" + if isinstance(value, bool): + return 'true' if value else 'false' + elif isinstance(value, str): + return f'"{value}"' + elif isinstance(value, (dict, list, tuple)): + return json.dumps(value) + else: + return str(value) + + def _get_path_removed(self, path: str) -> dict: + """Get all removed items for a given path.""" + removed = {} + for key, value in self.diff_paths.items(): + if value[0] == 'removed' and key.startswith(path + "["): + key_suffix = key[len(path):] + if key_suffix.count("[") == 1 and key_suffix.endswith("]"): + removed[literal_eval(key_suffix[1:-1])] = value[1] + return removed + + def _colorize_json(self, obj: Any, path: str = 'root', indent: int = 0) -> str: + """Recursively colorize JSON based on differences, with pretty-printing.""" + INDENT = ' ' + current_indent = INDENT * indent + next_indent = INDENT * (indent + 1) + if path in self.diff_paths and path not in self._colorize_skip_paths: + diff_type, old, new = self.diff_paths[path] + if diff_type == 'changed': + return f"{RED}{self._format_value(old)}{RESET} -> {GREEN}{self._format_value(new)}{RESET}" + elif diff_type == 'added': + return f"{GREEN}{self._format_value(new)}{RESET}" + elif diff_type == 'removed': + return f"{RED}{self._format_value(old)}{RESET}" + + if isinstance(obj, dict): + if not obj: + return '{}' + items = [] + for key, value in obj.items(): + new_path = f"{path}['{key}']" if isinstance(key, str) else f"{path}[{key}]" + if new_path in self.diff_paths and self.diff_paths[new_path][0] == 'added': + # Colorize both key and value for added fields + items.append(f'{next_indent}{GREEN}"{key}": {self._colorize_json(value, new_path, indent + 1)}{RESET}') + else: + items.append(f'{next_indent}"{key}": {self._colorize_json(value, new_path, indent + 1)}') + for key, value in self._get_path_removed(path).items(): + new_path = f"{path}['{key}']" if isinstance(key, str) else f"{path}[{key}]" + items.append(f'{next_indent}{RED}"{key}": {self._colorize_json(value, new_path, indent + 1)}{RESET}') + return '{\n' + ',\n'.join(items) + f'\n{current_indent}' + '}' + + elif isinstance(obj, (list, tuple)): + if not obj: + return '[]' + removed_map = self._get_path_removed(path) + for index in removed_map: + self._colorize_skip_paths.add(f"{path}[{index}]") + items = [] + index = 0 + for value in obj: + new_path = f"{path}[{index}]" + while index == next(iter(removed_map), None): + items.append(f'{next_indent}{RED}{self._format_value(removed_map.pop(index))}{RESET}') + index += 1 + items.append(f'{next_indent}{self._colorize_json(value, new_path, indent + 1)}') + index += 1 + for value in removed_map.values(): + items.append(f'{next_indent}{RED}{self._format_value(value)}{RESET}') + return '[\n' + ',\n'.join(items) + f'\n{current_indent}' + ']' + else: + return self._format_value(obj) + + def __str__(self) -> str: + """Return the colorized, pretty-printed JSON string.""" + self._colorize_skip_paths = set() + return self._colorize_json(self.t2, indent=0) + + def __iter__(self): + """Make the view iterable by yielding the tree results.""" + yield from self.tree.items() diff --git a/deepdiff/commands.py b/deepdiff/commands.py index 1859e35a..67aa94d8 100644 --- a/deepdiff/commands.py +++ b/deepdiff/commands.py @@ -55,6 +55,7 @@ def cli(): @click.option('--truncate-datetime', required=False, type=click.Choice(['second', 'minute', 'hour', 'day'], case_sensitive=True), show_default=True, default=None) @click.option('--verbose-level', required=False, default=1, type=click.IntRange(0, 2), show_default=True) @click.option('--debug', is_flag=True, show_default=False) +@click.option('--view', required=False, type=click.Choice(['tree', 'colored'], case_sensitive=True), show_default=True, default="text") def diff( *args, **kwargs ): @@ -112,7 +113,10 @@ def diff( sys.stdout.buffer.write(delta.dumps()) else: try: - print(diff.to_json(indent=2)) + if kwargs["view"] == 'colored': + print(diff) + else: + print(diff.to_json(indent=2)) except Exception: pprint(diff, indent=2) diff --git a/deepdiff/diff.py b/deepdiff/diff.py index d2664ef6..50132f37 100755 --- a/deepdiff/diff.py +++ b/deepdiff/diff.py @@ -25,7 +25,7 @@ type_is_subclass_of_type_group, type_in_type_group, get_doc, number_to_string, datetime_normalize, KEY_TO_VAL_STR, booleans, np_ndarray, np_floating, get_numpy_ndarray_rows, RepeatedTimer, - TEXT_VIEW, TREE_VIEW, DELTA_VIEW, detailed__dict__, add_root_to_paths, + TEXT_VIEW, TREE_VIEW, DELTA_VIEW, COLORED_VIEW, detailed__dict__, add_root_to_paths, np, get_truncate_datetime, dict_, CannotCompare, ENUM_INCLUDE_KEYS, PydanticBaseModel, Opcode, SetOrdered, ipranges) from deepdiff.serialization import SerializationMixin @@ -40,6 +40,7 @@ from deepdiff.deephash import DeepHash, combine_hashes_lists from deepdiff.base import Base from deepdiff.lfucache import LFUCache, DummyLFU +from deepdiff.colored_view import ColoredView if TYPE_CHECKING: from pytz.tzinfo import BaseTzInfo @@ -365,7 +366,10 @@ def _group_by_sort_key(x): self.tree.remove_empty_keys() view_results = self._get_view_results(self.view) - self.update(view_results) + if self.view == COLORED_VIEW: + self._colored_view = view_results + else: + self.update(view_results) finally: if self.is_root: if cache_purge_level: @@ -1759,6 +1763,8 @@ def _get_view_results(self, view): result.remove_empty_keys() elif view == DELTA_VIEW: result = self._to_delta_dict(report_repetition_required=False) + elif view == COLORED_VIEW: + result = ColoredView(self.t2, tree_results=self.tree, verbose_level=self.verbose_level) else: raise ValueError(INVALID_VIEW_MSG.format(view)) return result @@ -1899,6 +1905,11 @@ def affected_root_keys(self): result.add(root_key) return result + def __str__(self): + if hasattr(self, '_colored_view') and self.view == COLORED_VIEW: + return str(self._colored_view) + return super().__str__() + if __name__ == "__main__": # pragma: no cover import doctest diff --git a/deepdiff/helper.py b/deepdiff/helper.py index f06b315c..150bbf22 100644 --- a/deepdiff/helper.py +++ b/deepdiff/helper.py @@ -209,6 +209,7 @@ class IndexedHash(NamedTuple): TREE_VIEW = 'tree' TEXT_VIEW = 'text' DELTA_VIEW = '_delta' +COLORED_VIEW = 'colored' ENUM_INCLUDE_KEYS = ['__objclass__', 'name', 'value'] diff --git a/docs/colored_view.rst b/docs/colored_view.rst new file mode 100644 index 00000000..06b5041d --- /dev/null +++ b/docs/colored_view.rst @@ -0,0 +1,60 @@ +.. _colored_view_label: + +Colored View +============ + +The `ColoredView` feature in `deepdiff` provides a human-readable, color-coded JSON output of the +differences between two objects. This feature is particularly useful for visualizing changes in a +clear and intuitive manner. + +- **Color-Coded Differences:** + + - **Added Elements:** Shown in green. + - **Removed Elements:** Shown in red. + - **Changed Elements:** The old value is shown in red, and the new value is shown in green. + +Usage +----- + +To use the `ColoredView`, simply pass the `COLORED_VIEW` option to the `DeepDiff` function: + +.. code-block:: python + + from deepdiff import DeepDiff + from deepdiff.helper import COLORED_VIEW + + t1 = {"name": "John", "age": 30, "scores": [1, 2, 3]} + t2 = {"name": "John", "age": 31, "scores": [1, 2, 4], "new": "value"} + + diff = DeepDiff(t1, t2, view=COLORED_VIEW) + print(diff) + +Or from command line: + +.. code-block:: bash + + deep diff --view colored t1.json t2.json + +Example Output +-------------- + +The output will look something like this: + +.. raw:: html + +
+    {
+      "name": "John",
+      "age": 30 -> 31,
+      "scores": [
+        1,
+        2,
+        3 -> 4
+      ],
+      "address": {
+        "city": "New York" -> "Boston",
+        "zip": "10001"
+      },
+      "new": "value"
+    }
+    
diff --git a/docs/index.rst b/docs/index.rst index c49c0fff..9b0e68d7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -132,6 +132,7 @@ References deephash delta extract + colored_view commandline changelog authors diff --git a/tests/test_colored_view.py b/tests/test_colored_view.py new file mode 100644 index 00000000..08487339 --- /dev/null +++ b/tests/test_colored_view.py @@ -0,0 +1,140 @@ +from deepdiff import DeepDiff +from deepdiff.helper import COLORED_VIEW +from deepdiff.colored_view import RED, GREEN, RESET + +def test_colored_view_basic(): + t1 = { + "name": "John", + "age": 30, + "gender": "male", + "scores": [1, 2, 3], + "address": { + "city": "New York", + "zip": "10001", + }, + } + + t2 = { + "name": "John", + "age": 31, # Changed + "scores": [1, 2, 4], # Changed + "address": { + "city": "Boston", # Changed + "zip": "10001", + }, + "team": "abc", # Added + } + + diff = DeepDiff(t1, t2, view=COLORED_VIEW) + result = str(diff) + + expected = f'''{{ + "name": "John", + "age": {RED}30{RESET} -> {GREEN}31{RESET}, + "scores": [ + 1, + 2, + {RED}3{RESET} -> {GREEN}4{RESET} + ], + "address": {{ + "city": {RED}"New York"{RESET} -> {GREEN}"Boston"{RESET}, + "zip": "10001" + }}, + {GREEN}"team": {GREEN}"abc"{RESET}{RESET}, + {RED}"gender": {RED}"male"{RESET}{RESET} +}}''' + assert result == expected + +def test_colored_view_nested_changes(): + t1 = { + "level1": { + "level2": { + "level3": { + "level4": True + } + } + } + } + + t2 = { + "level1": { + "level2": { + "level3": { + "level4": False + } + } + } + } + + diff = DeepDiff(t1, t2, view=COLORED_VIEW) + result = str(diff) + + expected = f'''{{ + "level1": {{ + "level2": {{ + "level3": {{ + "level4": {RED}true{RESET} -> {GREEN}false{RESET} + }} + }} + }} +}}''' + assert result == expected + +def test_colored_view_list_changes(): + t1 = [1, 2, 3, 4] + t2 = [1, 5, 3, 6] + + diff = DeepDiff(t1, t2, view=COLORED_VIEW) + result = str(diff) + + expected = f'''[ + 1, + {RED}2{RESET} -> {GREEN}5{RESET}, + 3, + {RED}4{RESET} -> {GREEN}6{RESET} +]''' + assert result == expected + +def test_colored_view_list_deletions(): + t1 = [1, 2, 3, 4, 5, 6] + t2 = [2, 4] + + diff = DeepDiff(t1, t2, view=COLORED_VIEW) + result = str(diff) + + expected = f'''[ + {RED}1{RESET}, + 2, + {RED}3{RESET}, + 4, + {RED}5{RESET}, + {RED}6{RESET} +]''' + assert result == expected + +def test_colored_view_with_ignore_order(): + t1 = [1, 2, 3] + t2 = [3, 2, 1] + + diff = DeepDiff(t1, t2, view=COLORED_VIEW, ignore_order=True) + result = str(diff) + + expected = '''[ + 3, + 2, + 1 +]''' + assert result == expected + +def test_colored_view_with_empty_diff(): + t1 = {"a": 1, "b": 2} + t2 = {"a": 1, "b": 2} + + diff = DeepDiff(t1, t2, view=COLORED_VIEW) + result = str(diff) + + expected = '''{ + "a": 1, + "b": 2 +}''' + assert result == expected From eca5dfe9e8442bfeda8805a486e484b672ede2a7 Mon Sep 17 00:00:00 2001 From: Mauricio Villegas <5780272+mauvilsa@users.noreply.github.com> Date: Mon, 19 May 2025 17:10:13 +0200 Subject: [PATCH 02/39] Colored compact view --- deepdiff/colored_view.py | 12 ++- deepdiff/commands.py | 4 +- deepdiff/diff.py | 12 +-- deepdiff/helper.py | 1 + docs/colored_view.rst | 51 +++++++++-- docs/commandline.rst | 9 ++ docs/view.rst | 1 + tests/test_colored_view.py | 173 ++++++++++++++++++++++++++++++++++++- 8 files changed, 249 insertions(+), 14 deletions(-) diff --git a/deepdiff/colored_view.py b/deepdiff/colored_view.py index c946f008..7513df1c 100644 --- a/deepdiff/colored_view.py +++ b/deepdiff/colored_view.py @@ -17,10 +17,11 @@ class ColoredView: """A view that shows JSON with color-coded differences.""" - def __init__(self, t2, tree_results, verbose_level=1): + def __init__(self, t2, tree_results, verbose_level=1, compact=False): self.t2 = t2 self.tree = tree_results self.verbose_level = verbose_level + self.compact = compact self.diff_paths = self._collect_diff_paths() def _collect_diff_paths(self) -> Dict[str, str]: @@ -63,11 +64,16 @@ def _get_path_removed(self, path: str) -> dict: removed[literal_eval(key_suffix[1:-1])] = value[1] return removed + def _has_differences(self, path_prefix: str) -> bool: + """Check if a path prefix has any differences under it.""" + return any(diff_path.startswith(path_prefix + "[") for diff_path in self.diff_paths) + def _colorize_json(self, obj: Any, path: str = 'root', indent: int = 0) -> str: """Recursively colorize JSON based on differences, with pretty-printing.""" INDENT = ' ' current_indent = INDENT * indent next_indent = INDENT * (indent + 1) + if path in self.diff_paths and path not in self._colorize_skip_paths: diff_type, old, new = self.diff_paths[path] if diff_type == 'changed': @@ -77,6 +83,9 @@ def _colorize_json(self, obj: Any, path: str = 'root', indent: int = 0) -> str: elif diff_type == 'removed': return f"{RED}{self._format_value(old)}{RESET}" + if isinstance(obj, (dict, list)) and self.compact and not self._has_differences(path): + return '{...}' if isinstance(obj, dict) else '[...]' + if isinstance(obj, dict): if not obj: return '{}' @@ -99,6 +108,7 @@ def _colorize_json(self, obj: Any, path: str = 'root', indent: int = 0) -> str: removed_map = self._get_path_removed(path) for index in removed_map: self._colorize_skip_paths.add(f"{path}[{index}]") + items = [] index = 0 for value in obj: diff --git a/deepdiff/commands.py b/deepdiff/commands.py index 67aa94d8..359a6efa 100644 --- a/deepdiff/commands.py +++ b/deepdiff/commands.py @@ -54,8 +54,8 @@ def cli(): @click.option('--significant-digits', required=False, default=None, type=int, show_default=True) @click.option('--truncate-datetime', required=False, type=click.Choice(['second', 'minute', 'hour', 'day'], case_sensitive=True), show_default=True, default=None) @click.option('--verbose-level', required=False, default=1, type=click.IntRange(0, 2), show_default=True) +@click.option('--view', required=False, type=click.Choice(['-', 'colored', 'colored_compact'], case_sensitive=True), show_default=True, default="-") @click.option('--debug', is_flag=True, show_default=False) -@click.option('--view', required=False, type=click.Choice(['tree', 'colored'], case_sensitive=True), show_default=True, default="text") def diff( *args, **kwargs ): @@ -113,7 +113,7 @@ def diff( sys.stdout.buffer.write(delta.dumps()) else: try: - if kwargs["view"] == 'colored': + if kwargs["view"] in {'colored', 'colored_compact'}: print(diff) else: print(diff.to_json(indent=2)) diff --git a/deepdiff/diff.py b/deepdiff/diff.py index 50132f37..2135042f 100755 --- a/deepdiff/diff.py +++ b/deepdiff/diff.py @@ -25,7 +25,7 @@ type_is_subclass_of_type_group, type_in_type_group, get_doc, number_to_string, datetime_normalize, KEY_TO_VAL_STR, booleans, np_ndarray, np_floating, get_numpy_ndarray_rows, RepeatedTimer, - TEXT_VIEW, TREE_VIEW, DELTA_VIEW, COLORED_VIEW, detailed__dict__, add_root_to_paths, + TEXT_VIEW, TREE_VIEW, DELTA_VIEW, COLORED_VIEW, COLORED_COMPACT_VIEW, __dict__, add_root_to_paths, np, get_truncate_datetime, dict_, CannotCompare, ENUM_INCLUDE_KEYS, PydanticBaseModel, Opcode, SetOrdered, ipranges) from deepdiff.serialization import SerializationMixin @@ -81,7 +81,7 @@ def _report_progress(_stats, progress_logger, duration): PREVIOUS_DIFF_COUNT = 'PREVIOUS DIFF COUNT' PREVIOUS_DISTANCE_CACHE_HIT_COUNT = 'PREVIOUS DISTANCE CACHE HIT COUNT' CANT_FIND_NUMPY_MSG = 'Unable to import numpy. This must be a bug in DeepDiff since a numpy array is detected.' -INVALID_VIEW_MSG = 'The only valid values for the view parameter are text and tree. But {} was passed.' +INVALID_VIEW_MSG = "view parameter must be one of 'text', 'tree', 'delta', 'colored' or 'colored_compact'. But {} was passed." CUTOFF_RANGE_ERROR_MSG = 'cutoff_distance_for_pairs needs to be a positive float max 1.' VERBOSE_LEVEL_RANGE_MSG = 'verbose_level should be 0, 1, or 2.' PURGE_LEVEL_RANGE_MSG = 'cache_purge_level should be 0, 1, or 2.' @@ -366,7 +366,7 @@ def _group_by_sort_key(x): self.tree.remove_empty_keys() view_results = self._get_view_results(self.view) - if self.view == COLORED_VIEW: + if self.view in {COLORED_VIEW, COLORED_COMPACT_VIEW}: self._colored_view = view_results else: self.update(view_results) @@ -1764,7 +1764,9 @@ def _get_view_results(self, view): elif view == DELTA_VIEW: result = self._to_delta_dict(report_repetition_required=False) elif view == COLORED_VIEW: - result = ColoredView(self.t2, tree_results=self.tree, verbose_level=self.verbose_level) + result = ColoredView(self.t2, tree_results=result, verbose_level=self.verbose_level) + elif view == COLORED_COMPACT_VIEW: + result = ColoredView(self.t2, tree_results=result, verbose_level=self.verbose_level, compact=True) else: raise ValueError(INVALID_VIEW_MSG.format(view)) return result @@ -1906,7 +1908,7 @@ def affected_root_keys(self): return result def __str__(self): - if hasattr(self, '_colored_view') and self.view == COLORED_VIEW: + if hasattr(self, '_colored_view') and self.view in {COLORED_VIEW, COLORED_COMPACT_VIEW}: return str(self._colored_view) return super().__str__() diff --git a/deepdiff/helper.py b/deepdiff/helper.py index 150bbf22..32a2ab19 100644 --- a/deepdiff/helper.py +++ b/deepdiff/helper.py @@ -210,6 +210,7 @@ class IndexedHash(NamedTuple): TEXT_VIEW = 'text' DELTA_VIEW = '_delta' COLORED_VIEW = 'colored' +COLORED_COMPACT_VIEW = 'colored_compact' ENUM_INCLUDE_KEYS = ['__objclass__', 'name', 'value'] diff --git a/docs/colored_view.rst b/docs/colored_view.rst index 06b5041d..16f49ab7 100644 --- a/docs/colored_view.rst +++ b/docs/colored_view.rst @@ -23,8 +23,8 @@ To use the `ColoredView`, simply pass the `COLORED_VIEW` option to the `DeepDiff from deepdiff import DeepDiff from deepdiff.helper import COLORED_VIEW - t1 = {"name": "John", "age": 30, "scores": [1, 2, 3]} - t2 = {"name": "John", "age": 31, "scores": [1, 2, 4], "new": "value"} + t1 = {"name": "John", "age": 30, "scores": [1, 2, 3], "address": {"city": "New York", "zip": "10001"}} + t2 = {"name": "John", "age": 31, "scores": [1, 2, 4], "address": {"city": "Boston", "zip": "10001"}, "new": "value"} diff = DeepDiff(t1, t2, view=COLORED_VIEW) print(diff) @@ -35,9 +35,6 @@ Or from command line: deep diff --view colored t1.json t2.json -Example Output --------------- - The output will look something like this: .. raw:: html @@ -58,3 +55,47 @@ The output will look something like this: "new": "value" } + +Colored Compact View +-------------------- + +For a more concise output, especially with deeply nested objects where many parts are unchanged, +the `ColoredView` with the compact option can be used. This view is similar but collapses +unchanged nested dictionaries to `{...}` and unchanged lists/tuples to `[...]`. To use the compact +option do: + +.. code-block:: python + + from deepdiff import DeepDiff + from deepdiff.helper import COLORED_COMPACT_VIEW + + t1 = {"name": "John", "age": 30, "scores": [1, 2, 3], "address": {"city": "New York", "zip": "10001"}} + t2 = {"name": "John", "age": 31, "scores": [1, 2, 4], "address": {"city": "New York", "zip": "10001"}, "new": "value"} + + diff = DeepDiff(t1, t2, view=COLORED_COMPACT_VIEW) + print(diff) + +Or from command line: + +.. code-block:: bash + + deep diff --view colored_compact t1.json t2.json + + +The output will look something like this: + +.. raw:: html + +
+    {
+      "name": "John",
+      "age": 30 -> 31,
+      "scores": [
+        1,
+        2,
+        3 -> 4
+      ],
+      "address": {...},
+      "new": "value"
+    }
+    
diff --git a/docs/commandline.rst b/docs/commandline.rst index 86652443..94b6aa73 100644 --- a/docs/commandline.rst +++ b/docs/commandline.rst @@ -72,6 +72,9 @@ to get the options: --significant-digits INTEGER --truncate-datetime [second|minute|hour|day] --verbose-level INTEGER RANGE [default: 1] + --view [-|colored|colored_compact] + [default: -] + Format for displaying differences. --help Show this message and exit. @@ -109,6 +112,12 @@ The path is perhaps more readable now: `root['Molotov']['zip']`. It is more clea .. Note:: The parameters in the deep diff commandline are a subset of those in :ref:`deepdiff_module_label` 's Python API. +To output in a specific format, for example the colored compact view (see :doc:`colored_view` for output details): + +.. code-block:: bash + + $ deep diff t1.json t2.json --view colored_compact + .. _deep_grep_command: diff --git a/docs/view.rst b/docs/view.rst index 6343590f..743022ad 100644 --- a/docs/view.rst +++ b/docs/view.rst @@ -9,6 +9,7 @@ You have the options of text view and tree view. The main difference is that the tree view has the capabilities to traverse the objects to see what objects were compared to what other objects. While the view options decide the format of the output that is mostly machine readable, regardless of the view you choose, you can get a more human readable output by using the pretty() method. +DeepDiff also offers other specialized views such as the :doc:`colored_view` (which includes a compact variant) and :doc:`delta` view for specific use cases. .. _text_view_label: diff --git a/tests/test_colored_view.py b/tests/test_colored_view.py index 08487339..883b14cc 100644 --- a/tests/test_colored_view.py +++ b/tests/test_colored_view.py @@ -1,5 +1,5 @@ from deepdiff import DeepDiff -from deepdiff.helper import COLORED_VIEW +from deepdiff.helper import COLORED_VIEW, COLORED_COMPACT_VIEW from deepdiff.colored_view import RED, GREEN, RESET def test_colored_view_basic(): @@ -138,3 +138,174 @@ def test_colored_view_with_empty_diff(): "b": 2 }''' assert result == expected + +def test_compact_view_basic(): + t1 = { + "name": "John", + "age": 30, + "gender": "male", + "scores": [1, 2, 3], + "address": { + "city": "New York", + "zip": "10001", + "details": { + "type": "apartment", + "floor": 5 + } + }, + "hobbies": ["reading", {"sport": "tennis", "level": "advanced"}] + } + + t2 = { + "name": "John", + "age": 31, # Changed + "scores": [1, 2, 4], # Changed + "address": { + "city": "Boston", # Changed + "zip": "10001", + "details": { + "type": "apartment", + "floor": 5 + } + }, + "team": "abc", # Added + "hobbies": ["reading", {"sport": "tennis", "level": "advanced"}] + } + + diff = DeepDiff(t1, t2, view=COLORED_COMPACT_VIEW) + result = str(diff) + + expected = f'''{{ + "name": "John", + "age": {RED}30{RESET} -> {GREEN}31{RESET}, + "scores": [ + 1, + 2, + {RED}3{RESET} -> {GREEN}4{RESET} + ], + "address": {{ + "city": {RED}"New York"{RESET} -> {GREEN}"Boston"{RESET}, + "zip": "10001", + "details": {{...}} + }}, + {GREEN}"team": {GREEN}"abc"{RESET}{RESET}, + "hobbies": [...], + {RED}"gender": {RED}"male"{RESET}{RESET} +}}''' + assert result == expected + +def test_compact_view_nested_changes(): + t1 = { + "level1": { + "unchanged1": { + "deep1": True, + "deep2": [1, 2, 3] + }, + "level2": { + "a": 1, + "b": "test", + "c": [1, 2, 3], + "d": {"x": 1, "y": 2} + }, + "unchanged2": [1, 2, {"a": 1}] + } + } + + t2 = { + "level1": { + "unchanged1": { + "deep1": True, + "deep2": [1, 2, 3] + }, + "level2": { + "a": 2, # Changed + "b": "test", + "c": [1, 2, 4], # Changed + "d": {"x": 1, "y": 3} # Changed + }, + "unchanged2": [1, 2, {"a": 1}] + } + } + + diff = DeepDiff(t1, t2, view=COLORED_COMPACT_VIEW) + result = str(diff) + + expected = f'''{{ + "level1": {{ + "unchanged1": {{...}}, + "level2": {{ + "a": {RED}1{RESET} -> {GREEN}2{RESET}, + "b": "test", + "c": [ + 1, + 2, + {RED}3{RESET} -> {GREEN}4{RESET} + ], + "d": {{ + "x": 1, + "y": {RED}2{RESET} -> {GREEN}3{RESET} + }} + }}, + "unchanged2": [...] + }} +}}''' + assert result == expected + +def test_compact_view_no_changes(): + # Test with dict + t1 = {"a": 1, "b": [1, 2], "c": {"x": True}} + t2 = {"a": 1, "b": [1, 2], "c": {"x": True}} + diff = DeepDiff(t1, t2, view=COLORED_COMPACT_VIEW) + assert str(diff) == "{...}" + + # Test with list + t1 = [1, {"a": 1}, [1, 2]] + t2 = [1, {"a": 1}, [1, 2]] + diff = DeepDiff(t1, t2, view=COLORED_COMPACT_VIEW) + assert str(diff) == "[...]" + +def test_compact_view_list_changes(): + t1 = [1, {"a": 1, "b": {"x": 1, "y": 2}}, [1, 2, {"z": 3}]] + t2 = [1, {"a": 2, "b": {"x": 1, "y": 2}}, [1, 2, {"z": 3}]] + + diff = DeepDiff(t1, t2, view=COLORED_COMPACT_VIEW) + result = str(diff) + + expected = f'''[ + 1, + {{ + "a": {RED}1{RESET} -> {GREEN}2{RESET}, + "b": {{...}} + }}, + [...] +]''' + assert result == expected + +def test_compact_view_primitive_siblings(): + t1 = { + "changed": 1, + "str_sibling": "hello", + "int_sibling": 42, + "bool_sibling": True, + "nested_sibling": {"a": 1, "b": 2} + } + + t2 = { + "changed": 2, + "str_sibling": "hello", + "int_sibling": 42, + "bool_sibling": True, + "nested_sibling": {"a": 1, "b": 2} + } + + diff = DeepDiff(t1, t2, view=COLORED_COMPACT_VIEW) + result = str(diff) + + expected = f'''{{ + "changed": {RED}1{RESET} -> {GREEN}2{RESET}, + "str_sibling": "hello", + "int_sibling": 42, + "bool_sibling": true, + "nested_sibling": {{...}} +}}''' + assert result == expected From 77fa8d3440f7c70f79fb546dc7ca806757622f61 Mon Sep 17 00:00:00 2001 From: Mauricio Villegas <5780272+mauvilsa@users.noreply.github.com> Date: Tue, 20 May 2025 15:39:34 +0200 Subject: [PATCH 03/39] Fix CLI --- deepdiff/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deepdiff/commands.py b/deepdiff/commands.py index 359a6efa..50b0d4d2 100644 --- a/deepdiff/commands.py +++ b/deepdiff/commands.py @@ -54,7 +54,7 @@ def cli(): @click.option('--significant-digits', required=False, default=None, type=int, show_default=True) @click.option('--truncate-datetime', required=False, type=click.Choice(['second', 'minute', 'hour', 'day'], case_sensitive=True), show_default=True, default=None) @click.option('--verbose-level', required=False, default=1, type=click.IntRange(0, 2), show_default=True) -@click.option('--view', required=False, type=click.Choice(['-', 'colored', 'colored_compact'], case_sensitive=True), show_default=True, default="-") +@click.option('--view', required=False, type=click.Choice(['tree', 'colored', 'colored_compact'], case_sensitive=True), show_default=True, default="tree") @click.option('--debug', is_flag=True, show_default=False) def diff( *args, **kwargs From 18a0334d24f55943edc0e8362352793b85654b8a Mon Sep 17 00:00:00 2001 From: Mauricio Villegas <5780272+mauvilsa@users.noreply.github.com> Date: Tue, 20 May 2025 16:21:41 +0200 Subject: [PATCH 04/39] Fix bool evaluation --- deepdiff/diff.py | 5 +++++ tests/test_colored_view.py | 25 +++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/deepdiff/diff.py b/deepdiff/diff.py index 2135042f..8245221b 100755 --- a/deepdiff/diff.py +++ b/deepdiff/diff.py @@ -1907,6 +1907,11 @@ def affected_root_keys(self): result.add(root_key) return result + def __bool__(self): + if hasattr(self, '_colored_view') and self.view in {COLORED_VIEW, COLORED_COMPACT_VIEW}: + return bool(self.tree) # Use the tree for boolean evaluation, not the view + return super().__bool__() + def __str__(self): if hasattr(self, '_colored_view') and self.view in {COLORED_VIEW, COLORED_COMPACT_VIEW}: return str(self._colored_view) diff --git a/tests/test_colored_view.py b/tests/test_colored_view.py index 883b14cc..171b59b6 100644 --- a/tests/test_colored_view.py +++ b/tests/test_colored_view.py @@ -309,3 +309,28 @@ def test_compact_view_primitive_siblings(): "nested_sibling": {{...}} }}''' assert result == expected + + +def test_colored_view_bool_evaluation(): + # Test COLORED_VIEW + # Scenario 1: No differences + t1_no_diff = {"a": 1, "b": 2} + t2_no_diff = {"a": 1, "b": 2} + diff_no_diff_colored = DeepDiff(t1_no_diff, t2_no_diff, view=COLORED_VIEW) + assert not bool(diff_no_diff_colored), "bool(diff) should be False when no diffs (colored view)" + + # Scenario 2: With differences + t1_with_diff = {"a": 1, "b": 2} + t2_with_diff = {"a": 1, "b": 3} + diff_with_diff_colored = DeepDiff(t1_with_diff, t2_with_diff, view=COLORED_VIEW) + assert bool(diff_with_diff_colored), "bool(diff) should be True when diffs exist (colored view)" + + # Test COLORED_COMPACT_VIEW + # Scenario 1: No differences + diff_no_diff_compact = DeepDiff(t1_no_diff, t2_no_diff, view=COLORED_COMPACT_VIEW) + assert not bool(diff_no_diff_compact), "bool(diff) should be False when no diffs (compact view)" + + # Scenario 2: With differences + diff_with_diff_compact = DeepDiff(t1_with_diff, t2_with_diff, view=COLORED_COMPACT_VIEW) + assert bool(diff_with_diff_compact), "bool(diff) should be True when diffs exist (compact view)" + From 82f890c8fcb0c7cad6da7b7235ab556adc9c297b Mon Sep 17 00:00:00 2001 From: Mauricio Villegas <5780272+mauvilsa@users.noreply.github.com> Date: Thu, 22 May 2025 09:39:07 +0200 Subject: [PATCH 05/39] Fixes and more tests --- deepdiff/colored_view.py | 6 ++-- deepdiff/commands.py | 4 ++- deepdiff/diff.py | 11 +++---- tests/test_colored_view.py | 67 ++++++++++++++++++++++++++++++++++++-- 4 files changed, 75 insertions(+), 13 deletions(-) diff --git a/deepdiff/colored_view.py b/deepdiff/colored_view.py index 7513df1c..5b0008c3 100644 --- a/deepdiff/colored_view.py +++ b/deepdiff/colored_view.py @@ -1,9 +1,10 @@ import json +import os from ast import literal_eval from importlib.util import find_spec from typing import Any, Dict -if find_spec("colorama"): +if os.name == "nt" and find_spec("colorama"): import colorama colorama.init() @@ -14,6 +15,7 @@ GREEN = '\033[32m' RESET = '\033[0m' + class ColoredView: """A view that shows JSON with color-coded differences.""" @@ -127,7 +129,7 @@ def _colorize_json(self, obj: Any, path: str = 'root', indent: int = 0) -> str: def __str__(self) -> str: """Return the colorized, pretty-printed JSON string.""" self._colorize_skip_paths = set() - return self._colorize_json(self.t2, indent=0) + return self._colorize_json(self.t2) def __iter__(self): """Make the view iterable by yielding the tree results.""" diff --git a/deepdiff/commands.py b/deepdiff/commands.py index 50b0d4d2..eae0ff44 100644 --- a/deepdiff/commands.py +++ b/deepdiff/commands.py @@ -54,7 +54,7 @@ def cli(): @click.option('--significant-digits', required=False, default=None, type=int, show_default=True) @click.option('--truncate-datetime', required=False, type=click.Choice(['second', 'minute', 'hour', 'day'], case_sensitive=True), show_default=True, default=None) @click.option('--verbose-level', required=False, default=1, type=click.IntRange(0, 2), show_default=True) -@click.option('--view', required=False, type=click.Choice(['tree', 'colored', 'colored_compact'], case_sensitive=True), show_default=True, default="tree") +@click.option('--view', required=False, type=click.Choice(['tree', 'colored', 'colored_compact'], case_sensitive=True)) @click.option('--debug', is_flag=True, show_default=False) def diff( *args, **kwargs @@ -75,6 +75,8 @@ def diff( t2_path = kwargs.pop("t2") t1_extension = t1_path.split('.')[-1] t2_extension = t2_path.split('.')[-1] + if "view" in kwargs and kwargs["view"] is None: + kwargs.pop("view") for name, t_path, t_extension in [('t1', t1_path, t1_extension), ('t2', t2_path, t2_extension)]: try: diff --git a/deepdiff/diff.py b/deepdiff/diff.py index 8245221b..02fc9e35 100755 --- a/deepdiff/diff.py +++ b/deepdiff/diff.py @@ -25,7 +25,8 @@ type_is_subclass_of_type_group, type_in_type_group, get_doc, number_to_string, datetime_normalize, KEY_TO_VAL_STR, booleans, np_ndarray, np_floating, get_numpy_ndarray_rows, RepeatedTimer, - TEXT_VIEW, TREE_VIEW, DELTA_VIEW, COLORED_VIEW, COLORED_COMPACT_VIEW, __dict__, add_root_to_paths, + TEXT_VIEW, TREE_VIEW, DELTA_VIEW, COLORED_VIEW, COLORED_COMPACT_VIEW, + detailed__dict__, add_root_to_paths, np, get_truncate_datetime, dict_, CannotCompare, ENUM_INCLUDE_KEYS, PydanticBaseModel, Opcode, SetOrdered, ipranges) from deepdiff.serialization import SerializationMixin @@ -366,7 +367,8 @@ def _group_by_sort_key(x): self.tree.remove_empty_keys() view_results = self._get_view_results(self.view) - if self.view in {COLORED_VIEW, COLORED_COMPACT_VIEW}: + if isinstance(view_results, ColoredView): + self.update(view_results.tree) self._colored_view = view_results else: self.update(view_results) @@ -1907,11 +1909,6 @@ def affected_root_keys(self): result.add(root_key) return result - def __bool__(self): - if hasattr(self, '_colored_view') and self.view in {COLORED_VIEW, COLORED_COMPACT_VIEW}: - return bool(self.tree) # Use the tree for boolean evaluation, not the view - return super().__bool__() - def __str__(self): if hasattr(self, '_colored_view') and self.view in {COLORED_VIEW, COLORED_COMPACT_VIEW}: return str(self._colored_view) diff --git a/tests/test_colored_view.py b/tests/test_colored_view.py index 171b59b6..360e8d48 100644 --- a/tests/test_colored_view.py +++ b/tests/test_colored_view.py @@ -2,6 +2,7 @@ from deepdiff.helper import COLORED_VIEW, COLORED_COMPACT_VIEW from deepdiff.colored_view import RED, GREEN, RESET + def test_colored_view_basic(): t1 = { "name": "John", @@ -45,6 +46,7 @@ def test_colored_view_basic(): }}''' assert result == expected + def test_colored_view_nested_changes(): t1 = { "level1": { @@ -80,6 +82,7 @@ def test_colored_view_nested_changes(): }}''' assert result == expected + def test_colored_view_list_changes(): t1 = [1, 2, 3, 4] t2 = [1, 5, 3, 6] @@ -95,6 +98,7 @@ def test_colored_view_list_changes(): ]''' assert result == expected + def test_colored_view_list_deletions(): t1 = [1, 2, 3, 4, 5, 6] t2 = [2, 4] @@ -112,7 +116,59 @@ def test_colored_view_list_deletions(): ]''' assert result == expected -def test_colored_view_with_ignore_order(): + +def test_colored_view_list_additions(): + t1 = [2, 4] + t2 = [1, 2, 3, 4, 5] + + diff = DeepDiff(t1, t2, view=COLORED_VIEW) + result = str(diff) + + expected = f'''[ + {GREEN}1{RESET}, + 2, + {GREEN}3{RESET}, + 4, + {GREEN}5{RESET} +]''' + assert result == expected + + +def test_colored_view_list_changes_deletions(): + t1 = [1, 5, 7, 3, 6] + t2 = [1, 2, 3, 4] + + diff = DeepDiff(t1, t2, view=COLORED_VIEW) + result = str(diff) + + expected = f'''[ + 1, + {RED}5{RESET} -> {GREEN}2{RESET}, + {RED}7{RESET}, + 3, + {RED}6{RESET} -> {GREEN}4{RESET} +]''' + assert result == expected + + +def test_colored_view_list_changes_additions(): + t1 = [1, 2, 3, 4] + t2 = [1, 5, 7, 3, 6] + + diff = DeepDiff(t1, t2, view=COLORED_VIEW) + result = str(diff) + + expected = f'''[ + 1, + {RED}2{RESET} -> {GREEN}5{RESET}, + {GREEN}7{RESET}, + 3, + {RED}4{RESET} -> {GREEN}6{RESET} +]''' + assert result == expected + + +def test_colored_view_list_no_changes_with_ignore_order(): t1 = [1, 2, 3] t2 = [3, 2, 1] @@ -126,7 +182,8 @@ def test_colored_view_with_ignore_order(): ]''' assert result == expected -def test_colored_view_with_empty_diff(): + +def test_colored_view_no_changes(): t1 = {"a": 1, "b": 2} t2 = {"a": 1, "b": 2} @@ -139,6 +196,7 @@ def test_colored_view_with_empty_diff(): }''' assert result == expected + def test_compact_view_basic(): t1 = { "name": "John", @@ -194,6 +252,7 @@ def test_compact_view_basic(): }}''' assert result == expected + def test_compact_view_nested_changes(): t1 = { "level1": { @@ -251,6 +310,7 @@ def test_compact_view_nested_changes(): }}''' assert result == expected + def test_compact_view_no_changes(): # Test with dict t1 = {"a": 1, "b": [1, 2], "c": {"x": True}} @@ -264,6 +324,7 @@ def test_compact_view_no_changes(): diff = DeepDiff(t1, t2, view=COLORED_COMPACT_VIEW) assert str(diff) == "[...]" + def test_compact_view_list_changes(): t1 = [1, {"a": 1, "b": {"x": 1, "y": 2}}, [1, 2, {"z": 3}]] t2 = [1, {"a": 2, "b": {"x": 1, "y": 2}}, [1, 2, {"z": 3}]] @@ -281,6 +342,7 @@ def test_compact_view_list_changes(): ]''' assert result == expected + def test_compact_view_primitive_siblings(): t1 = { "changed": 1, @@ -333,4 +395,3 @@ def test_colored_view_bool_evaluation(): # Scenario 2: With differences diff_with_diff_compact = DeepDiff(t1_with_diff, t2_with_diff, view=COLORED_COMPACT_VIEW) assert bool(diff_with_diff_compact), "bool(diff) should be True when diffs exist (compact view)" - From 50617f9af536e97bf7537e16719578fb5be5122c Mon Sep 17 00:00:00 2001 From: Sep Dehpour Date: Tue, 27 May 2025 12:50:33 -0700 Subject: [PATCH 06/39] adding uv --- .gitignore | 1 + MANIFEST.in | 1 + README.md | 10 + uv.lock | 1624 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1636 insertions(+) create mode 100644 uv.lock diff --git a/.gitignore b/.gitignore index 11f27848..7dac60b2 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ __pycache__/ # Distribution / packaging .Python env/ +.venv build/ develop-eggs/ dist/ diff --git a/MANIFEST.in b/MANIFEST.in index 29249f3f..4dfbf568 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -7,6 +7,7 @@ include *.txt include *.sh include pytest.ini include *.py +exclude uv.lock recursive-include docs/ *.rst recursive-include docs/ *.png recursive-include tests *.csv diff --git a/README.md b/README.md index 228f940b..383eae9a 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,16 @@ Please take a look at the [CHANGELOG](CHANGELOG.md) file. :mega: **Please fill out our [fast 5-question survey](https://forms.gle/E6qXexcgjoKnSzjB8)** so that we can learn how & why you use DeepDiff, and what improvements we should make. Thank you! :dancers: +# Local dev + +1. Clone the repo +2. Switch to the dev branch +3. Create your own branch +4. Install dependencies + + - Method 1: Use [`uv`](https://github.com/astral-sh/uv) to install the dependencies: `uv sync --all-extras`. + - Method 2: Use pip: `pip install -e ".[cli,coverage,dev,docs,static,test]"` + # Contribute 1. Please make your PR against the dev branch diff --git a/uv.lock b/uv.lock new file mode 100644 index 00000000..0950548e --- /dev/null +++ b/uv.lock @@ -0,0 +1,1624 @@ +version = 1 +revision = 2 +requires-python = ">=3.9" +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", + "python_full_version < '3.10'", +] + +[[package]] +name = "alabaster" +version = "0.7.16" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/3e/13dd8e5ed9094e734ac430b5d0eb4f2bb001708a8b7856cbf8e084e001ba/alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65", size = 23776, upload-time = "2024-01-10T00:56:10.189Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/34/d4e1c02d3bee589efb5dfa17f88ea08bdb3e3eac12bc475462aec52ed223/alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92", size = 13511, upload-time = "2024-01-10T00:56:08.388Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "argcomplete" +version = "3.6.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/16/0f/861e168fc813c56a78b35f3c30d91c6757d1fd185af1110f1aec784b35d0/argcomplete-3.6.2.tar.gz", hash = "sha256:d0519b1bc867f5f4f4713c41ad0aba73a4a5f007449716b16f385f2166dc6adf", size = 73403, upload-time = "2025-04-03T04:57:03.52Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/da/e42d7a9d8dd33fa775f467e4028a47936da2f01e4b0e561f9ba0d74cb0ca/argcomplete-3.6.2-py3-none-any.whl", hash = "sha256:65b3133a29ad53fb42c48cf5114752c7ab66c1c38544fdf6460f450c09b42591", size = 43708, upload-time = "2025-04-03T04:57:01.591Z" }, +] + +[[package]] +name = "asttokens" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/e7/82da0a03e7ba5141f05cce0d302e6eed121ae055e0456ca228bf693984bc/asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7", size = 61978, upload-time = "2024-11-30T04:30:14.439Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2", size = 26918, upload-time = "2024-11-30T04:30:10.946Z" }, +] + +[[package]] +name = "attrs" +version = "25.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, +] + +[[package]] +name = "babel" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, +] + +[[package]] +name = "bump2version" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/29/2a/688aca6eeebfe8941235be53f4da780c6edee05dbbea5d7abaa3aab6fad2/bump2version-1.0.1.tar.gz", hash = "sha256:762cb2bfad61f4ec8e2bdf452c7c267416f8c70dd9ecb1653fd0bbb01fa936e6", size = 36236, upload-time = "2020-10-07T18:38:40.119Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/e3/fa60c47d7c344533142eb3af0b73234ef8ea3fb2da742ab976b947e717df/bump2version-1.0.1-py2.py3-none-any.whl", hash = "sha256:37f927ea17cde7ae2d7baf832f8e80ce3777624554a653006c9144f8017fe410", size = 22030, upload-time = "2020-10-07T18:38:38.148Z" }, +] + +[[package]] +name = "certifi" +version = "2025.4.26" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705, upload-time = "2025-04-26T02:12:29.51Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618, upload-time = "2025-04-26T02:12:27.662Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/28/9901804da60055b406e1a1c5ba7aac1276fb77f1dde635aabfc7fd84b8ab/charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941", size = 201818, upload-time = "2025-05-02T08:31:46.725Z" }, + { url = "https://files.pythonhosted.org/packages/d9/9b/892a8c8af9110935e5adcbb06d9c6fe741b6bb02608c6513983048ba1a18/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd", size = 144649, upload-time = "2025-05-02T08:31:48.889Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a5/4179abd063ff6414223575e008593861d62abfc22455b5d1a44995b7c101/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6", size = 155045, upload-time = "2025-05-02T08:31:50.757Z" }, + { url = "https://files.pythonhosted.org/packages/3b/95/bc08c7dfeddd26b4be8c8287b9bb055716f31077c8b0ea1cd09553794665/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d", size = 147356, upload-time = "2025-05-02T08:31:52.634Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2d/7a5b635aa65284bf3eab7653e8b4151ab420ecbae918d3e359d1947b4d61/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86", size = 149471, upload-time = "2025-05-02T08:31:56.207Z" }, + { url = "https://files.pythonhosted.org/packages/ae/38/51fc6ac74251fd331a8cfdb7ec57beba8c23fd5493f1050f71c87ef77ed0/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c", size = 151317, upload-time = "2025-05-02T08:31:57.613Z" }, + { url = "https://files.pythonhosted.org/packages/b7/17/edee1e32215ee6e9e46c3e482645b46575a44a2d72c7dfd49e49f60ce6bf/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0", size = 146368, upload-time = "2025-05-02T08:31:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/26/2c/ea3e66f2b5f21fd00b2825c94cafb8c326ea6240cd80a91eb09e4a285830/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef", size = 154491, upload-time = "2025-05-02T08:32:01.219Z" }, + { url = "https://files.pythonhosted.org/packages/52/47/7be7fa972422ad062e909fd62460d45c3ef4c141805b7078dbab15904ff7/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6", size = 157695, upload-time = "2025-05-02T08:32:03.045Z" }, + { url = "https://files.pythonhosted.org/packages/2f/42/9f02c194da282b2b340f28e5fb60762de1151387a36842a92b533685c61e/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366", size = 154849, upload-time = "2025-05-02T08:32:04.651Z" }, + { url = "https://files.pythonhosted.org/packages/67/44/89cacd6628f31fb0b63201a618049be4be2a7435a31b55b5eb1c3674547a/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db", size = 150091, upload-time = "2025-05-02T08:32:06.719Z" }, + { url = "https://files.pythonhosted.org/packages/1f/79/4b8da9f712bc079c0f16b6d67b099b0b8d808c2292c937f267d816ec5ecc/charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a", size = 98445, upload-time = "2025-05-02T08:32:08.66Z" }, + { url = "https://files.pythonhosted.org/packages/7d/d7/96970afb4fb66497a40761cdf7bd4f6fca0fc7bafde3a84f836c1f57a926/charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509", size = 105782, upload-time = "2025-05-02T08:32:10.46Z" }, + { url = "https://files.pythonhosted.org/packages/05/85/4c40d00dcc6284a1c1ad5de5e0996b06f39d8232f1031cd23c2f5c07ee86/charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", size = 198794, upload-time = "2025-05-02T08:32:11.945Z" }, + { url = "https://files.pythonhosted.org/packages/41/d9/7a6c0b9db952598e97e93cbdfcb91bacd89b9b88c7c983250a77c008703c/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", size = 142846, upload-time = "2025-05-02T08:32:13.946Z" }, + { url = "https://files.pythonhosted.org/packages/66/82/a37989cda2ace7e37f36c1a8ed16c58cf48965a79c2142713244bf945c89/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", size = 153350, upload-time = "2025-05-02T08:32:15.873Z" }, + { url = "https://files.pythonhosted.org/packages/df/68/a576b31b694d07b53807269d05ec3f6f1093e9545e8607121995ba7a8313/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", size = 145657, upload-time = "2025-05-02T08:32:17.283Z" }, + { url = "https://files.pythonhosted.org/packages/92/9b/ad67f03d74554bed3aefd56fe836e1623a50780f7c998d00ca128924a499/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f", size = 147260, upload-time = "2025-05-02T08:32:18.807Z" }, + { url = "https://files.pythonhosted.org/packages/a6/e6/8aebae25e328160b20e31a7e9929b1578bbdc7f42e66f46595a432f8539e/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", size = 149164, upload-time = "2025-05-02T08:32:20.333Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f2/b3c2f07dbcc248805f10e67a0262c93308cfa149a4cd3d1fe01f593e5fd2/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", size = 144571, upload-time = "2025-05-02T08:32:21.86Z" }, + { url = "https://files.pythonhosted.org/packages/60/5b/c3f3a94bc345bc211622ea59b4bed9ae63c00920e2e8f11824aa5708e8b7/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", size = 151952, upload-time = "2025-05-02T08:32:23.434Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4d/ff460c8b474122334c2fa394a3f99a04cf11c646da895f81402ae54f5c42/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", size = 155959, upload-time = "2025-05-02T08:32:24.993Z" }, + { url = "https://files.pythonhosted.org/packages/a2/2b/b964c6a2fda88611a1fe3d4c400d39c66a42d6c169c924818c848f922415/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", size = 153030, upload-time = "2025-05-02T08:32:26.435Z" }, + { url = "https://files.pythonhosted.org/packages/59/2e/d3b9811db26a5ebf444bc0fa4f4be5aa6d76fc6e1c0fd537b16c14e849b6/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", size = 148015, upload-time = "2025-05-02T08:32:28.376Z" }, + { url = "https://files.pythonhosted.org/packages/90/07/c5fd7c11eafd561bb51220d600a788f1c8d77c5eef37ee49454cc5c35575/charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", size = 98106, upload-time = "2025-05-02T08:32:30.281Z" }, + { url = "https://files.pythonhosted.org/packages/a8/05/5e33dbef7e2f773d672b6d79f10ec633d4a71cd96db6673625838a4fd532/charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", size = 105402, upload-time = "2025-05-02T08:32:32.191Z" }, + { url = "https://files.pythonhosted.org/packages/d7/a4/37f4d6035c89cac7930395a35cc0f1b872e652eaafb76a6075943754f095/charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", size = 199936, upload-time = "2025-05-02T08:32:33.712Z" }, + { url = "https://files.pythonhosted.org/packages/ee/8a/1a5e33b73e0d9287274f899d967907cd0bf9c343e651755d9307e0dbf2b3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", size = 143790, upload-time = "2025-05-02T08:32:35.768Z" }, + { url = "https://files.pythonhosted.org/packages/66/52/59521f1d8e6ab1482164fa21409c5ef44da3e9f653c13ba71becdd98dec3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", size = 153924, upload-time = "2025-05-02T08:32:37.284Z" }, + { url = "https://files.pythonhosted.org/packages/86/2d/fb55fdf41964ec782febbf33cb64be480a6b8f16ded2dbe8db27a405c09f/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", size = 146626, upload-time = "2025-05-02T08:32:38.803Z" }, + { url = "https://files.pythonhosted.org/packages/8c/73/6ede2ec59bce19b3edf4209d70004253ec5f4e319f9a2e3f2f15601ed5f7/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", size = 148567, upload-time = "2025-05-02T08:32:40.251Z" }, + { url = "https://files.pythonhosted.org/packages/09/14/957d03c6dc343c04904530b6bef4e5efae5ec7d7990a7cbb868e4595ee30/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", size = 150957, upload-time = "2025-05-02T08:32:41.705Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c8/8174d0e5c10ccebdcb1b53cc959591c4c722a3ad92461a273e86b9f5a302/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", size = 145408, upload-time = "2025-05-02T08:32:43.709Z" }, + { url = "https://files.pythonhosted.org/packages/58/aa/8904b84bc8084ac19dc52feb4f5952c6df03ffb460a887b42615ee1382e8/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", size = 153399, upload-time = "2025-05-02T08:32:46.197Z" }, + { url = "https://files.pythonhosted.org/packages/c2/26/89ee1f0e264d201cb65cf054aca6038c03b1a0c6b4ae998070392a3ce605/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", size = 156815, upload-time = "2025-05-02T08:32:48.105Z" }, + { url = "https://files.pythonhosted.org/packages/fd/07/68e95b4b345bad3dbbd3a8681737b4338ff2c9df29856a6d6d23ac4c73cb/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", size = 154537, upload-time = "2025-05-02T08:32:49.719Z" }, + { url = "https://files.pythonhosted.org/packages/77/1a/5eefc0ce04affb98af07bc05f3bac9094513c0e23b0562d64af46a06aae4/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", size = 149565, upload-time = "2025-05-02T08:32:51.404Z" }, + { url = "https://files.pythonhosted.org/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357, upload-time = "2025-05-02T08:32:53.079Z" }, + { url = "https://files.pythonhosted.org/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776, upload-time = "2025-05-02T08:32:54.573Z" }, + { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" }, + { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" }, + { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" }, + { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" }, + { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" }, + { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" }, + { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" }, + { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" }, + { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" }, + { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" }, + { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" }, + { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" }, + { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" }, + { url = "https://files.pythonhosted.org/packages/28/f8/dfb01ff6cc9af38552c69c9027501ff5a5117c4cc18dcd27cb5259fa1888/charset_normalizer-3.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4", size = 201671, upload-time = "2025-05-02T08:34:12.696Z" }, + { url = "https://files.pythonhosted.org/packages/32/fb/74e26ee556a9dbfe3bd264289b67be1e6d616329403036f6507bb9f3f29c/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7", size = 144744, upload-time = "2025-05-02T08:34:14.665Z" }, + { url = "https://files.pythonhosted.org/packages/ad/06/8499ee5aa7addc6f6d72e068691826ff093329fe59891e83b092ae4c851c/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836", size = 154993, upload-time = "2025-05-02T08:34:17.134Z" }, + { url = "https://files.pythonhosted.org/packages/f1/a2/5e4c187680728219254ef107a6949c60ee0e9a916a5dadb148c7ae82459c/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597", size = 147382, upload-time = "2025-05-02T08:34:19.081Z" }, + { url = "https://files.pythonhosted.org/packages/4c/fe/56aca740dda674f0cc1ba1418c4d84534be51f639b5f98f538b332dc9a95/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7", size = 149536, upload-time = "2025-05-02T08:34:21.073Z" }, + { url = "https://files.pythonhosted.org/packages/53/13/db2e7779f892386b589173dd689c1b1e304621c5792046edd8a978cbf9e0/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f", size = 151349, upload-time = "2025-05-02T08:34:23.193Z" }, + { url = "https://files.pythonhosted.org/packages/69/35/e52ab9a276186f729bce7a0638585d2982f50402046e4b0faa5d2c3ef2da/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba", size = 146365, upload-time = "2025-05-02T08:34:25.187Z" }, + { url = "https://files.pythonhosted.org/packages/a6/d8/af7333f732fc2e7635867d56cb7c349c28c7094910c72267586947561b4b/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12", size = 154499, upload-time = "2025-05-02T08:34:27.359Z" }, + { url = "https://files.pythonhosted.org/packages/7a/3d/a5b2e48acef264d71e036ff30bcc49e51bde80219bb628ba3e00cf59baac/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518", size = 157735, upload-time = "2025-05-02T08:34:29.798Z" }, + { url = "https://files.pythonhosted.org/packages/85/d8/23e2c112532a29f3eef374375a8684a4f3b8e784f62b01da931186f43494/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5", size = 154786, upload-time = "2025-05-02T08:34:31.858Z" }, + { url = "https://files.pythonhosted.org/packages/c7/57/93e0169f08ecc20fe82d12254a200dfaceddc1c12a4077bf454ecc597e33/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3", size = 150203, upload-time = "2025-05-02T08:34:33.88Z" }, + { url = "https://files.pythonhosted.org/packages/2c/9d/9bf2b005138e7e060d7ebdec7503d0ef3240141587651f4b445bdf7286c2/charset_normalizer-3.4.2-cp39-cp39-win32.whl", hash = "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471", size = 98436, upload-time = "2025-05-02T08:34:35.907Z" }, + { url = "https://files.pythonhosted.org/packages/6d/24/5849d46cf4311bbf21b424c443b09b459f5b436b1558c04e45dbb7cc478b/charset_normalizer-3.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e", size = 105772, upload-time = "2025-05-02T08:34:37.935Z" }, + { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" }, +] + +[[package]] +name = "click" +version = "8.1.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" }, +] + +[[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 = "colorlog" +version = "6.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d3/7a/359f4d5df2353f26172b3cc39ea32daa39af8de522205f512f458923e677/colorlog-6.9.0.tar.gz", hash = "sha256:bfba54a1b93b94f54e1f4fe48395725a3d92fd2a4af702f6bd70946bdc0c6ac2", size = 16624, upload-time = "2024-10-29T18:34:51.011Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/51/9b208e85196941db2f0654ad0357ca6388ab3ed67efdbfc799f35d1f83aa/colorlog-6.9.0-py3-none-any.whl", hash = "sha256:5906e71acd67cb07a71e779c47c4bcb45fb8c2993eebe9e5adcd6a6f1b283eff", size = 11424, upload-time = "2024-10-29T18:34:49.815Z" }, +] + +[[package]] +name = "coverage" +version = "7.6.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0c/d6/2b53ab3ee99f2262e6f0b8369a43f6d66658eab45510331c0b3d5c8c4272/coverage-7.6.12.tar.gz", hash = "sha256:48cfc4641d95d34766ad41d9573cc0f22a48aa88d22657a1fe01dca0dbae4de2", size = 805941, upload-time = "2025-02-11T14:47:03.797Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/67/81dc41ec8f548c365d04a29f1afd492d3176b372c33e47fa2a45a01dc13a/coverage-7.6.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:704c8c8c6ce6569286ae9622e534b4f5b9759b6f2cd643f1c1a61f666d534fe8", size = 208345, upload-time = "2025-02-11T14:44:51.83Z" }, + { url = "https://files.pythonhosted.org/packages/33/43/17f71676016c8829bde69e24c852fef6bd9ed39f774a245d9ec98f689fa0/coverage-7.6.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ad7525bf0241e5502168ae9c643a2f6c219fa0a283001cee4cf23a9b7da75879", size = 208775, upload-time = "2025-02-11T14:44:54.852Z" }, + { url = "https://files.pythonhosted.org/packages/86/25/c6ff0775f8960e8c0840845b723eed978d22a3cd9babd2b996e4a7c502c6/coverage-7.6.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06097c7abfa611c91edb9e6920264e5be1d6ceb374efb4986f38b09eed4cb2fe", size = 237925, upload-time = "2025-02-11T14:44:56.675Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3d/5f5bd37046243cb9d15fff2c69e498c2f4fe4f9b42a96018d4579ed3506f/coverage-7.6.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:220fa6c0ad7d9caef57f2c8771918324563ef0d8272c94974717c3909664e674", size = 235835, upload-time = "2025-02-11T14:44:59.007Z" }, + { url = "https://files.pythonhosted.org/packages/b5/f1/9e6b75531fe33490b910d251b0bf709142e73a40e4e38a3899e6986fe088/coverage-7.6.12-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3688b99604a24492bcfe1c106278c45586eb819bf66a654d8a9a1433022fb2eb", size = 236966, upload-time = "2025-02-11T14:45:02.744Z" }, + { url = "https://files.pythonhosted.org/packages/4f/bc/aef5a98f9133851bd1aacf130e754063719345d2fb776a117d5a8d516971/coverage-7.6.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d1a987778b9c71da2fc8948e6f2656da6ef68f59298b7e9786849634c35d2c3c", size = 236080, upload-time = "2025-02-11T14:45:05.416Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d0/56b4ab77f9b12aea4d4c11dc11cdcaa7c29130b837eb610639cf3400c9c3/coverage-7.6.12-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:cec6b9ce3bd2b7853d4a4563801292bfee40b030c05a3d29555fd2a8ee9bd68c", size = 234393, upload-time = "2025-02-11T14:45:08.627Z" }, + { url = "https://files.pythonhosted.org/packages/0d/77/28ef95c5d23fe3dd191a0b7d89c82fea2c2d904aef9315daf7c890e96557/coverage-7.6.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ace9048de91293e467b44bce0f0381345078389814ff6e18dbac8fdbf896360e", size = 235536, upload-time = "2025-02-11T14:45:10.313Z" }, + { url = "https://files.pythonhosted.org/packages/29/62/18791d3632ee3ff3f95bc8599115707d05229c72db9539f208bb878a3d88/coverage-7.6.12-cp310-cp310-win32.whl", hash = "sha256:ea31689f05043d520113e0552f039603c4dd71fa4c287b64cb3606140c66f425", size = 211063, upload-time = "2025-02-11T14:45:12.278Z" }, + { url = "https://files.pythonhosted.org/packages/fc/57/b3878006cedfd573c963e5c751b8587154eb10a61cc0f47a84f85c88a355/coverage-7.6.12-cp310-cp310-win_amd64.whl", hash = "sha256:676f92141e3c5492d2a1596d52287d0d963df21bf5e55c8b03075a60e1ddf8aa", size = 211955, upload-time = "2025-02-11T14:45:14.579Z" }, + { url = "https://files.pythonhosted.org/packages/64/2d/da78abbfff98468c91fd63a73cccdfa0e99051676ded8dd36123e3a2d4d5/coverage-7.6.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e18aafdfb3e9ec0d261c942d35bd7c28d031c5855dadb491d2723ba54f4c3015", size = 208464, upload-time = "2025-02-11T14:45:18.314Z" }, + { url = "https://files.pythonhosted.org/packages/31/f2/c269f46c470bdabe83a69e860c80a82e5e76840e9f4bbd7f38f8cebbee2f/coverage-7.6.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66fe626fd7aa5982cdebad23e49e78ef7dbb3e3c2a5960a2b53632f1f703ea45", size = 208893, upload-time = "2025-02-11T14:45:19.881Z" }, + { url = "https://files.pythonhosted.org/packages/47/63/5682bf14d2ce20819998a49c0deadb81e608a59eed64d6bc2191bc8046b9/coverage-7.6.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ef01d70198431719af0b1f5dcbefc557d44a190e749004042927b2a3fed0702", size = 241545, upload-time = "2025-02-11T14:45:22.215Z" }, + { url = "https://files.pythonhosted.org/packages/6a/b6/6b6631f1172d437e11067e1c2edfdb7238b65dff965a12bce3b6d1bf2be2/coverage-7.6.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e92ae5a289a4bc4c0aae710c0948d3c7892e20fd3588224ebe242039573bf0", size = 239230, upload-time = "2025-02-11T14:45:24.864Z" }, + { url = "https://files.pythonhosted.org/packages/c7/01/9cd06cbb1be53e837e16f1b4309f6357e2dfcbdab0dd7cd3b1a50589e4e1/coverage-7.6.12-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e695df2c58ce526eeab11a2e915448d3eb76f75dffe338ea613c1201b33bab2f", size = 241013, upload-time = "2025-02-11T14:45:27.203Z" }, + { url = "https://files.pythonhosted.org/packages/4b/26/56afefc03c30871326e3d99709a70d327ac1f33da383cba108c79bd71563/coverage-7.6.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d74c08e9aaef995f8c4ef6d202dbd219c318450fe2a76da624f2ebb9c8ec5d9f", size = 239750, upload-time = "2025-02-11T14:45:29.577Z" }, + { url = "https://files.pythonhosted.org/packages/dd/ea/88a1ff951ed288f56aa561558ebe380107cf9132facd0b50bced63ba7238/coverage-7.6.12-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e995b3b76ccedc27fe4f477b349b7d64597e53a43fc2961db9d3fbace085d69d", size = 238462, upload-time = "2025-02-11T14:45:31.096Z" }, + { url = "https://files.pythonhosted.org/packages/6e/d4/1d9404566f553728889409eff82151d515fbb46dc92cbd13b5337fa0de8c/coverage-7.6.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b1f097878d74fe51e1ddd1be62d8e3682748875b461232cf4b52ddc6e6db0bba", size = 239307, upload-time = "2025-02-11T14:45:32.713Z" }, + { url = "https://files.pythonhosted.org/packages/12/c1/e453d3b794cde1e232ee8ac1d194fde8e2ba329c18bbf1b93f6f5eef606b/coverage-7.6.12-cp311-cp311-win32.whl", hash = "sha256:1f7ffa05da41754e20512202c866d0ebfc440bba3b0ed15133070e20bf5aeb5f", size = 211117, upload-time = "2025-02-11T14:45:34.228Z" }, + { url = "https://files.pythonhosted.org/packages/d5/db/829185120c1686fa297294f8fcd23e0422f71070bf85ef1cc1a72ecb2930/coverage-7.6.12-cp311-cp311-win_amd64.whl", hash = "sha256:e216c5c45f89ef8971373fd1c5d8d1164b81f7f5f06bbf23c37e7908d19e8558", size = 212019, upload-time = "2025-02-11T14:45:35.724Z" }, + { url = "https://files.pythonhosted.org/packages/e2/7f/4af2ed1d06ce6bee7eafc03b2ef748b14132b0bdae04388e451e4b2c529b/coverage-7.6.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b172f8e030e8ef247b3104902cc671e20df80163b60a203653150d2fc204d1ad", size = 208645, upload-time = "2025-02-11T14:45:37.95Z" }, + { url = "https://files.pythonhosted.org/packages/dc/60/d19df912989117caa95123524d26fc973f56dc14aecdec5ccd7d0084e131/coverage-7.6.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:641dfe0ab73deb7069fb972d4d9725bf11c239c309ce694dd50b1473c0f641c3", size = 208898, upload-time = "2025-02-11T14:45:40.27Z" }, + { url = "https://files.pythonhosted.org/packages/bd/10/fecabcf438ba676f706bf90186ccf6ff9f6158cc494286965c76e58742fa/coverage-7.6.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e549f54ac5f301e8e04c569dfdb907f7be71b06b88b5063ce9d6953d2d58574", size = 242987, upload-time = "2025-02-11T14:45:43.982Z" }, + { url = "https://files.pythonhosted.org/packages/4c/53/4e208440389e8ea936f5f2b0762dcd4cb03281a7722def8e2bf9dc9c3d68/coverage-7.6.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:959244a17184515f8c52dcb65fb662808767c0bd233c1d8a166e7cf74c9ea985", size = 239881, upload-time = "2025-02-11T14:45:45.537Z" }, + { url = "https://files.pythonhosted.org/packages/c4/47/2ba744af8d2f0caa1f17e7746147e34dfc5f811fb65fc153153722d58835/coverage-7.6.12-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bda1c5f347550c359f841d6614fb8ca42ae5cb0b74d39f8a1e204815ebe25750", size = 242142, upload-time = "2025-02-11T14:45:47.069Z" }, + { url = "https://files.pythonhosted.org/packages/e9/90/df726af8ee74d92ee7e3bf113bf101ea4315d71508952bd21abc3fae471e/coverage-7.6.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1ceeb90c3eda1f2d8c4c578c14167dbd8c674ecd7d38e45647543f19839dd6ea", size = 241437, upload-time = "2025-02-11T14:45:48.602Z" }, + { url = "https://files.pythonhosted.org/packages/f6/af/995263fd04ae5f9cf12521150295bf03b6ba940d0aea97953bb4a6db3e2b/coverage-7.6.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f16f44025c06792e0fb09571ae454bcc7a3ec75eeb3c36b025eccf501b1a4c3", size = 239724, upload-time = "2025-02-11T14:45:51.333Z" }, + { url = "https://files.pythonhosted.org/packages/1c/8e/5bb04f0318805e190984c6ce106b4c3968a9562a400180e549855d8211bd/coverage-7.6.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b076e625396e787448d27a411aefff867db2bffac8ed04e8f7056b07024eed5a", size = 241329, upload-time = "2025-02-11T14:45:53.19Z" }, + { url = "https://files.pythonhosted.org/packages/9e/9d/fa04d9e6c3f6459f4e0b231925277cfc33d72dfab7fa19c312c03e59da99/coverage-7.6.12-cp312-cp312-win32.whl", hash = "sha256:00b2086892cf06c7c2d74983c9595dc511acca00665480b3ddff749ec4fb2a95", size = 211289, upload-time = "2025-02-11T14:45:54.74Z" }, + { url = "https://files.pythonhosted.org/packages/53/40/53c7ffe3c0c3fff4d708bc99e65f3d78c129110d6629736faf2dbd60ad57/coverage-7.6.12-cp312-cp312-win_amd64.whl", hash = "sha256:7ae6eabf519bc7871ce117fb18bf14e0e343eeb96c377667e3e5dd12095e0288", size = 212079, upload-time = "2025-02-11T14:45:57.22Z" }, + { url = "https://files.pythonhosted.org/packages/76/89/1adf3e634753c0de3dad2f02aac1e73dba58bc5a3a914ac94a25b2ef418f/coverage-7.6.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:488c27b3db0ebee97a830e6b5a3ea930c4a6e2c07f27a5e67e1b3532e76b9ef1", size = 208673, upload-time = "2025-02-11T14:45:59.618Z" }, + { url = "https://files.pythonhosted.org/packages/ce/64/92a4e239d64d798535c5b45baac6b891c205a8a2e7c9cc8590ad386693dc/coverage-7.6.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d1095bbee1851269f79fd8e0c9b5544e4c00c0c24965e66d8cba2eb5bb535fd", size = 208945, upload-time = "2025-02-11T14:46:01.869Z" }, + { url = "https://files.pythonhosted.org/packages/b4/d0/4596a3ef3bca20a94539c9b1e10fd250225d1dec57ea78b0867a1cf9742e/coverage-7.6.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0533adc29adf6a69c1baa88c3d7dbcaadcffa21afbed3ca7a225a440e4744bf9", size = 242484, upload-time = "2025-02-11T14:46:03.527Z" }, + { url = "https://files.pythonhosted.org/packages/1c/ef/6fd0d344695af6718a38d0861408af48a709327335486a7ad7e85936dc6e/coverage-7.6.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53c56358d470fa507a2b6e67a68fd002364d23c83741dbc4c2e0680d80ca227e", size = 239525, upload-time = "2025-02-11T14:46:05.973Z" }, + { url = "https://files.pythonhosted.org/packages/0c/4b/373be2be7dd42f2bcd6964059fd8fa307d265a29d2b9bcf1d044bcc156ed/coverage-7.6.12-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64cbb1a3027c79ca6310bf101014614f6e6e18c226474606cf725238cf5bc2d4", size = 241545, upload-time = "2025-02-11T14:46:07.79Z" }, + { url = "https://files.pythonhosted.org/packages/a6/7d/0e83cc2673a7790650851ee92f72a343827ecaaea07960587c8f442b5cd3/coverage-7.6.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:79cac3390bfa9836bb795be377395f28410811c9066bc4eefd8015258a7578c6", size = 241179, upload-time = "2025-02-11T14:46:11.853Z" }, + { url = "https://files.pythonhosted.org/packages/ff/8c/566ea92ce2bb7627b0900124e24a99f9244b6c8c92d09ff9f7633eb7c3c8/coverage-7.6.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9b148068e881faa26d878ff63e79650e208e95cf1c22bd3f77c3ca7b1d9821a3", size = 239288, upload-time = "2025-02-11T14:46:13.411Z" }, + { url = "https://files.pythonhosted.org/packages/7d/e4/869a138e50b622f796782d642c15fb5f25a5870c6d0059a663667a201638/coverage-7.6.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8bec2ac5da793c2685ce5319ca9bcf4eee683b8a1679051f8e6ec04c4f2fd7dc", size = 241032, upload-time = "2025-02-11T14:46:15.005Z" }, + { url = "https://files.pythonhosted.org/packages/ae/28/a52ff5d62a9f9e9fe9c4f17759b98632edd3a3489fce70154c7d66054dd3/coverage-7.6.12-cp313-cp313-win32.whl", hash = "sha256:200e10beb6ddd7c3ded322a4186313d5ca9e63e33d8fab4faa67ef46d3460af3", size = 211315, upload-time = "2025-02-11T14:46:16.638Z" }, + { url = "https://files.pythonhosted.org/packages/bc/17/ab849b7429a639f9722fa5628364c28d675c7ff37ebc3268fe9840dda13c/coverage-7.6.12-cp313-cp313-win_amd64.whl", hash = "sha256:2b996819ced9f7dbb812c701485d58f261bef08f9b85304d41219b1496b591ef", size = 212099, upload-time = "2025-02-11T14:46:18.268Z" }, + { url = "https://files.pythonhosted.org/packages/d2/1c/b9965bf23e171d98505eb5eb4fb4d05c44efd256f2e0f19ad1ba8c3f54b0/coverage-7.6.12-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:299cf973a7abff87a30609879c10df0b3bfc33d021e1adabc29138a48888841e", size = 209511, upload-time = "2025-02-11T14:46:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/57/b3/119c201d3b692d5e17784fee876a9a78e1b3051327de2709392962877ca8/coverage-7.6.12-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4b467a8c56974bf06e543e69ad803c6865249d7a5ccf6980457ed2bc50312703", size = 209729, upload-time = "2025-02-11T14:46:22.258Z" }, + { url = "https://files.pythonhosted.org/packages/52/4e/a7feb5a56b266304bc59f872ea07b728e14d5a64f1ad3a2cc01a3259c965/coverage-7.6.12-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2458f275944db8129f95d91aee32c828a408481ecde3b30af31d552c2ce284a0", size = 253988, upload-time = "2025-02-11T14:46:23.999Z" }, + { url = "https://files.pythonhosted.org/packages/65/19/069fec4d6908d0dae98126aa7ad08ce5130a6decc8509da7740d36e8e8d2/coverage-7.6.12-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a9d8be07fb0832636a0f72b80d2a652fe665e80e720301fb22b191c3434d924", size = 249697, upload-time = "2025-02-11T14:46:25.617Z" }, + { url = "https://files.pythonhosted.org/packages/1c/da/5b19f09ba39df7c55f77820736bf17bbe2416bbf5216a3100ac019e15839/coverage-7.6.12-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d47376a4f445e9743f6c83291e60adb1b127607a3618e3185bbc8091f0467b", size = 252033, upload-time = "2025-02-11T14:46:28.069Z" }, + { url = "https://files.pythonhosted.org/packages/1e/89/4c2750df7f80a7872267f7c5fe497c69d45f688f7b3afe1297e52e33f791/coverage-7.6.12-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b95574d06aa9d2bd6e5cc35a5bbe35696342c96760b69dc4287dbd5abd4ad51d", size = 251535, upload-time = "2025-02-11T14:46:29.818Z" }, + { url = "https://files.pythonhosted.org/packages/78/3b/6d3ae3c1cc05f1b0460c51e6f6dcf567598cbd7c6121e5ad06643974703c/coverage-7.6.12-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:ecea0c38c9079570163d663c0433a9af4094a60aafdca491c6a3d248c7432827", size = 249192, upload-time = "2025-02-11T14:46:31.563Z" }, + { url = "https://files.pythonhosted.org/packages/6e/8e/c14a79f535ce41af7d436bbad0d3d90c43d9e38ec409b4770c894031422e/coverage-7.6.12-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2251fabcfee0a55a8578a9d29cecfee5f2de02f11530e7d5c5a05859aa85aee9", size = 250627, upload-time = "2025-02-11T14:46:33.145Z" }, + { url = "https://files.pythonhosted.org/packages/cb/79/b7cee656cfb17a7f2c1b9c3cee03dd5d8000ca299ad4038ba64b61a9b044/coverage-7.6.12-cp313-cp313t-win32.whl", hash = "sha256:eb5507795caabd9b2ae3f1adc95f67b1104971c22c624bb354232d65c4fc90b3", size = 212033, upload-time = "2025-02-11T14:46:35.79Z" }, + { url = "https://files.pythonhosted.org/packages/b6/c3/f7aaa3813f1fa9a4228175a7bd368199659d392897e184435a3b66408dd3/coverage-7.6.12-cp313-cp313t-win_amd64.whl", hash = "sha256:f60a297c3987c6c02ffb29effc70eadcbb412fe76947d394a1091a3615948e2f", size = 213240, upload-time = "2025-02-11T14:46:38.119Z" }, + { url = "https://files.pythonhosted.org/packages/6c/eb/cf062b1c3dbdcafd64a2a154beea2e4aa8e9886c34e41f53fa04925c8b35/coverage-7.6.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e7575ab65ca8399c8c4f9a7d61bbd2d204c8b8e447aab9d355682205c9dd948d", size = 208343, upload-time = "2025-02-11T14:46:39.744Z" }, + { url = "https://files.pythonhosted.org/packages/95/42/4ebad0ab065228e29869a060644712ab1b0821d8c29bfefa20c2118c9e19/coverage-7.6.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8161d9fbc7e9fe2326de89cd0abb9f3599bccc1287db0aba285cb68d204ce929", size = 208769, upload-time = "2025-02-11T14:46:41.548Z" }, + { url = "https://files.pythonhosted.org/packages/44/9f/421e84f7f9455eca85ff85546f26cbc144034bb2587e08bfc214dd6e9c8f/coverage-7.6.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a1e465f398c713f1b212400b4e79a09829cd42aebd360362cd89c5bdc44eb87", size = 237553, upload-time = "2025-02-11T14:46:44.96Z" }, + { url = "https://files.pythonhosted.org/packages/c9/c4/a2c4f274bcb711ed5db2ccc1b851ca1c45f35ed6077aec9d6c61845d80e3/coverage-7.6.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f25d8b92a4e31ff1bd873654ec367ae811b3a943583e05432ea29264782dc32c", size = 235473, upload-time = "2025-02-11T14:46:47.023Z" }, + { url = "https://files.pythonhosted.org/packages/e0/10/a3d317e38e5627b06debe861d6c511b1611dd9dc0e2a47afbe6257ffd341/coverage-7.6.12-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a936309a65cc5ca80fa9f20a442ff9e2d06927ec9a4f54bcba9c14c066323f2", size = 236575, upload-time = "2025-02-11T14:46:48.697Z" }, + { url = "https://files.pythonhosted.org/packages/4d/49/51cd991b56257d2e07e3d5cb053411e9de5b0f4e98047167ec05e4e19b55/coverage-7.6.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aa6f302a3a0b5f240ee201297fff0bbfe2fa0d415a94aeb257d8b461032389bd", size = 235690, upload-time = "2025-02-11T14:46:51.262Z" }, + { url = "https://files.pythonhosted.org/packages/f7/87/631e5883fe0a80683a1f20dadbd0f99b79e17a9d8ea9aff3a9b4cfe50b93/coverage-7.6.12-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f973643ef532d4f9be71dd88cf7588936685fdb576d93a79fe9f65bc337d9d73", size = 234040, upload-time = "2025-02-11T14:46:52.962Z" }, + { url = "https://files.pythonhosted.org/packages/7c/34/edd03f6933f766ec97dddd178a7295855f8207bb708dbac03777107ace5b/coverage-7.6.12-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:78f5243bb6b1060aed6213d5107744c19f9571ec76d54c99cc15938eb69e0e86", size = 235048, upload-time = "2025-02-11T14:46:54.65Z" }, + { url = "https://files.pythonhosted.org/packages/ee/1e/d45045b7d3012fe518c617a57b9f9396cdaebe6455f1b404858b32c38cdd/coverage-7.6.12-cp39-cp39-win32.whl", hash = "sha256:69e62c5034291c845fc4df7f8155e8544178b6c774f97a99e2734b05eb5bed31", size = 211085, upload-time = "2025-02-11T14:46:56.233Z" }, + { url = "https://files.pythonhosted.org/packages/df/ea/086cb06af14a84fe773b86aa140892006a906c5ec947e609ceb6a93f6257/coverage-7.6.12-cp39-cp39-win_amd64.whl", hash = "sha256:b01a840ecc25dce235ae4c1b6a0daefb2a203dba0e6e980637ee9c2f6ee0df57", size = 211965, upload-time = "2025-02-11T14:46:57.84Z" }, + { url = "https://files.pythonhosted.org/packages/7a/7f/05818c62c7afe75df11e0233bd670948d68b36cdbf2a339a095bc02624a8/coverage-7.6.12-pp39.pp310-none-any.whl", hash = "sha256:7e39e845c4d764208e7b8f6a21c541ade741e2c41afabdfa1caa28687a3c98cf", size = 200558, upload-time = "2025-02-11T14:47:00.292Z" }, + { url = "https://files.pythonhosted.org/packages/fb/b2/f655700e1024dec98b10ebaafd0cedbc25e40e4abe62a3c8e2ceef4f8f0a/coverage-7.6.12-py3-none-any.whl", hash = "sha256:eb8668cfbc279a536c633137deeb9435d2962caec279c3f8cf8b91fff6ff8953", size = 200552, upload-time = "2025-02-11T14:47:01.999Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11'" }, +] + +[[package]] +name = "decorator" +version = "5.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, +] + +[[package]] +name = "deepdiff" +version = "8.5.0" +source = { editable = "." } +dependencies = [ + { name = "orderly-set" }, +] + +[package.optional-dependencies] +cli = [ + { name = "click" }, + { name = "pyyaml" }, +] +coverage = [ + { name = "coverage" }, +] +dev = [ + { name = "bump2version" }, + { name = "ipdb" }, + { name = "jsonpickle" }, + { name = "nox" }, + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "orjson" }, + { name = "pandas" }, + { name = "polars" }, + { name = "python-dateutil" }, + { name = "tomli" }, + { name = "tomli-w" }, +] +docs = [ + { name = "sphinx" }, + { name = "sphinx-sitemap" }, + { name = "sphinxemoji" }, +] +optimize = [ + { name = "orjson" }, +] +static = [ + { name = "flake8" }, + { name = "flake8-pyproject" }, + { name = "pydantic" }, +] +test = [ + { name = "pytest" }, + { name = "pytest-benchmark" }, + { name = "pytest-cov" }, + { name = "python-dotenv" }, +] + +[package.metadata] +requires-dist = [ + { name = "bump2version", marker = "extra == 'dev'", specifier = "~=1.0.0" }, + { name = "click", marker = "extra == 'cli'", specifier = "~=8.1.0" }, + { name = "coverage", marker = "extra == 'coverage'", specifier = "~=7.6.0" }, + { name = "flake8", marker = "extra == 'static'", specifier = "~=7.1.0" }, + { name = "flake8-pyproject", marker = "extra == 'static'", specifier = "~=1.2.3" }, + { name = "ipdb", marker = "extra == 'dev'", specifier = "~=0.13.0" }, + { name = "jsonpickle", marker = "extra == 'dev'", specifier = "~=4.0.0" }, + { name = "nox", marker = "extra == 'dev'", specifier = "==2025.5.1" }, + { name = "numpy", marker = "python_full_version >= '3.10' and extra == 'dev'", specifier = "~=2.2.0" }, + { name = "numpy", marker = "python_full_version < '3.10' and extra == 'dev'", specifier = "~=2.0" }, + { name = "orderly-set", specifier = ">=5.4.1,<6" }, + { name = "orjson", marker = "extra == 'dev'", specifier = "~=3.10.0" }, + { name = "orjson", marker = "extra == 'optimize'" }, + { name = "pandas", marker = "extra == 'dev'", specifier = "~=2.2.0" }, + { name = "polars", marker = "extra == 'dev'", specifier = "~=1.21.0" }, + { name = "pydantic", marker = "extra == 'static'", specifier = "~=2.10.0" }, + { name = "pytest", marker = "extra == 'test'", specifier = "~=8.3.0" }, + { name = "pytest-benchmark", marker = "extra == 'test'", specifier = "~=5.1.0" }, + { name = "pytest-cov", marker = "extra == 'test'", specifier = "~=6.0.0" }, + { name = "python-dateutil", marker = "extra == 'dev'", specifier = "~=2.9.0" }, + { name = "python-dotenv", marker = "extra == 'test'", specifier = "~=1.0.0" }, + { name = "pyyaml", marker = "extra == 'cli'", specifier = "~=6.0.0" }, + { name = "sphinx", marker = "extra == 'docs'", specifier = "~=6.2.0" }, + { name = "sphinx-sitemap", marker = "extra == 'docs'", specifier = "~=2.6.0" }, + { name = "sphinxemoji", marker = "extra == 'docs'", specifier = "~=0.3.0" }, + { name = "tomli", marker = "extra == 'dev'", specifier = "~=2.2.0" }, + { name = "tomli-w", marker = "extra == 'dev'", specifier = "~=1.2.0" }, +] +provides-extras = ["coverage", "cli", "dev", "docs", "static", "test", "optimize"] + +[[package]] +name = "dependency-groups" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/55/f054de99871e7beb81935dea8a10b90cd5ce42122b1c3081d5282fdb3621/dependency_groups-1.3.1.tar.gz", hash = "sha256:78078301090517fd938c19f64a53ce98c32834dfe0dee6b88004a569a6adfefd", size = 10093, upload-time = "2025-05-02T00:34:29.452Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/c7/d1ec24fb280caa5a79b6b950db565dab30210a66259d17d5bb2b3a9f878d/dependency_groups-1.3.1-py3-none-any.whl", hash = "sha256:51aeaa0dfad72430fcfb7bcdbefbd75f3792e5919563077f30bc0d73f4493030", size = 8664, upload-time = "2025-05-02T00:34:27.085Z" }, +] + +[[package]] +name = "distlib" +version = "0.3.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923, upload-time = "2024-10-09T18:35:47.551Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973, upload-time = "2024-10-09T18:35:44.272Z" }, +] + +[[package]] +name = "docutils" +version = "0.19" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/330ea8d383eb2ce973df34d1239b3b21e91cd8c865d21ff82902d952f91f/docutils-0.19.tar.gz", hash = "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6", size = 2056383, upload-time = "2022-07-05T20:17:31.045Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/69/e391bd51bc08ed9141ecd899a0ddb61ab6465309f1eb470905c0c8868081/docutils-0.19-py3-none-any.whl", hash = "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc", size = 570472, upload-time = "2022-07-05T20:17:26.388Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +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 = "executing" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/91/50/a9d80c47ff289c611ff12e63f7c5d13942c65d68125160cefd768c73e6e4/executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755", size = 978693, upload-time = "2025-01-22T15:41:29.403Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/8f/c4d9bafc34ad7ad5d8dc16dd1347ee0e507a52c3adb6bfa8887e1c6a26ba/executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa", size = 26702, upload-time = "2025-01-22T15:41:25.929Z" }, +] + +[[package]] +name = "filelock" +version = "3.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075, upload-time = "2025-03-14T07:11:40.47Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215, upload-time = "2025-03-14T07:11:39.145Z" }, +] + +[[package]] +name = "flake8" +version = "7.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mccabe" }, + { name = "pycodestyle" }, + { name = "pyflakes" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/16/3f2a0bb700ad65ac9663262905a025917c020a3f92f014d2ba8964b4602c/flake8-7.1.2.tar.gz", hash = "sha256:c586ffd0b41540951ae41af572e6790dbd49fc12b3aa2541685d253d9bd504bd", size = 48119, upload-time = "2025-02-16T18:45:44.296Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/f8/08d37b2cd89da306e3520bd27f8a85692122b42b56c0c2c3784ff09c022f/flake8-7.1.2-py2.py3-none-any.whl", hash = "sha256:1cbc62e65536f65e6d754dfe6f1bada7f5cf392d6f5db3c2b85892466c3e7c1a", size = 57745, upload-time = "2025-02-16T18:45:42.351Z" }, +] + +[[package]] +name = "flake8-pyproject" +version = "1.2.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flake8" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/1d/635e86f9f3a96b7ea9e9f19b5efe17a987e765c39ca496e4a893bb999112/flake8_pyproject-1.2.3-py3-none-any.whl", hash = "sha256:6249fe53545205af5e76837644dc80b4c10037e73a0e5db87ff562d75fb5bd4a", size = 4756, upload-time = "2023-03-21T20:51:38.911Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "imagesize" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026, upload-time = "2022-07-01T12:21:05.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769, upload-time = "2022-07-01T12:21:02.467Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "8.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656, upload-time = "2025-04-27T15:29:00.214Z" }, +] + +[[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 = "ipdb" +version = "0.13.13" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "decorator" }, + { name = "ipython", version = "8.18.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "ipython", version = "8.36.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "ipython", version = "9.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/1b/7e07e7b752017f7693a0f4d41c13e5ca29ce8cbcfdcc1fd6c4ad8c0a27a0/ipdb-0.13.13.tar.gz", hash = "sha256:e3ac6018ef05126d442af680aad863006ec19d02290561ac88b8b1c0b0cfc726", size = 17042, upload-time = "2023-03-09T15:40:57.487Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/4c/b075da0092003d9a55cf2ecc1cae9384a1ca4f650d51b00fc59875fe76f6/ipdb-0.13.13-py3-none-any.whl", hash = "sha256:45529994741c4ab6d2388bfa5d7b725c2cf7fe9deffabdb8a6113aa5ed449ed4", size = 12130, upload-time = "2023-03-09T15:40:55.021Z" }, +] + +[[package]] +name = "ipython" +version = "8.18.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, + { name = "decorator", marker = "python_full_version < '3.10'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.10'" }, + { name = "jedi", marker = "python_full_version < '3.10'" }, + { name = "matplotlib-inline", marker = "python_full_version < '3.10'" }, + { name = "pexpect", marker = "python_full_version < '3.10' and sys_platform != 'win32'" }, + { name = "prompt-toolkit", marker = "python_full_version < '3.10'" }, + { name = "pygments", marker = "python_full_version < '3.10'" }, + { name = "stack-data", marker = "python_full_version < '3.10'" }, + { name = "traitlets", marker = "python_full_version < '3.10'" }, + { name = "typing-extensions", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/b9/3ba6c45a6df813c09a48bac313c22ff83efa26cbb55011218d925a46e2ad/ipython-8.18.1.tar.gz", hash = "sha256:ca6f079bb33457c66e233e4580ebfc4128855b4cf6370dddd73842a9563e8a27", size = 5486330, upload-time = "2023-11-27T09:58:34.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/6b/d9fdcdef2eb6a23f391251fde8781c38d42acd82abe84d054cb74f7863b0/ipython-8.18.1-py3-none-any.whl", hash = "sha256:e8267419d72d81955ec1177f8a29aaa90ac80ad647499201119e2f05e99aa397", size = 808161, upload-time = "2023-11-27T09:58:30.538Z" }, +] + +[[package]] +name = "ipython" +version = "8.36.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version == '3.10.*' and sys_platform == 'win32'" }, + { name = "decorator", marker = "python_full_version == '3.10.*'" }, + { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, + { name = "jedi", marker = "python_full_version == '3.10.*'" }, + { name = "matplotlib-inline", marker = "python_full_version == '3.10.*'" }, + { name = "pexpect", marker = "python_full_version == '3.10.*' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit", marker = "python_full_version == '3.10.*'" }, + { name = "pygments", marker = "python_full_version == '3.10.*'" }, + { name = "stack-data", marker = "python_full_version == '3.10.*'" }, + { name = "traitlets", marker = "python_full_version == '3.10.*'" }, + { name = "typing-extensions", marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a2/9f/d9a73710df947b7804bd9d93509463fb3a89e0ddc99c9fcc67279cddbeb6/ipython-8.36.0.tar.gz", hash = "sha256:24658e9fe5c5c819455043235ba59cfffded4a35936eefceceab6b192f7092ff", size = 5604997, upload-time = "2025-04-25T18:03:38.031Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/d7/c1c9f371790b3a181e343c4815a361e5a0cc7d90ef6642d64ba5d05de289/ipython-8.36.0-py3-none-any.whl", hash = "sha256:12b913914d010dcffa2711505ec8be4bf0180742d97f1e5175e51f22086428c1", size = 831074, upload-time = "2025-04-25T18:03:34.951Z" }, +] + +[[package]] +name = "ipython" +version = "9.2.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" }, + { name = "decorator", marker = "python_full_version >= '3.11'" }, + { name = "ipython-pygments-lexers", marker = "python_full_version >= '3.11'" }, + { name = "jedi", marker = "python_full_version >= '3.11'" }, + { name = "matplotlib-inline", marker = "python_full_version >= '3.11'" }, + { name = "pexpect", marker = "python_full_version >= '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit", marker = "python_full_version >= '3.11'" }, + { name = "pygments", marker = "python_full_version >= '3.11'" }, + { name = "stack-data", marker = "python_full_version >= '3.11'" }, + { name = "traitlets", marker = "python_full_version >= '3.11'" }, + { name = "typing-extensions", marker = "python_full_version == '3.11.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/02/63a84444a7409b3c0acd1de9ffe524660e0e5d82ee473e78b45e5bfb64a4/ipython-9.2.0.tar.gz", hash = "sha256:62a9373dbc12f28f9feaf4700d052195bf89806279fc8ca11f3f54017d04751b", size = 4424394, upload-time = "2025-04-25T17:55:40.498Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/ce/5e897ee51b7d26ab4e47e5105e7368d40ce6cfae2367acdf3165396d50be/ipython-9.2.0-py3-none-any.whl", hash = "sha256:fef5e33c4a1ae0759e0bba5917c9db4eb8c53fee917b6a526bd973e1ca5159f6", size = 604277, upload-time = "2025-04-25T17:55:37.625Z" }, +] + +[[package]] +name = "ipython-pygments-lexers" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments", marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" }, +] + +[[package]] +name = "jedi" +version = "0.19.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287, upload-time = "2024-11-11T01:41:42.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "jsonpickle" +version = "4.0.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/33/4bda317ab294722fcdfff8f63aab74af9fda3675a4652d984a101aa7587e/jsonpickle-4.0.5.tar.gz", hash = "sha256:f299818b39367c361b3f26bdba827d4249ab5d383cd93144d0f94b5417aacb35", size = 315661, upload-time = "2025-03-29T19:22:56.92Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/1b/0e79cf115e0f54f1e8f56effb6ffd2ef8f92e9c324d692ede660067f1bfe/jsonpickle-4.0.5-py3-none-any.whl", hash = "sha256:b4ac7d0a75ddcdfd93445737f1d36ff28768690d43e54bf5d0ddb1d915e580df", size = 46382, upload-time = "2025-03-29T19:22:54.252Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/90/d08277ce111dd22f77149fd1a5d4653eeb3b3eaacbdfcbae5afb2600eebd/MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", size = 14357, upload-time = "2024-10-18T15:20:51.44Z" }, + { url = "https://files.pythonhosted.org/packages/04/e1/6e2194baeae0bca1fae6629dc0cbbb968d4d941469cbab11a3872edff374/MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", size = 12393, upload-time = "2024-10-18T15:20:52.426Z" }, + { url = "https://files.pythonhosted.org/packages/1d/69/35fa85a8ece0a437493dc61ce0bb6d459dcba482c34197e3efc829aa357f/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", size = 21732, upload-time = "2024-10-18T15:20:53.578Z" }, + { url = "https://files.pythonhosted.org/packages/22/35/137da042dfb4720b638d2937c38a9c2df83fe32d20e8c8f3185dbfef05f7/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", size = 20866, upload-time = "2024-10-18T15:20:55.06Z" }, + { url = "https://files.pythonhosted.org/packages/29/28/6d029a903727a1b62edb51863232152fd335d602def598dade38996887f0/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", size = 20964, upload-time = "2024-10-18T15:20:55.906Z" }, + { url = "https://files.pythonhosted.org/packages/cc/cd/07438f95f83e8bc028279909d9c9bd39e24149b0d60053a97b2bc4f8aa51/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", size = 21977, upload-time = "2024-10-18T15:20:57.189Z" }, + { url = "https://files.pythonhosted.org/packages/29/01/84b57395b4cc062f9c4c55ce0df7d3108ca32397299d9df00fedd9117d3d/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", size = 21366, upload-time = "2024-10-18T15:20:58.235Z" }, + { url = "https://files.pythonhosted.org/packages/bd/6e/61ebf08d8940553afff20d1fb1ba7294b6f8d279df9fd0c0db911b4bbcfd/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", size = 21091, upload-time = "2024-10-18T15:20:59.235Z" }, + { url = "https://files.pythonhosted.org/packages/11/23/ffbf53694e8c94ebd1e7e491de185124277964344733c45481f32ede2499/MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50", size = 15065, upload-time = "2024-10-18T15:21:00.307Z" }, + { url = "https://files.pythonhosted.org/packages/44/06/e7175d06dd6e9172d4a69a72592cb3f7a996a9c396eee29082826449bbc3/MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", size = 15514, upload-time = "2024-10-18T15:21:01.122Z" }, + { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353, upload-time = "2024-10-18T15:21:02.187Z" }, + { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392, upload-time = "2024-10-18T15:21:02.941Z" }, + { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984, upload-time = "2024-10-18T15:21:03.953Z" }, + { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120, upload-time = "2024-10-18T15:21:06.495Z" }, + { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032, upload-time = "2024-10-18T15:21:07.295Z" }, + { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057, upload-time = "2024-10-18T15:21:08.073Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359, upload-time = "2024-10-18T15:21:09.318Z" }, + { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306, upload-time = "2024-10-18T15:21:10.185Z" }, + { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094, upload-time = "2024-10-18T15:21:11.005Z" }, + { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521, upload-time = "2024-10-18T15:21:12.911Z" }, + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, + { url = "https://files.pythonhosted.org/packages/a7/ea/9b1530c3fdeeca613faeb0fb5cbcf2389d816072fab72a71b45749ef6062/MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a", size = 14344, upload-time = "2024-10-18T15:21:43.721Z" }, + { url = "https://files.pythonhosted.org/packages/4b/c2/fbdbfe48848e7112ab05e627e718e854d20192b674952d9042ebd8c9e5de/MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff", size = 12389, upload-time = "2024-10-18T15:21:44.666Z" }, + { url = "https://files.pythonhosted.org/packages/f0/25/7a7c6e4dbd4f867d95d94ca15449e91e52856f6ed1905d58ef1de5e211d0/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13", size = 21607, upload-time = "2024-10-18T15:21:45.452Z" }, + { url = "https://files.pythonhosted.org/packages/53/8f/f339c98a178f3c1e545622206b40986a4c3307fe39f70ccd3d9df9a9e425/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144", size = 20728, upload-time = "2024-10-18T15:21:46.295Z" }, + { url = "https://files.pythonhosted.org/packages/1a/03/8496a1a78308456dbd50b23a385c69b41f2e9661c67ea1329849a598a8f9/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29", size = 20826, upload-time = "2024-10-18T15:21:47.134Z" }, + { url = "https://files.pythonhosted.org/packages/e6/cf/0a490a4bd363048c3022f2f475c8c05582179bb179defcee4766fb3dcc18/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0", size = 21843, upload-time = "2024-10-18T15:21:48.334Z" }, + { url = "https://files.pythonhosted.org/packages/19/a3/34187a78613920dfd3cdf68ef6ce5e99c4f3417f035694074beb8848cd77/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0", size = 21219, upload-time = "2024-10-18T15:21:49.587Z" }, + { url = "https://files.pythonhosted.org/packages/17/d8/5811082f85bb88410ad7e452263af048d685669bbbfb7b595e8689152498/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178", size = 20946, upload-time = "2024-10-18T15:21:50.441Z" }, + { url = "https://files.pythonhosted.org/packages/7c/31/bd635fb5989440d9365c5e3c47556cfea121c7803f5034ac843e8f37c2f2/MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f", size = 15063, upload-time = "2024-10-18T15:21:51.385Z" }, + { url = "https://files.pythonhosted.org/packages/b3/73/085399401383ce949f727afec55ec3abd76648d04b9f22e1c0e99cb4bec3/MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a", size = 15506, upload-time = "2024-10-18T15:21:52.974Z" }, +] + +[[package]] +name = "matplotlib-inline" +version = "0.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/5b/a36a337438a14116b16480db471ad061c36c3694df7c2084a0da7ba538b7/matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90", size = 8159, upload-time = "2024-04-15T13:44:44.803Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca", size = 9899, upload-time = "2024-04-15T13:44:43.265Z" }, +] + +[[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 = "nox" +version = "2025.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "argcomplete" }, + { name = "attrs" }, + { name = "colorlog" }, + { name = "dependency-groups" }, + { name = "packaging" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b4/80/47712208c410defec169992e57c179f0f4d92f5dd17ba8daca50a8077e23/nox-2025.5.1.tar.gz", hash = "sha256:2a571dfa7a58acc726521ac3cd8184455ebcdcbf26401c7b737b5bc6701427b2", size = 4023334, upload-time = "2025-05-01T16:35:48.056Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/be/7b423b02b09eb856beffe76fe8c4121c99852db74dd12a422dcb72d1134e/nox-2025.5.1-py3-none-any.whl", hash = "sha256:56abd55cf37ff523c254fcec4d152ed51e5fe80e2ab8317221d8b828ac970a31", size = 71753, upload-time = "2025-05-01T16:35:46.037Z" }, +] + +[[package]] +name = "numpy" +version = "2.0.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/75/10dd1f8116a8b796cb2c737b674e02d02e80454bda953fa7e65d8c12b016/numpy-2.0.2.tar.gz", hash = "sha256:883c987dee1880e2a864ab0dc9892292582510604156762362d9326444636e78", size = 18902015, upload-time = "2024-08-26T20:19:40.945Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/21/91/3495b3237510f79f5d81f2508f9f13fea78ebfdf07538fc7444badda173d/numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece", size = 21165245, upload-time = "2024-08-26T20:04:14.625Z" }, + { url = "https://files.pythonhosted.org/packages/05/33/26178c7d437a87082d11019292dce6d3fe6f0e9026b7b2309cbf3e489b1d/numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04", size = 13738540, upload-time = "2024-08-26T20:04:36.784Z" }, + { url = "https://files.pythonhosted.org/packages/ec/31/cc46e13bf07644efc7a4bf68df2df5fb2a1a88d0cd0da9ddc84dc0033e51/numpy-2.0.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8c5713284ce4e282544c68d1c3b2c7161d38c256d2eefc93c1d683cf47683e66", size = 5300623, upload-time = "2024-08-26T20:04:46.491Z" }, + { url = "https://files.pythonhosted.org/packages/6e/16/7bfcebf27bb4f9d7ec67332ffebee4d1bf085c84246552d52dbb548600e7/numpy-2.0.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:becfae3ddd30736fe1889a37f1f580e245ba79a5855bff5f2a29cb3ccc22dd7b", size = 6901774, upload-time = "2024-08-26T20:04:58.173Z" }, + { url = "https://files.pythonhosted.org/packages/f9/a3/561c531c0e8bf082c5bef509d00d56f82e0ea7e1e3e3a7fc8fa78742a6e5/numpy-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2da5960c3cf0df7eafefd806d4e612c5e19358de82cb3c343631188991566ccd", size = 13907081, upload-time = "2024-08-26T20:05:19.098Z" }, + { url = "https://files.pythonhosted.org/packages/fa/66/f7177ab331876200ac7563a580140643d1179c8b4b6a6b0fc9838de2a9b8/numpy-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:496f71341824ed9f3d2fd36cf3ac57ae2e0165c143b55c3a035ee219413f3318", size = 19523451, upload-time = "2024-08-26T20:05:47.479Z" }, + { url = "https://files.pythonhosted.org/packages/25/7f/0b209498009ad6453e4efc2c65bcdf0ae08a182b2b7877d7ab38a92dc542/numpy-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a61ec659f68ae254e4d237816e33171497e978140353c0c2038d46e63282d0c8", size = 19927572, upload-time = "2024-08-26T20:06:17.137Z" }, + { url = "https://files.pythonhosted.org/packages/3e/df/2619393b1e1b565cd2d4c4403bdd979621e2c4dea1f8532754b2598ed63b/numpy-2.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d731a1c6116ba289c1e9ee714b08a8ff882944d4ad631fd411106a30f083c326", size = 14400722, upload-time = "2024-08-26T20:06:39.16Z" }, + { url = "https://files.pythonhosted.org/packages/22/ad/77e921b9f256d5da36424ffb711ae79ca3f451ff8489eeca544d0701d74a/numpy-2.0.2-cp310-cp310-win32.whl", hash = "sha256:984d96121c9f9616cd33fbd0618b7f08e0cfc9600a7ee1d6fd9b239186d19d97", size = 6472170, upload-time = "2024-08-26T20:06:50.361Z" }, + { url = "https://files.pythonhosted.org/packages/10/05/3442317535028bc29cf0c0dd4c191a4481e8376e9f0db6bcf29703cadae6/numpy-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:c7b0be4ef08607dd04da4092faee0b86607f111d5ae68036f16cc787e250a131", size = 15905558, upload-time = "2024-08-26T20:07:13.881Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cf/034500fb83041aa0286e0fb16e7c76e5c8b67c0711bb6e9e9737a717d5fe/numpy-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:49ca4decb342d66018b01932139c0961a8f9ddc7589611158cb3c27cbcf76448", size = 21169137, upload-time = "2024-08-26T20:07:45.345Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d9/32de45561811a4b87fbdee23b5797394e3d1504b4a7cf40c10199848893e/numpy-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:11a76c372d1d37437857280aa142086476136a8c0f373b2e648ab2c8f18fb195", size = 13703552, upload-time = "2024-08-26T20:08:06.666Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ca/2f384720020c7b244d22508cb7ab23d95f179fcfff33c31a6eeba8d6c512/numpy-2.0.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:807ec44583fd708a21d4a11d94aedf2f4f3c3719035c76a2bbe1fe8e217bdc57", size = 5298957, upload-time = "2024-08-26T20:08:15.83Z" }, + { url = "https://files.pythonhosted.org/packages/0e/78/a3e4f9fb6aa4e6fdca0c5428e8ba039408514388cf62d89651aade838269/numpy-2.0.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8cafab480740e22f8d833acefed5cc87ce276f4ece12fdaa2e8903db2f82897a", size = 6905573, upload-time = "2024-08-26T20:08:27.185Z" }, + { url = "https://files.pythonhosted.org/packages/a0/72/cfc3a1beb2caf4efc9d0b38a15fe34025230da27e1c08cc2eb9bfb1c7231/numpy-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a15f476a45e6e5a3a79d8a14e62161d27ad897381fecfa4a09ed5322f2085669", size = 13914330, upload-time = "2024-08-26T20:08:48.058Z" }, + { url = "https://files.pythonhosted.org/packages/ba/a8/c17acf65a931ce551fee11b72e8de63bf7e8a6f0e21add4c937c83563538/numpy-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13e689d772146140a252c3a28501da66dfecd77490b498b168b501835041f951", size = 19534895, upload-time = "2024-08-26T20:09:16.536Z" }, + { url = "https://files.pythonhosted.org/packages/ba/86/8767f3d54f6ae0165749f84648da9dcc8cd78ab65d415494962c86fac80f/numpy-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9ea91dfb7c3d1c56a0e55657c0afb38cf1eeae4544c208dc465c3c9f3a7c09f9", size = 19937253, upload-time = "2024-08-26T20:09:46.263Z" }, + { url = "https://files.pythonhosted.org/packages/df/87/f76450e6e1c14e5bb1eae6836478b1028e096fd02e85c1c37674606ab752/numpy-2.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c1c9307701fec8f3f7a1e6711f9089c06e6284b3afbbcd259f7791282d660a15", size = 14414074, upload-time = "2024-08-26T20:10:08.483Z" }, + { url = "https://files.pythonhosted.org/packages/5c/ca/0f0f328e1e59f73754f06e1adfb909de43726d4f24c6a3f8805f34f2b0fa/numpy-2.0.2-cp311-cp311-win32.whl", hash = "sha256:a392a68bd329eafac5817e5aefeb39038c48b671afd242710b451e76090e81f4", size = 6470640, upload-time = "2024-08-26T20:10:19.732Z" }, + { url = "https://files.pythonhosted.org/packages/eb/57/3a3f14d3a759dcf9bf6e9eda905794726b758819df4663f217d658a58695/numpy-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:286cd40ce2b7d652a6f22efdfc6d1edf879440e53e76a75955bc0c826c7e64dc", size = 15910230, upload-time = "2024-08-26T20:10:43.413Z" }, + { url = "https://files.pythonhosted.org/packages/45/40/2e117be60ec50d98fa08c2f8c48e09b3edea93cfcabd5a9ff6925d54b1c2/numpy-2.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:df55d490dea7934f330006d0f81e8551ba6010a5bf035a249ef61a94f21c500b", size = 20895803, upload-time = "2024-08-26T20:11:13.916Z" }, + { url = "https://files.pythonhosted.org/packages/46/92/1b8b8dee833f53cef3e0a3f69b2374467789e0bb7399689582314df02651/numpy-2.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8df823f570d9adf0978347d1f926b2a867d5608f434a7cff7f7908c6570dcf5e", size = 13471835, upload-time = "2024-08-26T20:11:34.779Z" }, + { url = "https://files.pythonhosted.org/packages/7f/19/e2793bde475f1edaea6945be141aef6c8b4c669b90c90a300a8954d08f0a/numpy-2.0.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9a92ae5c14811e390f3767053ff54eaee3bf84576d99a2456391401323f4ec2c", size = 5038499, upload-time = "2024-08-26T20:11:43.902Z" }, + { url = "https://files.pythonhosted.org/packages/e3/ff/ddf6dac2ff0dd50a7327bcdba45cb0264d0e96bb44d33324853f781a8f3c/numpy-2.0.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a842d573724391493a97a62ebbb8e731f8a5dcc5d285dfc99141ca15a3302d0c", size = 6633497, upload-time = "2024-08-26T20:11:55.09Z" }, + { url = "https://files.pythonhosted.org/packages/72/21/67f36eac8e2d2cd652a2e69595a54128297cdcb1ff3931cfc87838874bd4/numpy-2.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05e238064fc0610c840d1cf6a13bf63d7e391717d247f1bf0318172e759e692", size = 13621158, upload-time = "2024-08-26T20:12:14.95Z" }, + { url = "https://files.pythonhosted.org/packages/39/68/e9f1126d757653496dbc096cb429014347a36b228f5a991dae2c6b6cfd40/numpy-2.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0123ffdaa88fa4ab64835dcbde75dcdf89c453c922f18dced6e27c90d1d0ec5a", size = 19236173, upload-time = "2024-08-26T20:12:44.049Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e9/1f5333281e4ebf483ba1c888b1d61ba7e78d7e910fdd8e6499667041cc35/numpy-2.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:96a55f64139912d61de9137f11bf39a55ec8faec288c75a54f93dfd39f7eb40c", size = 19634174, upload-time = "2024-08-26T20:13:13.634Z" }, + { url = "https://files.pythonhosted.org/packages/71/af/a469674070c8d8408384e3012e064299f7a2de540738a8e414dcfd639996/numpy-2.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec9852fb39354b5a45a80bdab5ac02dd02b15f44b3804e9f00c556bf24b4bded", size = 14099701, upload-time = "2024-08-26T20:13:34.851Z" }, + { url = "https://files.pythonhosted.org/packages/d0/3d/08ea9f239d0e0e939b6ca52ad403c84a2bce1bde301a8eb4888c1c1543f1/numpy-2.0.2-cp312-cp312-win32.whl", hash = "sha256:671bec6496f83202ed2d3c8fdc486a8fc86942f2e69ff0e986140339a63bcbe5", size = 6174313, upload-time = "2024-08-26T20:13:45.653Z" }, + { url = "https://files.pythonhosted.org/packages/b2/b5/4ac39baebf1fdb2e72585c8352c56d063b6126be9fc95bd2bb5ef5770c20/numpy-2.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:cfd41e13fdc257aa5778496b8caa5e856dc4896d4ccf01841daee1d96465467a", size = 15606179, upload-time = "2024-08-26T20:14:08.786Z" }, + { url = "https://files.pythonhosted.org/packages/43/c1/41c8f6df3162b0c6ffd4437d729115704bd43363de0090c7f913cfbc2d89/numpy-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9059e10581ce4093f735ed23f3b9d283b9d517ff46009ddd485f1747eb22653c", size = 21169942, upload-time = "2024-08-26T20:14:40.108Z" }, + { url = "https://files.pythonhosted.org/packages/39/bc/fd298f308dcd232b56a4031fd6ddf11c43f9917fbc937e53762f7b5a3bb1/numpy-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:423e89b23490805d2a5a96fe40ec507407b8ee786d66f7328be214f9679df6dd", size = 13711512, upload-time = "2024-08-26T20:15:00.985Z" }, + { url = "https://files.pythonhosted.org/packages/96/ff/06d1aa3eeb1c614eda245c1ba4fb88c483bee6520d361641331872ac4b82/numpy-2.0.2-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:2b2955fa6f11907cf7a70dab0d0755159bca87755e831e47932367fc8f2f2d0b", size = 5306976, upload-time = "2024-08-26T20:15:10.876Z" }, + { url = "https://files.pythonhosted.org/packages/2d/98/121996dcfb10a6087a05e54453e28e58694a7db62c5a5a29cee14c6e047b/numpy-2.0.2-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:97032a27bd9d8988b9a97a8c4d2c9f2c15a81f61e2f21404d7e8ef00cb5be729", size = 6906494, upload-time = "2024-08-26T20:15:22.055Z" }, + { url = "https://files.pythonhosted.org/packages/15/31/9dffc70da6b9bbf7968f6551967fc21156207366272c2a40b4ed6008dc9b/numpy-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e795a8be3ddbac43274f18588329c72939870a16cae810c2b73461c40718ab1", size = 13912596, upload-time = "2024-08-26T20:15:42.452Z" }, + { url = "https://files.pythonhosted.org/packages/b9/14/78635daab4b07c0930c919d451b8bf8c164774e6a3413aed04a6d95758ce/numpy-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b258c385842546006213344c50655ff1555a9338e2e5e02a0756dc3e803dd", size = 19526099, upload-time = "2024-08-26T20:16:11.048Z" }, + { url = "https://files.pythonhosted.org/packages/26/4c/0eeca4614003077f68bfe7aac8b7496f04221865b3a5e7cb230c9d055afd/numpy-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fec9451a7789926bcf7c2b8d187292c9f93ea30284802a0ab3f5be8ab36865d", size = 19932823, upload-time = "2024-08-26T20:16:40.171Z" }, + { url = "https://files.pythonhosted.org/packages/f1/46/ea25b98b13dccaebddf1a803f8c748680d972e00507cd9bc6dcdb5aa2ac1/numpy-2.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9189427407d88ff25ecf8f12469d4d39d35bee1db5d39fc5c168c6f088a6956d", size = 14404424, upload-time = "2024-08-26T20:17:02.604Z" }, + { url = "https://files.pythonhosted.org/packages/c8/a6/177dd88d95ecf07e722d21008b1b40e681a929eb9e329684d449c36586b2/numpy-2.0.2-cp39-cp39-win32.whl", hash = "sha256:905d16e0c60200656500c95b6b8dca5d109e23cb24abc701d41c02d74c6b3afa", size = 6476809, upload-time = "2024-08-26T20:17:13.553Z" }, + { url = "https://files.pythonhosted.org/packages/ea/2b/7fc9f4e7ae5b507c1a3a21f0f15ed03e794c1242ea8a242ac158beb56034/numpy-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:a3f4ab0caa7f053f6797fcd4e1e25caee367db3112ef2b6ef82d749530768c73", size = 15911314, upload-time = "2024-08-26T20:17:36.72Z" }, + { url = "https://files.pythonhosted.org/packages/8f/3b/df5a870ac6a3be3a86856ce195ef42eec7ae50d2a202be1f5a4b3b340e14/numpy-2.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7f0a0c6f12e07fa94133c8a67404322845220c06a9e80e85999afe727f7438b8", size = 21025288, upload-time = "2024-08-26T20:18:07.732Z" }, + { url = "https://files.pythonhosted.org/packages/2c/97/51af92f18d6f6f2d9ad8b482a99fb74e142d71372da5d834b3a2747a446e/numpy-2.0.2-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:312950fdd060354350ed123c0e25a71327d3711584beaef30cdaa93320c392d4", size = 6762793, upload-time = "2024-08-26T20:18:19.125Z" }, + { url = "https://files.pythonhosted.org/packages/12/46/de1fbd0c1b5ccaa7f9a005b66761533e2f6a3e560096682683a223631fe9/numpy-2.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26df23238872200f63518dd2aa984cfca675d82469535dc7162dc2ee52d9dd5c", size = 19334885, upload-time = "2024-08-26T20:18:47.237Z" }, + { url = "https://files.pythonhosted.org/packages/cc/dc/d330a6faefd92b446ec0f0dfea4c3207bb1fef3c4771d19cf4543efd2c78/numpy-2.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a46288ec55ebbd58947d31d72be2c63cbf839f0a63b49cb755022310792a3385", size = 15828784, upload-time = "2024-08-26T20:19:11.19Z" }, +] + +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +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 = "orderly-set" +version = "5.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/03/4a/38030da31c13dcd5a531490006e63a0954083fb115113be9393179738e25/orderly_set-5.4.1.tar.gz", hash = "sha256:a1fb5a4fdc5e234e9e8d8e5c1bbdbc4540f4dfe50d12bf17c8bc5dbf1c9c878d", size = 20943, upload-time = "2025-05-06T22:34:13.512Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/bc/e0dfb4db9210d92b44e49d6e61ba5caefbd411958357fa9d7ff489eeb835/orderly_set-5.4.1-py3-none-any.whl", hash = "sha256:b5e21d21680bd9ef456885db800c5cb4f76a03879880c0175e1b077fb166fd83", size = 12339, upload-time = "2025-05-06T22:34:12.564Z" }, +] + +[[package]] +name = "orjson" +version = "3.10.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/81/0b/fea456a3ffe74e70ba30e01ec183a9b26bec4d497f61dcfce1b601059c60/orjson-3.10.18.tar.gz", hash = "sha256:e8da3947d92123eda795b68228cafe2724815621fe35e8e320a9e9593a4bcd53", size = 5422810, upload-time = "2025-04-29T23:30:08.423Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/16/2ceb9fb7bc2b11b1e4a3ea27794256e93dee2309ebe297fd131a778cd150/orjson-3.10.18-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a45e5d68066b408e4bc383b6e4ef05e717c65219a9e1390abc6155a520cac402", size = 248927, upload-time = "2025-04-29T23:28:08.643Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e1/d3c0a2bba5b9906badd121da449295062b289236c39c3a7801f92c4682b0/orjson-3.10.18-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be3b9b143e8b9db05368b13b04c84d37544ec85bb97237b3a923f076265ec89c", size = 136995, upload-time = "2025-04-29T23:28:11.503Z" }, + { url = "https://files.pythonhosted.org/packages/d7/51/698dd65e94f153ee5ecb2586c89702c9e9d12f165a63e74eb9ea1299f4e1/orjson-3.10.18-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9b0aa09745e2c9b3bf779b096fa71d1cc2d801a604ef6dd79c8b1bfef52b2f92", size = 132893, upload-time = "2025-04-29T23:28:12.751Z" }, + { url = "https://files.pythonhosted.org/packages/b3/e5/155ce5a2c43a85e790fcf8b985400138ce5369f24ee6770378ee6b691036/orjson-3.10.18-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53a245c104d2792e65c8d225158f2b8262749ffe64bc7755b00024757d957a13", size = 137017, upload-time = "2025-04-29T23:28:14.498Z" }, + { url = "https://files.pythonhosted.org/packages/46/bb/6141ec3beac3125c0b07375aee01b5124989907d61c72c7636136e4bd03e/orjson-3.10.18-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f9495ab2611b7f8a0a8a505bcb0f0cbdb5469caafe17b0e404c3c746f9900469", size = 138290, upload-time = "2025-04-29T23:28:16.211Z" }, + { url = "https://files.pythonhosted.org/packages/77/36/6961eca0b66b7809d33c4ca58c6bd4c23a1b914fb23aba2fa2883f791434/orjson-3.10.18-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:73be1cbcebadeabdbc468f82b087df435843c809cd079a565fb16f0f3b23238f", size = 142828, upload-time = "2025-04-29T23:28:18.065Z" }, + { url = "https://files.pythonhosted.org/packages/8b/2f/0c646d5fd689d3be94f4d83fa9435a6c4322c9b8533edbb3cd4bc8c5f69a/orjson-3.10.18-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe8936ee2679e38903df158037a2f1c108129dee218975122e37847fb1d4ac68", size = 132806, upload-time = "2025-04-29T23:28:19.782Z" }, + { url = "https://files.pythonhosted.org/packages/ea/af/65907b40c74ef4c3674ef2bcfa311c695eb934710459841b3c2da212215c/orjson-3.10.18-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7115fcbc8525c74e4c2b608129bef740198e9a120ae46184dac7683191042056", size = 135005, upload-time = "2025-04-29T23:28:21.367Z" }, + { url = "https://files.pythonhosted.org/packages/c7/d1/68bd20ac6a32cd1f1b10d23e7cc58ee1e730e80624e3031d77067d7150fc/orjson-3.10.18-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:771474ad34c66bc4d1c01f645f150048030694ea5b2709b87d3bda273ffe505d", size = 413418, upload-time = "2025-04-29T23:28:23.097Z" }, + { url = "https://files.pythonhosted.org/packages/31/31/c701ec0bcc3e80e5cb6e319c628ef7b768aaa24b0f3b4c599df2eaacfa24/orjson-3.10.18-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:7c14047dbbea52886dd87169f21939af5d55143dad22d10db6a7514f058156a8", size = 153288, upload-time = "2025-04-29T23:28:25.02Z" }, + { url = "https://files.pythonhosted.org/packages/d9/31/5e1aa99a10893a43cfc58009f9da840990cc8a9ebb75aa452210ba18587e/orjson-3.10.18-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:641481b73baec8db14fdf58f8967e52dc8bda1f2aba3aa5f5c1b07ed6df50b7f", size = 137181, upload-time = "2025-04-29T23:28:26.318Z" }, + { url = "https://files.pythonhosted.org/packages/bf/8c/daba0ac1b8690011d9242a0f37235f7d17df6d0ad941021048523b76674e/orjson-3.10.18-cp310-cp310-win32.whl", hash = "sha256:607eb3ae0909d47280c1fc657c4284c34b785bae371d007595633f4b1a2bbe06", size = 142694, upload-time = "2025-04-29T23:28:28.092Z" }, + { url = "https://files.pythonhosted.org/packages/16/62/8b687724143286b63e1d0fab3ad4214d54566d80b0ba9d67c26aaf28a2f8/orjson-3.10.18-cp310-cp310-win_amd64.whl", hash = "sha256:8770432524ce0eca50b7efc2a9a5f486ee0113a5fbb4231526d414e6254eba92", size = 134600, upload-time = "2025-04-29T23:28:29.422Z" }, + { url = "https://files.pythonhosted.org/packages/97/c7/c54a948ce9a4278794f669a353551ce7db4ffb656c69a6e1f2264d563e50/orjson-3.10.18-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e0a183ac3b8e40471e8d843105da6fbe7c070faab023be3b08188ee3f85719b8", size = 248929, upload-time = "2025-04-29T23:28:30.716Z" }, + { url = "https://files.pythonhosted.org/packages/9e/60/a9c674ef1dd8ab22b5b10f9300e7e70444d4e3cda4b8258d6c2488c32143/orjson-3.10.18-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:5ef7c164d9174362f85238d0cd4afdeeb89d9e523e4651add6a5d458d6f7d42d", size = 133364, upload-time = "2025-04-29T23:28:32.392Z" }, + { url = "https://files.pythonhosted.org/packages/c1/4e/f7d1bdd983082216e414e6d7ef897b0c2957f99c545826c06f371d52337e/orjson-3.10.18-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afd14c5d99cdc7bf93f22b12ec3b294931518aa019e2a147e8aa2f31fd3240f7", size = 136995, upload-time = "2025-04-29T23:28:34.024Z" }, + { url = "https://files.pythonhosted.org/packages/17/89/46b9181ba0ea251c9243b0c8ce29ff7c9796fa943806a9c8b02592fce8ea/orjson-3.10.18-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b672502323b6cd133c4af6b79e3bea36bad2d16bca6c1f645903fce83909a7a", size = 132894, upload-time = "2025-04-29T23:28:35.318Z" }, + { url = "https://files.pythonhosted.org/packages/ca/dd/7bce6fcc5b8c21aef59ba3c67f2166f0a1a9b0317dcca4a9d5bd7934ecfd/orjson-3.10.18-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:51f8c63be6e070ec894c629186b1c0fe798662b8687f3d9fdfa5e401c6bd7679", size = 137016, upload-time = "2025-04-29T23:28:36.674Z" }, + { url = "https://files.pythonhosted.org/packages/1c/4a/b8aea1c83af805dcd31c1f03c95aabb3e19a016b2a4645dd822c5686e94d/orjson-3.10.18-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f9478ade5313d724e0495d167083c6f3be0dd2f1c9c8a38db9a9e912cdaf947", size = 138290, upload-time = "2025-04-29T23:28:38.3Z" }, + { url = "https://files.pythonhosted.org/packages/36/d6/7eb05c85d987b688707f45dcf83c91abc2251e0dd9fb4f7be96514f838b1/orjson-3.10.18-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:187aefa562300a9d382b4b4eb9694806e5848b0cedf52037bb5c228c61bb66d4", size = 142829, upload-time = "2025-04-29T23:28:39.657Z" }, + { url = "https://files.pythonhosted.org/packages/d2/78/ddd3ee7873f2b5f90f016bc04062713d567435c53ecc8783aab3a4d34915/orjson-3.10.18-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da552683bc9da222379c7a01779bddd0ad39dd699dd6300abaf43eadee38334", size = 132805, upload-time = "2025-04-29T23:28:40.969Z" }, + { url = "https://files.pythonhosted.org/packages/8c/09/c8e047f73d2c5d21ead9c180203e111cddeffc0848d5f0f974e346e21c8e/orjson-3.10.18-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e450885f7b47a0231979d9c49b567ed1c4e9f69240804621be87c40bc9d3cf17", size = 135008, upload-time = "2025-04-29T23:28:42.284Z" }, + { url = "https://files.pythonhosted.org/packages/0c/4b/dccbf5055ef8fb6eda542ab271955fc1f9bf0b941a058490293f8811122b/orjson-3.10.18-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:5e3c9cc2ba324187cd06287ca24f65528f16dfc80add48dc99fa6c836bb3137e", size = 413419, upload-time = "2025-04-29T23:28:43.673Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f3/1eac0c5e2d6d6790bd2025ebfbefcbd37f0d097103d76f9b3f9302af5a17/orjson-3.10.18-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:50ce016233ac4bfd843ac5471e232b865271d7d9d44cf9d33773bcd883ce442b", size = 153292, upload-time = "2025-04-29T23:28:45.573Z" }, + { url = "https://files.pythonhosted.org/packages/1f/b4/ef0abf64c8f1fabf98791819ab502c2c8c1dc48b786646533a93637d8999/orjson-3.10.18-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b3ceff74a8f7ffde0b2785ca749fc4e80e4315c0fd887561144059fb1c138aa7", size = 137182, upload-time = "2025-04-29T23:28:47.229Z" }, + { url = "https://files.pythonhosted.org/packages/a9/a3/6ea878e7b4a0dc5c888d0370d7752dcb23f402747d10e2257478d69b5e63/orjson-3.10.18-cp311-cp311-win32.whl", hash = "sha256:fdba703c722bd868c04702cac4cb8c6b8ff137af2623bc0ddb3b3e6a2c8996c1", size = 142695, upload-time = "2025-04-29T23:28:48.564Z" }, + { url = "https://files.pythonhosted.org/packages/79/2a/4048700a3233d562f0e90d5572a849baa18ae4e5ce4c3ba6247e4ece57b0/orjson-3.10.18-cp311-cp311-win_amd64.whl", hash = "sha256:c28082933c71ff4bc6ccc82a454a2bffcef6e1d7379756ca567c772e4fb3278a", size = 134603, upload-time = "2025-04-29T23:28:50.442Z" }, + { url = "https://files.pythonhosted.org/packages/03/45/10d934535a4993d27e1c84f1810e79ccf8b1b7418cef12151a22fe9bb1e1/orjson-3.10.18-cp311-cp311-win_arm64.whl", hash = "sha256:a6c7c391beaedd3fa63206e5c2b7b554196f14debf1ec9deb54b5d279b1b46f5", size = 131400, upload-time = "2025-04-29T23:28:51.838Z" }, + { url = "https://files.pythonhosted.org/packages/21/1a/67236da0916c1a192d5f4ccbe10ec495367a726996ceb7614eaa687112f2/orjson-3.10.18-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:50c15557afb7f6d63bc6d6348e0337a880a04eaa9cd7c9d569bcb4e760a24753", size = 249184, upload-time = "2025-04-29T23:28:53.612Z" }, + { url = "https://files.pythonhosted.org/packages/b3/bc/c7f1db3b1d094dc0c6c83ed16b161a16c214aaa77f311118a93f647b32dc/orjson-3.10.18-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:356b076f1662c9813d5fa56db7d63ccceef4c271b1fb3dd522aca291375fcf17", size = 133279, upload-time = "2025-04-29T23:28:55.055Z" }, + { url = "https://files.pythonhosted.org/packages/af/84/664657cd14cc11f0d81e80e64766c7ba5c9b7fc1ec304117878cc1b4659c/orjson-3.10.18-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:559eb40a70a7494cd5beab2d73657262a74a2c59aff2068fdba8f0424ec5b39d", size = 136799, upload-time = "2025-04-29T23:28:56.828Z" }, + { url = "https://files.pythonhosted.org/packages/9a/bb/f50039c5bb05a7ab024ed43ba25d0319e8722a0ac3babb0807e543349978/orjson-3.10.18-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f3c29eb9a81e2fbc6fd7ddcfba3e101ba92eaff455b8d602bf7511088bbc0eae", size = 132791, upload-time = "2025-04-29T23:28:58.751Z" }, + { url = "https://files.pythonhosted.org/packages/93/8c/ee74709fc072c3ee219784173ddfe46f699598a1723d9d49cbc78d66df65/orjson-3.10.18-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6612787e5b0756a171c7d81ba245ef63a3533a637c335aa7fcb8e665f4a0966f", size = 137059, upload-time = "2025-04-29T23:29:00.129Z" }, + { url = "https://files.pythonhosted.org/packages/6a/37/e6d3109ee004296c80426b5a62b47bcadd96a3deab7443e56507823588c5/orjson-3.10.18-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ac6bd7be0dcab5b702c9d43d25e70eb456dfd2e119d512447468f6405b4a69c", size = 138359, upload-time = "2025-04-29T23:29:01.704Z" }, + { url = "https://files.pythonhosted.org/packages/4f/5d/387dafae0e4691857c62bd02839a3bf3fa648eebd26185adfac58d09f207/orjson-3.10.18-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9f72f100cee8dde70100406d5c1abba515a7df926d4ed81e20a9730c062fe9ad", size = 142853, upload-time = "2025-04-29T23:29:03.576Z" }, + { url = "https://files.pythonhosted.org/packages/27/6f/875e8e282105350b9a5341c0222a13419758545ae32ad6e0fcf5f64d76aa/orjson-3.10.18-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9dca85398d6d093dd41dc0983cbf54ab8e6afd1c547b6b8a311643917fbf4e0c", size = 133131, upload-time = "2025-04-29T23:29:05.753Z" }, + { url = "https://files.pythonhosted.org/packages/48/b2/73a1f0b4790dcb1e5a45f058f4f5dcadc8a85d90137b50d6bbc6afd0ae50/orjson-3.10.18-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:22748de2a07fcc8781a70edb887abf801bb6142e6236123ff93d12d92db3d406", size = 134834, upload-time = "2025-04-29T23:29:07.35Z" }, + { url = "https://files.pythonhosted.org/packages/56/f5/7ed133a5525add9c14dbdf17d011dd82206ca6840811d32ac52a35935d19/orjson-3.10.18-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:3a83c9954a4107b9acd10291b7f12a6b29e35e8d43a414799906ea10e75438e6", size = 413368, upload-time = "2025-04-29T23:29:09.301Z" }, + { url = "https://files.pythonhosted.org/packages/11/7c/439654221ed9c3324bbac7bdf94cf06a971206b7b62327f11a52544e4982/orjson-3.10.18-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:303565c67a6c7b1f194c94632a4a39918e067bd6176a48bec697393865ce4f06", size = 153359, upload-time = "2025-04-29T23:29:10.813Z" }, + { url = "https://files.pythonhosted.org/packages/48/e7/d58074fa0cc9dd29a8fa2a6c8d5deebdfd82c6cfef72b0e4277c4017563a/orjson-3.10.18-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:86314fdb5053a2f5a5d881f03fca0219bfdf832912aa88d18676a5175c6916b5", size = 137466, upload-time = "2025-04-29T23:29:12.26Z" }, + { url = "https://files.pythonhosted.org/packages/57/4d/fe17581cf81fb70dfcef44e966aa4003360e4194d15a3f38cbffe873333a/orjson-3.10.18-cp312-cp312-win32.whl", hash = "sha256:187ec33bbec58c76dbd4066340067d9ece6e10067bb0cc074a21ae3300caa84e", size = 142683, upload-time = "2025-04-29T23:29:13.865Z" }, + { url = "https://files.pythonhosted.org/packages/e6/22/469f62d25ab5f0f3aee256ea732e72dc3aab6d73bac777bd6277955bceef/orjson-3.10.18-cp312-cp312-win_amd64.whl", hash = "sha256:f9f94cf6d3f9cd720d641f8399e390e7411487e493962213390d1ae45c7814fc", size = 134754, upload-time = "2025-04-29T23:29:15.338Z" }, + { url = "https://files.pythonhosted.org/packages/10/b0/1040c447fac5b91bc1e9c004b69ee50abb0c1ffd0d24406e1350c58a7fcb/orjson-3.10.18-cp312-cp312-win_arm64.whl", hash = "sha256:3d600be83fe4514944500fa8c2a0a77099025ec6482e8087d7659e891f23058a", size = 131218, upload-time = "2025-04-29T23:29:17.324Z" }, + { url = "https://files.pythonhosted.org/packages/04/f0/8aedb6574b68096f3be8f74c0b56d36fd94bcf47e6c7ed47a7bd1474aaa8/orjson-3.10.18-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:69c34b9441b863175cc6a01f2935de994025e773f814412030f269da4f7be147", size = 249087, upload-time = "2025-04-29T23:29:19.083Z" }, + { url = "https://files.pythonhosted.org/packages/bc/f7/7118f965541aeac6844fcb18d6988e111ac0d349c9b80cda53583e758908/orjson-3.10.18-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:1ebeda919725f9dbdb269f59bc94f861afbe2a27dce5608cdba2d92772364d1c", size = 133273, upload-time = "2025-04-29T23:29:20.602Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d9/839637cc06eaf528dd8127b36004247bf56e064501f68df9ee6fd56a88ee/orjson-3.10.18-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5adf5f4eed520a4959d29ea80192fa626ab9a20b2ea13f8f6dc58644f6927103", size = 136779, upload-time = "2025-04-29T23:29:22.062Z" }, + { url = "https://files.pythonhosted.org/packages/2b/6d/f226ecfef31a1f0e7d6bf9a31a0bbaf384c7cbe3fce49cc9c2acc51f902a/orjson-3.10.18-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7592bb48a214e18cd670974f289520f12b7aed1fa0b2e2616b8ed9e069e08595", size = 132811, upload-time = "2025-04-29T23:29:23.602Z" }, + { url = "https://files.pythonhosted.org/packages/73/2d/371513d04143c85b681cf8f3bce743656eb5b640cb1f461dad750ac4b4d4/orjson-3.10.18-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f872bef9f042734110642b7a11937440797ace8c87527de25e0c53558b579ccc", size = 137018, upload-time = "2025-04-29T23:29:25.094Z" }, + { url = "https://files.pythonhosted.org/packages/69/cb/a4d37a30507b7a59bdc484e4a3253c8141bf756d4e13fcc1da760a0b00cb/orjson-3.10.18-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0315317601149c244cb3ecef246ef5861a64824ccbcb8018d32c66a60a84ffbc", size = 138368, upload-time = "2025-04-29T23:29:26.609Z" }, + { url = "https://files.pythonhosted.org/packages/1e/ae/cd10883c48d912d216d541eb3db8b2433415fde67f620afe6f311f5cd2ca/orjson-3.10.18-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0da26957e77e9e55a6c2ce2e7182a36a6f6b180ab7189315cb0995ec362e049", size = 142840, upload-time = "2025-04-29T23:29:28.153Z" }, + { url = "https://files.pythonhosted.org/packages/6d/4c/2bda09855c6b5f2c055034c9eda1529967b042ff8d81a05005115c4e6772/orjson-3.10.18-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb70d489bc79b7519e5803e2cc4c72343c9dc1154258adf2f8925d0b60da7c58", size = 133135, upload-time = "2025-04-29T23:29:29.726Z" }, + { url = "https://files.pythonhosted.org/packages/13/4a/35971fd809a8896731930a80dfff0b8ff48eeb5d8b57bb4d0d525160017f/orjson-3.10.18-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e9e86a6af31b92299b00736c89caf63816f70a4001e750bda179e15564d7a034", size = 134810, upload-time = "2025-04-29T23:29:31.269Z" }, + { url = "https://files.pythonhosted.org/packages/99/70/0fa9e6310cda98365629182486ff37a1c6578e34c33992df271a476ea1cd/orjson-3.10.18-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:c382a5c0b5931a5fc5405053d36c1ce3fd561694738626c77ae0b1dfc0242ca1", size = 413491, upload-time = "2025-04-29T23:29:33.315Z" }, + { url = "https://files.pythonhosted.org/packages/32/cb/990a0e88498babddb74fb97855ae4fbd22a82960e9b06eab5775cac435da/orjson-3.10.18-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8e4b2ae732431127171b875cb2668f883e1234711d3c147ffd69fe5be51a8012", size = 153277, upload-time = "2025-04-29T23:29:34.946Z" }, + { url = "https://files.pythonhosted.org/packages/92/44/473248c3305bf782a384ed50dd8bc2d3cde1543d107138fd99b707480ca1/orjson-3.10.18-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2d808e34ddb24fc29a4d4041dcfafbae13e129c93509b847b14432717d94b44f", size = 137367, upload-time = "2025-04-29T23:29:36.52Z" }, + { url = "https://files.pythonhosted.org/packages/ad/fd/7f1d3edd4ffcd944a6a40e9f88af2197b619c931ac4d3cfba4798d4d3815/orjson-3.10.18-cp313-cp313-win32.whl", hash = "sha256:ad8eacbb5d904d5591f27dee4031e2c1db43d559edb8f91778efd642d70e6bea", size = 142687, upload-time = "2025-04-29T23:29:38.292Z" }, + { url = "https://files.pythonhosted.org/packages/4b/03/c75c6ad46be41c16f4cfe0352a2d1450546f3c09ad2c9d341110cd87b025/orjson-3.10.18-cp313-cp313-win_amd64.whl", hash = "sha256:aed411bcb68bf62e85588f2a7e03a6082cc42e5a2796e06e72a962d7c6310b52", size = 134794, upload-time = "2025-04-29T23:29:40.349Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/f53038a5a72cc4fd0b56c1eafb4ef64aec9685460d5ac34de98ca78b6e29/orjson-3.10.18-cp313-cp313-win_arm64.whl", hash = "sha256:f54c1385a0e6aba2f15a40d703b858bedad36ded0491e55d35d905b2c34a4cc3", size = 131186, upload-time = "2025-04-29T23:29:41.922Z" }, + { url = "https://files.pythonhosted.org/packages/df/db/69488acaa2316788b7e171f024912c6fe8193aa2e24e9cfc7bc41c3669ba/orjson-3.10.18-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:c95fae14225edfd699454e84f61c3dd938df6629a00c6ce15e704f57b58433bb", size = 249301, upload-time = "2025-04-29T23:29:44.719Z" }, + { url = "https://files.pythonhosted.org/packages/23/21/d816c44ec5d1482c654e1d23517d935bb2716e1453ff9380e861dc6efdd3/orjson-3.10.18-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5232d85f177f98e0cefabb48b5e7f60cff6f3f0365f9c60631fecd73849b2a82", size = 136786, upload-time = "2025-04-29T23:29:46.517Z" }, + { url = "https://files.pythonhosted.org/packages/a5/9f/f68d8a9985b717e39ba7bf95b57ba173fcd86aeca843229ec60d38f1faa7/orjson-3.10.18-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2783e121cafedf0d85c148c248a20470018b4ffd34494a68e125e7d5857655d1", size = 132711, upload-time = "2025-04-29T23:29:48.605Z" }, + { url = "https://files.pythonhosted.org/packages/b5/63/447f5955439bf7b99bdd67c38a3f689d140d998ac58e3b7d57340520343c/orjson-3.10.18-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e54ee3722caf3db09c91f442441e78f916046aa58d16b93af8a91500b7bbf273", size = 136841, upload-time = "2025-04-29T23:29:50.31Z" }, + { url = "https://files.pythonhosted.org/packages/68/9e/4855972f2be74097242e4681ab6766d36638a079e09d66f3d6a5d1188ce7/orjson-3.10.18-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2daf7e5379b61380808c24f6fc182b7719301739e4271c3ec88f2984a2d61f89", size = 138082, upload-time = "2025-04-29T23:29:51.992Z" }, + { url = "https://files.pythonhosted.org/packages/08/0f/e68431e53a39698d2355faf1f018c60a3019b4b54b4ea6be9dc6b8208a3d/orjson-3.10.18-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7f39b371af3add20b25338f4b29a8d6e79a8c7ed0e9dd49e008228a065d07781", size = 142618, upload-time = "2025-04-29T23:29:53.642Z" }, + { url = "https://files.pythonhosted.org/packages/32/da/bdcfff239ddba1b6ef465efe49d7e43cc8c30041522feba9fd4241d47c32/orjson-3.10.18-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b819ed34c01d88c6bec290e6842966f8e9ff84b7694632e88341363440d4cc0", size = 132627, upload-time = "2025-04-29T23:29:55.318Z" }, + { url = "https://files.pythonhosted.org/packages/0c/28/bc634da09bbe972328f615b0961f1e7d91acb3cc68bddbca9e8dd64e8e24/orjson-3.10.18-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2f6c57debaef0b1aa13092822cbd3698a1fb0209a9ea013a969f4efa36bdea57", size = 134832, upload-time = "2025-04-29T23:29:56.985Z" }, + { url = "https://files.pythonhosted.org/packages/1d/d2/e8ac0c2d0ec782ed8925b4eb33f040cee1f1fbd1d8b268aeb84b94153e49/orjson-3.10.18-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:755b6d61ffdb1ffa1e768330190132e21343757c9aa2308c67257cc81a1a6f5a", size = 413161, upload-time = "2025-04-29T23:29:59.148Z" }, + { url = "https://files.pythonhosted.org/packages/28/f0/397e98c352a27594566e865999dc6b88d6f37d5bbb87b23c982af24114c4/orjson-3.10.18-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ce8d0a875a85b4c8579eab5ac535fb4b2a50937267482be402627ca7e7570ee3", size = 153012, upload-time = "2025-04-29T23:30:01.066Z" }, + { url = "https://files.pythonhosted.org/packages/93/bf/2c7334caeb48bdaa4cae0bde17ea417297ee136598653b1da7ae1f98c785/orjson-3.10.18-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57b5d0673cbd26781bebc2bf86f99dd19bd5a9cb55f71cc4f66419f6b50f3d77", size = 136999, upload-time = "2025-04-29T23:30:02.93Z" }, + { url = "https://files.pythonhosted.org/packages/35/72/4827b1c0c31621c2aa1e661a899cdd2cfac0565c6cd7131890daa4ef7535/orjson-3.10.18-cp39-cp39-win32.whl", hash = "sha256:951775d8b49d1d16ca8818b1f20c4965cae9157e7b562a2ae34d3967b8f21c8e", size = 142560, upload-time = "2025-04-29T23:30:04.805Z" }, + { url = "https://files.pythonhosted.org/packages/72/91/ef8e76868e7eed478887c82f60607a8abf58dadd24e95817229a4b2e2639/orjson-3.10.18-cp39-cp39-win_amd64.whl", hash = "sha256:fdd9d68f83f0bc4406610b1ac68bdcded8c5ee58605cc69e643a06f4d075f429", size = 134455, upload-time = "2025-04-29T23:30:06.588Z" }, +] + +[[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 = "pandas" +version = "2.2.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9c/d6/9f8431bacc2e19dca897724cd097b1bb224a6ad5433784a44b587c7c13af/pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667", size = 4399213, upload-time = "2024-09-20T13:10:04.827Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/aa/70/c853aec59839bceed032d52010ff5f1b8d87dc3114b762e4ba2727661a3b/pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5", size = 12580827, upload-time = "2024-09-20T13:08:42.347Z" }, + { url = "https://files.pythonhosted.org/packages/99/f2/c4527768739ffa4469b2b4fff05aa3768a478aed89a2f271a79a40eee984/pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348", size = 11303897, upload-time = "2024-09-20T13:08:45.807Z" }, + { url = "https://files.pythonhosted.org/packages/ed/12/86c1747ea27989d7a4064f806ce2bae2c6d575b950be087837bdfcabacc9/pandas-2.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d9c45366def9a3dd85a6454c0e7908f2b3b8e9c138f5dc38fed7ce720d8453ed", size = 66480908, upload-time = "2024-09-20T18:37:13.513Z" }, + { url = "https://files.pythonhosted.org/packages/44/50/7db2cd5e6373ae796f0ddad3675268c8d59fb6076e66f0c339d61cea886b/pandas-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86976a1c5b25ae3f8ccae3a5306e443569ee3c3faf444dfd0f41cda24667ad57", size = 13064210, upload-time = "2024-09-20T13:08:48.325Z" }, + { url = "https://files.pythonhosted.org/packages/61/61/a89015a6d5536cb0d6c3ba02cebed51a95538cf83472975275e28ebf7d0c/pandas-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b8661b0238a69d7aafe156b7fa86c44b881387509653fdf857bebc5e4008ad42", size = 16754292, upload-time = "2024-09-20T19:01:54.443Z" }, + { url = "https://files.pythonhosted.org/packages/ce/0d/4cc7b69ce37fac07645a94e1d4b0880b15999494372c1523508511b09e40/pandas-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37e0aced3e8f539eccf2e099f65cdb9c8aa85109b0be6e93e2baff94264bdc6f", size = 14416379, upload-time = "2024-09-20T13:08:50.882Z" }, + { url = "https://files.pythonhosted.org/packages/31/9e/6ebb433de864a6cd45716af52a4d7a8c3c9aaf3a98368e61db9e69e69a9c/pandas-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:56534ce0746a58afaf7942ba4863e0ef81c9c50d3f0ae93e9497d6a41a057645", size = 11598471, upload-time = "2024-09-20T13:08:53.332Z" }, + { url = "https://files.pythonhosted.org/packages/a8/44/d9502bf0ed197ba9bf1103c9867d5904ddcaf869e52329787fc54ed70cc8/pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039", size = 12602222, upload-time = "2024-09-20T13:08:56.254Z" }, + { url = "https://files.pythonhosted.org/packages/52/11/9eac327a38834f162b8250aab32a6781339c69afe7574368fffe46387edf/pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd", size = 11321274, upload-time = "2024-09-20T13:08:58.645Z" }, + { url = "https://files.pythonhosted.org/packages/45/fb/c4beeb084718598ba19aa9f5abbc8aed8b42f90930da861fcb1acdb54c3a/pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698", size = 15579836, upload-time = "2024-09-20T19:01:57.571Z" }, + { url = "https://files.pythonhosted.org/packages/cd/5f/4dba1d39bb9c38d574a9a22548c540177f78ea47b32f99c0ff2ec499fac5/pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc", size = 13058505, upload-time = "2024-09-20T13:09:01.501Z" }, + { url = "https://files.pythonhosted.org/packages/b9/57/708135b90391995361636634df1f1130d03ba456e95bcf576fada459115a/pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3", size = 16744420, upload-time = "2024-09-20T19:02:00.678Z" }, + { url = "https://files.pythonhosted.org/packages/86/4a/03ed6b7ee323cf30404265c284cee9c65c56a212e0a08d9ee06984ba2240/pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32", size = 14440457, upload-time = "2024-09-20T13:09:04.105Z" }, + { url = "https://files.pythonhosted.org/packages/ed/8c/87ddf1fcb55d11f9f847e3c69bb1c6f8e46e2f40ab1a2d2abadb2401b007/pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5", size = 11617166, upload-time = "2024-09-20T13:09:06.917Z" }, + { url = "https://files.pythonhosted.org/packages/17/a3/fb2734118db0af37ea7433f57f722c0a56687e14b14690edff0cdb4b7e58/pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9", size = 12529893, upload-time = "2024-09-20T13:09:09.655Z" }, + { url = "https://files.pythonhosted.org/packages/e1/0c/ad295fd74bfac85358fd579e271cded3ac969de81f62dd0142c426b9da91/pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4", size = 11363475, upload-time = "2024-09-20T13:09:14.718Z" }, + { url = "https://files.pythonhosted.org/packages/c6/2a/4bba3f03f7d07207481fed47f5b35f556c7441acddc368ec43d6643c5777/pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3", size = 15188645, upload-time = "2024-09-20T19:02:03.88Z" }, + { url = "https://files.pythonhosted.org/packages/38/f8/d8fddee9ed0d0c0f4a2132c1dfcf0e3e53265055da8df952a53e7eaf178c/pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319", size = 12739445, upload-time = "2024-09-20T13:09:17.621Z" }, + { url = "https://files.pythonhosted.org/packages/20/e8/45a05d9c39d2cea61ab175dbe6a2de1d05b679e8de2011da4ee190d7e748/pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8", size = 16359235, upload-time = "2024-09-20T19:02:07.094Z" }, + { url = "https://files.pythonhosted.org/packages/1d/99/617d07a6a5e429ff90c90da64d428516605a1ec7d7bea494235e1c3882de/pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a", size = 14056756, upload-time = "2024-09-20T13:09:20.474Z" }, + { url = "https://files.pythonhosted.org/packages/29/d4/1244ab8edf173a10fd601f7e13b9566c1b525c4f365d6bee918e68381889/pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13", size = 11504248, upload-time = "2024-09-20T13:09:23.137Z" }, + { url = "https://files.pythonhosted.org/packages/64/22/3b8f4e0ed70644e85cfdcd57454686b9057c6c38d2f74fe4b8bc2527214a/pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015", size = 12477643, upload-time = "2024-09-20T13:09:25.522Z" }, + { url = "https://files.pythonhosted.org/packages/e4/93/b3f5d1838500e22c8d793625da672f3eec046b1a99257666c94446969282/pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28", size = 11281573, upload-time = "2024-09-20T13:09:28.012Z" }, + { url = "https://files.pythonhosted.org/packages/f5/94/6c79b07f0e5aab1dcfa35a75f4817f5c4f677931d4234afcd75f0e6a66ca/pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0", size = 15196085, upload-time = "2024-09-20T19:02:10.451Z" }, + { url = "https://files.pythonhosted.org/packages/e8/31/aa8da88ca0eadbabd0a639788a6da13bb2ff6edbbb9f29aa786450a30a91/pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24", size = 12711809, upload-time = "2024-09-20T13:09:30.814Z" }, + { url = "https://files.pythonhosted.org/packages/ee/7c/c6dbdb0cb2a4344cacfb8de1c5808ca885b2e4dcfde8008266608f9372af/pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659", size = 16356316, upload-time = "2024-09-20T19:02:13.825Z" }, + { url = "https://files.pythonhosted.org/packages/57/b7/8b757e7d92023b832869fa8881a992696a0bfe2e26f72c9ae9f255988d42/pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb", size = 14022055, upload-time = "2024-09-20T13:09:33.462Z" }, + { url = "https://files.pythonhosted.org/packages/3b/bc/4b18e2b8c002572c5a441a64826252ce5da2aa738855747247a971988043/pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d", size = 11481175, upload-time = "2024-09-20T13:09:35.871Z" }, + { url = "https://files.pythonhosted.org/packages/76/a3/a5d88146815e972d40d19247b2c162e88213ef51c7c25993942c39dbf41d/pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468", size = 12615650, upload-time = "2024-09-20T13:09:38.685Z" }, + { url = "https://files.pythonhosted.org/packages/9c/8c/f0fd18f6140ddafc0c24122c8a964e48294acc579d47def376fef12bcb4a/pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18", size = 11290177, upload-time = "2024-09-20T13:09:41.141Z" }, + { url = "https://files.pythonhosted.org/packages/ed/f9/e995754eab9c0f14c6777401f7eece0943840b7a9fc932221c19d1abee9f/pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2", size = 14651526, upload-time = "2024-09-20T19:02:16.905Z" }, + { url = "https://files.pythonhosted.org/packages/25/b0/98d6ae2e1abac4f35230aa756005e8654649d305df9a28b16b9ae4353bff/pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4", size = 11871013, upload-time = "2024-09-20T13:09:44.39Z" }, + { url = "https://files.pythonhosted.org/packages/cc/57/0f72a10f9db6a4628744c8e8f0df4e6e21de01212c7c981d31e50ffc8328/pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d", size = 15711620, upload-time = "2024-09-20T19:02:20.639Z" }, + { url = "https://files.pythonhosted.org/packages/ab/5f/b38085618b950b79d2d9164a711c52b10aefc0ae6833b96f626b7021b2ed/pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a", size = 13098436, upload-time = "2024-09-20T13:09:48.112Z" }, + { url = "https://files.pythonhosted.org/packages/ca/8c/8848a4c9b8fdf5a534fe2077af948bf53cd713d77ffbcd7bd15710348fd7/pandas-2.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc6b93f9b966093cb0fd62ff1a7e4c09e6d546ad7c1de191767baffc57628f39", size = 12595535, upload-time = "2024-09-20T13:09:51.339Z" }, + { url = "https://files.pythonhosted.org/packages/9c/b9/5cead4f63b6d31bdefeb21a679bc5a7f4aaf262ca7e07e2bc1c341b68470/pandas-2.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5dbca4c1acd72e8eeef4753eeca07de9b1db4f398669d5994086f788a5d7cc30", size = 11319822, upload-time = "2024-09-20T13:09:54.31Z" }, + { url = "https://files.pythonhosted.org/packages/31/af/89e35619fb573366fa68dc26dad6ad2c08c17b8004aad6d98f1a31ce4bb3/pandas-2.2.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8cd6d7cc958a3910f934ea8dbdf17b2364827bb4dafc38ce6eef6bb3d65ff09c", size = 15625439, upload-time = "2024-09-20T19:02:23.689Z" }, + { url = "https://files.pythonhosted.org/packages/3d/dd/bed19c2974296661493d7acc4407b1d2db4e2a482197df100f8f965b6225/pandas-2.2.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99df71520d25fade9db7c1076ac94eb994f4d2673ef2aa2e86ee039b6746d20c", size = 13068928, upload-time = "2024-09-20T13:09:56.746Z" }, + { url = "https://files.pythonhosted.org/packages/31/a3/18508e10a31ea108d746c848b5a05c0711e0278fa0d6f1c52a8ec52b80a5/pandas-2.2.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:31d0ced62d4ea3e231a9f228366919a5ea0b07440d9d4dac345376fd8e1477ea", size = 16783266, upload-time = "2024-09-20T19:02:26.247Z" }, + { url = "https://files.pythonhosted.org/packages/c4/a5/3429bd13d82bebc78f4d78c3945efedef63a7cd0c15c17b2eeb838d1121f/pandas-2.2.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7eee9e7cea6adf3e3d24e304ac6b8300646e2a5d1cd3a3c2abed9101b0846761", size = 14450871, upload-time = "2024-09-20T13:09:59.779Z" }, + { url = "https://files.pythonhosted.org/packages/2f/49/5c30646e96c684570925b772eac4eb0a8cb0ca590fa978f56c5d3ae73ea1/pandas-2.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:4850ba03528b6dd51d6c5d273c46f183f39a9baf3f0143e566b89450965b105e", size = 11618011, upload-time = "2024-09-20T13:10:02.351Z" }, +] + +[[package]] +name = "parso" +version = "0.8.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/94/68e2e17afaa9169cf6412ab0f28623903be73d1b32e208d9e8e541bb086d/parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d", size = 400609, upload-time = "2024-04-05T09:43:55.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650, upload-time = "2024-04-05T09:43:53.299Z" }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, +] + +[[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 = "polars" +version = "1.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/49/3733f0a34fd2504264579bad2c66021e175ab548b21767340721e10a1dcf/polars-1.21.0.tar.gz", hash = "sha256:7692d0fe0fb4faac18ef9423de55789e289f4d3f26d42519bd23ef8afb672d62", size = 4323012, upload-time = "2025-01-24T17:56:43.723Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/c3/976f0251e96c957143905530b236f1e278b28a8eb5850eab94595bf5d220/polars-1.21.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:063f8807f633f8fd15458a43971d930f6ee568b8e95936d7736c9054fc4f6f52", size = 31015281, upload-time = "2025-01-24T17:55:05.645Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/c55c19dde172e34dd7a5074a1dcac6472074236131698269db236550283e/polars-1.21.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:519863e0990e3323e7a32fc66bac3ad9da51938a1ffce6c09a92e0b1adb026a5", size = 28033973, upload-time = "2025-01-24T17:55:11.734Z" }, + { url = "https://files.pythonhosted.org/packages/da/72/b108cd7e063f03f5b029edbd73ca514291dd3e3d88617965d09df64d71ba/polars-1.21.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbecddca35c57efde99070517db5d2c63d4c6d0e3c992123ba3be93e86e7bfac", size = 31641844, upload-time = "2025-01-24T17:55:17.384Z" }, + { url = "https://files.pythonhosted.org/packages/ac/0a/1df51a9e09fb9974a511eb098e13afed916e8643556799799884f22c7869/polars-1.21.0-cp39-abi3-manylinux_2_24_aarch64.whl", hash = "sha256:d9ce8e6f0d8140e67b0f7c276d22bb5f3345ce7412558643c8b5c270db254b64", size = 29005158, upload-time = "2025-01-24T17:55:23.123Z" }, + { url = "https://files.pythonhosted.org/packages/90/4b/f75f0eb9527c943440c6ed90be7e97146a00699fee69f9d5aff577f15659/polars-1.21.0-cp39-abi3-win_amd64.whl", hash = "sha256:c4517abb008af890e4ca8fb6bb0372868381017af0ecadf9d062e2f91f50b276", size = 31729901, upload-time = "2025-01-24T17:55:28.192Z" }, + { url = "https://files.pythonhosted.org/packages/e6/a0/d48548f4c9e139b02eacfc074bfd02d98d9bb5f9bf9c03ec5649a481d8ff/polars-1.21.0-cp39-abi3-win_arm64.whl", hash = "sha256:6bb0ba805defb05b76fdca392e48d84d1f16403de5be25d4dd8cdc7fccfd4251", size = 28179572, upload-time = "2025-01-24T17:55:33.648Z" }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.51" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/6e/9d084c929dfe9e3bfe0c6a47e31f78a25c54627d64a66e884a8bf5474f1c/prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed", size = 428940, upload-time = "2025-04-15T09:18:47.731Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/4f/5249960887b1fbe561d9ff265496d170b55a735b76724f10ef19f9e40716/prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07", size = 387810, upload-time = "2025-04-15T09:18:44.753Z" }, +] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" }, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, +] + +[[package]] +name = "py-cpuinfo" +version = "9.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/37/a8/d832f7293ebb21690860d2e01d8115e5ff6f2ae8bbdc953f0eb0fa4bd2c7/py-cpuinfo-9.0.0.tar.gz", hash = "sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690", size = 104716, upload-time = "2022-10-25T20:38:06.303Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5", size = 22335, upload-time = "2022-10-25T20:38:27.636Z" }, +] + +[[package]] +name = "pycodestyle" +version = "2.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/aa/210b2c9aedd8c1cbeea31a50e42050ad56187754b34eb214c46709445801/pycodestyle-2.12.1.tar.gz", hash = "sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521", size = 39232, upload-time = "2024-08-04T20:26:54.576Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/d8/a211b3f85e99a0daa2ddec96c949cac6824bd305b040571b82a03dd62636/pycodestyle-2.12.1-py2.py3-none-any.whl", hash = "sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3", size = 31284, upload-time = "2024-08-04T20:26:53.173Z" }, +] + +[[package]] +name = "pydantic" +version = "2.10.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b7/ae/d5220c5c52b158b1de7ca89fc5edb72f304a70a4c540c84c8844bf4008de/pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236", size = 761681, upload-time = "2025-01-24T01:42:12.693Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/3c/8cc1cc84deffa6e25d2d0c688ebb80635dfdbf1dbea3e30c541c8cf4d860/pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584", size = 431696, upload-time = "2025-01-24T01:42:10.371Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.27.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443, upload-time = "2024-12-18T11:31:54.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/bc/fed5f74b5d802cf9a03e83f60f18864e90e3aed7223adaca5ffb7a8d8d64/pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa", size = 1895938, upload-time = "2024-12-18T11:27:14.406Z" }, + { url = "https://files.pythonhosted.org/packages/71/2a/185aff24ce844e39abb8dd680f4e959f0006944f4a8a0ea372d9f9ae2e53/pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c", size = 1815684, upload-time = "2024-12-18T11:27:16.489Z" }, + { url = "https://files.pythonhosted.org/packages/c3/43/fafabd3d94d159d4f1ed62e383e264f146a17dd4d48453319fd782e7979e/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a", size = 1829169, upload-time = "2024-12-18T11:27:22.16Z" }, + { url = "https://files.pythonhosted.org/packages/a2/d1/f2dfe1a2a637ce6800b799aa086d079998959f6f1215eb4497966efd2274/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5", size = 1867227, upload-time = "2024-12-18T11:27:25.097Z" }, + { url = "https://files.pythonhosted.org/packages/7d/39/e06fcbcc1c785daa3160ccf6c1c38fea31f5754b756e34b65f74e99780b5/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c", size = 2037695, upload-time = "2024-12-18T11:27:28.656Z" }, + { url = "https://files.pythonhosted.org/packages/7a/67/61291ee98e07f0650eb756d44998214231f50751ba7e13f4f325d95249ab/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7", size = 2741662, upload-time = "2024-12-18T11:27:30.798Z" }, + { url = "https://files.pythonhosted.org/packages/32/90/3b15e31b88ca39e9e626630b4c4a1f5a0dfd09076366f4219429e6786076/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a", size = 1993370, upload-time = "2024-12-18T11:27:33.692Z" }, + { url = "https://files.pythonhosted.org/packages/ff/83/c06d333ee3a67e2e13e07794995c1535565132940715931c1c43bfc85b11/pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236", size = 1996813, upload-time = "2024-12-18T11:27:37.111Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f7/89be1c8deb6e22618a74f0ca0d933fdcb8baa254753b26b25ad3acff8f74/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962", size = 2005287, upload-time = "2024-12-18T11:27:40.566Z" }, + { url = "https://files.pythonhosted.org/packages/b7/7d/8eb3e23206c00ef7feee17b83a4ffa0a623eb1a9d382e56e4aa46fd15ff2/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9", size = 2128414, upload-time = "2024-12-18T11:27:43.757Z" }, + { url = "https://files.pythonhosted.org/packages/4e/99/fe80f3ff8dd71a3ea15763878d464476e6cb0a2db95ff1c5c554133b6b83/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af", size = 2155301, upload-time = "2024-12-18T11:27:47.36Z" }, + { url = "https://files.pythonhosted.org/packages/2b/a3/e50460b9a5789ca1451b70d4f52546fa9e2b420ba3bfa6100105c0559238/pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4", size = 1816685, upload-time = "2024-12-18T11:27:50.508Z" }, + { url = "https://files.pythonhosted.org/packages/57/4c/a8838731cb0f2c2a39d3535376466de6049034d7b239c0202a64aaa05533/pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31", size = 1982876, upload-time = "2024-12-18T11:27:53.54Z" }, + { url = "https://files.pythonhosted.org/packages/c2/89/f3450af9d09d44eea1f2c369f49e8f181d742f28220f88cc4dfaae91ea6e/pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc", size = 1893421, upload-time = "2024-12-18T11:27:55.409Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e3/71fe85af2021f3f386da42d291412e5baf6ce7716bd7101ea49c810eda90/pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7", size = 1814998, upload-time = "2024-12-18T11:27:57.252Z" }, + { url = "https://files.pythonhosted.org/packages/a6/3c/724039e0d848fd69dbf5806894e26479577316c6f0f112bacaf67aa889ac/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15", size = 1826167, upload-time = "2024-12-18T11:27:59.146Z" }, + { url = "https://files.pythonhosted.org/packages/2b/5b/1b29e8c1fb5f3199a9a57c1452004ff39f494bbe9bdbe9a81e18172e40d3/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306", size = 1865071, upload-time = "2024-12-18T11:28:02.625Z" }, + { url = "https://files.pythonhosted.org/packages/89/6c/3985203863d76bb7d7266e36970d7e3b6385148c18a68cc8915fd8c84d57/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99", size = 2036244, upload-time = "2024-12-18T11:28:04.442Z" }, + { url = "https://files.pythonhosted.org/packages/0e/41/f15316858a246b5d723f7d7f599f79e37493b2e84bfc789e58d88c209f8a/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459", size = 2737470, upload-time = "2024-12-18T11:28:07.679Z" }, + { url = "https://files.pythonhosted.org/packages/a8/7c/b860618c25678bbd6d1d99dbdfdf0510ccb50790099b963ff78a124b754f/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048", size = 1992291, upload-time = "2024-12-18T11:28:10.297Z" }, + { url = "https://files.pythonhosted.org/packages/bf/73/42c3742a391eccbeab39f15213ecda3104ae8682ba3c0c28069fbcb8c10d/pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d", size = 1994613, upload-time = "2024-12-18T11:28:13.362Z" }, + { url = "https://files.pythonhosted.org/packages/94/7a/941e89096d1175d56f59340f3a8ebaf20762fef222c298ea96d36a6328c5/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b", size = 2002355, upload-time = "2024-12-18T11:28:16.587Z" }, + { url = "https://files.pythonhosted.org/packages/6e/95/2359937a73d49e336a5a19848713555605d4d8d6940c3ec6c6c0ca4dcf25/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474", size = 2126661, upload-time = "2024-12-18T11:28:18.407Z" }, + { url = "https://files.pythonhosted.org/packages/2b/4c/ca02b7bdb6012a1adef21a50625b14f43ed4d11f1fc237f9d7490aa5078c/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6", size = 2153261, upload-time = "2024-12-18T11:28:21.471Z" }, + { url = "https://files.pythonhosted.org/packages/72/9d/a241db83f973049a1092a079272ffe2e3e82e98561ef6214ab53fe53b1c7/pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c", size = 1812361, upload-time = "2024-12-18T11:28:23.53Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ef/013f07248041b74abd48a385e2110aa3a9bbfef0fbd97d4e6d07d2f5b89a/pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc", size = 1982484, upload-time = "2024-12-18T11:28:25.391Z" }, + { url = "https://files.pythonhosted.org/packages/10/1c/16b3a3e3398fd29dca77cea0a1d998d6bde3902fa2706985191e2313cc76/pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4", size = 1867102, upload-time = "2024-12-18T11:28:28.593Z" }, + { url = "https://files.pythonhosted.org/packages/d6/74/51c8a5482ca447871c93e142d9d4a92ead74de6c8dc5e66733e22c9bba89/pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0", size = 1893127, upload-time = "2024-12-18T11:28:30.346Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f3/c97e80721735868313c58b89d2de85fa80fe8dfeeed84dc51598b92a135e/pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef", size = 1811340, upload-time = "2024-12-18T11:28:32.521Z" }, + { url = "https://files.pythonhosted.org/packages/9e/91/840ec1375e686dbae1bd80a9e46c26a1e0083e1186abc610efa3d9a36180/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7", size = 1822900, upload-time = "2024-12-18T11:28:34.507Z" }, + { url = "https://files.pythonhosted.org/packages/f6/31/4240bc96025035500c18adc149aa6ffdf1a0062a4b525c932065ceb4d868/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934", size = 1869177, upload-time = "2024-12-18T11:28:36.488Z" }, + { url = "https://files.pythonhosted.org/packages/fa/20/02fbaadb7808be578317015c462655c317a77a7c8f0ef274bc016a784c54/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6", size = 2038046, upload-time = "2024-12-18T11:28:39.409Z" }, + { url = "https://files.pythonhosted.org/packages/06/86/7f306b904e6c9eccf0668248b3f272090e49c275bc488a7b88b0823444a4/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c", size = 2685386, upload-time = "2024-12-18T11:28:41.221Z" }, + { url = "https://files.pythonhosted.org/packages/8d/f0/49129b27c43396581a635d8710dae54a791b17dfc50c70164866bbf865e3/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2", size = 1997060, upload-time = "2024-12-18T11:28:44.709Z" }, + { url = "https://files.pythonhosted.org/packages/0d/0f/943b4af7cd416c477fd40b187036c4f89b416a33d3cc0ab7b82708a667aa/pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4", size = 2004870, upload-time = "2024-12-18T11:28:46.839Z" }, + { url = "https://files.pythonhosted.org/packages/35/40/aea70b5b1a63911c53a4c8117c0a828d6790483f858041f47bab0b779f44/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3", size = 1999822, upload-time = "2024-12-18T11:28:48.896Z" }, + { url = "https://files.pythonhosted.org/packages/f2/b3/807b94fd337d58effc5498fd1a7a4d9d59af4133e83e32ae39a96fddec9d/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4", size = 2130364, upload-time = "2024-12-18T11:28:50.755Z" }, + { url = "https://files.pythonhosted.org/packages/fc/df/791c827cd4ee6efd59248dca9369fb35e80a9484462c33c6649a8d02b565/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57", size = 2158303, upload-time = "2024-12-18T11:28:54.122Z" }, + { url = "https://files.pythonhosted.org/packages/9b/67/4e197c300976af185b7cef4c02203e175fb127e414125916bf1128b639a9/pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc", size = 1834064, upload-time = "2024-12-18T11:28:56.074Z" }, + { url = "https://files.pythonhosted.org/packages/1f/ea/cd7209a889163b8dcca139fe32b9687dd05249161a3edda62860430457a5/pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9", size = 1989046, upload-time = "2024-12-18T11:28:58.107Z" }, + { url = "https://files.pythonhosted.org/packages/bc/49/c54baab2f4658c26ac633d798dab66b4c3a9bbf47cff5284e9c182f4137a/pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b", size = 1885092, upload-time = "2024-12-18T11:29:01.335Z" }, + { url = "https://files.pythonhosted.org/packages/41/b1/9bc383f48f8002f99104e3acff6cba1231b29ef76cfa45d1506a5cad1f84/pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b", size = 1892709, upload-time = "2024-12-18T11:29:03.193Z" }, + { url = "https://files.pythonhosted.org/packages/10/6c/e62b8657b834f3eb2961b49ec8e301eb99946245e70bf42c8817350cbefc/pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154", size = 1811273, upload-time = "2024-12-18T11:29:05.306Z" }, + { url = "https://files.pythonhosted.org/packages/ba/15/52cfe49c8c986e081b863b102d6b859d9defc63446b642ccbbb3742bf371/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9", size = 1823027, upload-time = "2024-12-18T11:29:07.294Z" }, + { url = "https://files.pythonhosted.org/packages/b1/1c/b6f402cfc18ec0024120602bdbcebc7bdd5b856528c013bd4d13865ca473/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9", size = 1868888, upload-time = "2024-12-18T11:29:09.249Z" }, + { url = "https://files.pythonhosted.org/packages/bd/7b/8cb75b66ac37bc2975a3b7de99f3c6f355fcc4d89820b61dffa8f1e81677/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1", size = 2037738, upload-time = "2024-12-18T11:29:11.23Z" }, + { url = "https://files.pythonhosted.org/packages/c8/f1/786d8fe78970a06f61df22cba58e365ce304bf9b9f46cc71c8c424e0c334/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a", size = 2685138, upload-time = "2024-12-18T11:29:16.396Z" }, + { url = "https://files.pythonhosted.org/packages/a6/74/d12b2cd841d8724dc8ffb13fc5cef86566a53ed358103150209ecd5d1999/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e", size = 1997025, upload-time = "2024-12-18T11:29:20.25Z" }, + { url = "https://files.pythonhosted.org/packages/a0/6e/940bcd631bc4d9a06c9539b51f070b66e8f370ed0933f392db6ff350d873/pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4", size = 2004633, upload-time = "2024-12-18T11:29:23.877Z" }, + { url = "https://files.pythonhosted.org/packages/50/cc/a46b34f1708d82498c227d5d80ce615b2dd502ddcfd8376fc14a36655af1/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27", size = 1999404, upload-time = "2024-12-18T11:29:25.872Z" }, + { url = "https://files.pythonhosted.org/packages/ca/2d/c365cfa930ed23bc58c41463bae347d1005537dc8db79e998af8ba28d35e/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee", size = 2130130, upload-time = "2024-12-18T11:29:29.252Z" }, + { url = "https://files.pythonhosted.org/packages/f4/d7/eb64d015c350b7cdb371145b54d96c919d4db516817f31cd1c650cae3b21/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1", size = 2157946, upload-time = "2024-12-18T11:29:31.338Z" }, + { url = "https://files.pythonhosted.org/packages/a4/99/bddde3ddde76c03b65dfd5a66ab436c4e58ffc42927d4ff1198ffbf96f5f/pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130", size = 1834387, upload-time = "2024-12-18T11:29:33.481Z" }, + { url = "https://files.pythonhosted.org/packages/71/47/82b5e846e01b26ac6f1893d3c5f9f3a2eb6ba79be26eef0b759b4fe72946/pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee", size = 1990453, upload-time = "2024-12-18T11:29:35.533Z" }, + { url = "https://files.pythonhosted.org/packages/51/b2/b2b50d5ecf21acf870190ae5d093602d95f66c9c31f9d5de6062eb329ad1/pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b", size = 1885186, upload-time = "2024-12-18T11:29:37.649Z" }, + { url = "https://files.pythonhosted.org/packages/27/97/3aef1ddb65c5ccd6eda9050036c956ff6ecbfe66cb7eb40f280f121a5bb0/pydantic_core-2.27.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993", size = 1896475, upload-time = "2024-12-18T11:30:18.316Z" }, + { url = "https://files.pythonhosted.org/packages/ad/d3/5668da70e373c9904ed2f372cb52c0b996426f302e0dee2e65634c92007d/pydantic_core-2.27.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308", size = 1772279, upload-time = "2024-12-18T11:30:20.547Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9e/e44b8cb0edf04a2f0a1f6425a65ee089c1d6f9c4c2dcab0209127b6fdfc2/pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4", size = 1829112, upload-time = "2024-12-18T11:30:23.255Z" }, + { url = "https://files.pythonhosted.org/packages/1c/90/1160d7ac700102effe11616e8119e268770f2a2aa5afb935f3ee6832987d/pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf", size = 1866780, upload-time = "2024-12-18T11:30:25.742Z" }, + { url = "https://files.pythonhosted.org/packages/ee/33/13983426df09a36d22c15980008f8d9c77674fc319351813b5a2739b70f3/pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76", size = 2037943, upload-time = "2024-12-18T11:30:28.036Z" }, + { url = "https://files.pythonhosted.org/packages/01/d7/ced164e376f6747e9158c89988c293cd524ab8d215ae4e185e9929655d5c/pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118", size = 2740492, upload-time = "2024-12-18T11:30:30.412Z" }, + { url = "https://files.pythonhosted.org/packages/8b/1f/3dc6e769d5b7461040778816aab2b00422427bcaa4b56cc89e9c653b2605/pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630", size = 1995714, upload-time = "2024-12-18T11:30:34.358Z" }, + { url = "https://files.pythonhosted.org/packages/07/d7/a0bd09bc39283530b3f7c27033a814ef254ba3bd0b5cfd040b7abf1fe5da/pydantic_core-2.27.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54", size = 1997163, upload-time = "2024-12-18T11:30:37.979Z" }, + { url = "https://files.pythonhosted.org/packages/2d/bb/2db4ad1762e1c5699d9b857eeb41959191980de6feb054e70f93085e1bcd/pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f", size = 2005217, upload-time = "2024-12-18T11:30:40.367Z" }, + { url = "https://files.pythonhosted.org/packages/53/5f/23a5a3e7b8403f8dd8fc8a6f8b49f6b55c7d715b77dcf1f8ae919eeb5628/pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362", size = 2127899, upload-time = "2024-12-18T11:30:42.737Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ae/aa38bb8dd3d89c2f1d8362dd890ee8f3b967330821d03bbe08fa01ce3766/pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96", size = 2155726, upload-time = "2024-12-18T11:30:45.279Z" }, + { url = "https://files.pythonhosted.org/packages/98/61/4f784608cc9e98f70839187117ce840480f768fed5d386f924074bf6213c/pydantic_core-2.27.2-cp39-cp39-win32.whl", hash = "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e", size = 1817219, upload-time = "2024-12-18T11:30:47.718Z" }, + { url = "https://files.pythonhosted.org/packages/57/82/bb16a68e4a1a858bb3768c2c8f1ff8d8978014e16598f001ea29a25bf1d1/pydantic_core-2.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67", size = 1985382, upload-time = "2024-12-18T11:30:51.871Z" }, + { url = "https://files.pythonhosted.org/packages/46/72/af70981a341500419e67d5cb45abe552a7c74b66326ac8877588488da1ac/pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e", size = 1891159, upload-time = "2024-12-18T11:30:54.382Z" }, + { url = "https://files.pythonhosted.org/packages/ad/3d/c5913cccdef93e0a6a95c2d057d2c2cba347815c845cda79ddd3c0f5e17d/pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8", size = 1768331, upload-time = "2024-12-18T11:30:58.178Z" }, + { url = "https://files.pythonhosted.org/packages/f6/f0/a3ae8fbee269e4934f14e2e0e00928f9346c5943174f2811193113e58252/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3", size = 1822467, upload-time = "2024-12-18T11:31:00.6Z" }, + { url = "https://files.pythonhosted.org/packages/d7/7a/7bbf241a04e9f9ea24cd5874354a83526d639b02674648af3f350554276c/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f", size = 1979797, upload-time = "2024-12-18T11:31:07.243Z" }, + { url = "https://files.pythonhosted.org/packages/4f/5f/4784c6107731f89e0005a92ecb8a2efeafdb55eb992b8e9d0a2be5199335/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133", size = 1987839, upload-time = "2024-12-18T11:31:09.775Z" }, + { url = "https://files.pythonhosted.org/packages/6d/a7/61246562b651dff00de86a5f01b6e4befb518df314c54dec187a78d81c84/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc", size = 1998861, upload-time = "2024-12-18T11:31:13.469Z" }, + { url = "https://files.pythonhosted.org/packages/86/aa/837821ecf0c022bbb74ca132e117c358321e72e7f9702d1b6a03758545e2/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50", size = 2116582, upload-time = "2024-12-18T11:31:17.423Z" }, + { url = "https://files.pythonhosted.org/packages/81/b0/5e74656e95623cbaa0a6278d16cf15e10a51f6002e3ec126541e95c29ea3/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9", size = 2151985, upload-time = "2024-12-18T11:31:19.901Z" }, + { url = "https://files.pythonhosted.org/packages/63/37/3e32eeb2a451fddaa3898e2163746b0cffbbdbb4740d38372db0490d67f3/pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151", size = 2004715, upload-time = "2024-12-18T11:31:22.821Z" }, + { url = "https://files.pythonhosted.org/packages/29/0e/dcaea00c9dbd0348b723cae82b0e0c122e0fa2b43fa933e1622fd237a3ee/pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656", size = 1891733, upload-time = "2024-12-18T11:31:26.876Z" }, + { url = "https://files.pythonhosted.org/packages/86/d3/e797bba8860ce650272bda6383a9d8cad1d1c9a75a640c9d0e848076f85e/pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278", size = 1768375, upload-time = "2024-12-18T11:31:29.276Z" }, + { url = "https://files.pythonhosted.org/packages/41/f7/f847b15fb14978ca2b30262548f5fc4872b2724e90f116393eb69008299d/pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb", size = 1822307, upload-time = "2024-12-18T11:31:33.123Z" }, + { url = "https://files.pythonhosted.org/packages/9c/63/ed80ec8255b587b2f108e514dc03eed1546cd00f0af281e699797f373f38/pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd", size = 1979971, upload-time = "2024-12-18T11:31:35.755Z" }, + { url = "https://files.pythonhosted.org/packages/a9/6d/6d18308a45454a0de0e975d70171cadaf454bc7a0bf86b9c7688e313f0bb/pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc", size = 1987616, upload-time = "2024-12-18T11:31:38.534Z" }, + { url = "https://files.pythonhosted.org/packages/82/8a/05f8780f2c1081b800a7ca54c1971e291c2d07d1a50fb23c7e4aef4ed403/pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b", size = 1998943, upload-time = "2024-12-18T11:31:41.853Z" }, + { url = "https://files.pythonhosted.org/packages/5e/3e/fe5b6613d9e4c0038434396b46c5303f5ade871166900b357ada4766c5b7/pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b", size = 2116654, upload-time = "2024-12-18T11:31:44.756Z" }, + { url = "https://files.pythonhosted.org/packages/db/ad/28869f58938fad8cc84739c4e592989730bfb69b7c90a8fff138dff18e1e/pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2", size = 2152292, upload-time = "2024-12-18T11:31:48.613Z" }, + { url = "https://files.pythonhosted.org/packages/a1/0c/c5c5cd3689c32ed1fe8c5d234b079c12c281c051759770c05b8bed6412b5/pydantic_core-2.27.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35", size = 2004961, upload-time = "2024-12-18T11:31:52.446Z" }, +] + +[[package]] +name = "pyflakes" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/f9/669d8c9c86613c9d568757c7f5824bd3197d7b1c6c27553bc5618a27cce2/pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f", size = 63788, upload-time = "2024-01-05T00:28:47.703Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/d7/f1b7db88d8e4417c5d47adad627a93547f44bdc9028372dbd2313f34a855/pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a", size = 62725, upload-time = "2024-01-05T00:28:45.903Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" }, +] + +[[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-benchmark" +version = "5.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "py-cpuinfo" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/39/d0/a8bd08d641b393db3be3819b03e2d9bb8760ca8479080a26a5f6e540e99c/pytest-benchmark-5.1.0.tar.gz", hash = "sha256:9ea661cdc292e8231f7cd4c10b0319e56a2118e2c09d9f50e1b3d150d2aca105", size = 337810, upload-time = "2024-10-30T11:51:48.521Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/d6/b41653199ea09d5969d4e385df9bbfd9a100f28ca7e824ce7c0a016e3053/pytest_benchmark-5.1.0-py3-none-any.whl", hash = "sha256:922de2dfa3033c227c96da942d1878191afa135a29485fb942e85dff1c592c89", size = 44259, upload-time = "2024-10-30T11:51:45.94Z" }, +] + +[[package]] +name = "pytest-cov" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", extra = ["toml"] }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/be/45/9b538de8cef30e17c7b45ef42f538a94889ed6a16f2387a6c89e73220651/pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0", size = 66945, upload-time = "2024-10-29T20:13:35.363Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/3b/48e79f2cd6a61dbbd4807b4ed46cb564b4fd50a76166b1c4ea5c1d9e2371/pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35", size = 22949, upload-time = "2024-10-29T20:13:33.215Z" }, +] + +[[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 = "python-dotenv" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115, upload-time = "2024-01-23T06:33:00.505Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863, upload-time = "2024-01-23T06:32:58.246Z" }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199, upload-time = "2024-08-06T20:31:40.178Z" }, + { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758, upload-time = "2024-08-06T20:31:42.173Z" }, + { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463, upload-time = "2024-08-06T20:31:44.263Z" }, + { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280, upload-time = "2024-08-06T20:31:50.199Z" }, + { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239, upload-time = "2024-08-06T20:31:52.292Z" }, + { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802, upload-time = "2024-08-06T20:31:53.836Z" }, + { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527, upload-time = "2024-08-06T20:31:55.565Z" }, + { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052, upload-time = "2024-08-06T20:31:56.914Z" }, + { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774, upload-time = "2024-08-06T20:31:58.304Z" }, + { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload-time = "2024-08-06T20:32:03.408Z" }, + { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload-time = "2024-08-06T20:32:04.926Z" }, + { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload-time = "2024-08-06T20:32:06.459Z" }, + { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167, upload-time = "2024-08-06T20:32:08.338Z" }, + { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952, upload-time = "2024-08-06T20:32:14.124Z" }, + { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301, upload-time = "2024-08-06T20:32:16.17Z" }, + { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638, upload-time = "2024-08-06T20:32:18.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload-time = "2024-08-06T20:32:19.889Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload-time = "2024-08-06T20:32:21.273Z" }, + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, + { url = "https://files.pythonhosted.org/packages/65/d8/b7a1db13636d7fb7d4ff431593c510c8b8fca920ade06ca8ef20015493c5/PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", size = 184777, upload-time = "2024-08-06T20:33:25.896Z" }, + { url = "https://files.pythonhosted.org/packages/0a/02/6ec546cd45143fdf9840b2c6be8d875116a64076218b61d68e12548e5839/PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", size = 172318, upload-time = "2024-08-06T20:33:27.212Z" }, + { url = "https://files.pythonhosted.org/packages/0e/9a/8cc68be846c972bda34f6c2a93abb644fb2476f4dcc924d52175786932c9/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", size = 720891, upload-time = "2024-08-06T20:33:28.974Z" }, + { url = "https://files.pythonhosted.org/packages/e9/6c/6e1b7f40181bc4805e2e07f4abc10a88ce4648e7e95ff1abe4ae4014a9b2/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", size = 722614, upload-time = "2024-08-06T20:33:34.157Z" }, + { url = "https://files.pythonhosted.org/packages/3d/32/e7bd8535d22ea2874cef6a81021ba019474ace0d13a4819c2a4bce79bd6a/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", size = 737360, upload-time = "2024-08-06T20:33:35.84Z" }, + { url = "https://files.pythonhosted.org/packages/d7/12/7322c1e30b9be969670b672573d45479edef72c9a0deac3bb2868f5d7469/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", size = 699006, upload-time = "2024-08-06T20:33:37.501Z" }, + { url = "https://files.pythonhosted.org/packages/82/72/04fcad41ca56491995076630c3ec1e834be241664c0c09a64c9a2589b507/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", size = 723577, upload-time = "2024-08-06T20:33:39.389Z" }, + { url = "https://files.pythonhosted.org/packages/ed/5e/46168b1f2757f1fcd442bc3029cd8767d88a98c9c05770d8b420948743bb/PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", size = 144593, upload-time = "2024-08-06T20:33:46.63Z" }, + { url = "https://files.pythonhosted.org/packages/19/87/5124b1c1f2412bb95c59ec481eaf936cd32f0fe2a7b16b97b81c4c017a6a/PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", size = 162312, upload-time = "2024-08-06T20:33:49.073Z" }, +] + +[[package]] +name = "requests" +version = "2.32.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload-time = "2024-05-29T15:37:49.536Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" }, +] + +[[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 = "snowballstemmer" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575, upload-time = "2025-05-09T16:34:51.843Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z" }, +] + +[[package]] +name = "sphinx" +version = "6.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils" }, + { name = "imagesize" }, + { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/6d/392defcc95ca48daf62aecb89550143e97a4651275e62a3d7755efe35a3a/Sphinx-6.2.1.tar.gz", hash = "sha256:6d56a34697bb749ffa0152feafc4b19836c755d90a7c59b72bc7dfd371b9cc6b", size = 6681092, upload-time = "2023-04-25T11:01:40.914Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/d8/45ba6097c39ba44d9f0e1462fb232e13ca4ddb5aea93a385dcfa964687da/sphinx-6.2.1-py3-none-any.whl", hash = "sha256:97787ff1fa3256a3eef9eda523a63dbf299f7b47e053cfcf684a1c2a8380c912", size = 3024615, upload-time = "2023-04-25T11:01:08.562Z" }, +] + +[[package]] +name = "sphinx-sitemap" +version = "2.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/ed/96cc112b671e06df01c8306c25ce3331ccfece0d30235e32eb039afc8094/sphinx_sitemap-2.6.0.tar.gz", hash = "sha256:5e0c66b9f2e371ede80c659866a9eaad337d46ab02802f9c7e5f7bc5893c28d2", size = 6042, upload-time = "2024-04-29T00:18:13.399Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/d4/dffb4da380be24fd390d284634735d6fba560980014050e52569c04d215b/sphinx_sitemap-2.6.0-py3-none-any.whl", hash = "sha256:7478e417d141f99c9af27ccd635f44c03a471a08b20e778a0f9daef7ace1d30b", size = 5632, upload-time = "2024-04-29T00:18:12.147Z" }, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" }, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" }, +] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" }, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, +] + +[[package]] +name = "sphinxemoji" +version = "0.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/47/36/12a061ef2a2c2001c1b083d456db680f59074aef39f9cdd83a7937a4d626/sphinxemoji-0.3.1.tar.gz", hash = "sha256:23ecff1f1e765393e49083b45386b7da81ced97c9a18a1dfd191460a97da3b11", size = 46377, upload-time = "2024-01-18T18:07:44.53Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/9f/c872bbf4a2588b700a972d48c6e39087e4d33b3e76c563b4902e5abfbf13/sphinxemoji-0.3.1-py3-none-any.whl", hash = "sha256:dae483695f8d1e90a28a6e9bbccc08d256202afcc1d0fbd33b51b3b4352d439e", size = 46074, upload-time = "2024-01-18T18:07:42.496Z" }, +] + +[[package]] +name = "stack-data" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asttokens" }, + { name = "executing" }, + { name = "pure-eval" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, +] + +[[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 = "tomli-w" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/75/241269d1da26b624c0d5e110e8149093c759b7a286138f4efd61a60e75fe/tomli_w-1.2.0.tar.gz", hash = "sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021", size = 7184, upload-time = "2025-01-15T12:07:24.262Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/18/c86eb8e0202e32dd3df50d43d7ff9854f8e0603945ff398974c1d91ac1ef/tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90", size = 6675, upload-time = "2025-01-15T12:07:22.074Z" }, +] + +[[package]] +name = "traitlets" +version = "5.14.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621, upload-time = "2024-04-19T11:11:49.746Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" }, +] + +[[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 = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, +] + +[[package]] +name = "urllib3" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672, upload-time = "2025-04-10T15:23:39.232Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload-time = "2025-04-10T15:23:37.377Z" }, +] + +[[package]] +name = "virtualenv" +version = "20.31.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/56/2c/444f465fb2c65f40c3a104fd0c495184c4f2336d65baf398e3c75d72ea94/virtualenv-20.31.2.tar.gz", hash = "sha256:e10c0a9d02835e592521be48b332b6caee6887f332c111aa79a09b9e79efc2af", size = 6076316, upload-time = "2025-05-08T17:58:23.811Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/40/b1c265d4b2b62b58576588510fc4d1fe60a86319c8de99fd8e9fec617d2c/virtualenv-20.31.2-py3-none-any.whl", hash = "sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11", size = 6057982, upload-time = "2025-05-08T17:58:21.15Z" }, +] + +[[package]] +name = "wcwidth" +version = "0.2.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301, upload-time = "2024-01-06T02:10:57.829Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166, upload-time = "2024-01-06T02:10:55.763Z" }, +] + +[[package]] +name = "zipp" +version = "3.22.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/12/b6/7b3d16792fdf94f146bed92be90b4eb4563569eca91513c8609aebf0c167/zipp-3.22.0.tar.gz", hash = "sha256:dd2f28c3ce4bc67507bfd3781d21b7bb2be31103b51a4553ad7d90b84e57ace5", size = 25257, upload-time = "2025-05-26T14:46:32.217Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/da/f64669af4cae46f17b90798a827519ce3737d31dbafad65d391e49643dc4/zipp-3.22.0-py3-none-any.whl", hash = "sha256:fe208f65f2aca48b81f9e6fd8cf7b8b32c26375266b009b413d45306b6148343", size = 9796, upload-time = "2025-05-26T14:46:30.775Z" }, +] From bce634391a30acec7abd13674688592eefbad5ea Mon Sep 17 00:00:00 2001 From: Mauricio Villegas <5780272+mauvilsa@users.noreply.github.com> Date: Wed, 28 May 2025 14:33:09 +0200 Subject: [PATCH 07/39] Fix array bugs --- deepdiff/colored_view.py | 47 ++++++++++++++++++++------------------ deepdiff/commands.py | 2 +- deepdiff/diff.py | 4 ++-- tests/test_colored_view.py | 30 ++++++++++++++++++++++++ 4 files changed, 58 insertions(+), 25 deletions(-) diff --git a/deepdiff/colored_view.py b/deepdiff/colored_view.py index 5b0008c3..2463dcbf 100644 --- a/deepdiff/colored_view.py +++ b/deepdiff/colored_view.py @@ -4,6 +4,9 @@ from importlib.util import find_spec from typing import Any, Dict +from deepdiff.model import TextResult, TreeResult + + if os.name == "nt" and find_spec("colorama"): import colorama @@ -19,30 +22,31 @@ class ColoredView: """A view that shows JSON with color-coded differences.""" - def __init__(self, t2, tree_results, verbose_level=1, compact=False): + def __init__(self, t2: Any, tree_result: TreeResult, compact: bool = False): self.t2 = t2 - self.tree = tree_results - self.verbose_level = verbose_level + self.tree = tree_result self.compact = compact self.diff_paths = self._collect_diff_paths() def _collect_diff_paths(self) -> Dict[str, str]: """Collect all paths that have differences and their types.""" + text_result = TextResult(tree_results=self.tree, verbose_level=2) diff_paths = {} - for diff_type, items in self.tree.items(): + for diff_type, items in text_result.items(): + if not items: + continue try: - iterator = iter(items) + iter(items) except TypeError: continue - for item in items: - if type(item).__name__ == "DiffLevel": - path = item.path() - if diff_type in ('values_changed', 'type_changes'): - diff_paths[path] = ('changed', item.t1, item.t2) - elif diff_type in ('dictionary_item_added', 'iterable_item_added', 'set_item_added'): - diff_paths[path] = ('added', None, item.t2) - elif diff_type in ('dictionary_item_removed', 'iterable_item_removed', 'set_item_removed'): - diff_paths[path] = ('removed', item.t1, None) + for path, item in items.items(): + if diff_type in ("values_changed", "type_changes"): + changed_path = item.get("new_path") or path + diff_paths[changed_path] = ("changed", item["old_value"], item["new_value"]) + elif diff_type in ("dictionary_item_added", "iterable_item_added", "set_item_added"): + diff_paths[path] = ("added", None, item) + elif diff_type in ("dictionary_item_removed", "iterable_item_removed", "set_item_removed"): + diff_paths[path] = ("removed", item, None) return diff_paths def _format_value(self, value: Any) -> str: @@ -112,14 +116,13 @@ def _colorize_json(self, obj: Any, path: str = 'root', indent: int = 0) -> str: self._colorize_skip_paths.add(f"{path}[{index}]") items = [] - index = 0 - for value in obj: - new_path = f"{path}[{index}]" - while index == next(iter(removed_map), None): - items.append(f'{next_indent}{RED}{self._format_value(removed_map.pop(index))}{RESET}') - index += 1 - items.append(f'{next_indent}{self._colorize_json(value, new_path, indent + 1)}') - index += 1 + remove_index = 0 + for index, value in enumerate(obj): + while remove_index == next(iter(removed_map), None): + items.append(f'{next_indent}{RED}{self._format_value(removed_map.pop(remove_index))}{RESET}') + remove_index += 1 + items.append(f'{next_indent}{self._colorize_json(value, f"{path}[{index}]", indent + 1)}') + remove_index += 1 for value in removed_map.values(): items.append(f'{next_indent}{RED}{self._format_value(value)}{RESET}') return '[\n' + ',\n'.join(items) + f'\n{current_indent}' + ']' diff --git a/deepdiff/commands.py b/deepdiff/commands.py index eae0ff44..0543aa18 100644 --- a/deepdiff/commands.py +++ b/deepdiff/commands.py @@ -54,7 +54,7 @@ def cli(): @click.option('--significant-digits', required=False, default=None, type=int, show_default=True) @click.option('--truncate-datetime', required=False, type=click.Choice(['second', 'minute', 'hour', 'day'], case_sensitive=True), show_default=True, default=None) @click.option('--verbose-level', required=False, default=1, type=click.IntRange(0, 2), show_default=True) -@click.option('--view', required=False, type=click.Choice(['tree', 'colored', 'colored_compact'], case_sensitive=True)) +@click.option('--view', required=False, type=click.Choice(['tree', 'colored', 'colored_compact'], case_sensitive=True), show_default=True, default='tree') @click.option('--debug', is_flag=True, show_default=False) def diff( *args, **kwargs diff --git a/deepdiff/diff.py b/deepdiff/diff.py index 02fc9e35..fd154e59 100755 --- a/deepdiff/diff.py +++ b/deepdiff/diff.py @@ -1766,9 +1766,9 @@ def _get_view_results(self, view): elif view == DELTA_VIEW: result = self._to_delta_dict(report_repetition_required=False) elif view == COLORED_VIEW: - result = ColoredView(self.t2, tree_results=result, verbose_level=self.verbose_level) + result = ColoredView(t2=self.t2, tree_result=self.tree, compact=False) elif view == COLORED_COMPACT_VIEW: - result = ColoredView(self.t2, tree_results=result, verbose_level=self.verbose_level, compact=True) + result = ColoredView(t2=self.t2, tree_result=self.tree, compact=True) else: raise ValueError(INVALID_VIEW_MSG.format(view)) return result diff --git a/tests/test_colored_view.py b/tests/test_colored_view.py index 360e8d48..3c8c1a5b 100644 --- a/tests/test_colored_view.py +++ b/tests/test_colored_view.py @@ -183,6 +183,36 @@ def test_colored_view_list_no_changes_with_ignore_order(): assert result == expected +def test_colored_view_list_with_ignore_order(): + t1 = { + "hobbies": [ + "reading", + "hiking" + ] + } + + t2 = { + "hobbies": [ + "hiking", + "painting", + "coding" + ] + } + + diff = DeepDiff(t1, t2, view=COLORED_VIEW, ignore_order=True) + result = str(diff) + + expected = f'''{{ + "hobbies": [ + {RED}"reading"{RESET}, + "hiking", + {GREEN}"painting"{RESET}, + {GREEN}"coding"{RESET} + ] +}}''' + assert result == expected + + def test_colored_view_no_changes(): t1 = {"a": 1, "b": 2} t2 = {"a": 1, "b": 2} From 5a7ded314a18966c867394284e0c0c7bb3d1188f Mon Sep 17 00:00:00 2001 From: Sep Dehpour Date: Sat, 7 Jun 2025 00:51:13 -0700 Subject: [PATCH 08/39] adding support to ip addresses --- deepdiff/helper.py | 2 +- deepdiff/serialization.py | 3 +++ tests/test_diff_other.py | 7 +++++++ tests/test_serialization.py | 17 +++++++++++++++++ 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/deepdiff/helper.py b/deepdiff/helper.py index f06b315c..b023c214 100644 --- a/deepdiff/helper.py +++ b/deepdiff/helper.py @@ -188,7 +188,7 @@ def get_semvar_as_integer(version): only_complex_number = (complex,) + numpy_complex_numbers only_numbers = (int, float, complex, Decimal) + numpy_numbers datetimes = (datetime.datetime, datetime.date, datetime.timedelta, datetime.time, np_datetime64) -ipranges = (ipaddress.IPv4Interface, ipaddress.IPv6Interface, ipaddress.IPv4Network, ipaddress.IPv6Network) +ipranges = (ipaddress.IPv4Interface, ipaddress.IPv6Interface, ipaddress.IPv4Network, ipaddress.IPv6Network, ipaddress.IPv4Address, ipaddress.IPv6Address) uuids = (uuid.UUID, ) times = (datetime.datetime, datetime.time,np_datetime64) numbers: Tuple = only_numbers + datetimes diff --git a/deepdiff/serialization.py b/deepdiff/serialization.py index 1c18a264..8c29a5ad 100644 --- a/deepdiff/serialization.py +++ b/deepdiff/serialization.py @@ -32,6 +32,7 @@ pydantic_base_model_type, PydanticBaseModel, NotPresent, + ipranges, ) from deepdiff.model import DeltaResult @@ -114,6 +115,7 @@ class UnsupportedFormatErr(TypeError): 'OrderedDict': collections.OrderedDict, 'Pattern': re.Pattern, 'iprange': str, + 'IPv4Address': str, } @@ -612,6 +614,7 @@ def _serialize_tuple(value): tuple: _serialize_tuple, Mapping: dict, NotPresent: str, + ipranges: str, } if PydanticBaseModel is not pydantic_base_model_type: diff --git a/tests/test_diff_other.py b/tests/test_diff_other.py index 067ee669..316b98b9 100644 --- a/tests/test_diff_other.py +++ b/tests/test_diff_other.py @@ -12,6 +12,7 @@ PURGE_LEVEL_RANGE_MSG) from concurrent.futures.process import ProcessPoolExecutor from concurrent.futures import as_completed +from ipaddress import IPv4Address # Only the prep part of DeepHash. We don't need to test the actual hash function. DeepHashPrep = partial(DeepHash, apply_hash=False) @@ -200,3 +201,9 @@ def test_multi_processing3_deephash(self): for future in as_completed(futures, timeout=10): assert not future._exception assert expected_result == future._result + + def test_ip_address_diff(self): + ip1 = IPv4Address("128.0.0.1") + ip2 = IPv4Address("128.0.0.2") + diff = DeepDiff(ip1, ip2) + assert {'values_changed': {'root': {'new_value': IPv4Address('128.0.0.2'), 'old_value': IPv4Address('128.0.0.1')}}} == diff diff --git a/tests/test_serialization.py b/tests/test_serialization.py index 6f16cdca..0bfe3fba 100644 --- a/tests/test_serialization.py +++ b/tests/test_serialization.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +from ipaddress import IPv4Address import os import json import sys @@ -9,6 +10,7 @@ from pickle import UnpicklingError from decimal import Decimal from collections import Counter +from pydantic import BaseModel, IPvAnyAddress from deepdiff import DeepDiff from deepdiff.helper import pypy3, py_current_version, np_ndarray, Opcode, SetOrdered from deepdiff.serialization import ( @@ -25,6 +27,11 @@ t2 = {1: 1, 2: 2, 3: 3, 4: {"a": "hello", "b": "world\n\n\nEnd"}} +class SampleSchema(BaseModel): + works: bool = False + ips: list[IPvAnyAddress] + + class SomeStats(NamedTuple): counter: Optional[Counter] context_aware_counter: Optional[Counter] = None @@ -115,6 +122,16 @@ def test_to_dict_at_different_verbose_level(self, verbose_level, expected): ddiff = DeepDiff(t1, t2, verbose_level=verbose_level) assert expected == ddiff.to_dict() + def test_serialize_pydantic_model(self): + obj = SampleSchema( + works=True, + ips=["128.0.0.1"] + ) + serialized = json_dumps(obj) + obj_again = json_loads(serialized) + assert {'works': True, 'ips': ['128.0.0.1']} == obj_again + assert {'works': True, 'ips': [IPv4Address('128.0.0.1')]} == obj.model_dump() + @pytest.mark.skipif(pypy3, reason='clevercsv is not supported in pypy3') class TestLoadContet: From 662f9ba7e30066665b4afe496e0236b7759d38ae Mon Sep 17 00:00:00 2001 From: Sep Dehpour Date: Sat, 7 Jun 2025 00:53:00 -0700 Subject: [PATCH 09/39] adding ip address modules safe for import --- deepdiff/serialization.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/deepdiff/serialization.py b/deepdiff/serialization.py index 8c29a5ad..c50171e4 100644 --- a/deepdiff/serialization.py +++ b/deepdiff/serialization.py @@ -86,6 +86,12 @@ class UnsupportedFormatErr(TypeError): 'collections.OrderedDict', 're.Pattern', 'deepdiff.helper.Opcode', + 'ipaddress.IPv4Interface', + 'ipaddress.IPv6Interface', + 'ipaddress.IPv4Network', + 'ipaddress.IPv6Network', + 'ipaddress.IPv4Address', + 'ipaddress.IPv6Address', } From c7e581f1f2d63e18eda18326f95bea60639c3af7 Mon Sep 17 00:00:00 2001 From: Sep Dehpour Date: Sat, 7 Jun 2025 00:54:46 -0700 Subject: [PATCH 10/39] adding support to ip addresses --- deepdiff/serialization.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/deepdiff/serialization.py b/deepdiff/serialization.py index c50171e4..0de9d69e 100644 --- a/deepdiff/serialization.py +++ b/deepdiff/serialization.py @@ -11,6 +11,7 @@ import decimal # NOQA import orderly_set # NOQA import collections # NOQA +import ipaddress from copy import deepcopy, copy from functools import partial from collections.abc import Mapping @@ -121,7 +122,8 @@ class UnsupportedFormatErr(TypeError): 'OrderedDict': collections.OrderedDict, 'Pattern': re.Pattern, 'iprange': str, - 'IPv4Address': str, + 'IPv4Address': ipaddress.IPv4Address, + 'IPv6Address': ipaddress.IPv6Address, } From d5f23df4fac070b0d839ec573ee4b0e70650b5dd Mon Sep 17 00:00:00 2001 From: Paul Date: Fri, 20 Jun 2025 16:58:10 +0200 Subject: [PATCH 11/39] Adding support for applying deltas to NamedTuple --- deepdiff/delta.py | 20 +++++++++++++++----- tests/test_delta.py | 12 ++++++++++++ 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/deepdiff/delta.py b/deepdiff/delta.py index 6916c992..ff7cd207 100644 --- a/deepdiff/delta.py +++ b/deepdiff/delta.py @@ -330,11 +330,21 @@ def _set_new_value(self, parent, parent_to_obj_elem, parent_to_obj_action, Set the element value on an object and if necessary convert the object to the proper mutable type """ if isinstance(obj, tuple): - # convert this object back to a tuple later - obj = self._coerce_obj( - parent, obj, path, parent_to_obj_elem, - parent_to_obj_action, elements, - to_type=list, from_type=tuple) + # Check if it's a NamedTuple and use _replace() to generate a new copy with the change + if hasattr(obj, '_fields') and hasattr(obj, '_replace'): + if action == GETATTR: + obj = obj._replace(**{elem: new_value}) + if parent: + self._simple_set_elem_value(obj=parent, path_for_err_reporting=path, + elem=parent_to_obj_elem, value=obj, + action=parent_to_obj_action) + return + else: + # Regular tuple - convert this object back to a tuple later + obj = self._coerce_obj( + parent, obj, path, parent_to_obj_elem, + parent_to_obj_action, elements, + to_type=list, from_type=tuple) if elem != 0 and self.force and isinstance(obj, list) and len(obj) == 0: # it must have been a dictionary obj = {} diff --git a/tests/test_delta.py b/tests/test_delta.py index 0dbc8956..35fee924 100644 --- a/tests/test_delta.py +++ b/tests/test_delta.py @@ -1,5 +1,6 @@ import copy import datetime +from typing import NamedTuple import pytest import os import io @@ -624,6 +625,17 @@ def compare_func(item1, item2, level=None): assert flat_rows_list == preserved_flat_dict_list assert flat_rows_list == flat_rows_list_again + def test_namedtuple_add_delta(self): + class Point(NamedTuple): + x: int + y: int + + p1 = Point(1, 1) + p2 = Point(1, 2) + diff = DeepDiff(p1, p2) + delta = Delta(diff) + assert p2 == p1 + delta + picklalbe_obj_without_item = PicklableClass(11) del picklalbe_obj_without_item.item From 10cb34250e37fd50888b5cc7fdd94d62b943161e Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 23 Jun 2025 16:34:49 +0200 Subject: [PATCH 12/39] Handle one more case where NamedTuple has a frozenset. --- deepdiff/delta.py | 7 ++++++- tests/test_delta.py | 10 +++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/deepdiff/delta.py b/deepdiff/delta.py index ff7cd207..065f5405 100644 --- a/deepdiff/delta.py +++ b/deepdiff/delta.py @@ -719,7 +719,12 @@ def _do_set_or_frozenset_item(self, items, func): obj = self._get_elem_and_compare_to_old_value( parent, path_for_err_reporting=path, expected_old_value=None, elem=elem, action=action, forced_old_value=set()) new_value = getattr(obj, func)(value) - self._simple_set_elem_value(parent, path_for_err_reporting=path, elem=elem, value=new_value, action=action) + if hasattr(parent, '_fields') and hasattr(parent, '_replace'): + # Handle parent NamedTuple by creating a new instance with _replace(). Will not work with nested objects. + new_parent = parent._replace(**{elem: new_value}) + self.root = new_parent + else: + self._simple_set_elem_value(parent, path_for_err_reporting=path, elem=elem, value=new_value, action=action) def _do_ignore_order_get_old(self, obj, remove_indexes_per_path, fixed_indexes_values, path_for_err_reporting): """ diff --git a/tests/test_delta.py b/tests/test_delta.py index 35fee924..aa7e9f40 100644 --- a/tests/test_delta.py +++ b/tests/test_delta.py @@ -636,7 +636,15 @@ class Point(NamedTuple): delta = Delta(diff) assert p2 == p1 + delta - + def test_namedtuple_frozenset_add_delta(self): + class Article(NamedTuple): + tags: frozenset + a1 = Article(frozenset(["a" ])) + a2 = Article(frozenset(["a", "b"])) + diff = DeepDiff(a1, a2) + delta = Delta(diff) + assert a2 == a1 + delta + picklalbe_obj_without_item = PicklableClass(11) del picklalbe_obj_without_item.item From f40fa0eeb302d845a57c888dddf6c17baf982b5b Mon Sep 17 00:00:00 2001 From: Romain Geissler Date: Sun, 29 Jun 2025 13:56:42 +0000 Subject: [PATCH 13/39] Fix test_delta.py with Python 3.14. --- tests/test_delta.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_delta.py b/tests/test_delta.py index 0dbc8956..2a11d648 100644 --- a/tests/test_delta.py +++ b/tests/test_delta.py @@ -195,7 +195,8 @@ def test_simple_set_elem_value(self): delta._simple_set_elem_value( obj={}, elem={1}, value=None, action=GET, path_for_err_reporting='mypath') assert str(excinfo.value) in {"Failed to set mypath due to unhashable type: 'set'", - "Failed to set mypath due to 'set' objects are unhashable"} + "Failed to set mypath due to 'set' objects are unhashable", + "Failed to set mypath due to cannot use 'set' as a dict key (unhashable type: 'set')"} def test_simple_delete_elem(self): delta = Delta({}, raise_errors=True) From 10baf2ac268fac9a5459ae1051a5e8e99472ba9c Mon Sep 17 00:00:00 2001 From: Sep Dehpour Date: Wed, 2 Jul 2025 12:59:01 -0700 Subject: [PATCH 14/39] serializing properties --- deepdiff/serialization.py | 28 +++++++++++++++++++++++++++- tests/test_serialization.py | 15 +++++++++++++-- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/deepdiff/serialization.py b/deepdiff/serialization.py index 0de9d69e..2b5819a4 100644 --- a/deepdiff/serialization.py +++ b/deepdiff/serialization.py @@ -643,7 +643,33 @@ def _convertor(obj): # This is to handle reverse() which creates a generator of type list_reverseiterator if obj.__class__.__name__ == 'list_reverseiterator': return list(copy(obj)) - raise TypeError('We do not know how to convert {} of type {} for json serialization. Please pass the default_mapping parameter with proper mapping of the object to a basic python type.'.format(obj, type(obj))) + # 3) gather @property values by scanning __class__.__dict__ and bases + props = {} + for cls in obj.__class__.__mro__: + for name, descriptor in cls.__dict__.items(): + if isinstance(descriptor, property) and not name.startswith('_'): + try: + props[name] = getattr(obj, name) + except Exception: + # skip properties that error out + pass + if props: + return props + + # 4) fallback: public __dict__ entries + if hasattr(obj, '__dict__'): + return { + k: v + for k, v in vars(obj).items() + if not k.startswith('_') + } + + # 5) give up + raise TypeError( + f"Don't know how to JSON-serialize {obj!r} " + f"(type {type(obj).__name__}); " + "consider adding it to default_mapping." + ) return _convertor diff --git a/tests/test_serialization.py b/tests/test_serialization.py index 0bfe3fba..5d2f2171 100644 --- a/tests/test_serialization.py +++ b/tests/test_serialization.py @@ -91,8 +91,7 @@ class B: t1 = A() t2 = B() ddiff = DeepDiff(t1, t2) - with pytest.raises(TypeError): - ddiff.to_json() + assert r'{"type_changes":{"root":{"old_type":"A","new_type":"B","old_value":{},"new_value":{}}}}' == ddiff.to_json() def test_serialize_custom_objects_with_default_mapping(self): class A: @@ -206,6 +205,18 @@ def get_the_pickle(): assert expected_msg == str(excinfo.value) + def test_seriaize_property(self): + + class Sample: + @property + def something(self): + return 10 + + sample = Sample() + serialized = json_dumps(sample) + assert '{"something":10}' == serialized + + class TestDeepDiffPretty: """Tests for pretty() method of DeepDiff""" From 8d1399b4fd1ca151fff00031be8e12e255ecf23c Mon Sep 17 00:00:00 2001 From: Sep Dehpour Date: Wed, 2 Jul 2025 14:06:16 -0700 Subject: [PATCH 15/39] adding Claude.md --- CLAUDE.md | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..b6510f76 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,79 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +DeepDiff is a Python library for deep comparison, searching, and hashing of Python objects. It provides: +- **DeepDiff**: Deep difference detection between objects +- **DeepSearch**: Search for objects within other objects +- **DeepHash**: Content-based hashing for any object +- **Delta**: Git-like diff objects that can be applied to other objects +- **CLI**: Command-line interface via `deep` command + +## Development Commands + +### Setup +```bash +# Install with all development dependencies +pip install -e ".[cli,coverage,dev,docs,static,test]" +# OR using uv (recommended) +uv sync --all-extras +``` + +### Testing +```bash +# Run tests with coverage +pytest --cov=deepdiff --cov-report term-missing + +# Run tests including slow ones +pytest --cov=deepdiff --runslow + +# Run single test file +pytest tests/test_diff_text.py + +# Run tests across multiple Python versions +nox -s pytest +``` + +### Quality Checks +```bash +# Linting (max line length: 120) +nox -s flake8 + +# Type checking +nox -s mypy + +# Run all quality checks +nox +``` + +## Architecture + +### Core Structure +- **deepdiff/diff.py**: Main DeepDiff implementation (most complex component) +- **deepdiff/deephash.py**: DeepHash functionality +- **deepdiff/base.py**: Shared base classes and functionality +- **deepdiff/model.py**: Core data structures and result objects +- **deepdiff/helper.py**: Utility functions and type definitions +- **deepdiff/delta.py**: Delta objects for applying changes + +### Key Patterns +- **Inheritance**: `Base` class provides common functionality with mixins +- **Result Objects**: Different result formats (`ResultDict`, `TreeResult`, `TextResult`) +- **Path Navigation**: Consistent path notation for nested object access +- **Performance**: LRU caching and numpy array optimization + +### Testing +- Located in `/tests/` directory +- Organized by functionality (e.g., `test_diff_text.py`, `test_hash.py`) +- Aims for ~100% test coverage +- Uses pytest with comprehensive fixtures + +## Development Notes + +- **Python Support**: 3.9+ and PyPy3 +- **Main Branch**: `master` (PRs typically go to `dev` branch) +- **Build System**: Modern `pyproject.toml` with `flit_core` +- **Dependencies**: Core dependency is `orderly-set>=5.4.1,<6` +- **CLI Tool**: Available as `deep` command after installation with `[cli]` extra \ No newline at end of file From 2b1a039d711970eee1b44c3a4ca513b889d5fa23 Mon Sep 17 00:00:00 2001 From: Akshat Gupta Date: Tue, 8 Jul 2025 12:28:09 +0000 Subject: [PATCH 16/39] gh-535: UUID set comparison failure --- deepdiff/deephash.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/deepdiff/deephash.py b/deepdiff/deephash.py index 47b900e5..579fda35 100644 --- a/deepdiff/deephash.py +++ b/deepdiff/deephash.py @@ -1,6 +1,7 @@ #!/usr/bin/env python import logging import datetime +import uuid from typing import Union, Optional, Any, List, TYPE_CHECKING from collections.abc import Iterable, MutableMapping from collections import defaultdict @@ -566,6 +567,17 @@ def _hash(self, obj, parent, parents_ids=EMPTY_FROZENSET): elif isinstance(obj, ipranges): result = self._prep_ipranges(obj) + elif isinstance(obj, uuid.UUID): + # Handle UUID objects (including uuid6.UUID) by using their integer value + result = f"uuid:{obj.int}" + if self.apply_hash: + result = self.hasher(result) + try: + self.hashes[obj] = (result, counts) + except TypeError: + self.hashes[get_id(obj)] = (result, counts) + return result, counts + elif isinstance(obj, MutableMapping): result, counts = self._prep_dict(obj=obj, parent=parent, parents_ids=parents_ids) From b9af75d5776024f228207166e07bff027f668b1e Mon Sep 17 00:00:00 2001 From: Akshat Gupta Date: Wed, 9 Jul 2025 17:59:16 +0000 Subject: [PATCH 17/39] Adding test cases --- tests/test_hash.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/test_hash.py b/tests/test_hash.py index 6d09a176..41d1413c 100755 --- a/tests/test_hash.py +++ b/tests/test_hash.py @@ -208,6 +208,30 @@ def test_numpy_datetime64(self): later_hash = DeepHash(later) assert a_hash[a] != later_hash[later] + def test_uuid6_hash_positive(self): + """Positive test case: Same UUID objects should produce the same hash.""" + import uuid6 + uuid_obj = uuid6.uuid7() + hash1 = DeepHash(uuid_obj) + hash2 = DeepHash(uuid_obj) + assert hash1[uuid_obj] == hash2[uuid_obj] + + import uuid + regular_uuid = uuid.uuid4() + hash_regular = DeepHash(regular_uuid) + assert hash_regular[regular_uuid] is not unprocessed + + def test_uuid6_deepdiff_negative(self): + """Negative test case: DeepDiff should detect differences between sets containing different UUID objects.""" + import uuid6 + dummy_id_1 = uuid6.uuid7() + dummy_id_2 = uuid6.uuid7() + set1 = {dummy_id_1} + set2 = {dummy_id_1, dummy_id_2} + diff = DeepDiff(set1, set2) + assert diff != {} + assert 'set_item_added' in diff + assert str(dummy_id_2) in str(diff) class TestDeepHashPrep: """DeepHashPrep Tests covering object serialization.""" From 5c815c80f4f94f36d85f13a6608693b36c8a28ee Mon Sep 17 00:00:00 2001 From: Sep Dehpour Date: Sun, 13 Jul 2025 00:21:19 -0700 Subject: [PATCH 18/39] updating docs --- CLAUDE.md | 4 ++-- README.md | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index b6510f76..62f9c302 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -16,7 +16,7 @@ DeepDiff is a Python library for deep comparison, searching, and hashing of Pyth ### Setup ```bash # Install with all development dependencies -pip install -e ".[cli,coverage,dev,docs,static,test]" +uv pip install -e ".[cli,coverage,dev,docs,static,test]" # OR using uv (recommended) uv sync --all-extras ``` @@ -76,4 +76,4 @@ nox - **Main Branch**: `master` (PRs typically go to `dev` branch) - **Build System**: Modern `pyproject.toml` with `flit_core` - **Dependencies**: Core dependency is `orderly-set>=5.4.1,<6` -- **CLI Tool**: Available as `deep` command after installation with `[cli]` extra \ No newline at end of file +- **CLI Tool**: Available as `deep` command after installation with `[cli]` extra diff --git a/README.md b/README.md index 383eae9a..9a5ef880 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,7 @@ Please take a look at the [CHANGELOG](CHANGELOG.md) file. - Method 1: Use [`uv`](https://github.com/astral-sh/uv) to install the dependencies: `uv sync --all-extras`. - Method 2: Use pip: `pip install -e ".[cli,coverage,dev,docs,static,test]"` +5. Build `flit build` # Contribute From 6b01e71627b2ebb8ba16aada9dbedf3509d6e583 Mon Sep 17 00:00:00 2001 From: Sep Dehpour Date: Sun, 13 Jul 2025 00:54:37 -0700 Subject: [PATCH 19/39] adding ignore_uuid_types flag --- CLAUDE.md | 3 + deepdiff/base.py | 10 ++- deepdiff/deephash.py | 7 +- deepdiff/diff.py | 39 ++++++++- tests/test_delta.py | 1 + tests/test_ignore_uuid_types.py | 150 ++++++++++++++++++++++++++++++++ 6 files changed, 203 insertions(+), 7 deletions(-) create mode 100644 tests/test_ignore_uuid_types.py diff --git a/CLAUDE.md b/CLAUDE.md index 62f9c302..c5970ef1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -21,6 +21,9 @@ uv pip install -e ".[cli,coverage,dev,docs,static,test]" uv sync --all-extras ``` +**Virtual Environment**: Activate with `source ~/.venvs/atlas/bin/activate` before running tests or Python commands + + ### Testing ```bash # Run tests with coverage diff --git a/deepdiff/base.py b/deepdiff/base.py index d16bad50..8953182d 100644 --- a/deepdiff/base.py +++ b/deepdiff/base.py @@ -1,3 +1,4 @@ +import uuid from deepdiff.helper import strings, numbers, SetOrdered @@ -21,7 +22,8 @@ def get_significant_digits(self, significant_digits, ignore_numeric_type_changes def get_ignore_types_in_groups(self, ignore_type_in_groups, ignore_string_type_changes, ignore_numeric_type_changes, - ignore_type_subclasses): + ignore_type_subclasses, + ignore_uuid_types=False): if ignore_type_in_groups: if isinstance(ignore_type_in_groups[0], type): ignore_type_in_groups = [ignore_type_in_groups] @@ -43,6 +45,12 @@ def get_ignore_types_in_groups(self, ignore_type_in_groups, if ignore_numeric_type_changes and self.numbers not in ignore_type_in_groups: ignore_type_in_groups.append(SetOrdered(self.numbers)) + if ignore_uuid_types: + # Create a group containing both UUID and str types + uuid_str_group = SetOrdered([uuid.UUID, str]) + if uuid_str_group not in ignore_type_in_groups: + ignore_type_in_groups.append(uuid_str_group) + if not ignore_type_subclasses: # is_instance method needs tuples. When we look for subclasses, we need them to be tuples ignore_type_in_groups = list(map(tuple, ignore_type_in_groups)) diff --git a/deepdiff/deephash.py b/deepdiff/deephash.py index 47b900e5..8e05e1e2 100644 --- a/deepdiff/deephash.py +++ b/deepdiff/deephash.py @@ -163,6 +163,7 @@ def __init__(self, ignore_string_type_changes=False, ignore_type_in_groups=None, ignore_type_subclasses=False, + ignore_uuid_types=False, include_paths=None, number_format_notation="f", number_to_string_func=None, @@ -177,7 +178,7 @@ def __init__(self, "The valid parameters are obj, hashes, exclude_types, significant_digits, truncate_datetime," "exclude_paths, include_paths, exclude_regex_paths, hasher, ignore_repetition, " "number_format_notation, apply_hash, ignore_type_in_groups, ignore_string_type_changes, " - "ignore_numeric_type_changes, ignore_type_subclasses, ignore_string_case " + "ignore_numeric_type_changes, ignore_type_subclasses, ignore_string_case, ignore_uuid_types, " "number_to_string_func, ignore_private_variables, parent, use_enum_value, default_timezone " "encodings, ignore_encoding_errors") % ', '.join(kwargs.keys())) if isinstance(hashes, MutableMapping): @@ -203,7 +204,9 @@ def __init__(self, ignore_type_in_groups=ignore_type_in_groups, ignore_string_type_changes=ignore_string_type_changes, ignore_numeric_type_changes=ignore_numeric_type_changes, - ignore_type_subclasses=ignore_type_subclasses) + ignore_type_subclasses=ignore_type_subclasses, + ignore_uuid_types=ignore_uuid_types, + ) self.ignore_string_type_changes = ignore_string_type_changes self.ignore_numeric_type_changes = ignore_numeric_type_changes self.ignore_string_case = ignore_string_case diff --git a/deepdiff/diff.py b/deepdiff/diff.py index fd154e59..c7634a42 100755 --- a/deepdiff/diff.py +++ b/deepdiff/diff.py @@ -9,6 +9,7 @@ import logging import types import datetime +import uuid from enum import Enum from copy import deepcopy from math import isclose as is_close @@ -108,6 +109,7 @@ def _report_progress(_stats, progress_logger, duration): 'number_format_notation', 'ignore_string_type_changes', 'ignore_numeric_type_changes', + 'ignore_uuid_types', 'use_enum_value', 'ignore_type_in_groups', 'ignore_type_subclasses', @@ -168,6 +170,7 @@ def __init__(self, ignore_string_type_changes: bool=False, ignore_type_in_groups: Optional[List[Tuple]]=None, ignore_type_subclasses: bool=False, + ignore_uuid_types: bool=False, include_obj_callback: Optional[Callable]=None, include_obj_callback_strict: Optional[Callable]=None, include_paths: Union[str, List[str], None]=None, @@ -199,7 +202,7 @@ def __init__(self, "The following parameter(s) are not valid: %s\n" "The valid parameters are ignore_order, report_repetition, significant_digits, " "number_format_notation, exclude_paths, include_paths, exclude_types, exclude_regex_paths, ignore_type_in_groups, " - "ignore_string_type_changes, ignore_numeric_type_changes, ignore_type_subclasses, truncate_datetime, " + "ignore_string_type_changes, ignore_numeric_type_changes, ignore_type_subclasses, ignore_uuid_types, truncate_datetime, " "ignore_private_variables, ignore_nan_inequality, number_to_string_func, verbose_level, " "view, hasher, hashes, max_passes, max_diffs, zip_ordered_iterables, " "cutoff_distance_for_pairs, cutoff_intersection_for_pairs, log_frequency_in_sec, cache_size, " @@ -222,6 +225,11 @@ def __init__(self, self.ignore_numeric_type_changes = ignore_numeric_type_changes if strings == ignore_type_in_groups or strings in ignore_type_in_groups: ignore_string_type_changes = True + # Handle ignore_uuid_types - check if uuid+str group is already in ignore_type_in_groups + uuid_str_group = (uuids[0], str) + if uuid_str_group == ignore_type_in_groups or uuid_str_group in ignore_type_in_groups: + ignore_uuid_types = True + self.ignore_uuid_types = ignore_uuid_types self.use_enum_value = use_enum_value self.log_scale_similarity_threshold = log_scale_similarity_threshold self.use_log_scale = use_log_scale @@ -233,7 +241,8 @@ def __init__(self, ignore_type_in_groups=ignore_type_in_groups, ignore_string_type_changes=ignore_string_type_changes, ignore_numeric_type_changes=ignore_numeric_type_changes, - ignore_type_subclasses=ignore_type_subclasses) + ignore_type_subclasses=ignore_type_subclasses, + ignore_uuid_types=ignore_uuid_types) self.report_repetition = report_repetition self.exclude_paths = add_root_to_paths(convert_item_or_items_into_set_else_none(exclude_paths)) self.include_paths = add_root_to_paths(convert_item_or_items_into_set_else_none(include_paths)) @@ -1710,7 +1719,18 @@ def _diff(self, level, parents_ids=frozenset(), _original_type=None, local_tree= self._diff_booleans(level, local_tree=local_tree) elif isinstance(level.t1, strings): - self._diff_str(level, local_tree=local_tree) + # Special handling when comparing string with UUID and ignore_uuid_types is True + if self.ignore_uuid_types and isinstance(level.t2, uuids): + try: + # Convert string to UUID for comparison + t1_uuid = uuid.UUID(level.t1) + if t1_uuid.int != level.t2.int: + self._report_result('values_changed', level, local_tree=local_tree) + except (ValueError, AttributeError): + # If string is not a valid UUID, report as changed + self._report_result('values_changed', level, local_tree=local_tree) + else: + self._diff_str(level, local_tree=local_tree) elif isinstance(level.t1, datetime.datetime): self._diff_datetime(level, local_tree=local_tree) @@ -1722,7 +1742,18 @@ def _diff(self, level, parents_ids=frozenset(), _original_type=None, local_tree= self._diff_time(level, local_tree=local_tree) elif isinstance(level.t1, uuids): - self._diff_uuids(level, local_tree=local_tree) + # Special handling when comparing UUID with string and ignore_uuid_types is True + if self.ignore_uuid_types and isinstance(level.t2, str): + try: + # Convert string to UUID for comparison + t2_uuid = uuid.UUID(level.t2) + if level.t1.int != t2_uuid.int: + self._report_result('values_changed', level, local_tree=local_tree) + except (ValueError, AttributeError): + # If string is not a valid UUID, report as changed + self._report_result('values_changed', level, local_tree=local_tree) + else: + self._diff_uuids(level, local_tree=local_tree) elif isinstance(level.t1, numbers): self._diff_numbers(level, local_tree=local_tree, report_type_change=report_type_change) diff --git a/tests/test_delta.py b/tests/test_delta.py index f8e8fb76..32e5b3e0 100644 --- a/tests/test_delta.py +++ b/tests/test_delta.py @@ -1508,6 +1508,7 @@ def test_delta_view_and_to_delta_dict_are_equal_when_parameteres_passed(self): 'include_obj_callback_strict': None, 'exclude_obj_callback': None, 'exclude_obj_callback_strict': None, + 'ignore_uuid_types': False, 'ignore_private_variables': True, 'ignore_nan_inequality': False, 'hasher': None, diff --git a/tests/test_ignore_uuid_types.py b/tests/test_ignore_uuid_types.py new file mode 100644 index 00000000..c6a1de47 --- /dev/null +++ b/tests/test_ignore_uuid_types.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python +import uuid +import unittest +from deepdiff import DeepDiff + + +class TestIgnoreUuidTypes(unittest.TestCase): + """Test ignore_uuid_types functionality""" + + def test_uuid_vs_string_without_ignore(self): + """Test that UUID vs string reports type change by default""" + test_uuid = uuid.UUID('12345678-1234-5678-1234-567812345678') + uuid_str = '12345678-1234-5678-1234-567812345678' + + result = DeepDiff(test_uuid, uuid_str) + + assert 'type_changes' in result + assert result['type_changes']['root']['old_type'] == uuid.UUID + assert result['type_changes']['root']['new_type'] == str + assert result['type_changes']['root']['old_value'] == test_uuid + assert result['type_changes']['root']['new_value'] == uuid_str + + def test_uuid_vs_string_with_ignore(self): + """Test that UUID vs string is ignored when ignore_uuid_types=True""" + test_uuid = uuid.UUID('12345678-1234-5678-1234-567812345678') + uuid_str = '12345678-1234-5678-1234-567812345678' + + result = DeepDiff(test_uuid, uuid_str, ignore_uuid_types=True) + + assert result == {} + + def test_string_vs_uuid_with_ignore(self): + """Test that string vs UUID is ignored when ignore_uuid_types=True (reverse order)""" + test_uuid = uuid.UUID('12345678-1234-5678-1234-567812345678') + uuid_str = '12345678-1234-5678-1234-567812345678' + + result = DeepDiff(uuid_str, test_uuid, ignore_uuid_types=True) + + assert result == {} + + def test_different_uuid_values_with_ignore(self): + """Test that different UUID values are still reported""" + uuid1 = uuid.UUID('12345678-1234-5678-1234-567812345678') + uuid2 = uuid.UUID('87654321-4321-8765-4321-876543218765') + + result = DeepDiff(uuid1, uuid2, ignore_uuid_types=True) + + assert 'values_changed' in result + assert result['values_changed']['root']['old_value'] == uuid1 + assert result['values_changed']['root']['new_value'] == uuid2 + + def test_uuid_vs_different_string_with_ignore(self): + """Test that UUID vs different UUID string reports value change""" + test_uuid = uuid.UUID('12345678-1234-5678-1234-567812345678') + different_str = '87654321-4321-8765-4321-876543218765' + + result = DeepDiff(test_uuid, different_str, ignore_uuid_types=True) + + assert 'values_changed' in result + assert result['values_changed']['root']['old_value'] == test_uuid + assert result['values_changed']['root']['new_value'] == different_str + + def test_uuid_vs_invalid_string_with_ignore(self): + """Test that UUID vs invalid UUID string reports value change""" + test_uuid = uuid.UUID('12345678-1234-5678-1234-567812345678') + invalid_str = 'not-a-uuid' + + result = DeepDiff(test_uuid, invalid_str, ignore_uuid_types=True) + + assert 'values_changed' in result + assert result['values_changed']['root']['old_value'] == test_uuid + assert result['values_changed']['root']['new_value'] == invalid_str + + def test_uuid_in_dict_with_ignore(self): + """Test that UUID vs string in dictionaries works correctly""" + test_uuid = uuid.UUID('12345678-1234-5678-1234-567812345678') + uuid_str = '12345678-1234-5678-1234-567812345678' + + dict1 = {'id': test_uuid, 'name': 'test', 'count': 42} + dict2 = {'id': uuid_str, 'name': 'test', 'count': 42} + + result = DeepDiff(dict1, dict2, ignore_uuid_types=True) + + assert result == {} + + def test_uuid_in_list_with_ignore(self): + """Test that UUID vs string in lists works correctly""" + test_uuid = uuid.UUID('12345678-1234-5678-1234-567812345678') + uuid_str = '12345678-1234-5678-1234-567812345678' + + list1 = [test_uuid, 'test', 42] + list2 = [uuid_str, 'test', 42] + + result = DeepDiff(list1, list2, ignore_uuid_types=True) + + assert result == {} + + def test_mixed_uuid_comparisons_with_ignore(self): + """Test mixed UUID/string comparisons in nested structures""" + uuid1 = uuid.UUID('12345678-1234-5678-1234-567812345678') + uuid2 = uuid.UUID('87654321-4321-8765-4321-876543218765') + + data1 = { + 'uuid_obj': uuid1, + 'uuid_str': '12345678-1234-5678-1234-567812345678', + 'nested': { + 'id': uuid2, + 'items': [uuid1, 'test'] + } + } + + data2 = { + 'uuid_obj': '12345678-1234-5678-1234-567812345678', # string version + 'uuid_str': uuid1, # UUID object version + 'nested': { + 'id': '87654321-4321-8765-4321-876543218765', # string version + 'items': ['12345678-1234-5678-1234-567812345678', 'test'] # string version + } + } + + result = DeepDiff(data1, data2, ignore_uuid_types=True) + + assert result == {} + + def test_uuid_with_other_ignore_flags(self): + """Test that ignore_uuid_types works with other ignore flags""" + test_uuid = uuid.UUID('12345678-1234-5678-1234-567812345678') + + data1 = { + 'id': test_uuid, + 'name': 'TEST', + 'count': 42 + } + + data2 = { + 'id': '12345678-1234-5678-1234-567812345678', + 'name': 'test', # different case + 'count': 42.0 # different numeric type + } + + result = DeepDiff(data1, data2, + ignore_uuid_types=True, + ignore_string_case=True, + ignore_numeric_type_changes=True) + + assert result == {} + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file From 791bdfa68c24754b4867e46bc9c90197b6b1ef82 Mon Sep 17 00:00:00 2001 From: Sep Dehpour Date: Sun, 13 Jul 2025 01:09:57 -0700 Subject: [PATCH 20/39] fixing the implementation for hashing uuid --- deepdiff/deephash.py | 11 ++--------- pyproject.toml | 1 + tests/test_hash.py | 3 +-- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/deepdiff/deephash.py b/deepdiff/deephash.py index da45f562..4e4ef415 100644 --- a/deepdiff/deephash.py +++ b/deepdiff/deephash.py @@ -488,7 +488,7 @@ def _prep_number(self, obj): number_format_notation=self.number_format_notation) return KEY_TO_VAL_STR.format(type_, obj) - def _prep_ipranges(self, obj): + def _prep_ipranges(self, obj) -> str: type_ = 'iprange' obj = str(obj) return KEY_TO_VAL_STR.format(type_, obj) @@ -572,14 +572,7 @@ def _hash(self, obj, parent, parents_ids=EMPTY_FROZENSET): elif isinstance(obj, uuid.UUID): # Handle UUID objects (including uuid6.UUID) by using their integer value - result = f"uuid:{obj.int}" - if self.apply_hash: - result = self.hasher(result) - try: - self.hashes[obj] = (result, counts) - except TypeError: - self.hashes[get_id(obj)] = (result, counts) - return result, counts + result = str(obj) elif isinstance(obj, MutableMapping): result, counts = self._prep_dict(obj=obj, parent=parent, parents_ids=parents_ids) diff --git a/pyproject.toml b/pyproject.toml index 3e1dcfcc..dff03fa3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,6 +55,7 @@ dev = [ "pandas~=2.2.0", "polars~=1.21.0", "nox==2025.5.1", + "uuid6==2025.0.1", ] docs = [ # We use the html style that is not supported in Sphinx 7 anymore. diff --git a/tests/test_hash.py b/tests/test_hash.py index 41d1413c..902cb142 100755 --- a/tests/test_hash.py +++ b/tests/test_hash.py @@ -2,6 +2,7 @@ import re import pytest import pytz +import uuid6 import logging import datetime import ipaddress @@ -210,7 +211,6 @@ def test_numpy_datetime64(self): def test_uuid6_hash_positive(self): """Positive test case: Same UUID objects should produce the same hash.""" - import uuid6 uuid_obj = uuid6.uuid7() hash1 = DeepHash(uuid_obj) hash2 = DeepHash(uuid_obj) @@ -223,7 +223,6 @@ def test_uuid6_hash_positive(self): def test_uuid6_deepdiff_negative(self): """Negative test case: DeepDiff should detect differences between sets containing different UUID objects.""" - import uuid6 dummy_id_1 = uuid6.uuid7() dummy_id_2 = uuid6.uuid7() set1 = {dummy_id_1} From 8121be35d5e8da35ebe9a159bbeec0591af7c7aa Mon Sep 17 00:00:00 2001 From: Sep Dehpour Date: Mon, 14 Jul 2025 01:43:11 -0700 Subject: [PATCH 21/39] supporing memoryview --- deepdiff/deephash.py | 2 + deepdiff/diff.py | 16 +++- deepdiff/helper.py | 2 +- tests/test_memoryview.py | 154 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 171 insertions(+), 3 deletions(-) create mode 100644 tests/test_memoryview.py diff --git a/deepdiff/deephash.py b/deepdiff/deephash.py index 4e4ef415..50c6fd60 100644 --- a/deepdiff/deephash.py +++ b/deepdiff/deephash.py @@ -100,6 +100,8 @@ def prepare_string_for_hashing( original_type = obj.__class__.__name__ # https://docs.python.org/3/library/codecs.html#codecs.decode errors_mode = 'ignore' if ignore_encoding_errors else 'strict' + if isinstance(obj, memoryview): + obj = obj.tobytes() if isinstance(obj, bytes): err = None encodings = ['utf-8'] if encodings is None else encodings diff --git a/deepdiff/diff.py b/deepdiff/diff.py index c7634a42..234555fd 100755 --- a/deepdiff/diff.py +++ b/deepdiff/diff.py @@ -594,6 +594,8 @@ def _get_clean_to_keys_mapping(self, keys, level): for key in keys: if self.ignore_string_type_changes and isinstance(key, bytes): clean_key = key.decode('utf-8') + elif self.ignore_string_type_changes and isinstance(key, memoryview): + clean_key = key.tobytes().decode('utf-8') elif self.use_enum_value and isinstance(key, Enum): clean_key = key.value elif isinstance(key, numbers): @@ -1060,13 +1062,23 @@ def _diff_str(self, level, local_tree=None): t1_str = level.t1 t2_str = level.t2 - if isinstance(level.t1, bytes_type): + if isinstance(level.t1, memoryview): + try: + t1_str = level.t1.tobytes().decode('ascii') + except UnicodeDecodeError: + do_diff = False + elif isinstance(level.t1, bytes_type): try: t1_str = level.t1.decode('ascii') except UnicodeDecodeError: do_diff = False - if isinstance(level.t2, bytes_type): + if isinstance(level.t2, memoryview): + try: + t2_str = level.t2.tobytes().decode('ascii') + except UnicodeDecodeError: + do_diff = False + elif isinstance(level.t2, bytes_type): try: t2_str = level.t2.decode('ascii') except UnicodeDecodeError: diff --git a/deepdiff/helper.py b/deepdiff/helper.py index 38f42b58..90d7fd2a 100644 --- a/deepdiff/helper.py +++ b/deepdiff/helper.py @@ -182,7 +182,7 @@ def get_semvar_as_integer(version): if np and get_semvar_as_integer(np.__version__) < 1019000: sys.exit('The minimum required Numpy version is 1.19.0. Please upgrade your Numpy package.') -strings = (str, bytes) # which are both basestring +strings = (str, bytes, memoryview) # which are both basestring unicode_type = str bytes_type = bytes only_complex_number = (complex,) + numpy_complex_numbers diff --git a/tests/test_memoryview.py b/tests/test_memoryview.py new file mode 100644 index 00000000..42fbc64f --- /dev/null +++ b/tests/test_memoryview.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python +import pytest +from deepdiff import DeepDiff + + +class TestMemoryView: + """Test memoryview support in DeepDiff""" + + def test_memoryview_basic_comparison(self): + """Test basic memoryview comparison without ignore_string_type_changes""" + t1 = memoryview(b"hello") + t2 = memoryview(b"world") + + diff = DeepDiff(t1, t2) + assert 'values_changed' in diff + assert diff['values_changed']['root']['old_value'] == t1 + assert diff['values_changed']['root']['new_value'] == t2 + + def test_memoryview_with_bytes_type_change(self): + """Test memoryview vs bytes comparison shows type change""" + t1 = memoryview(b"hello") + t2 = b"hello" + + diff = DeepDiff(t1, t2) + assert 'type_changes' in diff + assert diff['type_changes']['root']['old_type'] == memoryview + assert diff['type_changes']['root']['new_type'] == bytes + assert diff['type_changes']['root']['old_value'] == t1 + assert diff['type_changes']['root']['new_value'] == t2 + + def test_memoryview_with_str_type_change(self): + """Test memoryview vs str comparison shows type change""" + t1 = memoryview(b"hello") + t2 = "hello" + + diff = DeepDiff(t1, t2) + assert 'type_changes' in diff + assert diff['type_changes']['root']['old_type'] == memoryview + assert diff['type_changes']['root']['new_type'] == str + assert diff['type_changes']['root']['old_value'] == t1 + assert diff['type_changes']['root']['new_value'] == t2 + + def test_memoryview_ignore_string_type_changes_with_bytes(self): + """Test memoryview vs bytes with ignore_string_type_changes=True""" + t1 = memoryview(b"hello") + t2 = b"hello" + + diff = DeepDiff(t1, t2, ignore_string_type_changes=True) + assert diff == {} + + def test_memoryview_ignore_string_type_changes_with_str(self): + """Test memoryview vs str with ignore_string_type_changes=True""" + t1 = memoryview(b"hello") + t2 = "hello" + + diff = DeepDiff(t1, t2, ignore_string_type_changes=True) + assert diff == {} + + def test_memoryview_different_content_with_ignore_string_type_changes(self): + """Test memoryview with different content still shows value change""" + t1 = memoryview(b"hello") + t2 = "world" + + diff = DeepDiff(t1, t2, ignore_string_type_changes=True) + assert 'values_changed' in diff + # The values in the diff are the original objects, not converted strings + assert diff['values_changed']['root']['old_value'] == t1 + assert diff['values_changed']['root']['new_value'] == t2 + + def test_memoryview_in_dict_keys(self): + """Test memoryview as dictionary keys""" + t1 = {memoryview(b"key1"): "value1", memoryview(b"key2"): "value2"} + t2 = {b"key1": "value1", "key2": "value2"} + + # Without ignore_string_type_changes, should show differences + diff = DeepDiff(t1, t2) + assert 'dictionary_item_removed' in diff or 'dictionary_item_added' in diff + + # With ignore_string_type_changes, should be equal + diff = DeepDiff(t1, t2, ignore_string_type_changes=True) + assert diff == {} + + def test_memoryview_in_list(self): + """Test memoryview in lists""" + t1 = [memoryview(b"hello"), memoryview(b"world")] + t2 = ["hello", b"world"] + + diff = DeepDiff(t1, t2, ignore_string_type_changes=True) + assert diff == {} + + def test_memoryview_in_nested_structure(self): + """Test memoryview in nested structures""" + t1 = { + "data": { + "items": [memoryview(b"item1"), memoryview(b"item2")], + "metadata": {memoryview(b"key"): "value"} + } + } + t2 = { + "data": { + "items": ["item1", b"item2"], + "metadata": {"key": "value"} + } + } + + diff = DeepDiff(t1, t2, ignore_string_type_changes=True) + assert diff == {} + + def test_memoryview_with_non_ascii_bytes(self): + """Test memoryview with non-ASCII bytes""" + t1 = memoryview(b"\x80\x81\x82") + t2 = b"\x80\x81\x82" + + diff = DeepDiff(t1, t2, ignore_string_type_changes=True) + assert diff == {} + + def test_memoryview_text_diff(self): + """Test that text diff works with memoryview""" + t1 = {"data": memoryview(b"hello\nworld")} + t2 = {"data": memoryview(b"hello\nearth")} + + diff = DeepDiff(t1, t2) + assert 'values_changed' in diff + assert "root['data']" in diff['values_changed'] + # Should contain diff output + assert 'diff' in diff['values_changed']["root['data']"] + + def test_memoryview_with_ignore_type_in_groups(self): + """Test memoryview with ignore_type_in_groups parameter""" + from deepdiff.helper import strings + + t1 = memoryview(b"hello") + t2 = "hello" + + # Using ignore_type_in_groups with strings tuple + diff = DeepDiff(t1, t2, ignore_type_in_groups=[strings]) + assert diff == {} + + def test_memoryview_hash(self): + """Test that DeepHash works with memoryview""" + from deepdiff import DeepHash + + # Test basic hashing + obj1 = memoryview(b"hello") + hash1 = DeepHash(obj1) + assert hash1[obj1] + + # Test with ignore_string_type_changes + obj2 = "hello" + hash2 = DeepHash(obj2, ignore_string_type_changes=True) + hash1_ignore = DeepHash(obj1, ignore_string_type_changes=True) + + # When ignoring string type changes, memoryview and str of same content should hash the same + assert hash1_ignore[obj1] == hash2[obj2] \ No newline at end of file From 5e514c5ade894dd07e960a2ca4deecd40dce0b77 Mon Sep 17 00:00:00 2001 From: Sep Dehpour Date: Mon, 14 Jul 2025 02:01:20 -0700 Subject: [PATCH 22/39] serializing memoryview --- deepdiff/serialization.py | 1 + tests/test_serialization.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/deepdiff/serialization.py b/deepdiff/serialization.py index 2b5819a4..7c1e1d65 100644 --- a/deepdiff/serialization.py +++ b/deepdiff/serialization.py @@ -623,6 +623,7 @@ def _serialize_tuple(value): Mapping: dict, NotPresent: str, ipranges: str, + memoryview: lambda x: x.tobytes(), } if PydanticBaseModel is not pydantic_base_model_type: diff --git a/tests/test_serialization.py b/tests/test_serialization.py index 5d2f2171..f36ad4b3 100644 --- a/tests/test_serialization.py +++ b/tests/test_serialization.py @@ -418,7 +418,8 @@ def prefix_callback(**kwargs): (6, datetime.datetime(2023, 10, 11), datetime.datetime.fromisoformat), (7, datetime.datetime.utcnow(), datetime.datetime.fromisoformat), (8, field_stats1, lambda x: SomeStats(**x)), - (9, np.array([[ 101, 3533, 1998, 4532, 2024, 3415, 1012, 102]]), np.array) + (9, np.array([[ 101, 3533, 1998, 4532, 2024, 3415, 1012, 102]]), np.array), + (10, memoryview(b"hello"), lambda x: memoryview(x.encode('utf-8'))), ]) def test_json_dumps_and_loads(self, test_num, value, func_to_convert_back): serialized = json_dumps(value) @@ -444,4 +445,3 @@ def test_reversed_list(self): assert '[3,2,1]' == serialized assert '[3,2,1]' == serialized2, "We should have copied the original list. If this returns empty, it means we exhausted the original list." - From 687ea04c23b95135c9fbb07f2cf7d0a5332f44dd Mon Sep 17 00:00:00 2001 From: Sep Dehpour Date: Tue, 15 Jul 2025 14:46:51 -0700 Subject: [PATCH 23/39] adding one more test case --- tests/test_serialization.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_serialization.py b/tests/test_serialization.py index f36ad4b3..b75086cd 100644 --- a/tests/test_serialization.py +++ b/tests/test_serialization.py @@ -409,6 +409,10 @@ def prefix_callback(**kwargs): result = ddiff.pretty(prefix=prefix_callback) assert result == expected + def sig_to_bytes(inp: dict[str, str | bytes]): + inp['signature'] = inp['signature'].encode('utf-8') + return inp + @pytest.mark.parametrize('test_num, value, func_to_convert_back', [ (1, {'10': None}, None), (2, {"type_changes": {"root": {"old_type": None, "new_type": list, "new_value": ["你好", 2, 3, 5]}}}, None), @@ -420,6 +424,7 @@ def prefix_callback(**kwargs): (8, field_stats1, lambda x: SomeStats(**x)), (9, np.array([[ 101, 3533, 1998, 4532, 2024, 3415, 1012, 102]]), np.array), (10, memoryview(b"hello"), lambda x: memoryview(x.encode('utf-8'))), + (11, {'file_type': 'xlsx', 'signature': b'52bd9907785'}, sig_to_bytes) ]) def test_json_dumps_and_loads(self, test_num, value, func_to_convert_back): serialized = json_dumps(value) From e4b59cfd43414923b2e4d962723fe076cccfa7f4 Mon Sep 17 00:00:00 2001 From: Sep Dehpour Date: Mon, 21 Jul 2025 12:09:20 -0700 Subject: [PATCH 24/39] adding a test case for delta and list of bytes --- tests/test_delta.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/test_delta.py b/tests/test_delta.py index 32e5b3e0..cc45369c 100644 --- a/tests/test_delta.py +++ b/tests/test_delta.py @@ -1832,6 +1832,18 @@ def test_delta_set_in_objects(self): delta_again = Delta(flat_rows_list=flat_expected) assert delta.diff == delta_again.diff + def test_delta_array_of_bytes(self): + t1 = [] + t2 = [b"hello"] + delta = Delta(DeepDiff(t1, t2)) + flat_result = delta.to_flat_rows() + flat_expected = [FlatDeltaRow(path=[0], action=FlatDataAction.iterable_item_added, value=b'hello', type=bytes)] + assert flat_expected == flat_result + + delta_again = Delta(flat_rows_list=flat_expected) + assert delta.diff == delta_again.diff + assert t1 + delta_again == t2 + def test_delta_with_json_serializer(self): t1 = {"a": 1} t2 = {"a": 2} From f8397c76c8752e4522c9f0c76077cbb784b4acf1 Mon Sep 17 00:00:00 2001 From: Sep Dehpour Date: Mon, 21 Jul 2025 14:29:56 -0700 Subject: [PATCH 25/39] fixes a bug when _get_clean_to_keys_mapping would be called without explicit significant digits --- deepdiff/diff.py | 7 +++++-- tests/test_delta.py | 5 ++++- tests/test_diff_math.py | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/deepdiff/diff.py b/deepdiff/diff.py index 234555fd..43404d71 100755 --- a/deepdiff/diff.py +++ b/deepdiff/diff.py @@ -600,8 +600,11 @@ def _get_clean_to_keys_mapping(self, keys, level): clean_key = key.value elif isinstance(key, numbers): type_ = "number" if self.ignore_numeric_type_changes else key.__class__.__name__ - clean_key = self.number_to_string(key, significant_digits=self.significant_digits, - number_format_notation=self.number_format_notation) + if self.significant_digits is None: + clean_key = key + else: + clean_key = self.number_to_string(key, significant_digits=self.significant_digits, + number_format_notation=self.number_format_notation) clean_key = KEY_TO_VAL_STR.format(type_, clean_key) else: clean_key = key diff --git a/tests/test_delta.py b/tests/test_delta.py index cc45369c..7ab6f129 100644 --- a/tests/test_delta.py +++ b/tests/test_delta.py @@ -1835,7 +1835,10 @@ def test_delta_set_in_objects(self): def test_delta_array_of_bytes(self): t1 = [] t2 = [b"hello"] - delta = Delta(DeepDiff(t1, t2)) + diff = DeepDiff(t1, t2) + expected_diff = {'iterable_item_added': {'root[0]': b'hello'}} + assert expected_diff == diff + delta = Delta(diff) flat_result = delta.to_flat_rows() flat_expected = [FlatDeltaRow(path=[0], action=FlatDataAction.iterable_item_added, value=b'hello', type=bytes)] assert flat_expected == flat_result diff --git a/tests/test_diff_math.py b/tests/test_diff_math.py index 0902548b..9bfa3e49 100644 --- a/tests/test_diff_math.py +++ b/tests/test_diff_math.py @@ -110,3 +110,17 @@ def test_math_diff_ignore_order_warning(self, caplog): } assert res == expected # assert "math_epsilon will be ignored." in caplog.text + + def test_ignore_numeric_type_changes_with_numeric_keys_and_no_significant_digits(self): + """Test that ignore_numeric_type_changes works with numeric keys when significant_digits is None. + + This test covers the bug fix in _get_clean_to_keys_mapping where significant_digits=None + caused a crash when number_to_string was called without the required parameter. + """ + # Test case with numeric keys and ignore_numeric_type_changes=True, significant_digits=None + d1 = {1: "value1", 2.5: "value2"} + d2 = {1.0: "value1", 2.5: "value2"} # int vs float keys + + # This should not crash and should treat 1 and 1.0 as the same key + result = DeepDiff(d1, d2, ignore_numeric_type_changes=True, significant_digits=None) + assert result == {} From 43acb92c45b495969a299761499647c1c0e38b7f Mon Sep 17 00:00:00 2001 From: Sep Dehpour Date: Wed, 23 Jul 2025 13:06:11 -0700 Subject: [PATCH 26/39] fixing the case where group_by by a number would leak type info into the group path report --- deepdiff/diff.py | 28 ++++++++++++++++++++++------ tests/test_diff_text.py | 18 ++++++++++++++++-- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/deepdiff/diff.py b/deepdiff/diff.py index 43404d71..784eab47 100755 --- a/deepdiff/diff.py +++ b/deepdiff/diff.py @@ -332,10 +332,12 @@ def _group_by_sort_key(x): } self.hashes = dict_() if hashes is None else hashes self._numpy_paths = dict_() # if _numpy_paths is None else _numpy_paths + self.group_by_keys = set() # Track keys that originated from group_by operations self._shared_parameters = { 'hashes': self.hashes, '_stats': self._stats, '_distance_cache': self._distance_cache, + 'group_by_keys': self.group_by_keys, '_numpy_paths': self._numpy_paths, _ENABLE_CACHE_EVERY_X_DIFF: self.cache_tuning_sample_size * 10, } @@ -599,13 +601,21 @@ def _get_clean_to_keys_mapping(self, keys, level): elif self.use_enum_value and isinstance(key, Enum): clean_key = key.value elif isinstance(key, numbers): - type_ = "number" if self.ignore_numeric_type_changes else key.__class__.__name__ - if self.significant_digits is None: - clean_key = key + # Skip type prefixing for keys that originated from group_by operations + if hasattr(self, 'group_by_keys') and key in self.group_by_keys: + if self.significant_digits is None: + clean_key = key + else: + clean_key = self.number_to_string(key, significant_digits=self.significant_digits, + number_format_notation=self.number_format_notation) else: - clean_key = self.number_to_string(key, significant_digits=self.significant_digits, - number_format_notation=self.number_format_notation) - clean_key = KEY_TO_VAL_STR.format(type_, clean_key) + type_ = "number" if self.ignore_numeric_type_changes else key.__class__.__name__ + if self.significant_digits is None: + clean_key = key + else: + clean_key = self.number_to_string(key, significant_digits=self.significant_digits, + number_format_notation=self.number_format_notation) + clean_key = KEY_TO_VAL_STR.format(type_, clean_key) else: clean_key = key if self.ignore_string_case and isinstance(clean_key, str): @@ -1845,8 +1855,14 @@ def _group_iterable_to_dict(self, item, group_by, item_name): for row in item_copy: if isinstance(row, Mapping): key1 = self._get_key_for_group_by(row, group_by_level1, item_name) + # Track keys created by group_by to avoid type prefixing later + if hasattr(self, 'group_by_keys'): + self.group_by_keys.add(key1) if group_by_level2: key2 = self._get_key_for_group_by(row, group_by_level2, item_name) + # Track level 2 keys as well + if hasattr(self, 'group_by_keys'): + self.group_by_keys.add(key2) if key1 not in result: result[key1] = {} if self.group_by_sort_key: diff --git a/tests/test_diff_text.py b/tests/test_diff_text.py index 10fbdb21..1c01cbe0 100755 --- a/tests/test_diff_text.py +++ b/tests/test_diff_text.py @@ -9,9 +9,8 @@ from typing import List from decimal import Decimal from deepdiff import DeepDiff -from deepdiff.helper import pypy3, PydanticBaseModel +from deepdiff.helper import pypy3, PydanticBaseModel, SetOrdered, np_float64 from tests import CustomClass -from deepdiff.helper import np_float64 logging.disable(logging.CRITICAL) @@ -2258,3 +2257,18 @@ def test_range1(self): range2 = range(0, 8) diff = DeepDiff(range1, range2) assert {'iterable_item_removed': {'root[8]': 8, 'root[9]': 9}} == diff + + + def test_group_by_that_has_integers(self): + """Test that group_by with integer keys doesn't add type prefixes like 'int:33'""" + t1 = [{'row_num_in_file': 33, 'value': 'old'}] + t2 = [{'row_num_in_file': 33, 'value': 'new'}] + + diff = DeepDiff(t1, t2, group_by='row_num_in_file', ignore_string_type_changes=True) + + # Verify that the diff key contains the integer 33 without type prefix + changes = diff.get('values_changed', {}) + assert len(changes) == 1 + key = list(changes.keys())[0] + assert "int:" not in key + assert "[33]" in key or "['33']" in key From 59fbd1d6a5ead7dd66a46a25503756f95e1a9e48 Mon Sep 17 00:00:00 2001 From: Sep Dehpour Date: Thu, 31 Jul 2025 00:49:21 -0700 Subject: [PATCH 27/39] deepdiff seralizing python dict keys --- deepdiff/serialization.py | 5 ++++- tests/test_serialization.py | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/deepdiff/serialization.py b/deepdiff/serialization.py index 7c1e1d65..c9a02cd0 100644 --- a/deepdiff/serialization.py +++ b/deepdiff/serialization.py @@ -14,7 +14,7 @@ import ipaddress from copy import deepcopy, copy from functools import partial -from collections.abc import Mapping +from collections.abc import Mapping, KeysView from typing import ( Callable, Optional, Union, overload, Literal, Any, @@ -93,6 +93,7 @@ class UnsupportedFormatErr(TypeError): 'ipaddress.IPv6Network', 'ipaddress.IPv4Address', 'ipaddress.IPv6Address', + 'collections.abc.KeysView', } @@ -124,6 +125,7 @@ class UnsupportedFormatErr(TypeError): 'iprange': str, 'IPv4Address': ipaddress.IPv4Address, 'IPv6Address': ipaddress.IPv6Address, + 'KeysView': list, } @@ -624,6 +626,7 @@ def _serialize_tuple(value): NotPresent: str, ipranges: str, memoryview: lambda x: x.tobytes(), + KeysView: list, } if PydanticBaseModel is not pydantic_base_model_type: diff --git a/tests/test_serialization.py b/tests/test_serialization.py index b75086cd..cb153539 100644 --- a/tests/test_serialization.py +++ b/tests/test_serialization.py @@ -450,3 +450,8 @@ def test_reversed_list(self): assert '[3,2,1]' == serialized assert '[3,2,1]' == serialized2, "We should have copied the original list. If this returns empty, it means we exhausted the original list." + + def test_dict_keys(self): + dic = {"foo": "bar", "apple": "too sweet"} + serialized = json_dumps(dic.keys()) + assert '["foo","apple"]' == serialized From c35233249127475e92a030872f0daf423465cae4 Mon Sep 17 00:00:00 2001 From: Sep Dehpour Date: Thu, 7 Aug 2025 14:04:41 -0700 Subject: [PATCH 28/39] adding support for serialization of bytes taht are not utf8 compatible --- deepdiff/serialization.py | 15 ++++- tests/test_serialization.py | 112 ++++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+), 1 deletion(-) diff --git a/deepdiff/serialization.py b/deepdiff/serialization.py index c9a02cd0..ed489594 100644 --- a/deepdiff/serialization.py +++ b/deepdiff/serialization.py @@ -12,6 +12,7 @@ import orderly_set # NOQA import collections # NOQA import ipaddress +import base64 from copy import deepcopy, copy from functools import partial from collections.abc import Mapping, KeysView @@ -607,13 +608,25 @@ def _serialize_tuple(value): return value +def _serialize_bytes(value): + """ + Serialize bytes to JSON-compatible format. + First tries UTF-8 decoding for backward compatibility. + Falls back to base64 encoding for binary data. + """ + try: + return value.decode('utf-8') + except UnicodeDecodeError: + return base64.b64encode(value).decode('ascii') + + JSON_CONVERTOR = { decimal.Decimal: _serialize_decimal, SetOrdered: list, orderly_set.StableSetEq: list, set: list, type: lambda x: x.__name__, - bytes: lambda x: x.decode('utf-8'), + bytes: _serialize_bytes, datetime.datetime: lambda x: x.isoformat(), uuid.UUID: lambda x: str(x), np_float32: float, diff --git a/tests/test_serialization.py b/tests/test_serialization.py index cb153539..5ba8371f 100644 --- a/tests/test_serialization.py +++ b/tests/test_serialization.py @@ -6,6 +6,8 @@ import pytest import datetime import numpy as np +import hashlib +import base64 from typing import NamedTuple, Optional from pickle import UnpicklingError from decimal import Decimal @@ -455,3 +457,113 @@ def test_dict_keys(self): dic = {"foo": "bar", "apple": "too sweet"} serialized = json_dumps(dic.keys()) assert '["foo","apple"]' == serialized + + def test_non_utf8_bytes_serialization(self): + """Test that non-UTF-8 bytes are properly base64 encoded""" + # Create binary data that cannot be decoded as UTF-8 + binary_data = b'\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f' + + # Verify it's not UTF-8 decodable + with pytest.raises(UnicodeDecodeError): + binary_data.decode('utf-8') + + # Test serialization + test_data = {"binary": binary_data} + serialized = json_dumps(test_data) + + # Should contain base64 encoded data + expected_b64 = base64.b64encode(binary_data).decode('ascii') + assert expected_b64 in serialized + + # Should be deserializable + deserialized = json_loads(serialized) + assert deserialized == {"binary": expected_b64} + + def test_hash_bytes_serialization(self): + """Test serialization of hash-like binary data (blake3, sha256, etc.)""" + # Generate various hash-like byte sequences + test_cases = [ + hashlib.md5(b"test").digest(), + hashlib.sha1(b"test").digest(), + hashlib.sha256(b"test").digest(), + hashlib.sha512(b"test").digest()[:16], # Truncated + b'\xff\xfe\xfd\xfc' * 8, # Artificial binary pattern + ] + + for i, hash_bytes in enumerate(test_cases): + test_data = {"hash": hash_bytes} + + # Should not raise UnicodeDecodeError + serialized = json_dumps(test_data) + assert serialized # Should produce valid JSON + + # Should contain base64 if not UTF-8 decodable, or string if UTF-8 decodable + try: + utf8_decoded = hash_bytes.decode('utf-8') + # If UTF-8 decodable, should be in JSON as string + assert utf8_decoded in serialized + except UnicodeDecodeError: + # If not UTF-8 decodable, should be base64 encoded + expected_b64 = base64.b64encode(hash_bytes).decode('ascii') + assert expected_b64 in serialized + + def test_mixed_utf8_and_binary_bytes(self): + """Test data structure with both UTF-8 decodable and binary bytes""" + test_data = { + "utf8_text": b"hello world", # UTF-8 decodable + "binary_hash": hashlib.sha256(b"secret").digest(), # Binary + "empty_bytes": b"", # Edge case + "utf8_unicode": "café".encode('utf-8'), # UTF-8 with unicode + "non_utf8_byte": b"\xff\xfe\xfd", # Non-UTF-8 bytes + } + + # Should serialize without errors + serialized = json_dumps(test_data) + deserialized = json_loads(serialized) + + # UTF-8 decodable bytes should remain as strings + assert "hello world" in serialized + assert deserialized["utf8_text"] == "hello world" + + # Unicode UTF-8 should work + assert deserialized["utf8_unicode"] == "café" + + # Binary data should be base64 encoded + expected_hash_b64 = base64.b64encode(test_data["binary_hash"]).decode('ascii') + assert expected_hash_b64 in serialized + assert deserialized["binary_hash"] == expected_hash_b64 + + # Empty bytes should be empty string + assert deserialized["empty_bytes"] == "" + + # Non-UTF-8 bytes should be base64 encoded + expected_non_utf8_b64 = base64.b64encode(test_data["non_utf8_byte"]).decode('ascii') + assert expected_non_utf8_b64 in serialized + assert deserialized["non_utf8_byte"] == expected_non_utf8_b64 + + def test_bytes_in_deepdiff_serialization(self): + """Test that bytes work correctly in DeepDiff JSON serialization""" + t1 = { + "text": b"hello", + "hash": hashlib.sha256(b"data1").digest(), + } + t2 = { + "text": b"world", + "hash": hashlib.sha256(b"data2").digest(), + } + + diff = DeepDiff(t1, t2) + + # Should serialize without errors + json_output = diff.to_json() + assert json_output + + # Should contain both UTF-8 decoded strings and base64 encoded hashes + assert "hello" in json_output + assert "world" in json_output + + # Hash values should be base64 encoded + expected_hash1 = base64.b64encode(t1["hash"]).decode('ascii') + expected_hash2 = base64.b64encode(t2["hash"]).decode('ascii') + assert expected_hash1 in json_output + assert expected_hash2 in json_output From cdd3afc5372bf3e6db45e8a89ca05c0882166a50 Mon Sep 17 00:00:00 2001 From: Sep Dehpour Date: Fri, 8 Aug 2025 10:10:30 -0700 Subject: [PATCH 29/39] updating claude instructions --- CLAUDE.md | 40 +++++++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index c5970ef1..91970f1b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -21,25 +21,50 @@ uv pip install -e ".[cli,coverage,dev,docs,static,test]" uv sync --all-extras ``` -**Virtual Environment**: Activate with `source ~/.venvs/atlas/bin/activate` before running tests or Python commands +**Virtual Environment**: Activate with `source ~/.venvs/deep/bin/activate` before running tests or Python commands ### Testing ```bash # Run tests with coverage -pytest --cov=deepdiff --cov-report term-missing +source ~/.venvs/deep/bin/activate && pytest --cov=deepdiff --cov-report term-missing # Run tests including slow ones -pytest --cov=deepdiff --runslow +source ~/.venvs/deep/bin/activate && pytest --cov=deepdiff --runslow # Run single test file -pytest tests/test_diff_text.py +source ~/.venvs/deep/bin/activate && pytest tests/test_diff_text.py -# Run tests across multiple Python versions -nox -s pytest +# Run tests across multiple Python versions. No need to use this unless getting ready for creating a new build +source ~/.venvs/deep/bin/activate && nox -s pytest ``` -### Quality Checks +### **Type Checking with Pyright:** +Always use this pattern for type checking: +```bash +source ~/.venvs/deep/bin/activate && pyright {file_path} +``` + +Examples: +- `source ~/.venvs/deep/bin/activate && pyright deepdiff/diff.py` - Type check specific file +- `source ~/.venvs/deep/bin/activate && pyright deepdiff/` - Type check entire module +- `source ~/.venvs/deep/bin/activate && pyright .` - Type check entire repo + + +### Common Pitfalls to Avoid + +1. **Forgetting Virtual Environment**: ALWAYS activate venv before ANY Python command: + ```bash + source ~/.venvs/deep/bin/activate + ``` +2. **Running pytest without venv**: This will cause import errors. Always use: + ```bash + source ~/.venvs/deep/bin/activate && pytest + ``` +3. **Running module commands without venv**: Commands like `capi run`, `cettings shell`, etc. all require venv to be activated first + + +### Slow quality checks only to run before creating a build ```bash # Linting (max line length: 120) nox -s flake8 @@ -51,6 +76,7 @@ nox -s mypy nox ``` + ## Architecture ### Core Structure From be8c9f31baab3693130bab1ccfacc8771e10384e Mon Sep 17 00:00:00 2001 From: Sep Dehpour Date: Fri, 8 Aug 2025 10:15:31 -0700 Subject: [PATCH 30/39] adding .envrc example --- .direnvrc.example | 11 +++++++++++ .envrc.example | 1 + .gitignore | 3 +++ 3 files changed, 15 insertions(+) create mode 100644 .direnvrc.example create mode 100644 .envrc.example diff --git a/.direnvrc.example b/.direnvrc.example new file mode 100644 index 00000000..fa157d1e --- /dev/null +++ b/.direnvrc.example @@ -0,0 +1,11 @@ +function load_venv () { + ACTUAL_VENV_PATH="$HOME/.venvs/$1" + + if [ -d "$ACTUAL_VENV_PATH" ] && [ -f "$ACTUAL_VENV_PATH/bin/activate" ]; then + echo "direnv: Activating $ACTUAL_VENV_PATH..." + source "$ACTUAL_VENV_PATH/bin/activate" + export UV_PROJECT_ENVIRONMENT="$ACTUAL_VENV_PATH" + else + echo "direnv: Virtual environment at $ACTUAL_VENV_PATH not found or is incomplete." + fi +} diff --git a/.envrc.example b/.envrc.example new file mode 100644 index 00000000..c4b21782 --- /dev/null +++ b/.envrc.example @@ -0,0 +1 @@ +load_venv deep diff --git a/.gitignore b/.gitignore index 7dac60b2..8224ce2e 100644 --- a/.gitignore +++ b/.gitignore @@ -72,3 +72,6 @@ temp* .env pyrightconfig.json + +# direnv file +.envrc From 3eb50b8f345e9131882d49e4f1bdf3af937dc16c Mon Sep 17 00:00:00 2001 From: Sep Dehpour Date: Fri, 8 Aug 2025 10:22:21 -0700 Subject: [PATCH 31/39] adding a pyright config example --- pyrightconfig.json.example | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 pyrightconfig.json.example diff --git a/pyrightconfig.json.example b/pyrightconfig.json.example new file mode 100644 index 00000000..f3828950 --- /dev/null +++ b/pyrightconfig.json.example @@ -0,0 +1,4 @@ +{ + "venvPath": "/home/[your user name]/.venvs/", + "venv": "deep", +} From a1565c42ba2722ae3c5af0b8a3d8eb26e77870e4 Mon Sep 17 00:00:00 2001 From: Sep Dehpour Date: Fri, 8 Aug 2025 10:34:52 -0700 Subject: [PATCH 32/39] adding type hints to deephash --- deepdiff/deephash.py | 208 ++++++++++++++++++++++++++----------------- 1 file changed, 124 insertions(+), 84 deletions(-) diff --git a/deepdiff/deephash.py b/deepdiff/deephash.py index 50c6fd60..8ac51ae4 100644 --- a/deepdiff/deephash.py +++ b/deepdiff/deephash.py @@ -2,12 +2,13 @@ import logging import datetime import uuid -from typing import Union, Optional, Any, List, TYPE_CHECKING +from typing import Union, Optional, Any, List, TYPE_CHECKING, Dict, Tuple, Set, Callable, Iterator, Generator, TypeVar, Protocol from collections.abc import Iterable, MutableMapping from collections import defaultdict from hashlib import sha1, sha256 from pathlib import Path from enum import Enum +import re from deepdiff.helper import (strings, numbers, times, unprocessed, not_hashed, add_to_frozen_set, convert_item_or_items_into_set_else_none, get_doc, ipranges, convert_item_or_items_into_compiled_regexes_else_none, @@ -19,53 +20,65 @@ if TYPE_CHECKING: from pytz.tzinfo import BaseTzInfo + import pandas as pd + import polars as pl + import numpy as np + +# Type aliases for better readability +HashableType = Union[str, int, float, bytes, bool, tuple, frozenset, type(None)] +HashResult = Union[str, Any] # Can be string hash or unprocessed marker +HashTuple = Tuple[HashResult, int] # (hash_result, count) +HashesDict = Dict[Any, Union[HashTuple, List[Any]]] # Special case for UNPROCESSED_KEY +PathType = Union[str, List[str], Set[str]] +RegexType = Union[str, re.Pattern[str], List[Union[str, re.Pattern[str]]]] +NumberToStringFunc = Callable[..., str] # More flexible for different number_to_string implementations try: import pandas except ImportError: - pandas = False + pandas = False # type: ignore try: import polars except ImportError: - polars = False + polars = False # type: ignore try: import numpy as np - booleanTypes = (bool, np.bool_) + booleanTypes: Tuple[type, ...] = (bool, np.bool_) # type: ignore except ImportError: - booleanTypes = bool + booleanTypes = (bool,) # type: ignore -logger = logging.getLogger(__name__) +logger: logging.Logger = logging.getLogger(__name__) -UNPROCESSED_KEY = object() +UNPROCESSED_KEY: object = object() -EMPTY_FROZENSET = frozenset() +EMPTY_FROZENSET: frozenset = frozenset() -INDEX_VS_ATTRIBUTE = ('[%s]', '.%s') +INDEX_VS_ATTRIBUTE: Tuple[str, str] = ('[%s]', '.%s') -HASH_LOOKUP_ERR_MSG = '{} is not one of the hashed items.' +HASH_LOOKUP_ERR_MSG: str = '{} is not one of the hashed items.' -def sha256hex(obj): +def sha256hex(obj: Union[str, bytes]) -> str: """Use Sha256 as a cryptographic hash.""" if isinstance(obj, str): obj = obj.encode('utf-8') return sha256(obj).hexdigest() -def sha1hex(obj): +def sha1hex(obj: Union[str, bytes]) -> str: """Use Sha1 as a cryptographic hash.""" if isinstance(obj, str): obj = obj.encode('utf-8') return sha1(obj).hexdigest() -default_hasher = sha256hex +default_hasher: Callable[[Union[str, bytes]], str] = sha256hex -def combine_hashes_lists(items, prefix): +def combine_hashes_lists(items: List[List[str]], prefix: Union[str, bytes]) -> str: """ Combines lists of hashes into one hash This can be optimized in future. @@ -88,12 +101,12 @@ class BoolObj(Enum): def prepare_string_for_hashing( - obj, - ignore_string_type_changes=False, - ignore_string_case=False, - encodings=None, - ignore_encoding_errors=False, -): + obj: Union[str, bytes, memoryview], + ignore_string_type_changes: bool = False, + ignore_string_case: bool = False, + encodings: Optional[List[str]] = None, + ignore_encoding_errors: bool = False, +) -> str: """ Clean type conversions """ @@ -135,7 +148,7 @@ def prepare_string_for_hashing( obj = KEY_TO_VAL_STR.format(original_type, obj) if ignore_string_case: obj = obj.lower() - return obj + return str(obj) doc = get_doc('deephash_doc.rst') @@ -143,38 +156,65 @@ def prepare_string_for_hashing( class DeepHash(Base): __doc__ = doc + + # Class attributes + hashes: Dict[Any, Any] + exclude_types_tuple: Tuple[type, ...] + ignore_repetition: bool + exclude_paths: Optional[Set[str]] + include_paths: Optional[Set[str]] + exclude_regex_paths: Optional[List[re.Pattern[str]]] + hasher: Callable[[Union[str, bytes]], str] + use_enum_value: bool + default_timezone: Union[datetime.timezone, "BaseTzInfo"] + significant_digits: Optional[int] + truncate_datetime: Optional[str] + number_format_notation: str + ignore_type_in_groups: Any + ignore_string_type_changes: bool + ignore_numeric_type_changes: bool + ignore_string_case: bool + exclude_obj_callback: Optional[Callable[[Any, str], bool]] + apply_hash: bool + type_check_func: Callable[[type, Any], bool] + number_to_string: Any + ignore_private_variables: bool + encodings: Optional[List[str]] + ignore_encoding_errors: bool + ignore_iterable_order: bool + custom_operators: Optional[List[Any]] def __init__(self, obj: Any, *, - apply_hash=True, - custom_operators: Optional[List[Any]] =None, - default_timezone:Union[datetime.timezone, "BaseTzInfo"]=datetime.timezone.utc, - encodings=None, - exclude_obj_callback=None, - exclude_paths=None, - exclude_regex_paths=None, - exclude_types=None, - hasher=None, - hashes=None, - ignore_encoding_errors=False, - ignore_iterable_order=True, - ignore_numeric_type_changes=False, - ignore_private_variables=True, - ignore_repetition=True, - ignore_string_case=False, - ignore_string_type_changes=False, - ignore_type_in_groups=None, - ignore_type_subclasses=False, - ignore_uuid_types=False, - include_paths=None, - number_format_notation="f", - number_to_string_func=None, - parent="root", - significant_digits=None, - truncate_datetime=None, - use_enum_value=False, - **kwargs): + apply_hash: bool = True, + custom_operators: Optional[List[Any]] = None, + default_timezone: Union[datetime.timezone, "BaseTzInfo"] = datetime.timezone.utc, + encodings: Optional[List[str]] = None, + exclude_obj_callback: Optional[Callable[[Any, str], bool]] = None, + exclude_paths: Optional[PathType] = None, + exclude_regex_paths: Optional[RegexType] = None, + exclude_types: Optional[Union[List[type], Set[type], Tuple[type, ...]]] = None, + hasher: Optional[Callable[[Union[str, bytes]], str]] = None, + hashes: Optional[Union[Dict[Any, Any], "DeepHash"]] = None, + ignore_encoding_errors: bool = False, + ignore_iterable_order: bool = True, + ignore_numeric_type_changes: bool = False, + ignore_private_variables: bool = True, + ignore_repetition: bool = True, + ignore_string_case: bool = False, + ignore_string_type_changes: bool = False, + ignore_type_in_groups: Any = None, + ignore_type_subclasses: bool = False, + ignore_uuid_types: bool = False, + include_paths: Optional[PathType] = None, + number_format_notation: str = "f", + number_to_string_func: Optional[NumberToStringFunc] = None, + parent: str = "root", + significant_digits: Optional[int] = None, + truncate_datetime: Optional[str] = None, + use_enum_value: bool = False, + **kwargs) -> None: if kwargs: raise ValueError( ("The following parameter(s) are not valid: %s\n" @@ -197,7 +237,7 @@ def __init__(self, self.include_paths = add_root_to_paths(convert_item_or_items_into_set_else_none(include_paths)) self.exclude_regex_paths = convert_item_or_items_into_compiled_regexes_else_none(exclude_regex_paths) self.hasher = default_hasher if hasher is None else hasher - self.hashes[UNPROCESSED_KEY] = [] + self.hashes[UNPROCESSED_KEY] = [] # type: ignore self.use_enum_value = use_enum_value self.default_timezone = default_timezone self.significant_digits = self.get_significant_digits(significant_digits, ignore_numeric_type_changes) @@ -234,14 +274,14 @@ def __init__(self, else: del self.hashes[UNPROCESSED_KEY] - sha256hex = sha256hex - sha1hex = sha1hex + sha256hex: Callable[[Union[str, bytes]], str] = sha256hex + sha1hex: Callable[[Union[str, bytes]], str] = sha1hex - def __getitem__(self, obj, extract_index=0): + def __getitem__(self, obj: Any, extract_index: Optional[int] = 0) -> Any: return self._getitem(self.hashes, obj, extract_index=extract_index, use_enum_value=self.use_enum_value) @staticmethod - def _getitem(hashes, obj, extract_index=0, use_enum_value=False): + def _getitem(hashes: Dict[Any, Any], obj: Any, extract_index: Optional[int] = 0, use_enum_value: bool = False) -> Any: """ extract_index is zero for hash and 1 for count and None to get them both. To keep it backward compatible, we only get the hash by default so it is set to zero by default. @@ -255,7 +295,7 @@ def _getitem(hashes, obj, extract_index=0, use_enum_value=False): elif use_enum_value and isinstance(obj, Enum): key = obj.value - result_n_count = (None, 0) + result_n_count: Tuple[Any, int] = (None, 0) # type: ignore try: result_n_count = hashes[key] @@ -271,7 +311,7 @@ def _getitem(hashes, obj, extract_index=0, use_enum_value=False): return result_n_count if extract_index is None else result_n_count[extract_index] - def __contains__(self, obj): + def __contains__(self, obj: Any) -> bool: result = False try: result = obj in self.hashes @@ -281,7 +321,7 @@ def __contains__(self, obj): result = get_id(obj) in self.hashes return result - def get(self, key, default=None, extract_index=0): + def get(self, key: Any, default: Any = None, extract_index: Optional[int] = 0) -> Any: """ Get method for the hashes dictionary. It can extract the hash for a given key that is already calculated when extract_index=0 @@ -290,7 +330,7 @@ def get(self, key, default=None, extract_index=0): return self.get_key(self.hashes, key, default=default, extract_index=extract_index) @staticmethod - def get_key(hashes, key, default=None, extract_index=0, use_enum_value=False): + def get_key(hashes: Dict[Any, Any], key: Any, default: Any = None, extract_index: Optional[int] = 0, use_enum_value: bool = False) -> Any: """ get_key method for the hashes dictionary. It can extract the hash for a given key that is already calculated when extract_index=0 @@ -302,7 +342,7 @@ def get_key(hashes, key, default=None, extract_index=0, use_enum_value=False): result = default return result - def _get_objects_to_hashes_dict(self, extract_index=0): + def _get_objects_to_hashes_dict(self, extract_index: Optional[int] = 0) -> Dict[Any, Any]: """ A dictionary containing only the objects to hashes, or a dictionary of objects to the count of items that went to build them. @@ -316,7 +356,7 @@ def _get_objects_to_hashes_dict(self, extract_index=0): result[key] = value[extract_index] return result - def __eq__(self, other): + def __eq__(self, other: Any) -> bool: if isinstance(other, DeepHash): return self.hashes == other.hashes else: @@ -325,29 +365,29 @@ def __eq__(self, other): __req__ = __eq__ - def __repr__(self): + def __repr__(self) -> str: """ Hide the counts since it will be confusing to see them when they are hidden everywhere else. """ from deepdiff.summarize import summarize return summarize(self._get_objects_to_hashes_dict(extract_index=0), max_length=500) - def __str__(self): + def __str__(self) -> str: return str(self._get_objects_to_hashes_dict(extract_index=0)) - def __bool__(self): + def __bool__(self) -> bool: return bool(self.hashes) - def keys(self): + def keys(self) -> Any: return self.hashes.keys() - def values(self): + def values(self) -> Generator[Any, None, None]: return (i[0] for i in self.hashes.values()) # Just grab the item and not its count - def items(self): + def items(self) -> Generator[Tuple[Any, Any], None, None]: return ((i, v[0]) for i, v in self.hashes.items()) - def _prep_obj(self, obj, parent, parents_ids=EMPTY_FROZENSET, is_namedtuple=False, is_pydantic_object=False): + def _prep_obj(self, obj: Any, parent: str, parents_ids: frozenset = EMPTY_FROZENSET, is_namedtuple: bool = False, is_pydantic_object: bool = False) -> HashTuple: """prepping objects""" original_type = type(obj) if not isinstance(obj, type) else obj @@ -372,7 +412,7 @@ def _prep_obj(self, obj, parent, parents_ids=EMPTY_FROZENSET, is_namedtuple=Fals except AttributeError: pass else: - self.hashes[UNPROCESSED_KEY].append(obj) + self.hashes[UNPROCESSED_KEY].append(obj) # type: ignore return (unprocessed, 0) obj = d @@ -381,7 +421,7 @@ def _prep_obj(self, obj, parent, parents_ids=EMPTY_FROZENSET, is_namedtuple=Fals result = "nt{}".format(result) if is_namedtuple else "obj{}".format(result) return result, counts - def _skip_this(self, obj, parent): + def _skip_this(self, obj: Any, parent: str) -> bool: skip = False if self.exclude_paths and parent in self.exclude_paths: skip = True @@ -401,7 +441,7 @@ def _skip_this(self, obj, parent): skip = True return skip - def _prep_dict(self, obj, parent, parents_ids=EMPTY_FROZENSET, print_as_attribute=False, original_type=None): + def _prep_dict(self, obj: Union[Dict[Any, Any], MutableMapping], parent: str, parents_ids: frozenset = EMPTY_FROZENSET, print_as_attribute: bool = False, original_type: Optional[type] = None) -> HashTuple: result = [] counts = 1 @@ -440,7 +480,7 @@ def _prep_dict(self, obj, parent, parents_ids=EMPTY_FROZENSET, print_as_attribut type_str = 'dict' return "{}:{{{}}}".format(type_str, result), counts - def _prep_iterable(self, obj, parent, parents_ids=EMPTY_FROZENSET): + def _prep_iterable(self, obj: Iterable[Any], parent: str, parents_ids: frozenset = EMPTY_FROZENSET) -> HashTuple: counts = 1 result = defaultdict(int) @@ -475,19 +515,19 @@ def _prep_iterable(self, obj, parent, parents_ids=EMPTY_FROZENSET): return result, counts - def _prep_bool(self, obj): + def _prep_bool(self, obj: bool) -> BoolObj: return BoolObj.TRUE if obj else BoolObj.FALSE - def _prep_path(self, obj): + def _prep_path(self, obj: Path) -> str: type_ = obj.__class__.__name__ return KEY_TO_VAL_STR.format(type_, obj) - def _prep_number(self, obj): + def _prep_number(self, obj: Union[int, float, complex]) -> str: type_ = "number" if self.ignore_numeric_type_changes else obj.__class__.__name__ if self.significant_digits is not None: obj = self.number_to_string(obj, significant_digits=self.significant_digits, - number_format_notation=self.number_format_notation) + number_format_notation=self.number_format_notation) # type: ignore return KEY_TO_VAL_STR.format(type_, obj) def _prep_ipranges(self, obj) -> str: @@ -495,20 +535,20 @@ def _prep_ipranges(self, obj) -> str: obj = str(obj) return KEY_TO_VAL_STR.format(type_, obj) - def _prep_datetime(self, obj): + def _prep_datetime(self, obj: datetime.datetime) -> str: type_ = 'datetime' obj = datetime_normalize(self.truncate_datetime, obj, default_timezone=self.default_timezone) return KEY_TO_VAL_STR.format(type_, obj) - def _prep_date(self, obj): + def _prep_date(self, obj: datetime.date) -> str: type_ = 'datetime' # yes still datetime but it doesn't need normalization return KEY_TO_VAL_STR.format(type_, obj) - def _prep_tuple(self, obj, parent, parents_ids): + def _prep_tuple(self, obj: tuple, parent: str, parents_ids: frozenset) -> HashTuple: # Checking to see if it has _fields. Which probably means it is a named # tuple. try: - obj._asdict + obj._asdict # type: ignore # It must be a normal tuple except AttributeError: result, counts = self._prep_iterable(obj=obj, parent=parent, parents_ids=parents_ids) @@ -517,7 +557,7 @@ def _prep_tuple(self, obj, parent, parents_ids): result, counts = self._prep_obj(obj, parent, parents_ids=parents_ids, is_namedtuple=True) return result, counts - def _hash(self, obj, parent, parents_ids=EMPTY_FROZENSET): + def _hash(self, obj: Any, parent: str, parents_ids: frozenset = EMPTY_FROZENSET) -> HashTuple: """The main hash method""" counts = 1 if self.custom_operators is not None: @@ -561,7 +601,7 @@ def _hash(self, obj, parent, parents_ids=EMPTY_FROZENSET): result = self._prep_path(obj) elif isinstance(obj, times): - result = self._prep_datetime(obj) + result = self._prep_datetime(obj) # type: ignore elif isinstance(obj, datetime.date): result = self._prep_date(obj) @@ -574,7 +614,7 @@ def _hash(self, obj, parent, parents_ids=EMPTY_FROZENSET): elif isinstance(obj, uuid.UUID): # Handle UUID objects (including uuid6.UUID) by using their integer value - result = str(obj) + result = str(obj.int) elif isinstance(obj, MutableMapping): result, counts = self._prep_dict(obj=obj, parent=parent, parents_ids=parents_ids) @@ -606,7 +646,7 @@ def gen(): result, counts = self._prep_obj(obj=obj, parent=parent, parents_ids=parents_ids) if result is not_hashed: # pragma: no cover - self.hashes[UNPROCESSED_KEY].append(obj) + self.hashes[UNPROCESSED_KEY].append(obj) # type: ignore elif result is unprocessed: pass @@ -616,7 +656,7 @@ def gen(): result_cleaned = result else: result_cleaned = prepare_string_for_hashing( - result, ignore_string_type_changes=self.ignore_string_type_changes, + str(result), ignore_string_type_changes=self.ignore_string_type_changes, ignore_string_case=self.ignore_string_case) result = self.hasher(result_cleaned) From 91763592fedc09b958e39a25993be40344b9cb2d Mon Sep 17 00:00:00 2001 From: Sep Dehpour Date: Fri, 8 Aug 2025 10:43:05 -0700 Subject: [PATCH 33/39] adding type hints to the helper function --- deepdiff/helper.py | 188 +++++++++++++++++++++++---------------------- 1 file changed, 96 insertions(+), 92 deletions(-) diff --git a/deepdiff/helper.py b/deepdiff/helper.py index 90d7fd2a..0feb8750 100644 --- a/deepdiff/helper.py +++ b/deepdiff/helper.py @@ -9,7 +9,8 @@ import time import enum import ipaddress -from typing import NamedTuple, Any, List, Optional, Dict, Union, TYPE_CHECKING, Tuple +from typing import NamedTuple, Any, List, Optional, Dict, Union, TYPE_CHECKING, Tuple, Iterable, Iterator, Set, FrozenSet, Callable, Pattern, Type, TypeVar, Generic, Literal, overload +from collections.abc import Mapping, Sequence, Generator from ast import literal_eval from decimal import Decimal, localcontext, InvalidOperation as InvalidDecimalOperation from itertools import repeat @@ -29,7 +30,7 @@ class pydantic_base_model_type: class SetOrdered(SetOrderedBase): - def __repr__(self): + def __repr__(self) -> str: return str(list(self)) @@ -83,21 +84,21 @@ def __repr__(self): np_complexfloating = np.complexfloating np_datetime64 = np.datetime64 -numpy_numbers = ( +numpy_numbers: Tuple[Type[Any], ...] = ( np_int8, np_int16, np_int32, np_int64, np_uint8, np_uint16, np_uint32, np_uint64, np_intp, np_uintp, np_float32, np_float64, np_double, np_floating, np_complex64, np_complex128, np_cdouble,) -numpy_complex_numbers = ( +numpy_complex_numbers: Tuple[Type[Any], ...] = ( np_complexfloating, np_complex64, np_complex128, np_cdouble, ) -numpy_dtypes = set(numpy_numbers) +numpy_dtypes: Set[Type[Any]] = set(numpy_numbers) numpy_dtypes.add(np_bool_) # type: ignore numpy_dtypes.add(np_datetime64) # type: ignore -numpy_dtype_str_to_type = { +numpy_dtype_str_to_type: Dict[str, Type[Any]] = { item.__name__: item for item in numpy_dtypes } @@ -112,28 +113,28 @@ def __repr__(self): py_major_version = sys.version_info.major py_minor_version = sys.version_info.minor -py_current_version = Decimal("{}.{}".format(py_major_version, py_minor_version)) +py_current_version: Decimal = Decimal("{}.{}".format(py_major_version, py_minor_version)) py2 = py_major_version == 2 py3 = py_major_version == 3 py4 = py_major_version == 4 -NUMERICS = frozenset(string.digits) +NUMERICS: FrozenSet[str] = frozenset(string.digits) class EnumBase(str, enum.Enum): - def __repr__(self): + def __repr__(self) -> str: """ We need to add a single quotes so we can easily copy the value when we do ipdb. """ return f"'{self.name}'" - def __str__(self): + def __str__(self) -> str: return self.name -def _int_or_zero(value): +def _int_or_zero(value: str) -> int: """ Tries to extract some number from a string. @@ -151,19 +152,19 @@ def _int_or_zero(value): return 0 -def get_semvar_as_integer(version): +def get_semvar_as_integer(version: str) -> int: """ Converts: '1.23.5' to 1023005 """ - version = version.split('.') - if len(version) > 3: - version = version[:3] - elif len(version) < 3: - version.extend(['0'] * (3 - len(version))) + version_parts = version.split('.') + if len(version_parts) > 3: + version_parts = version_parts[:3] + elif len(version_parts) < 3: + version_parts.extend(['0'] * (3 - len(version_parts))) - return sum([10**(i * 3) * _int_or_zero(v) for i, v in enumerate(reversed(version))]) + return sum([10**(i * 3) * _int_or_zero(v) for i, v in enumerate(reversed(version_parts))]) # we used to use OrderedDictPlus when dictionaries in Python were not ordered. @@ -182,22 +183,22 @@ def get_semvar_as_integer(version): if np and get_semvar_as_integer(np.__version__) < 1019000: sys.exit('The minimum required Numpy version is 1.19.0. Please upgrade your Numpy package.') -strings = (str, bytes, memoryview) # which are both basestring +strings: Tuple[Type[str], Type[bytes], Type[memoryview]] = (str, bytes, memoryview) # which are both basestring unicode_type = str bytes_type = bytes -only_complex_number = (complex,) + numpy_complex_numbers -only_numbers = (int, float, complex, Decimal) + numpy_numbers -datetimes = (datetime.datetime, datetime.date, datetime.timedelta, datetime.time, np_datetime64) -ipranges = (ipaddress.IPv4Interface, ipaddress.IPv6Interface, ipaddress.IPv4Network, ipaddress.IPv6Network, ipaddress.IPv4Address, ipaddress.IPv6Address) -uuids = (uuid.UUID, ) -times = (datetime.datetime, datetime.time,np_datetime64) -numbers: Tuple = only_numbers + datetimes -booleans = (bool, np_bool_) +only_complex_number: Tuple[Type[Any], ...] = (complex,) + numpy_complex_numbers +only_numbers: Tuple[Type[Any], ...] = (int, float, complex, Decimal) + numpy_numbers +datetimes: Tuple[Type[Any], ...] = (datetime.datetime, datetime.date, datetime.timedelta, datetime.time, np_datetime64) +ipranges: Tuple[Type[Any], ...] = (ipaddress.IPv4Interface, ipaddress.IPv6Interface, ipaddress.IPv4Network, ipaddress.IPv6Network, ipaddress.IPv4Address, ipaddress.IPv6Address) +uuids: Tuple[Type[uuid.UUID]] = (uuid.UUID, ) +times: Tuple[Type[Any], ...] = (datetime.datetime, datetime.time, np_datetime64) +numbers: Tuple[Type[Any], ...] = only_numbers + datetimes +booleans: Tuple[Type[bool], Type[Any]] = (bool, np_bool_) -basic_types = strings + numbers + uuids + booleans + (type(None), ) +basic_types: Tuple[Type[Any], ...] = strings + numbers + uuids + booleans + (type(None), ) class IndexedHash(NamedTuple): - indexes: List + indexes: List[Any] item: Any current_dir = os.path.dirname(os.path.abspath(__file__)) @@ -212,10 +213,10 @@ class IndexedHash(NamedTuple): COLORED_VIEW = 'colored' COLORED_COMPACT_VIEW = 'colored_compact' -ENUM_INCLUDE_KEYS = ['__objclass__', 'name', 'value'] +ENUM_INCLUDE_KEYS: List[str] = ['__objclass__', 'name', 'value'] -def short_repr(item, max_length=15): +def short_repr(item: Any, max_length: int = 15) -> str: """Short representation of item if it is too long""" item = repr(item) if len(item) > max_length: @@ -229,7 +230,7 @@ class ListItemRemovedOrAdded: # pragma: no cover class OtherTypes: - def __repr__(self): + def __repr__(self) -> str: return "Error: {}".format(self.__class__.__name__) # pragma: no cover __str__ = __repr__ @@ -254,7 +255,7 @@ class NotPresent: # pragma: no cover We previously used None for this but this caused problem when users actually added and removed None. Srsly guys? :D """ - def __repr__(self): + def __repr__(self) -> str: return 'not present' # pragma: no cover __str__ = __repr__ @@ -309,22 +310,21 @@ class indexed_set(set): """ -def add_to_frozen_set(parents_ids, item_id): +def add_to_frozen_set(parents_ids: FrozenSet[str], item_id: str) -> FrozenSet[str]: return parents_ids | {item_id} -def convert_item_or_items_into_set_else_none(items): +def convert_item_or_items_into_set_else_none(items: Union[str, Iterable[str], None]) -> Optional[Set[str]]: if items: - if isinstance(items, strings): - items = {items} + if isinstance(items, str): + return {items} else: - items = set(items) + return set(items) else: - items = None - return items + return None -def add_root_to_paths(paths): +def add_root_to_paths(paths: Optional[Iterable[str]]) -> Optional[SetOrdered]: """ Sometimes the users want to just pass [key] instead of root[key] for example. @@ -352,24 +352,25 @@ def add_root_to_paths(paths): RE_COMPILED_TYPE = type(re.compile('')) -def convert_item_or_items_into_compiled_regexes_else_none(items): +def convert_item_or_items_into_compiled_regexes_else_none(items: Union[str, Pattern[str], Iterable[Union[str, Pattern[str]]], None]) -> Optional[List[Pattern[str]]]: if items: - if isinstance(items, (strings, RE_COMPILED_TYPE)): - items = [items] - items = [i if isinstance(i, RE_COMPILED_TYPE) else re.compile(i) for i in items] + if isinstance(items, (str, RE_COMPILED_TYPE)): + items_list = [items] # type: ignore + else: + items_list = list(items) # type: ignore + return [i if isinstance(i, RE_COMPILED_TYPE) else re.compile(i) for i in items_list] else: - items = None - return items + return None -def get_id(obj): +def get_id(obj: Any) -> str: """ Adding some characters to id so they are not just integers to reduce the risk of collision. """ return "{}{}".format(ID_PREFIX, id(obj)) -def get_type(obj): +def get_type(obj: Any) -> Type[Any]: """ Get the type of object or if it is a class, return the class itself. """ @@ -378,21 +379,21 @@ def get_type(obj): return obj if type(obj) is type else type(obj) -def numpy_dtype_string_to_type(dtype_str): +def numpy_dtype_string_to_type(dtype_str: str) -> Type[Any]: return numpy_dtype_str_to_type[dtype_str] -def type_in_type_group(item, type_group): +def type_in_type_group(item: Any, type_group: Tuple[Type[Any], ...]) -> bool: return get_type(item) in type_group -def type_is_subclass_of_type_group(item, type_group): +def type_is_subclass_of_type_group(item: Any, type_group: Tuple[Type[Any], ...]) -> bool: return isinstance(item, type_group) \ or (isinstance(item, type) and issubclass(item, type_group)) \ or type_in_type_group(item, type_group) -def get_doc(doc_filename): +def get_doc(doc_filename: str) -> str: try: with open(os.path.join(current_dir, '../docs/', doc_filename), 'r') as doc_file: doc = doc_file.read() @@ -401,13 +402,13 @@ def get_doc(doc_filename): return doc -number_formatting = { +number_formatting: Dict[str, str] = { "f": r'{:.%sf}', "e": r'{:.%se}', } -def number_to_string(number, significant_digits, number_format_notation="f"): +def number_to_string(number: Any, significant_digits: int, number_format_notation: Literal['f', 'e'] = 'f') -> Any: """ Convert numbers to string considering significant digits. """ @@ -450,7 +451,7 @@ def number_to_string(number, significant_digits, number_format_notation="f"): number = round(number=number, ndigits=significant_digits) # type: ignore if significant_digits == 0: - number = int(number) + number = int(number) # type: ignore if number == 0.0: # Special case for 0: "-0.xx" should compare equal to "0.xx" @@ -476,7 +477,7 @@ class DeepDiffDeprecationWarning(DeprecationWarning): pass -def cartesian_product(a, b): +def cartesian_product(a: Iterable[Tuple[Any, ...]], b: Iterable[Any]) -> Iterator[Tuple[Any, ...]]: """ Get the Cartesian product of two iterables @@ -491,7 +492,7 @@ def cartesian_product(a, b): yield i + (j,) -def cartesian_product_of_shape(dimentions, result=None): +def cartesian_product_of_shape(dimentions: Iterable[int], result: Optional[Tuple[Tuple[Any, ...], ...]] = None) -> Iterator[Tuple[Any, ...]]: """ Cartesian product of a dimentions iterable. This is mainly used to traverse Numpy ndarrays. @@ -501,18 +502,18 @@ def cartesian_product_of_shape(dimentions, result=None): if result is None: result = ((),) # a tuple with an empty tuple for dimension in dimentions: - result = cartesian_product(result, range(dimension)) - return result + result = tuple(cartesian_product(result, range(dimension))) + return iter(result) -def get_numpy_ndarray_rows(obj, shape=None): +def get_numpy_ndarray_rows(obj: Any, shape: Optional[Tuple[int, ...]] = None) -> Generator[Tuple[Tuple[int, ...], Any], None, None]: """ Convert a multi dimensional numpy array to list of rows """ if shape is None: - shape = obj.shape + shape = obj.shape # type: ignore - dimentions = shape[:-1] + dimentions = shape[:-1] if shape else () for path_tuple in cartesian_product_of_shape(dimentions): result = obj for index in path_tuple: @@ -522,12 +523,12 @@ def get_numpy_ndarray_rows(obj, shape=None): class _NotFound: - def __eq__(self, other): + def __eq__(self, other: Any) -> bool: return False __req__ = __eq__ - def __repr__(self): + def __repr__(self) -> str: return 'not found' __str__ = __repr__ @@ -544,7 +545,7 @@ class RepeatedTimer: https://stackoverflow.com/a/38317060/1497443 """ - def __init__(self, interval, function, *args, **kwargs): + def __init__(self, interval: float, function: Callable[..., Any], *args: Any, **kwargs: Any) -> None: self._timer = None self.interval = interval self.function = function @@ -554,22 +555,22 @@ def __init__(self, interval, function, *args, **kwargs): self.is_running = False self.start() - def _get_duration_sec(self): + def _get_duration_sec(self) -> int: return int(time.time() - self.start_time) - def _run(self): + def _run(self) -> None: self.is_running = False self.start() self.function(*self.args, **self.kwargs) - def start(self): + def start(self) -> None: self.kwargs.update(duration=self._get_duration_sec()) if not self.is_running: self._timer = Timer(self.interval, self._run) self._timer.start() self.is_running = True - def stop(self): + def stop(self) -> int: duration = self._get_duration_sec() if self._timer is not None: self._timer.cancel() @@ -577,30 +578,30 @@ def stop(self): return duration -def _eval_decimal(params): +def _eval_decimal(params: str) -> Decimal: return Decimal(params) -def _eval_datetime(params): - params = f'({params})' - params = literal_eval(params) - return datetime.datetime(*params) +def _eval_datetime(params: str) -> datetime.datetime: + params_with_parens = f'({params})' + params_tuple = literal_eval(params_with_parens) + return datetime.datetime(*params_tuple) # type: ignore -def _eval_date(params): - params = f'({params})' - params = literal_eval(params) - return datetime.date(*params) +def _eval_date(params: str) -> datetime.date: + params_with_parens = f'({params})' + params_tuple = literal_eval(params_with_parens) + return datetime.date(*params_tuple) # type: ignore -LITERAL_EVAL_PRE_PROCESS = [ +LITERAL_EVAL_PRE_PROCESS: List[Tuple[str, str, Callable[[str], Any]]] = [ ('Decimal(', ')', _eval_decimal), ('datetime.datetime(', ')', _eval_datetime), ('datetime.date(', ')', _eval_date), ] -def literal_eval_extended(item): +def literal_eval_extended(item: str) -> Any: """ An extended version of literal_eval """ @@ -615,7 +616,7 @@ def literal_eval_extended(item): raise -def time_to_seconds(t:datetime.time) -> int: +def time_to_seconds(t: datetime.time) -> int: return (t.hour * 60 + t.minute) * 60 + t.second @@ -645,7 +646,7 @@ def datetime_normalize( return obj -def has_timezone(dt): +def has_timezone(dt: datetime.datetime) -> bool: """ Function to check if a datetime object has a timezone @@ -659,7 +660,7 @@ def has_timezone(dt): return dt.tzinfo is not None and dt.tzinfo.utcoffset(dt) is not None -def get_truncate_datetime(truncate_datetime) -> Union[str, None]: +def get_truncate_datetime(truncate_datetime: Union[str, None]) -> Union[str, None]: """ Validates truncate_datetime value """ @@ -668,7 +669,7 @@ def get_truncate_datetime(truncate_datetime) -> Union[str, None]: return truncate_datetime -def cartesian_product_numpy(*arrays): +def cartesian_product_numpy(*arrays: Any) -> Any: """ Cartesian product of Numpy arrays by Paul Panzer https://stackoverflow.com/a/49445693/1497443 @@ -682,7 +683,7 @@ def cartesian_product_numpy(*arrays): return arr.reshape(la, -1).T -def diff_numpy_array(A, B): +def diff_numpy_array(A: Any, B: Any) -> Any: """ Numpy Array A - B return items in A that are not in B @@ -692,14 +693,14 @@ def diff_numpy_array(A, B): return A[~np.isin(A, B)] # type: ignore -PYTHON_TYPE_TO_NUMPY_TYPE = { +PYTHON_TYPE_TO_NUMPY_TYPE: Dict[Type[Any], Type[Any]] = { int: np_int64, float: np_float64, Decimal: np_float64 } -def get_homogeneous_numpy_compatible_type_of_seq(seq): +def get_homogeneous_numpy_compatible_type_of_seq(seq: Sequence[Any]) -> Union[Type[Any], Literal[False]]: """ Return with the numpy dtype if the array can be converted to a non-object numpy array. Originally written by mgilson https://stackoverflow.com/a/13252348/1497443 @@ -708,13 +709,16 @@ def get_homogeneous_numpy_compatible_type_of_seq(seq): iseq = iter(seq) first_type = type(next(iseq)) if first_type in {int, float, Decimal}: - type_ = first_type if all((type(x) is first_type) for x in iseq) else False - return PYTHON_TYPE_TO_NUMPY_TYPE.get(type_, False) + type_match = first_type if all((type(x) is first_type) for x in iseq) else False + if type_match: + return PYTHON_TYPE_TO_NUMPY_TYPE.get(type_match, False) + else: + return False else: return False -def detailed__dict__(obj, ignore_private_variables=True, ignore_keys=frozenset(), include_keys=None): +def detailed__dict__(obj: Any, ignore_private_variables: bool = True, ignore_keys: FrozenSet[str] = frozenset(), include_keys: Optional[List[str]] = None) -> Dict[str, Any]: """ Get the detailed dictionary of an object. @@ -754,7 +758,7 @@ def detailed__dict__(obj, ignore_private_variables=True, ignore_keys=frozenset() return result -def named_tuple_repr(self): +def named_tuple_repr(self: NamedTuple) -> str: fields = [] for field, value in self._asdict().items(): # Only include fields that do not have their default value From f7b6510ca0dffc47e6eac5fe7a7a5a7fba1186a0 Mon Sep 17 00:00:00 2001 From: Sep Dehpour Date: Fri, 8 Aug 2025 10:55:13 -0700 Subject: [PATCH 34/39] Adding type hints to model.py --- deepdiff/model.py | 136 ++++++++++++++++++++++++---------------------- 1 file changed, 72 insertions(+), 64 deletions(-) diff --git a/deepdiff/model.py b/deepdiff/model.py index bba2fe8e..ed398d60 100644 --- a/deepdiff/model.py +++ b/deepdiff/model.py @@ -1,17 +1,21 @@ import logging from collections.abc import Mapping from copy import copy +from typing import Any, Dict, List, Optional, Set, Union, Literal, Type, TYPE_CHECKING from deepdiff.helper import ( RemapDict, strings, notpresent, get_type, numpy_numbers, np, literal_eval_extended, dict_, SetOrdered) from deepdiff.path import stringify_element +if TYPE_CHECKING: + from deepdiff.diff import DeepDiff + logger = logging.getLogger(__name__) -FORCE_DEFAULT = 'fake' -UP_DOWN = {'up': 'down', 'down': 'up'} +FORCE_DEFAULT: Literal['fake'] = 'fake' +UP_DOWN: Dict[str, str] = {'up': 'down', 'down': 'up'} -REPORT_KEYS = { +REPORT_KEYS: Set[str] = { "type_changes", "dictionary_item_added", "dictionary_item_removed", @@ -27,7 +31,7 @@ "repetition_change", } -CUSTOM_FIELD = "__internal:custom:extra_info" +CUSTOM_FIELD: str = "__internal:custom:extra_info" class DoesNotExist(Exception): @@ -36,7 +40,7 @@ class DoesNotExist(Exception): class ResultDict(RemapDict): - def remove_empty_keys(self): + def remove_empty_keys(self) -> None: """ Remove empty keys from this object. Should always be called after the result is final. :return: @@ -48,11 +52,11 @@ def remove_empty_keys(self): class TreeResult(ResultDict): - def __init__(self): + def __init__(self) -> None: for key in REPORT_KEYS: self[key] = SetOrdered() - def mutual_add_removes_to_become_value_changes(self): + def mutual_add_removes_to_become_value_changes(self) -> None: """ There might be the same paths reported in the results as removed and added. In such cases they should be reported as value_changes. @@ -84,12 +88,16 @@ def mutual_add_removes_to_become_value_changes(self): if 'iterable_item_added' in self and not iterable_item_added: del self['iterable_item_added'] - def __getitem__(self, item): + def __getitem__(self, item: str) -> SetOrdered: if item not in self: self[item] = SetOrdered() - return self.get(item) + result = self.get(item) + if result is None: + result = SetOrdered() + self[item] = result + return result - def __len__(self): + def __len__(self) -> int: length = 0 for value in self.values(): if isinstance(value, SetOrdered): @@ -100,9 +108,9 @@ def __len__(self): class TextResult(ResultDict): - ADD_QUOTES_TO_STRINGS = True + ADD_QUOTES_TO_STRINGS: bool = True - def __init__(self, tree_results=None, verbose_level=1): + def __init__(self, tree_results: Optional['TreeResult'] = None, verbose_level: int = 1) -> None: self.verbose_level = verbose_level # TODO: centralize keys self.update({ @@ -124,10 +132,10 @@ def __init__(self, tree_results=None, verbose_level=1): if tree_results: self._from_tree_results(tree_results) - def __set_or_dict(self): + def __set_or_dict(self) -> Union[Dict[str, Any], SetOrdered]: return {} if self.verbose_level >= 2 else SetOrdered() - def _from_tree_results(self, tree): + def _from_tree_results(self, tree: 'TreeResult') -> None: """ Populate this object by parsing an existing reference-style result dictionary. :param tree: A TreeResult @@ -149,7 +157,7 @@ def _from_tree_results(self, tree): self._from_tree_deep_distance(tree) self._from_tree_custom_results(tree) - def _from_tree_default(self, tree, report_type, ignore_if_in_iterable_opcodes=False): + def _from_tree_default(self, tree: 'TreeResult', report_type: str, ignore_if_in_iterable_opcodes: bool = False) -> None: if report_type in tree: for change in tree[report_type]: # report each change @@ -291,9 +299,9 @@ def _from_tree_custom_results(self, tree): class DeltaResult(TextResult): - ADD_QUOTES_TO_STRINGS = False + ADD_QUOTES_TO_STRINGS: bool = False - def __init__(self, tree_results=None, ignore_order=None, always_include_values=False, _iterable_opcodes=None): + def __init__(self, tree_results: Optional['TreeResult'] = None, ignore_order: Optional[bool] = None, always_include_values: bool = False, _iterable_opcodes: Optional[Dict[str, Any]] = None) -> None: self.ignore_order = ignore_order self.always_include_values = always_include_values @@ -517,15 +525,15 @@ class DiffLevel: """ def __init__(self, - t1, - t2, - down=None, - up=None, - report_type=None, - child_rel1=None, - child_rel2=None, - additional=None, - verbose_level=1): + t1: Any, + t2: Any, + down: Optional['DiffLevel'] = None, + up: Optional['DiffLevel'] = None, + report_type: Optional[str] = None, + child_rel1: Optional['ChildRelationship'] = None, + child_rel2: Optional['ChildRelationship'] = None, + additional: Optional[Dict[str, Any]] = None, + verbose_level: int = 1) -> None: """ :param child_rel1: Either: - An existing ChildRelationship object describing the "down" relationship for t1; or @@ -581,7 +589,7 @@ def __init__(self, self.verbose_level = verbose_level - def __repr__(self): + def __repr__(self) -> str: if self.verbose_level: from deepdiff.summarize import summarize @@ -596,7 +604,7 @@ def __repr__(self): result = "<{}>".format(self.path()) return result - def __setattr__(self, key, value): + def __setattr__(self, key: str, value: Any) -> None: # Setting up or down, will set the opposite link in this linked list. if key in UP_DOWN and value is not None: self.__dict__[key] = value @@ -605,15 +613,15 @@ def __setattr__(self, key, value): else: self.__dict__[key] = value - def __iter__(self): + def __iter__(self) -> Any: yield self.t1 yield self.t2 @property - def repetition(self): + def repetition(self) -> Dict[str, Any]: return self.additional['repetition'] - def auto_generate_child_rel(self, klass, param, param2=None): + def auto_generate_child_rel(self, klass: Type['ChildRelationship'], param: Any, param2: Optional[Any] = None) -> None: """ Auto-populate self.child_rel1 and self.child_rel2. This requires self.down to be another valid DiffLevel object. @@ -630,7 +638,7 @@ def auto_generate_child_rel(self, klass, param, param2=None): klass=klass, parent=self.t2, child=self.down.t2, param=param if param2 is None else param2) # type: ignore @property - def all_up(self): + def all_up(self) -> 'DiffLevel': """ Get the root object of this comparison. (This is a convenient wrapper for following the up attribute as often as you can.) @@ -642,7 +650,7 @@ def all_up(self): return level @property - def all_down(self): + def all_down(self) -> 'DiffLevel': """ Get the leaf object of this comparison. (This is a convenient wrapper for following the down attribute as often as you can.) @@ -654,10 +662,10 @@ def all_down(self): return level @staticmethod - def _format_result(root, result): + def _format_result(root: str, result: Optional[str]) -> Optional[str]: return None if result is None else "{}{}".format(root, result) - def get_root_key(self, use_t2=False): + def get_root_key(self, use_t2: bool = False) -> Any: """ Get the path's root key value for this change @@ -674,7 +682,7 @@ def get_root_key(self, use_t2=False): return next_rel.param return notpresent - def path(self, root="root", force=None, get_parent_too=False, use_t2=False, output_format='str', reporting_move=False): + def path(self, root: str = "root", force: Optional[str] = None, get_parent_too: bool = False, use_t2: bool = False, output_format: Literal['str', 'list'] = 'str', reporting_move: bool = False) -> Any: """ A python syntax string describing how to descend to this level, assuming the top level object is called root. Returns None if the path is not representable as a string. @@ -765,18 +773,18 @@ def path(self, root="root", force=None, get_parent_too=False, use_t2=False, outp output = (self._format_result(root, parent), param, self._format_result(root, result)) # type: ignore else: self._path[cache_key] = result - output = self._format_result(root, result) + output = self._format_result(root, result) if isinstance(result, (str, type(None))) else None else: output = result return output def create_deeper(self, - new_t1, - new_t2, - child_relationship_class, - child_relationship_param=None, - child_relationship_param2=None, - report_type=None): + new_t1: Any, + new_t2: Any, + child_relationship_class: Type['ChildRelationship'], + child_relationship_param: Optional[Any] = None, + child_relationship_param2: Optional[Any] = None, + report_type: Optional[str] = None) -> 'DiffLevel': """ Start a new comparison level and correctly link it to this one. :rtype: DiffLevel @@ -791,12 +799,12 @@ def create_deeper(self, return result def branch_deeper(self, - new_t1, - new_t2, - child_relationship_class, - child_relationship_param=None, - child_relationship_param2=None, - report_type=None): + new_t1: Any, + new_t2: Any, + child_relationship_class: Type['ChildRelationship'], + child_relationship_param: Optional[Any] = None, + child_relationship_param2: Optional[Any] = None, + report_type: Optional[str] = None) -> 'DiffLevel': """ Branch this comparison: Do not touch this comparison line, but create a new one with exactly the same content, just one level deeper. @@ -807,7 +815,7 @@ def branch_deeper(self, return branch.create_deeper(new_t1, new_t2, child_relationship_class, child_relationship_param, child_relationship_param2, report_type) - def copy(self): + def copy(self) -> 'DiffLevel': """ Get a deep copy of this comparision line. :return: The leaf ("downmost") object of the copy. @@ -850,20 +858,20 @@ class ChildRelationship: # Format to a be used for representing param. # E.g. for a dict, this turns a formatted param param "42" into "[42]". - param_repr_format = None + param_repr_format: Optional[str] = None # This is a hook allowing subclasses to manipulate param strings. # :param string: Input string # :return: Manipulated string, as appropriate in this context. - quote_str = None + quote_str: Optional[str] = None @staticmethod - def create(klass, parent, child, param=None): + def create(klass: Type['ChildRelationship'], parent: Any, child: Any, param: Optional[Any] = None) -> 'ChildRelationship': if not issubclass(klass, ChildRelationship): raise TypeError return klass(parent, child, param) - def __init__(self, parent, child, param=None): + def __init__(self, parent: Any, child: Any, param: Optional[Any] = None) -> None: # The parent object of this relationship, e.g. a dict self.parent = parent @@ -873,7 +881,7 @@ def __init__(self, parent, child, param=None): # A subclass-dependent parameter describing how to get from parent to child, e.g. the key in a dict self.param = param - def __repr__(self): + def __repr__(self) -> str: from deepdiff.summarize import summarize name = "<{} parent:{}, child:{}, param:{}>" @@ -882,7 +890,7 @@ def __repr__(self): param = summarize(self.param, max_length=15) return name.format(self.__class__.__name__, parent, child, param) - def get_param_repr(self, force=None): + def get_param_repr(self, force: Optional[str] = None) -> Optional[str]: """ Returns a formatted param python parsable string describing this relationship, or None if the relationship is not representable as a string. @@ -899,7 +907,7 @@ def get_param_repr(self, force=None): """ return self.stringify_param(force) - def stringify_param(self, force=None): + def stringify_param(self, force: Optional[str] = None) -> Optional[str]: """ Convert param to a string. Return None if there is no string representation. This is called by get_param_repr() @@ -946,13 +954,13 @@ def stringify_param(self, force=None): class DictRelationship(ChildRelationship): - param_repr_format = "[{}]" - quote_str = "'{}'" + param_repr_format: Optional[str] = "[{}]" + quote_str: Optional[str] = "'{}'" class NumpyArrayRelationship(ChildRelationship): - param_repr_format = "[{}]" - quote_str = None + param_repr_format: Optional[str] = "[{}]" + quote_str: Optional[str] = None class SubscriptableIterableRelationship(DictRelationship): @@ -970,9 +978,9 @@ class SetRelationship(InaccessibleRelationship): class NonSubscriptableIterableRelationship(InaccessibleRelationship): - param_repr_format = "[{}]" + param_repr_format: Optional[str] = "[{}]" - def get_param_repr(self, force=None): + def get_param_repr(self, force: Optional[str] = None) -> Optional[str]: if force == 'yes': result = "(unrepresentable)" elif force == 'fake' and self.param: @@ -984,4 +992,4 @@ def get_param_repr(self, force=None): class AttributeRelationship(ChildRelationship): - param_repr_format = ".{}" + param_repr_format: Optional[str] = ".{}" From b6c93cac185aba74c252e24b017fa9ed6be6dc19 Mon Sep 17 00:00:00 2001 From: Sep Dehpour Date: Fri, 8 Aug 2025 11:07:19 -0700 Subject: [PATCH 35/39] adding type hints --- deepdiff/base.py | 14 +++++---- deepdiff/diff.py | 71 +++++++++++++++++++++++--------------------- deepdiff/distance.py | 2 +- deepdiff/helper.py | 2 +- 4 files changed, 47 insertions(+), 42 deletions(-) diff --git a/deepdiff/base.py b/deepdiff/base.py index 8953182d..4dfeea69 100644 --- a/deepdiff/base.py +++ b/deepdiff/base.py @@ -1,4 +1,5 @@ import uuid +from typing import List, Optional, Union, Tuple, Any, Type from deepdiff.helper import strings, numbers, SetOrdered @@ -10,7 +11,7 @@ class Base: numbers = numbers strings = strings - def get_significant_digits(self, significant_digits, ignore_numeric_type_changes): + def get_significant_digits(self, significant_digits: Optional[int], ignore_numeric_type_changes: bool) -> Optional[int]: if significant_digits is not None and significant_digits < 0: raise ValueError( "significant_digits must be None or a non-negative integer") @@ -19,11 +20,12 @@ def get_significant_digits(self, significant_digits, ignore_numeric_type_changes significant_digits = DEFAULT_SIGNIFICANT_DIGITS_WHEN_IGNORE_NUMERIC_TYPES return significant_digits - def get_ignore_types_in_groups(self, ignore_type_in_groups, - ignore_string_type_changes, - ignore_numeric_type_changes, - ignore_type_subclasses, - ignore_uuid_types=False): + def get_ignore_types_in_groups(self, + ignore_type_in_groups: Optional[Union[List[Any], Tuple[Any, ...]]], + ignore_string_type_changes: bool, + ignore_numeric_type_changes: bool, + ignore_type_subclasses: bool, + ignore_uuid_types: bool = False) -> List[Union[SetOrdered, Tuple[Type[Any], ...]]]: if ignore_type_in_groups: if isinstance(ignore_type_in_groups[0], type): ignore_type_in_groups = [ignore_type_in_groups] diff --git a/deepdiff/diff.py b/deepdiff/diff.py index 784eab47..43ccd00b 100755 --- a/deepdiff/diff.py +++ b/deepdiff/diff.py @@ -13,7 +13,7 @@ from enum import Enum from copy import deepcopy from math import isclose as is_close -from typing import List, Dict, Callable, Union, Any, Pattern, Tuple, Optional, Set, FrozenSet, TYPE_CHECKING, Protocol +from typing import List, Dict, Callable, Union, Any, Pattern, Tuple, Optional, Set, FrozenSet, TYPE_CHECKING, Protocol, Literal from collections.abc import Mapping, Iterable, Sequence from collections import defaultdict from inspect import getmembers @@ -67,7 +67,7 @@ PROGRESS_MSG = "DeepDiff {} seconds in progress. Pass #{}, Diff #{}" -def _report_progress(_stats, progress_logger, duration): +def _report_progress(_stats: Dict[str, Any], progress_logger: Callable[[str], None], duration: float) -> None: """ Report the progress every few seconds. """ @@ -130,6 +130,7 @@ class DeepDiffProtocol(Protocol): use_log_scale: bool log_scale_similarity_threshold: float view: str + math_epsilon: Optional[float] @@ -141,7 +142,7 @@ class DeepDiff(ResultDict, SerializationMixin, DistanceMixin, DeepDiffProtocol, def __init__(self, t1: Any, t2: Any, - _original_type=None, + _original_type: Optional[Any]=None, cache_purge_level: int=1, cache_size: int=0, cache_tuning_sample_size: int=0, @@ -154,12 +155,12 @@ def __init__(self, exclude_obj_callback_strict: Optional[Callable]=None, exclude_paths: Union[str, List[str], Set[str], FrozenSet[str], None]=None, exclude_regex_paths: Union[str, List[str], Pattern[str], List[Pattern[str]], None]=None, - exclude_types: Optional[List[Any]]=None, + exclude_types: Optional[List[type]]=None, get_deep_distance: bool=False, group_by: Union[str, Tuple[str, str], None]=None, group_by_sort_key: Union[str, Callable, None]=None, hasher: Optional[Callable]=None, - hashes: Optional[Dict]=None, + hashes: Optional[Dict[Any, Any]]=None, ignore_encoding_errors: bool=False, ignore_nan_inequality: bool=False, ignore_numeric_type_changes: bool=False, @@ -168,7 +169,7 @@ def __init__(self, ignore_private_variables: bool=True, ignore_string_case: bool=False, ignore_string_type_changes: bool=False, - ignore_type_in_groups: Optional[List[Tuple]]=None, + ignore_type_in_groups: Optional[List[Tuple[Any, ...]]]=None, ignore_type_subclasses: bool=False, ignore_uuid_types: bool=False, include_obj_callback: Optional[Callable]=None, @@ -181,9 +182,9 @@ def __init__(self, math_epsilon: Optional[float]=None, max_diffs: Optional[int]=None, max_passes: int=10000000, - number_format_notation: str="f", + number_format_notation: Literal["f", "e"]="f", number_to_string_func: Optional[Callable]=None, - progress_logger: Callable=logger.info, + progress_logger: Callable[[str], None]=logger.info, report_repetition: bool=False, significant_digits: Optional[int]=None, threshold_to_diff_deeper: float = 0.33, @@ -193,8 +194,8 @@ def __init__(self, verbose_level: int=1, view: str=TEXT_VIEW, zip_ordered_iterables: bool=False, - _parameters=None, - _shared_parameters=None, + _parameters: Optional[Dict[str, Any]]=None, + _shared_parameters: Optional[Dict[str, Any]]=None, **kwargs): super().__init__() if kwargs: @@ -443,8 +444,8 @@ def custom_report_result(self, report_type, level, extra_info=None): self.tree[report_type].add(level) @staticmethod - def _dict_from_slots(object): - def unmangle(attribute): + def _dict_from_slots(object: Any) -> Dict[str, Any]: + def unmangle(attribute: str) -> str: if attribute.startswith('__') and attribute != '__weakref__': return '_{type}{attribute}'.format( type=type(object).__name__, @@ -469,7 +470,7 @@ def unmangle(attribute): return {i: getattr(object, key) for i in all_slots if hasattr(object, key := unmangle(i))} - def _diff_enum(self, level, parents_ids=frozenset(), local_tree=None): + def _diff_enum(self, level: Any, parents_ids: FrozenSet[int]=frozenset(), local_tree: Optional[Any]=None) -> None: t1 = detailed__dict__(level.t1, include_keys=ENUM_INCLUDE_KEYS) t2 = detailed__dict__(level.t2, include_keys=ENUM_INCLUDE_KEYS) @@ -483,9 +484,11 @@ def _diff_enum(self, level, parents_ids=frozenset(), local_tree=None): local_tree=local_tree, ) - def _diff_obj(self, level, parents_ids=frozenset(), is_namedtuple=False, local_tree=None, is_pydantic_object=False): + def _diff_obj(self, level: Any, parents_ids: FrozenSet[int]=frozenset(), is_namedtuple: bool=False, local_tree: Optional[Any]=None, is_pydantic_object: bool=False) -> None: """Difference of 2 objects""" processing_error = False + t1: Optional[Dict[str, Any]] = None + t2: Optional[Dict[str, Any]] = None try: if is_namedtuple: t1 = level.t1._asdict() @@ -504,7 +507,7 @@ def _diff_obj(self, level, parents_ids=frozenset(), is_namedtuple=False, local_t t2 = {k: v for k, v in getmembers(level.t2) if not callable(v)} except AttributeError: processing_error = True - if processing_error is True: + if processing_error is True or t1 is None or t2 is None: self._report_result('unprocessed', level, local_tree=local_tree) return @@ -518,7 +521,7 @@ def _diff_obj(self, level, parents_ids=frozenset(), is_namedtuple=False, local_t local_tree=local_tree, ) - def _skip_this(self, level): + def _skip_this(self, level: Any) -> bool: """ Check whether this comparison should be skipped because one of the objects to compare meets exclusion criteria. :rtype: bool @@ -559,7 +562,7 @@ def _skip_this(self, level): return skip - def _skip_this_key(self, level, key): + def _skip_this_key(self, level: Any, key: Any) -> bool: # if include_paths is not set, than treet every path as included if self.include_paths is None: return False @@ -585,7 +588,7 @@ def _skip_this_key(self, level, key): up = up.up return True - def _get_clean_to_keys_mapping(self, keys, level): + def _get_clean_to_keys_mapping(self, keys: Any, level: Any) -> Dict[Any, Any]: """ Get a dictionary of cleaned value of keys to the keys themselves. This is mainly used to transform the keys when the type changes of keys should be ignored. @@ -607,14 +610,14 @@ def _get_clean_to_keys_mapping(self, keys, level): clean_key = key else: clean_key = self.number_to_string(key, significant_digits=self.significant_digits, - number_format_notation=self.number_format_notation) + number_format_notation=self.number_format_notation) # type: ignore # type: ignore else: type_ = "number" if self.ignore_numeric_type_changes else key.__class__.__name__ if self.significant_digits is None: clean_key = key else: clean_key = self.number_to_string(key, significant_digits=self.significant_digits, - number_format_notation=self.number_format_notation) + number_format_notation=self.number_format_notation) # type: ignore # type: ignore clean_key = KEY_TO_VAL_STR.format(type_, clean_key) else: clean_key = key @@ -630,14 +633,14 @@ def _get_clean_to_keys_mapping(self, keys, level): def _diff_dict( self, - level, - parents_ids=frozenset([]), - print_as_attribute=False, - override=False, - override_t1=None, - override_t2=None, - local_tree=None, - ): + level: Any, + parents_ids: FrozenSet[int]=frozenset([]), + print_as_attribute: bool=False, + override: bool=False, + override_t1: Optional[Any]=None, + override_t2: Optional[Any]=None, + local_tree: Optional[Any]=None, + ) -> None: """Difference of 2 dictionaries""" if override: # for special stuff like custom objects and named tuples we receive preprocessed t1 and t2 @@ -735,7 +738,7 @@ def _diff_dict( ) self._diff(next_level, parents_ids_added, local_tree=local_tree) - def _diff_set(self, level, local_tree=None): + def _diff_set(self, level: Any, local_tree: Optional[Any]=None) -> None: """Difference of sets""" t1_hashtable = self._create_hashtable(level, 't1') t2_hashtable = self._create_hashtable(level, 't2') @@ -766,7 +769,7 @@ def _diff_set(self, level, local_tree=None): self._report_result('set_item_removed', change_level, local_tree=local_tree) @staticmethod - def _iterables_subscriptable(t1, t2): + def _iterables_subscriptable(t1: Any, t2: Any) -> bool: try: if getattr(t1, '__getitem__') and getattr(t2, '__getitem__'): return True @@ -775,7 +778,7 @@ def _iterables_subscriptable(t1, t2): except AttributeError: return False - def _diff_iterable(self, level, parents_ids=frozenset(), _original_type=None, local_tree=None): + def _diff_iterable(self, level: Any, parents_ids: FrozenSet[int]=frozenset(), _original_type: Optional[type]=None, local_tree: Optional[Any]=None) -> None: """Difference of iterables""" if (self.ignore_order_func and self.ignore_order_func(level)) or self.ignore_order: self._diff_iterable_with_deephash(level, parents_ids, _original_type=_original_type, local_tree=local_tree) @@ -919,7 +922,7 @@ def _diff_iterable_in_order(self, level, parents_ids=frozenset(), _original_type local_tree=local_tree, ) - def _all_values_basic_hashable(self, iterable): + def _all_values_basic_hashable(self, iterable: Iterable[Any]) -> bool: """ Are all items basic hashable types? Or there are custom types too? @@ -1540,10 +1543,10 @@ def _diff_numbers(self, level, local_tree=None, report_type_change=True): # For Decimals, format seems to round 2.5 to 2 and 3.5 to 4 (to closest even number) t1_s = self.number_to_string(level.t1, significant_digits=self.significant_digits, - number_format_notation=self.number_format_notation) + number_format_notation=self.number_format_notation) # type: ignore t2_s = self.number_to_string(level.t2, significant_digits=self.significant_digits, - number_format_notation=self.number_format_notation) + number_format_notation=self.number_format_notation) # type: ignore t1_s = KEY_TO_VAL_STR.format(t1_type, t1_s) t2_s = KEY_TO_VAL_STR.format(t2_type, t2_s) diff --git a/deepdiff/distance.py b/deepdiff/distance.py index adaf5045..66812644 100644 --- a/deepdiff/distance.py +++ b/deepdiff/distance.py @@ -15,7 +15,7 @@ class DistanceProtocol(DeepDiffProtocol, Protocol): hashes: dict deephash_parameters: dict iterable_compare_func: Callable | None - math_epsilon: float + math_epsilon: float | None cutoff_distance_for_pairs: float def __get_item_rough_length(self, item, parent:str="root") -> float: diff --git a/deepdiff/helper.py b/deepdiff/helper.py index 0feb8750..ac9a5146 100644 --- a/deepdiff/helper.py +++ b/deepdiff/helper.py @@ -310,7 +310,7 @@ class indexed_set(set): """ -def add_to_frozen_set(parents_ids: FrozenSet[str], item_id: str) -> FrozenSet[str]: +def add_to_frozen_set(parents_ids: FrozenSet[int], item_id: int) -> FrozenSet[int]: return parents_ids | {item_id} From 33de0874bbc356ae83e74157f105a516e4db3d7a Mon Sep 17 00:00:00 2001 From: Sep Dehpour Date: Fri, 8 Aug 2025 11:14:19 -0700 Subject: [PATCH 36/39] adding type hints to search --- deepdiff/search.py | 132 +++++++++++++++++++++++++-------------------- 1 file changed, 73 insertions(+), 59 deletions(-) diff --git a/deepdiff/search.py b/deepdiff/search.py index 007c566c..ba06dcf9 100644 --- a/deepdiff/search.py +++ b/deepdiff/search.py @@ -1,6 +1,7 @@ #!/usr/bin/env python import re from collections.abc import MutableMapping, Iterable +from typing import Any, Dict, FrozenSet, List, Optional, Pattern, Set, Union, TYPE_CHECKING from deepdiff.helper import SetOrdered import logging @@ -8,13 +9,16 @@ strings, numbers, add_to_frozen_set, get_doc, dict_, RE_COMPILED_TYPE, ipranges ) +if TYPE_CHECKING: + from typing_extensions import Self + logger = logging.getLogger(__name__) doc = get_doc('search_doc.rst') -class DeepSearch(dict): +class DeepSearch(Dict[str, Union[Dict[str, Any], SetOrdered, List[str]]]): r""" **DeepSearch** @@ -80,20 +84,20 @@ class DeepSearch(dict): """ - warning_num = 0 + warning_num: int = 0 def __init__(self, - obj, - item, - exclude_paths=SetOrdered(), - exclude_regex_paths=SetOrdered(), - exclude_types=SetOrdered(), - verbose_level=1, - case_sensitive=False, - match_string=False, - use_regexp=False, - strict_checking=True, - **kwargs): + obj: Any, + item: Any, + exclude_paths: Union[SetOrdered, Set[str], List[str]] = SetOrdered(), + exclude_regex_paths: Union[SetOrdered, Set[Union[str, Pattern[str]]], List[Union[str, Pattern[str]]]] = SetOrdered(), + exclude_types: Union[SetOrdered, Set[type], List[type]] = SetOrdered(), + verbose_level: int = 1, + case_sensitive: bool = False, + match_string: bool = False, + use_regexp: bool = False, + strict_checking: bool = True, + **kwargs: Any) -> None: if kwargs: raise ValueError(( "The following parameter(s) are not valid: %s\n" @@ -101,20 +105,24 @@ def __init__(self, "case_sensitive, match_string and verbose_level." ) % ', '.join(kwargs.keys())) - self.obj = obj - self.case_sensitive = case_sensitive if isinstance(item, strings) else True - item = item if self.case_sensitive else item.lower() - self.exclude_paths = SetOrdered(exclude_paths) - self.exclude_regex_paths = [re.compile(exclude_regex_path) for exclude_regex_path in exclude_regex_paths] - self.exclude_types = SetOrdered(exclude_types) - self.exclude_types_tuple = tuple( + self.obj: Any = obj + self.case_sensitive: bool = case_sensitive if isinstance(item, strings) else True + item = item if self.case_sensitive else (item.lower() if isinstance(item, str) else item) + self.exclude_paths: SetOrdered = SetOrdered(exclude_paths) + self.exclude_regex_paths: List[Pattern[str]] = [re.compile(exclude_regex_path) for exclude_regex_path in exclude_regex_paths] + self.exclude_types: SetOrdered = SetOrdered(exclude_types) + self.exclude_types_tuple: tuple[type, ...] = tuple( exclude_types) # we need tuple for checking isinstance - self.verbose_level = verbose_level + self.verbose_level: int = verbose_level self.update( matched_paths=self.__set_or_dict(), matched_values=self.__set_or_dict(), unprocessed=[]) - self.use_regexp = use_regexp + # Type narrowing for mypy/pyright + self.matched_paths: Union[Dict[str, Any], SetOrdered] + self.matched_values: Union[Dict[str, Any], SetOrdered] + self.unprocessed: List[str] + self.use_regexp: bool = use_regexp if not strict_checking and (isinstance(item, numbers) or isinstance(item, ipranges)): item = str(item) if self.use_regexp: @@ -122,10 +130,10 @@ def __init__(self, item = re.compile(item) except TypeError as e: raise TypeError(f"The passed item of {item} is not usable for regex: {e}") from None - self.strict_checking = strict_checking + self.strict_checking: bool = strict_checking # Cases where user wants to match exact string item - self.match_string = match_string + self.match_string: bool = match_string self.__search(obj, item, parents_ids=frozenset({id(obj)})) @@ -134,21 +142,25 @@ def __init__(self, for k in empty_keys: del self[k] - def __set_or_dict(self): + def __set_or_dict(self) -> Union[Dict[str, Any], SetOrdered]: return dict_() if self.verbose_level >= 2 else SetOrdered() - def __report(self, report_key, key, value): + def __report(self, report_key: str, key: str, value: Any) -> None: if self.verbose_level >= 2: - self[report_key][key] = value + report_dict = self[report_key] + if isinstance(report_dict, dict): + report_dict[key] = value else: - self[report_key].add(key) + report_set = self[report_key] + if isinstance(report_set, SetOrdered): + report_set.add(key) def __search_obj(self, - obj, - item, - parent, - parents_ids=frozenset(), - is_namedtuple=False): + obj: Any, + item: Any, + parent: str, + parents_ids: FrozenSet[int] = frozenset(), + is_namedtuple: bool = False) -> None: """Search objects""" found = False if obj == item: @@ -170,14 +182,16 @@ def __search_obj(self, obj = {i: getattr(obj, i) for i in obj.__slots__} except AttributeError: if not found: - self['unprocessed'].append("%s" % parent) + unprocessed = self.get('unprocessed', []) + if isinstance(unprocessed, list): + unprocessed.append("%s" % parent) return self.__search_dict( obj, item, parent, parents_ids, print_as_attribute=True) - def __skip_this(self, item, parent): + def __skip_this(self, item: Any, parent: str) -> bool: skip = False if parent in self.exclude_paths: skip = True @@ -191,11 +205,11 @@ def __skip_this(self, item, parent): return skip def __search_dict(self, - obj, - item, - parent, - parents_ids=frozenset(), - print_as_attribute=False): + obj: Union[Dict[Any, Any], MutableMapping[Any, Any]], + item: Any, + parent: str, + parents_ids: FrozenSet[int] = frozenset(), + print_as_attribute: bool = False) -> None: """Search dictionaries""" if print_as_attribute: parent_text = "%s.%s" @@ -238,10 +252,10 @@ def __search_dict(self, parents_ids=parents_ids_added) def __search_iterable(self, - obj, - item, - parent="root", - parents_ids=frozenset()): + obj: Iterable[Any], + item: Any, + parent: str = "root", + parents_ids: FrozenSet[int] = frozenset()) -> None: """Search iterables except dictionaries, sets and strings.""" for i, thing in enumerate(obj): new_parent = "{}[{}]".format(parent, i) @@ -251,7 +265,7 @@ def __search_iterable(self, if self.case_sensitive or not isinstance(thing, strings): thing_cased = thing else: - thing_cased = thing.lower() + thing_cased = thing.lower() if isinstance(thing, str) else thing if not self.use_regexp and thing_cased == item: self.__report( @@ -264,19 +278,19 @@ def __search_iterable(self, self.__search(thing, item, "%s[%s]" % (parent, i), parents_ids_added) - def __search_str(self, obj, item, parent): + def __search_str(self, obj: Union[str, bytes, memoryview], item: Union[str, bytes, memoryview, Pattern[str]], parent: str) -> None: """Compare strings""" - obj_text = obj if self.case_sensitive else obj.lower() + obj_text = obj if self.case_sensitive else (obj.lower() if isinstance(obj, str) else obj) is_matched = False - if self.use_regexp: - is_matched = item.search(obj_text) - elif (self.match_string and item == obj_text) or (not self.match_string and item in obj_text): + if self.use_regexp and isinstance(item, type(re.compile(''))): + is_matched = bool(item.search(str(obj_text))) + elif (self.match_string and str(item) == str(obj_text)) or (not self.match_string and str(item) in str(obj_text)): is_matched = True if is_matched: self.__report(report_key='matched_values', key=parent, value=obj) - def __search_numbers(self, obj, item, parent): + def __search_numbers(self, obj: Any, item: Any, parent: str) -> None: if ( item == obj or ( not self.strict_checking and ( @@ -288,11 +302,11 @@ def __search_numbers(self, obj, item, parent): ): self.__report(report_key='matched_values', key=parent, value=obj) - def __search_tuple(self, obj, item, parent, parents_ids): + def __search_tuple(self, obj: tuple[Any, ...], item: Any, parent: str, parents_ids: FrozenSet[int]) -> None: # Checking to see if it has _fields. Which probably means it is a named # tuple. try: - obj._asdict + getattr(obj, '_asdict') # It must be a normal tuple except AttributeError: self.__search_iterable(obj, item, parent, parents_ids) @@ -301,7 +315,7 @@ def __search_tuple(self, obj, item, parent, parents_ids): self.__search_obj( obj, item, parent, parents_ids, is_namedtuple=True) - def __search(self, obj, item, parent="root", parents_ids=frozenset()): + def __search(self, obj: Any, item: Any, parent: str = "root", parents_ids: FrozenSet[int] = frozenset()) -> None: """The main search method""" if self.__skip_this(item, parent): return @@ -344,12 +358,12 @@ class grep: __doc__ = doc def __init__(self, - item, - **kwargs): - self.item = item - self.kwargs = kwargs + item: Any, + **kwargs: Any) -> None: + self.item: Any = item + self.kwargs: Dict[str, Any] = kwargs - def __ror__(self, other): + def __ror__(self, other: Any) -> "DeepSearch": return DeepSearch(obj=other, item=self.item, **self.kwargs) From e16507c15c9069e9011ba4e298a2ec031c68cd3f Mon Sep 17 00:00:00 2001 From: Sep Dehpour Date: Fri, 8 Aug 2025 11:23:18 -0700 Subject: [PATCH 37/39] fixing type hints --- deepdiff/search.py | 4 +--- uv.lock | 11 +++++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/deepdiff/search.py b/deepdiff/search.py index ba06dcf9..0cd6b27e 100644 --- a/deepdiff/search.py +++ b/deepdiff/search.py @@ -1,7 +1,7 @@ #!/usr/bin/env python import re from collections.abc import MutableMapping, Iterable -from typing import Any, Dict, FrozenSet, List, Optional, Pattern, Set, Union, TYPE_CHECKING +from typing import Any, Dict, FrozenSet, List, Pattern, Set, Union from deepdiff.helper import SetOrdered import logging @@ -9,8 +9,6 @@ strings, numbers, add_to_frozen_set, get_doc, dict_, RE_COMPILED_TYPE, ipranges ) -if TYPE_CHECKING: - from typing_extensions import Self logger = logging.getLogger(__name__) diff --git a/uv.lock b/uv.lock index 0950548e..adac0389 100644 --- a/uv.lock +++ b/uv.lock @@ -300,6 +300,7 @@ dev = [ { name = "python-dateutil" }, { name = "tomli" }, { name = "tomli-w" }, + { name = "uuid6" }, ] docs = [ { name = "sphinx" }, @@ -350,6 +351,7 @@ requires-dist = [ { name = "sphinxemoji", marker = "extra == 'docs'", specifier = "~=0.3.0" }, { name = "tomli", marker = "extra == 'dev'", specifier = "~=2.2.0" }, { name = "tomli-w", marker = "extra == 'dev'", specifier = "~=1.2.0" }, + { name = "uuid6", marker = "extra == 'dev'", specifier = "==2025.0.1" }, ] provides-extras = ["coverage", "cli", "dev", "docs", "static", "test", "optimize"] @@ -1591,6 +1593,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload-time = "2025-04-10T15:23:37.377Z" }, ] +[[package]] +name = "uuid6" +version = "2025.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/b7/4c0f736ca824b3a25b15e8213d1bcfc15f8ac2ae48d1b445b310892dc4da/uuid6-2025.0.1.tar.gz", hash = "sha256:cd0af94fa428675a44e32c5319ec5a3485225ba2179eefcf4c3f205ae30a81bd", size = 13932, upload-time = "2025-07-04T18:30:35.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/b2/93faaab7962e2aa8d6e174afb6f76be2ca0ce89fde14d3af835acebcaa59/uuid6-2025.0.1-py3-none-any.whl", hash = "sha256:80530ce4d02a93cdf82e7122ca0da3ebbbc269790ec1cb902481fa3e9cc9ff99", size = 6979, upload-time = "2025-07-04T18:30:34.001Z" }, +] + [[package]] name = "virtualenv" version = "20.31.2" From d469a4c34c6b65cab25088b0d3963561b80acf9b Mon Sep 17 00:00:00 2001 From: Sep Dehpour Date: Fri, 8 Aug 2025 11:39:39 -0700 Subject: [PATCH 38/39] making type hints compatible with old python --- deepdiff/distance.py | 12 ++++++------ deepdiff/helper.py | 2 ++ deepdiff/search.py | 4 ++-- tests/test_serialization.py | 6 +++--- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/deepdiff/distance.py b/deepdiff/distance.py index 66812644..af9c54e4 100644 --- a/deepdiff/distance.py +++ b/deepdiff/distance.py @@ -1,11 +1,11 @@ import math import datetime -from typing import TYPE_CHECKING, Callable, Protocol, Any +from typing import TYPE_CHECKING, Callable, Protocol, Any, Union, Optional from deepdiff.deephash import DeepHash from deepdiff.helper import ( DELTA_VIEW, numbers, strings, add_to_frozen_set, not_found, only_numbers, np, np_float64, time_to_seconds, cartesian_product_numpy, np_ndarray, np_array_factory, get_homogeneous_numpy_compatible_type_of_seq, dict_, - CannotCompare) + CannotCompare, NumberType) from collections.abc import Mapping, Iterable if TYPE_CHECKING: @@ -14,8 +14,8 @@ class DistanceProtocol(DeepDiffProtocol, Protocol): hashes: dict deephash_parameters: dict - iterable_compare_func: Callable | None - math_epsilon: float | None + iterable_compare_func: Optional[Callable] + math_epsilon: Optional[float] cutoff_distance_for_pairs: float def __get_item_rough_length(self, item, parent:str="root") -> float: @@ -268,7 +268,7 @@ def numpy_apply_log_keep_sign(array, offset=MATH_LOG_OFFSET): return signed_log_values -def logarithmic_similarity(a: numbers, b: numbers, threshold: float=0.1) -> float: +def logarithmic_similarity(a: NumberType, b: NumberType, threshold: float=0.1) -> float: """ A threshold of 0.1 translates to about 10.5% difference. A threshold of 0.5 translates to about 65% difference. @@ -277,7 +277,7 @@ def logarithmic_similarity(a: numbers, b: numbers, threshold: float=0.1) -> floa return logarithmic_distance(a, b) < threshold -def logarithmic_distance(a: numbers, b: numbers) -> float: +def logarithmic_distance(a: NumberType, b: NumberType) -> float: # Apply logarithm to the absolute values and consider the sign a = float(a) b = float(b) diff --git a/deepdiff/helper.py b/deepdiff/helper.py index ac9a5146..1b01931e 100644 --- a/deepdiff/helper.py +++ b/deepdiff/helper.py @@ -193,6 +193,8 @@ def get_semvar_as_integer(version: str) -> int: uuids: Tuple[Type[uuid.UUID]] = (uuid.UUID, ) times: Tuple[Type[Any], ...] = (datetime.datetime, datetime.time, np_datetime64) numbers: Tuple[Type[Any], ...] = only_numbers + datetimes +# Type alias for use in type annotations +NumberType = Union[int, float, complex, Decimal, datetime.datetime, datetime.date, datetime.timedelta, datetime.time, Any] booleans: Tuple[Type[bool], Type[Any]] = (bool, np_bool_) basic_types: Tuple[Type[Any], ...] = strings + numbers + uuids + booleans + (type(None), ) diff --git a/deepdiff/search.py b/deepdiff/search.py index 0cd6b27e..73f3323a 100644 --- a/deepdiff/search.py +++ b/deepdiff/search.py @@ -1,7 +1,7 @@ #!/usr/bin/env python import re from collections.abc import MutableMapping, Iterable -from typing import Any, Dict, FrozenSet, List, Pattern, Set, Union +from typing import Any, Dict, FrozenSet, List, Pattern, Set, Union, Tuple from deepdiff.helper import SetOrdered import logging @@ -300,7 +300,7 @@ def __search_numbers(self, obj: Any, item: Any, parent: str) -> None: ): self.__report(report_key='matched_values', key=parent, value=obj) - def __search_tuple(self, obj: tuple[Any, ...], item: Any, parent: str, parents_ids: FrozenSet[int]) -> None: + def __search_tuple(self, obj: Tuple[Any, ...], item: Any, parent: str, parents_ids: FrozenSet[int]) -> None: # Checking to see if it has _fields. Which probably means it is a named # tuple. try: diff --git a/tests/test_serialization.py b/tests/test_serialization.py index 5ba8371f..36c928f7 100644 --- a/tests/test_serialization.py +++ b/tests/test_serialization.py @@ -8,7 +8,7 @@ import numpy as np import hashlib import base64 -from typing import NamedTuple, Optional +from typing import NamedTuple, Optional, Union, List, Dict from pickle import UnpicklingError from decimal import Decimal from collections import Counter @@ -31,7 +31,7 @@ class SampleSchema(BaseModel): works: bool = False - ips: list[IPvAnyAddress] + ips: List[IPvAnyAddress] class SomeStats(NamedTuple): @@ -411,7 +411,7 @@ def prefix_callback(**kwargs): result = ddiff.pretty(prefix=prefix_callback) assert result == expected - def sig_to_bytes(inp: dict[str, str | bytes]): + def sig_to_bytes(inp: Dict[str, Union[str, bytes]]): inp['signature'] = inp['signature'].encode('utf-8') return inp From 0978fb88240b0d3daaf327d26b7fbcf85360578c Mon Sep 17 00:00:00 2001 From: Sep Dehpour Date: Fri, 8 Aug 2025 11:56:23 -0700 Subject: [PATCH 39/39] adding docs for 8.6.0 --- CHANGELOG.md | 21 +++++++++++++++-- README.md | 22 ++++++++++++++++++ docs/changelog.rst | 25 ++++++++++++++++++++ docs/diff_doc.rst | 4 ++++ docs/ignore_types_or_values.rst | 41 +++++++++++++++++++++++++++++++++ tests/test_diff_text.py | 2 +- 6 files changed, 112 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c6b532f..3935d83d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,24 @@ # DeepDiff Change log -- [Unreleased] - - Colored View: Output pretty-printed JSON with color-coded differences (added in green, removed in red, changed values show old in red and new in green). +- v8-6-0 + - Added Colored View thanks to @mauvilsa + - Added support for applying deltas to NamedTuple thanks to @paulsc + - Fixed test_delta.py with Python 3.14 thanks to @Romain-Geissler-1A + - Added python property serialization to json + - Added ip address serialization + - Switched to UV from pip + - Added Claude.md + - Added uuid hashing thanks to @akshat62 + - Added `ignore_uuid_types` flag to DeepDiff to avoid type reports when comparing UUID and string. + - Added comprehensive type hints across the codebase (multiple commits for better type safety) + - Added support for memoryview serialization + - Added support for bytes serialization (non-UTF8 compatible) + - Fixed bug where group_by with numbers would leak type info into group path reports + - Fixed bug in `_get_clean_to_keys_mapping without` explicit significant digits + - Added support for python dict key serialization + - Enhanced support for IP address serialization with safe module imports + - Added development tooling improvements (pyright config, .envrc example) + - Updated documentation and development instructions - v8-5-0 - Updating deprecated pydantic calls diff --git a/README.md b/README.md index 9a5ef880..9ef1f798 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,28 @@ Tested on Python 3.9+ and PyPy3. Please check the [ChangeLog](CHANGELOG.md) file for the detailed information. +DeepDiff 8-6-0 + +- Added Colored View thanks to @mauvilsa +- Added support for applying deltas to NamedTuple thanks to @paulsc +- Fixed test_delta.py with Python 3.14 thanks to @Romain-Geissler-1A +- Added python property serialization to json +- Added ip address serialization +- Switched to UV from pip +- Added Claude.md +- Added uuid hashing thanks to @akshat62 +- Added `ignore_uuid_types` flag to DeepDiff to avoid type reports when comparing UUID and string. +- Added comprehensive type hints across the codebase (multiple commits for better type safety) +- Added support for memoryview serialization +- Added support for bytes serialization (non-UTF8 compatible) +- Fixed bug where group_by with numbers would leak type info into group path reports +- Fixed bug in `_get_clean_to_keys_mapping without` explicit significant digits +- Added support for python dict key serialization +- Enhanced support for IP address serialization with safe module imports +- Added development tooling improvements (pyright config, .envrc example) +- Updated documentation and development instructions + + DeepDiff 8-5-0 - Updating deprecated pydantic calls diff --git a/docs/changelog.rst b/docs/changelog.rst index 13ecf231..01973624 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -5,6 +5,31 @@ Changelog DeepDiff Changelog +- v8-6-0 + - Added Colored View thanks to @mauvilsa + - Added support for applying deltas to NamedTuple thanks to @paulsc + - Fixed test_delta.py with Python 3.14 thanks to @Romain-Geissler-1A + - Added python property serialization to json + - Added ip address serialization + - Switched to UV from pip + - Added Claude.md + - Added uuid hashing thanks to @akshat62 + - Added ``ignore_uuid_types`` flag to DeepDiff to avoid type reports + when comparing UUID and string. + - Added comprehensive type hints across the codebase (multiple commits + for better type safety) + - Added support for memoryview serialization + - Added support for bytes serialization (non-UTF8 compatible) + - Fixed bug where group_by with numbers would leak type info into group + path reports + - Fixed bug in ``_get_clean_to_keys_mapping without`` explicit + significant digits + - Added support for python dict key serialization + - Enhanced support for IP address serialization with safe module imports + - Added development tooling improvements (pyright config, .envrc + example) + - Updated documentation and development instructions + - v8-5-0 - Updating deprecated pydantic calls - Switching to pyproject.toml diff --git a/docs/diff_doc.rst b/docs/diff_doc.rst index d3a12da4..50dc5734 100644 --- a/docs/diff_doc.rst +++ b/docs/diff_doc.rst @@ -121,6 +121,10 @@ ignore_type_subclasses: Boolean, default = False ignore_type_subclasses was incorrectly doing the reverse of its job up until DeepDiff 6.7.1 Please make sure to flip it in your use cases, when upgrading from older versions to 7.0.0 or above. +ignore_uuid_types: Boolean, default = False + :ref:`ignore_uuid_types_label` + Whether to ignore UUID vs string type differences when comparing. When set to True, comparing a UUID object with its string representation will not report as a type change. + ignore_string_case: Boolean, default = False :ref:`ignore_string_case_label` Whether to be case-sensitive or not when comparing strings. By settings ignore_string_case=False, strings will be compared case-insensitively. diff --git a/docs/ignore_types_or_values.rst b/docs/ignore_types_or_values.rst index fc191097..c1beb759 100644 --- a/docs/ignore_types_or_values.rst +++ b/docs/ignore_types_or_values.rst @@ -243,6 +243,47 @@ ignore_type_subclasses: Boolean, default = False {'values_changed': {'root.x': {'new_value': 3, 'old_value': 1}}, 'attribute_removed': [root.y]} +.. _ignore_uuid_types_label: + +Ignore UUID Types +------------------ + +ignore_uuid_types: Boolean, default = False + Whether to ignore UUID vs string type differences when comparing. When set to True, comparing a UUID object with its string representation will not report as a type change. + +Without ignore_uuid_types: + >>> import uuid + >>> from deepdiff import DeepDiff + >>> test_uuid = uuid.UUID('12345678-1234-5678-1234-567812345678') + >>> uuid_str = '12345678-1234-5678-1234-567812345678' + >>> DeepDiff(test_uuid, uuid_str) + {'type_changes': {'root': {'old_type': , 'new_type': , 'old_value': UUID('12345678-1234-5678-1234-567812345678'), 'new_value': '12345678-1234-5678-1234-567812345678'}}} + +With ignore_uuid_types=True: + >>> DeepDiff(test_uuid, uuid_str, ignore_uuid_types=True) + {} + +This works in both directions: + >>> DeepDiff(uuid_str, test_uuid, ignore_uuid_types=True) + {} + +The parameter works with nested structures like dictionaries and lists: + >>> dict1 = {'id': test_uuid, 'name': 'test'} + >>> dict2 = {'id': uuid_str, 'name': 'test'} + >>> DeepDiff(dict1, dict2, ignore_uuid_types=True) + {} + +Note that if the UUID and string represent different values, it will still report as a value change: + >>> different_uuid = uuid.UUID('87654321-4321-8765-4321-876543218765') + >>> DeepDiff(different_uuid, uuid_str, ignore_uuid_types=True) + {'values_changed': {'root': {'old_value': UUID('87654321-4321-8765-4321-876543218765'), 'new_value': '12345678-1234-5678-1234-567812345678'}}} + +This parameter can be combined with other ignore flags: + >>> data1 = {'id': test_uuid, 'name': 'TEST', 'count': 42} + >>> data2 = {'id': uuid_str, 'name': 'test', 'count': 42.0} + >>> DeepDiff(data1, data2, ignore_uuid_types=True, ignore_string_case=True, ignore_numeric_type_changes=True) + {} + .. _ignore_string_case_label: diff --git a/tests/test_diff_text.py b/tests/test_diff_text.py index 1c01cbe0..fb0087be 100755 --- a/tests/test_diff_text.py +++ b/tests/test_diff_text.py @@ -9,7 +9,7 @@ from typing import List from decimal import Decimal from deepdiff import DeepDiff -from deepdiff.helper import pypy3, PydanticBaseModel, SetOrdered, np_float64 +from deepdiff.helper import pypy3, PydanticBaseModel, np_float64 from tests import CustomClass