def getplace(id): print('id:',id) result = shareds.driver.session().run(cypher_getplace,id=id).single() print('result:',result) if not result: return dict(status="Error",resultCount=0) p = result.get('p') largerPlaces = result['largerPlaces'] smallerPlaces = result['smallerPlaces'] places1 = [] for h1,largerPlace,id2 in largerPlaces: if largerPlace is None: break name2 = largerPlace['pname'] type2 = largerPlace['type'] place = dict(name=name2,type=type2,id=id2) datetype = h1['datetype'] if datetype: date1 = h1['date1'] date2 = h1['date2'] d = DateRange(datetype, date1, date2) timespan = d.__str__() date1 = DateRange.DateInt(h1['date1']).long_date() date2 = str(DateRange.DateInt(h1['date2'])) place['datetype'] = datetype place['date1'] = date1 place['date2'] = date2 place['timespan'] = timespan places1.append(place) places2 = [] for h2,smallerPlace,id2 in smallerPlaces: if smallerPlace is None: break name2 = smallerPlace['pname'] type2 = smallerPlace['type'] place = dict(name=name2,type=type2,id=id2) datetype = h2['datetype'] if datetype: date1 = h2['date1'] date2 = h2['date2'] d = DateRange(datetype, date1, date2) timespan = d.__str__() date1 = str(DateRange.DateInt(h2['date1'])) date2 = str(DateRange.DateInt(h2['date2'])) place['datetype'] = datetype place['date1'] = date1 place['date2'] = date2 place['timespan'] = timespan places2.append(place) #names = [dict(name=pn['name'],lang=pn['lang']) for pn in result['names']] place = PlaceBl.from_node(p) place.names = [PlaceName.from_node(pn) for pn in result['names']] print(smallerPlaces) if smallerPlaces == [[None,None,None]]: smallerPlaces = [] place.surrounds = [PlaceName.from_node(p2) for (h2,p2,id2) in smallerPlaces] place.surrounds=sorted(places2,key=itemgetter('name')) return {"status":"OK", "statusText":"OK", "resultCount": 1, "place": place, }
def mergeplaces(self, id1, id2): with shareds.driver.session() as session: self.dbdriver.tx = session place, names = self.dbdriver.mergeplaces(id1, id2) # Select default names for default languages def_names = PlaceBl.find_default_names(names, ['fi', 'sv']) # Update default language name links if def_names: self.place_set_default_names(place, def_names) return place
def list_top_level_places(): result = shareds.driver.session().run(cypher_list_top_level_places) places = [] for rec in result: place_node = rec['p'] place = PlaceBl.from_node(place_node) place.names = [PlaceName.from_node(pn) for (r,pn) in rec['names']] places.append(place) return {"status":"OK", "statusText":"OK", "resultCount": len(places), "places":sorted(places, key=attrgetter('pname')) }
def mergeplaces(self, id1, id2): cypher_delete_namelinks = """ match (node) -[r:NAME_LANG]-> (pn) where id(node) = $id delete r """ cypher_mergeplaces = """ match (p1:Place) where id(p1) = $id1 match (p2:Place) where id(p2) = $id2 call apoc.refactor.mergeNodes([p1,p2], {properties:'discard',mergeRels:true}) yield node with node match (node) -[r2:NAME]-> (pn2) return node, collect(pn2) as names """ self.tx.run(cypher_delete_namelinks, id=id1).single() rec = self.tx.run(cypher_mergeplaces, id1=id1, id2=id2).single() node = rec['node'] place = PlaceBl.from_node(node) name_nodes = rec['names'] name_objects = [PlaceName.from_node(n) for n in name_nodes] return place, name_objects
def get_object_from_node(node): ''' Noe4j database returns node objects, which are converted to corresponding objects by this function ''' try: label = list(node.labels)[0] except Exception as e: print("{} Empty node? {}".format(e, node)) return None if label == "Event": return Event_combo.from_node(node) elif label == "Name": return Name.from_node(node) elif label == "Person": return Person_combo.from_node(node) # elif label == "Refname": # return Refname.from_node(node) elif label == "Citation": return Citation.from_node(node) elif label == "Source": return Source.from_node(node) elif label == "Repository": return Repository.from_node(node) elif label == "Place": return PlaceBl.from_node(node) elif label == "Place_name": return PlaceName.from_node(node) elif label == "Note": return Note.from_node(node) elif label == "Family": return Family_combo.from_node(node) elif label == "Media": return Media.from_node(node) else: return None
def get_family_data( uuid, context: UserContext): # -> bl.family.FamilyReader.get_family_data """ Read Family information including Events, Children, Notes and Sources. 1) read (f:Family) --> (e:Event) (f:Family) -[:PARENT]-> (pp:Person) -> (np:Name) (f:Family) -[:CHILD]-> (pc:Person) -> (nc:Name) (f:Family) --> (fn:Note) (e:Event) --> (en:Note) (f:Family) --> (fac:Citation) --> (fas:Source) --> (far:Repository) (e:Event) --> (evc:Citation) --> (evs:Source) --> (evr:Repository) 2) read (pp:Person) --> (ppe:Event) --> (:Place) (pc:Person) --> (pce:Event) --> (:Place) 3) build Family_combo.mother, .names, event_birth, event_death Family_combo.father, .names, event_birth, event_death Family_combo.events Family_combo.notes Family_combo.sources / citation -> source -> repocitory ? Family_combo.children, .names, event_birth, event_death Returns a Family object with other objects included """ # Import here to handle circular dependency from .person_combo import Person_combo def set_birth_death(person, birth_node, death_node): ''' Set person.birth and person.death events from db nodes ''' if birth_node: person.event_birth = Event_combo.from_node(birth_node) if death_node: person.event_death = Event_combo.from_node(death_node) #------------ family = None with shareds.driver.session() as session: try: result = session.run(Cypher_family.get_family_data, pid=uuid) for record in result: if record['f']: # <Node id=272710 labels={'Family'} # properties={'father_sortname': '#Andersson#Anders', # 'change': 1519839324, 'rel_type': 'Married', 'handle': '_dcf94f357ea7b126cd8277f4495', # 'id': 'F0268', 'mother_sortname': 'Gröndahl#Juhantytär#Fredrika', # 'datetype': 3, 'date2': 1878089, 'date1': 1875043}> node = record['f'] family = Family_combo.from_node(node) for event_node, place_node in record['family_event']: if event_node: # event_node: # <Node id=242570 labels={'Event'} # properties={'datetype': 0, 'change': 1528183878, 'description': '', # 'handle': '_dcf94f35ea262b7e1a0a0066d6e', 'id': 'E1692', # 'date2': 1875043, 'type': 'Marriage', 'date1': 1875043}> e = Event_combo.from_node(event_node) if place_node: # place_node: <Node id=73479 labels={'Place'} properties={'coord': # [60.5625, 21.609722222222224], 'id': 'P0468', 'type': 'City', 'uuid': # 'd1d0693de1714a47acf6442d64246a50', 'pname': 'Taivassalo', 'change': # 1556953682}> e.place = PlaceBl.from_node(place_node) # Look for surrounding place: res = session.run(Cypher_person.get_places, uid_list=[e.uniq_id]) for rec in res: e.place.names = place_names_from_nodes( rec['pnames']) if rec['pi']: pl_in = PlaceBl.from_node( rec['pi']) pl_in.names = place_names_from_nodes( rec['pinames']) e.place.uppers.append(pl_in) family.events.append(e) uniq_id = -1 for role, person_node, name_node, birth_node, death_node in record[ 'parent']: # ['mother', # <Node id=235105 labels={'Person'} # properties={'sortname': 'Gröndahl#Juhantytär#Fredrika', 'datetype': 19, # 'confidence': '2.0', 'sex': 2, 'change': 1536161195, # 'handle': '_dcf94f357d9f565664a975f99f', 'id': 'I2475', # 'date2': 1937706, 'date1': 1856517}>, # <Node id=235106 labels={'Name'} # properties={'firstname': 'Fredrika', 'type': 'Married Name', # 'suffix': 'Juhantytär', 'prefix': '', 'surname': 'Gröndahl', 'order': 0}>, # <Node id=242532 labels={'Event'} # properties={'datetype': 0, 'change': 1519151217, 'description': '', # 'handle': '_dcf94f357db6f3c846e6472915f', 'id': 'E1531', # 'date2': 1856517, 'type': 'Birth', 'date1': 1856517}>, # <Node id=242536 labels={'Event'} # properties={'datetype': 0, 'change': 1519150640, 'description': '', # 'handle': '_dcf94f357e2d61f5f76e1ba7cb', 'id': 'E1532', # 'date2': 1937700, 'type': 'Death', 'date1': 1937700}> # ] if person_node: if uniq_id != person_node.id: # Skip person with double default name p = Person_combo.from_node(person_node) p.role = role if name_node: p.names.append( Name.from_node(name_node)) set_birth_death(p, birth_node, death_node) if role == 'father': family.father = p elif role == 'mother': family.mother = p if not context.use_common(): if family.father: family.father.too_new = False if family.mother: family.mother.too_new = False family.no_of_children = 0 family.num_hidden_children = 0 for person_node, name_node, birth_node, death_node in record[ 'child']: # record['child'][0]: # [<Node id=235176 labels={'Person'} # properties={'sortname': '#Andersdotter#Maria Christina', # 'datetype': 19, 'confidence': '2.0', 'sex': 2, 'change': 1532009600, # 'handle': '_dd2a65b2f8c7e05bc664bd49d54', 'id': 'I0781', 'date2': 1877226, 'date1': 1877219}>, # <Node id=235177 labels={'Name'} # properties={'firstname': 'Maria Christina', 'type': 'Birth Name', 'suffix': 'Andersdotter', # 'prefix': '', 'surname': '', 'order': 0}>, # <Node id=242697 labels={'Event'} # properties={'datetype': 0, 'change': 1532009545, 'description': '', 'handle': '_dd2a65b218a14e81692d77955d2', # 'id': 'E1886', 'date2': 1877219, 'type': 'Birth', 'date1': 1877219}>, # <Node id=242702 labels={'Event'} # properties={'datetype': 0, 'change': 1519916327, 'description': '', 'handle': '_dd2a65b218a4e85ab141faeab48', # 'id': 'E1887', 'date2': 1877226, 'type': 'Death', 'date1': 1877226}> # ] if person_node: family.no_of_children += 1 p = Person_combo.from_node(person_node) if name_node: p.names.append(Name.from_node(name_node)) set_birth_death(p, birth_node, death_node) if context.use_common(): if p.too_new: family.num_hidden_children += 1 continue else: p.too_new = False family.children.append(p) #family.no_of_children = len(family.children) for repository_node, source_node, citation_node in record[ 'sources']: # record['sources'][0]: # [<Node id=253027 labels={'Repository'} # properties={'handle': '_dcad22f5914b34fe61c341dad0', 'id': 'R0068', 'rname': 'Taivassalon seurakunnan arkisto', # 'type': 'Archive', 'change': '1546265916'}>, # <Node id=247578 labels={'Source'} # properties={'handle': '_e085cd6d68d256a94afecd2162d', 'id': 'S1418', # 'stitle': 'Taivassalon seurakunnan syntyneiden ja kastettujen luettelot 1790-1850 (I C:4)', # 'change': '1543186596'}>, # <Node id=246371 labels={'Citation'} # properties={'handle': '_dd12b0b88d5741ee11d8bef1ca5', 'id': 'C0854', 'page': 'Vigde år 1831 April 4', # /* dates missing here */, 'change': 1543186596, 'confidence': '2'}> # ] if repository_node: source = Source.from_node(source_node) cita = Citation.from_node(citation_node) repo = Repository.from_node(repository_node) source.repositories.append(repo) source.citations.append(cita) family.sources.append(source) for node in record['note']: note = Note.from_node(node) family.notes.append(note) except Exception as e: print('Error get_family_data: {} {}'.format( e.__class__.__name__, e)) raise return family
def get_one(oid): """ Read a Media object, selected by UUID or uniq_id. Luetaan tallenteen tiedot """ class MediaReferee(): ''' Carrier for a referee of media object. ''' def __init__(self): # Referencing object label, object self.obj = None self.label = None self.crop = None # If the referring object is Event, also list of: # - connected objects self.next_objs = [] if not oid: return None with shareds.driver.session(default_access_mode='READ') as session: # Use UUID record = session.run(Cypher_media.get_by_uuid, rid=oid).single() # RETURN media, # COLLECT(DISTINCT [properties(r), n]) as m_ref, # Referring event or object # COLLECT(DISTINCT [ID(n), m]) AS e_ref # Event Person or Family if not record: return None #Record[0]: the Media object # <Node id=435174 labels={'Media'} # properties={'src': 'Albumi-Silius/kuva002.jpg', 'batch_id': '2020-02-14.001', # 'mime': 'image/jpeg', 'change': 1574187478, 'description': 'kuva002', # 'id': 'O0024', 'uuid': 'fa2e240493434912986c2540b52a9464'}> media = Media.from_node(record['media']) referees = record['m_ref'] referees_next = record['e_ref'] # 1. If referrer is an Event, there is also secundary next objects event_refs = {} for referee_id, node_next in referees_next: #record[2]: Indirectly referring Person and Families # [ [29373, # <Node id=29387 labels=frozenset({'Person'}) # properties={'sortname': 'Silius#Carl Gustaf#', 'death_high': 1911, # 'sex': 1, 'change': 1557753049, 'confidence': '2.0', 'birth_low': 1852, # 'birth_high': 1852, 'id': 'I0036', 'uuid': '9fdcfc81bd17435e8e051325ac3e6eae', # 'death_low': 1911}>], # [29373, <Node id=30773 labels=frozenset({'Person'}) properties={...}>] # ] if not node_next: continue if "Person" in node_next.labels: obj_next = Person.from_node(node_next) obj_next.label = "Person" elif "Family" in node_next.labels: obj_next = FamilyBl.from_node(node_next) obj_next.label = "Family" else: print( f'models.gen.media.Media.get_one: unknown type {list(obj_next.labels)}' ) continue if not referee_id in event_refs.keys(): # A new Event having indirect referees event_refs[referee_id] = [obj_next] else: event_refs[referee_id].append(obj_next) # 2. Gather the directly referring objects media.ref = [] for prop, node in referees: #Record[1]: # [ [{'order': 0}, # <Node id=29373 labels=frozenset({'Event'}) # properties={'datetype': 4, 'change': 1515865582, 'description': '', # 'id': 'E0858', 'date2': 1999040, 'date1': 1928384, # 'type': 'Burial', 'uuid': '934415b2ccf4476fa9d8d9f4d93938b7'}> # ] ] if not node: continue mref = MediaReferee() mref.label, = node.labels # Get the 1st label if mref.label == 'Person': mref.obj = Person.from_node(node) mref.obj.label = "Person" elif mref.label == 'Place': mref.obj = PlaceBl.from_node(node) mref.obj.label = "Place" elif mref.label == 'Event': mref.obj = EventBl.from_node(node) mref.obj.label = "Event" # Has the relation cropping properties? left = prop.get('left') if left != None: upper = prop.get('upper') right = prop.get('right') lower = prop.get('lower') mref.crop = (left, upper, right, lower) # Eventuel next objects for this Event mref.next_objs = event_refs.get(mref.obj.uniq_id, []) # # A list [object label, object, relation properties] # media.ref.append([label,obj,crop]) media.ref.append(mref) return media
def save(self, tx, **kwargs): """ Saves a Place with Place_names, notes and hierarchy links. The 'uniq_id's of already created nodes can be found in 'place_keys' dictionary by 'handle'. Create node for Place self: 1) node exists: update its parameters and link to Batch 2) new node: create node and link to Batch For each 'self.surround_ref' link to upper node: 3) upper node exists: create link to that node 4) new upper node: create and link hierarchy to Place self Place names are always created as new 'Place_name' nodes. - If place has date information, add datetype, date1 and date2 parameters to NAME link - Notes are linked self using 'noteref_hlink's (the Notes have been saved before) Raises an error, if write fails. """ if 'batch_id' in kwargs: batch_id = kwargs['batch_id'] else: raise RuntimeError( f"Place_gramps.save needs a batch_id for {self.id}") # Create or update this Place self.uuid = self.newUuid() pl_attr = {} try: pl_attr = { "uuid": self.uuid, "handle": self.handle, "change": self.change, "id": self.id, "type": self.type, "pname": self.pname } if self.coord: # If no coordinates, don't set coord attribute pl_attr.update({"coord": self.coord.get_coordinates()}) # Create Place self if 'place_keys' in kwargs: # Check if this Place node is already created place_keys = kwargs['place_keys'] plid = place_keys.get(self.handle) else: plid = None if plid: # 1) node has been created but not connected to Batch. # update known Place node parameters and link from Batch self.uniq_id = plid if self.type: #print(f"Pl_save-1 Complete Place ({self.id} #{plid}) {self.handle} {self.pname}") result = tx.run( Cypher_place_in_batch.complete, #TODO batch_id=batch_id, plid=plid, p_attr=pl_attr) else: #print(f"Pl_save-1 NO UPDATE Place ({self.id} #{plid}) attr={pl_attr}") pass else: # 2) new node: create and link from Batch #print(f"Pl_save-2 Create a new Place ({self.id} #{self.uniq_id} {self.pname}) {self.handle}") result = tx.run(Cypher_place_in_batch.create, batch_id=batch_id, p_attr=pl_attr) self.uniq_id = result.single()[0] place_keys[self.handle] = self.uniq_id except Exception as err: print(f"iError Place.create: {err} attr={pl_attr}", file=stderr) raise # Create Place_names try: for name in self.names: n_attr = {"name": name.name, "lang": name.lang} if name.dates: n_attr.update(name.dates.for_db()) result = tx.run(Cypher_place_in_batch.add_name, pid=self.uniq_id, order=name.order, n_attr=n_attr) name.uniq_id = result.single()[0] #print(f"# ({self.uniq_id}:Place)-[:NAME]->({name.uniq_id}:{name})") except Exception as err: print("iError Place.add_name: {err}", file=stderr) raise # Select default names for default languages def_names = PlaceBl.find_default_names(self.names, ['fi', 'sv']) # Update default language name links dbdriver = Neo4jWriteDriver(shareds.driver, tx) db = DBwriter(dbdriver) db.place_set_default_names(self, def_names) # Make hierarchy relations to upper Place nodes for ref in self.surround_ref: try: up_handle = ref['hlink'] #print(f"Pl_save-surrounding {self} -[{ref['dates']}]-> {up_handle}") if 'dates' in ref and isinstance(ref['dates'], DateRange): rel_attr = ref['dates'].for_db() #_r = f"-[{ref['dates']}]->" else: rel_attr = {} #_r = f"-->" # Link to upper node uid = place_keys.get(up_handle) if place_keys else None if uid: # 3) Link to a known upper Place # The upper node is already created: create a link to that # upper Place node #print(f"Pl_save-3 Link ({self.id} #{self.uniq_id}) {_r} (#{uid})") result = tx.run(Cypher_place_in_batch.link_hier, plid=self.uniq_id, up_id=uid, r_attr=rel_attr) else: # 4) Link to unknown place # A new upper node: create a Place with only handle # parameter and link hierarchy to Place self #print(f"Pl_save-4 Link to empty upper Place ({self.id} #{self.uniq_id}) {_r} {up_handle}") result = tx.run(Cypher_place_in_batch.link_create_hier, plid=self.uniq_id, r_attr=rel_attr, up_handle=up_handle) place_keys[up_handle] = result.single()[0] except Exception as err: print( f"iError Place.link_hier: {err} at {self.id} --> {up_handle}", file=stderr) raise try: for note in self.notes: n_attr = { "url": note.url, "type": note.type, "text": note.text } result = tx.run(Cypher_place_in_batch.add_urls, batch_id=batch_id, pid=self.uniq_id, n_attr=n_attr) except Exception as err: traceback.print_exc() print(f"iError Place.add_urls: {err}", file=stderr) raise # Make the place note relations; the Notes have been stored before #TODO: Voi olla useita Noteja samalla handlella! Käytä uniq_id:tä! try: for n_handle in self.noteref_hlink: result = tx.run(Cypher_place_in_batch.link_note, pid=self.uniq_id, hlink=n_handle) except Exception as err: logger.error( f"Place_gramps.save: {err} in linking Notes {self.handle} -> {self.noteref_hlink}" ) #print(f"iError Place.link_notes {self.noteref_hlink}: {err}", file=stderr) raise # Make relations to the Media nodes and their Note and Citation references dbdriver = Neo4jWriteDriver(shareds.driver, tx) db = DBwriter(dbdriver) db.media_save_w_handles(self.uniq_id, self.media_refs) return
def read_object_places(self): ''' Read Place hierarchies for all objects in objs. ''' try: uids = list(self.objs.keys()) results = self.session.run(Cypher_person.get_places, uid_list=uids) for record in results: # <Record label='Event' uniq_id=426916 # pl=<Node id=306042 labels={'Place'} # properties={'id': 'P0456', 'type': 'Parish', 'uuid': '7aeb4e26754d46d0aacfd80910fa1bb1', # 'pname': 'Helsingin seurakunnat', 'change': 1543867969}> # pnames=[ # <Node id=306043 labels={'Place_name'} # properties={'name': 'Helsingin seurakunnat', 'lang': ''}>, # <Node id=306043 labels={'Place_name'} # properties={'name': 'Helsingin seurakunnat', 'lang': ''}> # ] ## ri=<Relationship id=631695 ## nodes=( ## <Node id=306042 labels={'Place'} properties={'id': 'P0456', ...>, ## <Node id=307637 labels={'Place'} ## properties={'coord': [60.16664166666666, 24.94353611111111], ## 'id': 'P0366', 'type': 'City', 'uuid': '93c25330a25f4fa49c1efffd7f4e941b', ## 'pname': 'Helsinki', 'change': 1556954884}> ## ) ## type='IS_INSIDE' properties={}> # pi=<Node id=307637 labels={'Place'} # properties={'coord': [60.16664166666666, 24.94353611111111], 'id': 'P0366', # 'type': 'City', 'uuid': '93c25330a25f4fa49c1efffd7f4e941b', 'pname': 'Helsinki', 'change': 1556954884}> # pinames=[ # <Node id=305800 labels={'Place_name'} properties={'name': 'Helsingfors', 'lang': ''}>, # <Node id=305799 labels={'Place_name'} properties={'name': 'Helsinki', 'lang': 'sv'}> # ]> src_label = record['label'] if src_label != "Event": traceback.print_exc() raise TypeError(f'An Event excepted, got {src_label}') src_uniq_id = record['uniq_id'] src = None # Use the Event from Person events for e in self.person.events: if e.uniq_id == src_uniq_id: src = e break if not src: traceback.print_exc() raise LookupError(f"ERROR: Unknown Event {src_uniq_id}!?") pl = PlaceBl.from_node(record['pl']) if not pl.uniq_id in self.objs.keys(): # A new place self.objs[pl.uniq_id] = pl #print(f"# new place (x:{src_label} {src.uniq_id} {src}) --> (pl:Place {pl.uniq_id} type:{pl.type})") pl.names = place_names_from_nodes(record['pnames']) #else: # print(f"# A known place (x:{src_label} {src.uniq_id} {src}) --> ({list(record['pl'].labels)[0]} {objs[pl.uniq_id]})") src.place_ref.append(pl.uniq_id) # Surrounding places if record['pi']: pl_in = PlaceBl.from_node(record['pi']) ##print(f"# Hierarchy ({pl}) -[:IS_INSIDE]-> (pi:Place {pl_in})") if pl_in.uniq_id in self.objs: pl.uppers.append(self.objs[pl_in.uniq_id]) ##print(f"# - Using a known place {objs[pl_in.uniq_id]}") else: pl.uppers.append(pl_in) self.objs[pl_in.uniq_id] = pl_in pl_in.names = place_names_from_nodes(record['pinames']) #print(f"# ({pl_in} names {pl_in.names})") pass except Exception as e: print( f"Could not read places for person {self.person.id} objects {self.objs}: {e}" ) return