1414
1515from gel ._internal import _typing_parametric as parametric
1616from gel ._internal ._qbmodel ._abstract ._base import AbstractGelLinkModel
17+ from gel ._internal ._qbmodel import _abstract
1718from gel ._internal ._tracked_list import (
1819 AbstractCollection ,
1920 DefaultList ,
@@ -74,10 +75,32 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
7475 self ._index_snapshot = None
7576 super ().__init__ (* args , ** kwargs )
7677
78+ def __copy__ (self ) -> Self :
79+ obj = type (self ).__new__ (type (self ))
80+
81+ obj .__gel_overwrite_data__ = self .__gel_overwrite_data__
82+ obj ._mode = self ._mode
83+ obj ._items = list (self ._items )
84+ obj ._index_snapshot = (
85+ dict (self ._index_snapshot ) if self ._index_snapshot else None
86+ )
87+ obj ._tracking_set = (
88+ dict (self ._tracking_set ) if self ._tracking_set else None
89+ )
90+ obj ._tracking_index = (
91+ dict (self ._tracking_index ) if self ._tracking_index else None
92+ )
93+ return obj
94+
95+ def __deepcopy__ (self , memo : dict [int , Any ]) -> Self :
96+ return self .__copy__ ()
97+
7798 def __gel_reconcile__ (
7899 self ,
79100 updated : AbstractLinkSet [_MT_co ],
80- existing_objects : dict [uuid .UUID , AbstractGelModel ],
101+ existing_objects : dict [
102+ uuid .UUID , AbstractGelModel | Iterable [AbstractGelModel ]
103+ ],
81104 new_objects : dict [uuid .UUID , AbstractGelModel ],
82105 ) -> Self :
83106 raise NotImplementedError
@@ -117,10 +140,10 @@ def _is_tracked(self, item: _MT_co) -> bool: # type: ignore [misc]
117140 def _reconcile (
118141 self ,
119142 updated : AbstractLinkSet [_MT_co ],
120- existing_objects : dict [uuid .UUID , AbstractGelModel ],
143+ existing_objects : dict [
144+ uuid .UUID , AbstractGelModel | Iterable [AbstractGelModel ]
145+ ],
121146 new_objects : dict [uuid .UUID , AbstractGelModel ],
122- * ,
123- is_computed : bool ,
124147 ) -> list [_MT_co ]:
125148 raise NotImplementedError
126149
@@ -250,6 +273,12 @@ def __repr__(self) -> str:
250273 else :
251274 return repr (self ._items )
252275
276+ def __gel_replace_with_empty__ (self ) -> None :
277+ self ._items .clear ()
278+ self ._index_snapshot = None
279+ self ._tracking_set = None
280+ self ._tracking_index = None
281+
253282
254283class ComputedLinkSet (
255284 parametric .SingleParametricType [_MT_co ],
@@ -266,10 +295,10 @@ def _pyid(item: _MT_co) -> int: # type: ignore [misc]
266295 def _reconcile (
267296 self ,
268297 updated : AbstractLinkSet [_MT_co ],
269- existing_objects : dict [uuid .UUID , AbstractGelModel ],
298+ existing_objects : dict [
299+ uuid .UUID , AbstractGelModel | Iterable [AbstractGelModel ]
300+ ],
270301 new_objects : dict [uuid .UUID , AbstractGelModel ],
271- * ,
272- is_computed : bool ,
273302 ) -> list [_MT_co ]:
274303 # This method is called by sync() when it refetches the link.
275304 #
@@ -319,30 +348,29 @@ def _reconcile(
319348
320349 updated_items : list [_MT_co ] = []
321350 for obj in updated :
322- obj_id : uuid .UUID = obj .id # type: ignore [attr-defined]
323-
324- try :
325- # This works because `GelModel` hashes and compares by `.id`
326- existing = existing_set [obj ]
327- except KeyError :
328- if is_computed and obj_id in existing_objects :
329- updated_items .append (existing_objects [obj_id ]) # type: ignore [arg-type]
330- else :
331- updated_items .append (new_objects [obj_id ]) # type: ignore [arg-type]
332- else :
333- updated_items .append (existing )
351+ # This works because `GelModel` hashes and compares by `.id`
352+ existing : _MT_co | None = existing_set .get (obj )
353+
354+ updated_items .append (
355+ _abstract .reconcile_link ( # type: ignore [misc]
356+ existing = existing ,
357+ refetched = obj ,
358+ existing_objects = existing_objects ,
359+ new_objects = new_objects ,
360+ )
361+ )
334362
335363 return updated_items
336364
337365 def __gel_reconcile__ ( # pyright: ignore [reportIncompatibleMethodOverride]
338366 self ,
339367 updated : AbstractLinkSet [_MT_co ],
340- existing_objects : dict [uuid .UUID , AbstractGelModel ],
368+ existing_objects : dict [
369+ uuid .UUID , AbstractGelModel | Iterable [AbstractGelModel ]
370+ ],
341371 new_objects : dict [uuid .UUID , AbstractGelModel ],
342372 ) -> Self :
343- new_items = self ._reconcile (
344- updated , existing_objects , new_objects , is_computed = True
345- )
373+ new_items = self ._reconcile (updated , existing_objects , new_objects )
346374 return type (self )(
347375 new_items ,
348376 __mode__ = self ._mode ,
@@ -415,10 +443,10 @@ def __gel_basetype_iter__(self) -> Iterator[_BMT_co]: # type: ignore [override]
415443 def _reconcile ( # pyright: ignore [reportIncompatibleMethodOverride]
416444 self ,
417445 updated : LinkWithPropsSet [_PT_co , _BMT_co ], # type: ignore [override]
418- existing_objects : dict [uuid .UUID , AbstractGelModel ],
446+ existing_objects : dict [
447+ uuid .UUID , AbstractGelModel | Iterable [AbstractGelModel ]
448+ ],
419449 new_objects : dict [uuid .UUID , AbstractGelModel ],
420- * ,
421- is_computed : bool ,
422450 ) -> list [_PT_co ]:
423451 # See comments in `LinkSet._reconcile()` for most implementation
424452 # details.
@@ -432,36 +460,29 @@ def _reconcile( # pyright: ignore [reportIncompatibleMethodOverride]
432460
433461 updated_items : list [_PT_co ] = []
434462 for obj in updated :
435- obj_id : uuid .UUID = obj .id # type: ignore [attr-defined]
436-
437- try :
438- existing = existing_set [obj ]
439- except KeyError :
440- if is_computed and obj_id in existing_objects :
441- new = existing_objects [obj_id ]
442- else :
443- new = new_objects [obj_id ]
444- # `obj` will have updated __linkprops__ but the wrapped
445- # model will be coming from new_objects
446- obj .__gel_replace_wrapped_model__ (new )
447- updated_items .append (obj )
448- else :
449- # updated will have newly refetched __linkprops__, so
450- # copy them
451- existing .__gel_replace_linkprops__ (obj .__linkprops__ )
452- updated_items .append (existing )
463+ # This works because `GelModel` hashes and compares by `.id`
464+ existing = existing_set .get (obj )
465+
466+ updated_items .append (
467+ _abstract .reconcile_proxy_link (
468+ existing = existing ,
469+ refetched = obj ,
470+ existing_objects = existing_objects ,
471+ new_objects = new_objects ,
472+ ) # type: ignore [arg-type]
473+ )
453474
454475 return updated_items
455476
456477 def __gel_reconcile__ ( # pyright: ignore [reportIncompatibleMethodOverride]
457478 self ,
458479 updated : LinkWithPropsSet [_PT_co , _BMT_co ], # type: ignore [override]
459- existing_objects : dict [uuid .UUID , AbstractGelModel ],
480+ existing_objects : dict [
481+ uuid .UUID , AbstractGelModel | Iterable [AbstractGelModel ]
482+ ],
460483 new_objects : dict [uuid .UUID , AbstractGelModel ],
461484 ) -> Self :
462- new_items = self ._reconcile (
463- updated , existing_objects , new_objects , is_computed = True
464- )
485+ new_items = self ._reconcile (updated , existing_objects , new_objects )
465486 return type (self )(
466487 new_items ,
467488 __mode__ = self ._mode ,
@@ -562,12 +583,12 @@ class AbstractMutableLinkSet(
562583 def __gel_reconcile__ ( # pyright: ignore [reportIncompatibleMethodOverride]
563584 self ,
564585 updated : AbstractLinkSet [_MT_co ],
565- existing_objects : dict [uuid .UUID , AbstractGelModel ],
586+ existing_objects : dict [
587+ uuid .UUID , AbstractGelModel | Iterable [AbstractGelModel ]
588+ ],
566589 new_objects : dict [uuid .UUID , AbstractGelModel ],
567590 ) -> Self :
568- self ._items = self ._reconcile (
569- updated , existing_objects , new_objects , is_computed = False
570- )
591+ self ._items = self ._reconcile (updated , existing_objects , new_objects )
571592 # Reset tracking: it will likely be not needed. Typically there
572593 # should be just one `sync()` call with no modifications of anything
573594 # after it.
@@ -595,12 +616,6 @@ def _ensure_snapshot(self) -> None:
595616 assert self ._tracking_index is not None
596617 self ._index_snapshot = dict (self ._tracking_index )
597618
598- def __gel_replace_with_empty__ (self ) -> None :
599- self ._items .clear ()
600- self ._index_snapshot = None
601- self ._tracking_set = None
602- self ._tracking_index = None
603-
604619 def __gel_get_added__ (self ) -> list [_MT_co ]:
605620 match bool (self ._index_snapshot ), bool (self ._tracking_index ):
606621 case True , False :
0 commit comments