Alma API BIBFRAME
Table of Contents
1. About this tutorial
This tutorial is about importing BIBFRAME from LoC into Alma. It’s goal is to give you a starting point for writing your own scripts fitting your usecase. It assumes some familiarity with python or another scripting language.
You can follow along by pasting the code into your Python interpreter.
The source code can be found on GitHub: https://github.com/OBVSG/bfloc2alma/
There’s a Video going through the material on Youtube: https://youtu.be/WYqgQV-oyoE
If you spot any errors, please report them to me: stefan.schuh@obvsg.at
English is not my native language, so I gladly take grammar corrections and style suggestions, too. ;-)
2. System requirements
This tutorial was developed and tested on Fedora Linux 41 with Python 3.13.1. There is nothing OS-specific here, so it should work without problems on Mac and Windows as well. As soon as I get access to a Windows machine, I will try it there.
On most Linux distributions Python comes preinstalled. You will have to install it on Windows. Get it here: https://www.python.org/
Note, that the invocations of the Python interpreter may vary a bit on your system. It’s python3
for me, but could also be just python
, python.exe
(on Windows) or similar.
2.1. Virtual environment and third party libraries
I don’t install any Python libraries outside of virtual environments, so I don’t mess up my system. If you are not concerned about that, you can skip that and jump ahead to pip install ...
$ python3 -m virtualenv .venv $ source .venv/bin/activate
This creates an isolated environment and activates it. You can see this as there will be (.venv)
prepended to your command line prompt.
Once you have done that, you have to install the only dependency:
(.venv)$ pip install requests
If you are done you can deactivate the environment by typing deactivate
on the prompt.
2.2. API Key
To follow along, you need an API key with read/write permissions, preferably for a sandbox environment.
I assume that you have the API key in an environment variable. In bash like shells, you can accomplish this by
$ export API_KEY_PSB=paste-api-key-here
YMMV on Windows. You can put it in the config.py
file, if you absolutely have no other choice. I strongly advise against doing so for security reasons.
3. Library code
3.1. Configuration
Our configuration will reside in it’s own file, so our script isn’t cluttered with constant definitions.
3.1.1. XML-Namespaces
XML-Namespaces that occur in BIBFRAME documents. This is to have prettier output (bf
instead of ns1
prefixes etc.) and also needed for XPath (ET.find()
etc.).
NS = { "bf": "http://id.loc.gov/ontologies/bibframe/", "bflc": "http://id.loc.gov/ontologies/bflc/", "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", "rdfs": "http://www.w3.org/2000/01/rdf-schema#", "madsrdf": "http://www.loc.gov/mads/rdf/v1#", "dcterms": "http://purl.org/dc/terms/", "lclocal": "http://id.loc.gov/ontologies/lclocal/", } # register all namespaces for nicer output (no ns1, ns2, ... prefixes) for prfx, uri in NS.items(): ET.register_namespace(prfx, uri)
3.1.2. API-Key
You should never store any secrets in source files. You WILL eventually overlook it and publish it on GitHub. So we get it from an environment variable.
API_KEY = os.environ.get("API_KEY_PSB") or "put-your-key-here-if-you-absolutely-must"
3.1.3. API URL
Put the base URL of your Alma API Endpoint here. As I am in Austria, it’s the one for Europe. As I’m on it, I create a template for the bib API, so I only need to fill in the MMSID to get a full API-URL.
BASE_URL = "https://api-eu.hosted.exlibrisgroup.com/almaws/v1" bib_api = BASE_URL + "/bibs/{mms_id}"
3.1.4. Session headers
We use a session to make requests against the Alma API. All requests using this session are using the same headers:
ALMA_SESSION_HEADERS = { "accept": "application/xml", "content-type": "application/xml", "validate": "false", "authorization": f"apikey {API_KEY}" }
3.1.5. Put together config.py
import os from xml.etree import ElementTree as ET NS = { "bf": "http://id.loc.gov/ontologies/bibframe/", "bflc": "http://id.loc.gov/ontologies/bflc/", "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", "rdfs": "http://www.w3.org/2000/01/rdf-schema#", "madsrdf": "http://www.loc.gov/mads/rdf/v1#", "dcterms": "http://purl.org/dc/terms/", "lclocal": "http://id.loc.gov/ontologies/lclocal/", } # register all namespaces for nicer output (no ns1, ns2, ... prefixes) for prfx, uri in NS.items(): ET.register_namespace(prfx, uri) API_KEY = os.environ.get("API_KEY_PSB") or "put-your-key-here-if-you-absolutely-must" BASE_URL = "https://api-eu.hosted.exlibrisgroup.com/almaws/v1" bib_api = BASE_URL + "/bibs/{mms_id}" ALMA_SESSION_HEADERS = { "accept": "application/xml", "content-type": "application/xml", "validate": "false", "authorization": f"apikey {API_KEY}" }
3.2. Functions
At the end I want to have a CLI program that takes some LoC-IDs, gets the data from there and puts it into Alma and prints out the newly created MMSIDs - so I can delete them again ;-)
But first we define some functions and try it with one example, step by step.
3.2.1. Imports
For our main module to work we need to import some modules.
- Standard library
os
: To get the API key from the environmentxml.etree.ElementTree
: We need to change the XML. Never do that by means of string manipulation, you will regret it - at least I have. Time and time again. For our purposes the built in library should suffice.
- Third party
requests
: HTTP for Humans for making HTTP requests.
import os from xml.etree import ElementTree as ET import requests # our configuration # from bfloc2alma.config import NS
3.2.2. Getting data from LoC
We use a function to get records from LoC. The function takes following arguments:
loc_id
: The ID of the record in LoC. Note that this is the bare number and as such is not unambiguous, as works and instances can have the same number and are differentiated in the URI.entity
: whether to fetch a work or an instance with the given IDcompact
: whether to get the full or the compact versionas_tree
: whether to return the result as XML string or asElementTree.Element
data structure for further handling.session
: if there is a session, multiple calls to the same endpoint perform much better. If we intend to get a lot of records, we can therefore pass a session to this function.
# get BIBFRAME from LoC def get_bibframe_from_loc(loc_id, entity="work", compact=True, as_tree=False, session=None): """Get a BIBFRAME work from LoC. Return the xml of the record. entity: "work" or "instance" If compact: get compact format. If as_tree: return the XML as ElementTree """ url = f"https://id.loc.gov/resources/{entity}s/{loc_id}{'.bibframe' if compact else ''}.rdf" # use session if available if session: response = session.get(url) else: response = requests.get(url) response.raise_for_status() if as_tree: return ET.fromstring(response.text) else: return response.text
None
3.2.3. Prepare BIBFRAME record for the Alma API
For a BIBFRAME record to be posted to Almas API it has to be wrapped in some XML. It’s a bad idea to do this by string manipulation, so we use the xml.etree.ElementTree
-API of Python.
# prep BIBFRAME record for Alma def prep_rec(bf_rec): """Wrap BIBFRAME to be posted to Almas API. <bib> <record_format>lcbf_work</record_format> <record> [BIBFRAME HERE ...] </record> </bib> """ NS = {"bf": "http://id.loc.gov/ontologies/bibframe/"} # bf_rec needs to be an ET.Element to be handled further if type(bf_rec) == str: bf_rec = ET.fromstring(bf_rec) elif type(bf_rec) != ET.Element: raise Error("bf_rec must be str or ET.Element!") # check which entity we have if bf_rec.find('bf:Work', NS) is not None: entity = "work" elif bf_rec.find('bf:Instance', NS) is not None: entity = "instance" else: raise Exception("Input is neither a work nor an instance!") # create XML tree bib = ET.Element('bib') record_format = ET.Element('record_format') record_format.text = f"lcbf_{entity}" bib.append(record_format) record = ET.Element("record") record.append(bf_rec) bib.append(record) return ET.tostring(bib)
3.2.4. Helpers
A small function to get the MMS-ID from an API response:
def get_mmsid(response): """Get the MMS ID from an Alma API response.""" response_tree = ET.fromstring(response.text) mms = response_tree.find('mms_id') return mms.text
4. Walkthrough with one Example
Our Example is “Weapons of Math Destruction” by Cathy O’Neil. The ID is “19016283”. You can obtain an identifier for your favorite book by searching for it at https://id.loc.gov/ and copying the Identifer from the “Identifier”-column of the search results.
Another one would be Mary Roach’s “Stiff”, “12983234”.
4.1. Getting the data from LoC
So, let’s get our work and instance:
work_xml = get_bibframe_from_loc("19016283", entity="work", compact=True) instance_xml = get_bibframe_from_loc("19016283", entity="instance", compact=True)
None
How does it look?
work_xml
<?xml version="1.0" encoding="utf-8"?> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <bf:Work rdf:about="http://id.loc.gov/resources/works/19016283" xmlns:bf="http://id.loc.gov/ontologies/bibframe/"> <bflc:aap xmlns:bflc="http://id.loc.gov/ontologies/bflc/">O'Neil, Cathy Weapons of math destruction</bflc:aap> <bflc:aap-normalized xmlns:bflc="http://id.loc.gov/ontologies/bflc/">o'neilcathyweaponsofmathdestruction</bflc:aap-normalized> <rdf:type rdf:resource="http://id.loc.gov/ontologies/bibframe/Text"/> <rdf:type rdf:resource="http://id.loc.gov/ontologies/bibframe/Monograph"/> <bf:language rdf:resource="http://id.loc.gov/vocabulary/languages/eng"/> <bf:supplementaryContent rdf:resource="http://id.loc.gov/vocabulary/msupplcont/bibliography"/> <bf:supplementaryContent rdf:resource="http://id.loc.gov/vocabulary/msupplcont/index"/> <bf:geographicCoverage rdf:resource="http://id.loc.gov/vocabulary/geographicAreas/n-us"/> <bf:classification> <bf:ClassificationLcc> <bf:classificationPortion>QA76.9.B45</bf:classificationPortion> <bf:itemPortion>O64 2016</bf:itemPortion> <bf:assigner rdf:resource="http://id.loc.gov/vocabulary/organizations/dlc"/> <bf:status rdf:resource="http://id.loc.gov/vocabulary/mstatus/uba"/> </bf:ClassificationLcc> </bf:classification> <bf:classification> <bf:ClassificationDdc> <bf:classificationPortion>005.7</bf:classificationPortion> <bf:source> <bf:Source> <bf:code>23</bf:code> </bf:Source> </bf:source> <bf:edition>full</bf:edition> <bf:assigner rdf:resource="http://id.loc.gov/vocabulary/organizations/dlc"/> </bf:ClassificationDdc> </bf:classification> <bf:contribution> <bf:Contribution> <rdf:type rdf:resource="http://id.loc.gov/ontologies/bibframe/PrimaryContribution"/> <bf:agent rdf:resource="http://id.loc.gov/rwo/agents/no2013123474"/> <bf:role rdf:resource="http://id.loc.gov/vocabulary/relators/aut"/> </bf:Contribution> </bf:contribution> <bf:title> <bf:Title> <bf:mainTitle>Weapons of math destruction</bf:mainTitle> </bf:Title> </bf:title> <bf:content rdf:resource="http://id.loc.gov/vocabulary/contentTypes/txt"/> <bf:subject> <bf:Topic> <rdf:type rdf:resource="http://www.loc.gov/mads/rdf/v1#ComplexSubject"/> <rdfs:label xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#">Big data--Social aspects--United States</rdfs:label> <madsrdf:authoritativeLabel xmlns:madsrdf="http://www.loc.gov/mads/rdf/v1#">Big data--Social aspects--United States</madsrdf:authoritativeLabel> <madsrdf:isMemberOfMADSScheme rdf:resource="http://id.loc.gov/authorities/subjects" xmlns:madsrdf="http://www.loc.gov/mads/rdf/v1#"/> <madsrdf:componentList rdf:parseType="Collection" xmlns:madsrdf="http://www.loc.gov/mads/rdf/v1#"> <madsrdf:Topic rdf:about="http://id.loc.gov/authorities/subjects/sh2012003227"/> <madsrdf:Topic rdf:about="http://id.loc.gov/authorities/subjects/sh00002758"/> <madsrdf:Geographic rdf:about="http://id.loc.gov/rwo/agents/n78095330-781"/> </madsrdf:componentList> <bflc:aap-normalized xmlns:bflc="http://id.loc.gov/ontologies/bflc/">bigdatasocialaspectsunitedstates</bflc:aap-normalized> <bf:source rdf:resource="http://id.loc.gov/authorities/subjects"/> </bf:Topic> </bf:subject> <bf:subject> <bf:Topic> <rdf:type rdf:resource="http://www.loc.gov/mads/rdf/v1#ComplexSubject"/> <rdfs:label xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#">Big data--Political aspects--United States</rdfs:label> <madsrdf:authoritativeLabel xmlns:madsrdf="http://www.loc.gov/mads/rdf/v1#">Big data--Political aspects--United States</madsrdf:authoritativeLabel> <madsrdf:isMemberOfMADSScheme rdf:resource="http://id.loc.gov/authorities/subjects" xmlns:madsrdf="http://www.loc.gov/mads/rdf/v1#"/> <madsrdf:componentList rdf:parseType="Collection" xmlns:madsrdf="http://www.loc.gov/mads/rdf/v1#"> <madsrdf:Topic rdf:about="http://id.loc.gov/authorities/subjects/sh2012003227"/> <madsrdf:Topic rdf:about="http://id.loc.gov/authorities/subjects/sh00005651"/> <madsrdf:Geographic rdf:about="http://id.loc.gov/rwo/agents/n78095330-781"/> </madsrdf:componentList> <bflc:aap-normalized xmlns:bflc="http://id.loc.gov/ontologies/bflc/">bigdatapoliticalaspectsunitedstates</bflc:aap-normalized> <bf:source rdf:resource="http://id.loc.gov/authorities/subjects"/> </bf:Topic> </bf:subject> <bf:subject> <bf:Topic> <rdf:type rdf:resource="http://www.loc.gov/mads/rdf/v1#ComplexSubject"/> <rdfs:label xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#">Social indicators--Mathematical models--Moral and ethical aspects</rdfs:label> <madsrdf:authoritativeLabel xmlns:madsrdf="http://www.loc.gov/mads/rdf/v1#">Social indicators--Mathematical models--Moral and ethical aspects</madsrdf:authoritativeLabel> <madsrdf:isMemberOfMADSScheme rdf:resource="http://id.loc.gov/authorities/subjects" xmlns:madsrdf="http://www.loc.gov/mads/rdf/v1#"/> <madsrdf:componentList rdf:parseType="Collection" xmlns:madsrdf="http://www.loc.gov/mads/rdf/v1#"> <madsrdf:Topic rdf:about="http://id.loc.gov/authorities/subjects/sh85123962"/> <madsrdf:Topic rdf:about="http://id.loc.gov/authorities/subjects/sh2002007921"/> <madsrdf:Topic rdf:about="http://id.loc.gov/authorities/subjects/sh00006099"/> </madsrdf:componentList> <bflc:aap-normalized xmlns:bflc="http://id.loc.gov/ontologies/bflc/">socialindicatorsmathematicalmodelsmoralandethicalaspects</bflc:aap-normalized> <bf:source rdf:resource="http://id.loc.gov/authorities/subjects"/> </bf:Topic> </bf:subject> <bf:subject rdf:resource="http://id.loc.gov/authorities/subjects/sh2008102152"/> <bf:subject rdf:resource="http://id.loc.gov/authorities/subjects/sh2009100039"/> <dcterms:isPartOf rdf:resource="http://id.loc.gov/resources/works" xmlns:dcterms="http://purl.org/dc/terms/"/> <bf:relation> <bf:Relation> <bf:relationship rdf:resource="http://id.loc.gov/vocabulary/relationship/otherphysicalformat"/> <bf:relationship rdf:resource="http://id.loc.gov/entities/relationships/onlineversion"/> <bf:associatedResource rdf:resource="http://id.loc.gov/resources/works/19044863"/> </bf:Relation> </bf:relation> <bf:hasInstance rdf:resource="http://id.loc.gov/resources/instances/19016283"/> <bf:adminMetadata> <bf:AdminMetadata> <bf:status rdf:resource="http://id.loc.gov/vocabulary/mstatus/n"/> <bf:date rdf:datatype="http://www.w3.org/2001/XMLSchema#date">2016-03-15</bf:date> <bf:agent rdf:resource="http://id.loc.gov/vocabulary/organizations/dlc"/> </bf:AdminMetadata> </bf:adminMetadata> <bf:adminMetadata> <bf:AdminMetadata> <bf:status rdf:resource="http://id.loc.gov/vocabulary/mstatus/c"/> <bf:date rdf:datatype="http://www.w3.org/2001/XMLSchema#dateTime">2019-05-16T11:05:36</bf:date> <bf:descriptionModifier rdf:resource="http://id.loc.gov/vocabulary/organizations/dlc"/> </bf:AdminMetadata> </bf:adminMetadata> <bf:adminMetadata> <bf:AdminMetadata> <bf:status rdf:resource="http://id.loc.gov/vocabulary/mstatus/c"/> <bf:agent rdf:resource="http://id.loc.gov/vocabulary/organizations/dlcmrc"/> <bf:generationProcess rdf:resource="https://github.com/lcnetdev/marc2bibframe2/releases/tag/v2.7.0"/> <bf:date rdf:datatype="http://www.w3.org/2001/XMLSchema#dateTime">2024-08-03T15:19:09.987793-04:00</bf:date> </bf:AdminMetadata> </bf:adminMetadata> <bf:adminMetadata> <bf:AdminMetadata> <bf:descriptionLevel rdf:resource="http://id.loc.gov/ontologies/bibframe-2-3-0/"/> <bflc:encodingLevel rdf:resource="http://id.loc.gov/vocabulary/menclvl/f" xmlns:bflc="http://id.loc.gov/ontologies/bflc/"/> <bf:descriptionConventions rdf:resource="http://id.loc.gov/vocabulary/descriptionConventions/isbd"/> <bf:identifiedBy> <bf:Local> <rdf:value>19016283</rdf:value> <bf:assigner rdf:resource="http://id.loc.gov/vocabulary/organizations/dlc"/> </bf:Local> </bf:identifiedBy> <lclocal:d906 xmlns:lclocal="http://id.loc.gov/ontologies/lclocal/">=906 $a 7 $b cbc $c orignew $d 1 $e ecip $f 20 $g y-gencatlg</lclocal:d906> <lclocal:d925 xmlns:lclocal="http://id.loc.gov/ontologies/lclocal/">=925 0 $a Acquire $b 1 shelf copy $x Sel/ddw, 2016-09-26</lclocal:d925> <lclocal:d955 xmlns:lclocal="http://id.loc.gov/ontologies/lclocal/">=955 $b rk14 2016-03-15 $c rk14 2016-03-15 telework to subj $d re23 2016-04-04 (telework) to Dewey $w xm09 2016-04-05 $a xn05 2016-09-21 1 copy rec'd., to CIP ver. $f rk05 2016-10-07 to CALM (telework) x-copy/discard to CALM $a hh12 2019-04-12 Copy On Order for Loan $a hh12 2019-05-16 Book picked up by C. Townsend, 4/22/2019</lclocal:d955> <bf:descriptionLanguage rdf:resource="http://id.loc.gov/vocabulary/languages/eng"/> <bf:descriptionConventions rdf:resource="http://id.loc.gov/vocabulary/descriptionConventions/rda"/> <bf:descriptionAuthentication rdf:resource="http://id.loc.gov/vocabulary/marcauthen/pcc"/> </bf:AdminMetadata> </bf:adminMetadata> </bf:Work> </rdf:RDF>
4.2. Creating BIBFRAME work and instance in Alma
Before posting something to Alma, we initiate a session. With that, all requests to the Alma API can share the same parameters (API-Key etc.). It’s much faster for multiple calls, too. The headers are the same as in the config above. We could have taken the ALMA_API_HEADERS
, but I repeat them here so we can see them.
# session for API calls to Alma session_alma = requests.Session() session_alma.headers.update({ "accept": "application/xml", "content-type": "application/xml", "validate": "false", "authorization": f"apikey {API_KEY}" })
Now we prepare the payload and post it to Alma:
prepd_work = prep_rec(work_xml) work_post_resp = session_alma.post(bib_api.format(mms_id=""), data=prepd_work)
Let’s look at the result:
work_post_resp.text
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <bib> <mms_id>97148831599003331</mms_id> <record_format>lc_bf_work</record_format> <linked_record_id/> <title>Weapons of math destruction</title> <author>O'Neil, Cathy</author> <holdings link="https://api-eu.hosted.exlibrisgroup.com/almaws/v1/bibs/97148831599003331/holdings"/> <created_by>API, development_PSB-OBV_rw</created_by> <created_date>2025-03-27Z</created_date> <last_modified_by>API, development_PSB-OBV_rw</last_modified_by> <last_modified_date>2025-03-27Z</last_modified_date> <suppress_from_publishing>true</suppress_from_publishing> <suppress_from_external_search>false</suppress_from_external_search> <suppress_from_metadoor>false</suppress_from_metadoor> <sync_with_oclc>NONE</sync_with_oclc> <sync_with_libraries_australia>NONE</sync_with_libraries_australia> <originating_system>43ACC_NETWORK</originating_system> <originating_system_id>19016283</originating_system_id> <brief_level desc="10">10</brief_level> <record> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <bf:Work xmlns:bf="http://id.loc.gov/ontologies/bibframe/" rdf:about="http://id.loc.gov/resources/works/19016283"> <bflc:aap xmlns:bflc="http://id.loc.gov/ontologies/bflc/">O'Neil, Cathy Weapons of math destruction</bflc:aap> <bflc:aap-normalized xmlns:bflc="http://id.loc.gov/ontologies/bflc/">o'neilcathyweaponsofmathdestruction</bflc:aap-normalized> <rdf:type rdf:resource="http://id.loc.gov/ontologies/bibframe/Text"/> <rdf:type rdf:resource="http://id.loc.gov/ontologies/bibframe/Monograph"/> <bf:language rdf:resource="http://id.loc.gov/vocabulary/languages/eng"/> <bf:supplementaryContent rdf:resource="http://id.loc.gov/vocabulary/msupplcont/bibliography"/> <bf:supplementaryContent rdf:resource="http://id.loc.gov/vocabulary/msupplcont/index"/> <bf:geographicCoverage rdf:resource="http://id.loc.gov/vocabulary/geographicAreas/n-us"/> <bf:classification> <bf:ClassificationLcc> <bf:classificationPortion>QA76.9.B45</bf:classificationPortion> <bf:itemPortion>O64 2016</bf:itemPortion> <bf:assigner rdf:resource="http://id.loc.gov/vocabulary/organizations/dlc"/> <bf:status rdf:resource="http://id.loc.gov/vocabulary/mstatus/uba"/> </bf:ClassificationLcc> </bf:classification> <bf:classification> <bf:ClassificationDdc> <bf:classificationPortion>005.7</bf:classificationPortion> <bf:source> <bf:Source> <bf:code>23</bf:code> </bf:Source> </bf:source> <bf:edition>full</bf:edition> <bf:assigner rdf:resource="http://id.loc.gov/vocabulary/organizations/dlc"/> </bf:ClassificationDdc> </bf:classification> <bf:contribution> <bf:Contribution> <rdf:type rdf:resource="http://id.loc.gov/ontologies/bibframe/PrimaryContribution"/> <bf:agent rdf:resource="http://id.loc.gov/rwo/agents/no2013123474"/> <bf:role rdf:resource="http://id.loc.gov/vocabulary/relators/aut"/> </bf:Contribution> </bf:contribution> <bf:title> <bf:Title> <bf:mainTitle>Weapons of math destruction</bf:mainTitle> </bf:Title> </bf:title> <bf:content rdf:resource="http://id.loc.gov/vocabulary/contentTypes/txt"/> <bf:subject> <bf:Topic> <rdf:type rdf:resource="http://www.loc.gov/mads/rdf/v1#ComplexSubject"/> <rdfs:label xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#">Big data--Social aspects--United States</rdfs:label> <madsrdf:authoritativeLabel xmlns:madsrdf="http://www.loc.gov/mads/rdf/v1#">Big data--Social aspects--United States</madsrdf:authoritativeLabel> <madsrdf:isMemberOfMADSScheme xmlns:madsrdf="http://www.loc.gov/mads/rdf/v1#" rdf:resource="http://id.loc.gov/authorities/subjects"/> <madsrdf:componentList xmlns:madsrdf="http://www.loc.gov/mads/rdf/v1#" rdf:parseType="Collection"> <madsrdf:Topic rdf:about="http://id.loc.gov/authorities/subjects/sh2012003227"/> <madsrdf:Topic rdf:about="http://id.loc.gov/authorities/subjects/sh00002758"/> <madsrdf:Geographic rdf:about="http://id.loc.gov/rwo/agents/n78095330-781"/> </madsrdf:componentList> <bflc:aap-normalized xmlns:bflc="http://id.loc.gov/ontologies/bflc/">bigdatasocialaspectsunitedstates</bflc:aap-normalized> <bf:source rdf:resource="http://id.loc.gov/authorities/subjects"/> </bf:Topic> </bf:subject> <bf:subject> <bf:Topic> <rdf:type rdf:resource="http://www.loc.gov/mads/rdf/v1#ComplexSubject"/> <rdfs:label xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#">Big data--Political aspects--United States</rdfs:label> <madsrdf:authoritativeLabel xmlns:madsrdf="http://www.loc.gov/mads/rdf/v1#">Big data--Political aspects--United States</madsrdf:authoritativeLabel> <madsrdf:isMemberOfMADSScheme xmlns:madsrdf="http://www.loc.gov/mads/rdf/v1#" rdf:resource="http://id.loc.gov/authorities/subjects"/> <madsrdf:componentList xmlns:madsrdf="http://www.loc.gov/mads/rdf/v1#" rdf:parseType="Collection"> <madsrdf:Topic rdf:about="http://id.loc.gov/authorities/subjects/sh2012003227"/> <madsrdf:Topic rdf:about="http://id.loc.gov/authorities/subjects/sh00005651"/> <madsrdf:Geographic rdf:about="http://id.loc.gov/rwo/agents/n78095330-781"/> </madsrdf:componentList> <bflc:aap-normalized xmlns:bflc="http://id.loc.gov/ontologies/bflc/">bigdatapoliticalaspectsunitedstates</bflc:aap-normalized> <bf:source rdf:resource="http://id.loc.gov/authorities/subjects"/> </bf:Topic> </bf:subject> <bf:subject> <bf:Topic> <rdf:type rdf:resource="http://www.loc.gov/mads/rdf/v1#ComplexSubject"/> <rdfs:label xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#">Social indicators--Mathematical models--Moral and ethical aspects</rdfs:label> <madsrdf:authoritativeLabel xmlns:madsrdf="http://www.loc.gov/mads/rdf/v1#">Social indicators--Mathematical models--Moral and ethical aspects</madsrdf:authoritativeLabel> <madsrdf:isMemberOfMADSScheme xmlns:madsrdf="http://www.loc.gov/mads/rdf/v1#" rdf:resource="http://id.loc.gov/authorities/subjects"/> <madsrdf:componentList xmlns:madsrdf="http://www.loc.gov/mads/rdf/v1#" rdf:parseType="Collection"> <madsrdf:Topic rdf:about="http://id.loc.gov/authorities/subjects/sh85123962"/> <madsrdf:Topic rdf:about="http://id.loc.gov/authorities/subjects/sh2002007921"/> <madsrdf:Topic rdf:about="http://id.loc.gov/authorities/subjects/sh00006099"/> </madsrdf:componentList> <bflc:aap-normalized xmlns:bflc="http://id.loc.gov/ontologies/bflc/">socialindicatorsmathematicalmodelsmoralandethicalaspects</bflc:aap-normalized> <bf:source rdf:resource="http://id.loc.gov/authorities/subjects"/> </bf:Topic> </bf:subject> <bf:subject rdf:resource="http://id.loc.gov/authorities/subjects/sh2008102152"/> <bf:subject rdf:resource="http://id.loc.gov/authorities/subjects/sh2009100039"/> <dcterms:isPartOf xmlns:dcterms="http://purl.org/dc/terms/" rdf:resource="http://id.loc.gov/resources/works"/> <bf:relation> <bf:Relation> <bf:relationship rdf:resource="http://id.loc.gov/vocabulary/relationship/otherphysicalformat"/> <bf:relationship rdf:resource="http://id.loc.gov/entities/relationships/onlineversion"/> <bf:associatedResource rdf:resource="http://id.loc.gov/resources/works/19044863"/> </bf:Relation> </bf:relation> <bf:hasInstance rdf:resource="http://id.loc.gov/resources/instances/19016283"/> <bf:adminMetadata> <bf:AdminMetadata> <bf:status rdf:resource="http://id.loc.gov/vocabulary/mstatus/n"/> <bf:date rdf:datatype="http://www.w3.org/2001/XMLSchema#date">2016-03-15</bf:date> <bf:agent rdf:resource="http://id.loc.gov/vocabulary/organizations/dlc"/> </bf:AdminMetadata> </bf:adminMetadata> <bf:adminMetadata> <bf:AdminMetadata> <bf:status rdf:resource="http://id.loc.gov/vocabulary/mstatus/c"/> <bf:date rdf:datatype="http://www.w3.org/2001/XMLSchema#dateTime">2019-05-16T11:05:36</bf:date> <bf:descriptionModifier rdf:resource="http://id.loc.gov/vocabulary/organizations/dlc"/> </bf:AdminMetadata> </bf:adminMetadata> <bf:adminMetadata> <bf:AdminMetadata> <bf:status rdf:resource="http://id.loc.gov/vocabulary/mstatus/c"/> <bf:agent rdf:resource="http://id.loc.gov/vocabulary/organizations/dlcmrc"/> <bf:generationProcess rdf:resource="https://github.com/lcnetdev/marc2bibframe2/releases/tag/v2.7.0"/> <bf:date rdf:datatype="http://www.w3.org/2001/XMLSchema#dateTime">2024-08-03T15:19:09.987793-04:00</bf:date> </bf:AdminMetadata> </bf:adminMetadata> <bf:adminMetadata> <bf:AdminMetadata> <bf:descriptionLevel rdf:resource="http://id.loc.gov/ontologies/bibframe-2-3-0/"/> <bflc:encodingLevel xmlns:bflc="http://id.loc.gov/ontologies/bflc/" rdf:resource="http://id.loc.gov/vocabulary/menclvl/f"/> <bf:descriptionConventions rdf:resource="http://id.loc.gov/vocabulary/descriptionConventions/isbd"/> <bf:identifiedBy> <bf:Local> <rdf:value>19016283</rdf:value> <bf:assigner rdf:resource="http://id.loc.gov/vocabulary/organizations/dlc"/> </bf:Local> </bf:identifiedBy> <lclocal:d906 xmlns:lclocal="http://id.loc.gov/ontologies/lclocal/">=906 $a 7 $b cbc $c orignew $d 1 $e ecip $f 20 $g y-gencatlg</lclocal:d906> <lclocal:d925 xmlns:lclocal="http://id.loc.gov/ontologies/lclocal/">=925 0 $a Acquire $b 1 shelf copy $x Sel/ddw, 2016-09-26</lclocal:d925> <lclocal:d955 xmlns:lclocal="http://id.loc.gov/ontologies/lclocal/">=955 $b rk14 2016-03-15 $c rk14 2016-03-15 telework to subj $d re23 2016-04-04 (telework) to Dewey $w xm09 2016-04-05 $a xn05 2016-09-21 1 copy rec'd., to CIP ver. $f rk05 2016-10-07 to CALM (telework) x-copy/discard to CALM $a hh12 2019-04-12 Copy On Order for Loan $a hh12 2019-05-16 Book picked up by C. Townsend, 4/22/2019</lclocal:d955> <bf:descriptionLanguage rdf:resource="http://id.loc.gov/vocabulary/languages/eng"/> <bf:descriptionConventions rdf:resource="http://id.loc.gov/vocabulary/descriptionConventions/rda"/> <bf:descriptionAuthentication rdf:resource="http://id.loc.gov/vocabulary/marcauthen/pcc"/> </bf:AdminMetadata> </bf:adminMetadata> <bf:sameAs rdf:about="https://eu02.alma.exlibrisgroup.com/bf/works/97148831599003331?env=sandbox"/> <bf:adminMetadata> <bf:AdminMetadata> <bf:identifiedBy> <bf:Local> <rdf:value>97148831599003331</rdf:value> </bf:Local> <bf:source>ALMA</bf:source> </bf:identifiedBy> </bf:AdminMetadata> </bf:adminMetadata> </bf:Work> </rdf:RDF> </record> </bib>
4.3. Parsing the responses from Alma for further processing
That worked! Maybe we want to do something with the Data. To be able to do that, we parse the response into an XML tree, so we can get information (for example the MMS-ID) out of it or manipulate the data.
work_tree = ET.fromstring(work_post_resp.text) work_mms = work_tree.find('mms_id') work_mms.text
Note that, while not obvious in this case, the argument to find
is an XPath-expression.
Now, let’s do that with the instance:
instance_post_resp = session_alma.post(bib_api.format(mms_id=""), data=prep_rec(instance_xml)) instance_mms = ET.fromstring(instance_post_resp.text).find('mms_id') instance_mms.text
4.4. Update
To update a record, we take the API response from earlier and make a change to it. We already have it as an ElementTree
in the variable work_tree
. For simplicity’s sake, we just change the text of an existing element:
# change the title work_tree.find('record//bf:title/bf:Title/bf:mainTitle', NS).text = "!!! *** CHANGED TITLE *** !!!"
The stars and exclamation marks are for better visibility, so I can spot it in the wall of text of XML output without line breaks.🔍🤓
Interestingly, Alma can’t digest it’s own output. Do you remember the <record_format>lcbf_work</record_format>
in the wrapper around our BIBFRAME? If you go back and take a close look, you will see, that the text in Alma’s response is a bit different: lc_bf_format
. I don’t know why this is, but if you put it into Alma, the record format is not recognized. There’s no error, Alma just saves an empty record. We don’t want that, so we have to fill in the correct text again:
work_tree.find("record_format").text = "lcbf_work"
To get the changed record back into Alma, we need to make a PUT request with the changed record as payload:
changed_work_put_resp = session_alma.put(bib_api.format(mms_id=work_mms.text), data=ET.tostring(work_tree))
4.5. Deleting the records again
Now, let’s delete the records, so we don’t have to look for new examples every time this runs.
Note that when one tries to delete the work before the instance, this will fail:
work_del_res = session_alma.delete(bib_api.format(mms_id=work_mms.text)) work_del_res.text
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <web_service_result xmlns="http://com/exlibris/urm/general/xmlbeans"> <errorsExist>true</errorsExist> <errorList> <error> <errorCode>10109</errorCode> <errorMessage>Work associated with an instance cannot be deleted.</errorMessage> <trackingId>E01-1102165359-2MLXS-AWAE273450733</trackingId> </error> </errorList> </web_service_result>
So we delete the instance first:
instance_del_res = session_alma.delete(bib_api.format(mms_id=instance_mms.text)) instance_del_res.status_code
As expected, Alma returns HTTP 204
, now for the work.
work_del_res = session_alma.delete(bib_api.format(mms_id=work_mms.text)) work_del_res.status_code
All good!
5. Simple CLI for importing multiple records
Here is a simple script which allows one to specify multiple identifiers on the command line to load into Alma. The invocation would be like this:
(.venv)$ python3 bfloc2alma.py 19016283 12983234
5.1. Imports
We import the usual suspects and our configuration and library code. It is assumed that your API key is stored in an environment variable API_KEY_PSB
. The argparse
library is used to create a simple command line interface.
#!/usr/bin/env python3 import argparse from sys import exit import requests from config import * # bad practice, import constants separately from lib import get_bibframe_from_loc, prep_rec, get_mmsid
5.2. Argument parsing
We want to be able to give the IDs on the command line, so we use argparse
to make a CLI.
parser = argparse.ArgumentParser() parser.add_argument("loc_ids", nargs="+", help="The LoC-IDs to be imported into Alma") parser.add_argument("-c", "--cleanup", help="Delete created records afterwards", action="store_true") args = parser.parse_args()
This is not only useful to get command line arguments, but gives us a nice help message too:
$ ./bfloc2alma.py -h usage: bfloc2alma.py [-h] [-c] loc_ids [loc_ids ...] positional arguments: loc_ids The LoC-IDs to be imported into Alma options: -h, --help show this help message and exit -c, --cleanup Delete created records afterwards
5.3. Getting data from LoC
Then we set up the session for posting to Alma:
session = requests.Session()
session.headers.update(ALMA_SESSION_HEADERS)
We could set up another session for getting the records from LoC and pass it to get_bibframe_from_loc
, but it’s not necessary.
Now, we are ready to get BIBFRAME from the LoC. To keep it simple, we put works and instances in the same list. get_bibframe_from_loc()
returns the XML-String of the BIBFRAME work or instance. So, loc_bf
will be a list of strings in the end.
# get the records from loc loc_bf = [] for loc_id in args.loc_ids: print(f"Getting bf work from loc: {loc_id}") bf_work = get_bibframe_from_loc(loc_id, "work") loc_bf.append(bf_work) print(f"Getting bf instance from loc: {loc_id}") bf_instance = get_bibframe_from_loc(loc_id, "instance") loc_bf.append(bf_instance)
5.4. Importing into Alma
Before beginning to put things into Alma, we initialize a list in which to put the MMS-IDs of the records. We will use this list to clean up afterwards, so we can use the same records multiple times for testing purposes.
# lists for the MMS-IDs for later use mmsids = []
To post the BIBFRAME works and instances to Alma, we iterate over the list of XML-strings we got earlier.
## prepare the XML and import it into alma for bf in loc_bf: post_res = session.post(bib_api.format(mms_id=""), prep_rec(bf)) # blow up if HTTP error try: post_res.raise_for_status() except Exception as error: print(post_res.text) continue mms = get_mmsid(post_res) mmsids.append(mms) entity = "work" if mms.startswith("97") else "instance" print(f"Imported {entity}: {mms}")
5.5. Cleanup after test runs
For test runs, we want to delete the records again. This is controlled by the --cleanup
flag on the command line. Work cannot be deleted if they have instances, so we need to reverse the list of MMS-IDs before iterating oder it.
if args.cleanup: # iterate over reversed list, so to delete the instances first mmsids.reverse() for mmsid in mmsids: print(f"deleting {mmsid}") del_res = session.delete(bib_api.format(mms_id=mmsid)) print(f" {del_res.status_code}")
5.6. Example execution
You can run the script with python3 bfloc2alma.py 19016283 12983234 --cleanup
. As we have added a shebang line, you can make the file executable on MacOS or Linux:
$ chmod +x bfloc2alma.py
After that you can run it like this:
./bfloc2alma.py 19016283 12983234 -c Getting bf work from loc: 19016283 Getting bf instance from loc: 19016283 Getting bf work from loc: 12983234 Getting bf instance from loc: 12983234 Imported work: 97148831596703331 Imported instance: 99148831596603331 Imported work: 97148831596503331 Imported instance: 99148831596403331 deleting 99148831596403331 204 deleting 97148831596503331 204 deleting 99148831596603331 204 deleting 97148831596703331 204