def _static_place_instances(self): ''' Create static instances for every place mentioned in the unique_locations service data. ''' places = self.helper.services.get('unique_locations', {}).get('places', {}) instances = {} for name, data in places.items(): place_data = None place = None components = [] for k in ('Sovereign', 'Country', 'Province', 'State', 'County', 'City'): component_name = data.get(k.lower()) if component_name: components = [component_name] + components rev = components rev.reverse() place_data = { 'name': component_name, 'type': k, 'part_of': place_data, 'uri': self.helper.make_shared_uri('PLACE', *rev) } parent = place place_data = self.helper.make_place(place_data) place = get_crom_object(place_data) instances[', '.join(components)] = place if place: instances[name] = place return instances
def _populate_object_prev_post_sales(self, data: dict, this_key, post_sale_map): hmo = get_crom_object(data) post_sales = data.get('post_sale', []) prev_sales = data.get('prev_sale', []) prev_post_sales_records = [(post_sales, False), (prev_sales, True)] for sales_data, rev in prev_post_sales_records: for sale_record in sales_data: pcno = sale_record.get('cat') plno = sale_record.get('lot') # plot = self.helper.shared_lot_number_from_lno(plno) pdate = implode_date(sale_record, '') if pcno and plno and pdate: if pcno == 'NA': desc = f'Also sold in an unidentified sale: {plno} ({pdate})' note = vocab.Note(ident='', content=desc) hmo.referred_to_by = note elif 'or' in plno.lower(): desc = f'Also sold in an uncertain lot: {pcno} {plno} ({pdate})' note = vocab.Note(ident='', content=desc) hmo.referred_to_by = note else: that_key = (pcno, plno, pdate) if rev: # `that_key` is for a previous sale for this object post_sale_map[this_key] = that_key else: # `that_key` is for a later sale for this object post_sale_map[that_key] = this_key
def add_imprint_orgs(data): ''' Given a `dict` representing an "article," extract the "imprint organization" records and their role (e.g. publisher, distributor), and add add a new 'organizations' key to the dictionary containing an array of `dict`s representing the organizations. Also construct an Activity for each organization's role, and associate it with the article and organization (article --role--> organization). The resulting organization `dict` will contain these keys: * `_aata_record_id`: The identifier of the corresponding article * `_aata_record_organization_seq`: A integer identifying this organization (unique within the scope of the article) * `label`: The name of the organization * `role`: The role the organization played in the article's creation (e.g. `'Publishing'`) * `properties`: A `dict` of additional properties associated with this organization's role in the article creation (e.g. `DatesOfPublication`) * `names`: A `list` of names this organization may be identified by * `identifiers`: A `list` of (identifier, identifier type) pairs * `uid`: A unique ID for this organization * `uuid`: A unique UUID for this organization used in assigning it a URN ''' lod_object = get_crom_object(data) organizations = [] for o in data.get('_organizations', []): org = {k: v for k, v in o.items()} org_obj = vocab.Group(ident=org['uri']) add_crom_data(data=org, what=org_obj) event = model.Activity() # TODO: change to vocab.Publishing for publishing activities lod_object.used_for = event event.carried_out_by = org_obj properties = o.get('properties') role = o.get('role') if role is not None: activity_names = { 'distributor': 'Distributing', 'publisher': 'Publishing', # TODO: Need to also handle roles: Organization, Sponsor, University } if role.lower() in activity_names: event_label = activity_names[role.lower()] event._label = event_label else: print('*** No/unknown organization role (%r) found for imprint_group in %s:' % ( role, lod_object,)) # pprint.pprint(o) if role == 'Publisher' and 'DatesOfPublication' in properties: pubdate = properties['DatesOfPublication'] span = CleanDateToSpan.string_to_span(pubdate) if span is not None: event.timespan = span organizations.append(org) data['organizations'] = organizations return data
def make_place(self, data: dict, base_uri=None): ''' Given a dictionary representing data about a place, construct a model.Place object, assign it as the crom data in the dictionary, and return the dictionary. The dictionary keys used to construct the place object are: - name - type (one of: 'City', 'State', 'Province', or 'Country') - part_of (a recursive place dictionary) ''' unique_locations = self.unique_locations TYPES = { 'city': vocab.instances['city'], 'province': vocab.instances['province'], 'state': vocab.instances['province'], 'country': vocab.instances['nation'], } if data is None: return None type_name = data.get('type', 'place').lower() name = data.get('name') label = name parent_data = data.get('part_of') place_type = TYPES.get(type_name) parent = None if parent_data: parent_data = self.make_place(parent_data, base_uri=base_uri) parent = get_crom_object(parent_data) if label: label = f'{label}, {parent._label}' placeargs = {} if label: placeargs['label'] = label if data.get('uri'): placeargs['ident'] = data['uri'] elif label in unique_locations: data['uri'] = self.make_proj_uri('PLACE', label) placeargs['ident'] = data['uri'] elif base_uri: data['uri'] = base_uri + urllib.parse.quote(label) placeargs['ident'] = data['uri'] p = model.Place(**placeargs) if place_type: p.classified_as = place_type if name: p.identified_by = model.Name(ident='', content=name) else: warnings.warn(f'Place with missing name on {p.id}') if parent: p.part_of = parent data['part_of'] = parent_data return add_crom_data(data=data, what=p)
def model_authorship_group(self, record, data): if not data: return record.setdefault('_people', []) record.setdefault('created_by', []) authors = _as_list(data.get('primary_author')) mlap = MakeLinkedArtPerson() mlao = MakeLinkedArtOrganization() ordered_data = [] article_label = record['label'] creation_id = record['uri'] + '-Creation' creation = model.Creation(ident=creation_id, label=f'Creation of {article_label}') for a in authors: gaia_id = a['gaia_authority_id'] gaia_type = a['gaia_authority_type'] name = a['author_name'] roles = _as_list(a['author_role']) order = a['author_order'] ordered_data.append((order, name)) p = { 'label': name, 'name': name, } if gaia_type == 'Person': uri = self.helper.person_uri(gaia_id) p['uri'] = uri mlap(p) elif gaia_type == 'Corp': uri = self.helper.corporate_body_uri(gaia_id) p['uri'] = uri mlao(p) else: raise Exception( f'Unexpected type of authorship record: {gaia_type}') # uri = self.helper.make_proj_uri(gaia_type, 'GAIA', gaia_id) record['_people'].append(p) for role in roles: part = model.Creation(ident='', label=f'{role} Creation sub-event') part.carried_out_by = get_crom_object(p) cl = self.helper.role_type(role) if cl: part.classified_as = cl creation.part = part ordered_authors = [p[1] for p in sorted(ordered_data)] order_string = self.helper.ordered_author_string(ordered_authors) creation.referred_to_by = vocab.Note(ident='', content=order_string) record['created_by'].append(creation)
def add_group(self, a, record=None, relative_id=None, **kwargs): self.add_uri(a, record_id=relative_id) self.add_names(a, referrer=record, group=True, **kwargs) self.add_props(a, **kwargs) self.make_la_org(a) g = get_crom_object(a) if record: g.referred_to_by = record return g
def add_aata_authors(data): ''' Given a `dict` representing an "article," extract the authorship records and their role (e.g. author, editor). yield a new `dict`s for each such creator (subsequently referred to as simply "author"). The resulting author `dict` will contain these keys: * `_aata_record_id`: The identifier of the corresponding article * `_aata_record_author_seq`: A integer identifying this author (unique within the scope of the article) * `label`: The name of the author * `creation_role`: The role the author played in the creation of the "article" (e.g. `'Author'`) * `names`: A `list` of names this organization may be identified by * `identifiers`: A `list` of (identifier, identifier type) pairs * `uid`: A unique ID for this organization * `parent`: The model object representing the corresponding article * `parent_data`: The `dict` representing the corresponding article * `events`: A `list` of `model.Creation` objects representing the part played by the author in the article's creation event. ''' lod_object = get_crom_object(data) event = model.Creation() lod_object.created_by = event authors = data.get('_authors', []) make_la_person = MakeLinkedArtPerson() for a in authors: make_la_person(a) person_name = a['label'] person = get_crom_object(a) subevent = model.Creation() # TODO: The should really be asserted as object -created_by-> CreationEvent -part-> SubEvent # however, right now that assertion would get lost as it's data that belongs to the object, # and we're on the author's chain in the bonobo graph; object serialization has already happened. # we need to serialize the object's relationship to the creation event, and let it get merged # with the rest of the object's data. event.part = subevent role = a.get('creation_role') if role is not None: subevent._label = f'Creation sub-event for {role} by “{person_name}”' subevent.carried_out_by = person yield data
def add_person(self, a, record=None, relative_id=None, **kwargs): self.add_uri(a, record_id=relative_id) self.add_names(a, referrer=record, **kwargs) self.add_props(a, **kwargs) auth_name = a.get('auth_name') if auth_name and self.is_anonymous_group(auth_name): self.make_la_org(a) else: self.make_la_person(a) return get_crom_object(a)
def add_transfer_of_custody(self, data, current_tx, xfer_to, xfer_from, sequence=1, purpose=None): buyers = xfer_to sellers = xfer_from hmo = get_crom_object(data) parent = data['parent_data'] auction_data = parent['auction_of_lot'] cno, lno, date = object_key(auction_data) xfer_label = None purpose_label = f'(for {purpose}) ' if purpose else '' try: object_label = f'“{hmo._label}”' xfer_label = f'Transfer of Custody {purpose_label}of {cno} {lno} ({date}): {object_label}' except AttributeError: object_label = '(object)' xfer_label = f'Transfer of Custody {purpose_label}of {cno} {lno} ({date})' # TODO: pass in the tx to use instead of getting it from `parent` tx_data = parent['_prov_entry_data'] current_tx = get_crom_object(tx_data) xfer_id = hmo.id + f'-CustodyTransfer-{sequence}' xfer = model.TransferOfCustody(ident=xfer_id, label=xfer_label) xfer.transferred_custody_of = hmo if purpose in self.custody_xfer_purposes: xfer.general_purpose = self.custody_xfer_purposes[purpose] for seller_data in sellers: seller = get_crom_object(seller_data) xfer.transferred_custody_from = seller for buyer_data in buyers: buyer = get_crom_object(buyer_data) xfer.transferred_custody_to = buyer current_tx.part = xfer
def __call__(self, data: dict, location_codes, unique_catalogs): '''Add information about the ownership of a physical copy of an auction catalog''' # Add the URI of this physical catalog to `unique_catalogs`. This data will be used # later to figure out which catalogs can be uniquely identified by a catalog number # and owner code (e.g. for owners who do not have multiple copies of a catalog). cno = data['catalog_number'] owner_code = data['owner_code'] copy_number = data.get('copy_number', '') owner_name = None entry_record = get_crom_object(data.get('_catalog')) with suppress(KeyError): owner_name = location_codes[owner_code] owner_uri = self.helper.make_proj_uri('ORGANIZATION', 'LOCATION-CODE', owner_code) data['_owner'] = { 'label': owner_name, 'uri': owner_uri, 'referred_to_by': [entry_record], 'identifiers': [ model.Name(ident='', content=owner_name), model.Identifier(ident='', content=str(owner_code)) ], } owner = model.Group(ident=owner_uri) owner.referred_to_by = entry_record add_crom_data(data['_owner'], owner) if not owner_code: warnings.warn(f'Setting empty identifier on {owner.id}') add_crom_data(data=data['_owner'], what=owner) catalog = get_crom_object(data) catalog.current_owner = owner owner_uri = self.helper.physical_catalog_uri( cno, owner_code, None ) # None here because we want a key that will stand in for all the copies belonging to a single owner copy_uri = self.helper.physical_catalog_uri(cno, owner_code, copy_number) unique_catalogs[owner_uri].add(copy_uri) return data
def set_possible_attribute(self, obj, prop, data): value = get_crom_object(data) if not value: return uncertain = data.get('uncertain', False) if uncertain: assignment = vocab.PossibleAssignment(ident='') assignment.assigned_property = prop assignment.assigned = value obj.attributed_by = assignment else: setattr(obj, prop, value)
def set_lot_objects(self, lot, cno, lno, auction_of_lot_uri, data, sale_type): '''Associate the set of objects with the auction lot.''' shared_lot_number = self.helper.shared_lot_number_from_lno(lno) set_type = vocab.AuctionLotSet if sale_type == 'Auction' else vocab.CollectionSet coll_label = f'Object Set for Lot {cno} {shared_lot_number}' coll = set_type(ident=f'{auction_of_lot_uri}-Set', label=coll_label) coll.identified_by = model.Name(ident='', content=coll_label) est_price = data.get('estimated_price') if est_price: coll.dimension = get_crom_object(est_price) start_price = data.get('start_price') if start_price: coll.dimension = get_crom_object(start_price) ask_price = data.get('ask_price') if ask_price: coll.dimension = get_crom_object(ask_price) lot.used_specific_object = coll data['_lot_object_set'] = add_crom_data(data={}, what=coll)
def __call__(self, data, language_code_map): ''' Given a `dict` representing an "article," extract the abstract records. yield a new `dict`s for each such record. The resulting asbtract `dict` will contain these keys: * `_LOD_OBJECT`: A `model.LinguisticObject` object representing the abstract * `_aata_record_id`: The identifier of the corresponding article * `_aata_record_author_seq`: A integer identifying this abstract (unique within the scope of the article) * `content`: The text content of the abstract * `language`: A model object representing the declared langauge of the abstract (if any) * `author_abstract_flag`: A boolean value indicating whether the article's authors also authored the abstract * `identifiers`: A `list` of (identifier, identifier type) pairs * `_authors`: The authorship information from the input article `dict` * `uid`: A unique ID for this abstract * `parent`: The model object representing the corresponding article * `parent_data`: The `dict` representing the corresponding article ''' lod_object = get_crom_object(data) for a in data.get('_abstracts', []): abstract_dict = { k: v for k, v in a.items() if k not in ('language', ) } abstract_uri = self.helper.make_proj_uri( 'Abstract', data['_aata_record_id'], a['_aata_record_abstract_seq']) content = a.get('content') abstract = vocab.Abstract(ident=abstract_uri, content=content) abstract.refers_to = lod_object langcode = a.get('language') if langcode is not None: language = self.helper.language_object_from_code( langcode, language_code_map) if language is not None: abstract.language = language abstract_dict['language'] = language if '_authors' in data: abstract_dict['_authors'] = data['_authors'] # create a uid based on the AATA record id, the sequence number of the abstract # in that record, and which author we're handling right now abstract_dict.update({ 'parent_data': data, 'uri': abstract_uri, }) add_crom_data(data=abstract_dict, what=abstract) yield abstract_dict
def __call__(self, data: dict, non_auctions): '''Add modeling for physical copies of an auction catalog''' catalog = get_crom_object(data['_catalog']) record = get_crom_object(data['_catalog_record']) cno = data['catalog_number'] owner = data['owner_code'] copy = data['copy_number'] sale_type = non_auctions.get(cno, 'Auction') catalogObject = self.helper.physical_catalog(cno, sale_type, owner, copy, add_name=True) catalogObject.referred_to_by = record data['uri'] = catalogObject.id info = data.get('annotation_info') if info: catalogObject.referred_to_by = vocab.Note(ident='', content=info) catalogObject.carries = catalog add_crom_data(data=data, what=catalogObject) return data
def make_issue(data: dict): parent_data = data['parent_data'] data['part_of'] = [parent_data] # the issue is a part of the journal volume_number = data.get('volume') if volume_number is not None: volume_data = parent_data.get('volumes', {}).get(volume_number) volume = get_crom_object(volume_data) if volume: data['part_of'].append(volume_data) # the issue is a part of the volume return data
def add_sellers(self, data: dict, sale_type, transaction, sellers, rel, source=None): hmo = get_crom_object(data) parent = data['parent_data'] auction_data = parent['auction_of_lot'] lot_object_key = object_key(auction_data) cno, lno, date = lot_object_key lot = get_crom_object(parent.get('_event_causing_prov_entry')) ts = getattr(lot, 'timespan', None) prev_procurements = [] tx_label_args = tuple([self.helper, sale_type, 'Sold', rel] + list(lot_object_key)) for i, seller_data in enumerate(sellers): seller = get_crom_object(seller_data) # The provenance entry for a seller's previous acquisition is specific to a # single transaction. Therefore, the provenance entry URI must not share a # prefix with the object URI, otherwise all such provenance entries are liable # to be merged during URI reconciliation as part of the prev/post sale rewriting. tx_uri = self.helper.prepend_uri_key(hmo.id, f'PROV,Seller-{i}') tx, acq = self.related_procurement(hmo, tx_label_args, current_ts=ts, buyer=seller, previous=True, ident=tx_uri, make_label=prov_entry_label) self.attach_source_catalog(data, acq, [seller_data]) if source: tx.referred_to_by = source prev_procurements.append(add_crom_data(data={}, what=tx)) data['_prov_entries'] += prev_procurements return prev_procurements
def model_place(self, data): country = data.get('country') state = data.get('state') if country: data['part_of'] = country if state: state['part_of'] = data.get('part_of') data['part_of'] = state pid = data['concept_group']['gaia_auth_id'] data.setdefault('label', f'Place ({pid})') place_base = self.helper.make_proj_uri('Place:') mlap = MakeLinkedArtPlace(base_uri=place_base) mlap(data) place = get_crom_object(data)
def __call__(self, data: dict): '''Add modeling for an auction event based on properties of the supplied `data` dict.''' cno = data['catalog_number'] sale_type = data.get('non_auction_flag', 'Auction') auction, uid, uri = self.helper.sale_event_for_catalog_number( cno, sale_type) auction.identified_by = model.Name(ident='', content=auction._label) data['uid'] = uid data['uri'] = uri add_crom_data(data=data, what=auction) catalog = get_crom_object(data['_catalog']) data['_record'] = data['_catalog'] return data
def _populate_object_visual_item(self, data: dict, subject_genre): hmo = get_crom_object(data) title = data.get('title') title = truncate_with_ellipsis(title, 100) or title vi_id = hmo.id + '-VisItem' vi = model.VisualItem(ident=vi_id) vidata = {'uri': vi_id} if title: vidata['label'] = f'Visual work of “{title}”' sales_record = get_crom_object(data['_record']) vidata['names'] = [(title, {'referred_to_by': [sales_record]})] for key in ('genre', 'subject'): if key in data: values = [v.strip() for v in data[key].split(';')] for value in values: for prop, mapping in subject_genre.items(): if value in mapping: aat_url = mapping[value] type = model.Type(ident=aat_url, label=value) setattr(vi, prop, type) data['_visual_item'] = add_crom_data(data=vidata, what=vi) hmo.shows = vi
def add_person(self, a, record=None, relative_id=None, **kwargs): self.add_uri(a, record_id=relative_id) auth_name = a.get('auth_name') # is_group will be true here if this person record is a stand-in # for a group of people (e.g. all French people, or 17th century Germans) is_group = auth_name and self.is_anonymous_group(auth_name) self.add_names(a, group=is_group, referrer=record, **kwargs) self.add_props(a, **kwargs) if is_group: self.make_la_org(a) else: self.make_la_person(a) p = get_crom_object(a) if record: p.referred_to_by = record return p
def make_aata_org_event(o: dict): ''' Given a `dict` representing an organization, create an `model.Activity` object to represent the organization's part in the "article" creation (associating any applicable publication timespan to the activity), associate the activity with the organization and the corresponding "article", and return a new `dict` that combines the input data with an `'events'` key having a `list` value containing the new activity object. For example, ``` make_aata_org_event({ 'event_label': 'Publishing', 'publication_date_span': model.TimeSpan(...), ... }) ``` will return: ``` { 'event_label': 'Publishing', 'publication_date_span': model.TimeSpan(...), 'events': [model.Activity(_label: 'Publishing', 'timespan': ts.TimeSpan(...))], ... } ``` and also set the article object's `used_for` property to the new activity. ''' event = model.Activity() lod_object = get_crom_object(o['parent_data']) lod_object.used_for = event event._label = o.get('event_label') if 'publication_date_span' in o: ts = o['publication_date_span'] event.timespan = ts org = {k: v for k, v in o.items()} org.update({ 'events': [event], }) yield org
def _populate_object_notes(self, data: dict, parent, unique_catalogs): hmo = get_crom_object(data) notes = data.get('hand_note', []) for note in notes: hand_note_content = note['hand_note'] owner = note.get('hand_note_so') cno = parent['auction_of_lot']['catalog_number'] catalog_uri = self.helper.make_proj_uri('CATALOG', cno, owner, None) catalogs = unique_catalogs.get(catalog_uri) note = vocab.Note(ident='', content=hand_note_content) hmo.referred_to_by = note if catalogs and len(catalogs) == 1: note.carried_by = vocab.AuctionCatalog( ident=catalog_uri, label=f'Sale Catalog {cno}, owned by “{owner}”') inscription = data.get('inscription') if inscription: hmo.referred_to_by = vocab.InscriptionStatement( ident='', content=inscription)
def _price_note(self, price): ''' For lots with multiple payment records, the first is asserted as the real payment. The rest are turned into LinguisticObjects and associated with the payment as `referred_to_by`. This function constructs the content for that LinguisticObject, containing price, currency, citations, and notes. ''' amnt = get_crom_object(price) try: value = amnt.value except: return None label = f'{value}' if hasattr(amnt, 'currency'): currency = amnt.currency label = f'{value} {currency._label}' notes = [] cites = [] if hasattr(amnt, 'referred_to_by'): for ref in amnt.referred_to_by: content = ref.content classification = {c._label for c in ref.classified_as} if 'Note' in classification: notes.append(content) else: cites.append(content) strings = [] if notes: strings.append(', '.join(notes)) if cites: strings.append(', '.join(cites)) if strings: content = '; '.join(strings) label += f'; {content}' return label
def __call__(self, data): d = {k: v for k, v in data.items()} parent = data['parent_data'] cno = str(parent['catalog_number']) sno = parent['star_record_no'] catalog = get_crom_object(d) for lugt_no in parent.get('lugt', {}).values(): if not lugt_no: warnings.warn(f'Setting empty identifier on {catalog.id}') catalog.identified_by = self.lugt_number_id(lugt_no) if not cno: warnings.warn(f'Setting empty identifier on {catalog.id}') catalog.identified_by = self.helper.gri_number_id(cno) if not sno: warnings.warn(f'Setting empty identifier on {catalog.id}') catalog.identified_by = vocab.SystemNumber(ident='', content=sno) notes = data.get('notes') if notes: note = vocab.Note(ident='', content=parent['notes']) catalog.referred_to_by = note return d
def handle_statements(self, data): record = get_crom_object(data['_entry_record']) data['referred_to_by'].append(record) source_content = data.get('source') if source_content: cite = vocab.BibliographyStatement(ident='', content=source_content) data['referred_to_by'].append(cite) project = data.get('project') if project: data['referred_to_by'].append( vocab.SourceStatement(ident='', content=project)) awards = { l.strip() for l in data.get('medal_received', '').split(';') } - {''} for award in awards: cite = vocab.Note( ident='', content=award ) # TODO: add proper classification for an Awards Statement data['referred_to_by'].append(cite)
def _populate_object_catalog_record(self, data: dict, parent, lot, cno, rec_num): hmo = get_crom_object(data) catalog_uri = self.helper.make_proj_uri('CATALOG', cno) catalog = vocab.AuctionCatalogText(ident=catalog_uri, label=f'Sale Catalog {cno}') record_uri = self.helper.make_proj_uri('CATALOG', cno, 'RECORD', rec_num) lot_object_id = parent['lot_object_id'] puid = parent.get('persistent_puid') puid_id = self.helper.gri_number_id(puid) record = vocab.ParagraphText( ident=record_uri, label= f'Sale recorded in catalog: {lot_object_id} (record number {rec_num})' ) record_data = {'uri': record_uri} record_data['identifiers'] = [ model.Name(ident='', content=f'Record of sale {lot_object_id}'), puid_id ] record.part_of = catalog if parent.get('transaction'): record.referred_to_by = vocab.PropertyStatusStatement( ident='', label='Transaction type for sales record', content=parent['transaction']) record.about = hmo data['_record'] = add_crom_data(data=record_data, what=record) return record
def __call__(self, data: dict, *, attribution_modifiers, attribution_group_types): '''Add modeling for artists as people involved in the production of an object''' hmo = get_crom_object(data) data['_organizations'] = [] data['_original_objects'] = [] try: hmo_label = f'{hmo._label}' except AttributeError: hmo_label = 'object' event_id = hmo.id + '-Prod' event = model.Production(ident=event_id, label=f'Production event for {hmo_label}') hmo.produced_by = event artists = data.get('_artists', []) sales_record = get_crom_object(data['_record']) pi = self.helper.person_identity for a in artists: a.setdefault('referred_to_by', []) a.update({ 'pi_record_no': data['pi_record_no'], 'ulan': a['artist_ulan'], 'auth_name': a['art_authority'], 'name': a['artist_name'] }) if a.get('biography'): bio = a['biography'] del a['biography'] cite = vocab.BiographyStatement(ident='', content=bio) a['referred_to_by'].append(cite) def is_or_anon(data: dict): if pi.is_anonymous(data): mods = { m.lower().strip() for m in data.get('attrib_mod_auth', '').split(';') } return 'or' in mods return False or_anon_records = [is_or_anon(a) for a in artists] uncertain_attribution = any(or_anon_records) for seq_no, a in enumerate(artists): attribute_assignment_id = event.id + f'-artist-assignment-{seq_no}' if is_or_anon(a): # do not model the "or anonymous" records; they turn into uncertainty on the other records continue person = self.helper.add_person(a, record=sales_record, relative_id=f'artist-{seq_no+1}', role='artist') artist_label = a.get('role_label') mod = a.get('attrib_mod_auth', '') mods = CaseFoldingSet({m.strip() for m in mod.split(';')} - {''}) attrib_assignment_classes = [model.AttributeAssignment] if uncertain_attribution or 'or' in mods: attrib_assignment_classes.append(vocab.PossibleAssignment) if mods: # TODO: this should probably be in its own JSON service file: STYLE_OF = attribution_modifiers['style of'] FORMERLY_ATTRIBUTED_TO = attribution_modifiers[ 'formerly attributed to'] ATTRIBUTED_TO = attribution_modifiers['attributed to'] COPY_AFTER = attribution_modifiers['copy after'] PROBABLY = attribution_modifiers['probably by'] POSSIBLY = attribution_modifiers['possibly by'] UNCERTAIN = attribution_modifiers['uncertain'] GROUP_TYPES = set(attribution_group_types.values()) GROUP_MODS = { k for k, v in attribution_group_types.items() if v in GROUP_TYPES } if 'copy by' in mods: # equivalent to no modifier pass elif ATTRIBUTED_TO.intersects(mods): # equivalent to no modifier pass elif STYLE_OF.intersects(mods): assignment = vocab.make_multitype_obj( *attrib_assignment_classes, ident=attribute_assignment_id, label=f'In the style of {artist_label}') event.attributed_by = assignment assignment.assigned_property = 'influenced_by' assignment.property_classified_as = vocab.instances[ 'style of'] assignment.assigned = person continue elif mods.intersects(GROUP_MODS): mod_name = list(GROUP_MODS & mods)[0] # TODO: use all matching types? clsname = attribution_group_types[mod_name] cls = getattr(vocab, clsname) group_label = f'{clsname} of {artist_label}' group_id = a['uri'] + f'-{clsname}' group = cls(ident=group_id, label=group_label) formation = model.Formation( ident='', label=f'Formation of {group_label}') formation.influenced_by = person group.formed_by = formation group_data = add_crom_data({'uri': group_id}, group) data['_organizations'].append(group_data) subevent_id = event_id + f'-{seq_no}' # TODO: fix for the case of post-sales merging subevent = model.Production( ident=subevent_id, label=f'Production sub-event for {group_label}') subevent.carried_out_by = group if uncertain_attribution: assignment = vocab.make_multitype_obj( *attrib_assignment_classes, ident=attribute_assignment_id, label=f'Possibly attributed to {group_label}') event.attributed_by = assignment assignment.assigned_property = 'part' assignment.assigned = subevent else: event.part = subevent continue elif FORMERLY_ATTRIBUTED_TO.intersects(mods): # the {uncertain_attribution} flag does not apply to this branch, because this branch is not making a statement # about a previous attribution. the uncertainty applies only to the current attribution. assignment = vocab.ObsoleteAssignment( ident=attribute_assignment_id, label=f'Formerly attributed to {artist_label}') event.attributed_by = assignment assignment.assigned_property = 'carried_out_by' assignment.assigned = person continue elif UNCERTAIN.intersects(mods): if POSSIBLY.intersects(mods): attrib_assignment_classes.append( vocab.PossibleAssignment) assignment = vocab.make_multitype_obj( *attrib_assignment_classes, ident=attribute_assignment_id, label=f'Possibly attributed to {artist_label}') assignment._label = f'Possibly by {artist_label}' else: attrib_assignment_classes.append( vocab.ProbableAssignment) assignment = vocab.make_multitype_obj( *attrib_assignment_classes, ident=attribute_assignment_id, label=f'Probably attributed to {artist_label}') assignment._label = f'Probably by {artist_label}' event.attributed_by = assignment assignment.assigned_property = 'carried_out_by' assignment.assigned = person continue elif COPY_AFTER.intersects(mods): # the {uncertain_attribution} flag does not apply to this branch, because this branch is not making a statement # about the artist of the work, but about the artist of the original work that this work is a copy of. cls = type(hmo) original_id = hmo.id + '-Orig' original_label = f'Original of {hmo_label}' original_hmo = cls(ident=original_id, label=original_label) original_event_id = original_hmo.id + '-Prod' original_event = model.Production( ident=original_event_id, label=f'Production event for {original_label}') original_hmo.produced_by = original_event original_subevent_id = original_event_id + f'-{seq_no}' # TODO: fix for the case of post-sales merging original_subevent = model.Production( ident=original_subevent_id, label=f'Production sub-event for {artist_label}') original_event.part = original_subevent original_subevent.carried_out_by = person event.influenced_by = original_hmo data['_original_objects'].append( add_crom_data(data={}, what=original_hmo)) continue elif mods & {'or', 'and'}: pass else: print(f'UNHANDLED attrib_mod_auth VALUE: {mods}') pprint.pprint(a) continue subprod_path = self.helper.make_uri_path(*a["uri_keys"]) subevent_id = event_id + f'-{subprod_path}' subevent = model.Production( ident=subevent_id, label=f'Production sub-event for {artist_label}') subevent.carried_out_by = person if uncertain_attribution or 'or' in mods: assignment = vocab.make_multitype_obj( *attrib_assignment_classes, ident=attribute_assignment_id, label=f'Possibly attributed to {artist_label}') event.attributed_by = assignment assignment.assigned_property = 'part' assignment.assigned = subevent else: event.part = subevent data['_artists'] = [a for a in artists if not is_or_anon(a)] return data
def __call__(self, data: dict, post_sale_map, unique_catalogs, subject_genre, destruction_types_map): '''Add modeling for an object described by a sales record''' hmo = get_crom_object(data) parent = data['parent_data'] auction_data = parent.get('auction_of_lot') if auction_data: lno = str(auction_data['lot_number']) data.setdefault('identifiers', []) if not lno: warnings.warn(f'Setting empty identifier on {hmo.id}') data['identifiers'].append(vocab.LotNumber(ident='', content=lno)) else: warnings.warn(f'***** NO AUCTION DATA FOUND IN populate_object') cno = auction_data['catalog_number'] lno = auction_data['lot_number'] date = implode_date(auction_data, 'lot_sale_') lot = self.helper.shared_lot_number_from_lno( lno ) # the current key for this object; may be associated later with prev and post object keys now_key = (cno, lno, date) data['_locations'] = [] data['_events'] = [] record = self._populate_object_catalog_record(data, parent, lot, cno, parent['pi_record_no']) self._populate_object_visual_item(data, subject_genre) self._populate_object_destruction(data, parent, destruction_types_map) self.populate_object_statements(data) self._populate_object_present_location(data, now_key, destruction_types_map) self._populate_object_notes(data, parent, unique_catalogs) self._populate_object_prev_post_sales(data, now_key, post_sale_map) for p in data.get('portal', []): url = p['portal_url'] hmo.referred_to_by = vocab.WebPage(ident=url, label=url) if 'title' in data: title = data['title'] if not hasattr(hmo, '_label'): typestring = data.get('object_type', 'Object') hmo._label = f'{typestring}: “{title}”' del data['title'] shorter = truncate_with_ellipsis(title, 100) if shorter: description = vocab.Description(ident='', content=title) description.referred_to_by = record hmo.referred_to_by = description title = shorter t = vocab.PrimaryName(ident='', content=title) t.classified_as = model.Type( ident='http://vocab.getty.edu/aat/300417193', label='Title') t.referred_to_by = record data['identifiers'].append(t) for d in data.get('other_titles', []): title = d['title'] t = vocab.Name(ident='', content=title) data['identifiers'].append(t) return data
def _populate_object_present_location(self, data: dict, now_key, destruction_types_map): hmo = get_crom_object(data) location = data.get('present_location') if location: loc = location.get('geog') note = location.get('note') if loc: if 'destroyed ' in loc.lower(): self.populate_destruction_events( data, loc, type_map=destruction_types_map) elif isinstance(note, str) and 'destroyed ' in note.lower(): # the object was destroyed, so any "present location" data is actually # an indication of the location of destruction. self.populate_destruction_events( data, note, type_map=destruction_types_map, location=loc) else: # TODO: if `parse_location_name` fails, still preserve the location string somehow current = parse_location_name( loc, uri_base=self.helper.uid_tag_prefix) inst = location.get('inst') if inst: owner_data = { 'label': f'{inst} ({loc})', 'identifiers': [model.Name(ident='', content=inst)] } ulan = None with suppress(ValueError, TypeError): ulan = int(location.get('insi')) if ulan: owner_data['ulan'] = ulan owner_data['uri'] = self.helper.make_proj_uri( 'ORG', 'ULAN', ulan) else: owner_data['uri'] = self.helper.make_proj_uri( 'ORG', 'NAME', inst, 'PLACE', loc) else: owner_data = { 'label': '(Anonymous organization)', 'uri': self.helper.make_proj_uri('ORG', 'CURR-OWN', *now_key), } if note: owner_data['note'] = note base_uri = hmo.id + '-Place,' place_data = self.helper.make_place(current, base_uri=base_uri) place = get_crom_object(place_data) make_la_org = pipeline.linkedart.MakeLinkedArtOrganization( ) owner_data = make_la_org(owner_data) owner = get_crom_object(owner_data) acc = location.get('acc') if acc: acc_number = vocab.AccessionNumber(ident='', content=acc) hmo.identified_by = acc_number assignment = model.AttributeAssignment(ident='') assignment.carried_out_by = owner acc_number.assigned_by = assignment owner.residence = place data['_locations'].append(place_data) data['_final_org'] = owner_data else: pass # there is no present location place string
def handle_prev_post_owner(self, data, hmo, tx_data, sale_type, lot_object_key, owner_record, record_id, rev, ts=None, make_label=None): current_tx = get_crom_object(tx_data) sales_record = get_crom_object(data['_record']) if rev: rel = f'leading to the previous ownership of' source_label = 'Source of information on history of the object prior to the current sale.' else: rel = f'leading to the subsequent ownership of' source_label = 'Source of information on history of the object after the current sale.' owner_record.update({ 'pi_record_no': data['pi_record_no'], 'ulan': owner_record.get('ulan', owner_record.get('own_ulan')), }) self.add_person(owner_record, record=sales_record, relative_id=record_id, role='artist') owner = get_crom_object(owner_record) # TODO: handle other fields of owner_record: own_auth_d, own_auth_q, own_ques, own_so if owner_record.get('own_auth_l'): loc = owner_record['own_auth_l'] current = parse_location_name(loc, uri_base=self.helper.uid_tag_prefix) place_data = self.helper.make_place(current) place = get_crom_object(place_data) owner.residence = place data['_owner_locations'].append(place_data) if owner_record.get('own_auth_p'): content = owner_record['own_auth_p'] owner.referred_to_by = vocab.Note(ident='', content=content) data.setdefault('_other_owners', []) data['_other_owners'].append(owner_record) # The Provenance Entry URI must not share a prefix with the object URI, otherwise # we run the rist of provenance entries being accidentally merged during URI # reconciliation as part of the prev/post sale rewriting. tx_uri = self.helper.prepend_uri_key(hmo.id, f'PROV-{record_id}') tx_label_args = tuple([self.helper, sale_type, 'Sold', rel] + list(lot_object_key)) tx, _ = self.related_procurement(hmo, tx_label_args, current_tx, ts, buyer=owner, previous=rev, ident=tx_uri, make_label=make_label) if owner_record.get('own_auth_e'): content = owner_record['own_auth_e'] tx.referred_to_by = vocab.Note(ident='', content=content) own_info_source = owner_record.get('own_so') if own_info_source: note = vocab.SourceStatement(ident='', content=own_info_source, label=source_label) tx.referred_to_by = note ptx_data = tx_data.copy() data['_prov_entries'].append(add_crom_data(data=ptx_data, what=tx))