Skip to content

Commit 3f75909

Browse files
SilverRainZDeepSeek V4 Pro
andcommitted
fix: Prevent PendingObject from pickling ObjDomain reference
Storing ObjDomain directly in the PendingObject dataclass causes pickle failures during doctree serialization because the domain contains unpicklable local functions (XRefRole closures in _role_cache). Store the domain name (str) instead of the domain instance, and look up the domain at resolve time via env.get_domain(). This depends on sphinx-notes/render#14 which adds the env parameter to UnresolvedContext.resolve(). Co-Authored-By: DeepSeek V4 Pro <service@deepseek.com>
1 parent ac9aa31 commit 3f75909

2 files changed

Lines changed: 20 additions & 16 deletions

File tree

src/sphinxnotes/any/domain.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -620,29 +620,30 @@ def setup_external_header_anchor(
620620

621621
@dataclass
622622
class PendingObject(UnresolvedContext):
623-
domain: ObjDomain
623+
domain_name: str
624624
objtype: str
625625
objid: str
626626

627627
@override
628-
def resolve(self) -> ResolvedContext:
628+
def resolve(self, env: BuildEnvironment) -> ResolvedContext:
629+
domain: ObjDomain = cast(ObjDomain, env.get_domain(self.domain_name))
629630
objid = self.objid
630-
if (self.objtype, objid) not in self.domain.objects:
631+
if (self.objtype, objid) not in domain.objects:
631632
objids = set()
632-
for objtype, objfield, objref in self.domain.references:
633+
for objtype, objfield, objref in domain.references:
633634
if objtype == self.objtype and objref == objid:
634-
objids.update(self.domain.references[objtype, objfield, objref])
635+
objids.update(domain.references[objtype, objfield, objref])
635636

636637
if len(objids) >= 1:
637638
objid = objids.pop()
638639
else:
639640
raise KeyError(f'Object not found: {(self.objtype, objid)}')
640641

641-
_, _, obj = self.domain.objects[self.objtype, objid]
642+
_, _, obj = domain.objects[self.objtype, objid]
642643
return obj
643644

644645
def __hash__(self) -> int:
645-
return hash((self.domain.name, self.objtype, self.objid))
646+
return hash((self.domain_name, self.objtype, self.objid))
646647

647648

648649
class ObjEmbedDirective(BaseContextDirective):
@@ -671,7 +672,7 @@ def get_rendered_objects(self) -> list[nodes.Node]:
671672
def current_context(self) -> UnresolvedContext | ResolvedContext:
672673
domain, objtype = self.get_domain_and_type()
673674
objid = self.arguments[0]
674-
return PendingObject(domain, objtype, objid)
675+
return PendingObject(domain.name, objtype, objid)
675676

676677
@override
677678
def current_template(self) -> Template:

tests/test_domain.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,19 @@
99

1010
class TestPendingObjectResolve(unittest.TestCase):
1111
def setUp(self):
12+
self.env = MagicMock()
1213
self.domain = ObjDomain(MagicMock())
14+
self.domain.name = 'obj'
1315
self.domain.data['objects'] = {}
1416
self.domain.data['references'] = {}
17+
self.env.get_domain.return_value = self.domain
1518

1619
def test_resolve_by_id(self):
1720
obj = MagicMock()
1821
self.domain.objects['foo', 'id1'] = ('doc1', 'anchor1', obj)
1922

20-
pending = PendingObject(domain=self.domain, objtype='foo', objid='id1')
21-
result = pending.resolve()
23+
pending = PendingObject(domain_name='obj', objtype='foo', objid='id1')
24+
result = pending.resolve(self.env)
2225

2326
self.assertIs(result, obj)
2427

@@ -27,19 +30,19 @@ def test_resolve_by_reference(self):
2730
self.domain.objects['foo', 'id1'] = ('doc1', 'anchor1', obj)
2831
self.domain.references['foo', 'field1', 'ref1'] = {'id1'}
2932

30-
pending = PendingObject(domain=self.domain, objtype='foo', objid='ref1')
31-
result = pending.resolve()
33+
pending = PendingObject(domain_name='obj', objtype='foo', objid='ref1')
34+
result = pending.resolve(self.env)
3235

3336
self.assertIs(result, obj)
3437

3538
def test_resolve_not_found(self):
3639
self.domain.objects['foo', 'id1'] = ('doc1', 'anchor1', MagicMock())
3740
self.domain.references['foo', 'field1', 'ref1'] = {'id1'}
3841

39-
pending = PendingObject(domain=self.domain, objtype='foo', objid='nonexistent')
42+
pending = PendingObject(domain_name='obj', objtype='foo', objid='nonexistent')
4043

4144
with self.assertRaises(KeyError):
42-
pending.resolve()
45+
pending.resolve(self.env)
4346

4447
def test_resolve_multiple_references(self):
4548
obj1 = MagicMock()
@@ -48,8 +51,8 @@ def test_resolve_multiple_references(self):
4851
self.domain.objects['foo', 'id2'] = ('doc2', 'anchor2', obj2)
4952
self.domain.references['foo', 'field1', 'ref1'] = {'id1', 'id2'}
5053

51-
pending = PendingObject(domain=self.domain, objtype='foo', objid='ref1')
52-
result = pending.resolve()
54+
pending = PendingObject(domain_name='obj', objtype='foo', objid='ref1')
55+
result = pending.resolve(self.env)
5356

5457
self.assertIn(result, [obj1, obj2])
5558

0 commit comments

Comments
 (0)