diff --git a/.gitignore b/.gitignore index 87230d5..d7b3982 100644 --- a/.gitignore +++ b/.gitignore @@ -141,3 +141,4 @@ checklink/cookies.txt # .gitconfig is now autogenerated .gitconfig +*.sqlite diff --git a/00_core.ipynb b/00_core.ipynb deleted file mode 100644 index 6c17de9..0000000 --- a/00_core.ipynb +++ /dev/null @@ -1,48 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# default_exp core" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# module name here\n", - "\n", - "> API details." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#hide\n", - "from nbdev.showdoc import *" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/LICENSE b/LICENSE index 261eeb9..c5f4635 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,21 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +MIT License + +Copyright (c) 2022 Tim Sherratt + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0fb8f0e --- /dev/null +++ b/Makefile @@ -0,0 +1,37 @@ +.ONESHELL: +SHELL := /bin/bash +SRC = $(wildcard ./*.ipynb) + +all: omeka_s_tools docs + +omeka_s_tools: $(SRC) + nbdev_build_lib + touch omeka_s_tools + +sync: + nbdev_update_lib + +docs_serve: docs + cd docs && bundle exec jekyll serve + +docs: $(SRC) + nbdev_build_docs + touch docs + +test: + nbdev_test_nbs + +release: pypi conda_release + nbdev_bump_version + +conda_release: + fastrelease_conda_package + +pypi: dist + twine upload --repository pypi dist/* + +dist: clean + python setup.py sdist bdist_wheel + +clean: + rm -rf dist \ No newline at end of file diff --git a/README.md b/README.md index 154894b..09566b1 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,24 @@ -# nbdev template +# Project name here +> Summary description here. -Use this template to more easily create your [nbdev](https://nbdev.fast.ai/) project. -_If you are using an older version of this template, and want to upgrade to the theme-based version, see [this helper script](https://gist.github.com/hamelsmu/977e82a23dcd8dcff9058079cb4a8f18) (more explanation of what this means is contained in the link to the script)_. +This file will become your README and also the index of your documentation. -## Troubleshooting Tips +## Install -- Make sure you are using the latest version of nbdev with `pip install -U nbdev` -- If you are using an older version of this template, see the instructions above on how to upgrade your template. -- It is important for you to spell the name of your user and repo correctly in `settings.ini` or the website will not have the correct address from which to source assets like CSS for your site. When in doubt, you can open your browser's developer console and see if there are any errors related to fetching assets for your website due to an incorrect URL generated by misspelled values from `settings.ini`. -- If you change the name of your repo, you have to make the appropriate changes in `settings.ini` -- After you make changes to `settings.ini`, run `nbdev_build_lib && nbdev_clean_nbs && nbdev_build_docs` to make sure all changes are propagated appropriately. +`pip install your_project_name` +## How to use -## Previewing Documents Locally +Fill me in please! Don't forget code examples: + +``` +1+1 +``` + + + + + 2 -It is often desirable to preview nbdev generated documentation locally before having it built and rendered by GitHub Pages. This requires you to run Jekyll locally, which requires installing Ruby and Jekyll. Instructions on how to install Jekyll are provided [on Jekyll's site](https://jekyllrb.com/). You can run the command `make docs_serve` from the root of your repo to serve the documentation locally after calling `nbdev_build_docs` to generate the docs. -In order to allow you to run Jekyll locally this project contains manifest files, called Gem files, that specify all Ruby dependencies for Jekyll & nbdev. **If you do not plan to preview documentation locally**, you can choose to delete `docs/Gemfile` and `docs/Gemfile.lock` from your nbdev project (for example, after creating a new repo from this template). diff --git a/api.ipynb b/api.ipynb new file mode 100644 index 0000000..7c0c1c7 --- /dev/null +++ b/api.ipynb @@ -0,0 +1,2191 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# default_exp api" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Omeka S API client\n", + "\n", + "> Tools to interact with the Omeka S REST API" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#hide\n", + "from nbdev.showdoc import *\n", + "import random\n", + "import os\n", + "%load_ext dotenv\n", + "%dotenv" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#export\n", + "import requests\n", + "import requests_cache\n", + "import json\n", + "from requests.adapters import HTTPAdapter\n", + "from requests.packages.urllib3.util.retry import Retry\n", + "from pathlib import Path\n", + "\n", + "class OmekaAPIClient(object):\n", + " \n", + " def __init__(self, api_url, key_identity=None, key_credential=None, use_cache=True):\n", + " self.api_url = api_url\n", + " self.params = {\n", + " 'key_identity': key_identity,\n", + " 'key_credential': key_credential\n", + " }\n", + " # Set up session and caching\n", + " if use_cache:\n", + " self.s = requests_cache.CachedSession()\n", + " self.s.cache.clear()\n", + " else:\n", + " self.s = requests.Session()\n", + " retries = Retry(total=10, backoff_factor=1, status_forcelist=[ 502, 503, 504, 524 ])\n", + " self.s.mount('http://', HTTPAdapter(max_retries=retries))\n", + " self.s.mount('https://', HTTPAdapter(max_retries=retries))\n", + " \n", + "\n", + " def format_resource_id(self, resource_id, resource_type):\n", + " '''\n", + " Generate a formatted id for the resource with the specified Omeka id number and resource type.\n", + " \n", + " Parameters:\n", + " * `resource_id` - numeric identifier used by Omeka for this resource\n", + " * `resource_type` - one of Omeka's resource types, eg: 'items', 'properties'\n", + " \n", + " Returns:\n", + " * a dict with values for '@id' and 'o:id'\n", + " '''\n", + " formatted_id = {\n", + " '@id': f'{self.api_url}/{resource_type}/{resource_id}',\n", + " 'o:id': resource_id\n", + " }\n", + " return formatted_id\n", + "\n", + " def get_resources(self, resource_type, **kwargs):\n", + " '''\n", + " Get a list of resources matching the supplied parameters.\n", + " This will return the first page of matching results. To retrieve additional pages, \n", + " you can supply the `page` parameter to move through the full result set.\n", + " \n", + " Parameters:\n", + " * `resource_type` - one of Omeka's resource types, eg: 'items', 'properties'\n", + " * there are many additional parameters you can supply as kwargs, see the Omeka documention\n", + " \n", + " Returns a dict with the following values:\n", + " * `total_results` - number of matching resources\n", + " * `results` - a list of dicts, each containing a JSON-LD formatted representation of a resource\n", + " '''\n", + " response = self.s.get(f'{self.api_url}/{resource_type}/', params=kwargs)\n", + " results = response.json()\n", + " return {'total_results': int(response.headers['Omeka-S-Total-Results']), 'results': results}\n", + " \n", + " def get_resource(self, resource_type, **kwargs):\n", + " '''\n", + " Get the first resource matching the supplied parameters.\n", + " \n", + " Parameters:\n", + " * `resource_type` - one of Omeka's resource types, eg: 'items', 'properties'\n", + " * there are many additional parameters you can supply as kwargs, see the Omeka documention\n", + " \n", + " Returns\n", + " * a dict containing a JSON-LD formatted representation of the resource\n", + " '''\n", + " \n", + " data = self.get_resources(resource_type, **kwargs)\n", + " try:\n", + " resource = data['results'][0]\n", + " except IndexError:\n", + " return\n", + " else:\n", + " return resource\n", + "\n", + " def get_resource_by_id(self, resource_id, resource_type='items'):\n", + " '''\n", + " Get a resource from its Omeka id.\n", + " \n", + " Parameters:\n", + " * `resource_id` - numeric identifier used by Omeka for this resource\n", + " * `resource_type` - one of Omeka's resource types, eg: 'items', 'properties'\n", + " \n", + " Returns\n", + " * a dict containing a JSON-LD formatted representation of the resource\n", + " '''\n", + " response = self.s.get(f'{self.api_url}/{resource_type}/{resource_id}')\n", + " return response.json()\n", + "\n", + " def get_template_by_label(self, label):\n", + " '''\n", + " Get a resource template from its Omeka label.\n", + " \n", + " Parameters:\n", + " * `label` - the name of the resource template in Omeka (eg. 'NewspaperArticle')\n", + " \n", + " Returns:\n", + " * dict containing representation of the template\n", + " '''\n", + " return self.get_resource('resource_templates', label=label)\n", + " \n", + " def get_resource_by_term(self, term, resource_type='properties'):\n", + " '''\n", + " Get the resource (property or class) associated with the suppied term.\n", + " \n", + " Parameters:\n", + " * `term` - property label qualified with vocabulary prefix (eg: 'schema:name')\n", + " \n", + " Returns:\n", + " * dict containing representation of the resource\n", + " '''\n", + " return self.get_resource(resource_type, term=term)\n", + " \n", + " def get_resource_from_vocab(self, local_name, vocabulary_namespace_uri='http://schema.org/', resource_type='properties'):\n", + " '''\n", + " Get the resource (property or class) associated with the suppied vocabulary and label.\n", + " \n", + " Parameters:\n", + " * `local_name` - label of the property or class\n", + " * `vocabulary_namespace_uri` - URI defining the vocab\n", + " \n", + " Returns:\n", + " * dict containing representation of the resource\n", + " '''\n", + " return self.get_resource(resource_type, local_name=local_name, vocabulary_namespace_uri=vocabulary_namespace_uri)\n", + " \n", + " def get_property_id(self, term):\n", + " '''\n", + " Get the numeric identifier associated with the supplied property term.\n", + " \n", + " Parameters:\n", + " * `term` - property label qualified with vocabulary prefix (eg: 'schema:name')\n", + " \n", + " Returns:\n", + " * numeric identifier\n", + " '''\n", + " resource = self.get_resource_by_term(term=term)\n", + " if resource:\n", + " return resource['o:id']\n", + "\n", + " def filter_items(self, params, **extra_filters):\n", + " for filter_type in ['resource_template_id', 'resource_class_id', 'item_set_id', 'is_public']:\n", + " filter_value = extra_filters.get(filter_type)\n", + " if filter_value:\n", + " params[filter_type] = filter_value\n", + " return params\n", + " \n", + " def filter_items_by_property(self, filter_property='schema:name', filter_value='', filter_type='eq', page=1, **extra_filters):\n", + " '''\n", + " Filter the list of items by searching for a value in a particular property.\n", + " Additional filters can also limit to items associated with particular templates, classes, or item sets.\n", + " \n", + " Parameters:\n", + " * `filter_property` - property term (eg: 'schema:name')\n", + " * `filter_value` - the value you want to find\n", + " * `filter_type` - how `filter_value` should be compared to the stored values (eg: 'eq')\n", + " * `page` - number of results page\n", + " \n", + " Additional parameters:\n", + " * `resource_template_id` - numeric identifier\n", + " * `resource_class_id` - numeric identifier\n", + " * `item_set_id` - numeric identifier\n", + " * `is_public` - boolean, True or False\n", + " \n", + " Returns a dict with the following values:\n", + " * `total_results` - number of matching resources\n", + " * `results` - a list of dicts, each containing a JSON-LD formatted representation of a resource\n", + " \n", + " '''\n", + " # We need to get the id of the property we're using\n", + " property_id = self.get_property_id(filter_property)\n", + " params = {\n", + " 'property[0][joiner]': 'and', # and / or joins multiple property searches\n", + " 'property[0][property]': property_id, # property id\n", + " 'property[0][type]': filter_type, # See above for options\n", + " 'property[0][text]': filter_value,\n", + " 'page': page\n", + " }\n", + " params = self.filter_items(params, **extra_filters)\n", + " # print(params)\n", + " data = self.get_resources('items', **params)\n", + " return data\n", + " \n", + " def search_items(self, query, search_type='fulltext_search', page=1, **extra_filters):\n", + " '''\n", + " Search for matching items.\n", + " Two search types are available:\n", + " * 'search` - looks for an exact match of the query in a property value\n", + " * 'fulltext_search` - looks for the occurance of the query anywhere\n", + " \n", + " Parameters:\n", + " * `query` - the text you want to search for\n", + " * `search_type` - one of 'fulltext_search' or 'search'\n", + " * `page` - number of results page\n", + " \n", + " Additional parameters:\n", + " * `resource_template_id` - numeric identifier\n", + " * `resource_class_id` - numeric identifier\n", + " * `item_set_id` - numeric identifier\n", + " * `is_public` - boolean, True or False\n", + " \n", + " Returns a dict with the following values:\n", + " * `total_results` - number of matching resources\n", + " * `results` - a list of dicts, each containing a JSON-LD formatted representation of a resource\n", + " '''\n", + " params = {'page': page}\n", + " params[search_type] = query\n", + " params = self.filter_items(params, **extra_filters)\n", + " return self.get_resources('items', **params)\n", + "\n", + " def get_template_properties(self, template_id):\n", + " '''\n", + " List properties used by the specified template.\n", + " \n", + " The resource template objects returned by the API don't include property terms.\n", + " This function gets the additional details, and organises the properties in a dictionary, \n", + " organised by term. This makes it easy to check if a particular term is used by a template.\n", + " \n", + " Parameters:\n", + " * `template_id` - numeric identifier for a template\n", + " \n", + " Returns:\n", + " * a dict organised by property terms, with values for `property_id` and `type`\n", + " '''\n", + " properties = {}\n", + " template = self.get_resource_by_id(template_id, 'resource_templates')\n", + " for prop in template['o:resource_template_property']:\n", + " prop_url = prop['o:property']['@id']\n", + " # The resource template doesn't include property terms, so we have to go to the property data\n", + " response = self.s.get(prop_url)\n", + " data = response.json()\n", + " # Use default data types if they're not defined in the resource template\n", + " data_type = ['literal', 'uri', 'resource:item'] if prop['o:data_type'] == [] else prop['o:data_type']\n", + " properties[data['o:term']] = {'property_id': data['o:id'], 'type': data_type}\n", + " return properties\n", + " \n", + " def prepare_property_value(self, value, property_id):\n", + " '''\n", + " Formats a property value according to its datatype as expected by Omeka. \n", + " The formatted value can be used in a payload to create a new item.\n", + " \n", + " Parameters:\n", + " * `value` - a dict containing a `value` and (optionally) a `type`\n", + " * `property_id` - the numeric identifier of the property\n", + " \n", + " Note that is no `type` is supplied, 'literal' will be used by default.\n", + " \n", + " Returns:\n", + " * a dict with values for `property_id`, `type`, and either `@id` or `@value`.\n", + " '''\n", + " if not isinstance(value, dict):\n", + " value = {'value': value}\n", + " \n", + " try:\n", + " data_type = value['type']\n", + " except KeyError:\n", + " data_type = 'literal'\n", + " \n", + " property_value = {\n", + " 'property_id': property_id,\n", + " 'type': data_type\n", + " }\n", + " \n", + " if data_type == 'resource:item':\n", + " property_value['@id'] = f'{self.api_url}/items/{value[\"value\"]}'\n", + " property_value['value_resource_id'] = value['value']\n", + " property_value['value_resource_name'] = 'items'\n", + " elif data_type == 'uri':\n", + " property_value['@id'] = value['value']\n", + " else:\n", + " property_value['@value'] = value['value']\n", + " return property_value\n", + "\n", + " def add_item(self, payload, media_files=None, template_id=None, class_id=None, item_set_id=None):\n", + " '''\n", + " Create a new item from the supplied payload, optionally uploading attached media files.\n", + " \n", + " Parameters:\n", + " * `payload` - a dict generated by `prepare_item_payload()` or `prepare_item_payload_using_template()`\n", + " * `media_files` - a list of paths pointing to media files, or a list of dicts with `path` and `title` values\n", + " * `template_id` - internal Omeka identifier of a resource template you want to attach to this item\n", + " * `class_id` - internal Omeka identifier of a resource class you want to attach to this item\n", + " * `item_set_id` - internal Omeka identifier for an item set you want to add this item to\n", + " \n", + " Returns:\n", + " * a dict providing the JSON-LD representation of the new item from Omeka\n", + " '''\n", + " if template_id:\n", + " payload['o:resource_template'] = self.format_resource_id(template_id, 'resource_templates')\n", + " if class_id:\n", + " payload['o:resource_class'] = self.format_resource_id(class_id, 'resource_classes')\n", + " if item_set_id:\n", + " payload['o:item_set'] = self.format_resource_id(template_id, 'item_sets')\n", + " if media_files:\n", + " files = self.add_media_to_payload(payload, media_files)\n", + " response = self.s.post(f'{self.api_url}/items', files=files, params=self.params)\n", + " else:\n", + " response = self.s.post(f'{self.api_url}/items', json=payload, params=self.params)\n", + " return response.json()\n", + " \n", + " def prepare_item_payload(self, terms):\n", + " '''\n", + " Prepare an item payload, ready for upload.\n", + " \n", + " Parameters:\n", + " * `terms`: a dict of terms, values, and (optionally) data types\n", + " \n", + " Returns:\n", + " * the payload dict\n", + " '''\n", + " payload = {}\n", + " for term, values in terms.items():\n", + " # Get the property id of the supplied term\n", + " try:\n", + " property_id = self.get_property_id(term)\n", + " except IndexError:\n", + " print(f'Term \"{term}\" not found')\n", + " else:\n", + " payload[term] = []\n", + " for value in values:\n", + " # Add a value formatted according to the data type\n", + " payload[term].append(self.prepare_property_value(value, property_id))\n", + " return payload\n", + " \n", + " def prepare_item_payload_using_template(self, terms, template_id):\n", + " '''\n", + " Prepare an item payload, checking the supplied terms and values against the specified template.\n", + " Note:\n", + " * terms that are not in the template will generate a warning and be dropped from the payload\n", + " * data types that don't match the template definitions will generate a warning and the term will be dropped from the payload\n", + " * if no data type is supplied, a type that conforms with the template definition will be used\n", + " \n", + " Parameters:\n", + " * `terms`: a dict of terms, values, and (optionally) data types\n", + " * `template_id`: Omeka's internal numeric identifier for the template\n", + " \n", + " Returns:\n", + " * the payload dict\n", + " '''\n", + " template_properties = self.get_template_properties(template_id)\n", + " payload = {}\n", + " for term, values in terms.items():\n", + " if term in template_properties:\n", + " property_details = template_properties[term]\n", + " payload[term] = []\n", + " for value in values:\n", + " if not isinstance(value, dict):\n", + " value = {'value': value}\n", + " # The supplied data type doesn't match the template\n", + " if 'type' in value and value['type'] not in property_details['type']:\n", + " print(f'Data type \"{value[\"type\"]}\" for term \"{term}\" not allowed by template')\n", + " break\n", + " elif 'type' not in value:\n", + " # Use default datatype from template if none is supplied\n", + " if len(property_details['type']) == 1:\n", + " value['type'] = property_details['type'][0]\n", + " # Use literal if allowed by template and data type not supplied\n", + " elif 'literal' in property_details['type']:\n", + " value['type'] = 'literal'\n", + " # Don't know what data type to use\n", + " else:\n", + " print(f'Specify data type for term \"{term}\"')\n", + " break\n", + " # Add a value formatted according to the data type\n", + " payload[term].append(self.prepare_property_value(value, property_details['property_id']))\n", + " # The supplied term is not in the template\n", + " else:\n", + " print(f'Term {term} not in template') \n", + " return payload\n", + " \n", + " def add_media_to_payload(self, payload, media_files):\n", + " '''\n", + " Add media files to the item payload.\n", + " \n", + " Parameters:\n", + " * `payload` - the payload dict to be modified\n", + " * `media_files` - media files to be uploaded\n", + " \n", + " The value of `media_files` can be either:\n", + " * a list of paths to the image/media files (filename is used as title)\n", + " * a list of dicts, each containing `title`, and `path` values\n", + " \n", + " Returns: \n", + " * the modified payload dict\n", + " '''\n", + " payload['o:media'] = []\n", + " files = {}\n", + " for index, media_file in enumerate(media_files):\n", + " if isinstance(media_file, dict):\n", + " title = media_file['title']\n", + " path = media_file['path']\n", + " else:\n", + " title = media_file[:-4]\n", + " path = media_file\n", + " payload['o:media'].append({'o:ingester': 'upload', 'file_index': str(index), 'o:item': {}, 'dcterms:title': [{'property_id': 1, '@value': title, 'type': 'literal'}]})\n", + " files[f'file[{index}]'] = Path(path).read_bytes()\n", + " files['data'] = (None, json.dumps(payload), 'application/json')\n", + " return files" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To initialise the `OmekaAPIClient` you need to provide the base url of your Omeka instance's API." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "omeka = OmekaAPIClient('http://timsherratt.org/collections/api')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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](#Adding-items) for an example." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Getting resources\n", + "\n", + "You can find a [full list of Omeka's resources](https://omeka.org/s/docs/developer/api/#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.\n", + "\n", + "You should be able to use `get_resources` with any of the resource types, though some of the parameters will be different. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "

OmekaAPIClient.get_resources[source]

\n", + "\n", + "> OmekaAPIClient.get_resources(**`resource_type`**, **\\*\\*`kwargs`**)\n", + "\n", + "Get a list of resources matching the supplied parameters.\n", + "This will return the first page of matching results. To retrieve additional pages, \n", + "you can supply the `page` parameter to move through the full result set.\n", + "\n", + "Parameters:\n", + "* `resource_type` - one of Omeka's resource types, eg: 'items', 'properties'\n", + "* there are many additional parameters you can supply as kwargs, see the Omeka documention\n", + "\n", + "Returns a dict with the following values:\n", + "* `total_results` - number of matching resources\n", + "* `results` - a list of dicts, each containing a JSON-LD formatted representation of a resource" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "show_doc(OmekaAPIClient.get_resources)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "46" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "data = omeka.get_resources('items')\n", + "data['total_results']" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "assert isinstance(data['total_results'], int)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Get a list of available resource templates\n", + "data = omeka.get_resources('resource_templates')\n", + "first_template = data['results'][0]\n", + "assert first_template['@type'] == 'o:ResourceTemplate'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "

OmekaAPIClient.get_resource_by_id[source]

\n", + "\n", + "> OmekaAPIClient.get_resource_by_id(**`resource_id`**, **`resource_type`**=*`'items'`*)\n", + "\n", + "Get a resource from its Omeka id.\n", + "\n", + "Parameters:\n", + "* `resource_id` - numeric identifier used by Omeka for this resource\n", + "* `resource_type` - one of Omeka's resource types, eg: 'items', 'properties'\n", + "\n", + "Returns\n", + "* a dict containing a JSON-LD formatted representation of the resource" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "show_doc(OmekaAPIClient.get_resource_by_id)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Get a random item from a list of items\n", + "data = omeka.get_resources('items')\n", + "random_item = random.choice(data['results'])\n", + "item_id = random_item['o:id']\n", + "\n", + "# Get the same item using its id\n", + "item_from_id = omeka.get_resource_by_id(item_id, 'items')\n", + "\n", + "# Check that they're the same\n", + "assert random_item == item_from_id" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "

OmekaAPIClient.get_template_by_label[source]

\n", + "\n", + "> OmekaAPIClient.get_template_by_label(**`label`**)\n", + "\n", + "Get a resource template from its Omeka label.\n", + "\n", + "Parameters:\n", + "* `label` - the name of the resource template in Omeka (eg. 'NewspaperArticle')\n", + "\n", + "Returns:\n", + "* dict containing representation of the template" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "show_doc(OmekaAPIClient.get_template_by_label)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "newspaper_template = omeka.get_template_by_label('Newspaper')\n", + "\n", + "assert newspaper_template['@type'] == 'o:ResourceTemplate'\n", + "assert newspaper_template['o:label'] == 'Newspaper'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Get a random template from list\n", + "template_from_list = random.choice(omeka.get_resources('resource_templates')['results'])\n", + "label = template_from_list['o:label']\n", + "\n", + "# Get the template using label\n", + "template_from_label = omeka.get_template_by_label(label)\n", + "\n", + "# Check they're both the same\n", + "assert template_from_list == template_from_label" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "

OmekaAPIClient.get_resource_by_term[source]

\n", + "\n", + "> OmekaAPIClient.get_resource_by_term(**`term`**, **`resource_type`**=*`'properties'`*)\n", + "\n", + "Get the resource (property or class) associated with the suppied term.\n", + "\n", + "Parameters:\n", + "* `term` - property label qualified with vocabulary prefix (eg: 'schema:name')\n", + "\n", + "Returns:\n", + "* dict containing representation of the resource" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "show_doc(OmekaAPIClient.get_resource_by_term)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'@context': 'http://timsherratt.org/collections/api-context',\n", + " '@id': 'http://timsherratt.org/collections/api/properties/1116',\n", + " '@type': 'o:Property',\n", + " 'o:id': 1116,\n", + " 'o:local_name': 'name',\n", + " 'o:label': 'name',\n", + " 'o:comment': 'The name of the item.',\n", + " 'o:term': 'schema:name',\n", + " 'o:vocabulary': {'@id': 'http://timsherratt.org/collections/api/vocabularies/5',\n", + " 'o:id': 5}}" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "prop = omeka.get_resource_by_term('schema:name')\n", + "prop" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "assert prop['@type'] == 'o:Property'\n", + "assert prop['o:term'] == 'schema:name'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "

OmekaAPIClient.get_resource_from_vocab[source]

\n", + "\n", + "> OmekaAPIClient.get_resource_from_vocab(**`local_name`**, **`vocabulary_namespace_uri`**=*`'http://schema.org/'`*, **`resource_type`**=*`'properties'`*)\n", + "\n", + "Get the resource (property or class) associated with the suppied vocabulary and label.\n", + "\n", + "Parameters:\n", + "* `local_name` - label of the property or class\n", + "* `vocabulary_namespace_uri` - URI defining the vocab\n", + "\n", + "Returns:\n", + "* dict containing representation of the resource" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "show_doc(OmekaAPIClient.get_resource_from_vocab)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'@context': 'http://timsherratt.org/collections/api-context',\n", + " '@id': 'http://timsherratt.org/collections/api/properties/1116',\n", + " '@type': 'o:Property',\n", + " 'o:id': 1116,\n", + " 'o:local_name': 'name',\n", + " 'o:label': 'name',\n", + " 'o:comment': 'The name of the item.',\n", + " 'o:term': 'schema:name',\n", + " 'o:vocabulary': {'@id': 'http://timsherratt.org/collections/api/vocabularies/5',\n", + " 'o:id': 5}}" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "prop = omeka.get_resource_from_vocab(\n", + " 'name', \n", + " vocabulary_namespace_uri='http://schema.org/',\n", + " resource_type='properties')\n", + "prop" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "vocab = requests.get(prop['o:vocabulary']['@id'])\n", + "namespace_uri = vocab.json()['o:namespace_uri']\n", + "\n", + "assert namespace_uri == 'http://schema.org/'\n", + "assert prop['o:local_name'] == 'name'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "

OmekaAPIClient.get_property_id[source]

\n", + "\n", + "> OmekaAPIClient.get_property_id(**`term`**)\n", + "\n", + "Get the numeric identifier associated with the supplied property term.\n", + "\n", + "Parameters:\n", + "* `term` - property label qualified with vocabulary prefix (eg: 'schema:name')\n", + "\n", + "Returns:\n", + "* numeric identifier" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "show_doc(OmekaAPIClient.get_property_id)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1116" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "prop_id = omeka.get_property_id('schema:name')\n", + "prop_id" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "assert isinstance(prop_id, int)\n", + "\n", + "prop = omeka.get_resource_by_id(prop_id, 'properties')\n", + "assert prop['o:term'] == 'schema:name'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "

OmekaAPIClient.filter_items_by_property[source]

\n", + "\n", + "> OmekaAPIClient.filter_items_by_property(**`filter_property`**=*`'schema:name'`*, **`filter_value`**=*`''`*, **`filter_type`**=*`'eq'`*, **`page`**=*`1`*, **\\*\\*`extra_filters`**)\n", + "\n", + "Filter the list of items by searching for a value in a particular property.\n", + "Additional filters can also limit to items associated with particular templates, classes, or item sets.\n", + "\n", + "Parameters:\n", + "* `filter_property` - property term (eg: 'schema:name')\n", + "* `filter_value` - the value you want to find\n", + "* `filter_type` - how `filter_value` should be compared to the stored values (eg: 'eq')\n", + "* `page` - number of results page\n", + "\n", + "Additional parameters:\n", + "* `resource_template_id` - numeric identifier\n", + "* `resource_class_id` - numeric identifier\n", + "* `item_set_id` - numeric identifier\n", + "* `is_public` - boolean, True or False\n", + "\n", + "Returns a dict with the following values:\n", + "* `total_results` - number of matching resources\n", + "* `results` - a list of dicts, each containing a JSON-LD formatted representation of a resource" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "show_doc(OmekaAPIClient.filter_items_by_property)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "items = omeka.filter_items_by_property(filter_property='schema:name', filter_value='WRAGGE.')\n", + "\n", + "assert len(items['results']) > 0\n", + "assert items['results'][0]['schema:name'][0]['@value'] == 'WRAGGE.'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "

OmekaAPIClient.search_items[source]

\n", + "\n", + "> OmekaAPIClient.search_items(**`query`**, **`search_type`**=*`'fulltext_search'`*, **`page`**=*`1`*, **\\*\\*`extra_filters`**)\n", + "\n", + "Search for matching items.\n", + "Two search types are available:\n", + "* 'search` - looks for an exact match of the query in a property value\n", + "* 'fulltext_search` - looks for the occurance of the query anywhere\n", + "\n", + "Parameters:\n", + "* `query` - the text you want to search for\n", + "* `search_type` - one of 'fulltext_search' or 'search'\n", + "* `page` - number of results page\n", + "\n", + "Additional parameters:\n", + "* `resource_template_id` - numeric identifier\n", + "* `resource_class_id` - numeric identifier\n", + "* `item_set_id` - numeric identifier\n", + "* `is_public` - boolean, True or False\n", + "\n", + "Returns a dict with the following values:\n", + "* `total_results` - number of matching resources\n", + "* `results` - a list of dicts, each containing a JSON-LD formatted representation of a resource" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "show_doc(OmekaAPIClient.search_items)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "6" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "items = omeka.search_items('wragge')\n", + "items['total_results']" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "assert items['total_results'] >= len(items['results'])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Adding items\n", + "\n", + "To add resources to Omeka you need authenticate your API requests by supplying `key_identity` and `key_credential` tokens as parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "omeka_auth = OmekaAPIClient(\n", + " api_url='https://timsherratt.org/collections/api',\n", + " key_identity=os.getenv('KEY_IDENTITY'), \n", + " key_credential=os.getenv('KEY_CREDENTIAL')\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There are two stages in creating a new item:\n", + "\n", + "* prepare a payload containing the item data in the format expected by Omeka\n", + "* upload the payload to Omeka\n", + "\n", + "### Prepare an item payload" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "

OmekaAPIClient.prepare_item_payload[source]

\n", + "\n", + "> OmekaAPIClient.prepare_item_payload(**`terms`**)\n", + "\n", + "Prepare an item payload, ready for upload.\n", + "\n", + "Parameters:\n", + "* `terms`: a dict of terms, values, and (optionally) data types\n", + "\n", + "Returns:\n", + "* the payload dict" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "show_doc(OmekaAPIClient.prepare_item_payload)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "test_item = {\n", + " 'dcterms:title': [\n", + " {\n", + " 'value': 'My first resource!'\n", + " }\n", + " ]\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'dcterms:title': [{'property_id': 1,\n", + " 'type': 'literal',\n", + " '@value': 'My first resource!'}]}" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "payload = omeka_auth.prepare_item_payload(test_item)\n", + "payload" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "first_property = list(payload.values())[0][0]\n", + "\n", + "assert 'property_id' in first_property\n", + "assert 'type' in first_property\n", + "assert '@value' in first_property or '@id' in first_property" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "

OmekaAPIClient.prepare_item_payload_using_template[source]

\n", + "\n", + "> OmekaAPIClient.prepare_item_payload_using_template(**`terms`**, **`template_id`**)\n", + "\n", + "Prepare an item payload, checking the supplied terms and values against the specified template.\n", + "Note:\n", + "* terms that are not in the template will generate a warning and be dropped from the payload\n", + "* data types that don't match the template definitions will generate a warning and the term will be dropped from the payload\n", + "* if no data type is supplied, a type that conforms with the template definition will be used\n", + "\n", + "Parameters:\n", + "* `terms`: a dict of terms, values, and (optionally) data types\n", + "* `template_id`: Omeka's internal numeric identifier for the template\n", + "\n", + "Returns:\n", + "* the payload dict" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "show_doc(OmekaAPIClient.prepare_item_payload_using_template)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once you've created a payload, you can add paths to any media files that you want to be attached to the item." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "test_newspaper_item = {\n", + " 'schema:name': [\n", + " {\n", + " 'value': 'Wragge!'\n", + " }\n", + " ]\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'schema:name': [{'property_id': 1116,\n", + " 'type': 'literal',\n", + " '@value': 'Wragge!'}]}" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "test_newspaper_payload = omeka_auth.prepare_item_payload_using_template(test_newspaper_item, 5)\n", + "test_newspaper_payload" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "assert test_newspaper_item['schema:name'][0]['value'] == test_newspaper_payload['schema:name'][0]['@value']\n", + "assert test_newspaper_payload['schema:name'][0]['type'] == 'literal'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "

OmekaAPIClient.add_media_to_payload[source]

\n", + "\n", + "> OmekaAPIClient.add_media_to_payload(**`payload`**, **`media_files`**)\n", + "\n", + "Add media files to the item payload.\n", + "\n", + "Parameters:\n", + "* `payload` - the payload dict to be modified\n", + "* `media_files` - media files to be uploaded\n", + "\n", + "The value of `media_files` can be either:\n", + "* a list of paths to the image/media files (filename is used as title)\n", + "* a list of dicts, each containing `title`, and `path` values\n", + "\n", + "Returns: \n", + "* the modified payload dict" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "show_doc(OmekaAPIClient.add_media_to_payload)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "payload_with_media = omeka_auth.add_media_to_payload({}, media_files=['media/nla.news-article226799674-24144902.jpg'])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "assert json.loads(payload_with_media['data'][1])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Upload the payload to Omeka and create a new item" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "

OmekaAPIClient.add_item[source]

\n", + "\n", + "> OmekaAPIClient.add_item(**`payload`**, **`media_files`**=*`None`*, **`template_id`**=*`None`*, **`class_id`**=*`None`*, **`item_set_id`**=*`None`*)\n", + "\n", + "Create a new item from the supplied payload, optionally uploading attached media files.\n", + "\n", + "Parameters:\n", + "* `payload` - a dict generated by `prepare_item_payload()` or `prepare_item_payload_using_template()`\n", + "* `media_files` - a list of paths pointing to media files, or a list of dicts with `path` and `title` values\n", + "* `template_id` - internal Omeka identifier of a resource template you want to attach to this item\n", + "* `class_id` - internal Omeka identifier of a resource class you want to attach to this item\n", + "* `item_set_id` - internal Omeka identifier for an item set you want to add this item to\n", + "\n", + "Returns:\n", + "* a dict providing the JSON-LD representation of the new item from Omeka" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "show_doc(OmekaAPIClient.add_item)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Add a simple item" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'@context': 'https://timsherratt.org/collections/api-context',\n", + " '@id': 'https://timsherratt.org/collections/api/items/729',\n", + " '@type': 'o:Item',\n", + " 'o:id': 729,\n", + " 'o:is_public': True,\n", + " 'o:owner': {'@id': 'https://timsherratt.org/collections/api/users/1',\n", + " 'o:id': 1},\n", + " 'o:resource_class': None,\n", + " 'o:resource_template': None,\n", + " 'o:thumbnail': None,\n", + " 'o:title': 'My first resource!',\n", + " 'thumbnail_display_urls': {'large': None, 'medium': None, 'square': None},\n", + " 'o:created': {'@value': '2022-01-26T04:46:35+00:00',\n", + " '@type': 'http://www.w3.org/2001/XMLSchema#dateTime'},\n", + " 'o:modified': {'@value': '2022-01-26T04:46:35+00:00',\n", + " '@type': 'http://www.w3.org/2001/XMLSchema#dateTime'},\n", + " 'o:media': [],\n", + " 'o:item_set': [],\n", + " 'o:site': [],\n", + " 'dcterms:title': [{'type': 'literal',\n", + " 'property_id': 1,\n", + " 'property_label': 'Title',\n", + " 'is_public': True,\n", + " '@value': 'My first resource!'}]}" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "new_item = omeka_auth.add_item(payload)\n", + "new_item" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "assert new_item['@type'] == 'o:Item'\n", + "assert test_item['dcterms:title'][0]['value'] == new_item['dcterms:title'][0]['@value']" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Utilities" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "

OmekaAPIClient.format_resource_id[source]

\n", + "\n", + "> OmekaAPIClient.format_resource_id(**`resource_id`**, **`resource_type`**)\n", + "\n", + "Generate a formatted id for the resource with the specified Omeka id number and resource type.\n", + "\n", + "Parameters:\n", + "* `resource_id` - numeric identifier used by Omeka for this resource\n", + "* `resource_type` - one of Omeka's resource types, eg: 'items', 'properties'\n", + "\n", + "Returns:\n", + "* a dict with values for '@id' and 'o:id'" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "show_doc(OmekaAPIClient.format_resource_id)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "id_url = omeka.format_resource_id(5, 'resource_templates')\n", + "\n", + "assert id_url == {\n", + " '@id': 'http://timsherratt.org/collections/api/resource_templates/5',\n", + " 'o:id': 5\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "

OmekaAPIClient.prepare_property_value[source]

\n", + "\n", + "> OmekaAPIClient.prepare_property_value(**`value`**, **`property_id`**)\n", + "\n", + "Formats a property value according to its datatype as expected by Omeka. \n", + "The formatted value can be used in a payload to create a new item.\n", + "\n", + "Parameters:\n", + "* `value` - a dict containing a `value` and (optionally) a `type`\n", + "* `property_id` - the numeric identifier of the property\n", + "\n", + "Note that is no `type` is supplied, 'literal' will be used by default.\n", + "\n", + "Returns:\n", + "* a dict with values for `property_id`, `type`, and either `@id` or `@value`." + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "show_doc(OmekaAPIClient.prepare_property_value)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'property_id': 393, 'type': 'uri', '@id': 'https://glam-workbench.net'}" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "prop_id = omeka_auth.get_property_id('schema:url')\n", + "prop_value = {'value': 'https://glam-workbench.net', 'type': 'uri'}\n", + "\n", + "formatted_prop = omeka_auth.prepare_property_value(prop_value, prop_id)\n", + "formatted_prop" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "assert prop_id == formatted_prop['property_id']\n", + "assert 'type' in formatted_prop\n", + "assert formatted_prop['@id'] == 'https://glam-workbench.net'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/markdown": [ + "

OmekaAPIClient.get_template_properties[source]

\n", + "\n", + "> OmekaAPIClient.get_template_properties(**`template_id`**)\n", + "\n", + "List properties used by the specified template.\n", + "\n", + "The resource template objects returned by the API don't include property terms.\n", + "This function gets the additional details, and organises the properties in a dictionary, \n", + "organised by term. This makes it easy to check if a particular term is used by a template.\n", + "\n", + "Parameters:\n", + "* `template_id` - numeric identifier for a template\n", + "\n", + "Returns:\n", + "* a dict organised by property terms, with values for `property_id` and `type`" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "show_doc(OmekaAPIClient.get_template_properties)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "template_id = omeka_auth.get_template_by_label('Newspaper')['o:id']\n", + "newspaper_properties = omeka_auth.get_template_properties(template_id)\n", + "\n", + "assert 'schema:name' in newspaper_properties" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Example: adding a newspaper article from Trove" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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.\n", + "\n", + "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.\n", + "\n", + "I've already created templates labelled 'Newspaper' and 'Newspaper article'.\n", + "\n", + "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. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "5" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "omeka_auth = OmekaAPIClient(\n", + " api_url='http://timsherratt.org/collections/api',\n", + " key_identity=os.getenv('KEY_IDENTITY'), \n", + " key_credential=os.getenv('KEY_CREDENTIAL')\n", + ")\n", + "\n", + "newspaper_template_id = omeka_auth.get_template_by_label('Newspaper')['o:id']\n", + "newspaper_template_id" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'schema:name': {'property_id': 1116, 'type': ['literal']},\n", + " 'schema:additionalType': {'property_id': 1199, 'type': ['uri']},\n", + " 'schema:description': {'property_id': 528,\n", + " 'type': ['literal', 'uri', 'resource:item']},\n", + " 'schema:alternateName': {'property_id': 282,\n", + " 'type': ['literal', 'uri', 'resource:item']},\n", + " 'schema:editor': {'property_id': 752, 'type': ['resource:item']},\n", + " 'schema:publisher': {'property_id': 967, 'type': ['resource:item']},\n", + " 'schema:issn': {'property_id': 931,\n", + " 'type': ['literal', 'uri', 'resource:item']},\n", + " 'schema:startDate': {'property_id': 513, 'type': ['numeric:timestamp']},\n", + " 'schema:endDate': {'property_id': 657, 'type': ['numeric:timestamp']},\n", + " 'schema:locationCreated': {'property_id': 706, 'type': ['resource:item']},\n", + " 'schema:hasPart': {'property_id': 1405, 'type': ['resource:item']},\n", + " 'schema:about': {'property_id': 1392, 'type': ['resource:item']},\n", + " 'schema:mentions': {'property_id': 747, 'type': ['resource:item']},\n", + " 'schema:contentLocation': {'property_id': 1419, 'type': ['resource:item']},\n", + " 'schema:temporalCoverage': {'property_id': 460,\n", + " 'type': ['literal', 'uri', 'resource:item']},\n", + " 'schema:subjectOf': {'property_id': 1391, 'type': ['resource:item']},\n", + " 'schema:url': {'property_id': 393, 'type': ['uri']},\n", + " 'schema:image': {'property_id': 742, 'type': ['resource:item']},\n", + " 'schema:identifier': {'property_id': 190,\n", + " 'type': ['literal', 'uri', 'resource:item']},\n", + " 'schema:sameAs': {'property_id': 246, 'type': ['uri']}}" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "omeka_auth.get_template_properties(newspaper_template_id)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "newspaper = {\n", + " 'schema.name': [\n", + " {\n", + " 'type': 'literal',\n", + " 'value': 'The Bendigo Independent (Vic. : 1891 - 1918)'\n", + " }\n", + " ],\n", + " 'schema:url': [\n", + " {\n", + " 'type': 'literal',\n", + " 'value': 'http://nla.gov.au/nla.news-title806'\n", + " }\n", + " ]\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Term schema.name not in template\n", + "Data type \"literal\" for term \"schema:url\" not allowed by template\n" + ] + } + ], + "source": [ + "payload = omeka_auth.prepare_item_payload_using_template(newspaper, newspaper_template_id)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "newspaper = {\n", + " 'schema:name': [\n", + " {\n", + " 'type': 'literal',\n", + " 'value': 'The Bendigo Independent (Vic. : 1891 - 1918)'\n", + " }\n", + " ],\n", + " 'schema:url': [\n", + " {\n", + " 'type': 'uri',\n", + " 'value': 'http://nla.gov.au/nla.news-title806'\n", + " }\n", + " ]\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "payload = omeka_auth.prepare_item_payload_using_template(newspaper, newspaper_template_id)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "That seems better! Now we can examine the payload." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'schema:name': [{'property_id': 1116,\n", + " 'type': 'literal',\n", + " '@value': 'The Bendigo Independent (Vic. : 1891 - 1918)'}],\n", + " 'schema:url': [{'property_id': 393,\n", + " 'type': 'uri',\n", + " '@id': 'http://nla.gov.au/nla.news-title806'}]}" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "payload" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "newspaper_item = omeka_auth.add_item(payload, template_id=newspaper_template_id)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can check the contents of `newspaper_item` to make sure it's been added succesfully." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'@context': 'http://timsherratt.org/collections/api-context',\n", + " '@id': 'http://timsherratt.org/collections/api/items/730',\n", + " '@type': 'o:Item',\n", + " 'o:id': 730,\n", + " 'o:is_public': True,\n", + " 'o:owner': {'@id': 'http://timsherratt.org/collections/api/users/1',\n", + " 'o:id': 1},\n", + " 'o:resource_class': None,\n", + " 'o:resource_template': {'@id': 'http://timsherratt.org/collections/api/resource_templates/5',\n", + " 'o:id': 5},\n", + " 'o:thumbnail': None,\n", + " 'o:title': 'The Bendigo Independent (Vic. : 1891 - 1918)',\n", + " 'thumbnail_display_urls': {'large': None, 'medium': None, 'square': None},\n", + " 'o:created': {'@value': '2022-01-26T04:46:55+00:00',\n", + " '@type': 'http://www.w3.org/2001/XMLSchema#dateTime'},\n", + " 'o:modified': {'@value': '2022-01-26T04:46:55+00:00',\n", + " '@type': 'http://www.w3.org/2001/XMLSchema#dateTime'},\n", + " 'o:media': [],\n", + " 'o:item_set': [],\n", + " 'o:site': [],\n", + " 'schema:name': [{'type': 'literal',\n", + " 'property_id': 1116,\n", + " 'property_label': 'name',\n", + " 'is_public': True,\n", + " '@value': 'The Bendigo Independent (Vic. : 1891 - 1918)'}],\n", + " 'schema:url': [{'type': 'uri',\n", + " 'property_id': 393,\n", + " 'property_label': 'url',\n", + " 'is_public': True,\n", + " '@id': 'http://nla.gov.au/nla.news-title806'}]}" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "newspaper_item" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Great! Now what about the article? Again let's start by having a look at the 'Newspaper article' template." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'schema:name': {'property_id': 1116,\n", + " 'type': ['literal', 'uri', 'resource:item']},\n", + " 'schema:additionalType': {'property_id': 1199, 'type': ['uri']},\n", + " 'schema:description': {'property_id': 528,\n", + " 'type': ['literal', 'uri', 'resource:item']},\n", + " 'schema:alternateName': {'property_id': 282,\n", + " 'type': ['literal', 'uri', 'resource:item']},\n", + " 'schema:datePublished': {'property_id': 928, 'type': ['numeric:timestamp']},\n", + " 'schema:pagination': {'property_id': 376,\n", + " 'type': ['literal', 'uri', 'resource:item']},\n", + " 'schema:creator': {'property_id': 329, 'type': ['resource:item']},\n", + " 'schema:contributor': {'property_id': 1303, 'type': ['resource:item']},\n", + " 'schema:isPartOf': {'property_id': 736, 'type': ['resource:item']},\n", + " 'schema:license': {'property_id': 1348,\n", + " 'type': ['literal', 'uri', 'resource:item']},\n", + " 'schema:about': {'property_id': 1392, 'type': ['resource:item']},\n", + " 'schema:mentions': {'property_id': 747, 'type': ['resource:item']},\n", + " 'schema:contentLocation': {'property_id': 1419, 'type': ['resource:item']},\n", + " 'schema:temporalCoverage': {'property_id': 460,\n", + " 'type': ['literal', 'uri', 'resource:item']},\n", + " 'schema:subjectOf': {'property_id': 1391, 'type': ['resource:item']},\n", + " 'schema:image': {'property_id': 742, 'type': ['resource:item']},\n", + " 'schema:url': {'property_id': 393, 'type': ['uri']},\n", + " 'schema:identifier': {'property_id': 190,\n", + " 'type': ['literal', 'uri', 'resource:item']},\n", + " 'schema:sameAs': {'property_id': 246, 'type': ['uri']},\n", + " 'schema:text': {'property_id': 833,\n", + " 'type': ['literal', 'uri', 'resource:item']}}" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "article_template_id = omeka_auth.get_template_by_label('Newspaper article')['o:id']\n", + "omeka_auth.get_template_properties(article_template_id)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This time we'll build our data file in stages." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Create an empty dictionary\n", + "article = {}\n", + "\n", + "# Add value for schema:name\n", + "article['schema:name'] = [\"MR WRAGGE'S PREDICTION. RENEWAL OF CYCLONE FORETOLD.\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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:\n", + "\n", + "* if the template defines a default data type, use that\n", + "* if 'literal' is in the template's list of expected data types, use that\n", + "* 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\n", + "\n", + "In the case above, the 'literal' data type will be assigned to the `schema:name` value.\n", + "\n", + "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'.\n", + "\n", + "```\n", + "'schema:isPartOf': {'property_id': 736, 'type': ['resource:item']}\n", + "```\n", + "\n", + "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." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "article['schema:isPartOf'] = [newspaper_item['o:id']]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next we'll add the publication date. To recognise dates as specific data types, you need to install the [Numeric Data Types](https://omeka.org/s/modules/NumericDataTypes/) 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:\n", + "\n", + "```\n", + "'schema:datePublished': {'property_id': 928, 'type': ['numeric:timestamp']}\n", + "```\n", + "\n", + "The date value itself is supplied in a standard ISO format -- 'YYYY-MM-DD'." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "article['schema:datePublished'] = ['1918-03-14']" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally we'll add a link to the article. Once again the template includes a default data type.\n", + "\n", + "```\n", + "'schema:url': {'property_id': 393, 'type': ['uri']}\n", + "```\n", + "\n", + "So we can just include the value." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "article['schema:url'] = ['http://nla.gov.au/nla.news-article226799674']" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now that we've assembled the article metadata, we can generate a payload." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "article_payload = omeka_auth.prepare_item_payload_using_template(article, article_template_id)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'schema:name': [{'property_id': 1116,\n", + " 'type': 'literal',\n", + " '@value': \"MR WRAGGE'S PREDICTION. RENEWAL OF CYCLONE FORETOLD.\"}],\n", + " 'schema:isPartOf': [{'property_id': 736,\n", + " 'type': 'resource:item',\n", + " '@id': 'http://timsherratt.org/collections/api/items/730',\n", + " 'value_resource_id': 730,\n", + " 'value_resource_name': 'items'}],\n", + " 'schema:datePublished': [{'property_id': 928,\n", + " 'type': 'numeric:timestamp',\n", + " '@value': '1918-03-14'}],\n", + " 'schema:url': [{'property_id': 393,\n", + " 'type': 'uri',\n", + " '@id': 'http://nla.gov.au/nla.news-article226799674'}]}" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "article_payload" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Create a list of paths pointing to media files\n", + "media_files = ['media/nla.news-article226799674-24144902.jpg']\n", + "\n", + "# Include the media files when we upload the payload\n", + "article_item = omeka_auth.add_item(article_payload, media_files=media_files, template_id=article_template_id)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When we look at the uploaded item, we'll see that the media files have been processed and added to the record." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'@context': 'http://timsherratt.org/collections/api-context',\n", + " '@id': 'http://timsherratt.org/collections/api/items/731',\n", + " '@type': 'o:Item',\n", + " 'o:id': 731,\n", + " 'o:is_public': True,\n", + " 'o:owner': {'@id': 'http://timsherratt.org/collections/api/users/1',\n", + " 'o:id': 1},\n", + " 'o:resource_class': None,\n", + " 'o:resource_template': {'@id': 'http://timsherratt.org/collections/api/resource_templates/4',\n", + " 'o:id': 4},\n", + " 'o:thumbnail': None,\n", + " 'o:title': \"MR WRAGGE'S PREDICTION. RENEWAL OF CYCLONE FORETOLD.\",\n", + " 'thumbnail_display_urls': {'large': 'http://timsherratt.org/collections/files/large/aa9ef3fe881ee92c46bc8f1500d7f9fa9f3d6bb4.jpg',\n", + " 'medium': 'http://timsherratt.org/collections/files/medium/aa9ef3fe881ee92c46bc8f1500d7f9fa9f3d6bb4.jpg',\n", + " 'square': 'http://timsherratt.org/collections/files/square/aa9ef3fe881ee92c46bc8f1500d7f9fa9f3d6bb4.jpg'},\n", + " 'o:created': {'@value': '2022-01-26T04:47:02+00:00',\n", + " '@type': 'http://www.w3.org/2001/XMLSchema#dateTime'},\n", + " 'o:modified': {'@value': '2022-01-26T04:47:02+00:00',\n", + " '@type': 'http://www.w3.org/2001/XMLSchema#dateTime'},\n", + " 'o:media': [{'@id': 'http://timsherratt.org/collections/api/media/732',\n", + " 'o:id': 732}],\n", + " 'o:item_set': [],\n", + " 'o:site': [],\n", + " 'schema:name': [{'type': 'literal',\n", + " 'property_id': 1116,\n", + " 'property_label': 'name',\n", + " 'is_public': True,\n", + " '@value': \"MR WRAGGE'S PREDICTION. RENEWAL OF CYCLONE FORETOLD.\"}],\n", + " 'schema:datePublished': [{'type': 'numeric:timestamp',\n", + " 'property_id': 928,\n", + " 'property_label': 'datePublished',\n", + " 'is_public': True,\n", + " '@value': '1918-03-14',\n", + " '@type': 'http://www.w3.org/2001/XMLSchema#date'}],\n", + " 'schema:isPartOf': [{'type': 'resource:item',\n", + " 'property_id': 736,\n", + " 'property_label': 'isPartOf',\n", + " 'is_public': True,\n", + " '@id': 'http://timsherratt.org/collections/api/items/730',\n", + " 'value_resource_id': 730,\n", + " 'value_resource_name': 'items',\n", + " 'url': None,\n", + " 'display_title': 'The Bendigo Independent (Vic. : 1891 - 1918)'}],\n", + " 'schema:url': [{'type': 'uri',\n", + " 'property_id': 393,\n", + " 'property_label': 'url',\n", + " 'is_public': True,\n", + " '@id': 'http://nla.gov.au/nla.news-article226799674'}]}" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "article_item" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## To do\n", + "\n", + "* update and delete methods" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 0000000..3d861f3 --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1,66 @@ +repository: wragge/omeka_s_tools +output: web +topnav_title: omeka_s_tools +site_title: omeka_s_tools +company_name: Copyright 2022, Tim Sherratt +description: Tools for working with Omeka S API +# Set to false to disable KaTeX math +use_math: true +# Add Google analytics id if you have one and want to use it here +google_analytics: +# See http://nbdev.fast.ai/search for help with adding Search +google_search: + +host: 127.0.0.1 +# the preview server used. Leave as is. +port: 4000 +# the port where the preview is rendered. + +exclude: + - .idea/ + - .gitignore + - vendor + +exclude: [vendor] + +highlighter: rouge +markdown: kramdown +kramdown: + input: GFM + auto_ids: true + hard_wrap: false + syntax_highlighter: rouge + +collections: + tooltips: + output: false + +defaults: + - + scope: + path: "" + type: "pages" + values: + layout: "page" + comments: true + search: true + sidebar: home_sidebar + topnav: topnav + - + scope: + path: "" + type: "tooltips" + values: + layout: "page" + comments: true + search: true + tooltip: true + +sidebars: +- home_sidebar + +plugins: + - jekyll-remote-theme + +remote_theme: fastai/nbdev-jekyll-theme +baseurl: /omeka_s_tools/ \ No newline at end of file diff --git a/docs/_data/sidebars/home_sidebar.yml b/docs/_data/sidebars/home_sidebar.yml new file mode 100644 index 0000000..a783a1d --- /dev/null +++ b/docs/_data/sidebars/home_sidebar.yml @@ -0,0 +1,18 @@ + +################################################# +### THIS FILE WAS AUTOGENERATED! DO NOT EDIT! ### +################################################# +# Instead edit ../../sidebar.json +entries: +- folders: + - folderitems: + - output: web,pdf + title: Overview + url: / + - output: web,pdf + title: Omeka S API client + url: api.html + output: web + title: omeka_s_tools + output: web + title: Sidebar diff --git a/docs/_data/topnav.yml b/docs/_data/topnav.yml new file mode 100644 index 0000000..3675993 --- /dev/null +++ b/docs/_data/topnav.yml @@ -0,0 +1,10 @@ +topnav: +- title: Topnav + items: + - title: github + external_url: https://github.com/wragge/omeka_s_tools/tree/master/ + +#Topnav dropdowns +topnav_dropdowns: +- title: Topnav dropdowns + folders: \ No newline at end of file diff --git a/docs/api.html b/docs/api.html new file mode 100644 index 0000000..dd11dfb --- /dev/null +++ b/docs/api.html @@ -0,0 +1,2075 @@ +--- + +title: Omeka S API client + + +keywords: fastai +sidebar: home_sidebar + +summary: "Tools to interact with the Omeka S REST API" +description: "Tools to interact with the Omeka S REST API" +nb_path: "api.ipynb" +--- + + +
+ + {% raw %} + +
+ +
+ {% endraw %} + + {% raw %} + +
+ +
+
+ +
+ + +
+

class OmekaAPIClient[source]

OmekaAPIClient(api_url, key_identity=None, key_credential=None)

+
+ +
+ +
+ +
+
+ +
+ {% endraw %} + + {% raw %} + +
+ +
+ {% endraw %} + +
+
+

To initialise the OmekaAPIClient you need to provide the base url of your Omeka instance's API.

+ +
+
+
+ {% raw %} + +
+
+ +
+
+
omeka = OmekaAPIClient('http://timsherratt.org/collections/api')
+
+ +
+
+
+ +
+ {% endraw %} + +
+
+

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.

+ +
+
+
+ {% raw %} + +
+ +
+
+ +
+ + +
+

OmekaAPIClient.get_resources[source]

OmekaAPIClient.get_resources(resource_type, **kwargs)

+
+

Get a list of resources matching the supplied parameters. +This will return the first page of matching results. To retrieve additional pages, +you can supply the page parameter to move through the full result set.

+

Parameters:

+
    +
  • resource_type - one of Omeka's resource types, eg: 'items', 'properties'
  • +
  • there are many additional parameters you can supply as kwargs, see the Omeka documention
  • +
+

Returns a dict with the following values:

+
    +
  • total_results - number of matching resources
  • +
  • results - a list of dicts, each containing a JSON-LD formatted representation of a resource
  • +
+ +
+ +
+ +
+
+ +
+ {% endraw %} + + {% raw %} + +
+
+ +
+
+
data = omeka.get_resources('items')
+data['total_results']
+
+ +
+
+
+ +
+
+ +
+ + + +
+
30
+
+ +
+ +
+
+ +
+ {% endraw %} + + {% raw %} + +
+
+ +
+
+
assert isinstance(data['total_results'], int)
+
+ +
+
+
+ +
+ {% endraw %} + + {% raw %} + +
+
+ +
+
+
data = omeka.get_resources('resource_templates')
+first_template = data['results'][0]
+assert first_template['@type'] == 'o:ResourceTemplate'
+
+ +
+
+
+ +
+ {% endraw %} + + {% raw %} + +
+ +
+
+ +
+ + +
+

OmekaAPIClient.get_resource_by_id[source]

OmekaAPIClient.get_resource_by_id(resource_id, resource_type='items')

+
+

Get a resource from its Omeka id.

+

Parameters:

+
    +
  • resource_id - numeric identifier used by Omeka for this resource
  • +
  • resource_type - one of Omeka's resource types, eg: 'items', 'properties'
  • +
+

Returns

+
    +
  • a dict containing a JSON-LD formatted representation of the resource
  • +
+ +
+ +
+ +
+
+ +
+ {% endraw %} + + {% raw %} + +
+
+ +
+
+
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
+
+ +
+
+
+ +
+ {% endraw %} + +
+
+

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.

+ +
+
+
+ {% raw %} + +
+ +
+
+ +
+ + +
+

OmekaAPIClient.get_template_by_label[source]

OmekaAPIClient.get_template_by_label(label)

+
+

Get a resource template from its Omeka label.

+

Parameters:

+
    +
  • label - the name of the resource template in Omeka (eg. 'NewspaperArticle')
  • +
+

Returns:

+
    +
  • dict containing representation of the template
  • +
+ +
+ +
+ +
+
+ +
+ {% endraw %} + + {% raw %} + +
+
+ +
+
+
newspaper_template = omeka.get_template_by_label('Newspaper')
+
+assert newspaper_template['@type'] == 'o:ResourceTemplate'
+assert newspaper_template['o:label'] == 'Newspaper'
+
+ +
+
+
+ +
+ {% endraw %} + + {% raw %} + +
+
+ +
+
+
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
+
+ +
+
+
+ +
+ {% endraw %} + + {% raw %} + +
+ +
+
+ +
+ + +
+

OmekaAPIClient.get_resource_by_term[source]

OmekaAPIClient.get_resource_by_term(term, resource_type='properties')

+
+

Get the resource (property or class) associated with the suppied term.

+

Parameters:

+
    +
  • term - property label qualified with vocabulary prefix (eg: 'schema:name')
  • +
+

Returns:

+
    +
  • dict containing representation of the resource
  • +
+ +
+ +
+ +
+
+ +
+ {% endraw %} + + {% raw %} + +
+
+ +
+
+
prop = omeka.get_resource_by_term('schema:name')
+prop
+
+ +
+
+
+ +
+
+ +
+ + + +
+
{'@context': 'http://timsherratt.org/collections/api-context',
+ '@id': 'http://timsherratt.org/collections/api/properties/1116',
+ '@type': 'o:Property',
+ 'o:id': 1116,
+ 'o:local_name': 'name',
+ 'o:label': 'name',
+ 'o:comment': 'The name of the item.',
+ 'o:term': 'schema:name',
+ 'o:vocabulary': {'@id': 'http://timsherratt.org/collections/api/vocabularies/5',
+  'o:id': 5}}
+
+ +
+ +
+
+ +
+ {% endraw %} + + {% raw %} + +
+ +
+
+ +
+ + +
+

OmekaAPIClient.get_resource_from_vocab[source]

OmekaAPIClient.get_resource_from_vocab(local_name, vocabulary_namespace_uri='http://schema.org/', resource_type='properties')

+
+

Get the resource (property or class) associated with the suppied vocabulary and label.

+

Parameters:

+
    +
  • local_name - label of the property or class
  • +
  • vocabulary_namespace_uri - URI defining the vocab
  • +
+

Returns:

+
    +
  • dict containing representation of the resource
  • +
+ +
+ +
+ +
+
+ +
+ {% endraw %} + + {% raw %} + +
+
+ +
+
+
omeka.get_resource_from_vocab(
+    'name', 
+    vocabulary_namespace_uri='http://schema.org/',
+    resource_type='properties')
+
+ +
+
+
+ +
+
+ +
+ + + +
+
{'@context': 'http://timsherratt.org/collections/api-context',
+ '@id': 'http://timsherratt.org/collections/api/properties/1116',
+ '@type': 'o:Property',
+ 'o:id': 1116,
+ 'o:local_name': 'name',
+ 'o:label': 'name',
+ 'o:comment': 'The name of the item.',
+ 'o:term': 'schema:name',
+ 'o:vocabulary': {'@id': 'http://timsherratt.org/collections/api/vocabularies/5',
+  'o:id': 5}}
+
+ +
+ +
+
+ +
+ {% endraw %} + + {% raw %} + +
+ +
+
+ +
+ + +
+

OmekaAPIClient.get_property_id[source]

OmekaAPIClient.get_property_id(term)

+
+

Get the numeric identifier associated with the supplied property term.

+

Parameters:

+
    +
  • term - property label qualified with vocabulary prefix (eg: 'schema:name')
  • +
+

Returns:

+
    +
  • numeric identifier
  • +
+ +
+ +
+ +
+
+ +
+ {% endraw %} + + {% raw %} + +
+
+ +
+
+
omeka.get_property_id('schema:name')
+
+ +
+
+
+ +
+
+ +
+ + + +
+
1116
+
+ +
+ +
+
+ +
+ {% endraw %} + + {% raw %} + +
+ +
+
+ +
+ + +
+

OmekaAPIClient.filter_items_by_property[source]

OmekaAPIClient.filter_items_by_property(filter_property='schema:name', filter_value='', filter_type='eq', page=1, **extra_filters)

+
+

Filter the list of items by searching for a value in a particular property. +Additional filters can also limit to items associated with particular templates, classes, or item sets.

+

Parameters:

+
    +
  • filter_property - property term (eg: 'schema:name')
  • +
  • filter_value - the value you want to find
  • +
  • filter_type - how filter_value should be compared to the stored values (eg: 'eq')
  • +
  • page - number of results page
  • +
+

Additional parameters:

+
    +
  • resource_template_id - numeric identifier
  • +
  • resource_class_id - numeric identifier
  • +
  • item_set_id - numeric identifier
  • +
  • is_public - boolean, True or False
  • +
+

Returns a dict with the following values:

+
    +
  • total_results - number of matching resources
  • +
  • results - a list of dicts, each containing a JSON-LD formatted representation of a resource
  • +
+ +
+ +
+ +
+
+ +
+ {% endraw %} + + {% raw %} + +
+
+ +
+
+
omeka.filter_items_by_property(search_property='schema:name', search_text='name1')
+
+ +
+
+
+ +
+
+ +
+ + + +
+
{'total_results': '3',
+ 'results': [{'@context': 'http://timsherratt.org/collections/api-context',
+   '@id': 'http://timsherratt.org/collections/api/items/694',
+   '@type': 'o:Item',
+   'o:id': 694,
+   'o:is_public': True,
+   'o:owner': {'@id': 'http://timsherratt.org/collections/api/users/1',
+    'o:id': 1},
+   'o:resource_class': None,
+   'o:resource_template': None,
+   'o:thumbnail': None,
+   'o:title': None,
+   'thumbnail_display_urls': {'large': None, 'medium': None, 'square': None},
+   'o:created': {'@value': '2022-01-24T03:56:55+00:00',
+    '@type': 'http://www.w3.org/2001/XMLSchema#dateTime'},
+   'o:modified': {'@value': '2022-01-24T03:56:55+00:00',
+    '@type': 'http://www.w3.org/2001/XMLSchema#dateTime'},
+   'o:media': [],
+   'o:item_set': [],
+   'o:site': [],
+   'schema:name': [{'type': 'literal',
+     'property_id': 1116,
+     'property_label': 'name',
+     'is_public': True,
+     '@value': 'name1'},
+    {'type': 'literal',
+     'property_id': 1116,
+     'property_label': 'name',
+     'is_public': True,
+     '@value': 'name2'}]},
+  {'@context': 'http://timsherratt.org/collections/api-context',
+   '@id': 'http://timsherratt.org/collections/api/items/695',
+   '@type': 'o:Item',
+   'o:id': 695,
+   'o:is_public': True,
+   'o:owner': {'@id': 'http://timsherratt.org/collections/api/users/1',
+    'o:id': 1},
+   'o:resource_class': None,
+   'o:resource_template': None,
+   'o:thumbnail': None,
+   'o:title': None,
+   'thumbnail_display_urls': {'large': None, 'medium': None, 'square': None},
+   'o:created': {'@value': '2022-01-24T04:05:41+00:00',
+    '@type': 'http://www.w3.org/2001/XMLSchema#dateTime'},
+   'o:modified': {'@value': '2022-01-24T04:05:41+00:00',
+    '@type': 'http://www.w3.org/2001/XMLSchema#dateTime'},
+   'o:media': [],
+   'o:item_set': [],
+   'o:site': [],
+   'schema:name': [{'type': 'literal',
+     'property_id': 1116,
+     'property_label': 'name',
+     'is_public': True,
+     '@value': 'name1'},
+    {'type': 'literal',
+     'property_id': 1116,
+     'property_label': 'name',
+     'is_public': True,
+     '@value': 'name2'}]},
+  {'@context': 'http://timsherratt.org/collections/api-context',
+   '@id': 'http://timsherratt.org/collections/api/items/696',
+   '@type': 'o:Item',
+   'o:id': 696,
+   'o:is_public': True,
+   'o:owner': {'@id': 'http://timsherratt.org/collections/api/users/1',
+    'o:id': 1},
+   'o:resource_class': None,
+   'o:resource_template': None,
+   'o:thumbnail': None,
+   'o:title': None,
+   'thumbnail_display_urls': {'large': None, 'medium': None, 'square': None},
+   'o:created': {'@value': '2022-01-24T04:07:26+00:00',
+    '@type': 'http://www.w3.org/2001/XMLSchema#dateTime'},
+   'o:modified': {'@value': '2022-01-24T04:07:26+00:00',
+    '@type': 'http://www.w3.org/2001/XMLSchema#dateTime'},
+   'o:media': [],
+   'o:item_set': [],
+   'o:site': [],
+   'schema:name': [{'type': 'literal',
+     'property_id': 1116,
+     'property_label': 'name',
+     'is_public': True,
+     '@value': 'name1'},
+    {'type': 'literal',
+     'property_id': 1116,
+     'property_label': 'name',
+     'is_public': True,
+     '@value': 'name2'}]}]}
+
+ +
+ +
+
+ +
+ {% endraw %} + + {% raw %} + +
+ +
+
+ +
+ + +
+

OmekaAPIClient.search_items[source]

OmekaAPIClient.search_items(query, search_type='fulltext_search', page=1, **extra_filters)

+
+

Search for matching items. +Two search types are available:

+
    +
  • 'search` - looks for an exact match of the query in a property value
  • +
  • 'fulltext_search` - looks for the occurance of the query anywhere
  • +
+

Parameters:

+
    +
  • query - the text you want to search for
  • +
  • search_type - one of 'fulltext_search' or 'search'
  • +
  • page - number of results page
  • +
+

Additional parameters:

+
    +
  • resource_template_id - numeric identifier
  • +
  • resource_class_id - numeric identifier
  • +
  • item_set_id - numeric identifier
  • +
  • is_public - boolean, True or False
  • +
+

Returns a dict with the following values:

+
    +
  • total_results - number of matching resources
  • +
  • results - a list of dicts, each containing a JSON-LD formatted representation of a resource
  • +
+ +
+ +
+ +
+
+ +
+ {% endraw %} + + {% raw %} + +
+
+ +
+
+
data = omeka.search_items('Newcastle')
+
+ +
+
+
+ +
+ {% endraw %} + + {% raw %} + +
+
+ +
+
+
item_from_list = random.choice(omeka.get_resources('items'))
+
+# Get the same item using its id
+item_from_id = omeka.get_resource_by_id(item_from_list['o:id'])
+
+# Check that they're the same
+assert item_from_list == item_from_id
+
+ +
+
+
+ +
+ {% endraw %} + + {% raw %} + +
+
+ +
+
+
template_from_list = random.choice(omeka.get_resources('resource_templates'))
+template_from_label = omeka.get_resource_by_label(template_from_list['o:label'], 'resource_templates')
+assert template_from_list == template_from_label
+
+ +
+
+
+ +
+ {% endraw %} + + {% raw %} + +
+
+ +
+
+
property_from_list = random.choice(omeka.get_resources('properties'))
+vocabulary = s.get(property_from_list['o:vocabulary']['@id']).json()
+vocab_namespace_uri = vocabulary['o:namespace_uri']
+property_id = omeka.get_rdf_resource_id(property_from_list['o:local_name'], vocabulary_namespace_uri=vocab_namespace_uri)
+
+assert property_from_list['@id'] == property_id['@id']
+assert property_from_list['o:id'] == property_id['o:id']         
+
+ +
+
+
+ +
+ {% endraw %} + + {% raw %} + +
+
+ +
+
+
item_from_list = random.choice(omeka.get_resources('items'))
+name = item_from_list['schema:name'][0]['@value']
+
+found_item = omeka.find_item(name, property_name='name', vocabulary_namespace_uri='http://schema.org/')
+print(found_item)
+assert item_from_list == found_item
+
+ +
+
+
+ +
+ {% endraw %} + +
+
+

Adding items

To add resources to Omeka you need authenticate your API requests by supplying key_identity and key_credential tokens as parameters.

+ +
+
+
+ {% raw %} + +
+
+ +
+
+
omeka_auth = OmekaAPIClient(
+    api_url='http://timsherratt.org/collections/api',
+    key_identity=os.getenv('KEY_IDENTITY'), 
+    key_credential=os.getenv('KEY_CREDENTIAL')
+)
+
+ +
+
+
+ +
+ {% endraw %} + +
+
+

There are two stages in creating a new item:

+
    +
  • prepare a payload containing the item data in the format expected by Omeka
  • +
  • upload the payload to Omeka
  • +
+

Prepare an item payload

+
+
+
+ {% raw %} + +
+ +
+
+ +
+ + +
+

OmekaAPIClient.prepare_item_payload[source]

OmekaAPIClient.prepare_item_payload(terms)

+
+

Prepare an item payload, ready for upload.

+

Parameters:

+
    +
  • terms: a dict of terms, values, and (optionally) data types
  • +
+

Returns:

+
    +
  • the payload dict
  • +
+ +
+ +
+ +
+
+ +
+ {% endraw %} + + {% raw %} + +
+
+ +
+
+
test_item = {
+    'dcterms:title': [
+        {
+            'value': 'My first resource!'
+        }
+    ]
+}
+
+ +
+
+
+ +
+ {% endraw %} + + {% raw %} + +
+
+ +
+
+
payload = omeka_auth.prepare_item(test_item)
+payload
+
+ +
+
+
+ +
+
+ +
+ + + +
+
{'dcterms:title': [{'property_id': 1,
+   'type': 'literal',
+   '@value': 'My first resource!'}]}
+
+ +
+ +
+
+ +
+ {% endraw %} + +
+
+

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.

+ +
+
+
+ {% raw %} + +
+
+ +
+
+
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
+
+ +
+
+
+ +
+ {% endraw %} + +
+
+

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.

+ +
+
+
+ {% raw %} + +
+ +
+
+ +
+ + +
+

OmekaAPIClient.prepare_item_payload_using_template[source]

OmekaAPIClient.prepare_item_payload_using_template(terms, template_id)

+
+

Prepare an item payload, checking the supplied terms and values against the specified template. +Note:

+
    +
  • terms that are not in the template will generate a warning and be dropped from the payload
  • +
  • data types that don't match the template definitions will generate a warning and the term will be dropped from the payload
  • +
  • if no data type is supplied, a type that conforms with the template definition will be used
  • +
+

Parameters:

+
    +
  • terms: a dict of terms, values, and (optionally) data types
  • +
  • template_id: Omeka's internal numeric identifier for the template
  • +
+

Returns:

+
    +
  • the payload dict
  • +
+ +
+ +
+ +
+
+ +
+ {% endraw %} + +
+
+

Once you've created a payload, you can add paths to any media files that you want to be attached to the item.

+ +
+
+
+ {% raw %} + +
+ +
+
+ +
+ + +
+

OmekaAPIClient.add_media_to_payload[source]

OmekaAPIClient.add_media_to_payload(payload, media_files)

+
+

Add media files to the item payload.

+

Parameters:

+
    +
  • payload - the payload dict to be modified
  • +
  • media_files - media files to be uploaded
  • +
+

The value of media_files can be either:

+
    +
  • a list of paths to the image/media files (filename is used as title)
  • +
  • a list of dicts, each containing title, and path values
  • +
+

Returns:

+
    +
  • the modified payload dict
  • +
+ +
+ +
+ +
+
+ +
+ {% endraw %} + +
+
+

Upload the payload to Omeka and create a new item

+
+
+
+ {% raw %} + +
+ +
+
+ +
+ + +
+

OmekaAPIClient.add_item[source]

OmekaAPIClient.add_item(payload, media_files=None, template_id=None, class_id=None, item_set_id=None)

+
+

Create a new item from the supplied payload, optionally uploading attached media files.

+

Parameters:

+
    +
  • payload - a dict generated by prepare_item_payload() or prepare_item_payload_using_template()
  • +
  • media_files - a list of paths pointing to media files, or a list of dicts with path and title values
  • +
  • template_id - internal Omeka identifier of a resource template you want to attach to this item
  • +
  • class_id - internal Omeka identifier of a resource class you want to attach to this item
  • +
  • item_set_id - internal Omeka identifier for an item set you want to add this item to
  • +
+

Returns:

+
    +
  • a dict providing the JSON-LD representation of the new item from Omeka
  • +
+ +
+ +
+ +
+
+ +
+ {% endraw %} + +
+
+

Add a simple item

+
+
+
+ {% raw %} + +
+
+ +
+
+
omeka_auth.add_item(payload)
+
+ +
+
+
+ +
+
+ +
+ + + +
+
{'@context': 'http://timsherratt.org/collections/api-context',
+ '@id': 'http://timsherratt.org/collections/api/items/710',
+ '@type': 'o:Item',
+ 'o:id': 710,
+ 'o:is_public': True,
+ 'o:owner': {'@id': 'http://timsherratt.org/collections/api/users/1',
+  'o:id': 1},
+ 'o:resource_class': None,
+ 'o:resource_template': None,
+ 'o:thumbnail': None,
+ 'o:title': 'My first resource!',
+ 'thumbnail_display_urls': {'large': None, 'medium': None, 'square': None},
+ 'o:created': {'@value': '2022-01-25T04:09:55+00:00',
+  '@type': 'http://www.w3.org/2001/XMLSchema#dateTime'},
+ 'o:modified': {'@value': '2022-01-25T04:09:55+00:00',
+  '@type': 'http://www.w3.org/2001/XMLSchema#dateTime'},
+ 'o:media': [],
+ 'o:item_set': [],
+ 'o:site': [],
+ 'dcterms:title': [{'type': 'literal',
+   'property_id': 1,
+   'property_label': 'Title',
+   'is_public': True,
+   '@value': 'My first resource!'}]}
+
+ +
+ +
+
+ +
+ {% endraw %} + + {% raw %} + +
+
+ +
+
+
response['o:id']
+
+ +
+
+
+ +
+ {% endraw %} + + {% raw %} + +
+
+ +
+
+
id_url = omeka.format_resource_id(5, 'resource_templates')
+
+assert id_url == {
+    '@id': 'http://timsherratt.org/collections/api/resource_templates/5',
+    'o:id': 5
+}
+
+ +
+
+
+ +
+ {% endraw %} + + {% raw %} + +
+
+ +
+
+
media_files = ['images/image1.png', 'images/image2.png']
+item = omeka_auth.prepare_item_using_template(test_item, 5)
+print(item)
+omeka_auth.add_item(item, template_id=5, media_files=media_files)
+
+ +
+
+
+ +
+ {% endraw %} + +
+
+

Utilities

+
+
+
+
+
+

Example: adding a newspaper article from Trove

+
+
+
+
+
+

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.

+

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.

+ +
+
+
+ {% raw %} + +
+
+ +
+
+
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
+
+ +
+
+
+ +
+
+ +
+ + + +
+
5
+
+ +
+ +
+
+ +
+ {% endraw %} + +
+
+

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.

+ +
+
+
+ {% raw %} + +
+
+ +
+
+
omeka_auth.get_template_properties(newspaper_template_id)
+
+ +
+
+
+ +
+
+ +
+ + + +
+
{'schema:name': {'property_id': 1116, 'type': ['literal']},
+ 'schema:additionalType': {'property_id': 1199, 'type': ['uri']},
+ 'schema:description': {'property_id': 528,
+  'type': ['literal', 'uri', 'resource:item']},
+ 'schema:alternateName': {'property_id': 282,
+  'type': ['literal', 'uri', 'resource:item']},
+ 'schema:editor': {'property_id': 752, 'type': ['resource:item']},
+ 'schema:publisher': {'property_id': 967, 'type': ['resource:item']},
+ 'schema:issn': {'property_id': 931,
+  'type': ['literal', 'uri', 'resource:item']},
+ 'schema:startDate': {'property_id': 513, 'type': ['numeric:timestamp']},
+ 'schema:endDate': {'property_id': 657, 'type': ['numeric:timestamp']},
+ 'schema:locationCreated': {'property_id': 706, 'type': ['resource:item']},
+ 'schema:hasPart': {'property_id': 1405, 'type': ['resource:item']},
+ 'schema:about': {'property_id': 1392, 'type': ['resource:item']},
+ 'schema:mentions': {'property_id': 747, 'type': ['resource:item']},
+ 'schema:contentLocation': {'property_id': 1419, 'type': ['resource:item']},
+ 'schema:temporalCoverage': {'property_id': 460,
+  'type': ['literal', 'uri', 'resource:item']},
+ 'schema:subjectOf': {'property_id': 1391, 'type': ['resource:item']},
+ 'schema:url': {'property_id': 393, 'type': ['uri']},
+ 'schema:image': {'property_id': 742, 'type': ['resource:item']},
+ 'schema:identifier': {'property_id': 190,
+  'type': ['literal', 'uri', 'resource:item']},
+ 'schema:sameAs': {'property_id': 246, 'type': ['uri']}}
+
+ +
+ +
+
+ +
+ {% endraw %} + +
+
+

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.

+ +
+
+
+ {% raw %} + +
+
+ +
+
+
newspaper = {
+    'schema.name': [
+        {
+            'type': 'literal',
+            'value': 'The Bendigo Independent (Vic. : 1891 - 1918)'
+        }
+    ],
+    'schema:url': [
+        {
+            'type': 'literal',
+            'value': 'http://nla.gov.au/nla.news-title806'
+        }
+    ]
+}
+
+ +
+
+
+ +
+ {% endraw %} + +
+
+

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.

+ +
+
+
+ {% raw %} + +
+
+ +
+
+
payload = omeka_auth.prepare_item_payload_using_template(newspaper, newspaper_template_id)
+
+ +
+
+
+ +
+
+ +
+ +
+
Term schema.name not in template
+Data type "literal" for term "schema:url" not allowed by template
+
+
+
+ +
+
+ +
+ {% endraw %} + +
+
+

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.

+ +
+
+
+ {% raw %} + +
+
+ +
+
+
newspaper = {
+    'schema:name': [
+        {
+            'type': 'literal',
+            'value': 'The Bendigo Independent (Vic. : 1891 - 1918)'
+        }
+    ],
+    'schema:url': [
+        {
+            'type': 'uri',
+            'value': 'http://nla.gov.au/nla.news-title806'
+        }
+    ]
+}
+
+ +
+
+
+ +
+ {% endraw %} + + {% raw %} + +
+
+ +
+
+
payload = omeka_auth.prepare_item_payload_using_template(newspaper, newspaper_template_id)
+
+ +
+
+
+ +
+ {% endraw %} + +
+
+

That seems better! Now we can examine the payload.

+ +
+
+
+ {% raw %} + +
+
+ +
+
+
payload
+
+ +
+
+
+ +
+
+ +
+ + + +
+
{'schema:name': [{'property_id': 1116,
+   'type': 'literal',
+   '@value': 'The Bendigo Independent (Vic. : 1891 - 1918)'}],
+ 'schema:url': [{'property_id': 393,
+   'type': 'uri',
+   '@id': 'http://nla.gov.au/nla.news-title806'}]}
+
+ +
+ +
+
+ +
+ {% endraw %} + +
+
+

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.

+ +
+
+
+ {% raw %} + +
+
+ +
+
+
newspaper_item = omeka_auth.add_item(payload, template_id=newspaper_template_id)
+
+ +
+
+
+ +
+ {% endraw %} + +
+
+

We can check the contents of newspaper_item to make sure it's been added succesfully.

+ +
+
+
+ {% raw %} + +
+
+ +
+
+
newspaper_item
+
+ +
+
+
+ +
+
+ +
+ + + +
+
{'@context': 'http://timsherratt.org/collections/api-context',
+ '@id': 'http://timsherratt.org/collections/api/items/712',
+ '@type': 'o:Item',
+ 'o:id': 712,
+ 'o:is_public': True,
+ 'o:owner': {'@id': 'http://timsherratt.org/collections/api/users/1',
+  'o:id': 1},
+ 'o:resource_class': None,
+ 'o:resource_template': {'@id': 'http://timsherratt.org/collections/api/resource_templates/5',
+  'o:id': 5},
+ 'o:thumbnail': None,
+ 'o:title': 'The Bendigo Independent (Vic. : 1891 - 1918)',
+ 'thumbnail_display_urls': {'large': None, 'medium': None, 'square': None},
+ 'o:created': {'@value': '2022-01-25T12:11:22+00:00',
+  '@type': 'http://www.w3.org/2001/XMLSchema#dateTime'},
+ 'o:modified': {'@value': '2022-01-25T12:11:22+00:00',
+  '@type': 'http://www.w3.org/2001/XMLSchema#dateTime'},
+ 'o:media': [],
+ 'o:item_set': [],
+ 'o:site': [],
+ 'schema:name': [{'type': 'literal',
+   'property_id': 1116,
+   'property_label': 'name',
+   'is_public': True,
+   '@value': 'The Bendigo Independent (Vic. : 1891 - 1918)'}],
+ 'schema:url': [{'type': 'uri',
+   'property_id': 393,
+   'property_label': 'url',
+   'is_public': True,
+   '@id': 'http://nla.gov.au/nla.news-title806'}]}
+
+ +
+ +
+
+ +
+ {% endraw %} + +
+
+

Great! Now what about the article? Again let's start by having a look at the 'Newspaper article' template.

+ +
+
+
+ {% raw %} + +
+
+ +
+
+
article_template_id = omeka_auth.get_template_by_label('Newspaper article')['o:id']
+omeka_auth.get_template_properties(article_template_id)
+
+ +
+
+
+ +
+
+ +
+ + + +
+
{'schema:name': {'property_id': 1116,
+  'type': ['literal', 'uri', 'resource:item']},
+ 'schema:additionalType': {'property_id': 1199, 'type': ['uri']},
+ 'schema:description': {'property_id': 528,
+  'type': ['literal', 'uri', 'resource:item']},
+ 'schema:alternateName': {'property_id': 282,
+  'type': ['literal', 'uri', 'resource:item']},
+ 'schema:datePublished': {'property_id': 928, 'type': ['numeric:timestamp']},
+ 'schema:pagination': {'property_id': 376,
+  'type': ['literal', 'uri', 'resource:item']},
+ 'schema:creator': {'property_id': 329, 'type': ['resource:item']},
+ 'schema:contributor': {'property_id': 1303, 'type': ['resource:item']},
+ 'schema:isPartOf': {'property_id': 736, 'type': ['resource:item']},
+ 'schema:license': {'property_id': 1348,
+  'type': ['literal', 'uri', 'resource:item']},
+ 'schema:about': {'property_id': 1392, 'type': ['resource:item']},
+ 'schema:mentions': {'property_id': 747, 'type': ['resource:item']},
+ 'schema:contentLocation': {'property_id': 1419, 'type': ['resource:item']},
+ 'schema:temporalCoverage': {'property_id': 460,
+  'type': ['literal', 'uri', 'resource:item']},
+ 'schema:subjectOf': {'property_id': 1391, 'type': ['resource:item']},
+ 'schema:image': {'property_id': 742, 'type': ['resource:item']},
+ 'schema:url': {'property_id': 393, 'type': ['uri']},
+ 'schema:identifier': {'property_id': 190,
+  'type': ['literal', 'uri', 'resource:item']},
+ 'schema:sameAs': {'property_id': 246, 'type': ['uri']},
+ 'schema:text': {'property_id': 833,
+  'type': ['literal', 'uri', 'resource:item']}}
+
+ +
+ +
+
+ +
+ {% endraw %} + +
+
+

This time we'll build our data file in stages.

+ +
+
+
+ {% raw %} + +
+
+ +
+
+
article = {}
+
+# Add value for schema:name
+article['schema:name'] = ["MR WRAGGE'S PREDICTION. RENEWAL OF CYCLONE FORETOLD."]
+
+ +
+
+
+ +
+ {% endraw %} + +
+
+

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.

+ +
+
+
+ {% raw %} + +
+
+ +
+
+
article['schema:isPartOf'] = [newspaper_item['o:id']]
+
+ +
+
+
+ +
+ {% endraw %} + +
+
+

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'.

+ +
+
+
+ {% raw %} + +
+
+ +
+
+
article['schema:datePublished'] = ['1918-03-14']
+
+ +
+
+
+ +
+ {% endraw %} + +
+
+

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.

+ +
+
+
+ {% raw %} + +
+
+ +
+
+
article['schema:url'] = ['http://nla.gov.au/nla.news-article226799674']
+
+ +
+
+
+ +
+ {% endraw %} + +
+
+

Now that we've assembled the article metadata, we can generate a payload.

+ +
+
+
+ {% raw %} + +
+
+ +
+
+
article_payload = omeka_auth.prepare_item_payload_using_template(article, article_template_id)
+
+ +
+
+
+ +
+ {% endraw %} + +
+
+

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.

+ +
+
+
+ {% raw %} + +
+
+ +
+
+
article_payload
+
+ +
+
+
+ +
+
+ +
+ + + +
+
{'schema:name': [{'property_id': 1116,
+   'type': 'literal',
+   '@value': "MR WRAGGE'S PREDICTION. RENEWAL OF CYCLONE FORETOLD."}],
+ 'schema:datePublished': [{'property_id': 928,
+   'type': 'numeric:timestamp',
+   '@value': '1918-03-14'}],
+ 'schema:isPartOf': [{'property_id': 736,
+   'type': 'resource:item',
+   '@id': 'http://timsherratt.org/collections/api/items/712',
+   'value_resource_id': 712,
+   'value_resource_name': 'items'}],
+ 'schema:url': [{'property_id': 393,
+   'type': 'uri',
+   '@id': 'http://nla.gov.au/nla.news-article226799674'}]}
+
+ +
+ +
+
+ +
+ {% endraw %} + +
+
+

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.

+ +
+
+
+ {% raw %} + +
+
+ +
+
+
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)
+
+ +
+
+
+ +
+ {% endraw %} + +
+
+

When we look at the uploaded item, we'll see that the media files have been processed and added to the record.

+ +
+
+
+ {% raw %} + +
+
+ +
+
+
article_item
+
+ +
+
+
+ +
+
+ +
+ + + +
+
{'@context': 'http://timsherratt.org/collections/api-context',
+ '@id': 'http://timsherratt.org/collections/api/items/714',
+ '@type': 'o:Item',
+ 'o:id': 714,
+ 'o:is_public': True,
+ 'o:owner': {'@id': 'http://timsherratt.org/collections/api/users/1',
+  'o:id': 1},
+ 'o:resource_class': None,
+ 'o:resource_template': {'@id': 'http://timsherratt.org/collections/api/resource_templates/4',
+  'o:id': 4},
+ 'o:thumbnail': None,
+ 'o:title': "MR WRAGGE'S PREDICTION. RENEWAL OF CYCLONE FORETOLD.",
+ 'thumbnail_display_urls': {'large': 'http://timsherratt.org/collections/files/large/1ba05f8df5df5f0003a4f4940aed209f0df53008.jpg',
+  'medium': 'http://timsherratt.org/collections/files/medium/1ba05f8df5df5f0003a4f4940aed209f0df53008.jpg',
+  'square': 'http://timsherratt.org/collections/files/square/1ba05f8df5df5f0003a4f4940aed209f0df53008.jpg'},
+ 'o:created': {'@value': '2022-01-26T01:56:28+00:00',
+  '@type': 'http://www.w3.org/2001/XMLSchema#dateTime'},
+ 'o:modified': {'@value': '2022-01-26T01:56:28+00:00',
+  '@type': 'http://www.w3.org/2001/XMLSchema#dateTime'},
+ 'o:media': [{'@id': 'http://timsherratt.org/collections/api/media/715',
+   'o:id': 715}],
+ 'o:item_set': [],
+ 'o:site': [],
+ 'schema:name': [{'type': 'literal',
+   'property_id': 1116,
+   'property_label': 'name',
+   'is_public': True,
+   '@value': "MR WRAGGE'S PREDICTION. RENEWAL OF CYCLONE FORETOLD."}],
+ 'schema:datePublished': [{'type': 'numeric:timestamp',
+   'property_id': 928,
+   'property_label': 'datePublished',
+   'is_public': True,
+   '@value': '1918-03-14',
+   '@type': 'http://www.w3.org/2001/XMLSchema#date'}],
+ 'schema:isPartOf': [{'type': 'resource:item',
+   'property_id': 736,
+   'property_label': 'isPartOf',
+   'is_public': True,
+   '@id': 'http://timsherratt.org/collections/api/items/712',
+   'value_resource_id': 712,
+   'value_resource_name': 'items',
+   'url': None,
+   'display_title': 'The Bendigo Independent (Vic. : 1891 - 1918)'}],
+ 'schema:url': [{'type': 'uri',
+   'property_id': 393,
+   'property_label': 'url',
+   'is_public': True,
+   '@id': 'http://nla.gov.au/nla.news-article226799674'}]}
+
+ +
+ +
+
+ +
+ {% endraw %} + +
+ + diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..efdf89f --- /dev/null +++ b/docs/index.html @@ -0,0 +1,100 @@ +--- + +title: Project name here + + +keywords: fastai +sidebar: home_sidebar + +summary: "Summary description here." +description: "Summary description here." +nb_path: "index.ipynb" +--- + + +
+ + {% raw %} + +
+ +
+ {% endraw %} + +
+
+

This file will become your README and also the index of your documentation.

+ +
+
+
+
+
+

Install

+
+
+
+
+
+

pip install your_project_name

+ +
+
+
+
+
+

How to use

+
+
+
+
+
+

Fill me in please! Don't forget code examples:

+ +
+
+
+ {% raw %} + +
+
+ +
+
+
1+1
+
+ +
+
+
+ +
+
+ +
+ + + +
+
2
+
+ +
+ +
+
+ +
+ {% endraw %} + +
+ + diff --git a/docs/sidebar.json b/docs/sidebar.json new file mode 100644 index 0000000..54e8b7a --- /dev/null +++ b/docs/sidebar.json @@ -0,0 +1,6 @@ +{ + "omeka_s_tools": { + "Overview": "/", + "Omeka S API client": "api.html" + } +} \ No newline at end of file diff --git a/index.ipynb b/index.ipynb index 31356b0..b9abe03 100644 --- a/index.ipynb +++ b/index.ipynb @@ -7,16 +7,16 @@ "outputs": [], "source": [ "#hide\n", - "from your_lib.core import *" + "from omeka_s_tools.api import OmekaAPIClient" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "# Project name here\n", + "# Omeka S Tools\n", "\n", - "> Summary description here." + "> Tools for working with data in an instance of Omeka S" ] }, { @@ -48,10 +48,33 @@ ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "omeka = OmekaAPIClient('http://timsherratt.org/collections/api')" + ] + }, + { + "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "49" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "Fill me in please! Don't forget code examples:" + "items = omeka.get_resources('items')\n", + "items['total_results']" ] }, { @@ -62,7 +85,42 @@ { "data": { "text/plain": [ - "2" + "{'@context': 'http://timsherratt.org/collections/api-context',\n", + " '@id': 'http://timsherratt.org/collections/api/items/671',\n", + " '@type': ['o:Item', 'schema:Newspaper'],\n", + " 'o:id': 671,\n", + " 'o:is_public': True,\n", + " 'o:owner': {'@id': 'http://timsherratt.org/collections/api/users/1',\n", + " 'o:id': 1},\n", + " 'o:resource_class': {'@id': 'http://timsherratt.org/collections/api/resource_classes/161',\n", + " 'o:id': 161},\n", + " 'o:resource_template': {'@id': 'http://timsherratt.org/collections/api/resource_templates/5',\n", + " 'o:id': 5},\n", + " 'o:thumbnail': None,\n", + " 'o:title': \"Newcastle Morning Herald and Miners' Advocate (NSW : 1876 - 1954)\",\n", + " 'thumbnail_display_urls': {'large': None, 'medium': None, 'square': None},\n", + " 'o:created': {'@value': '2022-01-20T06:36:11+00:00',\n", + " '@type': 'http://www.w3.org/2001/XMLSchema#dateTime'},\n", + " 'o:modified': {'@value': '2022-01-20T06:36:11+00:00',\n", + " '@type': 'http://www.w3.org/2001/XMLSchema#dateTime'},\n", + " 'o:media': [],\n", + " 'o:item_set': [],\n", + " 'o:site': [],\n", + " 'schema:name': [{'type': 'literal',\n", + " 'property_id': 1116,\n", + " 'property_label': 'name',\n", + " 'is_public': True,\n", + " '@value': \"Newcastle Morning Herald and Miners' Advocate (NSW : 1876 - 1954)\"}],\n", + " 'schema:url': [{'type': 'uri',\n", + " 'property_id': 393,\n", + " 'property_label': 'url',\n", + " 'is_public': True,\n", + " '@id': 'http://nla.gov.au/nla.news-title356'}],\n", + " 'schema:identifier': [{'type': 'literal',\n", + " 'property_id': 190,\n", + " 'property_label': 'identifier',\n", + " 'is_public': True,\n", + " '@value': '356'}]}" ] }, "execution_count": null, @@ -71,7 +129,14 @@ } ], "source": [ - "1+1" + "items['results'][0]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "See the documentation for more examples." ] }, { @@ -84,11 +149,11 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } diff --git a/media/nla.news-article226799674-24144902.jpg b/media/nla.news-article226799674-24144902.jpg new file mode 100644 index 0000000..e2a0349 Binary files /dev/null and b/media/nla.news-article226799674-24144902.jpg differ diff --git a/omeka_s_tools/__init__.py b/omeka_s_tools/__init__.py new file mode 100644 index 0000000..3047ba3 --- /dev/null +++ b/omeka_s_tools/__init__.py @@ -0,0 +1 @@ +__version__ = "0.0.1a" diff --git a/omeka_s_tools/_nbdev.py b/omeka_s_tools/_nbdev.py new file mode 100644 index 0000000..6c56d75 --- /dev/null +++ b/omeka_s_tools/_nbdev.py @@ -0,0 +1,13 @@ +# AUTOGENERATED BY NBDEV! DO NOT EDIT! + +__all__ = ["index", "modules", "custom_doc_links", "git_url"] + +index = {"OmekaAPIClient": "api.ipynb"} + +modules = ["api.py"] + +doc_url = "https://wragge.github.io/omeka_s_tools/" + +git_url = "https://github.com/wragge/omeka_s_tools/tree/master/" + +def custom_doc_links(name): return None diff --git a/omeka_s_tools/api.py b/omeka_s_tools/api.py new file mode 100644 index 0000000..85be8b6 --- /dev/null +++ b/omeka_s_tools/api.py @@ -0,0 +1,409 @@ +# AUTOGENERATED! DO NOT EDIT! File to edit: api.ipynb (unless otherwise specified). + +__all__ = ['OmekaAPIClient'] + +# Cell +import requests +import requests_cache +import json +from requests.adapters import HTTPAdapter +from requests.packages.urllib3.util.retry import Retry +from pathlib import Path + +class OmekaAPIClient(object): + + def __init__(self, api_url, key_identity=None, key_credential=None, use_cache=True): + self.api_url = api_url + self.params = { + 'key_identity': key_identity, + 'key_credential': key_credential + } + # Set up session and caching + if use_cache: + self.s = requests_cache.CachedSession() + self.s.cache.clear() + else: + self.s = requests.Session() + retries = Retry(total=10, backoff_factor=1, status_forcelist=[ 502, 503, 504, 524 ]) + self.s.mount('http://', HTTPAdapter(max_retries=retries)) + self.s.mount('https://', HTTPAdapter(max_retries=retries)) + + + def format_resource_id(self, resource_id, resource_type): + ''' + Generate a formatted id for the resource with the specified Omeka id number and resource type. + + Parameters: + * `resource_id` - numeric identifier used by Omeka for this resource + * `resource_type` - one of Omeka's resource types, eg: 'items', 'properties' + + Returns: + * a dict with values for '@id' and 'o:id' + ''' + formatted_id = { + '@id': f'{self.api_url}/{resource_type}/{resource_id}', + 'o:id': resource_id + } + return formatted_id + + def get_resources(self, resource_type, **kwargs): + ''' + Get a list of resources matching the supplied parameters. + This will return the first page of matching results. To retrieve additional pages, + you can supply the `page` parameter to move through the full result set. + + Parameters: + * `resource_type` - one of Omeka's resource types, eg: 'items', 'properties' + * there are many additional parameters you can supply as kwargs, see the Omeka documention + + Returns a dict with the following values: + * `total_results` - number of matching resources + * `results` - a list of dicts, each containing a JSON-LD formatted representation of a resource + ''' + response = self.s.get(f'{self.api_url}/{resource_type}/', params=kwargs) + results = response.json() + return {'total_results': int(response.headers['Omeka-S-Total-Results']), 'results': results} + + def get_resource(self, resource_type, **kwargs): + ''' + Get the first resource matching the supplied parameters. + + Parameters: + * `resource_type` - one of Omeka's resource types, eg: 'items', 'properties' + * there are many additional parameters you can supply as kwargs, see the Omeka documention + + Returns + * a dict containing a JSON-LD formatted representation of the resource + ''' + + data = self.get_resources(resource_type, **kwargs) + try: + resource = data['results'][0] + except IndexError: + return + else: + return resource + + def get_resource_by_id(self, resource_id, resource_type='items'): + ''' + Get a resource from its Omeka id. + + Parameters: + * `resource_id` - numeric identifier used by Omeka for this resource + * `resource_type` - one of Omeka's resource types, eg: 'items', 'properties' + + Returns + * a dict containing a JSON-LD formatted representation of the resource + ''' + response = self.s.get(f'{self.api_url}/{resource_type}/{resource_id}') + return response.json() + + def get_template_by_label(self, label): + ''' + Get a resource template from its Omeka label. + + Parameters: + * `label` - the name of the resource template in Omeka (eg. 'NewspaperArticle') + + Returns: + * dict containing representation of the template + ''' + return self.get_resource('resource_templates', label=label) + + def get_resource_by_term(self, term, resource_type='properties'): + ''' + Get the resource (property or class) associated with the suppied term. + + Parameters: + * `term` - property label qualified with vocabulary prefix (eg: 'schema:name') + + Returns: + * dict containing representation of the resource + ''' + return self.get_resource(resource_type, term=term) + + def get_resource_from_vocab(self, local_name, vocabulary_namespace_uri='http://schema.org/', resource_type='properties'): + ''' + Get the resource (property or class) associated with the suppied vocabulary and label. + + Parameters: + * `local_name` - label of the property or class + * `vocabulary_namespace_uri` - URI defining the vocab + + Returns: + * dict containing representation of the resource + ''' + return self.get_resource(resource_type, local_name=local_name, vocabulary_namespace_uri=vocabulary_namespace_uri) + + def get_property_id(self, term): + ''' + Get the numeric identifier associated with the supplied property term. + + Parameters: + * `term` - property label qualified with vocabulary prefix (eg: 'schema:name') + + Returns: + * numeric identifier + ''' + resource = self.get_resource_by_term(term=term) + if resource: + return resource['o:id'] + + def filter_items(self, params, **extra_filters): + for filter_type in ['resource_template_id', 'resource_class_id', 'item_set_id', 'is_public']: + filter_value = extra_filters.get(filter_type) + if filter_value: + params[filter_type] = filter_value + return params + + def filter_items_by_property(self, filter_property='schema:name', filter_value='', filter_type='eq', page=1, **extra_filters): + ''' + Filter the list of items by searching for a value in a particular property. + Additional filters can also limit to items associated with particular templates, classes, or item sets. + + Parameters: + * `filter_property` - property term (eg: 'schema:name') + * `filter_value` - the value you want to find + * `filter_type` - how `filter_value` should be compared to the stored values (eg: 'eq') + * `page` - number of results page + + Additional parameters: + * `resource_template_id` - numeric identifier + * `resource_class_id` - numeric identifier + * `item_set_id` - numeric identifier + * `is_public` - boolean, True or False + + Returns a dict with the following values: + * `total_results` - number of matching resources + * `results` - a list of dicts, each containing a JSON-LD formatted representation of a resource + + ''' + # We need to get the id of the property we're using + property_id = self.get_property_id(filter_property) + params = { + 'property[0][joiner]': 'and', # and / or joins multiple property searches + 'property[0][property]': property_id, # property id + 'property[0][type]': filter_type, # See above for options + 'property[0][text]': filter_value, + 'page': page + } + params = self.filter_items(params, **extra_filters) + # print(params) + data = self.get_resources('items', **params) + return data + + def search_items(self, query, search_type='fulltext_search', page=1, **extra_filters): + ''' + Search for matching items. + Two search types are available: + * 'search` - looks for an exact match of the query in a property value + * 'fulltext_search` - looks for the occurance of the query anywhere + + Parameters: + * `query` - the text you want to search for + * `search_type` - one of 'fulltext_search' or 'search' + * `page` - number of results page + + Additional parameters: + * `resource_template_id` - numeric identifier + * `resource_class_id` - numeric identifier + * `item_set_id` - numeric identifier + * `is_public` - boolean, True or False + + Returns a dict with the following values: + * `total_results` - number of matching resources + * `results` - a list of dicts, each containing a JSON-LD formatted representation of a resource + ''' + params = {'page': page} + params[search_type] = query + params = self.filter_items(params, **extra_filters) + return self.get_resources('items', **params) + + def get_template_properties(self, template_id): + ''' + List properties used by the specified template. + + The resource template objects returned by the API don't include property terms. + This function gets the additional details, and organises the properties in a dictionary, + organised by term. This makes it easy to check if a particular term is used by a template. + + Parameters: + * `template_id` - numeric identifier for a template + + Returns: + * a dict organised by property terms, with values for `property_id` and `type` + ''' + properties = {} + template = self.get_resource_by_id(template_id, 'resource_templates') + for prop in template['o:resource_template_property']: + prop_url = prop['o:property']['@id'] + # The resource template doesn't include property terms, so we have to go to the property data + response = self.s.get(prop_url) + data = response.json() + # Use default data types if they're not defined in the resource template + data_type = ['literal', 'uri', 'resource:item'] if prop['o:data_type'] == [] else prop['o:data_type'] + properties[data['o:term']] = {'property_id': data['o:id'], 'type': data_type} + return properties + + def prepare_property_value(self, value, property_id): + ''' + Formats a property value according to its datatype as expected by Omeka. + The formatted value can be used in a payload to create a new item. + + Parameters: + * `value` - a dict containing a `value` and (optionally) a `type` + * `property_id` - the numeric identifier of the property + + Note that is no `type` is supplied, 'literal' will be used by default. + + Returns: + * a dict with values for `property_id`, `type`, and either `@id` or `@value`. + ''' + if not isinstance(value, dict): + value = {'value': value} + + try: + data_type = value['type'] + except KeyError: + data_type = 'literal' + + property_value = { + 'property_id': property_id, + 'type': data_type + } + + if data_type == 'resource:item': + property_value['@id'] = f'{self.api_url}/items/{value["value"]}' + property_value['value_resource_id'] = value['value'] + property_value['value_resource_name'] = 'items' + elif data_type == 'uri': + property_value['@id'] = value['value'] + else: + property_value['@value'] = value['value'] + return property_value + + def add_item(self, payload, media_files=None, template_id=None, class_id=None, item_set_id=None): + ''' + Create a new item from the supplied payload, optionally uploading attached media files. + + Parameters: + * `payload` - a dict generated by `prepare_item_payload()` or `prepare_item_payload_using_template()` + * `media_files` - a list of paths pointing to media files, or a list of dicts with `path` and `title` values + * `template_id` - internal Omeka identifier of a resource template you want to attach to this item + * `class_id` - internal Omeka identifier of a resource class you want to attach to this item + * `item_set_id` - internal Omeka identifier for an item set you want to add this item to + + Returns: + * a dict providing the JSON-LD representation of the new item from Omeka + ''' + if template_id: + payload['o:resource_template'] = self.format_resource_id(template_id, 'resource_templates') + if class_id: + payload['o:resource_class'] = self.format_resource_id(class_id, 'resource_classes') + if item_set_id: + payload['o:item_set'] = self.format_resource_id(template_id, 'item_sets') + if media_files: + files = self.add_media_to_payload(payload, media_files) + response = self.s.post(f'{self.api_url}/items', files=files, params=self.params) + else: + response = self.s.post(f'{self.api_url}/items', json=payload, params=self.params) + return response.json() + + def prepare_item_payload(self, terms): + ''' + Prepare an item payload, ready for upload. + + Parameters: + * `terms`: a dict of terms, values, and (optionally) data types + + Returns: + * the payload dict + ''' + payload = {} + for term, values in terms.items(): + # Get the property id of the supplied term + try: + property_id = self.get_property_id(term) + except IndexError: + print(f'Term "{term}" not found') + else: + payload[term] = [] + for value in values: + # Add a value formatted according to the data type + payload[term].append(self.prepare_property_value(value, property_id)) + return payload + + def prepare_item_payload_using_template(self, terms, template_id): + ''' + Prepare an item payload, checking the supplied terms and values against the specified template. + Note: + * terms that are not in the template will generate a warning and be dropped from the payload + * data types that don't match the template definitions will generate a warning and the term will be dropped from the payload + * if no data type is supplied, a type that conforms with the template definition will be used + + Parameters: + * `terms`: a dict of terms, values, and (optionally) data types + * `template_id`: Omeka's internal numeric identifier for the template + + Returns: + * the payload dict + ''' + template_properties = self.get_template_properties(template_id) + payload = {} + for term, values in terms.items(): + if term in template_properties: + property_details = template_properties[term] + payload[term] = [] + for value in values: + if not isinstance(value, dict): + value = {'value': value} + # The supplied data type doesn't match the template + if 'type' in value and value['type'] not in property_details['type']: + print(f'Data type "{value["type"]}" for term "{term}" not allowed by template') + break + elif 'type' not in value: + # Use default datatype from template if none is supplied + if len(property_details['type']) == 1: + value['type'] = property_details['type'][0] + # Use literal if allowed by template and data type not supplied + elif 'literal' in property_details['type']: + value['type'] = 'literal' + # Don't know what data type to use + else: + print(f'Specify data type for term "{term}"') + break + # Add a value formatted according to the data type + payload[term].append(self.prepare_property_value(value, property_details['property_id'])) + # The supplied term is not in the template + else: + print(f'Term {term} not in template') + return payload + + def add_media_to_payload(self, payload, media_files): + ''' + Add media files to the item payload. + + Parameters: + * `payload` - the payload dict to be modified + * `media_files` - media files to be uploaded + + The value of `media_files` can be either: + * a list of paths to the image/media files (filename is used as title) + * a list of dicts, each containing `title`, and `path` values + + Returns: + * the modified payload dict + ''' + payload['o:media'] = [] + files = {} + for index, media_file in enumerate(media_files): + if isinstance(media_file, dict): + title = media_file['title'] + path = media_file['path'] + else: + title = media_file[:-4] + path = media_file + payload['o:media'].append({'o:ingester': 'upload', 'file_index': str(index), 'o:item': {}, 'dcterms:title': [{'property_id': 1, '@value': title, 'type': 'literal'}]}) + files[f'file[{index}]'] = Path(path).read_bytes() + files['data'] = (None, json.dumps(payload), 'application/json') + return files \ No newline at end of file diff --git a/settings.ini b/settings.ini index 959a6d8..110d01d 100644 --- a/settings.ini +++ b/settings.ini @@ -1,31 +1,31 @@ [DEFAULT] # All sections below are required unless otherwise specified host = github -lib_name = {lib_name} +lib_name = omeka_s_tools # For Enterprise Git add variable repo_name and company name -# repo_name = analytics +repo_name = omeka_s_tools # company_name = nike -user = {user} -# description = A description of your project -# keywords = some keywords -author = {author} -author_email = {author_email} -# copyright = Put your copyright information here -branch = {branch} -version = 0.0.1 +user = wragge +description = Tools for working with Omeka S API +keywords = Omeka +author = Tim Sherratt +author_email = tim@timsherratt.org +copyright = Tim Sherratt +branch = master +version = 0.0.1a min_python = 3.6 audience = Developers language = English # Set to True if you want to create a more fancy sidebar.json than the default custom_sidebar = False # Add licenses and see current list in `setup.py` -license = apache2 +license = mit # From 1-7: Planning Pre-Alpha Alpha Beta Production Mature Inactive -status = 2 +status = 3 # Optional. Same format as setuptools requirements -# requirements = +requirements = requests requests-cache # Optional. Same format as setuptools console_scripts # console_scripts = # Optional. Same format as setuptools dependency-links