@@ -387,3 +387,106 @@ def test_prefix_cache_rebound_avoids_redundant_retrieval(self):
387387
388388 calls = sorted (c .args [0 ] for c in retrieve .call_args_list )
389389 assert calls == ["x://later" , "x://one" , "x://primer" ]
390+
391+
392+ class TestSchemaAccessorIdentity :
393+ """Locks in the per-resource-handle identity model.
394+
395+ SchemaAccessor identity is the accessor instance itself (with
396+ discrimination on node, base_uri, and `_path_resolver` instance),
397+ not a value tuple of its inputs. This forces the recommended
398+ lifecycle: construct one SchemaAccessor per schema document and
399+ reuse it across all derived SchemaPaths.
400+ """
401+
402+ def test_same_instance_compares_equal_and_hashes_equal (self ):
403+ accessor = SchemaAccessor .from_schema ({"a" : 1 })
404+
405+ assert accessor == accessor
406+ assert hash (accessor ) == hash (accessor )
407+
408+ def test_accessor_is_hashable (self ):
409+ accessor = SchemaAccessor .from_schema ({"a" : 1 })
410+
411+ # Would raise TypeError before this PR (defining __eq__
412+ # without __hash__ silently makes instances unhashable).
413+ assert hash (accessor ) == hash (accessor )
414+ {accessor } # constructable as a set element
415+
416+ def test_distinct_from_schema_calls_not_equal (self ):
417+ # Each from_schema() call builds its own _path_resolver, so
418+ # the resulting accessors are distinct resource handles even
419+ # with identical arguments. This is the "reuse the accessor"
420+ # assertion: callers must hold onto the accessor instance,
421+ # not reconstruct it on demand.
422+ doc = {"a" : 1 }
423+
424+ acc1 = SchemaAccessor .from_schema (doc )
425+ acc2 = SchemaAccessor .from_schema (doc )
426+
427+ assert acc1 != acc2
428+ # Hashes are allowed to collide but are very unlikely to here.
429+
430+ def test_distinct_dicts_not_equal (self ):
431+ # Inherited from LookupAccessor: value-equal but distinct dicts
432+ # are distinct resources. Included for clarity.
433+ acc1 = SchemaAccessor .from_schema ({"a" : 1 })
434+ acc2 = SchemaAccessor .from_schema ({"a" : 1 })
435+
436+ assert acc1 != acc2
437+
438+ def test_different_base_uri_not_equal (self ):
439+ # Same schema dict by reference, different base_uri → different
440+ # resources, because $ref resolution differs.
441+ doc = {"a" : 1 }
442+
443+ acc1 = SchemaAccessor .from_schema (doc , base_uri = "https://a/" )
444+ acc2 = SchemaAccessor .from_schema (doc , base_uri = "https://b/" )
445+
446+ assert acc1 != acc2
447+
448+ def test_path_equality_follows_accessor_equality (self ):
449+ from jsonschema_path import SchemaPath
450+
451+ accessor = SchemaAccessor .from_schema ({"a" : {"b" : 1 }})
452+
453+ p1 = SchemaPath (accessor ) / "a"
454+ p2 = SchemaPath (accessor ) / "a"
455+
456+ # Same accessor instance + same parts → equal paths and
457+ # equal hashes (delegated to pathable's AccessorPath identity).
458+ assert p1 == p2
459+ assert hash (p1 ) == hash (p2 )
460+
461+ def test_path_inequality_across_distinct_accessors (self ):
462+ from jsonschema_path import SchemaPath
463+
464+ doc = {"a" : {"b" : 1 }}
465+ acc1 = SchemaAccessor .from_schema (doc )
466+ acc2 = SchemaAccessor .from_schema (doc )
467+
468+ p1 = SchemaPath (acc1 ) / "a"
469+ p2 = SchemaPath (acc2 ) / "a"
470+
471+ # Distinct accessor instances → distinct resources → unequal
472+ # paths even though parts and underlying dict reference match.
473+ assert p1 != p2
474+
475+ def test_resolved_cache_shared_when_accessor_reused (self ):
476+ # Two paths over the same accessor hit the same resolved cache.
477+ # If a future refactor reintroduces per-path caching, this test
478+ # fails because the second .get_resolved would return a fresh
479+ # object instead of the cached one.
480+ from jsonschema_path import SchemaPath
481+
482+ accessor = SchemaAccessor .from_schema (
483+ {"a" : {"b" : 1 }},
484+ resolved_cache_maxsize = 8 ,
485+ )
486+
487+ p1 = SchemaPath (accessor ) / "a" / "b"
488+ p2 = SchemaPath (accessor ) / "a" / "b"
489+
490+ with p1 .resolve () as r1 :
491+ with p2 .resolve () as r2 :
492+ assert r1 is r2
0 commit comments