First import the OmekaAPIClient
class.
from omeka_s_tools.api import OmekaAPIClient
To initialise the OmekaAPIClient
you need to provide the base url of your Omeka instance's API.
omeka = OmekaAPIClient('http://timsherratt.org/collections/api')
This will let you access details of all public resources provided by your Omeka instance. To access private resources, or create new resources, you'll have to authenticate your API requests by provide a key_identity
and key_credential
. See Adding items for an example.
Getting resources
You can find a full list of Omeka's resources in the API documentation. You're most likely to be interested in 'items', but accessing 'properties' and 'resource_templates' can be useful when you're creating new resources.
You should be able to use get_resources
with any of the resource types, though some of the parameters will be different.
data = omeka.get_resources('items')
data['total_results']
assert isinstance(data['total_results'], int)
data = omeka.get_resources('resource_templates')
first_template = data['results'][0]
assert first_template['@type'] == 'o:ResourceTemplate'
data = omeka.get_resources('items')
random_item = random.choice(data['results'])
item_id = random_item['o:id']
# Get the same item using its id
item_from_id = omeka.get_resource_by_id(item_id, 'items')
# Check that they're the same
assert random_item == item_from_id
It can be difficult to remember all the available parameters and the differences between resource types, so I've created a number of more specialised methods that are really just wrappers around get_resources
.
newspaper_template = omeka.get_template_by_label('Newspaper')
assert newspaper_template['@type'] == 'o:ResourceTemplate'
assert newspaper_template['o:label'] == 'Newspaper'
template_from_list = random.choice(omeka.get_resources('resource_templates')['results'])
label = template_from_list['o:label']
# Get the template using label
template_from_label = omeka.get_template_by_label(label)
# Check they're both the same
assert template_from_list == template_from_label
prop = omeka.get_resource_by_term('schema:name')
prop
assert prop['@type'] == 'o:Property'
assert prop['o:term'] == 'schema:name'
prop = omeka.get_resource_from_vocab(
'name',
vocabulary_namespace_uri='http://schema.org/',
resource_type='properties')
prop
vocab = requests.get(prop['o:vocabulary']['@id'])
namespace_uri = vocab.json()['o:namespace_uri']
assert namespace_uri == 'http://schema.org/'
assert prop['o:local_name'] == 'name'
prop_id = omeka.get_property_id('schema:name')
prop_id
assert isinstance(prop_id, int)
prop = omeka.get_resource_by_id(prop_id, 'properties')
assert prop['o:term'] == 'schema:name'
items = omeka.filter_items_by_property(filter_property='schema:name', filter_value='wragge', filter_type='in')
assert len(items['results']) > 0
assert 'wragge' in items['results'][0]['schema:name'][0]['@value'].lower()
items['results'][0]['schema:name'][0]['@value']
items = omeka.search_items('wragge')
items['total_results']
assert items['total_results'] >= len(items['results'])
omeka_auth = OmekaAPIClient(
api_url='https://timsherratt.org/collections/api',
key_identity=os.getenv('KEY_IDENTITY'),
key_credential=os.getenv('KEY_CREDENTIAL')
)
test_item = {
'dcterms:title': [
{
'value': 'My first resource!'
}
]
}
payload = omeka_auth.prepare_item_payload(test_item)
payload
Each term in the payload should have values for property_id
and type
, and the property value should be included as either @id
or @value
depending on the data type.
first_property = list(payload.values())[0][0]
assert 'property_id' in first_property
assert 'type' in first_property
assert '@value' in first_property or '@id' in first_property
One of the powerful features of Omeka S is the ability to create resource templates that define a set of properties for a particular type of item. We can use templates to help create our payload, ensuring that the terms and data types we supply match those expected by the template. This is useful for identifying potential problems before we upload to Omeka, especially as Omeka will sometimes just fail silently.
Once you've created a payload, you can add paths to any media files that you want to be attached to the item.
test_newspaper_item = {
'schema:name': [
{
'value': 'Wragge!'
}
]
}
test_newspaper_payload = omeka_auth.prepare_item_payload_using_template(test_newspaper_item, 5)
test_newspaper_payload
assert test_newspaper_item['schema:name'][0]['value'] == test_newspaper_payload['schema:name'][0]['@value']
assert test_newspaper_payload['schema:name'][0]['type'] == 'literal'
payload_with_media = omeka_auth.add_media_to_payload({}, media_files=['media/nla.news-article226799674-24144902.jpg'])
assert json.loads(payload_with_media['data'][1])
new_item = omeka_auth.add_item(payload)
new_item
assert new_item['@type'] == 'o:Item'
assert test_item['dcterms:title'][0]['value'] == new_item['dcterms:title'][0]['@value']
You can also add an item to an existing item set by supplying the item_set_id
parameter.
random_item_set = random.choice(omeka_auth.get_resources('item_sets')['results'])
item_set_id = random_item_set['o:id']
# Create a new item using the item set id
new_item_in_item_set = new_item = omeka_auth.add_item(payload, item_set_id=item_set_id)
# Check that the ids match
assert new_item_in_item_set['o:item_set'][0]['o:id'] == item_set_id
data = omeka.get_resources('items')
random_item = random.choice(data['results'])
deleted_resource = omeka_auth.delete_resource(random_item['o:id'], 'items')
# The id of the deleted item should be None
assert deleted_resource['o:id'] is None
# The titles of the random and deleted items should be the same
assert deleted_resource['o:title'] == random_item['o:title']
# Trying to get the random item should now raise an HTTP 404 error
with pytest.raises(requests.exceptions.HTTPError):
omeka_auth.get_resource_by_id(random_item['o:id'], 'items')
data = omeka_auth.filter_items_by_property(filter_property='schema:name', filter_type='ex')
random_item = random.choice(data['results'])
# Original title
title = random_item['schema:name'][0]['@value']
# Copy and update the original item
new_item = deepcopy(random_item)
new_item['schema:name'][0]['@value'] = title + ' - UPDATED!'
# Update the item
updated_item = omeka_auth.update_resource(new_item, 'items')
# Display the updated title
updated_title = updated_item['schema:name'][0]['@value']
print(updated_title)
# The id of the original and upated items should be the same
assert random_item['o:id'] == updated_item['o:id']
# But the titles should be different
assert title != updated_item['schema:name'][0]['@value']
omeka_auth = OmekaAPIClient(
api_url='https://timsherratt.org/collections/api',
key_identity=os.getenv('KEY_IDENTITY'),
key_credential=os.getenv('KEY_CREDENTIAL')
)
Add a media file to an item without any additional metadata.
data = omeka_auth.get_resources('items')
item = random.choice(data['results'])
item_id = item['o:id']
# Add a new media file to the item
media_resource = omeka_auth.add_media_to_item(item_id, 'media/nla.news-article226799674-24144902.jpg')
# Check that the media resource is the expected type
assert media_resource['@type'] == 'o:Media'
# Check that the media resource is linked to the item
assert media_resource['o:item']['o:id'] == item_id
Include a title for a new media item by supplying a string value to the payload
parameter.
media_resource = omeka_auth.add_media_to_item(item_id, 'media/nla.news-article226799674-24144902.jpg', payload='This has a title')
# The title should be saved as dcterms:title
assert media_resource['dcterms:title'][0]['@value'] == 'This has a title'
Include more detailed metadata with a new media item by generating a payload and supplying it using the payload
parameter.
media_metadata = {
'schema:name': ['This is a different title'],
'schema:identifier': ['myidis3']
}
# Convert metadata into an Omeka payload
media_payload = omeka_auth.prepare_item_payload(media_metadata)
# Add a new media file with a metadata payload to the item
media_resource = omeka_auth.add_media_to_item(item_id, 'media/nla.news-article226799674-24144902.jpg', payload=media_payload)
assert media_resource['schema:name'][0]['@value'] == 'This is a different title'
Use a specific resource template with the new media item by using the template_id
parameter.
data = omeka_auth.get_resources('resource_templates')
template = random.choice(data['results'])
template_id = template['o:id']
# Add a new media file and assign it to a template using the `template_id` parameter
media_resource = omeka_auth.add_media_to_item(item_id, 'media/nla.news-article226799674-24144902.jpg', payload=media_payload, template_id=template_id)
# Check the new media object has the correct template id
assert media_resource['o:resource_template']['o:id'] == template_id
The Omeka Mapping module lets you associate an item with a location on a map. Once you have the module installed, you can use the Omeka API to add map markers to items.
data = omeka_auth.get_resources('items')
item = random.choice(data['results'])
item_id = item['o:id']
# Add a marker using coords
marker = omeka_auth.add_marker_to_item(item_id, coords=[151.209900, -33.865143])
assert marker['@type'] == 'o-module-mapping:Marker'
assert marker['o:item']['o:id'] == item_id
You can also read the map coordinates from the item itself. By default OmekaAPIClient.add_marker_to_item
will look for values in schema:longitude
and schema:latitude
, but you can specify the vocabulary terms that you want to look for.
Here we'll create a simple item with values for schema:longitude
and schema:latitude
.
test_place = {
'schema:name': [
{
'value': 'Australia'
}
],
'schema:longitude': [
{
'value': '151.209900'
}
],
'schema:latitude': [
{
'value': '-33.865143'
}
],
}
payload = omeka_auth.prepare_item_payload(test_place)
new_place = omeka_auth.add_item(payload)
Now we can just call OmekaAPIClient.add_marker_to_item
without any coordinates, and it will get them from schema:longitude
and schema:latitude
.
new_marker = omeka_auth.add_marker_to_item(new_place['o:id'])
assert new_marker['@type'] == 'o-module-mapping:Marker'
assert new_marker['o:item']['o:id'] == new_place['o:id']
assert new_marker['o-module-mapping:lat'] == -33.865143
Managing templates
Resource templates can be exported from one Omeka instance and imported into another. This is a really useful way of sharing and resuing data structures. However, exported templates can't be uploaded via the API without some modification, as they don't include Omeka numeric identifiers for referenced classes and properties. Before uploading them, we need to find the referenced classes and properties in the local Omeka instance, and insert the local identifiers into the payload.
Similarly, if custom vocabs are referenced in the exported template, they will need to be updated to include the vocab's identifier in the new Omeka instance. Of course, you'll need to make sure it's installed first.
In general, you want to make sure that the any vocabs and data types referenced by the exported templates will be available in the new Omeka instance. So before you upload any templates you should check that:
- referenced vocabularies are already installed
- modules used in defining data types are installed ( eg the Numeric Data Types module provides additional data types such as
Timestamp
for dates - any custom vocabularies (created using the Custom Vocab are installed, along with the module itself of course
- the Value Suggest module is installed if the template includes references to the controlled vocabs it provides
omeka_auth = OmekaAPIClient(
api_url='https://timsherratt.org/collections/api',
key_identity=os.getenv('KEY_IDENTITY'),
key_credential=os.getenv('KEY_CREDENTIAL')
)
template_payload = omeka_auth.prepare_template_payload('templates/Person.json')
# Check the label
assert template_payload['o:label'] == 'Person'
# Check that there is a list of properties
assert len(template_payload['o:resource_template_property']) > 0
existing_template = omeka_auth.get_template_by_label('Person')
# If there is we'll delete it
# Trying to upload a template with the same label as an existing template will generate an error
if existing_template:
deleted = omeka_auth.delete_resource(existing_template['o:id'], 'resource_templates')
# Now we can add the new template
template_payload = omeka_auth.prepare_template_payload('templates/Person.json')
new_template = omeka_auth.upload_template(template_payload)
# Result should be a 'ResourceTemplate
assert new_template['@type'] == 'o:ResourceTemplate'
# Label should be 'Person'
assert new_template['o:label'] == 'Person'
# It should have properties
assert len(new_template['o:resource_template_property']) > 0
data_types = [{'name': 'customvocab:200000', 'label': "Date qualifiers"}]
# Localise!
localised_data_types = omeka_auth.localise_custom_vocabs(data_types)
print(localised_data_types)
# Localised data type should start with 'customvocab'
assert localised_data_types[0].startswith('customvocab:')
# The localised customvocab id should not be the same as the supplied one
assert localised_data_types[0] != data_types[0]['name']
template = json.loads(Path('templates/Person.json').read_bytes())
person_class_id = omeka_auth.get_resource_by_term('schema:Person', 'resource_classes')['o:id']
# Get resource id
class_id = omeka_auth.get_template_class_id(template)
assert class_id == person_class_id
template = json.loads(Path('templates/Person.json').read_bytes())
# get the id of the schema:name property
name_prop_id = omeka_auth.get_resource_by_term('schema:name', 'properties')['o:id']
# Get id of template property used for title
prop_id = omeka_auth.get_template_property_id(template, 'o:title_property')
# The ids should be the same
assert prop_id == name_prop_id
id_url = omeka.format_resource_id(5, 'resource_templates')
assert id_url == {
'@id': 'http://timsherratt.org/collections/api/resource_templates/5',
'o:id': 5
}
prop_id = omeka_auth.get_property_id('schema:url')
prop_value = {'value': 'https://glam-workbench.net', 'type': 'uri'}
formatted_prop = omeka_auth.prepare_property_value(prop_value, prop_id)
formatted_prop
assert prop_id == formatted_prop['property_id']
assert 'type' in formatted_prop
assert formatted_prop['@id'] == 'https://glam-workbench.net'
template_id = omeka_auth.get_template_by_label('Newspaper')['o:id']
newspaper_properties = omeka_auth.get_template_properties(template_id)
assert 'schema:name' in newspaper_properties
In the example below I'm going to manually step through the process of adding a new item to Omeka using the API in order to demonstrate the methods available. But of course the point of using the API is to automate such processes -- joining together the individual steps so they can be embedded into your own systems or workflows. For a more detailed example that uploads Trove newspaper articles from a variety of sources, including Trove searches, Trove lists, and Zotero libraries, see the GLAM Workbench.
Let's suppose we want to add this newspaper article in Trove to our Omeka instance. To take best advantage of Omeka's linked data infrastructure, we'll actually create two resources -- one for the article, and one for the newspaper it was published in.
I've already created templates labelled 'Newspaper' and 'Newspaper article'.
Let's start with the newspaper. First we need to find out the numeric identifier Omeka is using for the Newspaper template. We can use OmekaAPIClient.get_template_by_label
to find out.
omeka_auth = OmekaAPIClient(
api_url='http://timsherratt.org/collections/api',
key_identity=os.getenv('KEY_IDENTITY'),
key_credential=os.getenv('KEY_CREDENTIAL')
)
newspaper_template_id = omeka_auth.get_template_by_label('Newspaper')['o:id']
newspaper_template_id
For convenience, we can get a summary of the properties used in the Newspaper template using OmekaAPIClient.get_template_properties
. This is useful if we want to check which properties are in use, and what data types are expected.
omeka_auth.get_template_properties(newspaper_template_id)
The first step in preparing our data for upload is to create a dictionary, using the required property terms as keys. In this case, we'll assign data about the newspaper to schema.name
and schema.url
.
newspaper = {
'schema.name': [
{
'type': 'literal',
'value': 'The Bendigo Independent (Vic. : 1891 - 1918)'
}
],
'schema:url': [
{
'type': 'literal',
'value': 'http://nla.gov.au/nla.news-title806'
}
]
}
Now we can use this data to create a payload for upload to Omeka. OmekaAPIClient.prepare_item_payload_using_template
will check our data against the Newspaper template and build the payload.
payload = omeka_auth.prepare_item_payload_using_template(newspaper, newspaper_template_id)
Whoops! It seems we have some problems with our data file! First of all we've used a full stop, rather than a colon in schema:name
. Second, we've said the data type of schema:url
is 'literal', but if we check the template properties we'll see it' should be 'uri'. Let's make these changes.
newspaper = {
'schema:name': [
{
'type': 'literal',
'value': 'The Bendigo Independent (Vic. : 1891 - 1918)'
}
],
'schema:url': [
{
'type': 'uri',
'value': 'http://nla.gov.au/nla.news-title806'
}
]
}
payload = omeka_auth.prepare_item_payload_using_template(newspaper, newspaper_template_id)
That seems better! Now we can examine the payload.
payload
Notice how the values have been reformatted? They should now be ready to upload to Omeka using OmekaAPIClient.add_item
. We'll supply both the payload and the newspaper template id.
newspaper_item = omeka_auth.add_item(payload, template_id=newspaper_template_id)
We can check the contents of newspaper_item
to make sure it's been added succesfully.
newspaper_item
Great! Now what about the article? Again let's start by having a look at the 'Newspaper article' template.
article_template_id = omeka_auth.get_template_by_label('Newspaper article')['o:id']
omeka_auth.get_template_properties(article_template_id)
This time we'll build our data file in stages.
article = {}
# Add value for schema:name
article['schema:name'] = ["MR WRAGGE'S PREDICTION. RENEWAL OF CYCLONE FORETOLD."]
You'll notice that this time I've only included the value of the property, not the data type. This is because OmekaAPIClient.prepare_item_payload_using_template
will insert default data types if they're not specified. The basic rules it uses if you don't supply a data type are:
- if the template defines a default data type, use that
- if 'literal' is in the template's list of expected data types, use that
- if 'literal' is not in the template's list of expected data types print a warning asking the user to specify a data type and drop the property from the payload
In the case above, the 'literal' data type will be assigned to the schema:name
value.
To create a link between the article and the newspaper it was published in, we can use schema.isPartOf
. If we look in the list of template properties we see that this property expects a data type of 'resource:item'.
'schema:isPartOf': {'property_id': 736, 'type': ['resource:item']}
The 'resource:item' data type means that the value will be an identifier pointing to another Omeka item. So we include the identifier of the newly-created newspaper record. Once again, the data type will be automatically added when we generate the payload.
article['schema:isPartOf'] = [newspaper_item['o:id']]
Next we'll add the publication date. To recognise dates as specific data types, you need to install the Numeric Data Types module. Once that's done, you can set the 'numeric:timestamp' data type as the default for any date fields in your template. In the list of properties from the newspaper article template, you can see:
'schema:datePublished': {'property_id': 928, 'type': ['numeric:timestamp']}
The date value itself is supplied in a standard ISO format -- 'YYYY-MM-DD'.
article['schema:datePublished'] = ['1918-03-14']
Finally we'll add a link to the article. Once again the template includes a default data type.
'schema:url': {'property_id': 393, 'type': ['uri']}
So we can just include the value.
article['schema:url'] = ['http://nla.gov.au/nla.news-article226799674']
Now that we've assembled the article metadata, we can generate a payload.
article_payload = omeka_auth.prepare_item_payload_using_template(article, article_template_id)
As you can see below, the default data types have been inserted into the payload, and some additional fields have been added to define the link to the newspaper item.
article_payload
We could now go ahead and upload the payload to Omeka, but what if we want to include media files? In this case I have a JPG image of the article I want to attach. All you need to do is supply a list of paths pointing to media files using the media_files
parameter. The list can just include the paths as strings, in which case the file name will be used as the media object's title. Alternatively, you can supply a list of dicts, each containing a path
and title
value.
# The paths can be strings or pathlib Paths
media_files = ['media/nla.news-article226799674-24144902.jpg']
# Include the media files when we upload the payload
article_item = omeka_auth.add_item(article_payload, media_files=media_files, template_id=article_template_id)
When we look at the uploaded item, we'll see that the media files have been processed and added to the record.
article_item