33import logging
44import pendulum
55import datetime
6- from functools import reduce
76
87from django .apps import apps
98from django .core .exceptions import FieldDoesNotExist
109
1110from share .disambiguation import GraphDisambiguator
12- from share .util import TopologicalSorter
11+ from share .util import TopographicalSorter
1312from share .util import IDObfuscator
1413
1514
@@ -85,7 +84,7 @@ def __init__(self, data, namespace=None):
8584 self .nodes .append (self ._lookup [id , type ])
8685
8786 for k , v in tuple (blob .items ()):
88- if isinstance (v , dict ) and k not in { 'extra' , 'same_as' } and not k .startswith ('@' ):
87+ if isinstance (v , dict ) and k != 'extra' and not k .startswith ('@' ):
8988 related = (v .pop ('@id' ), v .pop ('@type' ).lower ())
9089 hints [(id , type ), related ] = k
9190 relations .add (((id , type ), related ))
@@ -105,17 +104,17 @@ def __init__(self, data, namespace=None):
105104 self .relations [self ._lookup [subject ]].add (edge )
106105 self .relations [self ._lookup [related ]].add (edge )
107106
108- self .sort_nodes ()
107+ self .nodes = TopographicalSorter ( self . nodes , dependencies = lambda n : tuple ( e . related for e in n . related ( backward = False ))). sorted ()
109108
110109 def prune (self ):
111110 gd = GraphDisambiguator ()
112111 gd .prune (self )
113- self .sort_nodes ()
112+ self .nodes = TopographicalSorter ( self . nodes , dependencies = lambda n : tuple ( e . related for e in n . related ( backward = False ))). sorted ()
114113
115114 def disambiguate (self ):
116115 gd = GraphDisambiguator ()
117116 gd .find_instances (self )
118- self .sort_nodes ()
117+ self .nodes = TopographicalSorter ( self . nodes , dependencies = lambda n : tuple ( e . related for e in n . related ( backward = False ))). sorted ()
119118
120119 def normalize (self ):
121120 # Freeze nodes to avoid oddities with inserting and removing nodes
@@ -141,7 +140,7 @@ def process(self, normalize=True, prune=True, disambiguate=True):
141140 if disambiguate :
142141 gd .find_instances (self )
143142
144- self .sort_nodes ()
143+ self .nodes = TopographicalSorter ( self . nodes , dependencies = lambda n : tuple ( e . related for e in n . related ( backward = False ))). sorted ()
145144
146145 def get (self , id , type ):
147146 return self ._lookup [(id , type )]
@@ -203,26 +202,19 @@ def serialize(self):
203202 for n in sorted (self .nodes , key = lambda x : x .type + str (x .id ))
204203 ]
205204
206- def sort_nodes (self ):
207- # Put merge nodes first, then everything else sorted topologically
208- nonmerges , merges = reduce (lambda lists , n : lists [n .is_merge ].append (n ) or lists , self .nodes , ([], []))
209- self .nodes = merges + TopologicalSorter (nonmerges , dependencies = lambda n : tuple (e .related for e in n .related (backward = False ))).sorted ()
210-
211205
212206class ChangeNode :
213207
214208 @property
215209 def id (self ):
216- instance = self .get_instance ()
217- return IDObfuscator .encode (instance ) if instance else self ._id
210+ return (self .instance and IDObfuscator .encode (self .instance )) or self ._id
218211
219212 @property
220213 def type (self ):
221214 model = apps .get_app_config ('share' ).get_model (self ._type )
222- instance = self .get_instance ()
223- if not instance or len (model .mro ()) >= len (type (instance ).mro ()):
215+ if not self .instance or len (model .mro ()) >= len (type (self .instance ).mro ()):
224216 return self ._type
225- return instance ._meta .model_name .lower ()
217+ return self . instance ._meta .model_name .lower ()
226218
227219 @property
228220 def ref (self ):
@@ -231,43 +223,41 @@ def ref(self):
231223 @property
232224 def model (self ):
233225 model = apps .get_model ('share' , self ._type )
234- instance = self .get_instance ()
235- if not instance or len (model .mro ()) >= len (type (instance ).mro ()):
226+ if not self .instance or len (model .mro ()) >= len (type (self .instance ).mro ()):
236227 return model
237228 # Special case to allow creators to be downgraded to contributors
238229 # This allows OSF users to mark project contributors as bibiliographic or non-bibiliographic
239230 # and have that be reflected in SHARE
240231 if issubclass (model , apps .get_model ('share' , 'contributor' )):
241232 return model
242- return type (instance )
233+ return type (self . instance )
243234
244235 @property
245236 def is_merge (self ):
246- return 'same_as' in self . attrs
237+ return False # TODO
247238
248239 @property
249240 def is_blank (self ):
250241 return self .id .startswith ('_:' )
251242
252243 @property
253244 def is_skippable (self ):
254- return self .instances and not self .change
245+ return self .is_merge or ( self . instance and not self .change )
255246
256247 @property
257248 def change (self ):
258249 changes , relations = {}, {}
259- instance = self .get_instance ()
260250
261251 extra = copy .deepcopy (self .extra )
262252 if self .namespace :
263- if self .namespace and getattr (instance , 'extra' , None ):
253+ if self .namespace and getattr (self . instance , 'extra' , None ):
264254 # NOTE extra changes are only diffed at the top level
265- instance .extra .data .setdefault (self .namespace , {})
255+ self . instance .extra .data .setdefault (self .namespace , {})
266256 changes ['extra' ] = {
267257 k : v
268258 for k , v in extra .items ()
269- if k not in instance .extra .data [self .namespace ]
270- or instance .extra .data [self .namespace ][k ] != v
259+ if k not in self . instance .extra .data [self .namespace ]
260+ or self . instance .extra .data [self .namespace ][k ] != v
271261 }
272262 else :
273263 changes ['extra' ] = extra
@@ -283,21 +273,21 @@ def change(self):
283273 if self .is_blank :
284274 return {** changes , ** self .attrs , ** relations }
285275
286- if instance and type (instance ) is not self .model :
276+ if self . instance and type (self . instance ) is not self .model :
287277 changes ['type' ] = self .model ._meta .label_lower
288278
289279 # Hacky fix for SHARE-604
290280 # If the given date_updated is older than the current one, don't accept any changes that would overwrite newer changes
291281 ignore_attrs = set ()
292- if issubclass (self .model , apps .get_model ('share' , 'creativework' )) and 'date_updated' in self .attrs and instance .date_updated :
282+ if issubclass (self .model , apps .get_model ('share' , 'creativework' )) and 'date_updated' in self .attrs and self . instance .date_updated :
293283 date_updated = pendulum .parse (self .attrs ['date_updated' ])
294- if date_updated < instance .date_updated :
295- logger .warning ('%s appears to be from the past, change date_updated (%s) is older than the current (%s). Ignoring conflicting changes.' , self , self .attrs ['date_updated' ], instance .date_updated )
284+ if date_updated < self . instance .date_updated :
285+ logger .warning ('%s appears to be from the past, change date_updated (%s) is older than the current (%s). Ignoring conflicting changes.' , self , self .attrs ['date_updated' ], self . instance .date_updated )
296286 # Just in case
297- ignore_attrs .update (instance .change .change .keys ())
287+ ignore_attrs .update (self . instance .change .change .keys ())
298288
299289 # Go back until we find a change that is older than us
300- for version in instance .versions .select_related ('change' ).all ():
290+ for version in self . instance .versions .select_related ('change' ).all ():
301291 if not version .date_updated or date_updated > version .date_updated :
302292 break
303293 ignore_attrs .update (version .change .change .keys ())
@@ -307,7 +297,7 @@ def change(self):
307297 if k in ignore_attrs :
308298 logger .debug ('Ignoring potentially conflicting change to "%s"' , k )
309299 continue
310- old_value = getattr (instance , k )
300+ old_value = getattr (self . instance , k )
311301 if isinstance (old_value , datetime .datetime ):
312302 v = pendulum .parse (v )
313303 if v != old_value :
@@ -320,25 +310,16 @@ def __init__(self, graph, id, type, attrs, namespace=None):
320310 self .graph = graph
321311 self ._id = id
322312 self ._type = type .lower ()
323- self .instances = set ()
313+ self .instance = None
324314 self .attrs = attrs
325315 self .extra = attrs .pop ('extra' , {})
326316 self .context = attrs .pop ('@context' , {})
327317 self .namespace = namespace
328318
329319 if not self .is_blank :
330- instance = IDObfuscator .load (self .id , None )
331- if not instance or instance ._meta .concrete_model is not self .model ._meta .concrete_model :
320+ self . instance = IDObfuscator .load (self .id , None )
321+ if not self . instance or self . instance ._meta .concrete_model is not self .model ._meta .concrete_model :
332322 raise UnresolvableReference ((self .id , self .type ))
333- self .instances .add (instance )
334-
335- def get_instance (self ):
336- try :
337- return max (self .instances , key = lambda i : i .date_modified , default = None )
338- except AttributeError :
339- if len (self .instances ) == 1 :
340- return next (i for i in self .instances )
341- raise
342323
343324 def related (self , name = None , forward = True , backward = True ):
344325 edges = tuple (
@@ -365,13 +346,12 @@ def resolve_attrs(self):
365346 name = edge .name if edge .subject == self else edge .remote_name
366347 node = edge .related if edge .subject == self else edge .subject
367348 many = edge .field .one_to_many if edge .subject == self else edge .field .remote_field .one_to_many
368- instance = node .get_instance ()
369- if not instance :
349+ if not node .instance :
370350 continue
371351 if many :
372- relations .setdefault (name , []).append (instance .pk )
352+ relations .setdefault (name , []).append (node . instance .pk )
373353 else :
374- relations [name ] = instance .pk
354+ relations [name ] = node . instance .pk
375355
376356 return {** self .attrs , ** relations }
377357
0 commit comments